init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

View File

@@ -0,0 +1,287 @@
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),
},
}
}