Files
evanpage/backend/internal/service/bookmark.go
root 832512469a backend: expand bookmark API with bulk ops and metadata fetcher
- bulk create/delete/move, reorder, rename-category endpoints
- /bookmarks/meta with SSRF-safe fetcher (blocks private/loopback IPs,
  8s timeout, 1 MiB body cap)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 22:52:43 +00:00

148 lines
3.5 KiB
Go

package service
import (
"errors"
"evanpage-backend/internal/domain"
"evanpage-backend/internal/repository"
)
type BookmarkService struct {
repo *repository.BookmarkRepository
}
type BookmarkInput struct {
Title string
URL string
Description string
Icon string
Category string
SortOrder int
}
func NewBookmarkService(repo *repository.BookmarkRepository) *BookmarkService {
return &BookmarkService{repo: repo}
}
func (s *BookmarkService) Create(userID uint, in BookmarkInput) (*domain.Bookmark, error) {
if in.Title == "" || in.URL == "" {
return nil, errors.New("title and url are required")
}
if in.Category == "" {
in.Category = "默认"
}
bm := &domain.Bookmark{
UserID: userID,
Title: in.Title,
URL: in.URL,
Description: in.Description,
Icon: in.Icon,
Category: in.Category,
SortOrder: in.SortOrder,
}
if err := s.repo.Create(bm); err != nil {
return nil, err
}
return bm, nil
}
func (s *BookmarkService) BulkCreate(userID uint, items []BookmarkInput) (int, error) {
bms := make([]*domain.Bookmark, 0, len(items))
for _, in := range items {
if in.Title == "" || in.URL == "" {
continue
}
cat := in.Category
if cat == "" {
cat = "默认"
}
bms = append(bms, &domain.Bookmark{
UserID: userID,
Title: in.Title,
URL: in.URL,
Description: in.Description,
Icon: in.Icon,
Category: cat,
SortOrder: in.SortOrder,
})
}
if err := s.repo.BulkCreate(bms); err != nil {
return 0, err
}
return len(bms), 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, in BookmarkInput) (*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 in.Title != "" {
bm.Title = in.Title
}
if in.URL != "" {
bm.URL = in.URL
}
bm.Description = in.Description
bm.Icon = in.Icon
if in.Category != "" {
bm.Category = in.Category
}
bm.SortOrder = in.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)
}
func (s *BookmarkService) BulkDelete(userID uint, ids []uint) (int64, error) {
return s.repo.BulkDeleteByUser(userID, ids)
}
func (s *BookmarkService) BulkUpdateCategory(userID uint, ids []uint, category string) (int64, error) {
if category == "" {
category = "默认"
}
return s.repo.BulkUpdateCategoryByUser(userID, ids, category)
}
func (s *BookmarkService) RenameCategory(userID uint, from, to string) (int64, error) {
if from == "" || to == "" {
return 0, errors.New("from and to are required")
}
if from == to {
return 0, nil
}
return s.repo.RenameCategoryByUser(userID, from, to)
}
func (s *BookmarkService) Reorder(userID uint, orderedIDs []uint) error {
return s.repo.ReorderByUser(userID, orderedIDs)
}