2025-11-23 22:49:46 +07:00

212 lines
6.7 KiB
Go

// internal/services/item_service.go
package services
import (
"errors"
"lost-and-found/internal/models"
"lost-and-found/internal/repositories"
"time"
"gorm.io/gorm"
)
type ItemService struct {
itemRepo *repositories.ItemRepository
categoryRepo *repositories.CategoryRepository
auditLogRepo *repositories.AuditLogRepository
revisionRepo *repositories.RevisionLogRepository
}
func NewItemService(db *gorm.DB) *ItemService {
return &ItemService{
itemRepo: repositories.NewItemRepository(db),
categoryRepo: repositories.NewCategoryRepository(db),
auditLogRepo: repositories.NewAuditLogRepository(db),
revisionRepo: repositories.NewRevisionLogRepository(db),
}
}
// CreateItemRequest represents create item data
type CreateItemRequest struct {
Name string `json:"name" binding:"required"`
CategoryID uint `json:"category_id" binding:"required"`
PhotoURL string `json:"photo_url"`
Location string `json:"location" binding:"required"`
Description string `json:"description" binding:"required"`
DateFound time.Time `json:"date_found" binding:"required"`
ReporterName string `json:"reporter_name" binding:"required"`
ReporterContact string `json:"reporter_contact" binding:"required"`
}
// UpdateItemRequest represents update item data
type UpdateItemRequest struct {
Name string `json:"name"`
CategoryID uint `json:"category_id"`
Location string `json:"location"`
Description string `json:"description"`
DateFound time.Time `json:"date_found"`
ReporterName string `json:"reporter_name"`
ReporterContact string `json:"reporter_contact"`
Reason string `json:"reason"` // Reason for edit
}
// GetAllItems gets all items (public view)
func (s *ItemService) GetAllItems(page, limit int, status, category, search string) ([]models.ItemPublicResponse, int64, error) {
items, total, err := s.itemRepo.FindAll(page, limit, status, category, search)
if err != nil {
return nil, 0, err
}
var responses []models.ItemPublicResponse
for _, item := range items {
responses = append(responses, item.ToPublicResponse())
}
return responses, total, nil
}
// GetItemByID gets item by ID
func (s *ItemService) GetItemByID(id uint, isManager bool) (interface{}, error) {
item, err := s.itemRepo.FindByID(id)
if err != nil {
return nil, err
}
// Manager can see full details
if isManager {
return item.ToDetailResponse(), nil
}
// Public can only see limited info
return item.ToPublicResponse(), nil
}
// CreateItem creates a new item
func (s *ItemService) CreateItem(reporterID uint, req CreateItemRequest, ipAddress, userAgent string) (*models.Item, error) {
// Verify category exists
if _, err := s.categoryRepo.FindByID(req.CategoryID); err != nil {
return nil, errors.New("invalid category")
}
item := &models.Item{
Name: req.Name,
CategoryID: req.CategoryID,
PhotoURL: req.PhotoURL,
Location: req.Location,
Description: req.Description,
DateFound: req.DateFound,
Status: models.ItemStatusUnclaimed,
ReporterID: reporterID,
ReporterName: req.ReporterName,
ReporterContact: req.ReporterContact,
}
if err := s.itemRepo.Create(item); err != nil {
return nil, errors.New("failed to create item")
}
// Log audit
s.auditLogRepo.Log(&reporterID, models.ActionCreate, models.EntityItem, &item.ID,
"Item created: "+item.Name, ipAddress, userAgent)
return item, nil
}
// UpdateItem updates an item
func (s *ItemService) UpdateItem(userID, itemID uint, req UpdateItemRequest, ipAddress, userAgent string) (*models.Item, error) {
item, err := s.itemRepo.FindByID(itemID)
if err != nil {
return nil, err
}
// Check if item can be edited
if !item.CanBeEdited() {
return nil, errors.New("cannot edit item with status: " + item.Status)
}
// Track changes for revision log
if req.Name != "" && req.Name != item.Name {
s.revisionRepo.Log(itemID, userID, "name", item.Name, req.Name, req.Reason)
item.Name = req.Name
}
if req.CategoryID != 0 && req.CategoryID != item.CategoryID {
oldCat, _ := s.categoryRepo.FindByID(item.CategoryID)
newCat, _ := s.categoryRepo.FindByID(req.CategoryID)
s.revisionRepo.Log(itemID, userID, "category", oldCat.Name, newCat.Name, req.Reason)
item.CategoryID = req.CategoryID
}
if req.Location != "" && req.Location != item.Location {
s.revisionRepo.Log(itemID, userID, "location", item.Location, req.Location, req.Reason)
item.Location = req.Location
}
if req.Description != "" && req.Description != item.Description {
s.revisionRepo.Log(itemID, userID, "description", item.Description, req.Description, req.Reason)
item.Description = req.Description
}
if err := s.itemRepo.Update(item); err != nil {
return nil, errors.New("failed to update item")
}
// Log audit
s.auditLogRepo.Log(&userID, models.ActionUpdate, models.EntityItem, &itemID,
"Item updated: "+item.Name, ipAddress, userAgent)
return item, nil
}
// UpdateItemStatus updates item status
func (s *ItemService) UpdateItemStatus(userID, itemID uint, status string, ipAddress, userAgent string) error {
if err := s.itemRepo.UpdateStatus(itemID, status); err != nil {
return errors.New("failed to update item status")
}
// Log audit
s.auditLogRepo.Log(&userID, models.ActionUpdate, models.EntityItem, &itemID,
"Item status updated to: "+status, ipAddress, userAgent)
return nil
}
// DeleteItem deletes an item
func (s *ItemService) DeleteItem(userID, itemID uint, ipAddress, userAgent string) error {
item, err := s.itemRepo.FindByID(itemID)
if err != nil {
return err
}
// Cannot delete verified or case closed items
if item.Status == models.ItemStatusVerified || item.Status == models.ItemStatusCaseClosed {
return errors.New("cannot delete item with status: " + item.Status)
}
if err := s.itemRepo.Delete(itemID); err != nil {
return errors.New("failed to delete item")
}
// Log audit
s.auditLogRepo.Log(&userID, models.ActionDelete, models.EntityItem, &itemID,
"Item deleted: "+item.Name, ipAddress, userAgent)
return nil
}
// GetItemsByReporter gets items by reporter
func (s *ItemService) GetItemsByReporter(reporterID uint, page, limit int) ([]models.Item, int64, error) {
return s.itemRepo.FindByReporter(reporterID, page, limit)
}
// GetItemRevisionHistory gets revision history for an item
func (s *ItemService) GetItemRevisionHistory(itemID uint, page, limit int) ([]models.RevisionLogResponse, int64, error) {
logs, total, err := s.revisionRepo.FindByItem(itemID, page, limit)
if err != nil {
return nil, 0, err
}
var responses []models.RevisionLogResponse
for _, log := range logs {
responses = append(responses, log.ToResponse())
}
return responses, total, nil
}