// internal/services/category_service.go package services import ( "errors" "lost-and-found/internal/models" "lost-and-found/internal/repositories" "strings" "gorm.io/gorm" ) type CategoryService struct { categoryRepo *repositories.CategoryRepository auditLogRepo *repositories.AuditLogRepository } func NewCategoryService(db *gorm.DB) *CategoryService { return &CategoryService{ categoryRepo: repositories.NewCategoryRepository(db), auditLogRepo: repositories.NewAuditLogRepository(db), } } // CreateCategoryRequest represents category creation data type CreateCategoryRequest struct { Name string `json:"name" binding:"required"` Description string `json:"description"` } // UpdateCategoryRequest represents category update data type UpdateCategoryRequest struct { Name string `json:"name"` Description string `json:"description"` } // GetAllCategories gets all categories func (s *CategoryService) GetAllCategories() ([]models.CategoryResponse, error) { return s.categoryRepo.GetAllWithItemCount() } // GetCategoryByID gets category by ID func (s *CategoryService) GetCategoryByID(id uint) (*models.Category, error) { return s.categoryRepo.FindByID(id) } // GetCategoryBySlug gets category by slug func (s *CategoryService) GetCategoryBySlug(slug string) (*models.Category, error) { return s.categoryRepo.FindBySlug(slug) } // CreateCategory creates a new category (admin only) func (s *CategoryService) CreateCategory(adminID uint, req CreateCategoryRequest, ipAddress, userAgent string) (*models.Category, error) { // Generate slug from name slug := s.generateSlug(req.Name) // Check if slug already exists existing, _ := s.categoryRepo.FindBySlug(slug) if existing != nil { return nil, errors.New("category with similar name already exists") } category := &models.Category{ Name: req.Name, Slug: slug, Description: req.Description, } if err := s.categoryRepo.Create(category); err != nil { return nil, errors.New("failed to create category") } // Log audit s.auditLogRepo.Log(&adminID, models.ActionCreate, models.EntityCategory, &category.ID, "Category created: "+category.Name, ipAddress, userAgent) return category, nil } // UpdateCategory updates a category (admin only) func (s *CategoryService) UpdateCategory(adminID, categoryID uint, req UpdateCategoryRequest, ipAddress, userAgent string) (*models.Category, error) { category, err := s.categoryRepo.FindByID(categoryID) if err != nil { return nil, err } // Update fields if req.Name != "" { category.Name = req.Name category.Slug = s.generateSlug(req.Name) } if req.Description != "" { category.Description = req.Description } if err := s.categoryRepo.Update(category); err != nil { return nil, errors.New("failed to update category") } // Log audit s.auditLogRepo.Log(&adminID, models.ActionUpdate, models.EntityCategory, &categoryID, "Category updated: "+category.Name, ipAddress, userAgent) return category, nil } // DeleteCategory deletes a category (admin only) func (s *CategoryService) DeleteCategory(adminID, categoryID uint, ipAddress, userAgent string) error { category, err := s.categoryRepo.FindByID(categoryID) if err != nil { return err } // Check if category has items _, count, err := s.categoryRepo.GetCategoryWithItemCount(categoryID) if err != nil { return err } if count > 0 { return errors.New("cannot delete category with existing items") } if err := s.categoryRepo.Delete(categoryID); err != nil { return errors.New("failed to delete category") } // Log audit s.auditLogRepo.Log(&adminID, models.ActionDelete, models.EntityCategory, &categoryID, "Category deleted: "+category.Name, ipAddress, userAgent) return nil } // generateSlug generates URL-friendly slug from name func (s *CategoryService) generateSlug(name string) string { slug := strings.ToLower(name) slug = strings.ReplaceAll(slug, " ", "_") slug = strings.ReplaceAll(slug, "/", "_") // Remove special characters validChars := "abcdefghijklmnopqrstuvwxyz0123456789_" result := "" for _, char := range slug { if strings.ContainsRune(validChars, char) { result += string(char) } } return result }