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 }