feat: add categories service with CRUD and slug validation
This commit is contained in:
parent
15bc6b7f73
commit
3c80f63a55
162
services/categories/service.go
Normal file
162
services/categories/service.go
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
package categories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
"lmika.dev/lmika/weiro/providers/db"
|
||||
"lmika.dev/lmika/weiro/services/publisher"
|
||||
)
|
||||
|
||||
type CreateCategoryParams struct {
|
||||
GUID string `form:"guid" json:"guid"`
|
||||
Name string `form:"name" json:"name"`
|
||||
Slug string `form:"slug" json:"slug"`
|
||||
Description string `form:"description" json:"description"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
db *db.Provider
|
||||
publisher *publisher.Queue
|
||||
}
|
||||
|
||||
func New(db *db.Provider, publisher *publisher.Queue) *Service {
|
||||
return &Service{db: db, publisher: publisher}
|
||||
}
|
||||
|
||||
func (s *Service) ListCategories(ctx context.Context) ([]*models.Category, error) {
|
||||
site, ok := models.GetSite(ctx)
|
||||
if !ok {
|
||||
return nil, models.SiteRequiredError
|
||||
}
|
||||
return s.db.SelectCategoriesOfSite(ctx, site.ID)
|
||||
}
|
||||
|
||||
// ListCategoriesWithCounts returns all categories for the site with published post counts.
|
||||
func (s *Service) ListCategoriesWithCounts(ctx context.Context) ([]models.CategoryWithCount, error) {
|
||||
site, ok := models.GetSite(ctx)
|
||||
if !ok {
|
||||
return nil, models.SiteRequiredError
|
||||
}
|
||||
|
||||
cats, err := s.db.SelectCategoriesOfSite(ctx, site.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]models.CategoryWithCount, len(cats))
|
||||
for i, cat := range cats {
|
||||
count, err := s.db.CountPostsOfCategory(ctx, cat.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[i] = models.CategoryWithCount{
|
||||
Category: *cat,
|
||||
PostCount: int(count),
|
||||
DescriptionBrief: models.BriefDescription(cat.Description),
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetCategory(ctx context.Context, id int64) (*models.Category, error) {
|
||||
return s.db.SelectCategory(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) CreateCategory(ctx context.Context, params CreateCategoryParams) (*models.Category, error) {
|
||||
site, ok := models.GetSite(ctx)
|
||||
if !ok {
|
||||
return nil, models.SiteRequiredError
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
slug := params.Slug
|
||||
if slug == "" {
|
||||
slug = models.GenerateCategorySlug(params.Name)
|
||||
}
|
||||
|
||||
// Check for slug collision
|
||||
if _, err := s.db.SelectCategoryBySlugAndSite(ctx, site.ID, slug); err == nil {
|
||||
return nil, models.SlugConflictError
|
||||
}
|
||||
|
||||
cat := &models.Category{
|
||||
SiteID: site.ID,
|
||||
GUID: params.GUID,
|
||||
Name: params.Name,
|
||||
Slug: slug,
|
||||
Description: params.Description,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if cat.GUID == "" {
|
||||
cat.GUID = models.NewNanoID()
|
||||
}
|
||||
|
||||
if err := s.db.SaveCategory(ctx, cat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.publisher.Queue(site)
|
||||
return cat, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateCategory(ctx context.Context, id int64, params CreateCategoryParams) (*models.Category, error) {
|
||||
site, ok := models.GetSite(ctx)
|
||||
if !ok {
|
||||
return nil, models.SiteRequiredError
|
||||
}
|
||||
|
||||
cat, err := s.db.SelectCategory(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cat.SiteID != site.ID {
|
||||
return nil, models.NotFoundError
|
||||
}
|
||||
|
||||
slug := params.Slug
|
||||
if slug == "" {
|
||||
slug = models.GenerateCategorySlug(params.Name)
|
||||
}
|
||||
|
||||
// Check slug collision (exclude self)
|
||||
if existing, err := s.db.SelectCategoryBySlugAndSite(ctx, site.ID, slug); err == nil && existing.ID != cat.ID {
|
||||
return nil, models.SlugConflictError
|
||||
}
|
||||
|
||||
cat.Name = params.Name
|
||||
cat.Slug = slug
|
||||
cat.Description = params.Description
|
||||
cat.UpdatedAt = time.Now()
|
||||
|
||||
if err := s.db.SaveCategory(ctx, cat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.publisher.Queue(site)
|
||||
return cat, nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteCategory(ctx context.Context, id int64) error {
|
||||
site, ok := models.GetSite(ctx)
|
||||
if !ok {
|
||||
return models.SiteRequiredError
|
||||
}
|
||||
|
||||
cat, err := s.db.SelectCategory(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cat.SiteID != site.ID {
|
||||
return models.NotFoundError
|
||||
}
|
||||
|
||||
if err := s.db.DeleteCategory(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.publisher.Queue(site)
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in a new issue