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:
87
backend/internal/service/bookmark.go
Normal file
87
backend/internal/service/bookmark.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"evanpage-backend/internal/domain"
|
||||
"evanpage-backend/internal/repository"
|
||||
)
|
||||
|
||||
type BookmarkService struct {
|
||||
repo *repository.BookmarkRepository
|
||||
}
|
||||
|
||||
func NewBookmarkService(repo *repository.BookmarkRepository) *BookmarkService {
|
||||
return &BookmarkService{repo: repo}
|
||||
}
|
||||
|
||||
func (s *BookmarkService) Create(userID uint, title, url, description, icon, category string, sortOrder int) (*domain.Bookmark, error) {
|
||||
if title == "" || url == "" {
|
||||
return nil, errors.New("title and url are required")
|
||||
}
|
||||
if category == "" {
|
||||
category = "默认"
|
||||
}
|
||||
bm := &domain.Bookmark{
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
URL: url,
|
||||
Description: description,
|
||||
Icon: icon,
|
||||
Category: category,
|
||||
SortOrder: sortOrder,
|
||||
}
|
||||
if err := s.repo.Create(bm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bm, nil
|
||||
}
|
||||
|
||||
func (s *BookmarkService) ListByUser(userID uint) ([]domain.Bookmark, error) {
|
||||
return s.repo.ListByUser(userID)
|
||||
}
|
||||
|
||||
func (s *BookmarkService) ListByUserAndCategory(userID uint, category string) ([]domain.Bookmark, error) {
|
||||
return s.repo.ListByUserAndCategory(userID, category)
|
||||
}
|
||||
|
||||
func (s *BookmarkService) ListCategoriesByUser(userID uint) ([]string, error) {
|
||||
return s.repo.ListCategoriesByUser(userID)
|
||||
}
|
||||
|
||||
func (s *BookmarkService) Update(userID, bookmarkID uint, title, url, description, icon, category string, sortOrder int) (*domain.Bookmark, error) {
|
||||
bm, err := s.repo.FindByID(bookmarkID)
|
||||
if err != nil {
|
||||
return nil, errors.New("bookmark not found")
|
||||
}
|
||||
if bm.UserID != userID {
|
||||
return nil, errors.New("forbidden")
|
||||
}
|
||||
if title != "" {
|
||||
bm.Title = title
|
||||
}
|
||||
if url != "" {
|
||||
bm.URL = url
|
||||
}
|
||||
bm.Description = description
|
||||
bm.Icon = icon
|
||||
if category != "" {
|
||||
bm.Category = category
|
||||
}
|
||||
bm.SortOrder = sortOrder
|
||||
if err := s.repo.Update(bm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bm, nil
|
||||
}
|
||||
|
||||
func (s *BookmarkService) Delete(userID, bookmarkID uint) error {
|
||||
bm, err := s.repo.FindByID(bookmarkID)
|
||||
if err != nil {
|
||||
return errors.New("bookmark not found")
|
||||
}
|
||||
if bm.UserID != userID {
|
||||
return errors.New("forbidden")
|
||||
}
|
||||
return s.repo.Delete(bookmarkID)
|
||||
}
|
||||
@@ -93,6 +93,10 @@ func (s *UserService) BindKeycloak(username, password, keycloakID, keycloakEmail
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *UserService) FindByRole(role string) (*domain.User, error) {
|
||||
return s.repo.FindByRole(role)
|
||||
}
|
||||
|
||||
func (s *UserService) ListUsers() ([]domain.User, error) {
|
||||
return s.repo.ListAll()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user