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,446 @@
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)
}