// 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 }