package api import ( "context" "net/http" "time" "github.com/danielgtaylor/huma/v2" "github.com/forfarm/backend/internal/domain" "github.com/go-chi/chi/v5" ) func (a *api) registerInventoryRoutes(_ chi.Router, api huma.API) { tags := []string{"inventory"} prefix := "/inventory" huma.Register(api, huma.Operation{ OperationID: "createInventoryItem", Method: http.MethodPost, Path: prefix, Tags: tags, }, a.createInventoryItemHandler) huma.Register(api, huma.Operation{ OperationID: "getInventoryItemsByUser", Method: http.MethodGet, Path: prefix, Tags: tags, }, a.getInventoryItemsByUserHandler) huma.Register(api, huma.Operation{ OperationID: "getInventoryItem", Method: http.MethodGet, Path: prefix + "/{id}", Tags: tags, }, a.getInventoryItemHandler) huma.Register(api, huma.Operation{ OperationID: "updateInventoryItem", Method: http.MethodPut, Path: prefix + "/{id}", Tags: tags, }, a.updateInventoryItemHandler) huma.Register(api, huma.Operation{ OperationID: "deleteInventoryItem", Method: http.MethodDelete, Path: prefix + "/{id}", Tags: tags, }, a.deleteInventoryItemHandler) huma.Register(api, huma.Operation{ OperationID: "getInventoryStatus", Method: http.MethodGet, Path: prefix + "/status", Tags: tags, }, a.getInventoryStatusHandler) huma.Register(api, huma.Operation{ OperationID: "getInventoryCategory", Method: http.MethodGet, Path: prefix + "/category", Tags: tags, }, a.getInventoryCategoryHandler) huma.Register(api, huma.Operation{ OperationID: "getHarvestUnits", Method: http.MethodGet, Path: "/harvest/units", Tags: []string{"harvest"}, }, a.getHarvestUnitsHandler) } type InventoryItemResponse struct { ID string `json:"id"` Name string `json:"name"` Category InventoryCategory `json:"category"` Quantity float64 `json:"quantity"` Unit HarvestUnit `json:"unit"` DateAdded time.Time `json:"dateAdded"` Status InventoryStatus `json:"status"` CreatedAt time.Time `json:"createdAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"` } type InventoryStatus struct { ID int `json:"id"` Name string `json:"name"` } type InventoryCategory struct { ID int `json:"id"` Name string `json:"name"` } type HarvestUnit struct { ID int `json:"id"` Name string `json:"name"` } type CreateInventoryItemInput struct { Header string `header:"Authorization" required:"true" example:"Bearer token"` Body struct { Name string `json:"name" required:"true"` CategoryID int `json:"categoryId" required:"true"` Quantity float64 `json:"quantity" required:"true"` UnitID int `json:"unitId" required:"true"` DateAdded time.Time `json:"dateAdded" required:"true"` StatusID int `json:"statusId" required:"true"` } } type CreateInventoryItemOutput struct { Body struct { ID string `json:"id"` } } type UpdateInventoryItemInput struct { Header string `header:"Authorization" required:"true" example:"Bearer token"` ID string `path:"id"` Body struct { Name string `json:"name"` CategoryID int `json:"categoryId"` Quantity float64 `json:"quantity"` UnitID int `json:"unitId"` DateAdded time.Time `json:"dateAdded"` StatusID int `json:"statusId"` } } type UpdateInventoryItemOutput struct { Body InventoryItemResponse } type GetInventoryItemsInput struct { Header string `header:"Authorization" required:"true" example:"Bearer token"` CategoryID int `query:"categoryId"` StatusID int `query:"statusId"` StartDate time.Time `query:"startDate" format:"date-time"` EndDate time.Time `query:"endDate" format:"date-time"` SearchQuery string `query:"search"` SortBy string `query:"sortBy" enum:"name,quantity,dateAdded,createdAt"` SortOrder string `query:"sortOrder" enum:"asc,desc" default:"desc"` } type GetInventoryItemsOutput struct { Body []InventoryItemResponse } type GetInventoryItemInput struct { Header string `header:"Authorization" required:"true" example:"Bearer token"` UserID string `header:"userId" required:"true" example:"user-uuid"` ID string `path:"id"` } type GetInventoryItemOutput struct { Body InventoryItemResponse } type DeleteInventoryItemInput struct { Header string `header:"Authorization" required:"true" example:"Bearer token"` ID string `path:"id"` } type DeleteInventoryItemOutput struct { Body struct { Message string `json:"message"` } } type GetInventoryStatusOutput struct { Body []InventoryStatus } type GetInventoryCategoryOutput struct { Body []InventoryCategory } type GetHarvestUnitsOutput struct { Body []HarvestUnit } func (a *api) createInventoryItemHandler(ctx context.Context, input *CreateInventoryItemInput) (*CreateInventoryItemOutput, error) { userID, err := a.getUserIDFromHeader(input.Header) item := &domain.InventoryItem{ UserID: userID, Name: input.Body.Name, CategoryID: input.Body.CategoryID, Quantity: input.Body.Quantity, UnitID: input.Body.UnitID, DateAdded: input.Body.DateAdded, StatusID: input.Body.StatusID, } if err := item.Validate(); err != nil { return nil, huma.Error422UnprocessableEntity(err.Error()) } err = a.inventoryRepo.CreateOrUpdate(ctx, item) if err != nil { return nil, err } return &CreateInventoryItemOutput{Body: struct { ID string `json:"id"` }{ID: item.ID}}, nil } func (a *api) getInventoryItemsByUserHandler(ctx context.Context, input *GetInventoryItemsInput) (*GetInventoryItemsOutput, error) { userID, err := a.getUserIDFromHeader(input.Header) filter := domain.InventoryFilter{ UserID: userID, CategoryID: input.CategoryID, StatusID: input.StatusID, StartDate: input.StartDate, EndDate: input.EndDate, SearchQuery: input.SearchQuery, } sort := domain.InventorySort{ Field: input.SortBy, Direction: input.SortOrder, } items, err := a.inventoryRepo.GetByUserID(ctx, userID, filter, sort) if err != nil { return nil, err } response := make([]InventoryItemResponse, len(items)) for i, item := range items { response[i] = InventoryItemResponse{ ID: item.ID, Name: item.Name, Category: InventoryCategory{ ID: item.Category.ID, Name: item.Category.Name, }, Quantity: item.Quantity, Unit: HarvestUnit{ ID: item.Unit.ID, Name: item.Unit.Name, }, DateAdded: item.DateAdded, Status: InventoryStatus{ ID: item.Status.ID, Name: item.Status.Name, }, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, } } return &GetInventoryItemsOutput{Body: response}, nil } func (a *api) getInventoryItemHandler(ctx context.Context, input *GetInventoryItemInput) (*GetInventoryItemOutput, error) { item, err := a.inventoryRepo.GetByID(ctx, input.ID, input.UserID) if err != nil { return nil, err } return &GetInventoryItemOutput{Body: InventoryItemResponse{ ID: item.ID, Name: item.Name, Category: InventoryCategory{ ID: item.Category.ID, Name: item.Category.Name, }, Quantity: item.Quantity, Unit: HarvestUnit{ ID: item.Unit.ID, Name: item.Unit.Name, }, DateAdded: item.DateAdded, Status: InventoryStatus{ ID: item.Status.ID, Name: item.Status.Name, }, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, }}, nil } func (a *api) updateInventoryItemHandler(ctx context.Context, input *UpdateInventoryItemInput) (*UpdateInventoryItemOutput, error) { userID, err := a.getUserIDFromHeader(input.Header) item, err := a.inventoryRepo.GetByID(ctx, input.ID, userID) if err != nil { return nil, err } if input.Body.Name != "" { item.Name = input.Body.Name } if input.Body.CategoryID != 0 { item.CategoryID = input.Body.CategoryID } if input.Body.Quantity != 0 { item.Quantity = input.Body.Quantity } if input.Body.UnitID != 0 { item.UnitID = input.Body.UnitID } if !input.Body.DateAdded.IsZero() { item.DateAdded = input.Body.DateAdded } if input.Body.StatusID != 0 { item.StatusID = input.Body.StatusID } if err := item.Validate(); err != nil { return nil, huma.Error422UnprocessableEntity(err.Error()) } err = a.inventoryRepo.CreateOrUpdate(ctx, &item) if err != nil { return nil, err } updatedItem, err := a.inventoryRepo.GetByID(ctx, input.ID, userID) if err != nil { return nil, err } return &UpdateInventoryItemOutput{Body: InventoryItemResponse{ ID: updatedItem.ID, Name: updatedItem.Name, Category: InventoryCategory{ ID: updatedItem.Category.ID, Name: updatedItem.Category.Name, }, Quantity: updatedItem.Quantity, Unit: HarvestUnit{ ID: updatedItem.Unit.ID, Name: updatedItem.Unit.Name, }, DateAdded: updatedItem.DateAdded, Status: InventoryStatus{ ID: updatedItem.Status.ID, Name: updatedItem.Status.Name, }, CreatedAt: updatedItem.CreatedAt, UpdatedAt: updatedItem.UpdatedAt, }}, nil } func (a *api) deleteInventoryItemHandler(ctx context.Context, input *DeleteInventoryItemInput) (*DeleteInventoryItemOutput, error) { userID, err := a.getUserIDFromHeader(input.Header) err = a.inventoryRepo.Delete(ctx, input.ID, userID) if err != nil { return nil, err } return &DeleteInventoryItemOutput{Body: struct { Message string `json:"message"` }{Message: "Inventory item deleted successfully"}}, nil } func (a *api) getInventoryStatusHandler(ctx context.Context, input *struct{}) (*GetInventoryStatusOutput, error) { statuses, err := a.inventoryRepo.GetStatuses(ctx) if err != nil { return nil, err } response := make([]InventoryStatus, len(statuses)) for i, status := range statuses { response[i] = InventoryStatus{ ID: status.ID, Name: status.Name, } } return &GetInventoryStatusOutput{Body: response}, nil } func (a *api) getInventoryCategoryHandler(ctx context.Context, input *struct{}) (*GetInventoryCategoryOutput, error) { categories, err := a.inventoryRepo.GetCategories(ctx) if err != nil { return nil, err } response := make([]InventoryCategory, len(categories)) for i, category := range categories { response[i] = InventoryCategory{ ID: category.ID, Name: category.Name, } } return &GetInventoryCategoryOutput{Body: response}, nil } func (a *api) getHarvestUnitsHandler(ctx context.Context, input *struct{}) (*GetHarvestUnitsOutput, error) { units, err := a.harvestRepo.GetUnits(ctx) if err != nil { return nil, err } response := make([]HarvestUnit, len(units)) for i, unit := range units { response[i] = HarvestUnit{ ID: unit.ID, Name: unit.Name, } } return &GetHarvestUnitsOutput{Body: response}, nil }