288 lines
9.4 KiB
Go
288 lines
9.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
flightinspection "wucher/internal/domain/flight_inspection"
|
|
"wucher/internal/shared/pkg/uuidv7"
|
|
"wucher/internal/transport/http/dto"
|
|
"wucher/internal/transport/http/jsonapi"
|
|
"wucher/internal/transport/http/response"
|
|
"wucher/internal/transport/http/validators"
|
|
)
|
|
|
|
type FlightInspectionHandler struct {
|
|
svc flightinspection.FlightInspectionService
|
|
validate *validators.Validator
|
|
}
|
|
|
|
func NewFlightInspectionHandler(svc flightinspection.FlightInspectionService) *FlightInspectionHandler {
|
|
return &FlightInspectionHandler{
|
|
svc: svc,
|
|
validate: validators.New(),
|
|
}
|
|
}
|
|
|
|
// CreateFlightInspection godoc
|
|
// @Summary Create flight inspection draft
|
|
// @Tags Flight Inspection
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.FlightInspectionCreateRequest true "JSON:API flight inspection create request"
|
|
// @Success 201 {object} dto.FlightInspectionResponse
|
|
// @Failure 422 {object} dto.ErrorResponse
|
|
// @Failure 400 {object} dto.ErrorResponse
|
|
// Public route disabled (managed via reserve AC flow).
|
|
func (h *FlightInspectionHandler) Create(c *fiber.Ctx) error {
|
|
var req dto.FlightInspectionCreateRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Invalid JSON",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
if errs := h.validate.ValidateStruct(req); errs != nil {
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, errs)
|
|
}
|
|
|
|
inspectionDate, err := time.Parse("2006-01-02", strings.TrimSpace(req.Data.Attributes.InspectionDate))
|
|
if err != nil {
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "inspection_date must be YYYY-MM-DD",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/inspection_date"},
|
|
}})
|
|
}
|
|
|
|
model := &flightinspection.FlightInspection{
|
|
InspectionDate: inspectionDate.UTC(),
|
|
CreatedBy: actorUserID(c),
|
|
UpdatedBy: actorUserID(c),
|
|
}
|
|
if err := h.svc.CreateDraft(c.UserContext(), model); err != nil {
|
|
return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Create failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
return response.Write(c, fiber.StatusCreated, flightInspectionResource(model))
|
|
}
|
|
|
|
// GetFlightInspection godoc
|
|
// @Summary Get flight inspection by ID
|
|
// @Tags Flight Inspection
|
|
// @Produce json
|
|
// @Param id path string true "Flight inspection UUID (UUIDv7)"
|
|
// @Success 200 {object} dto.FlightInspectionResponse
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Failure 422 {object} dto.ErrorResponse
|
|
// Public route disabled (managed via reserve AC flow).
|
|
func (h *FlightInspectionHandler) GetByID(c *fiber.Ctx) error {
|
|
id, err := uuidv7.ParseString(strings.TrimSpace(c.Params("id")))
|
|
if err != nil {
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "id is invalid UUID",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/path/id"},
|
|
}})
|
|
}
|
|
|
|
row, err := h.svc.GetByID(c.UserContext(), id)
|
|
if err != nil {
|
|
return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Get failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
if row == nil {
|
|
return response.WriteErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
|
|
Status: "404",
|
|
Title: "Not found",
|
|
Detail: "flight inspection not found",
|
|
}})
|
|
}
|
|
return response.Write(c, fiber.StatusOK, flightInspectionResource(row))
|
|
}
|
|
|
|
// UpdateFlightInspection godoc
|
|
// @Summary Update flight inspection
|
|
// @Tags Flight Inspection
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Flight inspection UUID (UUIDv7)"
|
|
// @Param request body dto.FlightInspectionUpdateRequest true "JSON:API flight inspection update request"
|
|
// @Success 200 {object} dto.FlightInspectionResponse
|
|
// @Failure 400 {object} dto.ErrorResponse
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Failure 422 {object} dto.ErrorResponse
|
|
// Public route disabled (managed via reserve AC flow).
|
|
func (h *FlightInspectionHandler) Update(c *fiber.Ctx) error {
|
|
idStr := strings.TrimSpace(c.Params("id"))
|
|
id, err := uuidv7.ParseString(idStr)
|
|
if err != nil {
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "id is invalid UUID",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/path/id"},
|
|
}})
|
|
}
|
|
|
|
var req dto.FlightInspectionUpdateRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Invalid JSON",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
if errs := h.validate.ValidateStruct(req); errs != nil {
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, errs)
|
|
}
|
|
if strings.TrimSpace(req.Data.ID) == "" || req.Data.ID != idStr {
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "id is required and must match path id",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/id"},
|
|
}})
|
|
}
|
|
|
|
if req.Data.Attributes.InspectionDate == nil && req.Data.Attributes.Status == nil {
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "at least one attribute must be provided",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/attributes"},
|
|
}})
|
|
}
|
|
|
|
row, err := h.svc.GetByID(c.UserContext(), id)
|
|
if err != nil {
|
|
return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Update failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
if row == nil {
|
|
return response.WriteErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
|
|
Status: "404",
|
|
Title: "Not found",
|
|
Detail: "flight inspection not found",
|
|
}})
|
|
}
|
|
|
|
if req.Data.Attributes.InspectionDate != nil {
|
|
inspectionDate, parseErr := time.Parse("2006-01-02", strings.TrimSpace(*req.Data.Attributes.InspectionDate))
|
|
if parseErr != nil {
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "inspection_date must be YYYY-MM-DD",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/inspection_date"},
|
|
}})
|
|
}
|
|
row.InspectionDate = inspectionDate.UTC()
|
|
}
|
|
|
|
if req.Data.Attributes.Status != nil {
|
|
nextStatus := strings.TrimSpace(*req.Data.Attributes.Status)
|
|
switch nextStatus {
|
|
case flightinspection.StatusDraft, flightinspection.StatusInProgress, flightinspection.StatusCompleted:
|
|
row.Status = nextStatus
|
|
default:
|
|
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "status must be one of: Draft, InProgress, Completed",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/status"},
|
|
}})
|
|
}
|
|
}
|
|
|
|
row.UpdatedBy = actorUserID(c)
|
|
if err := h.svc.Update(c.UserContext(), row); err != nil {
|
|
return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Update failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
|
|
updated, err := h.svc.GetByID(c.UserContext(), id)
|
|
if err == nil && updated != nil {
|
|
return response.Write(c, fiber.StatusOK, flightInspectionResource(updated))
|
|
}
|
|
return response.Write(c, fiber.StatusOK, flightInspectionResource(row))
|
|
}
|
|
|
|
// ListFlightInspections godoc
|
|
// @Summary List flight inspections
|
|
// @Tags Flight Inspection
|
|
// @Produce json
|
|
// @Param page[number] query int false "Page number (default 1)"
|
|
// @Param page[size] query int false "Page size (default 20, max 100)"
|
|
// @Param sort query string false "Sort order"
|
|
// @Success 200 {object} dto.FlightInspectionListResponse
|
|
// @Failure 422 {object} dto.ErrorResponse
|
|
// Public route disabled (managed via reserve AC flow).
|
|
func (h *FlightInspectionHandler) List(c *fiber.Ctx) error {
|
|
pageNumber := parseIntDefault(c.Query("page[number]"), 1)
|
|
if pageNumber < 1 {
|
|
pageNumber = 1
|
|
}
|
|
pageSize := parseIntDefault(c.Query("page[size]"), 20)
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
if pageSize > 100 {
|
|
pageSize = 100
|
|
}
|
|
offset := (pageNumber - 1) * pageSize
|
|
_ = offset
|
|
|
|
rows, total, err := h.svc.List(c.UserContext(), c.Query("sort"), 0, 0)
|
|
if err != nil {
|
|
return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "List failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
|
|
data := make([]dto.FlightInspectionResource, 0, len(rows))
|
|
for i := range rows {
|
|
data = append(data, flightInspectionResource(&rows[i]))
|
|
}
|
|
return response.WriteWithMeta(c, fiber.StatusOK, data, map[string]any{
|
|
"page_number": pageNumber,
|
|
"page_size": pageSize,
|
|
"total": total,
|
|
})
|
|
}
|
|
|
|
func flightInspectionResource(row *flightinspection.FlightInspection) dto.FlightInspectionResource {
|
|
id, _ := uuidv7.BytesToString(row.ID)
|
|
return dto.FlightInspectionResource{
|
|
Type: "flight_inspection",
|
|
ID: id,
|
|
Attributes: dto.FlightInspectionAttributes{
|
|
InspectionDate: row.InspectionDate.UTC().Format("2006-01-02"),
|
|
Status: row.Status,
|
|
CreatedAt: row.CreatedAt.UTC().Format(time.RFC3339),
|
|
CreatedBy: uuidStringOrEmpty(row.CreatedBy),
|
|
UpdatedAt: row.UpdatedAt.UTC().Format(time.RFC3339),
|
|
UpdatedBy: uuidStringOrEmpty(row.UpdatedBy),
|
|
},
|
|
}
|
|
}
|