backend: add bookmark CRUD with public list endpoint
New bookmarks feature: domain model, repository, service and handler supporting list/create/update/delete. Public endpoint exposes the admin user's bookmarks for the homepage navigation grid; authenticated endpoints scope by user. Dev Dockerfile drops air for plain `go run` and uses goproxy.cn to avoid build failures on the deploy host. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
160
backend/internal/handler/bookmark.go
Normal file
160
backend/internal/handler/bookmark.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"evanpage-backend/internal/domain"
|
||||
"evanpage-backend/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BookmarkHandler struct {
|
||||
bookmarkService *service.BookmarkService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
func NewBookmarkHandler(bookmarkService *service.BookmarkService, userService *service.UserService) *BookmarkHandler {
|
||||
return &BookmarkHandler{bookmarkService: bookmarkService, userService: userService}
|
||||
}
|
||||
|
||||
func getUserID(c *gin.Context) uint {
|
||||
uid, _ := c.Get("userID")
|
||||
id, _ := strconv.ParseUint(uid.(string), 10, 64)
|
||||
return uint(id)
|
||||
}
|
||||
|
||||
func (h *BookmarkHandler) Create(c *gin.Context) {
|
||||
var req struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
URL string `json:"url" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Category string `json:"category"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
userID := getUserID(c)
|
||||
bm, err := h.bookmarkService.Create(userID, req.Title, req.URL, req.Description, req.Icon, req.Category, req.SortOrder)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, bm)
|
||||
}
|
||||
|
||||
func (h *BookmarkHandler) List(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
category := c.Query("category")
|
||||
|
||||
var bms []domain.Bookmark
|
||||
var err error
|
||||
|
||||
if category != "" {
|
||||
bms, err = h.bookmarkService.ListByUserAndCategory(userID, category)
|
||||
} else {
|
||||
bms, err = h.bookmarkService.ListByUser(userID)
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
categories, _ := h.bookmarkService.ListCategoriesByUser(userID)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"bookmarks": bms,
|
||||
"categories": categories,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *BookmarkHandler) PublicList(c *gin.Context) {
|
||||
admin, err := h.userService.FindByRole("admin")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"bookmarks": []domain.Bookmark{},
|
||||
"categories": []string{},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
category := c.Query("category")
|
||||
var bms []domain.Bookmark
|
||||
if category != "" {
|
||||
bms, err = h.bookmarkService.ListByUserAndCategory(admin.ID, category)
|
||||
} else {
|
||||
bms, err = h.bookmarkService.ListByUser(admin.ID)
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
categories, _ := h.bookmarkService.ListCategoriesByUser(admin.ID)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"bookmarks": bms,
|
||||
"categories": categories,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *BookmarkHandler) Update(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Category string `json:"category"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
userID := getUserID(c)
|
||||
bm, err := h.bookmarkService.Update(userID, uint(id), req.Title, req.URL, req.Description, req.Icon, req.Category, req.SortOrder)
|
||||
if err != nil {
|
||||
if err.Error() == "forbidden" {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, bm)
|
||||
}
|
||||
|
||||
func (h *BookmarkHandler) Delete(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
userID := getUserID(c)
|
||||
if err := h.bookmarkService.Delete(userID, uint(id)); err != nil {
|
||||
if err.Error() == "forbidden" {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusNoContent, nil)
|
||||
}
|
||||
Reference in New Issue
Block a user