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:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user