package handlers import ( "context" "errors" "log/slog" "sort" "strconv" "strings" "time" "github.com/gofiber/fiber/v2" "gorm.io/gorm" complaint "wucher/internal/domain/complaint" easarelease "wucher/internal/domain/easa_release" fleetstatus "wucher/internal/domain/fleet_status" "wucher/internal/domain/helicopter" "wucher/internal/domain/mcf" "wucher/internal/shared/pkg/apperrorsx" "wucher/internal/shared/pkg/userctx" "wucher/internal/shared/pkg/uuidv7" requestdto "wucher/internal/transport/http/dto/request" responsedto "wucher/internal/transport/http/dto/response" "wucher/internal/transport/http/jsonapi" "wucher/internal/transport/http/response" "wucher/internal/transport/http/validators" ) type FleetStatusHandler struct { svc fleetstatus.Service helicopterSvc helicopter.Service complaintSvc complaintLister easaSvc easaReleaseLister historySvc fleetHistoryLister mcfSvc fleetMCFService signoffSvc complaintSignoffLookup storage fileManagerDownloadURLStorage validate *validators.Validator } type FleetStatusDeps struct { Helicopter helicopter.Service Complaint complaintLister EASA easaReleaseLister History fleetHistoryLister MCF fleetMCFService Signoff complaintSignoffLookup Storage fileManagerDownloadURLStorage } type complaintLister interface { ListByHelicopter(ctx context.Context, helicopterID []byte, limit, offset int) ([]complaint.Complaint, int64, error) ResolveUserNames(ctx context.Context, complaints []complaint.Complaint) (map[string]string, error) ActiveGroundingByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) ([]complaint.Complaint, error) OpenByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) ([]complaint.Complaint, error) } // fleetMCFService is the MCF access the fleet page needs: pending flags (for status) // plus the latest MCF per helicopter (for the mcf object / placeholder). type fleetMCFService interface { mcfPendingChecker LatestByHelicopterIDs(ctx context.Context, helicopterIDs [][]byte) (map[string]*mcf.MaintenanceCheckFlight, error) } type easaReleaseLister interface { GetLatestByHelicopter(ctx context.Context, helicopterID []byte) (*easarelease.EASARelease, error) } func NewFleetStatusHandler(svc fleetstatus.Service, deps FleetStatusDeps) *FleetStatusHandler { if svc == nil || deps.Helicopter == nil || deps.Complaint == nil || deps.EASA == nil || deps.History == nil || deps.MCF == nil || deps.Storage == nil { panic("fleet status handler: missing required dependency") } return &FleetStatusHandler{ svc: svc, helicopterSvc: deps.Helicopter, complaintSvc: deps.Complaint, easaSvc: deps.EASA, historySvc: deps.History, mcfSvc: deps.MCF, signoffSvc: deps.Signoff, storage: deps.Storage, validate: validators.New(), } } // CreateFleetStatus godoc // @Summary Create fleet status // @Description Create fleet status with maintenance schedules and file attachments. // @Tags Fleet Status // @Accept json // @Produce json // @Param request body requestdto.FleetStatusCreateRequest true "Fleet status create request" // @Success 201 {object} responsedto.FleetStatusResponse // @Failure 400 {object} jsonapi.ErrorDocument // @Failure 422 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/create [post] func (h *FleetStatusHandler) Create(c *fiber.Ctx) error { var req requestdto.FleetStatusCreateRequest if err := c.BodyParser(&req); err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidJSON, "", nil) } if errs := h.validate.ValidateStruct(req); errs != nil { return writeFleetStatusValidationErrors(c, fiber.StatusUnprocessableEntity, errs) } row, err := mapFleetStatusPayload(req.Data.Attributes, actorUserID(c)) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidPayload, "", nil) } if err := h.svc.Create(c.UserContext(), row); err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.Create", err) } created, err := h.svc.GetByID(c.UserContext(), row.ID) if err != nil { slog.WarnContext(c.UserContext(), "fleet status re-fetch after create failed", slog.Any("error", err), slog.String("id", idString(row.ID))) } if created == nil { created = row } createdStatus := h.deriveHelicopterStatus(c.UserContext(), created.Helicopter, created.HelicopterID) return response.Write(c, fiber.StatusCreated, fleetStatusDetailResource(c.UserContext(), h, created, createdStatus)) } // UpdateFleetStatus godoc // @Summary Update fleet status // @Description Update fleet status by ID with full replacement for schedules and files. // @Tags Fleet Status // @Accept json // @Produce json // @Param id path string true "Fleet status ID (UUIDv7)" // @Param request body requestdto.FleetStatusUpdateRequest true "Fleet status update request" // @Success 200 {object} responsedto.FleetStatusResponse // @Failure 400 {object} jsonapi.ErrorDocument // @Failure 404 {object} jsonapi.ErrorDocument // @Failure 422 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/update/{id} [patch] func (h *FleetStatusHandler) Update(c *fiber.Ctx) error { idStr := c.Params("id") id, err := uuidv7.ParseString(idStr) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "", &jsonapi.ErrorSource{Pointer: "/path/id"}) } var req requestdto.FleetStatusUpdateRequest if err := c.BodyParser(&req); err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidJSON, "", nil) } if errs := h.validate.ValidateStruct(req); errs != nil { return writeFleetStatusValidationErrors(c, fiber.StatusUnprocessableEntity, errs) } if strings.TrimSpace(req.Data.ID) == "" { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusIDRequired, "", &jsonapi.ErrorSource{Pointer: "/data/id"}) } if strings.TrimSpace(req.Data.ID) != idStr { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusIDMismatch, "", &jsonapi.ErrorSource{Pointer: "/data/id"}) } existing, err := h.svc.GetByID(c.UserContext(), id) if err != nil || existing == nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusNotFound, "", nil) } row, err := mergeFleetStatusPatch(existing, req.Data.Attributes, actorUserID(c)) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidPayload, "", nil) } if err := h.svc.Update(c.UserContext(), row); err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.Update", err) } if req.Data.Attributes.Summary != nil { if err := h.svc.ReviseSummary(c.UserContext(), row.HelicopterID, fleetSummaryManualInput(req.Data.Attributes.Summary), actorUserID(c)); err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.Update", err) } } updated, err := h.svc.GetByID(c.UserContext(), id) if err != nil { slog.WarnContext(c.UserContext(), "fleet status re-fetch after update failed", slog.Any("error", err), slog.String("id", idString(id))) } if updated == nil { updated = row } updatedStatus := h.deriveHelicopterStatus(c.UserContext(), updated.Helicopter, updated.HelicopterID) return response.Write(c, fiber.StatusOK, fleetStatusDetailResource(c.UserContext(), h, updated, updatedStatus)) } // DeleteFleetStatus godoc // @Summary Delete fleet status // @Description Soft delete fleet status by ID. // @Tags Fleet Status // @Produce json // @Param id path string true "Fleet status ID (UUIDv7)" // @Success 200 {object} responsedto.FleetStatusDeleteResponse // @Failure 400 {object} jsonapi.ErrorDocument // @Failure 422 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/delete/{id} [delete] func (h *FleetStatusHandler) Delete(c *fiber.Ctx) error { id, err := uuidv7.ParseString(c.Params("id")) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "", &jsonapi.ErrorSource{Pointer: "/path/id"}) } if err := h.svc.Delete(c.UserContext(), id, actorUserID(c)); err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.Delete", err) } return response.Write(c, fiber.StatusOK, map[string]any{"data": map[string]any{"type": "fleet_status_delete", "attributes": map[string]bool{"deleted": true}}}) } // GetFleetStatusByID godoc // @Summary Get fleet status by ID // @Tags Fleet Status // @Produce json // @Param id path string true "Fleet status ID (UUIDv7)" // @Success 200 {object} responsedto.FleetStatusResponse // @Failure 404 {object} jsonapi.ErrorDocument // @Failure 422 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/get/{id} [get] func (h *FleetStatusHandler) GetByID(c *fiber.Ctx) error { id, err := uuidv7.ParseString(c.Params("id")) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "", &jsonapi.ErrorSource{Pointer: "/path/id"}) } row, err := h.svc.GetByID(c.UserContext(), id) if err != nil || row == nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusNotFound, "", nil) } rowStatus := h.deriveHelicopterStatus(c.UserContext(), row.Helicopter, row.HelicopterID) return response.Write(c, fiber.StatusOK, fleetStatusDetailResource(c.UserContext(), h, row, rowStatus)) } // GetFleetStatusByHelicopterID godoc // @Summary Get latest fleet status by helicopter ID // @Tags Fleet Status // @Produce json // @Param helicopter_id path string true "Helicopter ID (UUIDv7)" // @Success 200 {object} responsedto.FleetStatusResponse // @Failure 404 {object} jsonapi.ErrorDocument // @Failure 422 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/get-by-helicopter/{helicopter_id} [get] func (h *FleetStatusHandler) GetByHelicopterID(c *fiber.Ctx) error { helicopterID, err := uuidv7.ParseString(c.Params("helicopter_id")) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "helicopter_id is invalid UUID", &jsonapi.ErrorSource{Pointer: "/path/helicopter_id"}) } latest, err := h.svc.LatestByHelicopterIDs(c.UserContext(), [][]byte{helicopterID}) if err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.GetByHelicopterID", err) } row := latest[string(helicopterID)] if row == nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusNotFound, "fleet status not found for helicopter", nil) } rowStatus := h.deriveHelicopterStatus(c.UserContext(), row.Helicopter, row.HelicopterID) return response.Write(c, fiber.StatusOK, fleetStatusDetailResource(c.UserContext(), h, row, rowStatus)) } // ListFleetStatus godoc // @Summary List fleet status // @Description Lists every helicopter from the master, each with its latest fleet status (or empty data when it has none). Paginated by helicopter. // @Tags Fleet Status // @Produce json // @Param page query int false "Page number" default(1) // @Param page_size query int false "Page size" default(20) // @Param filter query string false "Search helicopters by designation/identifier/type" // @Param sort query string false "Sort helicopters, example: designation" // @Param status query string false "Filter by operational status; comma-separated for multiple (e.g. available,booked). Values: available, booked, aog, mcf" // @Param helicopter_id query string false "Filter by helicopter ID (UUIDv7); combined with status uses AND logic" // @Success 200 {object} responsedto.FleetStatusListResponse // @Failure 400 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/get-all [get] func (h *FleetStatusHandler) List(c *fiber.Ctx) error { page, _ := strconv.Atoi(c.Query("page", "1")) if page < 1 { page = 1 } size, _ := strconv.Atoi(c.Query("page_size", "20")) if size < 1 { size = 20 } offset := (page - 1) * size statuses, err := parseHelicopterStatusFilter(c.Query("status")) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrInvalidRequest, "invalid status filter", nil) } // If helicopter_id is provided, fetch that single helicopter and apply status // filter in Go (AND logic). Pagination is irrelevant for a single result. if raw := strings.TrimSpace(c.Query("helicopter_id")); raw != "" { heliID, err := uuidv7.ParseString(raw) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrInvalidRequest, "invalid helicopter_id", &jsonapi.ErrorSource{Pointer: "/query/helicopter_id"}) } resources, err := h.singleHelicopterResources(c.UserContext(), heliID, statuses) if err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.List", err) } return response.WriteWithMeta(c, fiber.StatusOK, resources, map[string]any{ "page_number": page, "page_size": size, "total": int64(len(resources)), }) } var groundedIDs [][]byte if len(statuses) > 0 { groundedIDs = groundedHelicopterIDs(c.UserContext(), h.complaintSvc, time.Now().UTC()) } // Drive the listing from the helicopter master so each helicopter carries its // latest fleet status (or empty data when it has none). helis, total, err := h.helicopterSvc.List(c.UserContext(), c.Query("filter"), statuses, c.Query("sort"), size, offset, groundedIDs) if err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.List", err) } resources, err := h.fleetStatusResourcesForHelicopters(c.UserContext(), helis) if err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.List", err) } sortFleetStatusResourcesByStatus(resources) meta := map[string]any{ "page_number": page, "page_size": size, "total": total, } return response.WriteWithMeta(c, fiber.StatusOK, resources, meta) } // ListFleetStatusDatatable godoc // @Summary List fleet status (datatable) // @Description Lists every helicopter from the master, each with its latest fleet status (or empty data when it has none). Paginated by helicopter. // @Tags Fleet Status // @Produce json // @Param draw query int false "Datatable draw counter" default(1) // @Param start query int false "Datatable start offset" default(0) // @Param length query int false "Datatable page length" default(10) // @Param search query string false "Search helicopters by designation/identifier/type" // @Param sort query string false "Sort helicopters, example: designation" // @Param status query string false "Filter by operational status; comma-separated for multiple (e.g. available,booked). Values: available, booked, aog, mcf" // @Param helicopter_id query string false "Filter by helicopter ID (UUIDv7); combined with status uses AND logic" // @Success 200 {object} responsedto.FleetStatusDataTableResponse // @Failure 400 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/get-all/dt [get] func (h *FleetStatusHandler) ListDatatable(c *fiber.Ctx) error { start, _ := strconv.Atoi(c.Query("start", "0")) length, _ := strconv.Atoi(c.Query("length", "10")) draw, _ := strconv.Atoi(c.Query("draw", "1")) if length < 1 { length = 10 } statuses, err := parseHelicopterStatusFilter(c.Query("status")) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrInvalidRequest, "invalid status filter", nil) } // If helicopter_id is provided, fetch that single helicopter and apply status // filter in Go (AND logic). if raw := strings.TrimSpace(c.Query("helicopter_id")); raw != "" { heliID, err := uuidv7.ParseString(raw) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrInvalidRequest, "invalid helicopter_id", &jsonapi.ErrorSource{Pointer: "/query/helicopter_id"}) } resources, err := h.singleHelicopterResources(c.UserContext(), heliID, statuses) if err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.ListDatatable", err) } total := int64(len(resources)) return response.WriteWithMeta(c, fiber.StatusOK, resources, map[string]any{ "draw": draw, "records_total": total, "records_filtered": total, }) } var groundedIDs [][]byte if len(statuses) > 0 { groundedIDs = groundedHelicopterIDs(c.UserContext(), h.complaintSvc, time.Now().UTC()) } helis, total, err := h.helicopterSvc.List(c.UserContext(), c.Query("search"), statuses, c.Query("sort"), length, start, groundedIDs) if err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.ListDatatable", err) } resources, err := h.fleetStatusResourcesForHelicopters(c.UserContext(), helis) if err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.ListDatatable", err) } sortFleetStatusResourcesByStatus(resources) meta := map[string]any{ "draw": draw, "records_total": total, "records_filtered": total, } return response.WriteWithMeta(c, fiber.StatusOK, resources, meta) } func fleetStatusResourceStatus(r responsedto.FleetStatusResource) string { if r.Attributes.Helicopter != nil { return r.Attributes.Helicopter.Status } return "" } // sortFleetStatusResourcesByStatus stably orders the resources by status rank, keeping // the incoming (designation/sort) order within the same status. func sortFleetStatusResourcesByStatus(resources []responsedto.FleetStatusResource) { sort.SliceStable(resources, func(i, j int) bool { return helicopter.StatusRank(fleetStatusResourceStatus(resources[i])) < helicopter.StatusRank(fleetStatusResourceStatus(resources[j])) }) } // MarkFleetStatusServiced godoc // @Summary Mark fleet status serviced // @Description Move current maintenance schedules into service logs, then clear active schedules. // @Tags Fleet Status // @Produce json // @Param id path string true "Fleet status ID (UUIDv7)" // @Success 200 {object} responsedto.FleetStatusMarkServicedResponse // @Failure 400 {object} jsonapi.ErrorDocument // @Failure 404 {object} jsonapi.ErrorDocument // @Failure 422 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/mark-serviced/{id} [post] func (h *FleetStatusHandler) MarkServiced(c *fiber.Ctx) error { id, err := uuidv7.ParseString(c.Params("id")) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "", &jsonapi.ErrorSource{Pointer: "/path/id"}) } if err := h.svc.MarkServiced(c.UserContext(), id, time.Now().UTC(), actorUserID(c)); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusNotFound, "", nil) } return writeFleetStatusServiceError(c, "FleetStatusHandler.MarkServiced", err) } return response.Write(c, fiber.StatusOK, map[string]any{ "data": map[string]any{ "type": "fleet_status_mark_serviced", "attributes": map[string]any{ "serviced": true, }, }, }) } // ListFleetStatusServiceHistory godoc // @Summary List fleet history (timeline) by helicopter // @Description Unified timeline: maintenance servicing events merged with complaint/MEL/NSR/action/MCF/EASA/AOG action events, newest first. // @Tags Fleet Status // @Produce json // @Param helicopter_id path string true "Helicopter ID (UUIDv7)" // @Param page query int false "Page number" default(1) // @Param page_size query int false "Page size" default(20) // @Success 200 {object} responsedto.FleetHistoryListResponse // @Failure 400 {object} jsonapi.ErrorDocument // @Failure 422 {object} jsonapi.ErrorDocument // @Router /api/v1/fleet-status/history/{helicopter_id} [get] func (h *FleetStatusHandler) ListHistoryByHelicopter(c *fiber.Ctx) error { helicopterID, err := uuidv7.ParseString(c.Params("helicopter_id")) if err != nil { return writeFleetStatusAppError(c, apperrorsx.ErrFleetStatusInvalidID, "helicopter_id is invalid UUID", &jsonapi.ErrorSource{Pointer: "/path/helicopter_id"}) } page, _ := strconv.Atoi(c.Query("page", "1")) if page < 1 { page = 1 } size, _ := strconv.Atoi(c.Query("page_size", "20")) if size < 1 { size = 20 } offset := (page - 1) * size type timedEntry struct { at time.Time entry responsedto.FleetHistoryEntry } items := make([]timedEntry, 0) logs, _, err := h.svc.ListServiceHistoryByHelicopterID(c.UserContext(), helicopterID, 1000, 0) if err != nil { return writeFleetStatusServiceError(c, "FleetStatusHandler.ListHistoryByHelicopter", err) } for i := range logs { l := &logs[i] items = append(items, timedEntry{at: l.ServicedAt.UTC(), entry: responsedto.FleetHistoryEntry{ At: l.ServicedAt.UTC().Format(time.RFC3339), EntityType: "fleet_status", EntityID: idString(l.FleetStatusID), Action: "serviced", Message: "Serviced " + strings.TrimSpace(l.InspectionType+" "+l.Type), Actor: idString(l.CreatedBy), ActorName: userctx.GetDisplayName(c.UserContext(), l.CreatedBy), }}) } events, _, herr := h.historySvc.ListByHelicopter(c.UserContext(), helicopterID, 0, 0) if herr == nil { for i := range events { e := &events[i] items = append(items, timedEntry{at: e.CreatedAt.UTC(), entry: responsedto.FleetHistoryEntry{ At: e.CreatedAt.UTC().Format(time.RFC3339), EntityType: e.EntityType, EntityID: idString(e.EntityID), Action: e.Action, Message: e.Message, Actor: idString(e.Actor), ActorName: userctx.GetDisplayName(c.UserContext(), e.Actor), }}) } } sort.SliceStable(items, func(i, j int) bool { return items[i].at.After(items[j].at) }) total := int64(len(items)) start := offset if start > len(items) { start = len(items) } end := start + size if end > len(items) { end = len(items) } data := make([]responsedto.FleetHistoryEntry, 0, end-start) for _, it := range items[start:end] { data = append(data, it.entry) } res := responsedto.FleetHistoryListResponse{Data: data} res.Meta.PageNumber = page res.Meta.PageSize = size res.Meta.Total = total return response.Write(c, fiber.StatusOK, res) } // singleHelicopterResources fetches one helicopter by ID, builds its fleet // status resource, and applies an optional status filter (AND logic). Returns // an empty slice when the helicopter is not found or the status does not match. func (h *FleetStatusHandler) singleHelicopterResources(ctx context.Context, helicopterID []byte, statuses []string) ([]responsedto.FleetStatusResource, error) { heli, err := h.helicopterSvc.GetByID(ctx, helicopterID) if err != nil { return nil, err } if heli == nil { return []responsedto.FleetStatusResource{}, nil } resources, err := h.fleetStatusResourcesForHelicopters(ctx, []helicopter.Helicopter{*heli}) if err != nil { return nil, err } if len(statuses) > 0 && len(resources) > 0 { heliStatus := "" if resources[0].Attributes.Helicopter != nil { heliStatus = resources[0].Attributes.Helicopter.Status } for _, s := range statuses { if s == heliStatus { return resources, nil } } return []responsedto.FleetStatusResource{}, nil } return resources, nil } func writeFleetStatusAppError(c *fiber.Ctx, def apperrorsx.Def, detail string, source *jsonapi.ErrorSource) error { appErr := apperrorsx.New(def) if strings.TrimSpace(detail) != "" { appErr.Message = detail } if source != nil { appErr.Source = source } return apperrorsx.Write(c, appErr) } func writeFleetStatusValidationErrors(c *fiber.Ctx, status int, errs []jsonapi.ErrorObject) error { statusStr := strconv.Itoa(status) enriched := make([]jsonapi.ErrorObject, 0, len(errs)) for i := range errs { e := errs[i] e.Status = statusStr if strings.TrimSpace(e.Code) == "" || strings.TrimSpace(e.ErrorCode) == "" { code, errorCode := inferFleetStatusErrorCode(status, e.Title, e.Detail) if strings.TrimSpace(e.Code) == "" { e.Code = code } if strings.TrimSpace(e.ErrorCode) == "" { e.ErrorCode = errorCode } } enriched = append(enriched, e) } return jsonapi.WriteErrors(c, status, enriched) } func writeFleetStatusServiceError(c *fiber.Ctx, operation string, err error) error { mapped := apperrorsx.Translate(apperrorsx.ModuleFleetStatus, err) if mapped != nil && mapped.Code != apperrorsx.CodeInternalServerError { logFleetStatusServiceError(c, operation, mapped.Status, mapped.Code, mapped.ErrorCode, err) return apperrorsx.Write(c, mapped) } logFleetStatusServiceError(c, operation, fiber.StatusInternalServerError, apperrorsx.CodeInternalServerError, apperrorsx.ErrInternal.ErrorCode, err) appErr := apperrorsx.New(apperrorsx.ErrInternal) appErr.Message = "an internal error occurred" return apperrorsx.Write(c, appErr) } func logFleetStatusServiceError(c *fiber.Ctx, operation string, status int, code, errorCode string, err error) { requestID := requestIDFromCtx(c) args := []any{ slog.String("request_id", requestID), slog.String("operation", strings.TrimSpace(operation)), slog.Int("status", status), slog.String("code", strings.TrimSpace(code)), slog.String("error_code", strings.TrimSpace(errorCode)), slog.String("path", c.Path()), slog.Any("cause", err), } if status >= fiber.StatusInternalServerError { slog.ErrorContext(c.UserContext(), "fleet status service error", args...) return } slog.WarnContext(c.UserContext(), "fleet status service error", args...) } func inferFleetStatusErrorCode(status int, _ string, _ string) (string, string) { switch status { case fiber.StatusNotFound: return apperrorsx.CodeFleetStatusNotFound, apperrorsx.ErrFleetStatusNotFound.ErrorCode case fiber.StatusUnprocessableEntity: return apperrorsx.CodeFleetStatusInvalidPayload, apperrorsx.ErrFleetStatusInvalidPayload.ErrorCode case fiber.StatusBadRequest: return apperrorsx.CodeFleetStatusInvalidJSON, apperrorsx.ErrFleetStatusInvalidJSON.ErrorCode default: return apperrorsx.CodeInternalServerError, apperrorsx.ErrInternal.ErrorCode } }