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>
This commit is contained in:
root
2026-05-02 22:52:43 +00:00
parent 487b4c42c4
commit 832512469a
6 changed files with 474 additions and 52 deletions

View File

@@ -11,25 +11,34 @@ 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, title, url, description, icon, category string, sortOrder int) (*domain.Bookmark, error) {
if title == "" || url == "" {
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 category == "" {
category = "默认"
if in.Category == "" {
in.Category = "默认"
}
bm := &domain.Bookmark{
UserID: userID,
Title: title,
URL: url,
Description: description,
Icon: icon,
Category: category,
SortOrder: sortOrder,
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
@@ -37,6 +46,32 @@ func (s *BookmarkService) Create(userID uint, title, url, description, icon, cat
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)
}
@@ -49,7 +84,7 @@ 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) {
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")
@@ -57,18 +92,18 @@ func (s *BookmarkService) Update(userID, bookmarkID uint, title, url, descriptio
if bm.UserID != userID {
return nil, errors.New("forbidden")
}
if title != "" {
bm.Title = title
if in.Title != "" {
bm.Title = in.Title
}
if url != "" {
bm.URL = url
if in.URL != "" {
bm.URL = in.URL
}
bm.Description = description
bm.Icon = icon
if category != "" {
bm.Category = category
bm.Description = in.Description
bm.Icon = in.Icon
if in.Category != "" {
bm.Category = in.Category
}
bm.SortOrder = sortOrder
bm.SortOrder = in.SortOrder
if err := s.repo.Update(bm); err != nil {
return nil, err
}
@@ -85,3 +120,28 @@ func (s *BookmarkService) Delete(userID, bookmarkID uint) error {
}
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)
}