164 lines
5.5 KiB
Go
164 lines
5.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
applogger "github.com/gochat/gochat/pkg/logger"
|
|
)
|
|
|
|
// CategoryService implements business logic for Category CRUD.
|
|
type CategoryService struct {
|
|
repo *repository.CategoryRepo
|
|
relatedRepo *repository.RelatedCategoryRepo
|
|
}
|
|
|
|
func NewCategoryService(repo *repository.CategoryRepo, relatedRepo *repository.RelatedCategoryRepo) *CategoryService {
|
|
return &CategoryService{repo: repo, relatedRepo: relatedRepo}
|
|
}
|
|
|
|
// CreateCategoryRequest is the DTO for creating a category.
|
|
type CreateCategoryRequest struct {
|
|
Name string `json:"name" validate:"required,min=2"`
|
|
Slug string `json:"slug" validate:"required"`
|
|
Description string `json:"description"`
|
|
Icon string `json:"icon"`
|
|
Position int `json:"position"`
|
|
Locale string `json:"locale"`
|
|
ParentID *uint `json:"parent_id"`
|
|
AssociatedCategoryID *uint `json:"associated_category_id"`
|
|
RelatedCategoryIDs []uint `json:"related_category_ids"`
|
|
CustomAttributes json.RawMessage `json:"custom_attributes"`
|
|
}
|
|
|
|
// UpdateCategoryRequest is the DTO for updating a category.
|
|
type UpdateCategoryRequest struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Icon string `json:"icon"`
|
|
Position *int `json:"position"`
|
|
Locale string `json:"locale"`
|
|
ParentID *uint `json:"parent_id"`
|
|
AssociatedCategoryID *uint `json:"associated_category_id"`
|
|
RelatedCategoryIDs []uint `json:"related_category_ids"`
|
|
CustomAttributes json.RawMessage `json:"custom_attributes"`
|
|
}
|
|
|
|
func (s *CategoryService) Create(ctx context.Context, portalID uint, accountID uint, req *CreateCategoryRequest) (*model.Category, error) {
|
|
category := &model.Category{
|
|
AccountID: accountID,
|
|
PortalID: portalID,
|
|
Name: req.Name,
|
|
Slug: req.Slug,
|
|
Description: req.Description,
|
|
Icon: req.Icon,
|
|
Position: req.Position,
|
|
Locale: req.Locale,
|
|
ParentID: req.ParentID,
|
|
AssociatedCategoryID: req.AssociatedCategoryID,
|
|
CustomAttributes: req.CustomAttributes,
|
|
}
|
|
|
|
if err := s.repo.Create(ctx, category); err != nil {
|
|
return nil, fmt.Errorf("create category: %w", err)
|
|
}
|
|
|
|
// Handle related categories
|
|
if len(req.RelatedCategoryIDs) > 0 {
|
|
if err := s.relatedRepo.ReplaceAll(ctx, category.ID, req.RelatedCategoryIDs); err != nil {
|
|
applogger.L().Warnf("Failed to set related categories for category %d: %v", category.ID, err)
|
|
}
|
|
}
|
|
|
|
return category, nil
|
|
}
|
|
|
|
func (s *CategoryService) GetByID(ctx context.Context, id uint) (*model.Category, error) {
|
|
category, err := s.repo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get category: %w", err)
|
|
}
|
|
return category, nil
|
|
}
|
|
|
|
func (s *CategoryService) Update(ctx context.Context, id uint, req *UpdateCategoryRequest) (*model.Category, error) {
|
|
category, err := s.repo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("find category: %w", err)
|
|
}
|
|
|
|
if req.Name != "" {
|
|
category.Name = req.Name
|
|
}
|
|
if req.Description != "" {
|
|
category.Description = req.Description
|
|
}
|
|
if req.Icon != "" {
|
|
category.Icon = req.Icon
|
|
}
|
|
if req.Position != nil {
|
|
category.Position = *req.Position
|
|
}
|
|
if req.Locale != "" {
|
|
category.Locale = req.Locale
|
|
}
|
|
if req.ParentID != nil {
|
|
category.ParentID = req.ParentID
|
|
}
|
|
if req.AssociatedCategoryID != nil {
|
|
category.AssociatedCategoryID = req.AssociatedCategoryID
|
|
}
|
|
if req.CustomAttributes != nil {
|
|
category.CustomAttributes = req.CustomAttributes
|
|
}
|
|
|
|
if err := s.repo.Update(ctx, category); err != nil {
|
|
return nil, fmt.Errorf("update category: %w", err)
|
|
}
|
|
|
|
// Handle related categories — replace all if provided
|
|
if req.RelatedCategoryIDs != nil {
|
|
if err := s.relatedRepo.ReplaceAll(ctx, category.ID, req.RelatedCategoryIDs); err != nil {
|
|
applogger.L().Warnf("Failed to update related categories for category %d: %v", category.ID, err)
|
|
}
|
|
}
|
|
|
|
return category, nil
|
|
}
|
|
|
|
func (s *CategoryService) Delete(ctx context.Context, id uint) error {
|
|
// Check existence first — GORM Delete() returns nil even for non-existent IDs
|
|
if _, err := s.repo.GetByID(ctx, id); err != nil {
|
|
return fmt.Errorf("find category for delete: %w", err)
|
|
}
|
|
|
|
// Clean up related categories first
|
|
if err := s.relatedRepo.DeleteByCategoryID(ctx, id); err != nil {
|
|
applogger.L().Warnf("Failed to delete related categories for category %d: %v", id, err)
|
|
}
|
|
|
|
if err := s.repo.Delete(ctx, id); err != nil {
|
|
return fmt.Errorf("delete category: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListByPortalID returns categories for a portal with pagination.
|
|
func (s *CategoryService) ListByPortalID(ctx context.Context, portalID uint, locale string, page, perPage int) ([]model.Category, int64, error) {
|
|
offset := (page - 1) * perPage
|
|
if locale != "" {
|
|
return s.repo.FindByPortalIDAndLocale(ctx, portalID, locale, offset, perPage)
|
|
}
|
|
return s.repo.FindByPortalID(ctx, portalID, offset, perPage)
|
|
}
|
|
|
|
// Reorder batch-updates category positions.
|
|
func (s *CategoryService) Reorder(ctx context.Context, positions map[uint]int) error {
|
|
if err := s.repo.UpdatePositions(ctx, positions); err != nil {
|
|
return fmt.Errorf("reorder categories: %w", err)
|
|
}
|
|
return nil
|
|
} |