447 lines
16 KiB
Go
447 lines
16 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"gorm.io/gorm"
|
|
|
|
helicopterusage "wucher/internal/domain/helicopter_usage"
|
|
"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 HelicopterUsageHandler struct {
|
|
svc helicopterusage.Service
|
|
validate *validators.Validator
|
|
}
|
|
|
|
func NewHelicopterUsageHandler(svc helicopterusage.Service) *HelicopterUsageHandler {
|
|
return &HelicopterUsageHandler{
|
|
svc: svc,
|
|
validate: validators.New(),
|
|
}
|
|
}
|
|
|
|
// CreateHelicopterUsage godoc
|
|
// @Summary Create helicopter usage
|
|
// @Description Create helicopter usage counters for a helicopter.
|
|
// @Tags Helicopter - Helicopter Usage
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body dto.HelicopterUsageCreateRequest true "JSON:API helicopter usage create request"
|
|
// @Success 201 {object} dto.HelicopterUsageResponse
|
|
// @Failure 422 {object} dto.ErrorResponse
|
|
// @Failure 400 {object} dto.ErrorResponse
|
|
// @Router /api/v1/helicopter-usage/create [post]
|
|
func (h *HelicopterUsageHandler) Create(c *fiber.Ctx) error {
|
|
var req dto.HelicopterUsageCreateRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Invalid JSON",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
if errs := h.validate.ValidateStruct(req); errs != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, errs)
|
|
}
|
|
|
|
helicopterID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.Attributes.HelicopterID))
|
|
if err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "helicopter_id is invalid UUID",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/helicopter_id"},
|
|
}})
|
|
}
|
|
|
|
row := helicopterUsageFromCreate(req.Data.Attributes)
|
|
row.HelicopterID = helicopterID
|
|
row.CreatedBy = actorUserID(c)
|
|
row.UpdatedBy = actorUserID(c)
|
|
|
|
if err := h.svc.Create(c.UserContext(), row); err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Create failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
|
|
created, err := h.svc.GetByID(c.UserContext(), row.ID)
|
|
if err == nil && created != nil {
|
|
return response.Write(c, fiber.StatusCreated, helicopterUsageResource(created))
|
|
}
|
|
return response.Write(c, fiber.StatusCreated, helicopterUsageResource(row))
|
|
}
|
|
|
|
// GetHelicopterUsage godoc
|
|
// @Summary Get helicopter usage
|
|
// @Tags Helicopter - Helicopter Usage
|
|
// @Produce json
|
|
// @Param uuid path string true "Helicopter Usage UUID (UUIDv7)"
|
|
// @Success 200 {object} dto.HelicopterUsageResponse
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Router /api/v1/helicopter-usage/get/{uuid} [get]
|
|
func (h *HelicopterUsageHandler) Get(c *fiber.Ctx) error {
|
|
id, err := uuidv7.ParseString(c.Params("uuid"))
|
|
if err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "uuid is invalid UUID",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/path/uuid"},
|
|
}})
|
|
}
|
|
|
|
row, err := h.svc.GetByID(c.UserContext(), id)
|
|
if err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Get failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
if row == nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
|
|
Status: "404",
|
|
Title: "Not found",
|
|
Detail: "helicopter usage not found",
|
|
}})
|
|
}
|
|
|
|
return response.Write(c, fiber.StatusOK, helicopterUsageResource(row))
|
|
}
|
|
|
|
// UpdateHelicopterUsage godoc
|
|
// @Summary Update helicopter usage (partial)
|
|
// @Tags Helicopter - Helicopter Usage
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param uuid path string true "Helicopter Usage UUID (UUIDv7)"
|
|
// @Param request body dto.HelicopterUsageUpdateRequest true "JSON:API helicopter usage update request"
|
|
// @Success 200 {object} dto.HelicopterUsageResponse
|
|
// @Failure 422 {object} dto.ErrorResponse
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Router /api/v1/helicopter-usage/update/{uuid} [patch]
|
|
func (h *HelicopterUsageHandler) Update(c *fiber.Ctx) error {
|
|
uuidStr := c.Params("uuid")
|
|
id, err := uuidv7.ParseString(uuidStr)
|
|
if err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "uuid is invalid UUID",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/path/uuid"},
|
|
}})
|
|
}
|
|
|
|
var req dto.HelicopterUsageUpdateRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Invalid JSON",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
if errs := h.validate.ValidateStruct(req); errs != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, errs)
|
|
}
|
|
|
|
if strings.TrimSpace(req.Data.ID) == "" {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "id is required",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/id"},
|
|
}})
|
|
}
|
|
if req.Data.ID != uuidStr {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "id does not match path uuid",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/id"},
|
|
}})
|
|
}
|
|
|
|
attrs := req.Data.Attributes
|
|
if attrs.HelicopterID == nil &&
|
|
attrs.TotalLanding == nil &&
|
|
attrs.TotalAirframeHours == nil &&
|
|
attrs.TotalAirframeCycles == nil &&
|
|
attrs.TotalEngine1Hours == nil &&
|
|
attrs.TotalEngine1GpcNgN1 == nil &&
|
|
attrs.TotalEngine1PtcNfN2 == nil &&
|
|
attrs.TotalEngine1Ccc == nil &&
|
|
attrs.TotalEngine2Hours == nil &&
|
|
attrs.TotalEngine2GpcNgN1 == nil &&
|
|
attrs.TotalEngine2PtcNfN2 == nil &&
|
|
attrs.TotalEngine2Ccc == nil &&
|
|
attrs.TotalFlightReport == nil &&
|
|
attrs.TotalHookRelease == nil &&
|
|
attrs.TotalRotorBrakeCycle == nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "at least one attribute must be provided",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/attributes"},
|
|
}})
|
|
}
|
|
|
|
existing, err := h.svc.GetByID(c.UserContext(), id)
|
|
if err != nil || existing == nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
|
|
Status: "404",
|
|
Title: "Not found",
|
|
Detail: "helicopter usage not found",
|
|
}})
|
|
}
|
|
|
|
if attrs.HelicopterID != nil {
|
|
helicopterID, parseErr := uuidv7.ParseString(strings.TrimSpace(*attrs.HelicopterID))
|
|
if parseErr != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "helicopter_id is invalid UUID",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/data/attributes/helicopter_id"},
|
|
}})
|
|
}
|
|
existing.HelicopterID = helicopterID
|
|
}
|
|
existing.ApplyManualSummary(helicopterUsageManualInput(attrs))
|
|
existing.UpdatedBy = actorUserID(c)
|
|
|
|
if err := h.svc.Update(c.UserContext(), existing); err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Update failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
|
|
updated, err := h.svc.GetByID(c.UserContext(), existing.ID)
|
|
if err == nil && updated != nil {
|
|
return response.Write(c, fiber.StatusOK, helicopterUsageResource(updated))
|
|
}
|
|
return response.Write(c, fiber.StatusOK, helicopterUsageResource(existing))
|
|
}
|
|
|
|
// DeleteHelicopterUsage godoc
|
|
// @Summary Delete helicopter usage
|
|
// @Tags Helicopter - Helicopter Usage
|
|
// @Produce json
|
|
// @Param uuid path string true "Helicopter Usage UUID (UUIDv7)"
|
|
// @Success 200 {object} dto.GenericDeleteResponse
|
|
// @Failure 404 {object} dto.ErrorResponse
|
|
// @Router /api/v1/helicopter-usage/delete/{uuid} [delete]
|
|
func (h *HelicopterUsageHandler) Delete(c *fiber.Ctx) error {
|
|
id, err := uuidv7.ParseString(c.Params("uuid"))
|
|
if err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: "uuid is invalid UUID",
|
|
Source: &jsonapi.ErrorSource{Pointer: "/path/uuid"},
|
|
}})
|
|
}
|
|
|
|
if err := h.svc.Delete(c.UserContext(), id, actorUserID(c)); err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
|
|
Status: "404",
|
|
Title: "Not found",
|
|
Detail: "helicopter usage not found",
|
|
}})
|
|
}
|
|
return writeHelicopterUsageErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Delete failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
|
|
return response.Write(c, fiber.StatusOK, jsonapi.Resource{
|
|
Type: "helicopter_usage_delete",
|
|
Attributes: map[string]any{
|
|
"deleted": true,
|
|
},
|
|
})
|
|
}
|
|
|
|
// ListHelicopterUsage godoc
|
|
// @Summary List helicopter usage
|
|
// @Description JSON:API list without pagination. Sort values: helicopter_id, -helicopter_id, total_landing, -total_landing, total_airframe_hours, -total_airframe_hours, total_airframe_cycles, -total_airframe_cycles, total_engine_1_hours, -total_engine_1_hours, total_engine_1_gpc_ng_n1, -total_engine_1_gpc_ng_n1, total_engine_1_ptc_nf_n2, -total_engine_1_ptc_nf_n2, total_engine_1_ccc, -total_engine_1_ccc, total_engine_2_hours, -total_engine_2_hours, total_engine_2_gpc_ng_n1, -total_engine_2_gpc_ng_n1, total_engine_2_ptc_nf_n2, -total_engine_2_ptc_nf_n2, total_engine_2_ccc, -total_engine_2_ccc, total_flight_report, -total_flight_report, total_hook_release, -total_hook_release, total_rotor_brake_cycle, -total_rotor_brake_cycle, created_at, -created_at, updated_at, -updated_at.
|
|
// @Tags Helicopter - Helicopter Usage
|
|
// @Produce json
|
|
// @Param search query string false "Search by helicopter or usage ID"
|
|
// @Param sort query string false "Sort order"
|
|
// @Success 200 {object} dto.HelicopterUsageListResponse
|
|
// @Router /api/v1/helicopter-usage/get-all [get]
|
|
func (h *HelicopterUsageHandler) List(c *fiber.Ctx) error {
|
|
filter := strings.TrimSpace(c.Query("search"))
|
|
sort := strings.TrimSpace(c.Query("sort"))
|
|
|
|
rows, total, err := h.svc.List(c.UserContext(), filter, sort, 0, 0)
|
|
if err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "List failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
|
|
data := make([]dto.HelicopterUsageResource, 0, len(rows))
|
|
for i := range rows {
|
|
data = append(data, helicopterUsageResource(&rows[i]))
|
|
}
|
|
|
|
meta := map[string]any{
|
|
"total": total,
|
|
}
|
|
return response.WriteWithMeta(c, fiber.StatusOK, data, meta)
|
|
}
|
|
|
|
// ListHelicopterUsageDatatable godoc
|
|
// @Summary List helicopter usage (datatable)
|
|
// @Description Datatable response with simple pagination params.
|
|
// @Tags Helicopter - Helicopter Usage
|
|
// @Produce json
|
|
// @Param page query int false "Page number (default 1)"
|
|
// @Param size query int false "Page size (default 20, max 100)"
|
|
// @Param limit query int false "Page size override (same as size)"
|
|
// @Param draw query int false "Draw counter (optional)"
|
|
// @Param search query string false "Search term"
|
|
// @Success 200 {object} dto.HelicopterUsageListResponse
|
|
// @Router /api/v1/helicopter-usage/get-all/dt [get]
|
|
func (h *HelicopterUsageHandler) ListDatatable(c *fiber.Ctx) error {
|
|
pageNumber := parseIntDefault(c.Query("page"), 1)
|
|
if pageNumber < 1 {
|
|
pageNumber = 1
|
|
}
|
|
length := parseIntDefault(c.Query("limit"), 0)
|
|
if length <= 0 {
|
|
length = parseIntDefault(c.Query("size"), 20)
|
|
}
|
|
if length > 100 {
|
|
length = 100
|
|
}
|
|
if length < 1 {
|
|
length = 20
|
|
}
|
|
start := (pageNumber - 1) * length
|
|
draw := parseIntDefault(c.Query("draw"), pageNumber)
|
|
search := strings.TrimSpace(c.Query("search"))
|
|
sort := strings.TrimSpace(c.Query("sort"))
|
|
|
|
rows, total, err := h.svc.List(c.UserContext(), search, sort, length, start)
|
|
if err != nil {
|
|
return writeHelicopterUsageErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "List failed",
|
|
Detail: safeInternalDetail(),
|
|
}})
|
|
}
|
|
|
|
data := make([]dto.HelicopterUsageResource, 0, len(rows))
|
|
for i := range rows {
|
|
data = append(data, helicopterUsageResource(&rows[i]))
|
|
}
|
|
|
|
meta := map[string]any{
|
|
"draw": draw,
|
|
"records_total": total,
|
|
"records_filtered": total,
|
|
}
|
|
return response.WriteWithMeta(c, fiber.StatusOK, data, meta)
|
|
}
|
|
|
|
func helicopterUsageFromCreate(attrs dto.HelicopterUsageCreateAttributes) *helicopterusage.HelicopterUsage {
|
|
row := &helicopterusage.HelicopterUsage{}
|
|
row.ApplyManualSummary(helicopterusage.ManualSummaryInput{
|
|
AirframeHours: attrs.TotalAirframeHours,
|
|
AirframeCycles: attrs.TotalAirframeCycles,
|
|
Engine1Hours: attrs.TotalEngine1Hours,
|
|
Engine1GpcNgN1: attrs.TotalEngine1GpcNgN1,
|
|
Engine1PtcNfN2: attrs.TotalEngine1PtcNfN2,
|
|
Engine1Ccc: attrs.TotalEngine1Ccc,
|
|
Engine2Hours: attrs.TotalEngine2Hours,
|
|
Engine2GpcNgN1: attrs.TotalEngine2GpcNgN1,
|
|
Engine2PtcNfN2: attrs.TotalEngine2PtcNfN2,
|
|
Engine2Ccc: attrs.TotalEngine2Ccc,
|
|
Landing: attrs.TotalLanding,
|
|
FlightReport: attrs.TotalFlightReport,
|
|
HookRelease: attrs.TotalHookRelease,
|
|
RotorBrakeCycle: attrs.TotalRotorBrakeCycle,
|
|
})
|
|
return row
|
|
}
|
|
|
|
func helicopterUsageManualInput(attrs dto.HelicopterUsageUpdateAttributes) helicopterusage.ManualSummaryInput {
|
|
return helicopterusage.ManualSummaryInput{
|
|
AirframeHours: attrs.TotalAirframeHours,
|
|
AirframeCycles: attrs.TotalAirframeCycles,
|
|
Engine1Hours: attrs.TotalEngine1Hours,
|
|
Engine1GpcNgN1: attrs.TotalEngine1GpcNgN1,
|
|
Engine1PtcNfN2: attrs.TotalEngine1PtcNfN2,
|
|
Engine1Ccc: attrs.TotalEngine1Ccc,
|
|
Engine2Hours: attrs.TotalEngine2Hours,
|
|
Engine2GpcNgN1: attrs.TotalEngine2GpcNgN1,
|
|
Engine2PtcNfN2: attrs.TotalEngine2PtcNfN2,
|
|
Engine2Ccc: attrs.TotalEngine2Ccc,
|
|
Landing: attrs.TotalLanding,
|
|
FlightReport: attrs.TotalFlightReport,
|
|
HookRelease: attrs.TotalHookRelease,
|
|
RotorBrakeCycle: attrs.TotalRotorBrakeCycle,
|
|
}
|
|
}
|
|
|
|
func helicopterUsageResource(row *helicopterusage.HelicopterUsage) dto.HelicopterUsageResource {
|
|
if row == nil {
|
|
return dto.HelicopterUsageResource{}
|
|
}
|
|
return dto.HelicopterUsageResource{
|
|
Type: "helicopter_usage",
|
|
ID: idString(row.ID),
|
|
Attributes: dto.HelicopterUsageAttributes{
|
|
HelicopterID: idString(row.HelicopterID),
|
|
TotalLanding: row.TotalLanding,
|
|
TotalAirframeHours: row.TotalAirframeHours,
|
|
TotalAirframeCycles: row.TotalAirframeCycles,
|
|
TotalEngine1Hours: row.TotalEngine1Hours,
|
|
TotalEngine1GpcNgN1: row.TotalEngine1GpcNgN1,
|
|
TotalEngine1PtcNfN2: row.TotalEngine1PtcNfN2,
|
|
TotalEngine1Ccc: row.TotalEngine1Ccc,
|
|
TotalEngine2Hours: row.TotalEngine2Hours,
|
|
TotalEngine2GpcNgN1: row.TotalEngine2GpcNgN1,
|
|
TotalEngine2PtcNfN2: row.TotalEngine2PtcNfN2,
|
|
TotalEngine2Ccc: row.TotalEngine2Ccc,
|
|
TotalFlightReport: row.TotalFlightReport,
|
|
TotalHookRelease: row.TotalHookRelease,
|
|
TotalRotorBrakeCycle: row.TotalRotorBrakeCycle,
|
|
CreatedAt: row.CreatedAt.UTC().Format(time.RFC3339),
|
|
CreatedBy: uuidStringOrEmpty(row.CreatedBy),
|
|
UpdatedAt: row.UpdatedAt.UTC().Format(time.RFC3339),
|
|
UpdatedBy: uuidStringOrEmpty(row.UpdatedBy),
|
|
DeletedAt: timeStringOrEmpty(row.DeletedAt),
|
|
DeletedBy: uuidStringOrEmpty(row.DeletedBy),
|
|
},
|
|
}
|
|
}
|
|
|
|
func writeHelicopterUsageErrors(c *fiber.Ctx, status int, errs []jsonapi.ErrorObject) error {
|
|
for i := range errs {
|
|
errs[i].Status = strconv.Itoa(status)
|
|
}
|
|
return response.WriteErrors(c, status, errs)
|
|
}
|