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,67 @@
package dto
import responsedto "wucher/internal/transport/http/dto/response"
type AfterFlightInspectionAttributes struct {
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
FlightInspectionID string `json:"flight_inspection_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
FlightInspection *FlightInspectionResource `json:"flight_inspection,omitempty"`
Helicopter *responsedto.HelicopterResource `json:"helicopter,omitempty"`
MissionCode string `json:"mission_code,omitempty" example:"M-2026-001"`
MissionType []string `json:"mission_type,omitempty"`
TotalFlight int `json:"total_flight" example:"3"`
FlightDataCount int `json:"flight_data_count" example:"2"`
FlightTimeSeconds int64 `json:"flight_time_seconds" example:"9000"`
Route []string `json:"route"`
OilEngineNR1Checked *string `json:"oil_engine_nr1_checked" example:"1"`
OilEngineNR2Checked *string `json:"oil_engine_nr2_checked" example:"1"`
HydraulicLHChecked *string `json:"hydraulic_lh_checked" example:"1"`
HydraulicRHChecked *string `json:"hydraulic_rh_checked" example:"1"`
OilTransmissionMGBChecked *string `json:"oil_transmission_mgb_checked" example:"1"`
OilTransmissionIGBChecked *string `json:"oil_transmission_igb_checked" example:"1"`
OilTransmissionTGBChecked *string `json:"oil_transmission_tgb_checked" example:"1"`
Note *string `json:"note" example:"All checks completed"`
Total int `json:"total" example:"3"`
Completed int `json:"completed" example:"1"`
DoneFileChecklist []AfterFlightInspectionFileChecklistItem `json:"done_file_checklist,omitempty"`
FileChecklist []AfterFlightInspectionFileChecklistItem `json:"file_checklist"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-09T05:00:00Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-09T05:00:00Z"`
}
type AfterFlightInspectionFileChecklistItem struct {
HelicopterFileID string `json:"helicopter_file_id" example:"019d7000-aaaa-7bbb-8ccc-111111111111"`
FileName string `json:"file_name,omitempty" example:"After Flight Checklist.pdf"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
IsDone bool `json:"is_done" example:"false"`
}
type AfterFlightInspectionResource struct {
Type string `json:"type" example:"after_flight_inspection"`
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Attributes AfterFlightInspectionAttributes `json:"attributes"`
}
type AfterFlightInspectionResponse struct {
Data AfterFlightInspectionResource `json:"data"`
}
type AfterFlightInspectionUpsertAttributes struct {
OilEngineNR1Checked *string `json:"oil_engine_nr1_checked,omitempty"`
OilEngineNR2Checked *string `json:"oil_engine_nr2_checked,omitempty"`
HydraulicLHChecked *string `json:"hydraulic_lh_checked,omitempty"`
HydraulicRHChecked *string `json:"hydraulic_rh_checked,omitempty"`
OilTransmissionMGBChecked *string `json:"oil_transmission_mgb_checked,omitempty"`
OilTransmissionIGBChecked *string `json:"oil_transmission_igb_checked,omitempty"`
OilTransmissionTGBChecked *string `json:"oil_transmission_tgb_checked,omitempty"`
Note *string `json:"note,omitempty"`
}
type AfterFlightInspectionUpsertData struct {
Type string `json:"type" validate:"required,oneof=after_flight_inspection_upsert after_flight_inspection"`
Attributes AfterFlightInspectionUpsertAttributes `json:"attributes" validate:"required"`
}
type AfterFlightInspectionUpsertRequest struct {
Data AfterFlightInspectionUpsertData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,35 @@
package dto
// JSON:API Audit DTOs
type AuditLogAttributes struct {
RequestID string `json:"request_id,omitempty" example:"req-12345"`
ActorUserID string `json:"actor_user_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Layer string `json:"layer" example:"api"`
Module string `json:"module" example:"reserve_ac"`
Action string `json:"action" example:"http.request"`
Method string `json:"method,omitempty" example:"GET"`
Path string `json:"path,omitempty" example:"/api/v1/users/get-all"`
StatusCode int `json:"status_code,omitempty" example:"200"`
Success bool `json:"success" example:"true"`
IP string `json:"ip,omitempty" example:"127.0.0.1"`
UserAgent string `json:"user_agent,omitempty" example:"Mozilla/5.0"`
Message string `json:"message,omitempty" example:"HTTP 200"`
Metadata interface{} `json:"metadata,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-11T12:00:00Z"`
}
type AuditLogResource struct {
Type string `json:"type" example:"audit_log"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes AuditLogAttributes `json:"attributes"`
}
type AuditLogListResponse struct {
Data []AuditLogResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}

View File

@@ -0,0 +1,813 @@
package dto
import "encoding/json"
// JSON:API Request/Response DTOs for Auth
// Register
type RegisterAttributes struct {
Email string `json:"email" validate:"required,email" example:"user@example.com"`
Username string `json:"username" validate:"required,min=3,max=100" example:"john.doe"`
Password string `json:"password" validate:"required,min=8" example:"StrongP@ssw0rd"`
FirstName string `json:"first_name" validate:"required" example:"John"`
LastName string `json:"last_name" validate:"required" example:"Doe"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+628123456789"`
}
type RegisterData struct {
Type string `json:"type" validate:"required,eq=auth_register" example:"auth_register"`
Attributes RegisterAttributes `json:"attributes" validate:"required"`
}
type RegisterRequest struct {
Data RegisterData `json:"data" validate:"required"`
}
// Invite user
type InviteAttributes struct {
Email string `json:"email" validate:"required,email" example:"user@example.com"`
Username string `json:"username" validate:"required,min=3,max=100" example:"john.doe"`
FirstName string `json:"first_name" validate:"required" example:"John"`
LastName string `json:"last_name" validate:"required" example:"Doe"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+628123456789"`
RoleID string `json:"role_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
RoleIDs []string `json:"role_ids,omitempty" example:"[\"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d\",\"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9e\"]"`
}
type InviteData struct {
Type string `json:"type" validate:"required,eq=auth_invite" example:"auth_invite"`
Attributes InviteAttributes `json:"attributes" validate:"required"`
}
type InviteRequest struct {
Data InviteData `json:"data" validate:"required"`
}
// Set password from invite token
type SetPasswordAttributes struct {
Token string `json:"token" validate:"required" example:"kP8k2..."`
Password string `json:"password" validate:"required,min=8" example:"StrongP@ssw0rd"`
}
type SetPasswordData struct {
Type string `json:"type" validate:"required,eq=auth_set_password" example:"auth_set_password"`
Attributes SetPasswordAttributes `json:"attributes" validate:"required"`
}
type SetPasswordRequest struct {
Data SetPasswordData `json:"data" validate:"required"`
}
// Resend set-password invite
type ResendSetPasswordInviteAttributes struct {
UserID string `json:"user_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type ResendSetPasswordInviteData struct {
Type string `json:"type" validate:"required,eq=auth_resend_set_password" example:"auth_resend_set_password"`
Attributes ResendSetPasswordInviteAttributes `json:"attributes" validate:"required"`
}
type ResendSetPasswordInviteRequest struct {
Data ResendSetPasswordInviteData `json:"data" validate:"required"`
}
// Forgot password
type ForgotPasswordAttributes struct {
Email string `json:"email" validate:"required,email" example:"user@example.com"`
}
type ForgotPasswordData struct {
Type string `json:"type" validate:"required,eq=auth_forgot_password" example:"auth_forgot_password"`
Attributes ForgotPasswordAttributes `json:"attributes" validate:"required"`
}
type ForgotPasswordRequest struct {
Data ForgotPasswordData `json:"data" validate:"required"`
}
// Reset password
type ResetPasswordAttributes struct {
Token string `json:"token" validate:"required" example:"kP8k2..."`
NewPassword string `json:"new_password" validate:"required,min=8" example:"StrongP@ssw0rd!LongPassphrase"`
ConfirmPassword string `json:"confirm_password" validate:"required,min=8" example:"StrongP@ssw0rd!LongPassphrase"`
}
type ResetPasswordData struct {
Type string `json:"type" validate:"required,eq=auth_reset_password" example:"auth_reset_password"`
Attributes ResetPasswordAttributes `json:"attributes" validate:"required"`
}
type ResetPasswordRequest struct {
Data ResetPasswordData `json:"data" validate:"required"`
}
// Setup security PIN
type SecurityPINSetupAttributes struct {
PIN string `json:"pin" validate:"required,len=6,numeric" example:"123456"`
ConfirmPIN string `json:"confirm_pin" validate:"required,len=6,numeric" example:"123456"`
}
type SecurityPINSetupData struct {
Type string `json:"type" validate:"required,eq=auth_pin_setup" example:"auth_pin_setup"`
Attributes SecurityPINSetupAttributes `json:"attributes" validate:"required"`
}
type SecurityPINSetupRequest struct {
Data SecurityPINSetupData `json:"data" validate:"required"`
}
type SecurityPINSetupResponse struct {
Data struct {
Type string `json:"type" example:"auth_pin_setup"`
Attributes struct {
Configured bool `json:"configured" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
// Verify security PIN
type SecurityPINVerifyAttributes struct {
PIN string `json:"pin" validate:"required,len=6,numeric" example:"123456"`
Action string `json:"action" validate:"required,uuid4" example:"a546de2f-f1e6-4c2c-ab03-9b4bf39f4d74"`
}
type SecurityPINVerifyData struct {
Type string `json:"type" validate:"required,eq=auth_pin_verify" example:"auth_pin_verify"`
Attributes SecurityPINVerifyAttributes `json:"attributes" validate:"required"`
}
type SecurityPINVerifyRequest struct {
Data SecurityPINVerifyData `json:"data" validate:"required"`
}
type SecurityPINVerifyResponse struct {
Data struct {
Type string `json:"type" example:"auth_pin_verify"`
Attributes struct {
Verified bool `json:"verified" example:"true"`
ActionToken string `json:"action_token" example:"pinv_1234567890abcdef"`
ActionTokenTTLMS int64 `json:"action_token_ttl_ms" example:"900000"`
} `json:"attributes"`
} `json:"data"`
}
// Change security PIN
type SecurityPINChangeAttributes struct {
CurrentPIN string `json:"current_pin" validate:"required,len=6,numeric" example:"123456"`
NewPIN string `json:"new_pin" validate:"required,len=6,numeric" example:"654321"`
ConfirmPIN string `json:"confirm_pin" validate:"required,len=6,numeric" example:"654321"`
}
type SecurityPINChangeData struct {
Type string `json:"type" validate:"required,eq=auth_pin_change" example:"auth_pin_change"`
Attributes SecurityPINChangeAttributes `json:"attributes" validate:"required"`
}
type SecurityPINChangeRequest struct {
Data SecurityPINChangeData `json:"data" validate:"required"`
}
// Forgot security PIN
type ForgotSecurityPINAttributes struct {
Email string `json:"email" validate:"required,email" example:"user@example.com"`
}
type ForgotSecurityPINData struct {
Type string `json:"type" validate:"required,eq=auth_forgot_pin" example:"auth_forgot_pin"`
Attributes ForgotSecurityPINAttributes `json:"attributes" validate:"required"`
}
type ForgotSecurityPINRequest struct {
Data ForgotSecurityPINData `json:"data" validate:"required"`
}
// Request security PIN reset
type RequestSecurityPINAttributes struct {
Method string `json:"method,omitempty" example:"password"`
Password string `json:"password,omitempty" example:"StrongP@ssw0rd"`
ReauthToken string `json:"reauth_token,omitempty" example:"sso_reauth_1234567890abcdef"`
}
type RequestSecurityPINData struct {
Type string `json:"type" validate:"required,eq=auth_pin_request_reset" example:"auth_pin_request_reset"`
Attributes RequestSecurityPINAttributes `json:"attributes" validate:"required"`
}
type RequestSecurityPINRequest struct {
Data RequestSecurityPINData `json:"data" validate:"required"`
}
type RequestSecurityPINResetResponse struct {
Data struct {
Type string `json:"type" example:"auth_pin_request_reset"`
Attributes struct {
Message string `json:"message" example:"If allowed, a security PIN reset link has been sent to your email."`
} `json:"attributes"`
} `json:"data"`
}
// Reset security PIN
type ResetSecurityPINAttributes struct {
Token string `json:"token,omitempty" example:"kP8k2..."`
Method string `json:"method,omitempty" example:"password"`
Password string `json:"password,omitempty" example:"StrongP@ssw0rd"`
ReauthToken string `json:"reauth_token,omitempty" example:"sso_reauth_1234567890abcdef"`
NewPIN string `json:"new_pin" validate:"required,len=6,numeric" example:"123456"`
ConfirmPIN string `json:"confirm_pin" validate:"required,len=6,numeric" example:"123456"`
}
type ResetSecurityPINData struct {
Type string `json:"type" validate:"required,eq=auth_reset_pin" example:"auth_reset_pin"`
Attributes ResetSecurityPINAttributes `json:"attributes" validate:"required"`
}
type ResetSecurityPINRequest struct {
Data ResetSecurityPINData `json:"data" validate:"required"`
}
// Login
type LoginAttributes struct {
Email string `json:"email" validate:"required,email" example:"user@example.com"`
Password string `json:"password" validate:"required" example:"StrongP@ssw0rd"`
}
type LoginData struct {
Type string `json:"type" validate:"required,eq=auth_login" example:"auth_login"`
Attributes LoginAttributes `json:"attributes" validate:"required"`
}
type LoginRequest struct {
Data LoginData `json:"data" validate:"required"`
}
// Exchange frontend Microsoft token result into backend session tokens
type ExchangeIDTokenClaims struct {
Aud string `json:"aud,omitempty" example:"12345678-1234-1234-1234-123456789012"`
Iss string `json:"iss,omitempty" example:"https://login.microsoftonline.com/abc-tenant-id/v2.0"`
Iat int64 `json:"iat,omitempty" example:"1715600000"`
Nbf int64 `json:"nbf,omitempty" example:"1715600000"`
Exp int64 `json:"exp,omitempty" example:"1715603600"`
Name string `json:"name,omitempty" example:"Andrio Effendi"`
PreferredUsername string `json:"preferred_username,omitempty" example:"andrio@mybit-innovation.com"`
Email string `json:"email,omitempty" example:"andrio@mybit-innovation.com"`
OID string `json:"oid,omitempty" example:"00000000-0000-0000-1234-567890abcdef"`
Sub string `json:"sub,omitempty" example:"AbCdEfGhIjKlMnOpQrStUvWxYz"`
TID string `json:"tid,omitempty" example:"abc-tenant-id"`
Ver string `json:"ver,omitempty" example:"2.0"`
}
type ExchangeAttributes struct {
AccessToken string `json:"access_token" validate:"required" example:"eyJ0eXAiOiJKV1Qi..."`
IDToken string `json:"id_token" validate:"required" example:"eyJ0eXAiOiJKV1Qi..."`
IDTokenClaims ExchangeIDTokenClaims `json:"id_token_claims"`
}
type ExchangeData struct {
Type string `json:"type" validate:"required,eq=auth_exchange" example:"auth_exchange"`
Attributes ExchangeAttributes `json:"attributes" validate:"required"`
}
type ExchangeRequest struct {
Data ExchangeData `json:"data" validate:"required"`
}
func (c ExchangeIDTokenClaims) ToMap() map[string]any {
out := map[string]any{}
if c.Aud != "" {
out["aud"] = c.Aud
}
if c.Iss != "" {
out["iss"] = c.Iss
}
if c.Iat > 0 {
out["iat"] = c.Iat
}
if c.Nbf > 0 {
out["nbf"] = c.Nbf
}
if c.Exp > 0 {
out["exp"] = c.Exp
}
if c.Name != "" {
out["name"] = c.Name
}
if c.PreferredUsername != "" {
out["preferred_username"] = c.PreferredUsername
}
if c.Email != "" {
out["email"] = c.Email
}
if c.OID != "" {
out["oid"] = c.OID
}
if c.Sub != "" {
out["sub"] = c.Sub
}
if c.TID != "" {
out["tid"] = c.TID
}
if c.Ver != "" {
out["ver"] = c.Ver
}
return out
}
// WebAuthn register begin
type WebAuthnRegisterBeginData struct {
Type string `json:"type" validate:"required,eq=auth_webauthn_register_begin" example:"auth_webauthn_register_begin"`
}
type WebAuthnRegisterBeginRequest struct {
Data WebAuthnRegisterBeginData `json:"data" validate:"required"`
}
// WebAuthn register finish
type WebAuthnRegisterFinishAttributes struct {
ChallengeToken string `json:"challenge_token" validate:"required" example:"kP8k2..."`
Credential json.RawMessage `json:"credential" validate:"required"`
Name string `json:"name,omitempty" validate:"omitempty,max=100" example:"MacBook Touch ID"`
}
type WebAuthnRegisterFinishData struct {
Type string `json:"type" validate:"required,eq=auth_webauthn_register_finish" example:"auth_webauthn_register_finish"`
Attributes WebAuthnRegisterFinishAttributes `json:"attributes" validate:"required"`
}
type WebAuthnRegisterFinishRequest struct {
Data WebAuthnRegisterFinishData `json:"data" validate:"required"`
}
// WebAuthn login begin
type WebAuthnLoginBeginAttributes struct {
Email string `json:"email" validate:"required,email" example:"user@example.com"`
}
type WebAuthnLoginBeginData struct {
Type string `json:"type" validate:"required,eq=auth_webauthn_login_begin" example:"auth_webauthn_login_begin"`
Attributes WebAuthnLoginBeginAttributes `json:"attributes" validate:"required"`
}
type WebAuthnLoginBeginRequest struct {
Data WebAuthnLoginBeginData `json:"data" validate:"required"`
}
// WebAuthn login finish
type WebAuthnLoginFinishAttributes struct {
ChallengeToken string `json:"challenge_token" validate:"required" example:"kP8k2..."`
Credential json.RawMessage `json:"credential" validate:"required"`
}
type WebAuthnLoginFinishData struct {
Type string `json:"type" validate:"required,eq=auth_webauthn_login_finish" example:"auth_webauthn_login_finish"`
Attributes WebAuthnLoginFinishAttributes `json:"attributes" validate:"required"`
}
type WebAuthnLoginFinishRequest struct {
Data WebAuthnLoginFinishData `json:"data" validate:"required"`
}
// Sensitive-action re-auth attributes
type ReauthAttributes struct {
Method string `json:"method" validate:"required,oneof=pin password" example:"pin"`
Password string `json:"password,omitempty" validate:"omitempty,min=1" example:"StrongP@ssw0rd"`
}
// WebAuthn reset setup
type WebAuthnResetSetupData struct {
Type string `json:"type" validate:"required,eq=auth_webauthn_reset_setup" example:"auth_webauthn_reset_setup"`
Attributes ReauthAttributes `json:"attributes" validate:"required"`
}
type WebAuthnResetSetupRequest struct {
Data WebAuthnResetSetupData `json:"data" validate:"required"`
}
type WebAuthnResetSetupResponse struct {
Data struct {
Type string `json:"type" example:"auth_webauthn_reset_setup"`
Attributes struct {
Reset bool `json:"reset" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
// Login response when TOTP required
type LoginTOTPRequiredResponse struct {
Data struct {
Type string `json:"type" example:"auth_totp_required"`
Attributes struct {
RequiresTOTP bool `json:"requires_totp" example:"true"`
ChallengeToken string `json:"challenge_token" example:"kP8k2..."`
TOTPEnabled bool `json:"totp_enabled" example:"true"`
EmailOTPEnabled bool `json:"email_otp_enabled" example:"false"`
EmailOTPChallengeToken string `json:"email_otp_challenge_token,omitempty" example:"qA9j1..."`
EmailOTPResendCooldownSec int `json:"email_otp_resend_cooldown_seconds,omitempty" example:"60"`
EmailOTPMaxResendPerHour int `json:"email_otp_max_resend_per_hour,omitempty" example:"5"`
WebAuthnConfigured bool `json:"webauthn_configured" example:"false"`
LinkedSSO bool `json:"linked_sso" example:"false"`
PINConfigured bool `json:"pin_configured" example:"false"`
} `json:"attributes"`
} `json:"data"`
}
type LoginEmailOTPRequiredResponse struct {
Data struct {
Type string `json:"type" example:"auth_email_otp_required"`
Attributes struct {
RequiresEmailOTP bool `json:"requires_email_otp" example:"true"`
ChallengeToken string `json:"challenge_token" example:"kP8k2..."`
ResendCooldownSeconds int `json:"resend_cooldown_seconds" example:"60"`
MaxResendPerHour int `json:"max_resend_per_hour" example:"5"`
RequiresTOTPSetup bool `json:"requires_totp_setup" example:"true"`
WebAuthnConfigured bool `json:"webauthn_configured" example:"false"`
LinkedSSO bool `json:"linked_sso" example:"false"`
PINConfigured bool `json:"pin_configured" example:"false"`
} `json:"attributes"`
} `json:"data"`
}
type EmailOTPSendAttributes struct {
ChallengeToken string `json:"challenge_token" validate:"required" example:"kP8k2..."`
}
type EmailOTPSendData struct {
Type string `json:"type" validate:"required,eq=auth_email_otp_send" example:"auth_email_otp_send"`
Attributes EmailOTPSendAttributes `json:"attributes" validate:"required"`
}
type EmailOTPSendRequest struct {
Data EmailOTPSendData `json:"data" validate:"required"`
}
type EmailOTPVerifyAttributes struct {
ChallengeToken string `json:"challenge_token" validate:"required" example:"kP8k2..."`
Code string `json:"code" validate:"required,len=6,numeric" example:"123456"`
}
type EmailOTPVerifyData struct {
Type string `json:"type" validate:"required,eq=auth_email_otp_verify" example:"auth_email_otp_verify"`
Attributes EmailOTPVerifyAttributes `json:"attributes" validate:"required"`
}
type EmailOTPVerifyRequest struct {
Data EmailOTPVerifyData `json:"data" validate:"required"`
}
type EmailOTPSendResponse struct {
Data struct {
Type string `json:"type" example:"auth_email_otp_send"`
Attributes struct {
Sent bool `json:"sent" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
type EmailOTPVerifyResponse struct {
Data struct {
Type string `json:"type" example:"auth_email_otp_verify"`
Attributes struct {
Verified bool `json:"verified" example:"true"`
RequiresTOTPSetup bool `json:"requires_totp_setup" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
// Email verify response
type VerifyEmailResponse struct {
Data struct {
Type string `json:"type" example:"auth_verify_email"`
Attributes struct {
Verified bool `json:"verified" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
// Microsoft login
type MicrosoftLoginResponse struct {
Data struct {
Type string `json:"type" example:"auth_microsoft_url"`
Attributes struct {
URL string `json:"url" example:"https://login.microsoftonline.com/..."`
RedirectURL string `json:"redirect_url" example:"https://login.microsoftonline.com/..."`
} `json:"attributes"`
} `json:"data"`
}
// Microsoft callback
type MicrosoftCallbackResponse struct {
Data struct {
Type string `json:"type" example:"auth_identity"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes struct {
Provider string `json:"provider" example:"microsoft"`
ProviderSubject string `json:"provider_subject" example:"00000000-0000-0000-0000-000000000000"`
Email string `json:"email" example:"user@example.com"`
} `json:"attributes"`
} `json:"data"`
}
type SSOLinkAttributes struct {
Provider string `json:"provider" validate:"required,eq=microsoft" example:"microsoft"`
Code string `json:"code" validate:"required" example:"0.AXkA..."`
}
type SSOLinkData struct {
Type string `json:"type" validate:"required,eq=auth_sso_link" example:"auth_sso_link"`
Attributes SSOLinkAttributes `json:"attributes" validate:"required"`
}
type SSOLinkRequest struct {
Data SSOLinkData `json:"data" validate:"required"`
}
type SSOLinkResponse struct {
Data struct {
Type string `json:"type" example:"auth_sso_link"`
Attributes struct {
Provider string `json:"provider" example:"microsoft"`
ProviderSubject string `json:"provider_subject" example:"00000000-0000-0000-0000-000000000000"`
Email string `json:"email" example:"user@example.com"`
} `json:"attributes"`
} `json:"data"`
}
type SSOUnlinkAttributes struct {
Provider string `json:"provider" validate:"required,eq=microsoft" example:"microsoft"`
}
type SSOUnlinkData struct {
Type string `json:"type" validate:"required,eq=auth_sso_unlink" example:"auth_sso_unlink"`
Attributes SSOUnlinkAttributes `json:"attributes" validate:"required"`
}
type SSOUnlinkRequest struct {
Data SSOUnlinkData `json:"data" validate:"required"`
}
type SSOUnlinkResponse struct {
Data struct {
Type string `json:"type" example:"auth_sso_unlink"`
Attributes struct {
Provider string `json:"provider" example:"microsoft"`
Unlinked bool `json:"unlinked" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
// WebAuthn begin response
type WebAuthnBeginResponse struct {
Data struct {
Type string `json:"type" example:"auth_webauthn_register_options"`
Attributes struct {
ChallengeToken string `json:"challenge_token" example:"kP8k2..."`
ChallengeTTLMS int64 `json:"challenge_ttl_ms" example:"300000"`
PublicKey map[string]interface{} `json:"public_key"`
} `json:"attributes"`
} `json:"data"`
}
// TOTP setup
type TOTPSetupAttributes struct {
UserID string `json:"user_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Email string `json:"email" validate:"required,email" example:"user@example.com"`
}
type TOTPSetupData struct {
Type string `json:"type" validate:"required,eq=auth_totp_setup" example:"auth_totp_setup"`
Attributes TOTPSetupAttributes `json:"attributes" validate:"required"`
}
type TOTPSetupRequest struct {
Data TOTPSetupData `json:"data" validate:"required"`
}
type TOTPSetupResponse struct {
Data struct {
Type string `json:"type" example:"auth_totp_setup"`
Attributes struct {
Secret string `json:"secret" example:"JBSWY3DPEHPK3PXP"`
OtpauthURL string `json:"otpauth_url" example:"otpauth://totp/Wucher:user@example.com?..."`
Issuer string `json:"issuer" example:"Wucher"`
} `json:"attributes"`
} `json:"data"`
}
// TOTP verify
type TOTPVerifyAttributes struct {
ChallengeToken string `json:"challenge_token" validate:"required" example:"kP8k2..."`
Code string `json:"code" validate:"required,len=6" example:"123456"`
}
type TOTPVerifyData struct {
Type string `json:"type" validate:"required,eq=auth_totp_verify" example:"auth_totp_verify"`
Attributes TOTPVerifyAttributes `json:"attributes" validate:"required"`
}
type TOTPVerifyRequest struct {
Data TOTPVerifyData `json:"data" validate:"required"`
}
// TOTP confirm (enable)
type TOTPConfirmAttributes struct {
UserID string `json:"user_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Code string `json:"code" validate:"required,len=6" example:"123456"`
}
type TOTPConfirmData struct {
Type string `json:"type" validate:"required,eq=auth_totp_confirm" example:"auth_totp_confirm"`
Attributes TOTPConfirmAttributes `json:"attributes" validate:"required"`
}
type TOTPConfirmRequest struct {
Data TOTPConfirmData `json:"data" validate:"required"`
}
// Disable TOTP
type TOTPDisableAttributes struct {
UserID string `json:"user_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type TOTPDisableData struct {
Type string `json:"type" validate:"required,eq=auth_totp_disable" example:"auth_totp_disable"`
Attributes TOTPDisableAttributes `json:"attributes" validate:"required"`
}
type TOTPDisableRequest struct {
Data TOTPDisableData `json:"data" validate:"required"`
}
// TOTP reset setup
type TOTPResetSetupData struct {
Type string `json:"type" validate:"required,eq=auth_totp_reset_setup" example:"auth_totp_reset_setup"`
Attributes ReauthAttributes `json:"attributes" validate:"required"`
}
type TOTPResetSetupRequest struct {
Data TOTPResetSetupData `json:"data" validate:"required"`
}
type TOTPResetSetupResponse struct {
Data struct {
Type string `json:"type" example:"auth_totp_reset_setup"`
Attributes struct {
Reset bool `json:"reset" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
type AuthSessionItem struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeviceName string `json:"device_name" example:"Chrome on macOS"`
UserAgent string `json:"user_agent,omitempty" example:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..."`
IPAddress string `json:"ip_address,omitempty" example:"127.0.0.1"`
CreatedAt string `json:"created_at" example:"2026-03-13T12:00:00Z"`
LastActiveAt string `json:"last_active_at" example:"2026-03-13T12:10:00Z"`
ActiveNow bool `json:"active_now" example:"true"`
}
type AuthSessionsResponse struct {
Data struct {
Type string `json:"type" example:"auth_sessions"`
Attributes struct {
Sessions []AuthSessionItem `json:"sessions"`
} `json:"attributes"`
} `json:"data"`
}
type AuthSessionDeleteResponse struct {
Data struct {
Type string `json:"type" example:"auth_session_delete"`
Attributes struct {
Deleted bool `json:"deleted" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
// Update timezone
type TimezoneUpdateAttributes struct {
Timezone string `json:"timezone" validate:"required" example:"Asia/Jakarta"`
}
type TimezoneUpdateData struct {
Type string `json:"type" validate:"required,eq=auth_timezone_update" example:"auth_timezone_update"`
Attributes TimezoneUpdateAttributes `json:"attributes" validate:"required"`
}
type TimezoneUpdateRequest struct {
Data TimezoneUpdateData `json:"data" validate:"required"`
}
// Token response
type TokenResponse struct {
Data struct {
Type string `json:"type" example:"auth_tokens"`
Attributes struct {
AccessTokenExpiresIn int64 `json:"access_expires_in" example:"900"`
RefreshTokenExpiresIn int64 `json:"refresh_expires_in" example:"2592000"`
} `json:"attributes"`
} `json:"data"`
}
// User response
type UserAttributes struct {
Email string `json:"email" example:"user@example.com"`
Username string `json:"username,omitempty" example:"john.doe"`
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+628123456789"`
Timezone string `json:"timezone" example:"Asia/Jakarta"`
ProfilePicture *UserImage `json:"profile_picture,omitempty"`
RoleID string `json:"role_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
RoleIDs []string `json:"role_ids,omitempty" example:"[\"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d\",\"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9e\"]"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
EmailVerifiedAt string `json:"email_verified_at,omitempty" example:"2026-02-05T10:00:00Z"`
TOTPEnabled bool `json:"totp_enabled" example:"false"`
IsPreviouslyConfigured bool `json:"is_previously_configured" example:"false"`
WebAuthnConfigured bool `json:"webauthn_configured" example:"false"`
LinkedSSO bool `json:"linked_sso" example:"false"`
PINConfigured bool `json:"pin_configured" example:"false"`
Role RoleMini `json:"role,omitempty"`
Roles []RoleMini `json:"roles,omitempty"`
}
type UserImage struct {
UUID string `json:"uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9f"`
DownloadURL string `json:"download_url,omitempty" example:"https://example.com/presigned-url"`
ThumbnailDownloadURL string `json:"thumbnail_download_url,omitempty" example:"https://example.com/presigned-thumb-url"`
OriginalSizeBytes *int64 `json:"original_size_bytes,omitempty" example:"1024000"`
}
type UserFoto struct {
UUID string `json:"uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9f"`
DownloadURL string `json:"download_url,omitempty" example:"https://storage.example.com/base/lude.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=abc123"`
ThumbnailDownloadURL string `json:"thumbnail_download_url,omitempty" example:"https://storage.example.com/base/lude.thumb.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=abc123"`
OriginalSizeBytes *int64 `json:"original_size_bytes,omitempty" example:"1024000"`
}
type RoleMini struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" example:"admin"`
Description string `json:"description,omitempty" example:"Full access to all resources."`
}
type PermissionMini struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9a"`
Name string `json:"name" example:"View Users"`
Key string `json:"key" example:"users.read"`
Description string `json:"description,omitempty" example:"Permission to view users."`
RequiresPIN bool `json:"requires_pin" example:"false"`
}
// Auth current-user payload used by /auth/login and /auth/me endpoints.
type AuthCurrentUserAttributes struct {
Email string `json:"email" example:"user@example.com"`
EmailSSO string `json:"email_sso,omitempty" example:"user.sso@example.com"`
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+628123456789"`
Timezone string `json:"timezone" example:"Asia/Jakarta"`
Foto *UserFoto `json:"foto,omitempty"`
EmailVerifiedAt string `json:"email_verified_at,omitempty" example:"2026-02-05T10:00:00Z"`
Roles []RoleMini `json:"roles,omitempty"`
Permissions []PermissionMini `json:"permissions,omitempty"`
PermissionModules []PermissionGroupResource `json:"permission_modules,omitempty"`
TOTPEnabled bool `json:"totp_enabled" example:"false"`
IsPreviouslyConfigured bool `json:"is_previously_configured" example:"false"`
WebAuthnConfigured bool `json:"webauthn_configured" example:"false"`
LinkedSSO bool `json:"linked_sso" example:"false"`
PINConfigured bool `json:"pin_configured" example:"false"`
PINBlocked bool `json:"pin_blocked" example:"false"`
LoginMethod string `json:"login_method,omitempty" example:"email"`
AuthProvider string `json:"auth_provider,omitempty" example:"email"`
MicrosoftScope string `json:"microsoft_scope,omitempty" example:"openid profile email offline_access User.Read"`
DULBases []DULBase `json:"dul_bases,omitempty"`
}
type AuthCurrentUserResponse struct {
Data struct {
Type string `json:"type" example:"user"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes AuthCurrentUserAttributes `json:"attributes"`
} `json:"data"`
}
type UserResponse struct {
Data struct {
Type string `json:"type" example:"user"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes UserAttributes `json:"attributes"`
} `json:"data"`
}
// Error response for Swagger
type ErrorResponse struct {
Errors []struct {
Status string `json:"status" example:"422"`
Title string `json:"title" example:"Validation error"`
Detail string `json:"detail" example:"email is required"`
Source struct {
Pointer string `json:"pointer" example:"/data/attributes/email"`
} `json:"source"`
} `json:"errors"`
}

View File

@@ -0,0 +1,569 @@
package dto
import (
"context"
"strings"
"time"
"wucher/internal/domain/base"
"wucher/internal/shared/pkg/userctx"
"wucher/internal/shared/pkg/uuidv7"
)
// JSON:API Base DTOs
type BaseAttributes struct {
BaseName string `json:"base" example:"Lude"`
BaseAbbreviation string `json:"base_abbreviation,omitempty" example:"LOIG"`
BaseCategory string `json:"base_category,omitempty" example:"regular" enums:"regular,hems"`
Address string `json:"address,omitempty" example:"Lude Airfield"`
Latitude float64 `json:"latitude" example:"47.3769"`
Longitude float64 `json:"longitude" example:"8.5417"`
LandlineNumber string `json:"landline_number,omitempty" example:"+62-21-1234"`
MobileNumber string `json:"mobile_number,omitempty" example:"+62-812-1234"`
Email string `json:"email,omitempty" example:"lude@wucher.local"`
SortKey *int `json:"sortkey" example:"1"`
SMSAlert bool `json:"sms_alert" example:"false"`
Checklist bool `json:"checklist" example:"false"`
LegTime string `json:"leg_time,omitempty" example:"00:20"`
DefaultStartTimeType string `json:"default_start_time_type,omitempty" example:"FIXED" enums:"FIXED,BMCT,ECET"`
DefaultEndTimeType string `json:"default_end_time_type,omitempty" example:"FIXED" enums:"FIXED,BMCT,ECET"`
ShiftTime BaseShiftTime `json:"shift_time"`
UTC string `json:"utc" example:"0"`
Foto *BaseFoto `json:"foto,omitempty"`
Dry bool `json:"dry" example:"false"`
ControlCenter bool `json:"control_center" example:"false"`
DUL bool `json:"dul" example:"false"`
Notes string `json:"notes,omitempty" example:"Main base"`
HEMSEDCContactIDs []string `json:"hems_edc_contact_ids,omitempty"`
HEMSEDCs []BaseContact `json:"hems_edc,omitempty"`
MedPaxContactIDs []string `json:"med_pax_contact_ids,omitempty"`
MedPax []BaseContact `json:"med_pax,omitempty"`
ResponsiblePilotContactIDs []string `json:"responsible_pilot_contact_ids,omitempty"`
ResponsiblePilots []BaseContact `json:"responsible_pilots,omitempty"`
OperationalShiftTimes []BaseOperationalShiftTime `json:"operational_shift_times,omitempty"`
IsActive bool `json:"is_active" example:"true"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z" swaggerignore:"true"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d" swaggerignore:"true"`
}
type BaseContact struct {
ID string `json:"id"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
}
type BaseShiftTime struct {
Start string `json:"start,omitempty" example:"06:00"`
End string `json:"end,omitempty" example:"21:00"`
}
type BaseOperationalShiftTime struct {
DateStart string `json:"date_start" example:"2026-04-12"`
DateEnd string `json:"date_end" example:"2026-04-12"`
StartTimeType string `json:"start_time_type,omitempty" example:"FIXED" enums:"FIXED,BMCT,ECET"`
EndTimeType string `json:"end_time_type,omitempty" example:"FIXED" enums:"FIXED,BMCT,ECET"`
ShiftTime BaseShiftTime `json:"shift_time"`
}
type BaseFoto struct {
UUID string `json:"uuid" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DownloadURL string `json:"download_url" example:"https://storage.example.com/base/lude.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=abc123"`
ThumbnailDownloadURL string `json:"thumbnail_download_url,omitempty" example:"https://storage.example.com/base/lude.thumb.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=abc123"`
OriginalSizeBytes *int64 `json:"original_size_bytes,omitempty" example:"1024000"`
}
type BaseResource struct {
Type string `json:"type" example:"base"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes BaseAttributes `json:"attributes"`
}
type BaseResponse struct {
Data BaseResource `json:"data"`
}
type BaseListResponse struct {
Data []BaseResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type BaseDataTableResponse struct {
Data []BaseResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type BaseCreateAttributes struct {
BaseName string `json:"base" validate:"required" example:"Lude"`
BaseAbbreviation *string `json:"base_abbreviation,omitempty" example:"LOIG"`
BaseCategory *string `json:"base_category,omitempty" example:"regular" enums:"regular,hems"`
Address *string `json:"address,omitempty" example:"Lude Airfield"`
Latitude *float64 `json:"latitude" validate:"required,gte=-90,lte=90" example:"47.3769"`
Longitude *float64 `json:"longitude" validate:"required,gte=-180,lte=180" example:"8.5417"`
LandlineNumber *string `json:"landline_number,omitempty" example:"+62-21-1234"`
MobileNumber *string `json:"mobile_number,omitempty" example:"+62-812-1234"`
Email *string `json:"email,omitempty" example:"lude@wucher.local"`
SortKey *int `json:"sortkey" example:"1"`
SMSAlert *bool `json:"sms_alert,omitempty" example:"false"`
Checklist *bool `json:"checklist,omitempty" example:"false"`
LegTime *string `json:"leg_time,omitempty" example:"00:20"`
DefaultStartTimeType *string `json:"default_start_time_type,omitempty" example:"FIXED" enums:"FIXED,BMCT,ECET"`
DefaultEndTimeType *string `json:"default_end_time_type,omitempty" example:"FIXED" enums:"FIXED,BMCT,ECET"`
ShiftTime *BaseShiftTime `json:"shift_time,omitempty"`
DefaultShiftStart *string `json:"default_shift_start,omitempty" example:"06:00:00"`
DefaultShiftEnd *string `json:"default_shift_end,omitempty" example:"21:00:00"`
DefaultShiftTime *string `json:"default_shift_time,omitempty" example:"12:00"`
UTC *string `json:"utc,omitempty" example:"0"`
FileUUID *string `json:"file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e90"`
Dry *bool `json:"dry,omitempty" example:"false"`
ControlCenter *bool `json:"control_center,omitempty" example:"false"`
DUL *bool `json:"dul,omitempty" example:"false"`
Notes *string `json:"notes,omitempty" example:"Main base"`
HEMSEDCContactIDs []string `json:"hems_edc_contact_ids,omitempty"`
MedPaxContactIDs []string `json:"med_pax_contact_ids,omitempty"`
ResponsiblePilotContactIDs []string `json:"responsible_pilot_contact_ids,omitempty"`
OperationalShiftTimes []BaseOperationalShiftTime `json:"operational_shift_times,omitempty"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
CreatedBy *string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d" swaggerignore:"true"`
UpdatedBy *string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d" swaggerignore:"true"`
}
type BaseCreateData struct {
Type string `json:"type" validate:"required,oneof=base_create base hems_base" enums:"base" example:"base"`
Attributes BaseCreateAttributes `json:"attributes" validate:"required"`
}
type BaseCreateRequest struct {
Data BaseCreateData `json:"data" validate:"required"`
}
type BaseUpdateAttributes struct {
BaseName *string `json:"base,omitempty" example:"Lude"`
BaseAbbreviation *string `json:"base_abbreviation,omitempty" example:"LOIG"`
BaseCategory *string `json:"base_category,omitempty" example:"regular" enums:"regular,hems"`
Address *string `json:"address,omitempty" example:"Lude Airfield"`
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,gte=-90,lte=90" example:"47.3769"`
Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,gte=-180,lte=180" example:"8.5417"`
LandlineNumber *string `json:"landline_number,omitempty" example:"+62-21-1234"`
MobileNumber *string `json:"mobile_number,omitempty" example:"+62-812-1234"`
Email *string `json:"email,omitempty" example:"lude@wucher.local"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
SMSAlert *bool `json:"sms_alert,omitempty" example:"false"`
Checklist *bool `json:"checklist,omitempty" example:"false"`
LegTime *string `json:"leg_time,omitempty" example:"00:20"`
DefaultStartTimeType *string `json:"default_start_time_type,omitempty" example:"FIXED" enums:"FIXED,BMCT,ECET"`
DefaultEndTimeType *string `json:"default_end_time_type,omitempty" example:"FIXED" enums:"FIXED,BMCT,ECET"`
DefaultShiftStart *string `json:"default_shift_start,omitempty" example:"06:00:00"`
DefaultShiftEnd *string `json:"default_shift_end,omitempty" example:"21:00:00"`
DefaultShiftTime *string `json:"default_shift_time,omitempty" example:"12:00"`
ShiftTime *BaseShiftTime `json:"shift_time,omitempty"`
UTC *string `json:"utc,omitempty" example:"0"`
FileUUID *string `json:"file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e90"`
Dry *bool `json:"dry,omitempty" example:"false"`
ControlCenter *bool `json:"control_center,omitempty" example:"false"`
DUL *bool `json:"dul,omitempty" example:"false"`
Notes *string `json:"notes,omitempty" example:"Main base"`
HEMSEDCContactIDs *[]string `json:"hems_edc_contact_ids,omitempty"`
MedPaxContactIDs *[]string `json:"med_pax_contact_ids,omitempty"`
ResponsiblePilotContactIDs *[]string `json:"responsible_pilot_contact_ids,omitempty"`
OperationalShiftTimes []BaseOperationalShiftTime `json:"operational_shift_times,omitempty"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
CreatedBy *string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d" swaggerignore:"true"`
UpdatedBy *string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d" swaggerignore:"true"`
}
type BaseUpdateData struct {
Type string `json:"type" validate:"required,oneof=base_update base hems_base" enums:"base_update" example:"base_update"`
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes BaseUpdateAttributes `json:"attributes" validate:"required"`
}
type BaseUpdateRequest struct {
Data BaseUpdateData `json:"data" validate:"required"`
}
type BaseDeleteAttributes struct {
Deleted bool `json:"deleted" example:"true"`
}
type BaseDeleteResource struct {
Type string `json:"type" example:"base_delete"`
ID string `json:"id" example:"019d6b10-a385-7e9e-a553-8e892bcec0ef"`
Attributes BaseDeleteAttributes `json:"attributes"`
}
type BaseDeleteResponse struct {
Data BaseDeleteResource `json:"data"`
}
type BaseResponseMappingInput struct {
ResourceType string
BaseCategory string
ID string
DefaultStartTimeType string
DefaultEndTimeType string
ShiftTime BaseShiftTime
Foto *BaseFoto
Latitude float64
Longitude float64
HEMSEDCContactIDs []string
HEMSEDCs []BaseContact
MedPaxContactIDs []string
MedPax []BaseContact
ResponsiblePilotContactIDs []string
ResponsiblePilots []BaseContact
OperationalShiftTimes []BaseOperationalShiftTime
}
func MapBaseCreateAttributesToDomain(attrs BaseCreateAttributes) *base.Base {
row := &base.Base{}
row.BaseName = strings.TrimSpace(attrs.BaseName)
row.BaseAbbreviation = strings.TrimSpace(stringOrDefault(attrs.BaseAbbreviation, ""))
row.Address = strings.TrimSpace(stringOrDefault(attrs.Address, ""))
row.Latitude = floatOrDefault(attrs.Latitude, 0)
row.Longitude = floatOrDefault(attrs.Longitude, 0)
row.LandlineNumber = strings.TrimSpace(stringOrDefault(attrs.LandlineNumber, ""))
row.MobileNumber = strings.TrimSpace(stringOrDefault(attrs.MobileNumber, ""))
row.Email = strings.TrimSpace(stringOrDefault(attrs.Email, ""))
row.SortKey = cloneIntPtr(attrs.SortKey)
row.SMSAlert = boolOrDefault(attrs.SMSAlert, false)
row.Checklist = boolOrDefault(attrs.Checklist, false)
row.LegTime = strings.TrimSpace(stringOrDefault(attrs.LegTime, ""))
row.DefaultStartTimeType = normalizeShiftTimeType(stringOrDefault(attrs.DefaultStartTimeType, ""))
row.DefaultEndTimeType = normalizeShiftTimeType(stringOrDefault(attrs.DefaultEndTimeType, ""))
row.UTC = normalizeUTC(attrs.UTC)
row.Dry = boolOrDefault(attrs.Dry, false)
row.ControlCenter = boolOrDefault(attrs.ControlCenter, false)
row.DUL = boolOrDefault(attrs.DUL, false)
row.Notes = strings.TrimSpace(stringOrDefault(attrs.Notes, ""))
row.IsActive = boolOrDefault(attrs.IsActive, true)
row.OperationalShiftTimes = mapBaseOperationalShiftTimesToDomain(attrs.OperationalShiftTimes)
return row
}
func ApplyBaseUpdateAttributesToDomain(row *base.Base, attrs BaseUpdateAttributes) {
if attrs.BaseName != nil {
row.BaseName = strings.TrimSpace(*attrs.BaseName)
}
if attrs.BaseAbbreviation != nil {
row.BaseAbbreviation = strings.TrimSpace(*attrs.BaseAbbreviation)
}
if attrs.Address != nil {
row.Address = strings.TrimSpace(*attrs.Address)
}
if attrs.Latitude != nil {
row.Latitude = *attrs.Latitude
}
if attrs.Longitude != nil {
row.Longitude = *attrs.Longitude
}
if attrs.LandlineNumber != nil {
row.LandlineNumber = strings.TrimSpace(*attrs.LandlineNumber)
}
if attrs.MobileNumber != nil {
row.MobileNumber = strings.TrimSpace(*attrs.MobileNumber)
}
if attrs.Email != nil {
row.Email = strings.TrimSpace(*attrs.Email)
}
if attrs.SortKey.Set {
if attrs.SortKey.Valid {
v := attrs.SortKey.Value
row.SortKey = &v
} else {
row.SortKey = nil
}
}
if attrs.SMSAlert != nil {
row.SMSAlert = *attrs.SMSAlert
}
if attrs.Checklist != nil {
row.Checklist = *attrs.Checklist
}
if attrs.LegTime != nil {
row.LegTime = strings.TrimSpace(*attrs.LegTime)
}
if attrs.DefaultStartTimeType != nil {
row.DefaultStartTimeType = normalizeShiftTimeType(*attrs.DefaultStartTimeType)
}
if attrs.DefaultEndTimeType != nil {
row.DefaultEndTimeType = normalizeShiftTimeType(*attrs.DefaultEndTimeType)
}
if attrs.UTC != nil {
row.UTC = normalizeUTC(attrs.UTC)
}
if attrs.Dry != nil {
row.Dry = *attrs.Dry
}
if attrs.ControlCenter != nil {
row.ControlCenter = *attrs.ControlCenter
}
if attrs.DUL != nil {
row.DUL = *attrs.DUL
}
if attrs.Notes != nil {
row.Notes = strings.TrimSpace(*attrs.Notes)
}
if attrs.IsActive != nil {
row.IsActive = *attrs.IsActive
}
}
func mapBaseOperationalShiftTimesToDomain(items []BaseOperationalShiftTime) []base.BaseOperationalShiftTime {
if len(items) == 0 {
return nil
}
out := make([]base.BaseOperationalShiftTime, 0, len(items))
for i := range items {
dateStart := parseBaseOperationDatePtr(items[i].DateStart)
dateEnd := parseBaseOperationDatePtr(items[i].DateEnd)
out = append(out, base.BaseOperationalShiftTime{
DateStart: dateStart,
DateEnd: dateEnd,
StartTimeType: normalizeShiftTimeType(items[i].StartTimeType),
EndTimeType: normalizeShiftTimeType(items[i].EndTimeType),
ShiftStart: normalizeBaseClockValue(items[i].ShiftTime.Start),
ShiftEnd: normalizeBaseClockValue(items[i].ShiftTime.End),
})
}
return out
}
func MapBaseOperationalShiftTimesToDTO(items []base.BaseOperationalShiftTime) []BaseOperationalShiftTime {
if len(items) == 0 {
return nil
}
out := make([]BaseOperationalShiftTime, 0, len(items))
for i := range items {
out = append(out, BaseOperationalShiftTime{
DateStart: timePtrToDateString(items[i].DateStart),
DateEnd: timePtrToDateString(items[i].DateEnd),
StartTimeType: normalizeShiftTimeType(items[i].StartTimeType),
EndTimeType: normalizeShiftTimeType(items[i].EndTimeType),
ShiftTime: BaseShiftTime{
Start: DisplayBaseShiftValue(items[i].StartTimeType, items[i].ShiftStart),
End: DisplayBaseShiftValue(items[i].EndTimeType, items[i].ShiftEnd),
},
})
}
return out
}
func parseBaseOperationDate(raw string) time.Time {
parsed, err := time.Parse("2006-01-02", strings.TrimSpace(raw))
if err != nil {
return time.Time{}
}
return parsed.UTC()
}
func parseBaseOperationDatePtr(raw string) *time.Time {
parsed := parseBaseOperationDate(raw)
if parsed.IsZero() {
return nil
}
return &parsed
}
func timePtrToDateString(v *time.Time) string {
if v == nil || v.IsZero() {
return ""
}
return v.UTC().Format("2006-01-02")
}
func normalizeBaseClockValue(raw string) string {
trimmed := strings.TrimSpace(raw)
if trimmed == "" {
return ""
}
if len(trimmed) == 5 {
trimmed += ":00"
}
parsed, err := time.Parse("15:04:05", trimmed)
if err != nil {
return ""
}
return parsed.UTC().Format("15:04:05")
}
func normalizeBaseShiftDisplay(raw string) string {
s := strings.TrimSpace(raw)
if s == "" {
return s
}
layouts := []string{
"15:04:05",
"15:04",
"2006-01-02 15:04:05",
"2006-01-02 15:04",
time.RFC3339,
time.RFC3339Nano,
}
for _, layout := range layouts {
if parsed, err := time.Parse(layout, s); err == nil {
return parsed.UTC().Format("15:04")
}
}
if len(s) >= 5 && isDigitBaseClock(s[0]) && isDigitBaseClock(s[1]) && s[2] == ':' && isDigitBaseClock(s[3]) && isDigitBaseClock(s[4]) {
return s[:5]
}
return s
}
// DisplayBaseShiftValue returns the user-facing representation for a shift value.
// Fixed shifts are shown as HH:MM, while BMCT/ECET are shown as labels.
func DisplayBaseShiftValue(timeType, raw string) string {
switch normalizeShiftTimeType(timeType) {
case "BMCT", "ECET":
return normalizeShiftTimeType(timeType)
default:
return normalizeBaseShiftDisplay(raw)
}
}
func normalizeShiftTimeType(raw string) string {
switch strings.ToUpper(strings.TrimSpace(raw)) {
case "", "FIXED":
return "FIXED"
case "BMCT":
return "BMCT"
case "ECET":
return "ECET"
default:
return strings.ToUpper(strings.TrimSpace(raw))
}
}
func isDigitBaseClock(b byte) bool {
return b >= '0' && b <= '9'
}
func MapBaseToResource(row *base.Base, in BaseResponseMappingInput) BaseResource {
if row == nil {
return BaseResource{
Type: in.ResourceType,
Attributes: BaseAttributes{
BaseCategory: in.BaseCategory,
DefaultStartTimeType: normalizeShiftTimeType(in.DefaultStartTimeType),
DefaultEndTimeType: normalizeShiftTimeType(in.DefaultEndTimeType),
ShiftTime: in.ShiftTime,
OperationalShiftTimes: in.OperationalShiftTimes,
Latitude: in.Latitude,
Longitude: in.Longitude,
},
}
}
return BaseResource{
Type: in.ResourceType,
ID: in.ID,
Attributes: BaseAttributes{
BaseName: row.BaseName,
BaseAbbreviation: row.BaseAbbreviation,
BaseCategory: in.BaseCategory,
Address: row.Address,
Latitude: row.Latitude,
Longitude: row.Longitude,
LandlineNumber: row.LandlineNumber,
MobileNumber: row.MobileNumber,
Email: row.Email,
SortKey: cloneIntPtr(row.SortKey),
SMSAlert: row.SMSAlert,
Checklist: row.Checklist,
LegTime: row.LegTime,
DefaultStartTimeType: normalizeShiftTimeType(row.DefaultStartTimeType),
DefaultEndTimeType: normalizeShiftTimeType(row.DefaultEndTimeType),
ShiftTime: in.ShiftTime,
UTC: row.UTC,
Foto: in.Foto,
Dry: row.Dry,
ControlCenter: row.ControlCenter,
DUL: row.DUL,
Notes: row.Notes,
HEMSEDCContactIDs: in.HEMSEDCContactIDs,
HEMSEDCs: in.HEMSEDCs,
MedPaxContactIDs: in.MedPaxContactIDs,
MedPax: in.MedPax,
ResponsiblePilotContactIDs: in.ResponsiblePilotContactIDs,
ResponsiblePilots: in.ResponsiblePilots,
OperationalShiftTimes: in.OperationalShiftTimes,
IsActive: row.IsActive,
CreatedBy: bytesToUUID(row.CreatedBy),
UpdatedBy: bytesToUUID(row.UpdatedBy),
CreatedAt: row.CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: row.UpdatedAt.UTC().Format(time.RFC3339),
DeletedAt: timePtrToString(row.DeletedAt),
DeletedBy: bytesToUUID(row.DeletedBy),
},
}
}
func stringOrDefault(v *string, d string) string {
if v == nil {
return d
}
return *v
}
func boolOrDefault(v *bool, d bool) bool {
if v == nil {
return d
}
return *v
}
func normalizeUTC(v *string) string {
s := strings.TrimSpace(stringOrDefault(v, "0"))
if s == "" {
return "0"
}
return s
}
func floatOrDefault(v *float64, d float64) float64 {
if v == nil {
return d
}
return *v
}
func cloneIntPtr(v *int) *int {
if v == nil {
return nil
}
x := *v
return &x
}
func bytesToUUID(v []byte) string {
if len(v) == 0 {
return ""
}
if name := userctx.GetDisplayName(context.TODO(), v); name != "" {
return name
}
s, err := uuidv7.BytesToString(v)
if err != nil {
return ""
}
return s
}
func timePtrToString(v *time.Time) string {
if v == nil {
return ""
}
return v.UTC().Format(time.RFC3339)
}

View File

@@ -0,0 +1,53 @@
package dto
type BeforeFlightInspectionAttributes struct {
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
FlightInspectionID string `json:"flight_inspection_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
FlightInspection *FlightInspectionResource `json:"flight_inspection,omitempty"`
OilEngineNR1Checked *bool `json:"oil_engine_nr1_checked,omitempty"`
OilEngineNR2Checked *bool `json:"oil_engine_nr2_checked,omitempty"`
HydraulicLHChecked *bool `json:"hydraulic_lh_checked,omitempty"`
HydraulicRHChecked *bool `json:"hydraulic_rh_checked,omitempty"`
OilTransmissionMGBChecked *bool `json:"oil_transmission_mgb_checked,omitempty"`
OilTransmissionIGBChecked *bool `json:"oil_transmission_igb_checked,omitempty"`
OilTransmissionTGBChecked *bool `json:"oil_transmission_tgb_checked,omitempty"`
FuelAmount *float64 `json:"fuel_amount,omitempty" example:"500.5"`
FuelUnit string `json:"fuel_unit,omitempty" example:"LT"`
FuelAddedAmount *float64 `json:"fuel_added_amount,omitempty" example:"100.0"`
Note *string `json:"note,omitempty" example:"All checks completed"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-09T05:00:00Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-09T05:00:00Z"`
}
type BeforeFlightInspectionResource struct {
Type string `json:"type" example:"before_flight_inspection"`
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Attributes BeforeFlightInspectionAttributes `json:"attributes"`
}
type BeforeFlightInspectionResponse struct {
Data BeforeFlightInspectionResource `json:"data"`
}
type BeforeFlightInspectionUpsertAttributes struct {
OilEngineNR1Checked *bool `json:"oil_engine_nr1_checked,omitempty"`
OilEngineNR2Checked *bool `json:"oil_engine_nr2_checked,omitempty"`
HydraulicLHChecked *bool `json:"hydraulic_lh_checked,omitempty"`
HydraulicRHChecked *bool `json:"hydraulic_rh_checked,omitempty"`
OilTransmissionMGBChecked *bool `json:"oil_transmission_mgb_checked,omitempty"`
OilTransmissionIGBChecked *bool `json:"oil_transmission_igb_checked,omitempty"`
OilTransmissionTGBChecked *bool `json:"oil_transmission_tgb_checked,omitempty"`
FuelAmount *float64 `json:"fuel_amount,omitempty" validate:"omitempty,gte=0"`
FuelUnit *string `json:"fuel_unit,omitempty" validate:"omitempty,oneof=LT KG POUND"`
FuelAddedAmount *float64 `json:"fuel_added_amount,omitempty" validate:"omitempty,gte=0"`
Note *string `json:"note,omitempty"`
}
type BeforeFlightInspectionUpsertData struct {
Type string `json:"type" validate:"required,oneof=before_flight_inspection_upsert before_flight_inspection"`
Attributes BeforeFlightInspectionUpsertAttributes `json:"attributes" validate:"required"`
}
type BeforeFlightInspectionUpsertRequest struct {
Data BeforeFlightInspectionUpsertData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,423 @@
package dto
import "time"
type ContactBaseAttributes struct {
Email string `json:"email,omitempty" validate:"omitempty,email"`
SSOEmail string `json:"sso_email,omitempty" validate:"omitempty,email"`
IsAdmin *bool `json:"is_admin,omitempty"`
Username string `json:"username,omitempty" validate:"omitempty,max=100"`
FirstName string `json:"first_name,omitempty" validate:"omitempty,max=100"`
LastName string `json:"last_name,omitempty" validate:"omitempty,max=100"`
MobilePhone string `json:"mobile_phone,omitempty" validate:"omitempty,max=32"`
Timezone string `json:"timezone,omitempty" validate:"omitempty,max=64"`
FileUUID string `json:"file_uuid,omitempty" validate:"omitempty,max=64"`
SortKey *int `json:"sortkey"`
IsActive *bool `json:"is_active,omitempty"`
}
type PilotProfileAttributes struct {
PilotCategory string `json:"pilot_category,omitempty" validate:"omitempty,oneof=regular freelance dry_lease"`
ShortName string `json:"short_name,omitempty" validate:"omitempty,max=120"`
LicenseNo string `json:"license_no,omitempty" validate:"omitempty,max=120"`
LicenseIssuedAt *time.Time `json:"license_issued_at,omitempty"`
LicenseExpiredAt *time.Time `json:"license_expired_at,omitempty"`
TechnicianLicenseNo string `json:"technician_license_no,omitempty" validate:"omitempty,max=120"`
HasIFRQualification *bool `json:"has_ifr_qualification,omitempty"`
IsChiefPilot *bool `json:"is_chief_pilot,omitempty"`
DULBaseIDs []string `json:"dul_base_ids,omitempty" validate:"omitempty,dive,uuid" swaggerignore:"true"`
DULBase []DULBase `json:"dul_base,omitempty"`
TotalFlightMinutes *int `json:"total_flight_minutes,omitempty"`
ResponsiblePilotMinutes *int `json:"responsible_pilot_minutes,omitempty"`
SecondPilotMinutes *int `json:"second_pilot_minutes,omitempty"`
DoubleCommandMinutes *int `json:"double_command_minutes,omitempty"`
FlightInstructorMinutes *int `json:"flight_instructor_minutes,omitempty"`
NightFlightMinutes *int `json:"night_flight_minutes,omitempty"`
IFRFlightMinutes *int `json:"ifr_flight_minutes,omitempty"`
Location string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note string `json:"note,omitempty"`
WeightKG *float64 `json:"weight_kg,omitempty"`
}
type DULBase struct {
ID string `json:"id" example:"0197f3b2-c4d8-4b12-a1f9-c1de23456789"`
Name string `json:"name" example:"Halim Base"`
Category string `json:"category" example:"hems"`
}
type DoctorProfileAttributes struct {
ShortName string `json:"short_name,omitempty" validate:"omitempty,max=120"`
Specialization string `json:"specialization,omitempty" validate:"omitempty,max=120"`
SubSpecialization string `json:"sub_specialization,omitempty" validate:"omitempty,max=120"`
IsChiefPhysician *bool `json:"is_chief_physician,omitempty"`
Location string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note string `json:"note,omitempty"`
}
type AirRescuerProfileAttributes struct {
ShortName string `json:"short_name,omitempty" validate:"omitempty,max=120"`
ResponsibleFlightRescuer *bool `json:"responsible_flight_rescuer,omitempty"`
Location string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note string `json:"note,omitempty"`
}
type TechnicianProfileAttributes struct {
ShortName string `json:"short_name,omitempty" validate:"omitempty,max=120"`
LicenseNo string `json:"license_no,omitempty" validate:"omitempty,max=120"`
IsChiefTechnician *bool `json:"is_chief_technician,omitempty"`
IsResponsibleComplaints *bool `json:"is_responsible_complaints,omitempty"`
Location string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note string `json:"note,omitempty"`
}
type FlightAssistantProfileAttributes struct {
ShortName string `json:"short_name,omitempty" validate:"omitempty,max=120"`
Location string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note string `json:"note,omitempty"`
}
type StaffProfileAttributes struct {
ShortName string `json:"short_name,omitempty" validate:"omitempty,max=120"`
RoleLabel string `json:"role_label,omitempty" validate:"omitempty,max=120"`
Location string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note string `json:"note,omitempty"`
}
type ContactCreateAttributes struct {
RoleID string `json:"role_id,omitempty" example:"0195f6f8-0f8d-7e3e-b8f2-8abff9b5c1d1"`
RoleIDs []string `json:"role_ids,omitempty" example:"0195f6f8-0f8d-7e3e-b8f2-8abff9b5c1d1,0195f6f8-0f8d-7e3e-b8f2-8abff9b5c1d2"`
ContactBaseAttributes
AirRescuer *AirRescuerProfileAttributes `json:"air_rescuer,omitempty"`
Doctor *DoctorProfileAttributes `json:"doctor,omitempty"`
FlightAssistant *FlightAssistantProfileAttributes `json:"flight_assistant,omitempty"`
Pilot *PilotProfileAttributes `json:"pilot,omitempty"`
Staff *StaffProfileAttributes `json:"staff,omitempty"`
Technician *TechnicianProfileAttributes `json:"technician,omitempty"`
}
type ContactCreateData struct {
Type string `json:"type" validate:"required" example:"contact_create"`
Attributes ContactCreateAttributes `json:"attributes" validate:"required"`
}
type ContactCreateRequest struct {
Data ContactCreateData `json:"data" validate:"required"`
}
type PilotProfileUpdateAttributes struct {
PilotCategory *string `json:"pilot_category,omitempty" validate:"omitempty,oneof=regular freelance dry_lease"`
ShortName *string `json:"short_name,omitempty" validate:"omitempty,max=120"`
LicenseNo *string `json:"license_no,omitempty" validate:"omitempty,max=120"`
LicenseIssuedAt *time.Time `json:"license_issued_at,omitempty"`
LicenseExpiredAt *time.Time `json:"license_expired_at,omitempty"`
TechnicianLicenseNo *string `json:"technician_license_no,omitempty" validate:"omitempty,max=120"`
HasIFRQualification *bool `json:"has_ifr_qualification,omitempty"`
IsChiefPilot *bool `json:"is_chief_pilot,omitempty"`
DULBaseIDs []string `json:"dul_base_ids,omitempty" validate:"omitempty,dive,uuid" swaggerignore:"true"`
DULBase []DULBase `json:"dul_base,omitempty"`
TotalFlightMinutes *int `json:"total_flight_minutes,omitempty"`
ResponsiblePilotMinutes *int `json:"responsible_pilot_minutes,omitempty"`
SecondPilotMinutes *int `json:"second_pilot_minutes,omitempty"`
DoubleCommandMinutes *int `json:"double_command_minutes,omitempty"`
FlightInstructorMinutes *int `json:"flight_instructor_minutes,omitempty"`
NightFlightMinutes *int `json:"night_flight_minutes,omitempty"`
IFRFlightMinutes *int `json:"ifr_flight_minutes,omitempty"`
Location *string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode *string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine *string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note *string `json:"note,omitempty"`
WeightKG *float64 `json:"weight_kg,omitempty"`
}
type DoctorProfileUpdateAttributes struct {
ShortName *string `json:"short_name,omitempty" validate:"omitempty,max=120"`
Specialization *string `json:"specialization,omitempty" validate:"omitempty,max=120"`
SubSpecialization *string `json:"sub_specialization,omitempty" validate:"omitempty,max=120"`
IsChiefPhysician *bool `json:"is_chief_physician,omitempty"`
Location *string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode *string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine *string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note *string `json:"note,omitempty"`
}
type AirRescuerProfileUpdateAttributes struct {
ShortName *string `json:"short_name,omitempty" validate:"omitempty,max=120"`
ResponsibleFlightRescuer *bool `json:"responsible_flight_rescuer,omitempty"`
Location *string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode *string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine *string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note *string `json:"note,omitempty"`
}
type TechnicianProfileUpdateAttributes struct {
ShortName *string `json:"short_name,omitempty" validate:"omitempty,max=120"`
LicenseNo *string `json:"license_no,omitempty" validate:"omitempty,max=120"`
IsChiefTechnician *bool `json:"is_chief_technician,omitempty"`
IsResponsibleComplaints *bool `json:"is_responsible_complaints,omitempty"`
Location *string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode *string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine *string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note *string `json:"note,omitempty"`
}
type FlightAssistantProfileUpdateAttributes struct {
ShortName *string `json:"short_name,omitempty" validate:"omitempty,max=120"`
Location *string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode *string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine *string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note *string `json:"note,omitempty"`
}
type StaffProfileUpdateAttributes struct {
ShortName *string `json:"short_name,omitempty" validate:"omitempty,max=120"`
RoleLabel *string `json:"role_label,omitempty" validate:"omitempty,max=120"`
Location *string `json:"location,omitempty" validate:"omitempty,max=255"`
Postcode *string `json:"postcode,omitempty" validate:"omitempty,max=32"`
StreetLine *string `json:"street_line,omitempty" validate:"omitempty,max=255"`
Note *string `json:"note,omitempty"`
}
type ContactUpdateAttributes struct {
RoleID *string `json:"role_id,omitempty" example:"0195f6f8-0f8d-7e3e-b8f2-8abff9b5c1d1"`
RoleIDs []string `json:"role_ids,omitempty" example:"0195f6f8-0f8d-7e3e-b8f2-8abff9b5c1d1,0195f6f8-0f8d-7e3e-b8f2-8abff9b5c1d2"`
Email *string `json:"email,omitempty" validate:"omitempty,email"`
SSOEmail *string `json:"sso_email,omitempty" validate:"omitempty,email"`
IsAdmin *bool `json:"is_admin,omitempty"`
Username *string `json:"username,omitempty" validate:"omitempty,max=100"`
FirstName *string `json:"first_name,omitempty" validate:"omitempty,max=100"`
LastName *string `json:"last_name,omitempty" validate:"omitempty,max=100"`
MobilePhone *string `json:"mobile_phone,omitempty" validate:"omitempty,max=32"`
Timezone *string `json:"timezone,omitempty" validate:"omitempty,max=64"`
FileUUID *string `json:"file_uuid,omitempty" validate:"omitempty,max=64"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer"`
IsActive *bool `json:"is_active,omitempty"`
AirRescuer *AirRescuerProfileUpdateAttributes `json:"air_rescuer,omitempty"`
Doctor *DoctorProfileUpdateAttributes `json:"doctor,omitempty"`
FlightAssistant *FlightAssistantProfileUpdateAttributes `json:"flight_assistant,omitempty"`
Pilot *PilotProfileUpdateAttributes `json:"pilot,omitempty"`
Staff *StaffProfileUpdateAttributes `json:"staff,omitempty"`
Technician *TechnicianProfileUpdateAttributes `json:"technician,omitempty"`
}
type ContactUpdateData struct {
Type string `json:"type" validate:"required" example:"contact_update"`
Attributes ContactUpdateAttributes `json:"attributes" validate:"required"`
}
type ContactUpdateRequest struct {
Data ContactUpdateData `json:"data" validate:"required"`
}
type ContactListQuery struct {
Role string `query:"role" validate:"omitempty,oneof=pilot doctor air_rescuer technician flight_assistant staff"`
PilotCategory string `query:"pilot_category" validate:"omitempty,oneof=regular freelance dry_lease"`
Search string `query:"search"`
Sort string `query:"sort"`
Limit int `query:"limit" validate:"omitempty,min=1,max=200"`
Offset int `query:"offset" validate:"omitempty,min=0"`
}
type ContactListItem struct {
ID string `json:"id"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
RoleCode string `json:"role_code"`
RoleName string `json:"role_name"`
PilotCategory string `json:"pilot_category,omitempty"`
Username string `json:"username,omitempty"`
Email string `json:"email"`
IsAdmin bool `json:"is_admin"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
ShortName string `json:"short_name,omitempty"`
LicenseNo string `json:"license_no,omitempty"`
Location string `json:"location,omitempty"`
MobilePhone string `json:"mobile_phone,omitempty"`
SortKey *int `json:"sortkey"`
Note string `json:"note,omitempty"`
IsActive bool `json:"is_active"`
SetPassword bool `json:"set_password"`
}
type ContactBaseRole struct {
BaseID string `json:"base_id"`
BaseName string `json:"base_name"`
RoleCode string `json:"role_code"`
}
type ContactRole struct {
ID string `json:"id"`
RoleCode string `json:"role_code"`
RoleName string `json:"role_name"`
}
type ContactListAttributes struct {
Email string `json:"email,omitempty"`
Username string `json:"username,omitempty"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
MobilePhone string `json:"mobile_phone,omitempty"`
IsAdmin bool `json:"is_admin"`
IsActive bool `json:"is_active"`
SetPassword bool `json:"set_password"`
SortKey *int `json:"sortkey"`
SSOEmail *string `json:"sso_email"`
CreatedAt string `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
BaseRoles []ContactBaseRole `json:"base_roles"`
RoleCode string `json:"role_code,omitempty"`
RoleName string `json:"role_name,omitempty"`
Roles []ContactRole `json:"roles"`
PilotCategory string `json:"pilot_category,omitempty"`
ShortName string `json:"short_name,omitempty"`
LicenseNo string `json:"license_no,omitempty"`
Location string `json:"location,omitempty"`
Note string `json:"note,omitempty"`
Pilot *PilotProfileAttributes `json:"pilot,omitempty"`
Doctor *DoctorProfileAttributes `json:"doctor,omitempty"`
AirRescuer *AirRescuerProfileAttributes `json:"air_rescuer,omitempty"`
Technician *TechnicianProfileAttributes `json:"technician,omitempty"`
FlightAssistant *FlightAssistantProfileAttributes `json:"flight_assistant,omitempty"`
Staff *StaffProfileAttributes `json:"staff,omitempty"`
}
type ContactListResponse struct {
Data []struct {
Type string `json:"type" example:"contact"`
ID string `json:"id" example:"019dcce4-f15e-7685-8e4c-76f8ad20594b"`
Attributes ContactListAttributes `json:"attributes"`
} `json:"data"`
Meta struct {
Total int64 `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
} `json:"meta"`
}
type ContactDataTableResponse struct {
Data []ContactListItem `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type SensitiveActionRequiredResponse struct {
Data struct {
Type string `json:"type" example:"pin_verification_required"`
Attributes struct {
Required bool `json:"required" example:"true"`
Action string `json:"action"`
Method string `json:"method" example:"pin"`
Status int `json:"status" example:"202"`
} `json:"attributes"`
} `json:"data"`
}
type ContactStatusUpdateData struct {
Type string `json:"type" validate:"required,eq=contact_status_update"`
Attributes struct {
IsActive bool `json:"is_active"`
} `json:"attributes" validate:"required"`
}
type ContactStatusUpdateRequest struct {
Data ContactStatusUpdateData `json:"data" validate:"required"`
}
type ContactRoleUpdateData struct {
Type string `json:"type" validate:"required,eq=contact_role_update"`
Attributes struct {
RoleCode string `json:"role_code" validate:"required,oneof=pilot doctor air_rescuer technician flight_assistant staff"`
} `json:"attributes" validate:"required"`
}
type ContactRoleUpdateRequest struct {
Data ContactRoleUpdateData `json:"data" validate:"required"`
}
type ContactPilotCategoryUpdateData struct {
Type string `json:"type" validate:"required,eq=contact_pilot_category_update"`
Attributes struct {
PilotCategory string `json:"pilot_category" validate:"required,oneof=regular freelance dry_lease"`
} `json:"attributes" validate:"required"`
}
type ContactPilotCategoryUpdateRequest struct {
Data ContactPilotCategoryUpdateData `json:"data" validate:"required"`
}
type ContactLicenseUpdateData struct {
Type string `json:"type" validate:"required,eq=contact_license_update"`
Attributes struct {
LicenseNo *string `json:"license_no,omitempty"`
} `json:"attributes" validate:"required"`
}
type ContactLicenseUpdateRequest struct {
Data ContactLicenseUpdateData `json:"data" validate:"required"`
}
type ContactChiefFlagsUpdateData struct {
Type string `json:"type" validate:"required,eq=contact_chief_flags_update"`
Attributes struct {
IsChiefPilot *bool `json:"is_chief_pilot,omitempty"`
IsChiefPhysician *bool `json:"is_chief_physician,omitempty"`
IsChiefTechnician *bool `json:"is_chief_technician,omitempty"`
} `json:"attributes" validate:"required"`
}
type ContactChiefFlagsUpdateRequest struct {
Data ContactChiefFlagsUpdateData `json:"data" validate:"required"`
}
type ContactResponsibleComplaintsData struct {
Type string `json:"type" validate:"required,eq=contact_responsible_complaints_update"`
Attributes struct {
IsResponsibleComplaints bool `json:"is_responsible_complaints"`
} `json:"attributes" validate:"required"`
}
type ContactResponsibleComplaintsRequest struct {
Data ContactResponsibleComplaintsData `json:"data" validate:"required"`
}
type ContactBulkUpdateData struct {
Type string `json:"type" validate:"required,eq=contact_bulk_update" example:"contact_bulk_update"`
Attributes struct {
UserIDs []string `json:"user_ids" validate:"required,min=1,dive,required" example:"0195f6f8-0f8d-7e3e-b8f2-8abff9b5c1d1"`
IsActive bool `json:"is_active" example:"true"`
} `json:"attributes" validate:"required"`
}
type ContactBulkUpdateRequest struct {
Data ContactBulkUpdateData `json:"data" validate:"required"`
}
type ContactSimpleSuccessResponse struct {
Data struct {
Type string `json:"type"`
Attributes struct {
Updated bool `json:"updated,omitempty"`
Deleted bool `json:"deleted,omitempty"`
Count int64 `json:"count,omitempty"`
} `json:"attributes"`
} `json:"data"`
}

View File

@@ -0,0 +1,26 @@
package dto
// GenericDeleteResponse is a common success payload for DELETE endpoints.
type GenericDeleteResponse struct {
Data GenericDeleteResponseData `json:"data"`
}
type GenericDeleteResponseData struct {
Type string `json:"type" example:"delete_result"`
Attributes GenericDeleteAttributes `json:"attributes"`
}
type GenericDeleteAttributes struct {
Deleted bool `json:"deleted" example:"true"`
}
func NewGenericDeleteResponse(resourceType string) GenericDeleteResponse {
return GenericDeleteResponse{
Data: GenericDeleteResponseData{
Type: resourceType,
Attributes: GenericDeleteAttributes{
Deleted: true,
},
},
}
}

View File

@@ -0,0 +1,38 @@
package dto
type DULCreateAttributes struct {
Name string `json:"name" validate:"required,max=128"`
Date string `json:"date" validate:"required" example:"2026-05-25"`
Info *string `json:"info,omitempty"`
BaseID string `json:"base_id" validate:"required" example:"019e3ec1-94f6-7b6b-9cc5-1b5d05f46d74"`
FileUUID []string `json:"file_uuid,omitempty"`
ImageAttachmentIDs []string `json:"image_attachment_ids,omitempty" swaggerignore:"true"`
}
type DULCreateData struct {
Type string `json:"type" validate:"required,oneof=dul_create dul"`
Attributes DULCreateAttributes `json:"attributes" validate:"required"`
}
type DULCreateRequest struct {
Data DULCreateData `json:"data" validate:"required"`
}
type DULUpdateAttributes struct {
Name *string `json:"name,omitempty"`
Date *string `json:"date,omitempty" example:"2026-05-25"`
Info *string `json:"info,omitempty"`
BaseID *string `json:"base_id,omitempty"`
FileUUID []string `json:"file_uuid,omitempty"`
ImageAttachmentIDs []string `json:"image_attachment_ids,omitempty" swaggerignore:"true"`
}
type DULUpdateData struct {
Type string `json:"type" validate:"required,oneof=dul_update dul"`
ID string `json:"id" swaggerignore:"true"`
Attributes DULUpdateAttributes `json:"attributes" validate:"required"`
}
type DULUpdateRequest struct {
Data DULUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,74 @@
package dto
type DULImage struct {
UUID string `json:"uuid"`
DownloadURL string `json:"download_url,omitempty"`
OriginalSizeBytes int64 `json:"original_size_bytes,omitempty"`
ThumbnailDownloadURL string `json:"thumbnail_download_url,omitempty"`
}
type DULAttributes struct {
No uint64 `json:"no"`
Name string `json:"name"`
Date string `json:"date" example:"2026-05-25"`
Info string `json:"info,omitempty"`
Base DULBaseRef `json:"base"`
Images []DULImage `json:"images"`
CreatedAt string `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
}
type DULBaseRef struct {
ID string `json:"id"`
Name string `json:"name"`
Category string `json:"category,omitempty"`
}
type DULResource struct {
Type string `json:"type"`
ID string `json:"id"`
Attributes DULAttributes `json:"attributes"`
}
type DULResponse struct {
Data DULResource `json:"data"`
}
type DULDeleteResponse struct {
Data struct {
Type string `json:"type" example:"dul_delete"`
Attributes struct {
Deleted bool `json:"deleted" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
type DULGroup struct {
Base DULBaseRef `json:"base"`
DULs []DULResource `json:"duls"`
}
type DULListResponse struct {
Data []DULGroup `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
TotalBases int `json:"total_bases" example:"8"`
} `json:"meta"`
}
type DULDataTableResponse struct {
Data []DULGroup `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
TotalBases int `json:"total_bases" example:"8"`
RecordsTotal int64 `json:"records_total,omitempty" example:"120"`
RecordsFiltered int64 `json:"records_filtered,omitempty" example:"120"`
} `json:"meta"`
}

View File

@@ -0,0 +1,99 @@
package dto
// JSON:API Facility DTOs
type FacilityAttributes struct {
Category string `json:"category" example:"heslo-rope-label"`
Name string `json:"name" example:"Fuel Truck Rope Label"`
Type string `json:"type" example:"Sling"`
SortKey *int `json:"sortkey" example:"1"`
Length string `json:"length,omitempty" example:"10m"`
Weight string `json:"weight,omitempty" example:"10kg"`
Electric string `json:"electric" example:"available"`
Note string `json:"note,omitempty" example:"Dry lease fuel truck"`
IsActive bool `json:"is_active" example:"true"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type FacilityResource struct {
Type string `json:"type" example:"facility"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FacilityAttributes `json:"attributes"`
}
type FacilityResponse struct {
Data FacilityResource `json:"data"`
}
type FacilityDeleteResponse struct {
Data struct {
Type string `json:"type" example:"facility_delete"`
Attributes struct {
Deleted bool `json:"deleted" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
type FacilityListResponse struct {
Data []FacilityResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type FacilityDataTableResponse struct {
Data []FacilityResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type FacilityCreateAttributes struct {
Category string `json:"category" validate:"required" example:"heslo-rope-label"`
Name string `json:"name" validate:"required" example:"Fuel Truck Rope Label"`
Type string `json:"type" example:"Sling"`
SortKey *int `json:"sortkey" example:"1"`
Length *string `json:"length,omitempty" example:"10m"`
Weight *string `json:"weight,omitempty" example:"10kg"`
Electric *string `json:"electric,omitempty" example:"available"`
Note *string `json:"note,omitempty" example:"Dry lease fuel truck"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type FacilityCreateData struct {
Type string `json:"type" validate:"required,oneof=facility_create facility" example:"facility_create"`
Attributes FacilityCreateAttributes `json:"attributes" validate:"required"`
}
type FacilityCreateRequest struct {
Data FacilityCreateData `json:"data" validate:"required"`
}
type FacilityUpdateAttributes struct {
Category *string `json:"category,omitempty" example:"heslo-rope-label"`
Name *string `json:"name,omitempty" example:"Fuel Truck Rope Label"`
Type *string `json:"type,omitempty" example:"Sling"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
Length *string `json:"length,omitempty" example:"10m"`
Weight *string `json:"weight,omitempty" example:"10kg"`
Electric *string `json:"electric,omitempty" example:"available"`
Note *string `json:"note,omitempty" example:"Dry lease fuel truck"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type FacilityUpdateData struct {
Type string `json:"type" validate:"required,oneof=facility_update facility" example:"facility_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FacilityUpdateAttributes `json:"attributes" validate:"required"`
}
type FacilityUpdateRequest struct {
Data FacilityUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,91 @@
package dto
// JSON:API Federal State DTOs
type FederalStateAttributes struct {
Name string `json:"name" example:"Bayern"`
Note string `json:"note,omitempty" example:"Central region"`
LandID string `json:"land_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
LandName string `json:"land_name,omitempty" example:"Germany"`
LandISOCode string `json:"land_iso_code,omitempty" example:"DE"`
SortKey *int `json:"sortkey" example:"1"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FederalStateResource struct {
Type string `json:"type" example:"federal_state"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FederalStateAttributes `json:"attributes"`
}
type FederalStateResponse struct {
Data FederalStateResource `json:"data"`
}
type FederalStateDeleteResponse struct {
Data struct {
Type string `json:"type" example:"federal_state_delete"`
Attributes struct {
Deleted bool `json:"deleted" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
type FederalStateListResponse struct {
Data []FederalStateResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type FederalStateDataTableResponse struct {
Data []FederalStateResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type FederalStateCreateAttributes struct {
Name string `json:"name" validate:"required" example:"Bayern"`
Note *string `json:"note,omitempty" example:"Central region"`
LandID string `json:"land_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
SortKey *int `json:"sortkey" example:"1"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type FederalStateCreateData struct {
Type string `json:"type" validate:"required,oneof=federal_state_create federal_state" example:"federal_state_create"`
Attributes FederalStateCreateAttributes `json:"attributes" validate:"required"`
}
type FederalStateCreateRequest struct {
Data FederalStateCreateData `json:"data" validate:"required"`
}
type FederalStateUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"Bayern"`
Note *string `json:"note,omitempty" example:"Central region"`
LandID *string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type FederalStateUpdateData struct {
Type string `json:"type" validate:"required,oneof=federal_state_update federal_state" example:"federal_state_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FederalStateUpdateAttributes `json:"attributes" validate:"required"`
}
type FederalStateUpdateRequest struct {
Data FederalStateUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,57 @@
package dto
type FlightInspectionAttributes struct {
InspectionDate string `json:"inspection_date,omitempty" example:"2026-04-09"`
Status string `json:"status,omitempty" example:"Draft"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-09T05:00:00Z"`
CreatedBy string `json:"created_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-09T05:00:00Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
}
type FlightInspectionResource struct {
Type string `json:"type" example:"flight_inspection"`
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Attributes FlightInspectionAttributes `json:"attributes"`
}
type FlightInspectionResponse struct {
Data FlightInspectionResource `json:"data"`
}
type FlightInspectionListResponse struct {
Data []FlightInspectionResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}
type FlightInspectionCreateAttributes struct {
InspectionDate string `json:"inspection_date" validate:"required" example:"2026-04-09"`
}
type FlightInspectionCreateData struct {
Type string `json:"type" validate:"required,oneof=flight_inspection_create flight_inspection"`
Attributes FlightInspectionCreateAttributes `json:"attributes" validate:"required"`
}
type FlightInspectionCreateRequest struct {
Data FlightInspectionCreateData `json:"data" validate:"required"`
}
type FlightInspectionUpdateAttributes struct {
InspectionDate *string `json:"inspection_date,omitempty" example:"2026-04-09"`
Status *string `json:"status,omitempty" example:"InProgress"`
}
type FlightInspectionUpdateData struct {
Type string `json:"type" validate:"required,oneof=flight_inspection_update flight_inspection"`
ID string `json:"id" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Attributes FlightInspectionUpdateAttributes `json:"attributes" validate:"required"`
}
type FlightInspectionUpdateRequest struct {
Data FlightInspectionUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,73 @@
package dto
import (
"time"
flightprepcheck "wucher/internal/domain/flight_prep_check"
"wucher/internal/shared/pkg/uuidv7"
)
type FlightPrepCheckAttributes struct {
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
FlightInspectionID string `json:"flight_inspection_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
NotamBriefing bool `json:"notam_briefing" example:"true"`
WeatherBriefing bool `json:"weather_briefing" example:"false"`
OperationalFlightPlan bool `json:"operational_flight_plan" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-09T05:00:00Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-09T05:00:00Z"`
}
type FlightPrepCheckResource struct {
Type string `json:"type" example:"flight_prep_check"`
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Attributes FlightPrepCheckAttributes `json:"attributes"`
}
type FlightPrepCheckResponse struct {
Data FlightPrepCheckResource `json:"data"`
}
type FlightPrepCheckUpsertRequest struct {
Data struct {
Type string `json:"type" validate:"required,oneof=flight_prep_check_upsert flight_prep_check" example:"flight_prep_check_upsert"`
Attributes FlightPrepCheckUpsertAttributes `json:"attributes" validate:"required"`
} `json:"data" validate:"required"`
}
type FlightPrepCheckUpsertAttributes struct {
NotamBriefing *bool `json:"notam_briefing,omitempty" example:"true"`
WeatherBriefing *bool `json:"weather_briefing,omitempty" example:"true"`
OperationalFlightPlan *bool `json:"operational_flight_plan,omitempty" example:"false"`
}
func ToFlightPrepCheckDTO(check *flightprepcheck.FlightPrepCheck) *FlightPrepCheckResource {
if check == nil {
return nil
}
idStr, _ := uuidv7.BytesToString(check.ID)
flightInspectionIDStr, _ := uuidv7.BytesToString(check.FlightInspectionID)
createdAt := ""
updatedAt := ""
if !check.CreatedAt.IsZero() {
createdAt = check.CreatedAt.UTC().Format(time.RFC3339)
}
if !check.UpdatedAt.IsZero() {
updatedAt = check.UpdatedAt.UTC().Format(time.RFC3339)
}
return &FlightPrepCheckResource{
Type: "flight_prep_check",
ID: idStr,
Attributes: FlightPrepCheckAttributes{
ID: idStr,
FlightInspectionID: flightInspectionIDStr,
NotamBriefing: check.NOTAMBriefing,
WeatherBriefing: check.WeatherBriefing,
OperationalFlightPlan: check.OperationalFlightPlan,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
},
}
}

View File

@@ -0,0 +1,100 @@
package dto
import responsedto "wucher/internal/transport/http/dto/response"
type FMReportAttributes struct {
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
ReportCode *string `json:"report_code" example:"EC-135-1"`
TakeoverID string `json:"takeover_id,omitempty" example:"019e8713-aa19-7fb7-a510-a0332dc69697"`
FlightID string `json:"flight_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
FlightInspectionID string `json:"flight_inspection_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Engine1GpcN1 *string `json:"engine1_gpc_n1" example:"GPC/N1 63.2"`
Engine1PtcN2 *string `json:"engine1_ptc_n2" example:"PTC/N2 61.8"`
Engine2GpcN1 *string `json:"engine2_gpc_n1" example:"GPC/N1 62.9"`
Engine2PtcN2 *string `json:"engine2_ptc_n2" example:"PTC/N2 61.5"`
CompletedAt string `json:"completed_at,omitempty" example:"2026-04-20T10:15:00Z"`
CompletedBy string `json:"completed_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
PilotName *string `json:"pilot_name,omitempty" example:"John Doe"`
HelicopterIdentifier *string `json:"helicopter_identifier,omitempty" example:"OE-TEST"`
Flight *responsedto.FlightResource `json:"flight,omitempty"`
Missions []responsedto.MissionResource `json:"missions,omitempty"`
FlightInspection *FlightInspectionResource `json:"flight_inspection,omitempty"`
AfterFlightInspection *AfterFlightInspectionResource `json:"after_flight_inspection,omitempty"`
FleetStatus *responsedto.FleetStatusResource `json:"fleet_status,omitempty"`
HelicopterUsage *HelicopterUsageResource `json:"helicopter_usage,omitempty"`
UsageBreakdown *FMReportUsageBreakdown `json:"usage_breakdown,omitempty"`
TakeoverFiles []responsedto.TakeoverFileResource `json:"takeover_files,omitempty"`
FMReportAttachments *FMReportAttachments `json:"fm_report_attachments,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-20T08:00:00Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-20T08:00:00Z"`
}
type FMReportAirframeValues struct {
Hours float64 `json:"hours" example:"1523.5"`
Cycles float64 `json:"cycles" example:"320"`
}
type FMReportEngineValues struct {
Hours float64 `json:"hours" example:"1523.5"`
GpcNgN1 float64 `json:"gpc_ng_n1" example:"63.2"`
PtcNfN2 float64 `json:"ptc_nf_n2" example:"61.8"`
Ccc float64 `json:"ccc" example:"44"`
}
// FMReportCountersValues are the "Landings, Complaints & Sign-off" counters.
type FMReportCountersValues struct {
Landings float64 `json:"landings" example:"120"`
FlightReports float64 `json:"flight_reports" example:"80"`
HookReleases float64 `json:"hook_releases" example:"10"`
RotorBrakeCycles float64 `json:"rotor_brake_cycles" example:"8"`
}
// FMReportUsageSnapshot is one row of the breakdown table (all columns for a single
// prev/today/total bucket). An absent engine (single-engine aircraft) stays zero-valued.
type FMReportUsageSnapshot struct {
Airframe FMReportAirframeValues `json:"airframe"`
Engine1 FMReportEngineValues `json:"engine_1"`
Engine2 FMReportEngineValues `json:"engine_2"`
Counters FMReportCountersValues `json:"counters"`
}
// FMReportUsageBreakdown is the prev/today/total table shown on the FM report page,
// grouped by row: prev = total - today (the prior-day cumulative), today = this flight's
// contribution, total = the cumulative to date.
type FMReportUsageBreakdown struct {
Prev FMReportUsageSnapshot `json:"prev"`
Today FMReportUsageSnapshot `json:"today"`
Total FMReportUsageSnapshot `json:"total"`
}
type FMReportAttachments struct {
AdditionalFMReport []FMReportAttachmentItem `json:"additional_fm_report,omitempty"`
Briefing []FMReportAttachmentItem `json:"briefing,omitempty"`
WeightBalance []FMReportAttachmentItem `json:"weight_balance,omitempty"`
OFP []FMReportAttachmentItem `json:"ofp,omitempty"`
}
type FMReportAttachmentItem struct {
ID string `json:"id"`
FileID string `json:"file_id"`
DownloadURL *string `json:"download_url,omitempty"`
}
type FMReportResource struct {
Type string `json:"type" example:"fm_report"`
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Attributes FMReportAttributes `json:"attributes"`
}
type FMReportResponse struct {
Data FMReportResource `json:"data"`
}
type FMReportListResponse struct {
Data []FMReportResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}

View File

@@ -0,0 +1,77 @@
package dto
// JSON:API Forces Present DTOs
type ForcesPresentAttributes struct {
Name string `json:"name" example:"Firefighter Team A"`
SortKey *int `json:"sortkey" example:"1"`
Note string `json:"note,omitempty" example:"On-site responders"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type ForcesPresentResource struct {
Type string `json:"type" example:"forces_present"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes ForcesPresentAttributes `json:"attributes"`
}
type ForcesPresentResponse struct {
Data ForcesPresentResource `json:"data"`
}
type ForcesPresentListResponse struct {
Data []ForcesPresentResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type ForcesPresentDataTableResponse struct {
Data []ForcesPresentResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type ForcesPresentCreateAttributes struct {
Name string `json:"name" validate:"required" example:"Firefighter Team A"`
SortKey *int `json:"sortkey" example:"1"`
Note *string `json:"note,omitempty" example:"On-site responders"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type ForcesPresentCreateData struct {
Type string `json:"type" validate:"required,oneof=forces_present_create forces_present" example:"forces_present_create"`
Attributes ForcesPresentCreateAttributes `json:"attributes" validate:"required"`
}
type ForcesPresentCreateRequest struct {
Data ForcesPresentCreateData `json:"data" validate:"required"`
}
type ForcesPresentUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"Firefighter Team A"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
Note *string `json:"note,omitempty" example:"On-site responders"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type ForcesPresentUpdateData struct {
Type string `json:"type" validate:"required,oneof=forces_present_update forces_present" example:"forces_present_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes ForcesPresentUpdateAttributes `json:"attributes" validate:"required"`
}
type ForcesPresentUpdateRequest struct {
Data ForcesPresentUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,96 @@
package dto
// JSON:API Health Insurance Companies DTOs
type HealthInsuranceCompanyAttributes struct {
LandID string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalStateID string `json:"federal_state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalState string `json:"federal_state,omitempty" example:"Bayern"`
LandName string `json:"land_name" example:"Germany"`
LandISOCode string `json:"land_iso_code,omitempty" example:"DE"`
Name string `json:"name" example:"ABC Insurance"`
SortKey *int `json:"sortkey" example:"1"`
State string `json:"state,omitempty" example:"Berlin"`
Address string `json:"address,omitempty" example:"Main Street 10"`
MobileNumber string `json:"mobile_number,omitempty" example:"+4912345678"`
Email string `json:"email,omitempty" example:"info@abc-insurance.local"`
Note string `json:"note,omitempty" example:"Preferred insurance provider"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HealthInsuranceCompanyResource struct {
Type string `json:"type" example:"health_insurance_company"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HealthInsuranceCompanyAttributes `json:"attributes"`
}
type HealthInsuranceCompanyResponse struct {
Data HealthInsuranceCompanyResource `json:"data"`
}
type HealthInsuranceCompanyListResponse struct {
Data []HealthInsuranceCompanyResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type HealthInsuranceCompanyDataTableResponse struct {
Data []HealthInsuranceCompanyResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type HealthInsuranceCompanyCreateAttributes struct {
Name string `json:"name" validate:"required" example:"ABC Insurance"`
SortKey *int `json:"sortkey" example:"1"`
LandID *string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
State *string `json:"state,omitempty" example:"Berlin"`
Address *string `json:"address,omitempty" example:"Main Street 10"`
MobileNumber *string `json:"mobile_number,omitempty" example:"+4912345678"`
Email *string `json:"email,omitempty" example:"info@abc-insurance.local"`
Note *string `json:"note,omitempty" example:"Preferred insurance provider"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type HealthInsuranceCompanyCreateData struct {
Type string `json:"type" validate:"required,oneof=health_insurance_company_create health_insurance_company" example:"health_insurance_company_create"`
Attributes HealthInsuranceCompanyCreateAttributes `json:"attributes" validate:"required"`
}
type HealthInsuranceCompanyCreateRequest struct {
Data HealthInsuranceCompanyCreateData `json:"data" validate:"required"`
}
type HealthInsuranceCompanyUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"ABC Insurance"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
LandID *string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
State *string `json:"state,omitempty" example:"Berlin"`
Address *string `json:"address,omitempty" example:"Main Street 10"`
MobileNumber *string `json:"mobile_number,omitempty" example:"+4912345678"`
Email *string `json:"email,omitempty" example:"info@abc-insurance.local"`
Note *string `json:"note,omitempty" example:"Preferred insurance provider"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type HealthInsuranceCompanyUpdateData struct {
Type string `json:"type" validate:"required,oneof=health_insurance_company_update health_insurance_company" example:"health_insurance_company_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HealthInsuranceCompanyUpdateAttributes `json:"attributes" validate:"required"`
}
type HealthInsuranceCompanyUpdateRequest struct {
Data HealthInsuranceCompanyUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,101 @@
package dto
// JSON:API Helicopter Usage DTOs
type HelicopterUsageAttributes struct {
HelicopterID string `json:"helicopter_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
TotalLanding float64 `json:"total_landing" example:"120"`
TotalAirframeHours float64 `json:"total_airframe_hours" example:"1523.5"`
TotalAirframeCycles float64 `json:"total_airframe_cycles" example:"320"`
TotalEngine1Hours float64 `json:"total_engine_1_hours" example:"1523.5"`
TotalEngine1GpcNgN1 float64 `json:"total_engine_1_gpc_ng_n1" example:"98.7"`
TotalEngine1PtcNfN2 float64 `json:"total_engine_1_ptc_nf_n2" example:"97.2"`
TotalEngine1Ccc float64 `json:"total_engine_1_ccc" example:"44"`
TotalEngine2Hours float64 `json:"total_engine_2_hours" example:"1523.5"`
TotalEngine2GpcNgN1 float64 `json:"total_engine_2_gpc_ng_n1" example:"98.7"`
TotalEngine2PtcNfN2 float64 `json:"total_engine_2_ptc_nf_n2" example:"97.2"`
TotalEngine2Ccc float64 `json:"total_engine_2_ccc" example:"44"`
TotalFlightReport float64 `json:"total_flight_report" example:"80"`
TotalHookRelease float64 `json:"total_hook_release" example:"10"`
TotalRotorBrakeCycle float64 `json:"total_rotor_brake_cycle" example:"8"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HelicopterUsageResource struct {
Type string `json:"type" example:"helicopter_usage"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HelicopterUsageAttributes `json:"attributes"`
}
type HelicopterUsageResponse struct {
Data HelicopterUsageResource `json:"data"`
}
type HelicopterUsageListResponse struct {
Data []HelicopterUsageResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type HelicopterUsageCreateAttributes struct {
HelicopterID string `json:"helicopter_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
TotalLanding *float64 `json:"total_landing,omitempty" example:"120"`
TotalAirframeHours *float64 `json:"total_airframe_hours,omitempty" example:"1523.5"`
TotalAirframeCycles *float64 `json:"total_airframe_cycles,omitempty" example:"320"`
TotalEngine1Hours *float64 `json:"total_engine_1_hours,omitempty" example:"1523.5"`
TotalEngine1GpcNgN1 *float64 `json:"total_engine_1_gpc_ng_n1,omitempty" example:"98.7"`
TotalEngine1PtcNfN2 *float64 `json:"total_engine_1_ptc_nf_n2,omitempty" example:"97.2"`
TotalEngine1Ccc *float64 `json:"total_engine_1_ccc,omitempty" example:"44"`
TotalEngine2Hours *float64 `json:"total_engine_2_hours,omitempty" example:"1523.5"`
TotalEngine2GpcNgN1 *float64 `json:"total_engine_2_gpc_ng_n1,omitempty" example:"98.7"`
TotalEngine2PtcNfN2 *float64 `json:"total_engine_2_ptc_nf_n2,omitempty" example:"97.2"`
TotalEngine2Ccc *float64 `json:"total_engine_2_ccc,omitempty" example:"44"`
TotalFlightReport *float64 `json:"total_flight_report,omitempty" example:"80"`
TotalHookRelease *float64 `json:"total_hook_release,omitempty" example:"10"`
TotalRotorBrakeCycle *float64 `json:"total_rotor_brake_cycle,omitempty" example:"8"`
}
type HelicopterUsageCreateData struct {
Type string `json:"type" validate:"required,oneof=helicopter_usage_create helicopter_usage" example:"helicopter_usage_create"`
Attributes HelicopterUsageCreateAttributes `json:"attributes" validate:"required"`
}
type HelicopterUsageCreateRequest struct {
Data HelicopterUsageCreateData `json:"data" validate:"required"`
}
type HelicopterUsageUpdateAttributes struct {
HelicopterID *string `json:"helicopter_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
TotalLanding *float64 `json:"total_landing,omitempty" example:"120"`
TotalAirframeHours *float64 `json:"total_airframe_hours,omitempty" example:"1523.5"`
TotalAirframeCycles *float64 `json:"total_airframe_cycles,omitempty" example:"320"`
TotalEngine1Hours *float64 `json:"total_engine_1_hours,omitempty" example:"1523.5"`
TotalEngine1GpcNgN1 *float64 `json:"total_engine_1_gpc_ng_n1,omitempty" example:"98.7"`
TotalEngine1PtcNfN2 *float64 `json:"total_engine_1_ptc_nf_n2,omitempty" example:"97.2"`
TotalEngine1Ccc *float64 `json:"total_engine_1_ccc,omitempty" example:"44"`
TotalEngine2Hours *float64 `json:"total_engine_2_hours,omitempty" example:"1523.5"`
TotalEngine2GpcNgN1 *float64 `json:"total_engine_2_gpc_ng_n1,omitempty" example:"98.7"`
TotalEngine2PtcNfN2 *float64 `json:"total_engine_2_ptc_nf_n2,omitempty" example:"97.2"`
TotalEngine2Ccc *float64 `json:"total_engine_2_ccc,omitempty" example:"44"`
TotalFlightReport *float64 `json:"total_flight_report,omitempty" example:"80"`
TotalHookRelease *float64 `json:"total_hook_release,omitempty" example:"10"`
TotalRotorBrakeCycle *float64 `json:"total_rotor_brake_cycle,omitempty" example:"8"`
}
type HelicopterUsageUpdateData struct {
Type string `json:"type" validate:"required,oneof=helicopter_usage_update helicopter_usage" example:"helicopter_usage_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HelicopterUsageUpdateAttributes `json:"attributes" validate:"required"`
}
type HelicopterUsageUpdateRequest struct {
Data HelicopterUsageUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,70 @@
package dto
// JSON:API HEMS Operation Category DTOs
type HEMSOperationCategoryAttributes struct {
Name string `json:"name" example:"Firefighting"`
Type string `json:"type" example:"first"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HEMSOperationCategoryResource struct {
Type string `json:"type" example:"hems_operation_category"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HEMSOperationCategoryAttributes `json:"attributes"`
}
type HEMSOperationCategoryResponse struct {
Data HEMSOperationCategoryResource `json:"data"`
}
type HEMSOperationCategoryListResponse struct {
Data []HEMSOperationCategoryResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type HEMSOperationCategoryDataTableResponse struct {
Data []HEMSOperationCategoryResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type HEMSOperationCategoryCreateAttributes struct {
Name string `json:"name" validate:"required" example:"Firefighting"`
Type string `json:"type" validate:"required,oneof=first secondary misuse" example:"first"`
}
type HEMSOperationCategoryCreateData struct {
Type string `json:"type" validate:"required,oneof=hems_operation_category_create hems_operation_category" example:"hems_operation_category_create"`
Attributes HEMSOperationCategoryCreateAttributes `json:"attributes" validate:"required"`
}
type HEMSOperationCategoryCreateRequest struct {
Data HEMSOperationCategoryCreateData `json:"data" validate:"required"`
}
type HEMSOperationCategoryUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"Firefighting"`
Type *string `json:"type,omitempty" validate:"omitempty,oneof=first secondary misuse" example:"first"`
}
type HEMSOperationCategoryUpdateData struct {
Type string `json:"type" validate:"required,oneof=hems_operation_category_update hems_operation_category" example:"hems_operation_category_update"`
Attributes HEMSOperationCategoryUpdateAttributes `json:"attributes" validate:"required"`
}
type HEMSOperationCategoryUpdateRequest struct {
Data HEMSOperationCategoryUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,95 @@
package dto
// JSON:API HEMS Operation DTOs
type HEMSOperationOperationalDataRef struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HEMSOperationCategoryRef struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Firefighting"`
Type string `json:"type,omitempty" example:"first"`
}
type HEMSOperationMissionRef struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FlightID string `json:"flight_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Type string `json:"type,omitempty" example:"HEMS"`
}
type HEMSOperationAttributes struct {
Date string `json:"date" example:"2026-03-12T03:04:05Z"`
OperationalDataID string `json:"operational_data_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MissionID string `json:"mission_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
HEMSOperationCategoryID string `json:"hems_operation_category_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
OperationalData *HEMSOperationOperationalDataRef `json:"operational_data,omitempty"`
Mission *HEMSOperationMissionRef `json:"mission,omitempty"`
HEMSOperationCategory *HEMSOperationCategoryRef `json:"hems_operation_category,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HEMSOperationResource struct {
Type string `json:"type" example:"hems_operation"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HEMSOperationAttributes `json:"attributes"`
}
type HEMSOperationResponse struct {
Data HEMSOperationResource `json:"data"`
}
type HEMSOperationListResponse struct {
Data []HEMSOperationResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type HEMSOperationDataTableResponse struct {
Data []HEMSOperationResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type HEMSOperationCreateAttributes struct {
Date string `json:"date" validate:"required" example:"2026-03-12T03:04:05Z"`
OperationalDataID *string `json:"operational_data_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MissionID *string `json:"mission_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
HEMSOperationCategoryID *string `json:"hems_operation_category_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HEMSOperationCreateData struct {
Type string `json:"type" validate:"required,oneof=hems_operation_create hems_operation" example:"hems_operation_create"`
Attributes HEMSOperationCreateAttributes `json:"attributes" validate:"required"`
}
type HEMSOperationCreateRequest struct {
Data HEMSOperationCreateData `json:"data" validate:"required"`
}
type HEMSOperationUpdateAttributes struct {
Date *string `json:"date,omitempty" example:"2026-03-12T03:04:05Z"`
OperationalDataID *string `json:"operational_data_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MissionID *string `json:"mission_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
HEMSOperationCategoryID *string `json:"hems_operation_category_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HEMSOperationUpdateData struct {
Type string `json:"type" validate:"required,oneof=hems_operation_update hems_operation" example:"hems_operation_update"`
Attributes HEMSOperationUpdateAttributes `json:"attributes" validate:"required"`
}
type HEMSOperationUpdateRequest struct {
Data HEMSOperationUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,141 @@
package dto
// JSON:API HEMS Operational Data DTOs
type HEMSOperationalDataFileAttributes struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FileAttachmentID string `json:"file_attachment_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HEMSOperationalDataAttributes struct {
Time string `json:"time" example:"2026-03-12T03:04:05Z"`
LandID string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
VocationID string `json:"vocation_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
ForcePresentIDs []string `json:"force_present_ids,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
StateID string `json:"state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Location string `json:"location" example:"123 Main St, Anytown, USA"`
Postcode string `json:"postcode,omitempty" example:"12345"`
Notes string `json:"notes" example:"Notes about the operational data"`
Darkness bool `json:"darkness"`
FalseInformation bool `json:"false_information"`
Fog bool `json:"fog"`
Precipitation bool `json:"precipitation"`
MountainUse bool `json:"mountain_use"`
Wind bool `json:"wind"`
NFOSearch bool `json:"nfo_search"`
LandingSiteSearch bool `json:"landing_site_search"`
Temperature float64 `json:"temperature" example:"23.5"`
Altitude float64 `json:"altitude" example:"5.2"`
TerrainIndex string `json:"terrain_index" example:"Only helicopter landing"`
RopeRecovery bool `json:"rope_recovery"`
NightFlight bool `json:"night_flight"`
InstrumentFlight bool `json:"instrument_flight"`
AdditionalInfo string `json:"additional_info" example:"Additional information about the operational data"`
Files []HEMSOperationalDataFileAttributes `json:"files,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HEMSOperationalDataResource struct {
Type string `json:"type" example:"hems_operational_data"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HEMSOperationalDataAttributes `json:"attributes"`
}
type HEMSOperationalDataResponse struct {
Data HEMSOperationalDataResource `json:"data"`
}
type HEMSOperationalDataListResponse struct {
Data []HEMSOperationalDataResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type HEMSOperationalDataDataTableResponse struct {
Data []HEMSOperationalDataResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type HEMSOperationalDataCreateAttributes struct {
Time string `json:"time" validate:"required" example:"2026-03-12T03:04:05Z"`
LandID *string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
VocationID *string `json:"vocation_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
ForcePresentIDs []string `json:"force_present_ids,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
StateID *string `json:"state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Location string `json:"location" validate:"required" example:"123 Main St, Anytown, USA"`
Postcode *string `json:"postcode,omitempty" example:"12345"`
Notes *string `json:"notes,omitempty" example:"Notes about the operational data"`
Darkness *bool `json:"darkness,omitempty"`
FalseInformation *bool `json:"false_information,omitempty"`
Fog *bool `json:"fog,omitempty"`
Precipitation *bool `json:"precipitation,omitempty"`
MountainUse *bool `json:"mountain_use,omitempty"`
Wind *bool `json:"wind,omitempty"`
NFOSearch *bool `json:"nfo_search,omitempty"`
LandingSiteSearch *bool `json:"landing_site_search,omitempty"`
Temperature *float64 `json:"temperature,omitempty" example:"23.5"`
Altitude *float64 `json:"altitude,omitempty" example:"5.2"`
TerrainIndex *string `json:"terrain_index,omitempty" example:"Only helicopter landing"`
RopeRecovery *bool `json:"rope_recovery,omitempty"`
NightFlight *bool `json:"night_flight,omitempty"`
InstrumentFlight *bool `json:"instrument_flight,omitempty"`
AdditionalInfo *string `json:"additional_info,omitempty" example:"Additional information about the operational data"`
FileAttachmentIDs []string `json:"file_attachment_ids,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HEMSOperationalDataCreateData struct {
Type string `json:"type" validate:"required,oneof=hems_operational_data_create hems_operational_data" example:"hems_operational_data_create"`
Attributes HEMSOperationalDataCreateAttributes `json:"attributes" validate:"required"`
}
type HEMSOperationalDataCreateRequest struct {
Data HEMSOperationalDataCreateData `json:"data" validate:"required"`
}
type HEMSOperationalDataUpdateAttributes struct {
Time *string `json:"time,omitempty" example:"2026-03-12T03:04:05Z"`
LandID *string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
VocationID *string `json:"vocation_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
ForcePresentIDs *[]string `json:"force_present_ids,omitempty"`
StateID *string `json:"state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Location *string `json:"location,omitempty" example:"123 Main St, Anytown, USA"`
Postcode *string `json:"postcode,omitempty" example:"12345"`
Notes *string `json:"notes,omitempty" example:"Notes about the operational data"`
Darkness *bool `json:"darkness,omitempty"`
FalseInformation *bool `json:"false_information,omitempty"`
Fog *bool `json:"fog,omitempty"`
Precipitation *bool `json:"precipitation,omitempty"`
MountainUse *bool `json:"mountain_use,omitempty"`
Wind *bool `json:"wind,omitempty"`
NFOSearch *bool `json:"nfo_search,omitempty"`
LandingSiteSearch *bool `json:"landing_site_search,omitempty"`
Temperature *float64 `json:"temperature,omitempty" example:"23.5"`
Altitude *float64 `json:"altitude,omitempty" example:"5.2"`
TerrainIndex *string `json:"terrain_index,omitempty" example:"Only helicopter landing"`
RopeRecovery *bool `json:"rope_recovery,omitempty"`
NightFlight *bool `json:"night_flight,omitempty"`
InstrumentFlight *bool `json:"instrument_flight,omitempty"`
AdditionalInfo *string `json:"additional_info,omitempty" example:"Additional information about the operational data"`
FileAttachmentIDs *[]string `json:"file_attachment_ids,omitempty"`
}
type HEMSOperationalDataUpdateData struct {
Type string `json:"type" validate:"required,oneof=hems_operational_data_update hems_operational_data" example:"hems_operational_data_update"`
Attributes HEMSOperationalDataUpdateAttributes `json:"attributes" validate:"required"`
}
type HEMSOperationalDataUpdateRequest struct {
Data HEMSOperationalDataUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,89 @@
package dto
// JSON:API Hospital DTOs
type HospitalAttributes struct {
Name string `json:"name" example:"RSUD Kota"`
SortKey *int `json:"sortkey" example:"1"`
Address string `json:"address,omitempty" example:"Jl. Merdeka No. 10"`
LandlineNumber string `json:"landline_number,omitempty" example:"0211234567"`
MobileNumber string `json:"mobile_number,omitempty" example:"08123456789"`
Email string `json:"email,omitempty" example:"info@hospital.local"`
Note string `json:"note,omitempty" example:"Nearest trauma center"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type HospitalResource struct {
Type string `json:"type" example:"hospital"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HospitalAttributes `json:"attributes"`
}
type HospitalResponse struct {
Data HospitalResource `json:"data"`
}
type HospitalListResponse struct {
Data []HospitalResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type HospitalDataTableResponse struct {
Data []HospitalResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type HospitalCreateAttributes struct {
Name string `json:"name" validate:"required" example:"RSUD Kota"`
SortKey *int `json:"sortkey" example:"1"`
Address *string `json:"address,omitempty" example:"Jl. Merdeka No. 10"`
LandlineNumber *string `json:"landline_number,omitempty" example:"0211234567"`
MobileNumber *string `json:"mobile_number,omitempty" example:"08123456789"`
Email *string `json:"email,omitempty" example:"info@hospital.local"`
Note *string `json:"note,omitempty" example:"Nearest trauma center"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type HospitalCreateData struct {
Type string `json:"type" validate:"required,oneof=hospital_create hospital" example:"hospital_create"`
Attributes HospitalCreateAttributes `json:"attributes" validate:"required"`
}
type HospitalCreateRequest struct {
Data HospitalCreateData `json:"data" validate:"required"`
}
type HospitalUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"RSUD Kota"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
Address *string `json:"address,omitempty" example:"Jl. Merdeka No. 10"`
LandlineNumber *string `json:"landline_number,omitempty" example:"0211234567"`
MobileNumber *string `json:"mobile_number,omitempty" example:"08123456789"`
Email *string `json:"email,omitempty" example:"info@hospital.local"`
Note *string `json:"note,omitempty" example:"Nearest trauma center"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type HospitalUpdateData struct {
Type string `json:"type" validate:"required,oneof=hospital_update hospital" example:"hospital_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HospitalUpdateAttributes `json:"attributes" validate:"required"`
}
type HospitalUpdateRequest struct {
Data HospitalUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,101 @@
package dto
// JSON:API ICAO DTOs
type ICAOAttributes struct {
LandID string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalStateID string `json:"federal_state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalState string `json:"federal_state,omitempty" example:"Bayern"`
LandName string `json:"land_name,omitempty" example:"Germany"`
LandISOCode string `json:"land_iso_code,omitempty" example:"DE"`
ICAOCode string `json:"icao_code" example:"EDDB"`
Name string `json:"name,omitempty" example:"Berlin Base"`
SortKey *int `json:"sortkey" example:"1"`
Address string `json:"address,omitempty" example:"Muehlenstrasse 1"`
LandlineNumber string `json:"landline_number,omitempty" example:"030123456"`
MobileNumber string `json:"mobile_number,omitempty" example:"0170123456"`
Email string `json:"email,omitempty" example:"ops@example.local"`
Note string `json:"note,omitempty" example:"Night operations supported"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type ICAOResource struct {
Type string `json:"type" example:"icao"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes ICAOAttributes `json:"attributes"`
}
type ICAOResponse struct {
Data ICAOResource `json:"data"`
}
type ICAOListResponse struct {
Data []ICAOResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type ICAODataTableResponse struct {
Data []ICAOResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type ICAOCreateAttributes struct {
LandID *string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalStateID *string `json:"federal_state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
ICAOCode string `json:"icao_code" validate:"required" example:"EDDB"`
Name *string `json:"name,omitempty" example:"Berlin Base"`
SortKey *int `json:"sortkey" example:"1"`
Address *string `json:"address,omitempty" example:"Muehlenstrasse 1"`
LandlineNumber *string `json:"landline_number,omitempty" example:"030123456"`
MobileNumber *string `json:"mobile_number,omitempty" example:"0170123456"`
Email *string `json:"email,omitempty" example:"ops@example.local"`
Note *string `json:"note,omitempty" example:"Night operations supported"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type ICAOCreateData struct {
Type string `json:"type" validate:"required,oneof=icao_create icao" example:"icao_create"`
Attributes ICAOCreateAttributes `json:"attributes" validate:"required"`
}
type ICAOCreateRequest struct {
Data ICAOCreateData `json:"data" validate:"required"`
}
type ICAOUpdateAttributes struct {
LandID *string `json:"land_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalStateID *string `json:"federal_state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
ICAOCode *string `json:"icao_code,omitempty" example:"EDDB"`
Name *string `json:"name,omitempty" example:"Berlin Base"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
Address *string `json:"address,omitempty" example:"Muehlenstrasse 1"`
LandlineNumber *string `json:"landline_number,omitempty" example:"030123456"`
MobileNumber *string `json:"mobile_number,omitempty" example:"0170123456"`
Email *string `json:"email,omitempty" example:"ops@example.local"`
Note *string `json:"note,omitempty" example:"Night operations supported"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type ICAOUpdateData struct {
Type string `json:"type" validate:"required,oneof=icao_update icao" example:"icao_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes ICAOUpdateAttributes `json:"attributes" validate:"required"`
}
type ICAOUpdateRequest struct {
Data ICAOUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,87 @@
package dto
// JSON:API InsurancePatientData DTOs
type InsurancePatientDataHealthInsuranceCompany struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"ABC Insurance"`
State string `json:"state,omitempty" example:"Berlin"`
Address string `json:"address,omitempty" example:"Main Street 10"`
MobileNumber string `json:"mobile_number,omitempty" example:"+4912345678"`
Email string `json:"email,omitempty" example:"info@abc-insurance.local"`
}
type InsurancePatientDataFederalState struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Bayern"`
}
type InsurancePatientDataAttributes struct {
HealthInsuranceCompaniesId string `json:"health_insurance_companies_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalStateId string `json:"federal_state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
HealthInsuranceCompany *InsurancePatientDataHealthInsuranceCompany `json:"health_insurance_company,omitempty"`
FederalState *InsurancePatientDataFederalState `json:"federal_state,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type InsurancePatientDataResource struct {
Type string `json:"type" example:"insurance_patient_data"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes InsurancePatientDataAttributes `json:"attributes"`
}
type InsurancePatientDataResponse struct {
Data InsurancePatientDataResource `json:"data"`
}
type InsurancePatientDataListResponse struct {
Data []InsurancePatientDataResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type InsurancePatientDataDataTableResponse struct {
Data []InsurancePatientDataResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type InsurancePatientDataCreateAttributes struct {
HealthInsuranceCompaniesId string `json:"health_insurance_companies_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalStateId string `json:"federal_state_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type InsurancePatientDataCreateData struct {
Type string `json:"type" validate:"required,oneof=insurance_patient_data_create insurance_patient_data" example:"insurance_patient_data_create"`
Attributes InsurancePatientDataCreateAttributes `json:"attributes" validate:"required"`
}
type InsurancePatientDataCreateRequest struct {
Data InsurancePatientDataCreateData `json:"data" validate:"required"`
}
type InsurancePatientDataUpdateAttributes struct {
HealthInsuranceCompaniesId *string `json:"health_insurance_companies_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalStateId *string `json:"federal_state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type InsurancePatientDataUpdateData struct {
Type string `json:"type" validate:"required,oneof=insurance_patient_data_update insurance_patient_data" example:"insurance_patient_data_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes InsurancePatientDataUpdateAttributes `json:"attributes" validate:"required"`
}
type InsurancePatientDataUpdateRequest struct {
Data InsurancePatientDataUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,103 @@
package dto
import "wucher/internal/domain/land"
// JSON:API Land DTOs
type LandAttributes struct {
Name string `json:"name" example:"Germany"`
Note string `json:"note,omitempty" example:"Central Europe"`
ISOCode string `json:"iso_code,omitempty" example:"DE"`
BMDExportID *string `json:"bmd_export_id,omitempty" example:"DE001"`
FederalStateTotal int `json:"federal_state_total" example:"2"`
FederalStateList []LandFederalStateDTO `json:"federal_state_list,omitempty"`
SortKey *int `json:"sortkey" example:"1"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type LandFederalStateDTO struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" example:"Bayern"`
}
type LandFederalStateView struct {
ID []byte
Name string
}
type LandView struct {
Row land.Land
FederalStateTotal int
FederalStateList []LandFederalStateView
}
type LandResource struct {
Type string `json:"type" example:"land"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes LandAttributes `json:"attributes"`
}
type LandResponse struct {
Data LandResource `json:"data"`
}
type LandListResponse struct {
Data []LandResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type LandDataTableResponse struct {
Data []LandResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type LandCreateAttributes struct {
Name string `json:"name" validate:"required" example:"Germany"`
Note *string `json:"note,omitempty" example:"Central Europe"`
ISOCode string `json:"iso_code,omitempty" example:"DE"`
BMDExportID *string `json:"bmd_export_id,omitempty" example:"DE001"`
SortKey *int `json:"sortkey" example:"1"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type LandCreateData struct {
Type string `json:"type" validate:"required,oneof=land_create land" example:"land_create"`
Attributes LandCreateAttributes `json:"attributes" validate:"required"`
}
type LandCreateRequest struct {
Data LandCreateData `json:"data" validate:"required"`
}
type LandUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"Germany"`
Note *string `json:"note,omitempty" example:"Central Europe"`
ISOCode *string `json:"iso_code,omitempty" example:"DE"`
BMDExportID *string `json:"bmd_export_id,omitempty" example:"DE001"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type LandUpdateData struct {
Type string `json:"type" validate:"required,oneof=land_update land" example:"land_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes LandUpdateAttributes `json:"attributes" validate:"required"`
}
type LandUpdateRequest struct {
Data LandUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,263 @@
package dto
import (
"bytes"
"encoding/json"
)
// JSON:API Medicine DTOs
type MedicineAttributes struct {
Name string `json:"name" example:"Paracetamol"`
SortKey *int `json:"sortkey" example:"1"`
Note string `json:"note,omitempty" example:"Main stock medicine"`
Pack string `json:"pack,omitempty" example:"500mg"`
Unit string `json:"unit,omitempty" example:"tablet"`
Column int64 `json:"column" example:"1"`
MedicineGroupID string `json:"medicine_group_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MotorReactionID string `json:"motor_reaction_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MotorReactionIDs []string `json:"motor_reaction_ids,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MedicineGroup *MedicineGroupResource `json:"medicine_group,omitempty"`
MotorReaction *MotorReactionResource `json:"motor_reaction,omitempty"`
MotorReactions []MotorReactionResource `json:"motor_reactions,omitempty"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type MedicineResource struct {
Type string `json:"type" example:"medicine"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes MedicineAttributes `json:"attributes"`
}
type MedicineResponse struct {
Data MedicineResource `json:"data"`
}
type MedicineListResponse struct {
Data []MedicineResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type MedicineDataTableResponse struct {
Data []MedicineResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type MedicineCreateAttributes struct {
Name string `json:"name" validate:"required" example:"Paracetamol"`
SortKey *int `json:"sortkey" example:"1"`
Note *string `json:"note,omitempty" example:"Main stock medicine"`
Pack *string `json:"pack,omitempty" example:"500mg"`
Unit *string `json:"unit,omitempty" validate:"omitempty,max=50" example:"tablet"`
Column *int64 `json:"column,omitempty" example:"1"`
MedicineGroupID *string `json:"medicine_group_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MotorReactionID *string `json:"motor_reaction_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MotorReactionIDs []string `json:"motor_reaction_ids,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type MedicineCreateData struct {
Type string `json:"type" validate:"required,oneof=medicine_create medicine" example:"medicine_create"`
Attributes MedicineCreateAttributes `json:"attributes" validate:"required"`
}
type MedicineCreateRequest struct {
Data MedicineCreateData `json:"data" validate:"required"`
}
type MedicineUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"Paracetamol"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
Note *string `json:"note,omitempty" example:"Main stock medicine"`
Pack *string `json:"pack,omitempty" example:"500mg"`
Unit *string `json:"unit,omitempty" validate:"omitempty,max=50" example:"tablet"`
Column *int64 `json:"column,omitempty" example:"1"`
MedicineGroupID NullString `json:"medicine_group_id,omitempty" swaggertype:"string" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MotorReactionID NullString `json:"motor_reaction_id,omitempty" swaggertype:"string" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MotorReactionIDs []string `json:"motor_reaction_ids,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type MedicineUpdateData struct {
Type string `json:"type" validate:"required,oneof=medicine_update medicine" example:"medicine_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes MedicineUpdateAttributes `json:"attributes" validate:"required"`
}
type MedicineUpdateRequest struct {
Data MedicineUpdateData `json:"data" validate:"required"`
}
type MedicineGroupAttributes struct {
Name string `json:"name" example:"Spritze"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type MedicineGroupResource struct {
Type string `json:"type" example:"medicine_group"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes MedicineGroupAttributes `json:"attributes"`
}
type MedicineGroupResponse struct {
Data MedicineGroupResource `json:"data"`
}
type MedicineGroupListResponse struct {
Data []MedicineGroupResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type MedicineGroupDataTableResponse struct {
Data []MedicineGroupResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type MedicineGroupCreateAttributes struct {
Name string `json:"name" validate:"required" example:"Spritze"`
}
type MedicineGroupCreateData struct {
Type string `json:"type" validate:"required,oneof=medicine_group_create medicine_group" example:"medicine_group_create"`
Attributes MedicineGroupCreateAttributes `json:"attributes" validate:"required"`
}
type MedicineGroupCreateRequest struct {
Data MedicineGroupCreateData `json:"data" validate:"required"`
}
type MedicineGroupUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"Spritze"`
}
type MedicineGroupUpdateData struct {
Type string `json:"type" validate:"required,oneof=medicine_group_update medicine_group" example:"medicine_group_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes MedicineGroupUpdateAttributes `json:"attributes" validate:"required"`
}
type MedicineGroupUpdateRequest struct {
Data MedicineGroupUpdateData `json:"data" validate:"required"`
}
type MotorReactionAttributes struct {
Name string `json:"name" example:"gezielt auf Schmerzreiz"`
Score *int64 `json:"score" example:"5"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type MotorReactionResource struct {
Type string `json:"type" example:"motor_reaction"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes MotorReactionAttributes `json:"attributes"`
}
type MotorReactionResponse struct {
Data MotorReactionResource `json:"data"`
}
type MotorReactionListResponse struct {
Data []MotorReactionResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type MotorReactionDataTableResponse struct {
Data []MotorReactionResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type MotorReactionCreateAttributes struct {
Name string `json:"name" validate:"required" example:"gezielt auf Schmerzreiz"`
Score *int64 `json:"score,omitempty" example:"5"`
}
type MotorReactionCreateData struct {
Type string `json:"type" validate:"required,oneof=motor_reaction_create motor_reaction" example:"motor_reaction_create"`
Attributes MotorReactionCreateAttributes `json:"attributes" validate:"required"`
}
type MotorReactionCreateRequest struct {
Data MotorReactionCreateData `json:"data" validate:"required"`
}
type MotorReactionUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"gezielt auf Schmerzreiz"`
Score NullInt64 `json:"score,omitempty" swaggertype:"integer" example:"5"`
}
// NullInt64 allows PATCH semantics:
// - field omitted => Set=false (no change)
// - value null => Set=true, Valid=false (set DB NULL)
// - value number => Set=true, Valid=true, Value=<number>
type NullInt64 struct {
Set bool
Valid bool
Value int64
}
func (n *NullInt64) UnmarshalJSON(data []byte) error {
n.Set = true
trimmed := bytes.TrimSpace(data)
if bytes.Equal(trimmed, []byte("null")) {
n.Valid = false
n.Value = 0
return nil
}
var v int64
if err := json.Unmarshal(trimmed, &v); err != nil {
return err
}
n.Valid = true
n.Value = v
return nil
}
type MotorReactionUpdateData struct {
Type string `json:"type" validate:"required,oneof=motor_reaction_update motor_reaction" example:"motor_reaction_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes MotorReactionUpdateAttributes `json:"attributes" validate:"required"`
}
type MotorReactionUpdateRequest struct {
Data MotorReactionUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,33 @@
package dto
import (
"bytes"
"encoding/json"
)
// NullInt allows PATCH semantics:
// - field omitted => Set=false (no change)
// - value null => Set=true, Valid=false (set DB NULL)
// - value number => Set=true, Valid=true, Value=<number>
type NullInt struct {
Set bool
Valid bool
Value int
}
func (n *NullInt) UnmarshalJSON(data []byte) error {
n.Set = true
trimmed := bytes.TrimSpace(data)
if bytes.Equal(trimmed, []byte("null")) {
n.Valid = false
n.Value = 0
return nil
}
var v int
if err := json.Unmarshal(trimmed, &v); err != nil {
return err
}
n.Valid = true
n.Value = v
return nil
}

View File

@@ -0,0 +1,77 @@
package dto
// JSON:API OPC DTOs
type OpcAttributes struct {
Title string `json:"title" example:"Main OPC Base"`
SortKey *int `json:"sortkey" example:"1"`
Note string `json:"note,omitempty" example:"Primary operation control center"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type OpcResource struct {
Type string `json:"type" example:"opc"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes OpcAttributes `json:"attributes"`
}
type OpcResponse struct {
Data OpcResource `json:"data"`
}
type OpcListResponse struct {
Data []OpcResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type OpcDataTableResponse struct {
Data []OpcResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type OpcCreateAttributes struct {
Title string `json:"title" validate:"required" example:"Main OPC Base"`
SortKey *int `json:"sortkey" example:"1"`
Note *string `json:"note,omitempty" example:"Primary operation control center"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type OpcCreateData struct {
Type string `json:"type" validate:"required,oneof=opc_create opc" example:"opc_create"`
Attributes OpcCreateAttributes `json:"attributes" validate:"required"`
}
type OpcCreateRequest struct {
Data OpcCreateData `json:"data" validate:"required"`
}
type OpcUpdateAttributes struct {
Title *string `json:"title,omitempty" example:"Main OPC Base"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
Note *string `json:"note,omitempty" example:"Primary operation control center"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type OpcUpdateData struct {
Type string `json:"type" validate:"required,oneof=opc_update opc" example:"opc_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes OpcUpdateAttributes `json:"attributes" validate:"required"`
}
type OpcUpdateRequest struct {
Data OpcUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,114 @@
package dto
// JSON:API PatientData DTOs
type PatientDataOPC struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Title string `json:"title,omitempty" example:"Primary OPC"`
}
type PatientDataInsuranceDetail struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
HealthInsuranceCompanyID string `json:"health_insurance_companies_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FederalStateID string `json:"federal_state_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
HealthInsuranceCompany *InsurancePatientDataHealthInsuranceCompany `json:"health_insurance_company,omitempty"`
FederalState *InsurancePatientDataFederalState `json:"federal_state,omitempty"`
}
type PatientDataAttributes struct {
FamilyName string `json:"family_name" example:"Muster"`
FirstName string `json:"first_name" example:"Max"`
OPCId string `json:"opc_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
BirthDate string `json:"birth_date,omitempty" example:"1990-01-15"`
Age *int `json:"age,omitempty" example:"35"`
Gender string `json:"gender" example:"male"`
Street string `json:"street,omitempty" example:"Hauptstraße 1"`
SVNR string `json:"svnr,omitempty" example:"1234150190"`
Email string `json:"email,omitempty" example:"max.muster@example.com"`
Phone string `json:"phone,omitempty" example:"+431234567"`
CoInsured bool `json:"co_insured" example:"false"`
InsurancePatientDataId string `json:"insurance_patient_data_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
OPC *PatientDataOPC `json:"opc,omitempty"`
InsurancePatientData *PatientDataInsuranceDetail `json:"insurance_patient_data,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type PatientDataResource struct {
Type string `json:"type" example:"patient_data"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes PatientDataAttributes `json:"attributes"`
}
type PatientDataResponse struct {
Data PatientDataResource `json:"data"`
}
type PatientDataListResponse struct {
Data []PatientDataResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type PatientDataDataTableResponse struct {
Data []PatientDataResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type PatientDataCreateAttributes struct {
FamilyName string `json:"family_name" validate:"required" example:"Muster"`
FirstName string `json:"first_name" validate:"required" example:"Max"`
OPCId *string `json:"opc_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
BirthDate *string `json:"birth_date,omitempty" example:"1990-01-15"`
Gender string `json:"gender" validate:"required,oneof=male female" example:"male"`
Street *string `json:"street,omitempty" example:"Hauptstraße 1"`
SVNR *string `json:"svnr,omitempty" example:"1234150190"`
Email *string `json:"email,omitempty" example:"max.muster@example.com"`
Phone *string `json:"phone,omitempty" example:"+431234567"`
CoInsured *bool `json:"co_insured,omitempty" example:"false"`
InsurancePatientDataId *string `json:"insurance_patient_data_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type PatientDataCreateData struct {
Type string `json:"type" validate:"required,oneof=patient_data_create patient_data" example:"patient_data_create"`
Attributes PatientDataCreateAttributes `json:"attributes" validate:"required"`
}
type PatientDataCreateRequest struct {
Data PatientDataCreateData `json:"data" validate:"required"`
}
type PatientDataUpdateAttributes struct {
FamilyName *string `json:"family_name,omitempty" example:"Muster"`
FirstName *string `json:"first_name,omitempty" example:"Max"`
OPCId *string `json:"opc_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
BirthDate *string `json:"birth_date,omitempty" example:"1990-01-15"`
Gender *string `json:"gender,omitempty" example:"male"`
Street *string `json:"street,omitempty" example:"Hauptstraße 1"`
SVNR *string `json:"svnr,omitempty" example:"1234150190"`
Email *string `json:"email,omitempty" example:"max.muster@example.com"`
Phone *string `json:"phone,omitempty" example:"+431234567"`
CoInsured *bool `json:"co_insured,omitempty" example:"false"`
InsurancePatientDataId *string `json:"insurance_patient_data_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type PatientDataUpdateData struct {
Type string `json:"type" validate:"required,oneof=patient_data_update patient_data" example:"patient_data_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes PatientDataUpdateAttributes `json:"attributes" validate:"required"`
}
type PatientDataUpdateRequest struct {
Data PatientDataUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,27 @@
package request
// ActionSignoffSignAttributes — optional fields for the sign-off.
// - ComplaintID: when set, the sign-off marks that complaint's corrective action as
// signed off (the complaint must already have action_taken recorded, else 422).
// - When ComplaintID is omitted it is a complaint-independent (fleet) sign-off, which
// still allows an EASA release.
type ActionSignoffSignAttributes struct {
// Sign toggles the sign-off: true (or omitted) signs it, false unsigns (clears the
// signature). Combine with complaint_id to toggle a specific complaint's sign-off.
Sign *bool `json:"sign,omitempty" example:"true"`
ComplaintID *string `json:"complaint_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
// FlightID — REQUIRED for the fleet (complaint-independent) sign-off; scopes it to a
// flight so it no longer leaks across takeovers of the same aircraft. Ignored when
// complaint_id is set (that sign-off is keyed by the complaint).
FlightID *string `json:"flight_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
}
type ActionSignoffSignData struct {
Type string `json:"type,omitempty" example:"action_signoff"`
Attributes ActionSignoffSignAttributes `json:"attributes"`
}
// ActionSignoffSignRequest — body is OPTIONAL for the sign endpoint.
type ActionSignoffSignRequest struct {
Data ActionSignoffSignData `json:"data"`
}

View File

@@ -0,0 +1,87 @@
package request
import shareddto "wucher/internal/transport/http/dto/shared"
type AirRescuerChecklistCreateEntry struct {
ScopeCode string `json:"scope_code" validate:"required" example:"MO"`
Title string `json:"title" validate:"required" example:"Monday Checklist"`
Position *int `json:"position,omitempty" example:"1"`
Items []shareddto.AirRescuerChecklistItemInput `json:"items,omitempty" validate:"omitempty,dive"`
}
type AirRescuerChecklistCreateAttributes struct {
BaseID string `json:"base_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Checklists []AirRescuerChecklistCreateEntry `json:"checklists" validate:"required,min=1,dive"`
}
type AirRescuerChecklistCreateData struct {
Type string `json:"type" validate:"required,oneof=air_rescuer_checklist_create"`
Attributes AirRescuerChecklistCreateAttributes `json:"attributes" validate:"required"`
}
type AirRescuerChecklistCreateRequest struct {
Data AirRescuerChecklistCreateData `json:"data" validate:"required"`
}
type AirRescuerChecklistUpdateAttributes struct {
ScopeCode *string `json:"scope_code,omitempty" example:"MO"`
Title *string `json:"title,omitempty" example:"Monday Checklist Updated"`
Position *int `json:"position,omitempty" example:"1"`
NewItems []shareddto.AirRescuerChecklistItemInput `json:"new_items,omitempty" validate:"omitempty,dive"`
}
type AirRescuerChecklistUpdateData struct {
Type string `json:"type" validate:"required,oneof=air_rescuer_checklist_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes AirRescuerChecklistUpdateAttributes `json:"attributes" validate:"required"`
}
type AirRescuerChecklistUpdateRequest struct {
Data AirRescuerChecklistUpdateData `json:"data" validate:"required"`
}
type AirRescuerChecklistItemCreateAttributes struct {
Items []shareddto.AirRescuerChecklistItemInput `json:"items" validate:"required,min=1,dive"`
}
type AirRescuerChecklistItemCreateData struct {
Type string `json:"type" validate:"required,oneof=air_rescuer_checklist_item_create"`
Attributes AirRescuerChecklistItemCreateAttributes `json:"attributes" validate:"required"`
}
type AirRescuerChecklistItemCreateRequest struct {
Data AirRescuerChecklistItemCreateData `json:"data" validate:"required"`
}
type AirRescuerChecklistItemUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"Check radio"`
Position *int `json:"position,omitempty" example:"2"`
}
type AirRescuerChecklistItemUpdateData struct {
Type string `json:"type" validate:"required,oneof=air_rescuer_checklist_item_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes AirRescuerChecklistItemUpdateAttributes `json:"attributes" validate:"required"`
}
type AirRescuerChecklistItemUpdateRequest struct {
Data AirRescuerChecklistItemUpdateData `json:"data" validate:"required"`
}
type AirRescuerChecklistItemReorderEntry struct {
ItemUUID string `json:"item_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Position *int `json:"position" validate:"required" example:"2"`
}
type AirRescuerChecklistItemReorderAttributes struct {
Items []AirRescuerChecklistItemReorderEntry `json:"items" validate:"required,min=1,dive"`
}
type AirRescuerChecklistItemReorderData struct {
Type string `json:"type" validate:"required,oneof=air_rescuer_checklist_item_reorder"`
Attributes AirRescuerChecklistItemReorderAttributes `json:"attributes" validate:"required"`
}
type AirRescuerChecklistItemReorderRequest struct {
Data AirRescuerChecklistItemReorderData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,82 @@
package request
type ComplaintCreateAttributes struct {
// HelicopterID — the helicopter this complaint is filed against. REQUIRED.
HelicopterID string `json:"helicopter_id" validate:"required,uuid" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
// FlightID — the flight/report this complaint was filed in (attribution). OPTIONAL;
// open-defect tracking stays per-helicopter so defects carry forward across flights.
FlightID string `json:"flight_id,omitempty" validate:"omitempty,uuid" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
// Description — the defect description. REQUIRED.
Description string `json:"description" validate:"required" example:"Engine 2 N2 vibration intermittent during cruise above 110 KIAS"`
// MELSeverity — MEL (Minimum Equipment List) classification. OPTIONAL at creation:
// omit it to file the complaint as "pending_mel" and classify later. It MUST be
// classified (via update) before recording action taken. A pending complaint does
// NOT ground the aircraft — grounding starts at classification. Values: 0 = NON-MEL
// (grounds immediately), 1 = A (grace 1 day), 2 = B (3 days), 3 = C (10 days), 4 = D (120 days).
MELSeverity *int8 `json:"mel_severity,omitempty" validate:"omitempty,oneof=0 1 2 3 4" enums:"0,1,2,3,4" example:"2"`
// AircraftHoursAtReport — OPTIONAL airframe hours recorded when the defect was reported.
AircraftHoursAtReport *string `json:"aircraft_hours_at_report,omitempty" example:"1245.3"`
// NSR is no longer set here — it is its own flow: POST /complaints/nsr/sign/{id}.
}
type ComplaintCreateData struct {
Type string `json:"type" validate:"required,oneof=complaint_create complaint" example:"complaint_create"`
Attributes ComplaintCreateAttributes `json:"attributes" validate:"required"`
}
type ComplaintCreateRequest struct {
Data ComplaintCreateData `json:"data" validate:"required"`
}
type ComplaintUpdateAttributes struct {
// Description — defect description.
Description *string `json:"description,omitempty" example:"Engine 2 vibration confirmed"`
// MELSeverity — MEL classification. 0 = NON-MEL (immediate AOG), 1 = A (1 day),
// 2 = B (3 days), 3 = C (10 days), 4 = D (120 days). AOG after grace passes.
MELSeverity *int8 `json:"mel_severity,omitempty" validate:"omitempty,oneof=0 1 2 3 4" enums:"0,1,2,3,4" example:"2"`
// AircraftHoursAtReport — airframe hours at report time.
AircraftHoursAtReport *string `json:"aircraft_hours_at_report,omitempty" example:"1245.3"`
// NSR is no longer set here — it is its own flow: POST /complaints/nsr/sign/{id}.
}
// ComplaintNSRSignAttributes carries the (optional) justification for a Non-Safety
// Release decision. NSR is signed via its own endpoint, separate from complaint update.
type ComplaintNSRSignAttributes struct {
// NSRReason — OPTIONAL free-text justification for the NSR (MEL deferral) decision.
NSRReason *string `json:"nsr_reason,omitempty" example:"Released under MEL A, defect deferred"`
}
type ComplaintNSRSignData struct {
Type string `json:"type" validate:"omitempty,oneof=complaint_nsr_sign complaint_nsr" example:"complaint_nsr_sign"`
Attributes ComplaintNSRSignAttributes `json:"attributes"`
}
type ComplaintNSRSignRequest struct {
Data ComplaintNSRSignData `json:"data"`
}
type ComplaintUpdateData struct {
Type string `json:"type" validate:"required,oneof=complaint_update complaint" example:"complaint_update"`
ID string `json:"id" validate:"required" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Attributes ComplaintUpdateAttributes `json:"attributes" validate:"required"`
}
type ComplaintUpdateRequest struct {
Data ComplaintUpdateData `json:"data" validate:"required"`
}
type ComplaintActionTakenAttributes struct {
// ActionTaken — corrective action text. Required when recording (create); on update an
// empty value clears it (allowed only while the action has not been signed off).
ActionTaken string `json:"action_taken" example:"Tightened engine mount and rechecked torque"`
AircraftHoursAtFix *string `json:"aircraft_hours_at_fix,omitempty" example:"1250.1"`
}
type ComplaintActionTakenData struct {
Type string `json:"type" validate:"required,oneof=complaint_action_taken complaint" example:"complaint_action_taken"`
Attributes ComplaintActionTakenAttributes `json:"attributes" validate:"required"`
}
type ComplaintActionTakenRequest struct {
Data ComplaintActionTakenData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,143 @@
package request
type DutyRosterShiftTime struct {
ShiftStart string `json:"shift_start,omitempty" example:"06:00"`
ShiftEnd string `json:"shift_end,omitempty" example:"21:00"`
}
type DutyRosterShiftDate struct {
DateStart string `json:"date_start,omitempty" example:"2026-03-16"`
DateEnd string `json:"date_end,omitempty" example:"2026-03-16"`
}
type DutyRosterCrewInput struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
ShiftDate *DutyRosterShiftDate `json:"shift_date,omitempty"`
}
type DutyRosterPilotCrewInput struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
ShiftDate *DutyRosterShiftDate `json:"shift_date,omitempty"`
FlightInstructor bool `json:"flight_instructor,omitempty"`
LineChecker bool `json:"line_checker,omitempty"`
Supervisor bool `json:"supervisor,omitempty"`
Examiner bool `json:"examiner,omitempty"`
CoPilot bool `json:"co_pilot,omitempty"`
CrewType string `json:"crew_type,omitempty" example:"main" enums:"main,additional"`
}
type DutyRosterOtherPersonInput struct {
// OtherPersonID — OPTIONAL master person id to reuse (from GET /contact/other).
// When set, name/mobile_phone/email are snapshotted from the master (may be omitted).
OtherPersonID string `json:"other_person_id,omitempty" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Name string `json:"name,omitempty" example:"Guest Person"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+43-1234"`
Email string `json:"email,omitempty" example:"guest@example.com"`
}
type DutyRosterDetailsInput struct {
Pilot []DutyRosterPilotCrewInput `json:"pilot"`
Doctor []DutyRosterCrewInput `json:"doctor"`
Rescuer []DutyRosterCrewInput `json:"rescuer"`
Helper []DutyRosterCrewInput `json:"helper"`
OtherPerson []DutyRosterOtherPersonInput `json:"other_person"`
}
type DutyRosterCreateAttributes struct {
BaseID string `json:"base_id" validate:"required"`
DutyDate string `json:"duty_date" validate:"required"`
ShiftTime DutyRosterShiftTime `json:"shift_time"`
Detail DutyRosterDetailsInput `json:"roster_detail"`
}
type DutyRosterCreateData struct {
Type string `json:"type" validate:"required,oneof=duty_roster_create duty_roster"`
Attributes DutyRosterCreateAttributes `json:"attributes" validate:"required"`
}
type DutyRosterCreateRequest struct {
Data DutyRosterCreateData `json:"data" validate:"required"`
}
type DutyRosterUpdateAttributes struct {
BaseID *string `json:"base_id,omitempty"`
DutyDate *string `json:"duty_date,omitempty"`
ShiftTime *DutyRosterShiftTime `json:"shift_time,omitempty"`
Detail *DutyRosterDetailsInput `json:"roster_detail,omitempty"`
}
type DutyRosterUpdateData struct {
Type string `json:"type" validate:"required,oneof=duty_roster_update duty_roster"`
ID string `json:"id"`
Attributes DutyRosterUpdateAttributes `json:"attributes" validate:"required"`
}
type DutyRosterUpdateRequest struct {
Data DutyRosterUpdateData `json:"data" validate:"required"`
}
type DutyRosterDeleteCrewAttributes struct {
Detail DutyRosterDetailsInput `json:"roster_detail"`
}
type DutyRosterDeleteCrewData struct {
Type string `json:"type" validate:"required,oneof=duty_roster_crew_delete duty_roster_update duty_roster"`
Attributes DutyRosterDeleteCrewAttributes `json:"attributes" validate:"required"`
}
type DutyRosterDeleteCrewRequest struct {
Data DutyRosterDeleteCrewData `json:"data" validate:"required"`
}
type DutyRosterDeleteCrewByIDAttributes struct {
RosterID string `json:"roster_id" validate:"required"`
CrewID string `json:"crew_id" validate:"required"`
}
type DutyRosterDeleteCrewByIDData struct {
Type string `json:"type" validate:"required,oneof=duty_roster_crew_delete duty_roster_update duty_roster"`
Attributes DutyRosterDeleteCrewByIDAttributes `json:"attributes" validate:"required"`
}
type DutyRosterDeleteCrewByIDRequest struct {
Data DutyRosterDeleteCrewByIDData `json:"data" validate:"required"`
}
type DutyRosterAnyDeleteData struct {
ID string `json:"id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type DutyRosterAnyDeleteRequest struct {
Data DutyRosterAnyDeleteData `json:"data" validate:"required"`
}
// DutyRosterAnyCreateRequestDoc is documentation-only schema for cleaner Swagger examples.
type DutyRosterAnyCreateRequestDoc struct {
Data DutyRosterAnyCreateDataDoc `json:"data"`
}
type DutyRosterAnyCreateDataDoc struct {
Type string `json:"type" example:"duty_roster_create" enums:"duty_roster_create,duty_roster"`
Attributes DutyRosterAnyCreateAttributesDoc `json:"attributes"`
}
type DutyRosterAnyCreateAttributesDoc struct {
BaseID string `json:"base_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DutyDate string `json:"duty_date" example:"2026-03-16"`
ShiftTime DutyRosterShiftTime `json:"shift_time"`
Detail DutyRosterAnyCreateDetailDoc `json:"roster_detail"`
}
type DutyRosterAnyCreateDetailDoc struct {
Pilot []DutyRosterPilotAdditionalExampleDoc `json:"pilot"`
Doctor []DutyRosterCrewInput `json:"doctor"`
Rescuer []DutyRosterCrewInput `json:"rescuer"`
Helper []DutyRosterCrewInput `json:"helper"`
OtherPerson []DutyRosterOtherPersonInput `json:"other_person"`
}
type DutyRosterPilotAdditionalExampleDoc struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CrewType string `json:"crew_type" example:"additional" enums:"main,additional"`
FlightInstructor bool `json:"flight_instructor" example:"true"`
}

View File

@@ -0,0 +1,53 @@
package request
type EASAReleaseCreateAttributes struct {
HelicopterID string `json:"helicopter_id" validate:"required,uuid" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
// ComplaintID — OPTIONAL complaint (UUIDv7) this release closes. Set it to scope the
// release to a specific defect; leave empty for a standalone (aircraft-clear) release.
ComplaintID string `json:"complaint_id,omitempty" validate:"omitempty,uuid" example:"019d6774-1111-7fba-9ef4-3795c0da8a13"`
// FlightID — OPTIONAL flight context. Used for the standalone (no-complaint) sign-off gate.
FlightID string `json:"flight_id,omitempty" validate:"omitempty,uuid" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
EASADate *string `json:"easa_date,omitempty" example:"2026-06-16"`
EASAACHours *string `json:"easa_ac_hours,omitempty" example:"1245.3"`
EASALocation *string `json:"easa_location,omitempty" example:"LOWI"`
EASAWONo *string `json:"easa_wo_no,omitempty" example:"WO-2026-001"`
EASAMaintManualRevAirframe *string `json:"easa_maint_manual_rev_airframe,omitempty" example:"Rev 12"`
EASAMaintManualRevEngine *string `json:"easa_maint_manual_rev_engine,omitempty" example:"Rev 8"`
// EASASignerID — OPTIONAL contact id (UUIDv7) of the signer/technician. Required only to sign.
EASASignerID *string `json:"easa_signer_id,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
EASAPermitNo *string `json:"easa_permit_no,omitempty" example:"145.A.50"`
// Sign, when true, signs the release immediately at creation. Requires all
// required fields to be present; otherwise the request is rejected.
Sign bool `json:"sign,omitempty" example:"false"`
}
type EASAReleaseCreateData struct {
Type string `json:"type" validate:"required,oneof=easa_release_create easa_release" example:"easa_release_create"`
Attributes EASAReleaseCreateAttributes `json:"attributes" validate:"required"`
}
type EASAReleaseCreateRequest struct {
Data EASAReleaseCreateData `json:"data" validate:"required"`
}
type EASAReleaseUpdateAttributes struct {
EASADate *string `json:"easa_date,omitempty" example:"2026-06-16"`
EASAACHours *string `json:"easa_ac_hours,omitempty" example:"1245.3"`
EASALocation *string `json:"easa_location,omitempty" example:"LOWI"`
EASAWONo *string `json:"easa_wo_no,omitempty" example:"WO-2026-001"`
EASAMaintManualRevAirframe *string `json:"easa_maint_manual_rev_airframe,omitempty" example:"Rev 12"`
EASAMaintManualRevEngine *string `json:"easa_maint_manual_rev_engine,omitempty" example:"Rev 8"`
// EASASignerID — OPTIONAL contact id (UUIDv7) of the signer/technician. Required only to sign.
EASASignerID *string `json:"easa_signer_id,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
EASAPermitNo *string `json:"easa_permit_no,omitempty" example:"145.A.50"`
}
type EASAReleaseUpdateData struct {
Type string `json:"type" validate:"required,oneof=easa_release_update easa_release" example:"easa_release_update"`
ID string `json:"id" validate:"required" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Attributes EASAReleaseUpdateAttributes `json:"attributes" validate:"required"`
}
type EASAReleaseUpdateRequest struct {
Data EASAReleaseUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,19 @@
package request
type FileManagerAttachmentCreateAttributes struct {
FileID string `json:"file_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
RefType string `json:"ref_type" validate:"required" example:"helicopter"`
RefID string `json:"ref_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Category *string `json:"category,omitempty" example:"document"`
SortOrder *int `json:"sort_order,omitempty" example:"0"`
IsPrimary *bool `json:"is_primary,omitempty" example:"false"`
}
type FileManagerAttachmentCreateData struct {
Type string `json:"type" validate:"required,oneof=file_manager_attachment_create file_manager_attachment" example:"file_manager_attachment_create"`
Attributes FileManagerAttachmentCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerAttachmentCreateRequest struct {
Data FileManagerAttachmentCreateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,197 @@
package request
type FileManagerFileCreateAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FolderID *string `json:"folder_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" validate:"required" example:"policy-final.pdf"`
}
type FileManagerFileUploadIntentAttributes struct {
Name string `json:"name" validate:"required" example:"policy-final.pdf"`
ContentType string `json:"content_type" validate:"required" example:"application/pdf"`
SizeBytes int64 `json:"size_bytes" validate:"required,gte=0" example:"1024"`
}
type FileManagerFileUploadIntentData struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_upload_intent" example:"file_manager_file_upload_intent"`
Attributes FileManagerFileUploadIntentAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileUploadIntentRequest struct {
Data FileManagerFileUploadIntentData `json:"data" validate:"required"`
}
type FileManagerFileUploadIntentBulkRequest struct {
Data []FileManagerFileUploadIntentData `json:"data" validate:"required,min=1"`
}
type FileManagerFileCancelUploadAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerFileCancelUploadData struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_cancel_upload" example:"file_manager_file_cancel_upload"`
Attributes FileManagerFileCancelUploadAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileCancelUploadBulkRequest struct {
Data []FileManagerFileCancelUploadData `json:"data" validate:"required,min=1"`
}
type FileManagerFileUploadCompleteAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerFileUploadCompleteData struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_upload_complete" example:"file_manager_file_upload_complete"`
Attributes FileManagerFileUploadCompleteAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileUploadCompleteRequest struct {
Data FileManagerFileUploadCompleteData `json:"data" validate:"required"`
}
type FileManagerFileCreateData struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create file_manager_file" example:"file_manager_file_create"`
Attributes FileManagerFileCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileCreateRequest struct {
Data FileManagerFileCreateData `json:"data" validate:"required"`
}
// FileManagerFileCreateFlexibleRequest is used for swagger documentation.
// Runtime accepts:
// - single: {"data": { ...FileManagerFileCreateData }}
// - bulk: {"data": [ ...FileManagerFileBulkCreateItem ]}
type FileManagerFileCreateFlexibleRequest struct {
Data interface{} `json:"data"`
}
type FileManagerFileBulkCreateItem struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create file_manager_file" example:"file_manager_file_create"`
Attributes FileManagerFileCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileBulkCreateRequest struct {
Data []FileManagerFileBulkCreateItem `json:"data" validate:"required,min=1"`
}
type FileManagerFileUserCreateAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UserUUID string `json:"user_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" validate:"required" example:"profile.jpg"`
}
type FileManagerFileUserBulkCreateItem struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create file_manager_file" example:"file_manager_file_create"`
Attributes FileManagerFileUserCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileUserBulkCreateRequest struct {
Data []FileManagerFileUserBulkCreateItem `json:"data" validate:"required,min=1"`
}
type FileManagerFileAircraftCreateAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
AircraftUUID string `json:"aircraft_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" validate:"required" example:"aircraft.jpg"`
}
type FileManagerFileAircraftBulkCreateItem struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create file_manager_file" example:"file_manager_file_create"`
Attributes FileManagerFileAircraftCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileAircraftBulkCreateRequest struct {
Data []FileManagerFileAircraftBulkCreateItem `json:"data" validate:"required,min=1"`
}
type FileManagerFileBaseCreateAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
BaseUUID string `json:"base_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" validate:"required" example:"base.jpg"`
}
type FileManagerFileBaseBulkCreateItem struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create file_manager_file" example:"file_manager_file_create"`
Attributes FileManagerFileBaseCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileBaseBulkCreateRequest struct {
Data []FileManagerFileBaseBulkCreateItem `json:"data" validate:"required,min=1"`
}
type FileManagerFileDULCreateAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DULUUID string `json:"dul_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" validate:"required" example:"dul.jpg"`
}
type FileManagerFileDULBulkCreateItem struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create file_manager_file" example:"file_manager_file_create"`
Attributes FileManagerFileDULCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileDULBulkCreateRequest struct {
Data []FileManagerFileDULBulkCreateItem `json:"data" validate:"required,min=1"`
}
type FileManagerFileTakeoverCreateAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
TakeoverUUID string `json:"takeover_uuid,omitempty" example:"019e8713-aa19-7fb7-a510-a0332dc69697"`
Name string `json:"name" validate:"required" example:"takeover-report.pdf"`
}
type FileManagerFileTakeoverBulkCreateItem struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create file_manager_file" example:"file_manager_file_create"`
Attributes FileManagerFileTakeoverCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileTakeoverBulkCreateRequest struct {
Data []FileManagerFileTakeoverBulkCreateItem `json:"data" validate:"required,min=1"`
}
type FileManagerFileTemplateCreateAttributes struct {
TemplateUUID string `json:"template_uuid" validate:"required" example:"019f0386-0a00-7a4a-a2d8-30afc0e0c3ad"`
HelicopterID string `json:"helicopter_id" validate:"required" example:"019e8713-aa19-7fb7-a510-a0332dc69697"`
TakeoverID string `json:"takeover_id,omitempty" example:"019e8713-aa19-7fb7-a510-a0332dc69697"`
Name string `json:"name" validate:"required" example:"collabora-draft.docx"`
}
type FileManagerFileTemplateCreateRequest struct {
Data struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create_from_template file_manager_file_create"`
Attributes FileManagerFileTemplateCreateAttributes `json:"attributes" validate:"required"`
} `json:"data" validate:"required"`
}
type FileManagerFileMissionCreateAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
MissionUUID string `json:"mission_uuid" validate:"required" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Name string `json:"name" validate:"required" example:"mission-brief.pdf"`
}
type FileManagerFileMissionBulkCreateItem struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_create file_manager_file" example:"file_manager_file_create"`
Attributes FileManagerFileMissionCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileMissionBulkCreateRequest struct {
Data []FileManagerFileMissionBulkCreateItem `json:"data" validate:"required,min=1"`
}
type FileManagerFileUpdateAttributes struct {
FolderID *string `json:"folder_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name *string `json:"name,omitempty" example:"policy-v2.pdf"`
}
type FileManagerFileUpdateData struct {
Type string `json:"type" validate:"required,oneof=file_manager_file_update file_manager_file" example:"file_manager_file_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FileManagerFileUpdateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFileUpdateRequest struct {
Data FileManagerFileUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,47 @@
package request
type FileManagerFolderCreateAttributes struct {
ParentID *string `json:"parent_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" validate:"required" example:"operations"`
}
type FileManagerFolderCreateData struct {
Type string `json:"type" validate:"required,oneof=file_manager_folder_create file_manager_folder" example:"file_manager_folder_create"`
Attributes FileManagerFolderCreateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFolderCreateRequest struct {
Data FileManagerFolderCreateData `json:"data" validate:"required"`
}
type FileManagerFolderUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"operations-updated"`
ParentID *string `json:"parent_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerFolderUpdateData struct {
Type string `json:"type" validate:"required,oneof=file_manager_folder_update file_manager_folder" example:"file_manager_folder_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FileManagerFolderUpdateAttributes `json:"attributes" validate:"required"`
}
type FileManagerFolderUpdateRequest struct {
Data FileManagerFolderUpdateData `json:"data" validate:"required"`
}
type FileManagerNodeMoveItem struct {
UUID string `json:"uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DestinationFolderUUID *string `json:"destination_folder_uuid" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerNodeMoveBulkRequest struct {
Data []FileManagerNodeMoveItem `json:"data" validate:"required,min=1"`
}
type FileManagerNodePurgeItem struct {
UUID string `json:"uuid" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerNodePurgeBulkRequest struct {
Data []FileManagerNodePurgeItem `json:"data" validate:"required,min=1"`
}

View File

@@ -0,0 +1,13 @@
package request
type FileManagerRestoreTargetAttributes struct {
TargetFolderID *string `json:"target_folder_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerRestoreTargetData struct {
Attributes FileManagerRestoreTargetAttributes `json:"attributes"`
}
type FileManagerRestoreTargetRequest struct {
Data *FileManagerRestoreTargetData `json:"data,omitempty"`
}

View File

@@ -0,0 +1,70 @@
package request
import shared "wucher/internal/transport/http/dto/shared"
type FleetStatusMarkServicedAttributes struct {
ServicedAt string `json:"serviced_at,omitempty" example:"2026-05-29T07:43:06Z"`
}
type FleetStatusMarkServicedData struct {
Type string `json:"type" validate:"required,oneof=fleet_status_mark_serviced fleet_status" example:"fleet_status_mark_serviced"`
Attributes FleetStatusMarkServicedAttributes `json:"attributes"`
}
type FleetStatusMarkServicedRequest struct {
Data FleetStatusMarkServicedData `json:"data" validate:"required"`
}
type FleetStatusCreateAttributes struct {
HelicopterID string `json:"helicopter_id" validate:"required" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
MaintenanceSchedules []shared.MaintenanceScheduleAttributes `json:"maintenance_schedules" validate:"omitempty,dive"`
Files []shared.FleetStatusFileAttributes `json:"files"`
}
type FleetStatusCreateData struct {
Type string `json:"type" validate:"required,oneof=fleet_status_create fleet_status" example:"fleet_status_create"`
Attributes FleetStatusCreateAttributes `json:"attributes" validate:"required"`
}
type FleetStatusCreateRequest struct {
Data FleetStatusCreateData `json:"data" validate:"required"`
}
type FleetStatusUpdateAttributes struct {
HelicopterID *string `json:"helicopter_id,omitempty"`
MaintenanceSchedules []shared.MaintenanceScheduleAttributes `json:"maintenance_schedules" validate:"omitempty,dive"`
Files []shared.FleetStatusFileAttributes `json:"files"`
Summary *FleetStatusSummaryUpdate `json:"summary,omitempty"`
}
type FleetStatusSummaryUpdate struct {
Airframe *FleetStatusSummaryAirframeUpdate `json:"airframe,omitempty"`
Engine1 *FleetStatusSummaryEngineUpdate `json:"engine_1,omitempty"`
Engine2 *FleetStatusSummaryEngineUpdate `json:"engine_2,omitempty"`
Landings *float64 `json:"landings,omitempty" example:"120"`
FlightReports *float64 `json:"flight_reports,omitempty" example:"80"`
RotorBrakeCycles *float64 `json:"rotor_brake_cycles,omitempty" example:"8"`
HookReleases *float64 `json:"hook_releases,omitempty" example:"10"`
}
type FleetStatusSummaryAirframeUpdate struct {
Hours *float64 `json:"hours,omitempty" example:"6610.5"`
Cycles *float64 `json:"cycles,omitempty" example:"320"`
}
type FleetStatusSummaryEngineUpdate struct {
Hours *float64 `json:"hours,omitempty" example:"8001.7"`
GpcNgN1 *float64 `json:"gpc_ng_n1,omitempty" example:"98.7"`
PtcNfN2 *float64 `json:"ptc_nf_n2,omitempty" example:"97.2"`
Ccc *float64 `json:"ccc,omitempty" example:"44"`
}
type FleetStatusUpdateData struct {
Type string `json:"type" validate:"required,oneof=fleet_status_update fleet_status" example:"fleet_status_update"`
ID string `json:"id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes FleetStatusUpdateAttributes `json:"attributes" validate:"required"`
}
type FleetStatusUpdateRequest struct {
Data FleetStatusUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,135 @@
package request
type FlightDataCreateAttributes struct {
MissionID string `json:"mission_id" validate:"required"`
CoPilotID *string `json:"co_pilot_id,omitempty"`
FromICAOID *string `json:"from_icao_id,omitempty"`
FromHospitalID *string `json:"from_hospital_id,omitempty"`
ToICAOID *string `json:"to_icao_id,omitempty"`
ToHospitalID *string `json:"to_hospital_id,omitempty"`
FlightTakeOff *string `json:"flight_take_off,omitempty"`
FlightLanding *string `json:"flight_landing,omitempty"`
FlightDurationSecs *int64 `json:"flight_duration_seconds,omitempty"`
FlightREDSecs *int64 `json:"flight_red_seconds,omitempty"`
MaxN1 *float64 `json:"max_n1,omitempty"`
MaxN2 *float64 `json:"max_n2,omitempty"`
PaxCount *int `json:"pax_count,omitempty"`
LandingCount *int `json:"landing_count,omitempty"`
RotorBrakeCycle *int `json:"rotor_brake_cycle,omitempty"`
HookReleases *int `json:"hook_releases,omitempty"`
TicketNo *string `json:"ticket_no,omitempty"`
Engine *string `json:"engine,omitempty"`
DeliveryNoteNumber *string `json:"delivery_note_number,omitempty"`
CustomerName *string `json:"customer_name,omitempty"`
FlightPlanDistance *float64 `json:"flight_plan_distance,omitempty"`
FlightPlanTimeSecs *int64 `json:"flight_plan_time_seconds,omitempty"`
FlightPlanTrueCourse *float64 `json:"flight_plan_true_course,omitempty"`
FuelBeforeFlight *float64 `json:"fuel_before_flight,omitempty"`
FuelUpload *float64 `json:"fuel_upload,omitempty"`
FuelAfterFlight *float64 `json:"fuel_after_flight,omitempty"`
FuelPlanning *float64 `json:"fuel_planning,omitempty"`
FlightPositioning *bool `json:"flight_positioning,omitempty" example:"false"`
HESLO *FlightDataCreateSPOHESLOAttributes `json:"heslo,omitempty"`
Logging *FlightDataCreateSPOLoggingAttributes `json:"logging,omitempty"`
HEC *FlightDataCreateSPOHECAttributes `json:"hec,omitempty"`
OtherInformation *string `json:"other_information,omitempty"`
}
type FlightDataCreateSPOHESLOAttributes struct {
Flights []FlightDataCreateSPOHESLOFlight `json:"flights,omitempty"`
}
type FlightDataCreateSPOHESLOFlight struct {
ROT int `json:"rot" example:"0"`
// Facility category: heslo-rope-label.
HESLORopeLabelID string `json:"heslo_rope_label_id,omitempty" example:"string"`
HESLORopeLabelName string `json:"heslo_rope_label_name,omitempty" example:"string"`
// Facility category: heslo-rope-length.
Slings []FlightDataCreateSPOHESLOSling `json:"slings,omitempty"`
}
type FlightDataCreateSPOHESLOSling struct {
// Facility category: heslo-rope-length.
SlingID string `json:"sling_id,omitempty" example:"string"`
SlingName string `json:"sling_name,omitempty" example:"string"`
}
type FlightDataCreateSPOLoggingAttributes struct {
Slings []FlightDataCreateSPOLoggingSling `json:"slings,omitempty"`
}
type FlightDataCreateSPOLoggingSling struct {
// Facility category: heslo-hook.
SlingID string `json:"sling_id,omitempty" example:"string"`
SlingName string `json:"sling_name,omitempty" example:"string"`
}
type FlightDataCreateSPOHECAttributes struct {
Flights []FlightDataCreateSPOHECFlight `json:"flights,omitempty"`
Loads []FlightDataCreateSPOHECLoad `json:"loads,omitempty"`
Slings []FlightDataCreateSPOHECSling `json:"slings,omitempty"`
}
type FlightDataCreateSPOHECFlight struct {
// Facility category: hec-rope-label.
EquipmentID string `json:"equipment_id,omitempty" example:"string"`
EquipmentName string `json:"equipment_name,omitempty" example:"string"`
HECCycle int `json:"hec_cycle" example:"0"`
ROT int `json:"rot" example:"0"`
}
type FlightDataCreateSPOHECLoad struct {
// Weight band for the HEC load, e.g. "bis 199 kg", "200-600 kg", "600-800 kg", "800-1000 kg".
LoadCategory string `json:"load_category,omitempty" example:"200-600 kg"`
Quantity int `json:"quantity" example:"3"`
}
type FlightDataCreateSPOHECSling struct {
// Facility category: hec-rope-length.
SlingID string `json:"sling_id,omitempty" example:"string"`
SlingName string `json:"sling_name,omitempty" example:"string"`
}
type FlightDataCreateData struct {
Type string `json:"type" validate:"required,oneof=flight_data_create flight_data" example:"flight_data_create"`
Attributes FlightDataCreateAttributes `json:"attributes" validate:"required"`
}
type FlightDataCreateRequest struct {
Data FlightDataCreateData `json:"data" validate:"required"`
}
type FlightDataUpdateData struct {
Type string `json:"type" validate:"required,oneof=flight_data_update" example:"flight_data_update"`
Attributes FlightDataCreateAttributes `json:"attributes" validate:"required"`
}
type FlightDataUpdateRequest struct {
Data FlightDataUpdateData `json:"data" validate:"required"`
}
// FlightDataCreateSPOExampleRequest is used only for Swagger examples.
// Runtime parsing still uses FlightDataCreateRequest.
type FlightDataCreateSPOExampleRequest struct {
Data FlightDataCreateSPOExampleData `json:"data"`
}
type FlightDataCreateSPOExampleData struct {
Type string `json:"type" example:"flight_data_create"`
Attributes FlightDataCreateSPOExampleAttributes `json:"attributes"`
}
type FlightDataCreateSPOExampleAttributes struct {
MissionID string `json:"mission_id" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
CoPilotID string `json:"co_pilot_id,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
FlightTakeOff string `json:"flight_take_off,omitempty" example:"2026-04-20T08:00:00Z"`
FlightLanding string `json:"flight_landing,omitempty" example:"2026-04-20T09:15:00Z"`
FromICAOID string `json:"from_icao_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
ToICAOID string `json:"to_icao_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
HookReleases int `json:"hook_releases,omitempty" example:"0"`
TicketNo string `json:"ticket_no,omitempty" example:"SPO-001"`
Engine string `json:"engine,omitempty" example:"AIRBUS H145"`
DeliveryNoteNumber string `json:"delivery_note_number,omitempty" example:"DN-2026-0001"`
CustomerName string `json:"customer_name,omitempty" example:"Patient Transfer"`
OtherInformation string `json:"other_information,omitempty" example:"SPO mission sample"`
}

View File

@@ -0,0 +1,32 @@
package request
type FlightInspectionFileChecklistCreateAttributes struct {
IsDone *bool `json:"is_done,omitempty" example:"false"`
HelicopterFileID string `json:"helicopter_file_id" validate:"required" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FlightInspectionID string `json:"flight_inspection_id" validate:"required" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
}
type FlightInspectionFileChecklistCreateData struct {
Type string `json:"type" validate:"required,oneof=flight_inspection_file_checklist_create flight_inspection_file_checklist" example:"flight_inspection_file_checklist_create"`
Attributes FlightInspectionFileChecklistCreateAttributes `json:"attributes" validate:"required"`
}
type FlightInspectionFileChecklistCreateRequest struct {
Data FlightInspectionFileChecklistCreateData `json:"data" validate:"required"`
}
type FlightInspectionFileChecklistUpdateAttributes struct {
IsDone *bool `json:"is_done,omitempty" example:"true"`
HelicopterFileID *string `json:"helicopter_file_id,omitempty" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FlightInspectionID *string `json:"flight_inspection_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
}
type FlightInspectionFileChecklistUpdateData struct {
Type string `json:"type" validate:"required,oneof=flight_inspection_file_checklist_update flight_inspection_file_checklist" example:"flight_inspection_file_checklist_update"`
ID string `json:"id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes FlightInspectionFileChecklistUpdateAttributes `json:"attributes" validate:"required"`
}
type FlightInspectionFileChecklistUpdateRequest struct {
Data FlightInspectionFileChecklistUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,32 @@
package request
type FlightCreateAttributes struct {
Date string `json:"date" validate:"required" example:"2026-04-14"`
DutyRosterID *string `json:"duty_roster_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
ReserveAcID *string `json:"reserve_ac_id,omitempty" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
}
type FlightCreateData struct {
Type string `json:"type" validate:"required,oneof=flight_create flight" example:"flight_create"`
Attributes FlightCreateAttributes `json:"attributes" validate:"required"`
}
type FlightCreateRequest struct {
Data FlightCreateData `json:"data" validate:"required"`
}
type FlightUpdateAttributes struct {
Date *string `json:"date,omitempty" example:"2026-04-14"`
DutyRosterID *string `json:"duty_roster_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
ReserveAcID *string `json:"reserve_ac_id,omitempty" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
}
type FlightUpdateData struct {
Type string `json:"type" validate:"required,oneof=flight_update flight" example:"flight_update"`
ID string `json:"id" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Attributes FlightUpdateAttributes `json:"attributes" validate:"required"`
}
type FlightUpdateRequest struct {
Data FlightUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,21 @@
package request
type FMReportUpdateAttributes struct {
// Engine1GpcN1 - GPC/N1 value for engine 1. Optional.
Engine1GpcN1 *string `json:"engine1_gpc_n1,omitempty" example:"GPC/N1 63.2"`
// Engine1PtcN2 - PTC/N2 value for engine 1. Optional.
Engine1PtcN2 *string `json:"engine1_ptc_n2,omitempty" example:"PTC/N2 61.8"`
// Engine2GpcN1 - GPC/N1 value for engine 2. Optional.
Engine2GpcN1 *string `json:"engine2_gpc_n1,omitempty" example:"GPC/N1 63.2"`
// Engine2PtcN2 - PTC/N2 value for engine 2. Optional.
Engine2PtcN2 *string `json:"engine2_ptc_n2,omitempty" example:"PTC/N2 61.8"`
}
type FMReportUpdateData struct {
Type string `json:"type" validate:"required,oneof=fm_report_update fm_report" example:"fm_report_update"`
Attributes FMReportUpdateAttributes `json:"attributes" validate:"required"`
}
type FMReportUpdateRequest struct {
Data FMReportUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,87 @@
package request
type HelicopterFileCreateAttributes struct {
Section string `json:"section" validate:"required,oneof=bfi fpwb afi" enums:"bfi,fpwb,afi" example:"bfi"`
Position *int `json:"position,omitempty" example:"1"`
IsMandatory *bool `json:"is_mandatory,omitempty" example:"true"`
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"019d6d0d-2de2-7db1-80a2-2862d43af620"`
FileName string `json:"file_name" validate:"required" example:"Before Flight Checklist.pdf"`
FolderUUID *string `json:"folder_uuid,omitempty" example:"019d6d0d-8c24-72e3-9f58-c9ea0fbf0bb1"`
}
type HelicopterFileUploadAttributes struct {
Name string `json:"name" validate:"required" example:"Before Flight Checklist.pdf"`
ContentType string `json:"content_type" validate:"required" example:"application/pdf"`
SizeBytes int64 `json:"size_bytes" validate:"required,gte=0" example:"1024"`
}
type HelicopterFileUploadData struct {
Type string `json:"type" validate:"required,oneof=helicopter_file_upload" example:"helicopter_file_upload"`
Attributes HelicopterFileUploadAttributes `json:"attributes" validate:"required"`
}
type HelicopterFileUploadRequest struct {
Data HelicopterFileUploadData `json:"data" validate:"required"`
}
type HelicopterFileUploadBulkRequest struct {
Data []HelicopterFileUploadData `json:"data" validate:"required,min=1"`
}
type HelicopterFileCancelUploadAttributes struct {
UploadIntentUUID string `json:"upload_intent_uuid" validate:"required" example:"019d6d0d-2de2-7db1-80a2-2862d43af620"`
}
type HelicopterFileCancelUploadData struct {
Type string `json:"type" validate:"required,oneof=helicopter_file_cancel_upload" example:"helicopter_file_cancel_upload"`
Attributes HelicopterFileCancelUploadAttributes `json:"attributes" validate:"required"`
}
type HelicopterFileCancelUploadBulkRequest struct {
Data []HelicopterFileCancelUploadData `json:"data" validate:"required,min=1"`
}
type HelicopterFileCreateData struct {
Type string `json:"type" validate:"required,oneof=helicopter_file_create helicopter_file" example:"helicopter_file_create"`
Attributes HelicopterFileCreateAttributes `json:"attributes" validate:"required"`
}
type HelicopterFileCreateRequest struct {
Data HelicopterFileCreateData `json:"data" validate:"required"`
}
type HelicopterFileCreateBulkRequest struct {
Data []HelicopterFileCreateData `json:"data" validate:"required,min=1"`
}
type HelicopterFileUpdateAttributes struct {
Section *string `json:"section,omitempty" validate:"omitempty,oneof=bfi fpwb afi" enums:"bfi,fpwb,afi" example:"fpwb"`
Position *int `json:"position,omitempty" example:"1"`
IsMandatory *bool `json:"is_mandatory,omitempty" example:"true"`
HelicopterID *string `json:"helicopter_id,omitempty" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FileName *string `json:"file_name,omitempty" example:"Before Flight Checklist v2.pdf"`
}
type HelicopterFileUpdateData struct {
Type string `json:"type" validate:"required,oneof=helicopter_file_update helicopter_file" example:"helicopter_file_update"`
ID string `json:"id" validate:"required" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes HelicopterFileUpdateAttributes `json:"attributes" validate:"required"`
}
type HelicopterFileUpdateRequest struct {
Data HelicopterFileUpdateData `json:"data" validate:"required"`
}
type HelicopterFileUpdateBulkRequest struct {
Data []HelicopterFileUpdateData `json:"data" validate:"required,min=1"`
}
type HelicopterFileDeleteData struct {
Type string `json:"type" validate:"required,oneof=helicopter_file_delete helicopter_file" example:"helicopter_file_delete"`
ID string `json:"id" validate:"required" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
}
type HelicopterFileDeleteRequest struct {
Data HelicopterFileDeleteData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,70 @@
package request
import "wucher/internal/transport/http/dto"
type HelicopterCreateAttributes struct {
Designation string `json:"designation" validate:"required" example:"OE-XHZ"`
Identifier string `json:"identifier" validate:"required" example:"ABC"`
Type string `json:"type" validate:"required" example:"EC-135"`
SortKey *int `json:"sortkey" example:"10"`
Engine1 *string `json:"engine_1,omitempty" example:"TM"`
Engine2 *string `json:"engine_2,omitempty" example:"TM"`
Consumption *float64 `json:"consumption_lt_min,omitempty" example:"4.1"`
NR1 *bool `json:"nr1,omitempty" example:"true"`
NR2 *bool `json:"nr2,omitempty" example:"true"`
LH *bool `json:"lh,omitempty" example:"true"`
RH *bool `json:"rh,omitempty" example:"true"`
MGB *bool `json:"mgb,omitempty" example:"true"`
IGB *bool `json:"igb,omitempty" example:"false"`
TGB *bool `json:"tgb,omitempty" example:"true"`
UTCOffset *int `json:"utc_offset,omitempty" example:"-1"`
FileUUID *string `json:"file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e90"`
Dry *bool `json:"dry,omitempty" example:"false"`
AOG *bool `json:"aog,omitempty" example:"false"`
AOGReason *string `json:"aog_reason,omitempty" example:"Main rotor gearbox fault"`
Note *string `json:"note,omitempty" example:"Primary HEMS helicopter"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type HelicopterCreateData struct {
Type string `json:"type" validate:"required,oneof=helicopter_create helicopter" example:"helicopter_create"`
Attributes HelicopterCreateAttributes `json:"attributes" validate:"required"`
}
type HelicopterCreateRequest struct {
Data HelicopterCreateData `json:"data" validate:"required"`
}
type HelicopterUpdateAttributes struct {
Designation *string `json:"designation,omitempty" example:"OE-XHZ"`
Identifier *string `json:"identifier,omitempty" example:"ABC"`
Type *string `json:"type,omitempty" example:"EC-135"`
SortKey dto.NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"10"`
Engine1 *string `json:"engine_1,omitempty" example:"TM"`
Engine2 *string `json:"engine_2,omitempty" example:"TM"`
Consumption *float64 `json:"consumption_lt_min,omitempty" example:"4.1"`
NR1 *bool `json:"nr1,omitempty" example:"true"`
NR2 *bool `json:"nr2,omitempty" example:"true"`
LH *bool `json:"lh,omitempty" example:"true"`
RH *bool `json:"rh,omitempty" example:"true"`
MGB *bool `json:"mgb,omitempty" example:"true"`
IGB *bool `json:"igb,omitempty" example:"false"`
TGB *bool `json:"tgb,omitempty" example:"true"`
UTCOffset *int `json:"utc_offset,omitempty" example:"-1"`
FileUUID *string `json:"file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e90"`
Dry *bool `json:"dry,omitempty" example:"false"`
AOG *bool `json:"aog,omitempty" example:"false"`
AOGReason *string `json:"aog_reason,omitempty" example:"Main rotor gearbox fault"`
Note *string `json:"note,omitempty" example:"Primary HEMS helicopter"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type HelicopterUpdateData struct {
Type string `json:"type" validate:"required,oneof=helicopter_update helicopter" example:"helicopter_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HelicopterUpdateAttributes `json:"attributes" validate:"required"`
}
type HelicopterUpdateRequest struct {
Data HelicopterUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,19 @@
package request
type MasterSettingsMicrosoftEntraUpsertAttributes struct {
TenantID string `json:"MS_ENTRA_TENANT_ID" validate:"required" example:"your-tenant-id"`
ClientID string `json:"MS_ENTRA_CLIENT_ID" validate:"required" example:"your-client-id"`
ClientSecret string `json:"MS_ENTRA_CLIENT_SECRET" example:"********"`
RedirectURL string `json:"MS_ENTRA_REDIRECT_URL" validate:"required" example:"https://example.com/api/v1/auth/microsoft/callback"`
Authority string `json:"MS_ENTRA_AUTHORITY" validate:"required" example:"https://login.microsoftonline.com/"`
Scopes string `json:"MS_ENTRA_SCOPES" validate:"required" example:"openid,profile,email"`
}
type MasterSettingsMicrosoftEntraUpsertData struct {
Type string `json:"type" validate:"required,oneof=master_settings_microsoft_entra_upsert master_settings" example:"master_settings_microsoft_entra_upsert"`
Attributes MasterSettingsMicrosoftEntraUpsertAttributes `json:"attributes" validate:"required"`
}
type MasterSettingsMicrosoftEntraUpsertRequest struct {
Data MasterSettingsMicrosoftEntraUpsertData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,36 @@
package request
type MasterSettingsCreateAttributes struct {
CompanyName string `json:"company_name" validate:"required" example:"Wucher"`
Title *string `json:"title,omitempty" example:"Welcome to Wucher"`
Subtitle *string `json:"subtitle,omitempty" example:"Flight operations platform"`
LogoFileUUID *string `json:"logo_file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CoverFileUUID *string `json:"cover_file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type MasterSettingsCreateData struct {
Type string `json:"type" validate:"required,oneof=master_settings_create master_settings" example:"master_settings_create"`
Attributes MasterSettingsCreateAttributes `json:"attributes" validate:"required"`
}
type MasterSettingsCreateRequest struct {
Data MasterSettingsCreateData `json:"data" validate:"required"`
}
type MasterSettingsUpdateAttributes struct {
CompanyName *string `json:"company_name,omitempty" example:"Wucher"`
Title *string `json:"title,omitempty" example:"Welcome to Wucher"`
Subtitle *string `json:"subtitle,omitempty" example:"Flight operations platform"`
LogoFileUUID *string `json:"logo_file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CoverFileUUID *string `json:"cover_file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type MasterSettingsUpdateData struct {
Type string `json:"type" validate:"required,oneof=master_settings_update master_settings" example:"master_settings_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes MasterSettingsUpdateAttributes `json:"attributes" validate:"required"`
}
type MasterSettingsUpdateRequest struct {
Data MasterSettingsUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,52 @@
package request
// MCFSetAttributes toggles a helicopter's MCF (Yes/No) and is the DRAFT work endpoint.
// active=true (or omitted) ACTIVATES it — creates the draft MCF if none is open, or
// UPDATES the open draft with the provided check-flight fields (all optional; partial
// edits allowed). Finalize it later via POST /mcf/sign (sign:true).
// active=false DEACTIVATES it — cancels the open draft ("tidak jadi") and releases the
// MCF status; the cancelled record stays in the list. A signed MCF is a real event and
// is not cancelled by this.
type MCFSetAttributes struct {
Active *bool `json:"active,omitempty" example:"true"`
// FlightID — OPTIONAL flight this check flight is performed on (on activate).
FlightID string `json:"flight_id,omitempty" validate:"omitempty,uuid" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
AFHours string `json:"af_hours,omitempty" example:"1245.3"`
LandingCount *int `json:"landing_count,omitempty" example:"3"`
UTCTime string `json:"utc_time,omitempty" example:"2026-06-17T08:30:00Z"`
Date string `json:"date,omitempty" example:"2026-06-17"`
Result string `json:"result,omitempty" validate:"omitempty,oneof=passed failed" enums:"passed,failed" example:"passed"`
Notes *string `json:"notes,omitempty" example:"MCF required after engine mount work"`
}
type MCFSetData struct {
Type string `json:"type" validate:"required,oneof=mcf_set mcf" example:"mcf_set"`
Attributes MCFSetAttributes `json:"attributes" validate:"required"`
}
type MCFSetRequest struct {
Data MCFSetData `json:"data" validate:"required"`
}
// MCFCompleteAttributes signs (completes) or unsigns an MCF. sign toggles the state:
// - sign=true (or omitted) SIGNS it — af_hours, landing_count, utc_time, date and
// result are required. "passed" clears the aircraft; "failed" keeps it grounded.
// - sign=false UNSIGNS it — reverts the MCF to a draft (the fields below are ignored).
type MCFCompleteAttributes struct {
Sign *bool `json:"sign,omitempty" example:"true"`
AFHours string `json:"af_hours,omitempty" example:"1245.3"`
LandingCount *int `json:"landing_count,omitempty" example:"3"`
UTCTime string `json:"utc_time,omitempty" example:"2026-06-17T08:30:00Z"`
Date string `json:"date,omitempty" example:"2026-06-17"`
Result string `json:"result,omitempty" validate:"omitempty,oneof=passed failed" enums:"passed,failed" example:"passed"`
Notes *string `json:"notes,omitempty" example:"Vibration within limits after check flight"`
}
type MCFCompleteData struct {
Type string `json:"type" validate:"required,oneof=mcf_complete mcf_sign mcf" example:"mcf_sign"`
Attributes MCFCompleteAttributes `json:"attributes" validate:"required"`
}
type MCFCompleteRequest struct {
Data MCFCompleteData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,53 @@
package request
type MissionFileInput struct {
UploadIntentUUID string `json:"upload_intent_uuid" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" example:"mission-brief.pdf"`
}
type MissionCreateAttributes struct {
FlightID string `json:"flight_id" validate:"required" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Type string `json:"type" validate:"required" example:"HEMS"`
SubtypeID string `json:"subtype_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a99"`
Note string `json:"note,omitempty" example:"Mission note"`
Files []MissionFileInput `json:"files,omitempty"`
}
type MissionCreateData struct {
Type string `json:"type" validate:"required,oneof=mission_create mission" example:"mission_create"`
Attributes MissionCreateAttributes `json:"attributes" validate:"required"`
}
type MissionCreateRequest struct {
Data MissionCreateData `json:"data" validate:"required"`
}
type MissionUpdateAttributes struct {
Type string `json:"type" validate:"required" example:"SPO"`
SubtypeID string `json:"subtype_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a99"`
Note string `json:"note,omitempty" example:"Mission note"`
Files []MissionFileInput `json:"files,omitempty"`
}
type MissionUpdateData struct {
Type string `json:"type" validate:"required,oneof=mission_update mission" example:"mission_update"`
Attributes MissionUpdateAttributes `json:"attributes" validate:"required"`
}
type MissionUpdateRequest struct {
Data MissionUpdateData `json:"data" validate:"required"`
}
type MissionTypeCreateAttributes struct {
CodeType string `json:"code_type" validate:"required" example:"HEMS"`
TypeName string `json:"name" validate:"required" example:"HEMS"`
}
type MissionTypeCreateData struct {
Type string `json:"type" validate:"required,oneof=mission_type_create mission_type" example:"mission_type_create"`
Attributes MissionTypeCreateAttributes `json:"attributes" validate:"required"`
}
type MissionTypeCreateRequest struct {
Data MissionTypeCreateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,18 @@
package request
// OtherPersonAttributesInput carries the editable fields of a reusable non-staff person.
type OtherPersonAttributesInput struct {
Name string `json:"name" example:"Guest Person"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+43-1234"`
Email string `json:"email,omitempty" example:"guest@example.com"`
}
type OtherPersonRequestData struct {
Type string `json:"type,omitempty" example:"other_person"`
Attributes OtherPersonAttributesInput `json:"attributes"`
}
// OtherPersonRequest is the JSON:API body for create/update of the other-people master.
type OtherPersonRequest struct {
Data OtherPersonRequestData `json:"data"`
}

View File

@@ -0,0 +1,249 @@
package request
import "encoding/json"
type ReserveAcCreateAttributes struct {
AircraftID string `json:"helicopter_id" validate:"required" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FlightID string `json:"flight_id" validate:"required" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Inspection ReserveAcInspectionCreateAttributes `json:"inspection" validate:"required"`
}
type ReserveAcInspectionCreateAttributes struct {
InspectionDate string `json:"inspection_date" validate:"required" example:"2026-04-15"`
Before ReserveAcBeforeInspectionAttributes `json:"before" validate:"required"`
Prepare ReserveAcPrepareInspectionAttributes `json:"prepare" validate:"required"`
FleetStatusFile ReserveAcFleetStatusFileAttributes `json:"fleet_status_file,omitempty"`
}
type ReserveAcBeforeInspectionAttributes struct {
OilEngineNR1Checked *bool `json:"oil_engine_nr1_checked,omitempty"`
OilEngineNR2Checked *bool `json:"oil_engine_nr2_checked,omitempty"`
HydraulicLHChecked *bool `json:"hydraulic_lh_checked,omitempty"`
HydraulicRHChecked *bool `json:"hydraulic_rh_checked,omitempty"`
OilTransmissionMGBChecked *bool `json:"oil_transmission_mgb_checked,omitempty"`
OilTransmissionIGBChecked *bool `json:"oil_transmission_igb_checked,omitempty"`
OilTransmissionTGBChecked *bool `json:"oil_transmission_tgb_checked,omitempty"`
FuelAmount *float64 `json:"fuel_amount,omitempty"`
FuelUnit *string `json:"fuel_unit,omitempty"`
FuelAddedAmount *float64 `json:"fuel_added_amount,omitempty"`
Note *string `json:"note,omitempty"`
FileChecklist []ReserveAcFileChecklistItem `json:"file_checklist,omitempty"`
}
type ReserveAcFileChecklistItem struct {
HelicopterFileID string `json:"helicopter_file_id" validate:"required" example:"019d7000-aaaa-7bbb-8ccc-111111111111"`
IsDone bool `json:"is_done" example:"true"`
}
type ReserveAcPrepareInspectionAttributes struct {
NotamBriefing *bool `json:"notam_briefing,omitempty"`
WeatherBriefing *bool `json:"weather_briefing,omitempty"`
OperationalFlightPlan *bool `json:"operational_flight_plan,omitempty"`
FileChecklist []ReserveAcFileChecklistItem `json:"file_checklist,omitempty"`
}
type ReserveAcFleetStatusFileAttributes struct {
FileChecklist []ReserveAcFileChecklistItem `json:"file_checklist,omitempty"`
}
type ReserveAcAfterFlightInspectionAttributes struct {
OilEngineNR1Checked *string `json:"oil_engine_nr1_checked,omitempty"`
OilEngineNR2Checked *string `json:"oil_engine_nr2_checked,omitempty"`
HydraulicLHChecked *string `json:"hydraulic_lh_checked,omitempty"`
HydraulicRHChecked *string `json:"hydraulic_rh_checked,omitempty"`
OilTransmissionMGBChecked *string `json:"oil_transmission_mgb_checked,omitempty"`
OilTransmissionIGBChecked *string `json:"oil_transmission_igb_checked,omitempty"`
OilTransmissionTGBChecked *string `json:"oil_transmission_tgb_checked,omitempty"`
Note *string `json:"note,omitempty"`
FileChecklist []ReserveAcFileChecklistItem `json:"file_checklist,omitempty"`
}
type ReserveAcAfterFlightInspectionUpsertData struct {
Type string `json:"type" validate:"required,oneof=after_flight_inspection_upsert after_flight_inspection"`
Attributes ReserveAcAfterFlightInspectionAttributes `json:"attributes" validate:"required"`
}
type ReserveAcAfterFlightInspectionUpsertRequest struct {
Data ReserveAcAfterFlightInspectionUpsertData `json:"data" validate:"required"`
}
type LastFlightsInspectionUpsertAttributes struct {
FlightInspectionID string `json:"flight_inspection_id" validate:"required"`
ReserveAcAfterFlightInspectionAttributes
}
type LastFlightsInspectionCreateData struct {
Type string `json:"type" validate:"required,oneof=last_flights_inspection_create last_flights_inspection"`
Attributes LastFlightsInspectionUpsertAttributes `json:"attributes" validate:"required"`
}
type LastFlightsInspectionCreateRequest struct {
Data LastFlightsInspectionCreateData `json:"data" validate:"required"`
}
type LastFlightsInspectionUpdateData struct {
Type string `json:"type" validate:"required,oneof=last_flights_inspection_update last_flights_inspection"`
Attributes LastFlightsInspectionUpsertAttributes `json:"attributes" validate:"required"`
}
type LastFlightsInspectionUpdateRequest struct {
Data LastFlightsInspectionUpdateData `json:"data" validate:"required"`
}
type LastFlightsInspectionDeleteAttributes struct {
FlightInspectionID string `json:"flight_inspection_id" validate:"required"`
}
type LastFlightsInspectionDeleteData struct {
Type string `json:"type" validate:"required,oneof=last_flights_inspection_delete last_flights_inspection"`
Attributes LastFlightsInspectionDeleteAttributes `json:"attributes" validate:"required"`
}
type LastFlightsInspectionDeleteRequest struct {
Data LastFlightsInspectionDeleteData `json:"data" validate:"required"`
}
type ReserveAcCreateData struct {
Type string `json:"type" validate:"required,oneof=reserve_ac_create reserve_ac" example:"reserve_ac_create"`
Attributes ReserveAcCreateAttributes `json:"attributes" validate:"required"`
}
type ReserveAcCreateRequest struct {
Data ReserveAcCreateData `json:"data" validate:"required"`
}
type ReserveAcUpdateAttributes struct {
AircraftID *string `json:"helicopter_id,omitempty" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FlightID *string `json:"flight_id,omitempty" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Inspection *ReserveAcInspectionUpdateAttributes `json:"inspection,omitempty"`
}
type ReserveAcInspectionUpdateAttributes struct {
InspectionDate *string `json:"inspection_date,omitempty" example:"2026-04-15"`
Before *ReserveAcBeforeInspectionAttributes `json:"before,omitempty"`
Prepare *ReserveAcPrepareInspectionAttributes `json:"prepare,omitempty"`
}
type ReserveAcUpdateData struct {
Type string `json:"type" validate:"required,oneof=reserve_ac_update reserve_ac" example:"reserve_ac_update"`
ID string `json:"id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes ReserveAcUpdateAttributes `json:"attributes" validate:"required"`
}
type ReserveAcUpdateRequest struct {
Data ReserveAcUpdateData `json:"data" validate:"required"`
}
type ReserveAcDeleteData struct {
ID string `json:"id" validate:"required" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
}
type ReserveAcDeleteRequest struct {
Data ReserveAcDeleteData `json:"data" validate:"required"`
}
type TakeoverCreateAttributes struct {
BaseID *string `json:"base_id,omitempty"`
DutyDate string `json:"duty_date" validate:"required"`
Notes *string `json:"notes,omitempty"`
Detail *TakeoverRosterDetailsInput `json:"roster_detail,omitempty"`
AircraftID *string `json:"helicopter_id,omitempty"`
Inspection *ReserveAcInspectionCreateAttributes `json:"inspection,omitempty"`
Files []TakeoverCreateFileInput `json:"files,omitempty" validate:"omitempty,dive"`
EditedTemplateFileIDs []string `json:"edited_template_file_ids,omitempty" validate:"omitempty,dive,uuid"`
}
type TakeoverCreateData struct {
Type string `json:"type" validate:"required,oneof=takeover_create takeover"`
Attributes TakeoverCreateAttributes `json:"attributes" validate:"required"`
}
type TakeoverCreateRequest struct {
Data TakeoverCreateData `json:"data" validate:"required"`
}
type TakeoverCreateFileInput struct {
FileID string `json:"file_id,omitempty" validate:"omitempty,uuid" example:"019e8713-aa19-7fb7-a510-a0332dc69697"`
UploadIntentUUID string `json:"upload_intent_uuid,omitempty" validate:"omitempty,uuid" example:"019e8713-aa19-7fb7-a510-a0332dc69697"`
Name string `json:"name,omitempty" example:"takeover-report.pdf"`
}
type TakeoverUpdateAttributes struct {
BaseID *string `json:"base_id,omitempty"`
Bases *TakeoverBaseRef `json:"bases,omitempty"`
DutyDate *string `json:"duty_date,omitempty"`
Notes *string `json:"notes,omitempty"`
Detail *TakeoverRosterDetailsInput `json:"roster_detail,omitempty"`
RosterDetail *TakeoverRosterDetailsInput `json:"roster_details,omitempty"`
AircraftID *string `json:"helicopter_id,omitempty"`
Inspection *ReserveAcInspectionCreateAttributes `json:"inspection,omitempty"`
Files []TakeoverCreateFileInput `json:"files,omitempty" validate:"omitempty,dive"`
FilesAdd []TakeoverCreateFileInput `json:"files_add,omitempty" validate:"omitempty,dive"`
FilesRemove []string `json:"files_remove,omitempty" validate:"omitempty,dive,uuid"`
FilesReplace []TakeoverCreateFileInput `json:"files_replace,omitempty" validate:"omitempty,dive"`
}
type TakeoverUpdateData struct {
Type string `json:"type" validate:"required,oneof=takeover_update takeover"`
ID string `json:"id"`
Attributes TakeoverUpdateAttributes `json:"attributes" validate:"required"`
}
type TakeoverUpdateRequest struct {
Data TakeoverUpdateData `json:"data" validate:"required"`
}
func (a *TakeoverUpdateAttributes) UnmarshalJSON(data []byte) error {
type alias TakeoverUpdateAttributes
var raw struct {
alias
Bases *TakeoverBaseRef `json:"bases,omitempty"`
RosterDetail *TakeoverRosterDetailsInput `json:"roster_details,omitempty"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
*a = TakeoverUpdateAttributes(raw.alias)
if a.BaseID == nil && raw.Bases != nil && raw.Bases.ID != "" {
id := raw.Bases.ID
a.BaseID = &id
}
if a.Detail == nil && raw.RosterDetail != nil {
a.Detail = raw.RosterDetail
}
return nil
}
type TakeoverBaseRef struct {
ID string `json:"id,omitempty"`
}
type TakeoverRosterCrewInput struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type TakeoverRosterPilotInput struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FlightInstructor bool `json:"flight_instructor,omitempty"`
LineChecker bool `json:"line_checker,omitempty"`
Supervisor bool `json:"supervisor,omitempty"`
Examiner bool `json:"examiner,omitempty"`
CoPilot bool `json:"co_pilot,omitempty"`
CrewType string `json:"crew_type,omitempty" example:"main" enums:"main,additional"`
}
type TakeoverRosterOtherPersonInput struct {
// OtherPersonID — OPTIONAL master person id to reuse (from GET /contact/other).
OtherPersonID string `json:"other_person_id,omitempty" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Name string `json:"name,omitempty" example:"Guest Person"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+43-1234"`
Email string `json:"email,omitempty" example:"guest@example.com"`
}
type TakeoverRosterDetailsInput struct {
Pilot []TakeoverRosterPilotInput `json:"pilot"`
Doctor []TakeoverRosterCrewInput `json:"doctor"`
Rescuer []TakeoverRosterCrewInput `json:"rescuer"`
Helper []TakeoverRosterCrewInput `json:"helper"`
OtherPerson []TakeoverRosterOtherPersonInput `json:"other_person"`
}

View File

@@ -0,0 +1,26 @@
package response
type ActionSignoffAttributes struct {
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FlightID string `json:"flight_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
// Signed — true once checked; gates the maintenance release form.
Signed bool `json:"signed" example:"false"`
// SignedAt / SignedBy — when/who signed; empty until signed.
SignedAt *string `json:"signed_at,omitempty" example:"2026-06-16T03:04:05Z"`
SignedBy string `json:"signed_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
// SignedByName / SignedByShort — resolved display name + 3-letter short of the signer.
SignedByName *string `json:"signed_by_name,omitempty" example:"John Doe"`
SignedByShort *string `json:"signed_by_short,omitempty" example:"JDO"`
CreatedAt string `json:"created_at,omitempty" example:"2026-06-16T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-06-16T03:04:05Z"`
}
type ActionSignoffResource struct {
Type string `json:"type" example:"action_signoff"`
ID string `json:"id,omitempty" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Attributes ActionSignoffAttributes `json:"attributes"`
}
type ActionSignoffResponse struct {
Data ActionSignoffResource `json:"data"`
}

View File

@@ -0,0 +1,124 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
type AirRescuerChecklistItemResource struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Oxygen supply check"`
Position int `json:"position,omitempty" example:"1"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type AirRescuerChecklistAttributes struct {
Bases shareddto.AirRescuerChecklistBaseInfo `json:"bases"`
ScopeCode string `json:"scope_code,omitempty" example:"TU"`
ScopeCodeDe string `json:"scope_code_de,omitempty" example:"DI"`
Title string `json:"title,omitempty" example:"Monday Checklist"`
Position int `json:"position,omitempty" example:"1"`
Items []AirRescuerChecklistItemResource `json:"items"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type AirRescuerChecklistResource struct {
Type string `json:"type" example:"air_rescuer_checklist"`
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes AirRescuerChecklistAttributes `json:"attributes"`
}
type AirRescuerChecklistResponse struct {
Data AirRescuerChecklistResource `json:"data"`
}
type AirRescuerChecklistBulkResponse struct {
Data []AirRescuerChecklistResource `json:"data"`
}
type AirRescuerChecklistItemAttributes struct {
ChecklistID string `json:"checklist_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Check radio"`
Position int `json:"position,omitempty" example:"1"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type AirRescuerChecklistItemData struct {
Type string `json:"type" example:"air_rescuer_checklist_item"`
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes AirRescuerChecklistItemAttributes `json:"attributes"`
}
type AirRescuerChecklistItemResponse struct {
Data AirRescuerChecklistItemData `json:"data"`
}
type AirRescuerChecklistItemBulkResponse struct {
Data []AirRescuerChecklistItemData `json:"data"`
}
type AirRescuerChecklistCATChecklistSummary struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Title string `json:"title,omitempty" example:"Cockpit Flugretter"`
Position int `json:"position,omitempty" example:"1"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type AirRescuerChecklistCATGroup struct {
ScopeCode string `json:"scope_code,omitempty" example:"DA"`
ScopeCodeDe string `json:"scope_code_de,omitempty" example:"TA"`
ScopeCodeName string `json:"scope_code_name,omitempty" example:"Daily"`
ScopeCodeNameDe string `json:"scope_code_name_de,omitempty" example:"Taeglich"`
Checklists []AirRescuerChecklistCATChecklistSummary `json:"checklists"`
}
type AirRescuerChecklistTabChecklistDetail struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Title string `json:"title,omitempty" example:"Monday Checklist"`
Position int `json:"position,omitempty" example:"1"`
ChecklistItems []AirRescuerChecklistItemResource `json:"checklist_items"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type AirRescuerChecklistTabResource struct {
ScopeCode string `json:"scope_code,omitempty" example:"DA"`
ScopeCodeDe string `json:"scope_code_de,omitempty" example:"TA"`
ScopeCodeName string `json:"scope_code_name,omitempty" example:"Daily"`
ScopeCodeNameDe string `json:"scope_code_name_de,omitempty" example:"Taeglich"`
Groups []AirRescuerChecklistCATGroup `json:"groups,omitempty"`
Checklists *[]AirRescuerChecklistTabChecklistDetail `json:"checklists,omitempty"`
}
type AirRescuerChecklistBaseGroupAttributes struct {
Bases shareddto.AirRescuerChecklistBaseInfo `json:"bases"`
Tabs []AirRescuerChecklistTabResource `json:"tabs"`
}
type AirRescuerChecklistBaseGroupResource struct {
Type string `json:"type" example:"air_rescuer_checklist_group"`
ID string `json:"id,omitempty" example:"base-018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes AirRescuerChecklistBaseGroupAttributes `json:"attributes"`
}
type AirRescuerChecklistGroupedListResponse struct {
Data []AirRescuerChecklistBaseGroupResource `json:"data"`
Meta struct {
TotalBases int `json:"total_bases" example:"8"`
} `json:"meta"`
}
type AirRescuerChecklistGroupedDataTableResponse struct {
Data []AirRescuerChecklistBaseGroupResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int `json:"records_total" example:"8"`
RecordsFiltered int `json:"records_filtered" example:"8"`
} `json:"meta"`
}

View File

@@ -0,0 +1,98 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
// ComplaintMEL groups the MEL deferral attributes. All fields are always present
// (null when not set) so the response shape is predictable for clients.
type ComplaintMEL struct {
// Severity — MEL class: 0=NON-MEL, 1=A, 2=B, 3=C, 4=D. Always set (classification is required at creation).
Severity int8 `json:"severity" example:"2" enums:"0,1,2,3,4"`
// SeverityLabel — human label of the severity (NON-MEL/A/B/C/D).
SeverityLabel *string `json:"severity_label" example:"B"`
// ClassifiedAt / ClassifiedByName / ClassifiedByShort — when/who classified the MEL.
ClassifiedAt *string `json:"classified_at" example:"2026-06-15T03:04:05Z"`
ClassifiedByName *string `json:"classified_by_name" example:"John Doe"`
ClassifiedByShort *string `json:"classified_by_short" example:"JDO"`
// Deadline — grace deadline; after this the aircraft is AOG. null for NON-MEL.
Deadline *string `json:"deadline" example:"2026-06-18T03:04:05Z"`
}
type ComplaintNSR struct {
// IsNSR — Non-Safety Release: true = aircraft released to fly despite the open defect.
IsNSR bool `json:"is_nsr" example:"false"`
// Reason — optional justification for the NSR decision.
Reason *string `json:"reason" example:"not required"`
// DecidedAt / DecidedByName / DecidedByShort — when/who decided NSR (may differ from the reporter).
DecidedAt *string `json:"decided_at" example:"2026-06-15T03:04:05Z"`
DecidedByName *string `json:"decided_by_name" example:"John Doe"`
DecidedByShort *string `json:"decided_by_short" example:"JDO"`
}
type ComplaintAttributes struct {
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FlightID string `json:"flight_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Description string `json:"description" example:"Engine 2 N2 vibration intermittent during cruise above 110 KIAS"`
Helicopter *shareddto.FleetStatusHelicopter `json:"helicopter,omitempty"`
ReportedBy string `json:"reported_by" example:"John Doe"`
ReportedByShort string `json:"reported_by_short,omitempty" example:"JDO"`
ReportedAt string `json:"reported_at" example:"2026-06-15T03:04:05Z"`
AircraftHoursAtReport *string `json:"aircraft_hours_at_report" example:"1245.3"`
MEL ComplaintMEL `json:"mel"`
NSR ComplaintNSR `json:"nsr"`
ActionSigned bool `json:"action_signed" example:"true"`
ActionTaken *string `json:"action_taken" example:"tightened mount"`
ActionSignedAt *string `json:"action_signed_at" example:"2026-06-15T03:04:05Z"`
ActionSignedByName *string `json:"action_signed_by_name" example:"John Doe"`
ActionSignedByShort *string `json:"action_signed_by_short" example:"JDO"`
AircraftHoursAtFix *string `json:"aircraft_hours_at_fix" example:"1250.1"`
Status string `json:"status" example:"active_mel" enums:"active_non_mel,active_mel,serviced"`
CreatedAt string `json:"created_at" example:"2026-06-15T03:04:05Z"`
UpdatedAt string `json:"updated_at" example:"2026-06-15T03:04:05Z"`
}
type ComplaintResource struct {
Type string `json:"type" example:"complaint"`
ID string `json:"id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Attributes ComplaintAttributes `json:"attributes"`
}
type ComplaintResponse struct {
Data ComplaintResource `json:"data"`
}
type ComplaintListResponse struct {
Data []ComplaintResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}
type ComplaintHelicopterGroupAttributes struct {
Helicopter *shareddto.FleetStatusHelicopter `json:"helicopter"`
Complaints []ComplaintResource `json:"complaints"`
}
type ComplaintHelicopterGroupResource struct {
Type string `json:"type" example:"complaint_helicopter_group"`
Attributes ComplaintHelicopterGroupAttributes `json:"attributes"`
}
type ComplaintGroupedListResponse struct {
Data []ComplaintHelicopterGroupResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}
type ComplaintGroupedDataTableResponse struct {
Data []ComplaintHelicopterGroupResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"1"`
RecordsFiltered int64 `json:"records_filtered" example:"1"`
} `json:"meta"`
}

View File

@@ -0,0 +1,227 @@
package response
type DutyRosterShiftTime struct {
ShiftStart string `json:"shift_start,omitempty" example:"06:00"`
ShiftEnd string `json:"shift_end,omitempty" example:"21:00"`
}
type DutyRosterShiftDate struct {
DateStart string `json:"date_start,omitempty" example:"2026-03-16"`
DateEnd string `json:"date_end,omitempty" example:"2026-03-16"`
}
type DutyRosterCrew struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Hunter Wolfgang"`
Role string `json:"role,omitempty" example:"doctor"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+43-1234"`
Email string `json:"email,omitempty" example:"doctor@example.com"`
ShiftDate *DutyRosterShiftDate `json:"shift_date,omitempty"`
ShiftTime *DutyRosterShiftTime `json:"shift_time,omitempty"`
}
type DutyRosterPilotCrew struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Hunter Wolfgang"`
Role string `json:"role,omitempty" example:"pilot"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+43-1234"`
Email string `json:"email,omitempty" example:"pilot@example.com"`
ShiftDate *DutyRosterShiftDate `json:"shift_date,omitempty"`
CrewType string `json:"crew_type,omitempty" example:"main"`
FlightInstructor *bool `json:"flight_instructor,omitempty"`
LineChecker *bool `json:"line_checker,omitempty"`
Supervisor *bool `json:"supervisor,omitempty"`
Examiner *bool `json:"examiner,omitempty"`
CoPilot *bool `json:"co_pilot,omitempty"`
}
type DutyRosterDetailsResponse struct {
Pilot []DutyRosterPilotCrew `json:"pilot"`
Doctor []DutyRosterCrew `json:"doctor"`
Rescuer []DutyRosterCrew `json:"rescuer"`
Helper []DutyRosterCrew `json:"helper"`
OtherPerson []DutyRosterCrew `json:"other_person"`
}
type DutyRosterBaseInfo struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Type string `json:"type,omitempty" example:"hems"`
BaseName string `json:"base_name,omitempty" example:"Gallus 1 - Zurs Lech"`
BaseAbbreviation string `json:"base_abbreviation,omitempty" example:"G1"`
ShiftTime DutyRosterShiftTime `json:"shift_time,omitempty"`
}
type DutyRosterHelicopterSummary struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Designation string `json:"designation" example:"OE-XHZ"`
Identifier string `json:"identifier" example:"1234"`
Type string `json:"type" example:"EC-135"`
}
type DutyRosterAttributes struct {
Bases DutyRosterBaseInfo `json:"bases"`
FlightID string `json:"flight_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Helicopter DutyRosterHelicopterSummary `json:"helicopter"`
DutyDate string `json:"duty_date,omitempty" example:"2026-03-16"`
ShiftTime DutyRosterShiftTime `json:"shift_time"`
Detail DutyRosterDetailsResponse `json:"roster_detail"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type DutyRosterResource struct {
Type string `json:"type" example:"duty_roster"`
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes DutyRosterAttributes `json:"attributes"`
}
type DutyRosterResponse struct {
Data DutyRosterResource `json:"data"`
}
type DutyRosterListResponse struct {
Data []DutyRosterResource `json:"data"`
Meta struct {
Total int `json:"total" example:"31"`
} `json:"meta"`
}
type DutyRosterGetAllMeta struct {
PageNumber int `json:"page_number"`
PageSize int `json:"page_size"`
RecordsTotal int `json:"records_total"`
RecordsFiltered int `json:"records_filtered"`
From string `json:"from"`
To string `json:"to"`
Timezone string `json:"timezone"`
Total int `json:"total"`
}
type DutyRosterGetAllData struct {
HEMS []DutyRosterBaseGroupResource `json:"hems_base"`
Base []DutyRosterBaseGroupResource `json:"base"`
}
type DutyRosterGetAllResponse struct {
Data DutyRosterGetAllData `json:"data"`
Meta DutyRosterGetAllMeta `json:"meta"`
}
type DutyRosterAnyResource struct {
Type string `json:"type"`
ID string `json:"id,omitempty"`
Attributes map[string]any `json:"attributes"`
}
type DutyRosterAnyResponse struct {
Data DutyRosterAnyResource `json:"data"`
}
type DutyRosterAnyListResponse struct {
Data []DutyRosterAnyResource `json:"data"`
Meta struct {
Total int `json:"total" example:"31"`
} `json:"meta"`
}
type DutyRosterAnyDeleteResponse struct {
Data DutyRosterAnyDeleteResource `json:"data"`
}
type DutyRosterAnyDeleteResource struct {
Type string `json:"type" example:"duty_roster_delete"`
Attributes DutyRosterAnyDeleteAttributes `json:"attributes"`
}
type DutyRosterAnyDeleteAttributes struct {
Deleted bool `json:"deleted" example:"true"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Base DutyRosterBaseInfo `json:"base"`
}
type DutyRosterUnifiedDataTableResponse struct {
Data DutyRosterGetAllData `json:"data"`
Meta DutyRosterUnifiedDataTableMeta `json:"meta"`
}
type DutyRosterUnifiedDataTableMeta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int `json:"records_total" example:"20"`
RecordsFiltered int `json:"records_filtered" example:"20"`
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
From string `json:"from" example:"2026-03-01"`
To string `json:"to" example:"2026-03-31"`
Timezone string `json:"timezone" example:"UTC"`
Total int `json:"total" example:"20"`
TotalGroups int `json:"total_groups" example:"20"`
TotalRosters int `json:"total_rosters" example:"45"`
NonEmptyGroups int `json:"non_empty_groups" example:"18"`
}
type DutyRosterGroupRoster struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FlightID string `json:"flight_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DutyDate string `json:"duty_date,omitempty" example:"2026-03-16"`
ShiftTime DutyRosterShiftTime `json:"shift_time"`
Helicopter DutyRosterHelicopterSummary `json:"helicopter"`
Detail DutyRosterDetailsResponse `json:"roster_detail"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type DutyRosterBaseGroupAttributes struct {
Bases DutyRosterBaseInfo `json:"bases"`
Rosters []DutyRosterGroupRoster `json:"rosters"`
}
type DutyRosterBaseGroupResource struct {
Type string `json:"type" example:"hems_base_roster_group"`
Attributes DutyRosterBaseGroupAttributes `json:"attributes"`
}
type DutyRosterGroupedListResponse struct {
Data []DutyRosterBaseGroupResource `json:"data"`
Meta struct {
From string `json:"from,omitempty" example:"2026-03-16"`
To string `json:"to,omitempty" example:"2026-04-15"`
TotalBases int `json:"total_bases" example:"8"`
} `json:"meta"`
}
type DutyRosterGroupedDataTableResponse struct {
Data []DutyRosterBaseGroupResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int `json:"records_total" example:"8"`
RecordsFiltered int `json:"records_filtered" example:"8"`
} `json:"meta"`
}
type DutyRosterDeletedCrewItem struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
RosterID string `json:"roster_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UserID string `json:"user_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Pilot One"`
RoleCode string `json:"role_code,omitempty" example:"pilot"`
CrewType string `json:"crew_type,omitempty" example:"main"`
DateStart string `json:"date_start,omitempty" example:"2026-03-20"`
DateEnd string `json:"date_end,omitempty" example:"2026-03-22"`
}
type DutyRosterCrewDeleteAttributes struct {
Deleted bool `json:"deleted" example:"true"`
DeletedCount int `json:"deleted_count" example:"1"`
DeletedItems []DutyRosterDeletedCrewItem `json:"deleted_items"`
}
type DutyRosterCrewDeleteResource struct {
Type string `json:"type" example:"duty_roster_crew_delete"`
Attributes DutyRosterCrewDeleteAttributes `json:"attributes"`
}
type DutyRosterCrewDeleteResponse struct {
Data DutyRosterCrewDeleteResource `json:"data"`
}

View File

@@ -0,0 +1,55 @@
package response
type EASAReleaseAttributes struct {
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
ComplaintID string `json:"complaint_id,omitempty" example:"019d6774-1111-7fba-9ef4-3795c0da8a13"`
FlightID string `json:"flight_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
EASADate *string `json:"easa_date,omitempty" example:"2026-06-16"`
EASAACHours *string `json:"easa_ac_hours,omitempty" example:"1245.3"`
EASALocation *string `json:"easa_location,omitempty" example:"LOWI"`
EASAWONo *string `json:"easa_wo_no,omitempty" example:"WO-2026-001"`
EASAMaintManualRevAirframe *string `json:"easa_maint_manual_rev_airframe,omitempty" example:"Rev 12"`
EASAMaintManualRevEngine *string `json:"easa_maint_manual_rev_engine,omitempty" example:"Rev 8"`
// EASASignerID — contact id (UUIDv7) of the signer/technician.
EASASignerID *string `json:"easa_signer_id,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
// EASASignerName / EASASignerShort — resolved display name + 3-letter short of the signer contact.
EASASignerName *string `json:"easa_signer_name,omitempty" example:"John Doe"`
EASASignerShort *string `json:"easa_signer_short,omitempty" example:"JDO"`
EASAPermitNo *string `json:"easa_permit_no,omitempty" example:"145.A.50"`
// IsSigned — true once the release is signed (Release to Service); then the complaint becomes fixed.
IsSigned bool `json:"is_signed" example:"false"`
// SignedAt / SignedBy — when/who signed; empty until signed.
SignedAt *string `json:"signed_at,omitempty" example:"2026-06-16T03:04:05Z"`
SignedBy string `json:"signed_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
// MissingSignFields — required fields still empty; must all be filled before it can be signed.
MissingSignFields []string `json:"missing_sign_fields,omitempty"`
CreatedAt string `json:"created_at" example:"2026-06-16T03:04:05Z"`
UpdatedAt string `json:"updated_at" example:"2026-06-16T03:04:05Z"`
}
type EASAReleaseResource struct {
Type string `json:"type" example:"easa_release"`
ID string `json:"id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Attributes EASAReleaseAttributes `json:"attributes"`
}
type EASAReleaseResponse struct {
Data EASAReleaseResource `json:"data"`
}
// EASAReleaseDeleteResponse is the ack returned when an EASA release is voided/deleted.
type EASAReleaseDeleteResponse struct {
Data struct {
Type string `json:"type" example:"easa_release_delete"`
Attributes struct {
Deleted bool `json:"deleted" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
type EASAReleaseListResponse struct {
Data []EASAReleaseResource `json:"data"`
Meta struct {
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}

View File

@@ -0,0 +1,85 @@
package response
type FileManagerActionLocation struct {
FolderID string `json:"folder_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerActionResultAttributes struct {
Status string `json:"status" example:"trashed"`
Location *FileManagerActionLocation `json:"location,omitempty"`
}
type FileManagerActionResultResource struct {
Type string `json:"type" example:"file_manager_delete_result"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FileManagerActionResultAttributes `json:"attributes"`
}
type FileManagerActionResultResponse struct {
Data FileManagerActionResultResource `json:"data"`
}
type FileManagerMoveNodeResultAttributes struct {
NodeType string `json:"node_type" example:"folder"`
Status string `json:"status" example:"moved"`
Location *FileManagerActionLocation `json:"location,omitempty"`
}
type FileManagerMoveNodeResultResource struct {
Type string `json:"type" example:"file_manager_move_result"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FileManagerMoveNodeResultAttributes `json:"attributes"`
}
type FileManagerActionBulkError struct {
Status int `json:"status" example:"422"`
Code string `json:"code,omitempty" example:"FILE_MANAGER_VALIDATION_FAILED"`
ErrorCode string `json:"error_code,omitempty" example:"4222111"`
Title string `json:"title" example:"Validation error"`
Detail string `json:"detail" example:"duplicate name in target folder"`
}
type FileManagerMoveNodeBulkItem struct {
Index int `json:"index" example:"0"`
Success bool `json:"success" example:"true"`
Resource *FileManagerMoveNodeResultResource `json:"resource,omitempty"`
Error *FileManagerActionBulkError `json:"error,omitempty"`
}
type FileManagerMoveNodeBulkResponse struct {
Data []FileManagerMoveNodeBulkItem `json:"data"`
Meta struct {
Total int `json:"total" example:"3"`
Success int `json:"success" example:"2"`
Failed int `json:"failed" example:"1"`
PartialSuccess bool `json:"partial_success" example:"true"`
} `json:"meta"`
}
type FileManagerPurgeNodeResultAttributes struct {
NodeType string `json:"node_type" example:"folder"`
Status string `json:"status" example:"purged"`
}
type FileManagerPurgeNodeResultResource struct {
Type string `json:"type" example:"file_manager_purge_result"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FileManagerPurgeNodeResultAttributes `json:"attributes"`
}
type FileManagerPurgeNodeBulkItem struct {
Index int `json:"index" example:"0"`
Success bool `json:"success" example:"true"`
Resource *FileManagerPurgeNodeResultResource `json:"resource,omitempty"`
Error *FileManagerActionBulkError `json:"error,omitempty"`
}
type FileManagerPurgeNodeBulkResponse struct {
Data []FileManagerPurgeNodeBulkItem `json:"data"`
Meta struct {
Total int `json:"total" example:"3"`
Success int `json:"success" example:"2"`
Failed int `json:"failed" example:"1"`
PartialSuccess bool `json:"partial_success" example:"true"`
} `json:"meta"`
}

View File

@@ -0,0 +1,22 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
type FileManagerAttachmentResource struct {
Type string `json:"type" example:"file_manager_attachment"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerAttachmentAttributes `json:"attributes"`
}
type FileManagerAttachmentResponse struct {
Data FileManagerAttachmentResource `json:"data"`
}
type FileManagerAttachmentListResponse struct {
Data []FileManagerAttachmentResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}

View File

@@ -0,0 +1,10 @@
package response
type FileManagerFileUpdatedSSEPayload struct {
FileID string `json:"file_id"`
Status string `json:"status"`
FolderID *string `json:"folder_id"`
Name string `json:"name"`
MimeType string `json:"mime_type"`
UpdatedAt string `json:"updated_at"`
}

View File

@@ -0,0 +1,145 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
type FileManagerFileResource struct {
Type string `json:"type" example:"file_manager_file"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFileAttributes `json:"attributes"`
}
type FileManagerFileListResource struct {
Type string `json:"type" example:"file_manager_file"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFileListAttributes `json:"attributes"`
}
type FileManagerFileResponse struct {
Data struct {
Type string `json:"type" example:"file_manager_file"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFileAttributes `json:"attributes"`
} `json:"data"`
}
type FileManagerFileDetailResource struct {
Type string `json:"type" example:"file_manager_file"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFileDetailAttributes `json:"attributes"`
}
type FileManagerFileDetailResponse struct {
Data FileManagerFileDetailResource `json:"data"`
}
type FileManagerFileListResponse struct {
Data []FileManagerFileListResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type FileManagerFileDownloadURLResponse struct {
Data struct {
Type string `json:"type" example:"file_manager_file_download"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFileDownloadAttributes `json:"attributes"`
} `json:"data"`
}
type FileManagerFileUploadIntentResponse struct {
Data struct {
Type string `json:"type" example:"file_manager_file_upload_intent"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFileUploadIntentAttributes `json:"attributes"`
} `json:"data"`
}
type FileManagerFileUploadIntentResource struct {
Type string `json:"type" example:"file_manager_file_upload_intent"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFileUploadIntentAttributes `json:"attributes"`
}
type FileManagerFileWOPISessionData struct {
FileID string `json:"file_id" example:"019f0386-0a00-7a4a-a2d8-30afc0e0c3ad"`
CollaboraAction string `json:"collabora_action" example:"https://collabora.example.com/browser/123?WOPISrc=..."`
AccessToken string `json:"access_token" example:"eyJhbGciOiJIUzI1NiIs..."`
// AccessTokenTTL is the access token's absolute expiry time, in epoch
// milliseconds (equal to the JWT exp claim), as required by WOPI/Collabora.
AccessTokenTTL int64 `json:"access_token_ttl" example:"1782971996000"`
}
type FileManagerFileWOPISessionResponse struct {
Data FileManagerFileWOPISessionData `json:"data"`
}
type FileManagerFileBulkError struct {
Status int `json:"status" example:"422"`
Code string `json:"code,omitempty" example:"FILE_MANAGER_VALIDATION_FAILED"`
ErrorCode string `json:"error_code,omitempty" example:"4222111"`
Title string `json:"title" example:"Validation error"`
Detail string `json:"detail" example:"duplicate name in target folder"`
}
type FileManagerFileBulkItem struct {
Index int `json:"index" example:"0"`
Success bool `json:"success" example:"true"`
Message string `json:"message,omitempty" example:"created"`
Resource *FileManagerFileResource `json:"resource,omitempty"`
Error *FileManagerFileBulkError `json:"error,omitempty"`
}
type FileManagerFileBulkResponse struct {
Data []FileManagerFileBulkItem `json:"data"`
Meta struct {
Total int `json:"total" example:"3"`
Success int `json:"success" example:"2"`
Failed int `json:"failed" example:"1"`
PartialSuccess bool `json:"partial_success" example:"true"`
} `json:"meta"`
}
type FileManagerFileUploadIntentBulkItem struct {
Index int `json:"index" example:"0"`
Success bool `json:"success" example:"true"`
Resource *FileManagerFileUploadIntentResource `json:"resource,omitempty"`
Error *FileManagerFileBulkError `json:"error,omitempty"`
}
type FileManagerFileUploadIntentBulkResponse struct {
Data []FileManagerFileUploadIntentBulkItem `json:"data"`
Meta struct {
Total int `json:"total" example:"3"`
Success int `json:"success" example:"2"`
Failed int `json:"failed" example:"1"`
PartialSuccess bool `json:"partial_success" example:"true"`
} `json:"meta"`
}
type FileManagerFileCancelUploadResource struct {
Type string `json:"type" example:"file_manager_file_cancel_upload_result"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes struct {
Status string `json:"status" example:"canceled"`
} `json:"attributes"`
}
type FileManagerFileCancelUploadBulkItem struct {
Index int `json:"index" example:"0"`
Success bool `json:"success" example:"true"`
Resource *FileManagerFileCancelUploadResource `json:"resource,omitempty"`
Error *FileManagerFileBulkError `json:"error,omitempty"`
}
type FileManagerFileCancelUploadBulkResponse struct {
Data []FileManagerFileCancelUploadBulkItem `json:"data"`
Meta struct {
Total int `json:"total" example:"3"`
Success int `json:"success" example:"2"`
Failed int `json:"failed" example:"1"`
PartialSuccess bool `json:"partial_success" example:"true"`
} `json:"meta"`
}

View File

@@ -0,0 +1,99 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
type FileManagerFolderResource struct {
Type string `json:"type" example:"file_manager_folder"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFolderAttributes `json:"attributes"`
}
type FileManagerFolderResponse struct {
Data FileManagerFolderResource `json:"data"`
}
type FileManagerFolderDetailResource struct {
Type string `json:"type" example:"file_manager_folder"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFolderDetailAttributes `json:"attributes"`
}
type FileManagerFolderDetailResponse struct {
Data FileManagerFolderDetailResource `json:"data"`
}
type FileManagerFolderListResponse struct {
Data []FileManagerFolderResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type FileManagerNodeAttributes struct {
NodeType string `json:"node_type" example:"folder"`
ParentID *string `json:"parent_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" example:"operations"`
Extension string `json:"extension,omitempty" example:"pdf"`
SizeBytes int64 `json:"size_bytes,omitempty" example:"1024"`
MimeType string `json:"mime_type,omitempty" example:"application/pdf"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
Status string `json:"status" example:"ready"`
PathCache string `json:"path_cache,omitempty" example:"/root/operations"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type FileManagerNodeResource struct {
Type string `json:"type" example:"file_manager_node"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FileManagerNodeAttributes `json:"attributes"`
}
type FileManagerNodeListResponse struct {
Data []FileManagerNodeResource `json:"data"`
Meta struct {
ParentUUID *string `json:"parent_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Breadcrumb []FileManagerBreadcrumbItem `json:"breadcrumb,omitempty"`
IsRoot bool `json:"is_root" example:"true"`
Folders int `json:"folders" example:"4"`
Files int `json:"files" example:"8"`
Total int `json:"total" example:"12"`
} `json:"meta"`
}
type FileManagerBreadcrumbItem struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" example:"operations"`
}
type FileManagerTrashNodeAttributes struct {
NodeType string `json:"node_type" example:"folder"`
Name string `json:"name" example:"Documents"`
Extension string `json:"extension,omitempty" example:"pdf"`
SizeBytes int64 `json:"size_bytes,omitempty" example:"123456"`
MimeType string `json:"mime_type,omitempty" example:"application/pdf"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
PathCache string `json:"path_cache,omitempty" example:"/Documents"`
TrashedAt string `json:"trashed_at" example:"2026-04-01T10:00:00Z"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-31T08:52:03Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-31T08:52:03Z"`
}
type FileManagerTrashNodeResource struct {
Type string `json:"type" example:"file_manager_trash_node"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes FileManagerTrashNodeAttributes `json:"attributes"`
}
type FileManagerTrashListResponse struct {
Data []FileManagerTrashNodeResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int `json:"total" example:"120"`
Folders int `json:"folders" example:"50"`
Files int `json:"files" example:"70"`
} `json:"meta"`
}

View File

@@ -0,0 +1,22 @@
package response
// FleetHistoryEntry is one event in a helicopter's unified timeline (complaint /
// MEL / NSR / action / MCF / EASA / AOG events plus maintenance servicing).
type FleetHistoryEntry struct {
At string `json:"at" example:"2026-06-17T10:15:00Z"`
EntityType string `json:"entity_type" example:"easa_release"`
EntityID string `json:"entity_id,omitempty" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Action string `json:"action" example:"easa_signed"`
Message string `json:"message" example:"Release to Service (EASA signed)"`
Actor string `json:"actor,omitempty" example:"019e91e6-2908-7f37-93e6-015ed1b7587d"`
ActorName string `json:"actor_name,omitempty" example:"John Doe"`
}
type FleetHistoryListResponse struct {
Data []FleetHistoryEntry `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}

View File

@@ -0,0 +1,107 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
type FleetStatusAttributes struct {
Helicopter *shareddto.FleetStatusHelicopter `json:"helicopter,omitempty"`
Status string `json:"status" example:"active" enums:"active,serviced"`
ServicedAt string `json:"serviced_at,omitempty" example:"2026-05-29T07:43:06Z"`
MaintenanceSchedules []shareddto.MaintenanceScheduleAttributes `json:"maintenance_schedules"`
Files []FleetStatusFile `json:"files"`
Summary shareddto.FleetStatusSummary `json:"summary"`
MCF *shareddto.FleetStatusMCF `json:"mcf,omitempty"`
Complaints []shareddto.FleetStatusComplaint `json:"complaints,omitempty"`
EASARelease *shareddto.FleetStatusEASARelease `json:"easa_release,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-05-29T07:43:06Z"`
CreatedBy string `json:"created_by,omitempty" example:"John Doe"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-05-29T07:43:06Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"John Doe"`
}
type FleetStatusFile struct {
ID string `json:"id,omitempty" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
FileAttachmentID string `json:"file_attachment_id" example:"019d7000-aaaa-7bbb-8ccc-111111111111"`
Category string `json:"category" example:"wb"`
IsMandatory bool `json:"is_mandatory" example:"true"`
Name string `json:"name,omitempty" example:"weight_balance.pdf"`
DownloadURL string `json:"download_url,omitempty" example:"https://storage.example.com/fleet/wb.pdf?X-Amz-Signature=abc123"`
ThumbnailDownloadURL string `json:"thumbnail_download_url,omitempty" example:"https://storage.example.com/fleet/wb.thumb.jpg?X-Amz-Signature=abc123"`
SizeBytes *int64 `json:"size_bytes,omitempty" example:"1024000"`
}
type FleetStatusResource struct {
Type string `json:"type" example:"fleet_status"`
ID string `json:"id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes FleetStatusAttributes `json:"attributes"`
}
type FleetStatusResponse struct {
Data FleetStatusResource `json:"data"`
}
type FleetStatusListResponse struct {
Data []FleetStatusResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"100"`
} `json:"meta"`
}
type FleetStatusDataTableResponse struct {
Data []FleetStatusResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"100"`
RecordsFiltered int64 `json:"records_filtered" example:"20"`
} `json:"meta"`
}
// FleetStatusDeleteResponse is the ack returned when a fleet status is deleted.
type FleetStatusDeleteResponse struct {
Data struct {
Type string `json:"type" example:"fleet_status_delete"`
Attributes struct {
Deleted bool `json:"deleted" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
// FleetStatusMarkServicedResponse is the ack returned when a fleet status is marked serviced.
type FleetStatusMarkServicedResponse struct {
Data struct {
Type string `json:"type" example:"fleet_status_mark_serviced"`
Attributes struct {
Serviced bool `json:"serviced" example:"true"`
} `json:"attributes"`
} `json:"data"`
}
type FleetStatusServiceLogAttributes struct {
FleetStatusID string `json:"fleet_status_id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Helicopter *shareddto.FleetStatusHelicopter `json:"helicopter,omitempty"`
FleetStatusStatus string `json:"fleet_status_status,omitempty" example:"serviced"`
InspectionType string `json:"inspection_type" example:"A/F"`
Type string `json:"type" example:"100H"`
Due string `json:"due" example:"1200 FH"`
Ext string `json:"ext" example:"1300 FH"`
ServicedAt string `json:"serviced_at" example:"2026-05-29T07:43:06Z"`
CreatedAt string `json:"created_at" example:"2026-05-29T07:43:06Z"`
CreatedBy string `json:"created_by" example:"John Doe"`
}
type FleetStatusServiceLogResource struct {
Type string `json:"type" example:"fleet_status_service_log"`
ID string `json:"id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes FleetStatusServiceLogAttributes `json:"attributes"`
}
type FleetStatusServiceLogListResponse struct {
Data []FleetStatusServiceLogResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"100"`
} `json:"meta"`
}

View File

@@ -0,0 +1,139 @@
package response
type FlightDataAttributes struct {
Status string `json:"status" example:"in_progress"`
Mission *FlightDataMissionRef `json:"mission,omitempty"`
RosterDetails *TakeoverDutyRosterDetailsResponse `json:"roster_details,omitempty"`
HESLO *FlightDataSPOHESLODetails `json:"heslo,omitempty" extensions:"x-note=Only returned for SPO missions"`
Logging *FlightDataSPOLoggingDetails `json:"logging,omitempty" extensions:"x-note=Only returned for SPO missions"`
HEC *FlightDataSPOHECDetails `json:"hec,omitempty" extensions:"x-note=Only returned for SPO missions"`
CoPilot *FlightDataCoPilotRef `json:"co_pilot,omitempty"`
From *FlightDataLocationRef `json:"from,omitempty"`
To *FlightDataLocationRef `json:"to,omitempty"`
FlightTakeOff string `json:"flight_take_off,omitempty" example:"2026-04-20T08:00:00Z"`
FlightLanding string `json:"flight_landing,omitempty" example:"2026-04-20T09:15:00Z"`
FlightDuration int64 `json:"flight_duration_seconds" example:"4500"`
FlightRED int64 `json:"flight_red_seconds" example:"300"`
MaxN1 float64 `json:"max_n1" example:"97.5"`
MaxN2 float64 `json:"max_n2" example:"98.1"`
PaxCount int `json:"pax_count" example:"4"`
LandingCount int `json:"landing_count" example:"1"`
RotorBrakeCycle int `json:"rotor_brake_cycle" example:"1"`
HookReleases int `json:"hook_releases" example:"0"`
TicketNo string `json:"ticket_no,omitempty" example:"TKT-001"`
Engine string `json:"engine,omitempty" example:"PW207D1"`
DeliveryNoteNumber string `json:"delivery_note_number,omitempty" example:"DN-1001"`
CustomerName string `json:"customer_name,omitempty" example:"Air Service"`
FlightPlanDistance float64 `json:"flight_plan_distance" example:"120.5"`
FlightPlanTimeSeconds int64 `json:"flight_plan_time_seconds" example:"4200"`
FlightPlanTrueCourse float64 `json:"flight_plan_true_course" example:"182.3"`
FuelBeforeFlight float64 `json:"fuel_before_flight" example:"420"`
FuelUpload float64 `json:"fuel_upload" example:"100"`
FuelAfterFlight float64 `json:"fuel_after_flight" example:"280"`
FuelPlanning float64 `json:"fuel_planning" example:"390"`
FlightPositioning bool `json:"flight_positioning" example:"false"`
OtherInformation string `json:"other_information,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-20T08:00:00Z"`
CreatedBy string `json:"created_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-20T08:00:00Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
}
type FlightDataSPOHESLODetails struct {
Flights []FlightDataSPOHESLOFlight `json:"flights"`
}
type FlightDataSPOHESLOFlight struct {
ID string `json:"id"`
ROT int `json:"rot"`
// Facility category: heslo-rope-label.
HESLORopeLabelID string `json:"heslo_rope_label_id,omitempty"`
HESLORopeLabelName string `json:"heslo_rope_label_name,omitempty"`
Slings []FlightDataSPOHESLOSling `json:"slings"`
}
type FlightDataSPOHESLOSling struct {
ID string `json:"id"`
// Facility category: heslo-rope-length.
SlingID string `json:"sling_id,omitempty"`
SlingName string `json:"sling_name,omitempty"`
}
type FlightDataSPOLoggingDetails struct {
Slings []FlightDataSPOLoggingSling `json:"slings"`
}
type FlightDataSPOLoggingSling struct {
ID string `json:"id"`
// Facility category: heslo-hook.
SlingID string `json:"sling_id,omitempty"`
SlingName string `json:"sling_name,omitempty"`
}
type FlightDataSPOHECDetails struct {
Flights []FlightDataSPOHECFlight `json:"flights"`
Slings []FlightDataSPOHECSling `json:"slings"`
Loads []FlightDataSPOHECLoad `json:"loads"`
}
type FlightDataSPOHECFlight struct {
ID string `json:"id"`
ROT int `json:"rot"`
HECCycle int `json:"hec_cycle"`
// Facility category: hec-rope-label.
EquipmentID string `json:"equipment_id,omitempty"`
EquipmentName string `json:"equipment_name,omitempty"`
}
type FlightDataSPOHECSling struct {
ID string `json:"id"`
// Facility category: hec-rope-length.
SlingID string `json:"sling_id,omitempty"`
SlingName string `json:"sling_name,omitempty"`
}
type FlightDataSPOHECLoad struct {
ID string `json:"id"`
// Weight band for the HEC load, e.g. "bis 199 kg", "200-600 kg", "600-800 kg", "800-1000 kg".
LoadCategory string `json:"load_category,omitempty" example:"200-600 kg"`
Quantity int `json:"quantity" example:"3"`
}
type FlightDataMissionRef struct {
ID string `json:"id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Type string `json:"type,omitempty" example:"HEMS"`
}
type FlightDataCoPilotRef struct {
ID string `json:"id,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
FirstName string `json:"first_name,omitempty" example:"John"`
LastName string `json:"last_name,omitempty" example:"Doe"`
Email string `json:"email,omitempty" example:"john.doe@example.com"`
}
type FlightDataLocationRef struct {
Type string `json:"type,omitempty" example:"icao"`
ID string `json:"id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Code string `json:"code,omitempty" example:"WIII"`
Name string `json:"name,omitempty" example:"General Hospital"`
Label string `json:"label,omitempty" example:"WIII"`
}
type FlightDataResource struct {
Type string `json:"type" example:"flight_data"`
ID string `json:"id" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Attributes FlightDataAttributes `json:"attributes"`
}
type FlightDataResponse struct {
Data FlightDataResource `json:"data"`
}
type FlightDataListResponse struct {
Data []FlightDataResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}

View File

@@ -0,0 +1,22 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
type FlightInspectionFileChecklistResource struct {
Type string `json:"type" example:"flight_inspection_file_checklist"`
ID string `json:"id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes shareddto.FlightInspectionFileChecklistAttributes `json:"attributes"`
}
type FlightInspectionFileChecklistResponse struct {
Data FlightInspectionFileChecklistResource `json:"data"`
}
type FlightInspectionFileChecklistListResponse struct {
Data []FlightInspectionFileChecklistResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}

View File

@@ -0,0 +1,91 @@
package response
type FlightAttributes struct {
Status string `json:"status" example:"draft"`
MissionID string `json:"mission_id,omitempty" example:"019e9640-f7af-7940-80e8-1e9a83013afb"`
Code string `json:"code" example:"M-062226-001"`
Date string `json:"date" example:"2026-04-14"`
TotalDraft int `json:"total_draft" example:"6"`
Takeover *FlightTakeoverSummary `json:"takeover,omitempty"`
Steps []FlightStepItem `json:"steps"`
Draft []FlightDraftGroup `json:"draft,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-14T08:00:00Z"`
CreatedBy *UserRef `json:"created_by,omitempty"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-14T08:00:00Z"`
UpdatedBy *UserRef `json:"updated_by,omitempty"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-04-14T08:00:00Z"`
DeletedBy *UserRef `json:"deleted_by,omitempty"`
}
type FlightStepItem struct {
Phase string `json:"phase" example:"duty_roster"`
Position string `json:"position,omitempty" example:"takeover"`
Step int `json:"step,omitempty" example:"1"`
Complete bool `json:"complete" example:"true"`
ID string `json:"id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Status string `json:"status" example:"completed"`
MissionType []FlightMissionTypeSummary `json:"mission_type,omitempty"`
CompletedAt string `json:"completed_at" example:"2026-04-14T08:00:00Z"`
CompletedBy string `json:"completed_by" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
}
type FlightHelicopterSummary struct {
AOG bool `json:"aog" example:"false"`
Designation string `json:"designation" example:"AIRBUS H145"`
ID string `json:"id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Identifier string `json:"identifier" example:"D-HYAR"`
IsActive bool `json:"is_active" example:"true"`
Type string `json:"type" example:"H145"`
}
type FlightTakeoverSummary struct {
ID string `json:"id,omitempty" example:"019e8713-aa19-7fb7-a510-a0332dc69697"`
Helicopter *FlightHelicopterSummary `json:"helicopter,omitempty"`
DailyInspection *FlightDailyInspectionSummary `json:"daily_inspection,omitempty"`
RosterDetail *TakeoverDutyRosterDetailsResponse `json:"roster_detail,omitempty"`
}
type FlightDailyInspectionSummary struct {
ID string `json:"id" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Name string `json:"name" example:"Ganahl Stefan"`
ShortName string `json:"short_name" example:"GAN"`
Date string `json:"date" example:"17.04.2026"`
UTCTime string `json:"utc_time" example:"10:57"`
}
type FlightDraftItem struct {
ID string `json:"id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Type string `json:"type" example:"HEMS"`
Status string `json:"status" example:"draft"`
FlightDataID string `json:"flight_data_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a55"`
}
type FlightDraftGroup struct {
Type string `json:"type" example:"HEMS"`
Items []FlightDraftItem `json:"items"`
}
type FlightMissionTypeSummary struct {
Type string `json:"type" example:"HEMS"`
Draft int `json:"draft" example:"5"`
Completed int `json:"completed" example:"0"`
}
type FlightResource struct {
Type string `json:"type" example:"flight"`
ID string `json:"id" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Attributes FlightAttributes `json:"attributes"`
}
type FlightResponse struct {
Data FlightResource `json:"data"`
}
type FlightListResponse struct {
Data []FlightResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}

View File

@@ -0,0 +1,170 @@
package response
import (
shareddto "wucher/internal/transport/http/dto/shared"
"wucher/internal/transport/http/jsonapi"
)
type HelicopterFileResource struct {
Type string `json:"type" example:"helicopter_file"`
ID string `json:"id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes shareddto.HelicopterFileAttributes `json:"attributes"`
}
type HelicopterFileResponse struct {
Data HelicopterFileResource `json:"data"`
}
type HelicopterFileListResponse struct {
Data []HelicopterFileResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}
type HelicopterFileCollectionHelicopter struct {
ID string `json:"id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Name string `json:"name" example:"AIRBUS H145"`
}
type HelicopterFileCollectionAttachment struct {
ID string `json:"id" example:"019d6771-6c8e-7a2d-a7b8-03bcb65fcb3e"`
FileName string `json:"file_name" example:"Before Flight Checklist.pdf"`
DownloadURL string `json:"download_url" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
}
type HelicopterFileCollectionItem struct {
HelicopterFileID string `json:"helicopter_file_id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Section string `json:"section" example:"bfi"`
Position int `json:"position" example:"1"`
IsMandatory bool `json:"is_mandatory" example:"true"`
IsTemplate bool `json:"is_template" example:"true"`
AttachmentPending bool `json:"attachment_pending" example:"true"`
Attachment HelicopterFileCollectionAttachment `json:"attachment"`
CreatedAt string `json:"created_at" example:"2026-04-14T08:00:00Z"`
CreatedBy string `json:"created_by" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
UpdatedAt string `json:"updated_at" example:"2026-04-14T08:00:00Z"`
UpdatedBy string `json:"updated_by" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
}
type HelicopterFileCollectionSections struct {
Before []HelicopterFileCollectionItem `json:"before"`
Prepare []HelicopterFileCollectionItem `json:"prepare"`
After []HelicopterFileCollectionItem `json:"after"`
}
type HelicopterFileCollectionAttributes struct {
Helicopter HelicopterFileCollectionHelicopter `json:"helicopter"`
Files HelicopterFileCollectionSections `json:"files"`
}
type HelicopterFileCollectionResponse struct {
Type string `json:"type" example:"helicopter_file_collection"`
Attributes HelicopterFileCollectionAttributes `json:"attributes"`
}
type HelicopterFileCollectionListResponse struct {
Data []HelicopterFileCollectionResponse `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}
type HelicopterFileDeleteAttributes struct {
Deleted bool `json:"deleted" example:"true"`
}
type HelicopterFileDeleteResource struct {
Type string `json:"type" example:"helicopter_file_delete"`
Attributes HelicopterFileDeleteAttributes `json:"attributes"`
}
type HelicopterFileDeleteResponse struct {
Data HelicopterFileDeleteResource `json:"data"`
}
type HelicopterFileUploadIntentResource struct {
Type string `json:"type" example:"helicopter_file_upload_intent"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes shareddto.FileManagerFileUploadIntentAttributes `json:"attributes"`
}
type HelicopterFileUploadIntentResponse struct {
Data HelicopterFileUploadIntentResource `json:"data"`
}
type HelicopterFileBulkError struct {
Status int `json:"status" example:"422"`
Code string `json:"code,omitempty" example:"HELICOPTER_FILE_INVALID_PAYLOAD"`
ErrorCode string `json:"error_code,omitempty" example:"4220318"`
Title string `json:"title" example:"Validation error"`
Detail string `json:"detail" example:"helicopter_id is invalid UUID"`
}
type HelicopterFileUploadIntentBulkItem struct {
Index int `json:"index" example:"0"`
Success bool `json:"success" example:"true"`
Resource *HelicopterFileUploadIntentResource `json:"resource,omitempty"`
Error *HelicopterFileBulkError `json:"error,omitempty"`
}
type HelicopterFileUploadIntentBulkResponse struct {
Data []HelicopterFileUploadIntentBulkItem `json:"data"`
Meta struct {
Total int `json:"total" example:"3"`
Success int `json:"success" example:"2"`
Failed int `json:"failed" example:"1"`
PartialSuccess bool `json:"partial_success" example:"true"`
} `json:"meta"`
}
type HelicopterFileCreateBulkItem struct {
Index int `json:"index" example:"0"`
Success bool `json:"success" example:"true"`
Resource *jsonapi.Resource `json:"resource,omitempty"`
Error *HelicopterFileBulkError `json:"error,omitempty"`
}
type HelicopterFileCreateBulkResponse struct {
Data []HelicopterFileCreateBulkItem `json:"data"`
Meta struct {
Total int `json:"total" example:"3"`
Success int `json:"success" example:"2"`
Failed int `json:"failed" example:"1"`
PartialSuccess bool `json:"partial_success" example:"true"`
} `json:"meta"`
}
type HelicopterFileUpdateBulkItem struct {
Index int `json:"index" example:"0"`
Success bool `json:"success" example:"true"`
Resource *jsonapi.Resource `json:"resource,omitempty"`
Error *HelicopterFileBulkError `json:"error,omitempty"`
}
type HelicopterFileUpdateBulkResponse struct {
Data []HelicopterFileUpdateBulkItem `json:"data"`
Meta struct {
Total int `json:"total" example:"3"`
Success int `json:"success" example:"2"`
Failed int `json:"failed" example:"1"`
PartialSuccess bool `json:"partial_success" example:"true"`
} `json:"meta"`
}
type HelicopterFileCreatedSSEPayload struct {
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
HelicopterFileID string `json:"helicopter_file_id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
FileID string `json:"file_id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Section string `json:"section" example:"fpwb"`
Position int `json:"position" example:"1"`
IsMandatory bool `json:"is_mandatory" example:"true"`
AttachmentPending bool `json:"attachment_pending" example:"true"`
Name string `json:"name" example:"Before Flight Checklist.pdf"`
MimeType string `json:"mime_type,omitempty" example:"application/pdf"`
UpdatedAt string `json:"updated_at" example:"2026-07-01T06:00:00Z"`
}

View File

@@ -0,0 +1,88 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
// JSON:API Helicopter response DTOs
type HelicopterAttributes struct {
Designation string `json:"designation" example:"OE-XHZ"`
Identifier string `json:"identifier" example:"ABC-0001"`
Type string `json:"type" example:"EC-135"`
SortKey *int `json:"sortkey" example:"10"`
Engine1 string `json:"engine_1,omitempty" example:"TM"`
Engine2 string `json:"engine_2,omitempty" example:"TM"`
Consumption float64 `json:"consumption_lt_min" example:"4.1"`
NR1 bool `json:"nr1" example:"true"`
NR2 bool `json:"nr2" example:"true"`
LH bool `json:"lh" example:"true"`
RH bool `json:"rh" example:"true"`
MGB bool `json:"mgb" example:"true"`
IGB bool `json:"igb" example:"false"`
TGB bool `json:"tgb" example:"true"`
UTCOffset int `json:"utc_offset" example:"-1"`
Foto *shareddto.HelicopterFoto `json:"foto,omitempty"`
Dry bool `json:"dry" example:"false"`
AOG bool `json:"aog" example:"false"`
IsInUse bool `json:"is_in_use" example:"false"`
// Status is the operational status of the helicopter (read-time derived).
// Values: "available", "booked", "aog", "mcf". Precedence: mcf > aog > booked > available.
// "booked" = active flight assignment; "aog" = air_on_ground flag OR an open complaint
// grounding it (non-MEL, or MEL past its grace deadline); "mcf" = a pending maintenance
// check flight (MCF created, not yet passed).
Status string `json:"status" example:"available" enums:"available,booked,aog,mcf"`
AOGReason string `json:"aog_reason,omitempty" example:"Main rotor gearbox fault"`
Note string `json:"note,omitempty" example:"Primary HEMS helicopter"`
IsActive bool `json:"is_active" example:"true"`
CreatedBy *shareddto.HelicopterAuditUser `json:"created_by,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy *shareddto.HelicopterAuditUser `json:"updated_by,omitempty"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
LastDailyInspection *shareddto.HelicopterLastDailyInspection `json:"last_daily_inspection"`
// MaintenanceSchedules mirrors the helicopter's latest fleet status. It
// always lists every inspection type; entries without data are empty.
MaintenanceSchedules []shareddto.MaintenanceScheduleAttributes `json:"maintenance_schedules"`
}
type HelicopterResource struct {
Type string `json:"type" example:"helicopter"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HelicopterAttributes `json:"attributes"`
}
type HelicopterResponse struct {
Data HelicopterResource `json:"data"`
}
type HelicopterListResponse struct {
Data []HelicopterResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type HelicopterDataTableResponse struct {
Data []HelicopterResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type HelicopterNextReportNumberAttributes struct {
Identifier string `json:"identifier" example:"ABC"`
Sequence int `json:"sequence" example:"1"`
ReportNumber string `json:"report_number" example:"ABC-0001"`
}
type HelicopterNextReportNumberResource struct {
Type string `json:"type" example:"helicopter_report_number"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes HelicopterNextReportNumberAttributes `json:"attributes"`
}
type HelicopterNextReportNumberResponse struct {
Data HelicopterNextReportNumberResource `json:"data"`
}

View File

@@ -0,0 +1,19 @@
package response
type MasterSettingsMicrosoftEntraAttributes struct {
TenantID string `json:"MS_ENTRA_TENANT_ID" example:"your-tenant-id"`
ClientID string `json:"MS_ENTRA_CLIENT_ID" example:"your-client-id"`
ClientSecretMasked string `json:"MS_ENTRA_CLIENT_SECRET" example:"********"`
RedirectURL string `json:"MS_ENTRA_REDIRECT_URL" example:"https://example.com/api/v1/auth/microsoft/callback"`
Authority string `json:"MS_ENTRA_AUTHORITY" example:"https://login.microsoftonline.com/"`
Scopes string `json:"MS_ENTRA_SCOPES" example:"openid,profile,email"`
}
type MasterSettingsMicrosoftEntraResource struct {
Type string `json:"type" example:"master_settings_microsoft_entra_sso"`
Attributes MasterSettingsMicrosoftEntraAttributes `json:"attributes"`
}
type MasterSettingsMicrosoftEntraResponse struct {
Data MasterSettingsMicrosoftEntraResource `json:"data"`
}

View File

@@ -0,0 +1,49 @@
package response
type MasterSettingsAttributes struct {
CompanyName string `json:"company_name" example:"Wucher"`
Title string `json:"title,omitempty" example:"Welcome to Wucher"`
Subtitle string `json:"subtitle,omitempty" example:"Flight operations platform"`
LogoFileUUID *string `json:"logo_file_uuid" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
LogoURL *string `json:"logo_url" example:"https://cdn.example.com/logo.png"`
LogoThumbURL *string `json:"logo_thumbnail_url,omitempty" example:"https://cdn.example.com/logo.thumb.png"`
LogoSizeBytes *int64 `json:"logo_original_size_bytes,omitempty" example:"1024000"`
CoverFileUUID *string `json:"cover_file_uuid" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CoverURL *string `json:"cover_url" example:"https://cdn.example.com/cover.png"`
CoverThumbURL *string `json:"cover_thumbnail_url,omitempty" example:"https://cdn.example.com/cover.thumb.png"`
CoverSizeBytes *int64 `json:"cover_original_size_bytes,omitempty" example:"2048000"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type MasterSettingsResource struct {
Type string `json:"type" example:"master_settings"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes MasterSettingsAttributes `json:"attributes"`
}
type MasterSettingsResponse struct {
Data MasterSettingsResource `json:"data"`
}
type MasterSettingsListResponse struct {
Data []MasterSettingsResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type MasterSettingsDataTableResponse struct {
Data []MasterSettingsResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}

View File

@@ -0,0 +1,40 @@
package response
type MCFAttributes struct {
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FlightID string `json:"flight_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Date string `json:"date" example:"2026-06-17"`
AFHours string `json:"af_hours,omitempty" example:"1245.3"`
LandingCount int `json:"landing_count" example:"3"`
UTCTime *string `json:"utc_time,omitempty" example:"2026-06-17T08:30:00Z"`
DecidedBy string `json:"decided_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
DecidedAt *string `json:"decided_at,omitempty" example:"2026-06-16T03:04:05Z"`
Result *string `json:"result,omitempty" example:"passed" enums:"passed,failed"`
Notes *string `json:"notes,omitempty" example:"Vibration within limits"`
CompletedBy string `json:"completed_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
CompletedAt *string `json:"completed_at,omitempty" example:"2026-06-16T03:04:05Z"`
IsComplete bool `json:"is_complete" example:"false"`
IsCleared bool `json:"is_cleared" example:"false"`
IsPending bool `json:"is_pending" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-06-16T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-06-16T03:04:05Z"`
}
type MCFResource struct {
Type string `json:"type" example:"mcf"`
ID string `json:"id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Attributes MCFAttributes `json:"attributes"`
}
type MCFResponse struct {
Data MCFResource `json:"data"`
}
type MCFListResponse struct {
Data []MCFResource `json:"data"`
Meta struct {
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}

View File

@@ -0,0 +1,93 @@
package response
type MissionAttributes struct {
Flight *MissionFlightRef `json:"flight,omitempty"`
Type string `json:"type" example:"HEMS"`
MissionCode string `json:"mission_code" example:"HEMS-26-1"`
Subtypes *MissionSubtypeRef `json:"subtypes,omitempty"`
Note string `json:"note,omitempty" example:"Mission note"`
StartTime string `json:"start_time,omitempty" example:"04:49"`
EndTime string `json:"end_time,omitempty" example:"22:05"`
Status string `json:"status" example:"completed"`
Forms *MissionForms `json:"forms,omitempty"`
Files []MissionFileItem `json:"files,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-20T08:00:00Z"`
CreatedBy string `json:"created_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-20T08:00:00Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
}
type MissionForms struct {
FlightData []FlightDataResource `json:"flight_data,omitempty"`
}
type MissionFileItem struct {
ID string `json:"id" example:"019d6771-6c8e-7a2d-a7b8-03bcb65fcb3e"`
AttachmentID string `json:"attachment_id,omitempty" example:"019d6771-6c8e-7a2d-a7b8-03bcb65fcb3e"`
FileName string `json:"file_name,omitempty" example:"mission-brief.pdf"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/brief.pdf?X-Amz-Signature=..."`
ThumbnailURL string `json:"thumbnail_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/thumbnails/2026/04/05/brief.png?X-Amz-Signature=..."`
}
type MissionFlightRef struct {
ID string `json:"id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Code string `json:"code,omitempty" example:"M-062226-001"`
Date string `json:"date,omitempty" example:"2026-04-20"`
}
type MissionSubtypeRef struct {
ID string `json:"id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a99"`
Subtype string `json:"subtype,omitempty" example:"PRIM"`
SubtypeName string `json:"subtype_name,omitempty" example:"Nicht transportfaehig"`
}
type MissionResource struct {
Type string `json:"type" example:"mission"`
ID string `json:"id" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
Attributes MissionAttributes `json:"attributes"`
}
type MissionResponse struct {
Data MissionResource `json:"data"`
}
type MissionListResponse struct {
Data []MissionResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type MissionDataTableResponse struct {
Data []MissionResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"42"`
} `json:"meta"`
}
type MissionTypeSubtypeItem struct {
ID string `json:"id" example:"019d6774-98e8-7fba-9ef4-3795c0da8a99"`
Code string `json:"code" example:"PRIM"`
Name string `json:"name" example:"Primary"`
TaskName string `json:"task_name" example:"Nicht transportfaehig"`
}
type MissionTypeAttributes struct {
Code string `json:"code" example:"HEMS"`
Name string `json:"name" example:"HEMS"`
Subtypes []MissionTypeSubtypeItem `json:"subtypes,omitempty"`
}
type MissionTypeResource struct {
Type string `json:"type" example:"mission_type"`
ID string `json:"id" example:"019d6774-98e8-7fba-9ef4-3795c0da8a77"`
Attributes MissionTypeAttributes `json:"attributes"`
}
type MissionTypeListResponse struct {
Data []MissionTypeResource `json:"data"`
}

View File

@@ -0,0 +1,24 @@
package response
type OtherPersonAttributes struct {
Name string `json:"name" example:"Guest Person"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+43-1234"`
Email string `json:"email,omitempty" example:"guest@example.com"`
}
type OtherPersonResource struct {
Type string `json:"type" example:"other_person"`
ID string `json:"id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Attributes OtherPersonAttributes `json:"attributes"`
}
type OtherPersonListResponse struct {
Data []OtherPersonResource `json:"data"`
Meta struct {
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}
type OtherPersonResponse struct {
Data OtherPersonResource `json:"data"`
}

View File

@@ -0,0 +1,98 @@
package response
import shareddto "wucher/internal/transport/http/dto/shared"
type ReserveAcResource struct {
Type string `json:"type" example:"reserve_ac"`
ID string `json:"id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Attributes shareddto.ReserveAcAttributes `json:"attributes"`
}
type ReserveAcResponse struct {
Data ReserveAcResource `json:"data"`
}
type ReserveAcListResponse struct {
Data []ReserveAcResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"1"`
} `json:"meta"`
}
type ReserveAcHelicopterGroupResource struct {
Type string `json:"type"`
Attributes ReserveAcHelicopterGroupAttributes `json:"attributes"`
}
type ReserveAcHelicopterGroupAttributes struct {
Helicopter ReserveAcHelicopterIdentity `json:"helicopter"`
LastDailyInspection ReserveAcLastDailyInspectionDTO `json:"last_daily_inspection"`
BFI ReserveAcBFISection `json:"bfi"`
FPWB ReserveAcFPWBSection `json:"fpwb"`
}
type ReserveAcHelicopterIdentity struct {
ID string `json:"id"`
Designation string `json:"designation"`
Identifier string `json:"identifier"`
Type string `json:"type"`
Status string `json:"status"`
BookingDetails *ReserveAcBookingDetailsDTO `json:"booking_details,omitempty"`
}
type ReserveAcLastDailyInspectionDTO struct {
ID any `json:"id"`
InspectionBy ReserveAcInspectionBySummary `json:"inspection_by"`
InspectionAt any `json:"inspection_at"`
}
type ReserveAcInspectionBySummary struct {
ID any `json:"id"`
Name string `json:"name"`
ShortName string `json:"short_name"`
}
type ReserveAcBookingDetailsDTO struct {
ReserveAcID any `json:"reserve_ac_id,omitempty"`
FlightID any `json:"flight_id,omitempty"`
MissionCode any `json:"mission_code,omitempty"`
DutyDate any `json:"duty_date,omitempty"`
BookedAt any `json:"booked_at,omitempty"`
BookedBy map[string]any `json:"booked_by,omitempty"`
}
type ReserveAcHelicopterFileItem struct {
ID string `json:"id"`
FileName string `json:"file_name"`
URL string `json:"url"`
UploadedAt string `json:"uploaded_at"`
UploadedBy string `json:"uploaded_by"`
}
type ReserveAcHelicopterFileSection struct {
Files []ReserveAcHelicopterFileItem `json:"files"`
}
type ReserveAcBFISection struct {
FuelAddedAmount *float64 `json:"fuel_added_amount,omitempty"`
FuelAmount *float64 `json:"fuel_amount,omitempty"`
FuelUnit string `json:"fuel_unit,omitempty"`
HydraulicLHChecked *bool `json:"hydraulic_lh_checked,omitempty"`
HydraulicRHChecked *bool `json:"hydraulic_rh_checked,omitempty"`
Note *string `json:"note,omitempty"`
OilEngineNR1Checked *bool `json:"oil_engine_nr1_checked,omitempty"`
OilEngineNR2Checked *bool `json:"oil_engine_nr2_checked,omitempty"`
OilTransmissionIGBChecked *bool `json:"oil_transmission_igb_checked,omitempty"`
OilTransmissionMGBChecked *bool `json:"oil_transmission_mgb_checked,omitempty"`
OilTransmissionTGBChecked *bool `json:"oil_transmission_tgb_checked,omitempty"`
Files []ReserveAcHelicopterFileItem `json:"files"`
}
type ReserveAcFPWBSection struct {
NotamBriefing *bool `json:"notam_briefing,omitempty"`
WeatherBriefing *bool `json:"weather_briefing,omitempty"`
OperationalFlightPlan *bool `json:"operational_flight_plan,omitempty"`
Files []ReserveAcHelicopterFileItem `json:"files"`
}

View File

@@ -0,0 +1,112 @@
package response
import (
shareddto "wucher/internal/transport/http/dto/shared"
)
type TakeoverResponseAttributes struct {
ID string `json:"id,omitempty"`
FlightID string `json:"flight_id,omitempty"`
ReserveAcID string `json:"reserve_ac_id,omitempty"`
FlightInspectionID string `json:"flight_inspection_id,omitempty"`
ShiftStart string `json:"shift_start,omitempty"`
ShiftEnd string `json:"shift_end,omitempty"`
BaseID string `json:"base_id,omitempty"`
Base *DutyRosterBaseInfo `json:"base,omitempty"`
DutyDate string `json:"duty_date,omitempty"`
HelicopterID string `json:"helicopter_id,omitempty"`
Helicopter *TakeoverHelicopter `json:"helicopter,omitempty"`
Notes *string `json:"notes,omitempty"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
RosterDetail TakeoverDutyRosterDetailsResponse `json:"roster_detail"`
Inspection shareddto.ReserveAcInspectionSummary `json:"inspection"`
Files []TakeoverFileResource `json:"files,omitempty"`
}
// TakeoverHelicopter is the selected helicopter shown on a takeover, enriched
// with its last daily inspection and its maintenance schedule (the "next due"
// per inspection type) from the helicopter's latest fleet status.
type TakeoverHelicopter struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Designation string `json:"designation" example:"OE-XHZ"`
Identifier string `json:"identifier" example:"1234"`
Type string `json:"type" example:"EC-135"`
LastDailyInspection *shareddto.HelicopterLastDailyInspection `json:"last_daily_inspection"`
MaintenanceSchedules []shareddto.MaintenanceScheduleAttributes `json:"maintenance_schedules"`
}
type TakeoverDutyRosterAttributes struct {
Bases DutyRosterBaseInfo `json:"bases"`
FlightID string `json:"flight_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Helicopter DutyRosterHelicopterSummary `json:"helicopter"`
DutyDate string `json:"duty_date,omitempty" example:"2026-03-16"`
Detail TakeoverDutyRosterDetailsResponse `json:"roster_detail"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}
type TakeoverDutyRosterResource struct {
Type string `json:"type" example:"duty_roster"`
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes TakeoverDutyRosterAttributes `json:"attributes"`
}
type TakeoverDutyRosterCrew struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Hunter Wolfgang"`
Role string `json:"role,omitempty" example:"doctor"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+43-1234"`
Email string `json:"email,omitempty" example:"doctor@example.com"`
}
type TakeoverDutyRosterPilotCrew struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name,omitempty" example:"Hunter Wolfgang"`
Role string `json:"role,omitempty" example:"pilot"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+43-1234"`
Email string `json:"email,omitempty" example:"pilot@example.com"`
CrewType string `json:"crew_type,omitempty" example:"main"`
FlightInstructor *bool `json:"flight_instructor,omitempty"`
LineChecker *bool `json:"line_checker,omitempty"`
Supervisor *bool `json:"supervisor,omitempty"`
Examiner *bool `json:"examiner,omitempty"`
CoPilot *bool `json:"co_pilot,omitempty"`
}
type TakeoverDutyRosterDetailsResponse struct {
Pilot []TakeoverDutyRosterPilotCrew `json:"pilot"`
Doctor []TakeoverDutyRosterCrew `json:"doctor"`
Rescuer []TakeoverDutyRosterCrew `json:"rescuer"`
Helper []TakeoverDutyRosterCrew `json:"helper"`
OtherPerson []TakeoverDutyRosterCrew `json:"other_person"`
}
type TakeoverResource struct {
Type string `json:"type" example:"takeover"`
Attributes TakeoverResponseAttributes `json:"attributes"`
}
type TakeoverFileAttributes struct {
FileAttachmentID string `json:"file_attachment_id" example:"019d7000-aaaa-7bbb-8ccc-222222222222"`
FileName string `json:"file_name,omitempty" example:"takeover-report.pdf"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/takeover-report.pdf?X-Amz-Signature=..."`
}
type TakeoverFileResource struct {
Type string `json:"type" example:"takeover_file"`
ID string `json:"id,omitempty" example:"019d7000-aaaa-7bbb-8ccc-111111111111"`
Attributes TakeoverFileAttributes `json:"attributes"`
}
type TakeoverResponse struct {
Data TakeoverResource `json:"data"`
}
type TakeoverListResponse struct {
Data []TakeoverResource `json:"data"`
Meta map[string]any `json:"meta,omitempty"`
}

View File

@@ -0,0 +1,8 @@
package response
// UserRef is an audit user reference exposing both the raw UUID and a resolved
// display name, e.g. for created_by / updated_by fields.
type UserRef struct {
UUID string `json:"uuid" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
Name string `json:"name,omitempty" example:"Ganahl Stefan"`
}

View File

@@ -0,0 +1,174 @@
package dto
// JSON:API Role DTOs
type RoleAttributes struct {
Name string `json:"name" example:"admin"`
Description string `json:"description,omitempty" example:"Full access to all resources."`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-02-05T10:00:00Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-02-05T10:00:00Z"`
}
type RoleResource struct {
Type string `json:"type" example:"role"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes RoleAttributes `json:"attributes"`
Permissions []PermissionResource `json:"permissions,omitempty"`
}
type RoleResponse struct {
Data RoleResource `json:"data"`
}
type RoleListResponse struct {
Data []RoleResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type RoleDataTableResponse struct {
Data []RoleResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type PermissionAttributes struct {
Name string `json:"name" example:"User Management"`
Key string `json:"key" example:"user.manage"`
Description string `json:"description,omitempty" example:"Allows managing users."`
RequiresPIN bool `json:"requires_pin" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-02-05T10:00:00Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-02-05T10:00:00Z"`
}
type PermissionResource struct {
Type string `json:"type" example:"permission"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes PermissionAttributes `json:"attributes"`
}
type PermissionListResponse struct {
Data []PermissionResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type PermissionGroupResource struct {
Module string `json:"module" example:"dul"`
ModuleLabel string `json:"module_label" example:"DUL"`
Permissions []PermissionResource `json:"permissions"`
}
type PermissionGroupedResponse struct {
Data []PermissionGroupResource `json:"data"`
}
// Create
type RoleCreateAttributes struct {
Name string `json:"name" validate:"required" example:"admin"`
Description string `json:"description,omitempty" example:"Full access to all resources."`
}
type RoleCreateData struct {
Type string `json:"type" validate:"required,oneof=role_create role" example:"role_create"`
Attributes RoleCreateAttributes `json:"attributes" validate:"required"`
}
type RoleCreateRequest struct {
Data RoleCreateData `json:"data" validate:"required"`
}
// Update
type RoleUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"admin"`
Description *string `json:"description,omitempty" example:"Full access to all resources."`
}
type RoleUpdateData struct {
Type string `json:"type" validate:"required,oneof=role_update role" example:"role_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes RoleUpdateAttributes `json:"attributes" validate:"required"`
}
type RoleUpdateRequest struct {
Data RoleUpdateData `json:"data" validate:"required"`
}
// Role-Permission
type RolePermissionAttributes struct {
RoleID string `json:"role_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
PermissionID string `json:"permission_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9e"`
}
type RolePermissionResource struct {
Type string `json:"type" example:"role_permission"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9f"`
Attributes RolePermissionAttributes `json:"attributes"`
}
type RolePermissionResponse struct {
Data RolePermissionResource `json:"data"`
}
type RolePermissionListResponse struct {
Data []RolePermissionResource `json:"data"`
}
type RolePermissionAssignAttributes struct {
PermissionID string `json:"permission_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9e"`
}
type RolePermissionAssignData struct {
Type string `json:"type" validate:"required,eq=role_permission" example:"role_permission"`
Attributes RolePermissionAssignAttributes `json:"attributes" validate:"required"`
}
type RolePermissionAssignRequest struct {
Data RolePermissionAssignData `json:"data" validate:"required"`
}
type RolePermissionBulkAttributes struct {
PermissionIDs []string `json:"permission_ids" validate:"required" example:"[\"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9e\",\"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9f\"]"`
}
type RolePermissionAssignBulkData struct {
Type string `json:"type" validate:"required,eq=role_assign_permission_bulk" example:"role_assign_permission_bulk"`
Attributes RolePermissionBulkAttributes `json:"attributes" validate:"required"`
}
type RolePermissionAssignBulkRequest struct {
Data RolePermissionAssignBulkData `json:"data" validate:"required"`
}
type RolePermissionRemoveBulkData struct {
Type string `json:"type" validate:"required,eq=role_remove_permission_bulk" example:"role_remove_permission_bulk"`
Attributes RolePermissionBulkAttributes `json:"attributes" validate:"required"`
}
type RolePermissionRemoveBulkRequest struct {
Data RolePermissionRemoveBulkData `json:"data" validate:"required"`
}
type PermissionUpdateAttributes struct {
RequiresPIN *bool `json:"requires_pin" validate:"required" example:"true"`
}
type PermissionUpdateData struct {
Type string `json:"type" validate:"required,oneof=permission_update permission" example:"permission_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes PermissionUpdateAttributes `json:"attributes" validate:"required"`
}
type PermissionUpdateRequest struct {
Data PermissionUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,13 @@
package shared
type AirRescuerChecklistBaseInfo struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
BaseName string `json:"base_name,omitempty" example:"Gallus 1 - Zurs Lech"`
BaseAbbreviation string `json:"base_abbreviation,omitempty" example:"G1"`
BaseCategory string `json:"base_category,omitempty" example:"hems"`
}
type AirRescuerChecklistItemInput struct {
Name string `json:"name" validate:"required" example:"Oxygen supply check"`
Position *int `json:"position,omitempty" example:"1"`
}

View File

@@ -0,0 +1,13 @@
package shared
type FileManagerAttachmentAttributes struct {
FileID string `json:"file_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
RefType string `json:"ref_type" example:"helicopter"`
RefID string `json:"ref_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Category string `json:"category,omitempty" example:"document"`
SortOrder int `json:"sort_order" example:"0"`
IsPrimary bool `json:"is_primary" example:"false"`
AttachedBy string `json:"attached_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
}

View File

@@ -0,0 +1,104 @@
package shared
type FileManagerFileAttributes struct {
FileUUID string `json:"file_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
FolderID string `json:"folder_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" example:"policy.pdf"`
NameNormalized string `json:"name_normalized,omitempty" example:"policy.pdf"`
Extension string `json:"extension,omitempty" example:"pdf"`
SizeBytes int64 `json:"size_bytes" example:"1024"`
MimeType string `json:"mime_type" example:"application/pdf"`
IsTemplate bool `json:"is_template" example:"true"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
Status string `json:"status" example:"validated"`
FailureReason string `json:"failure_reason,omitempty" example:""`
UploadError string `json:"upload_error,omitempty" example:""`
ProcessingStartedAt string `json:"processing_started_at,omitempty" example:"2026-03-12T03:04:05Z"`
ProcessedAt string `json:"processed_at,omitempty" example:"2026-03-12T03:04:05Z"`
ValidatedAt string `json:"validated_at,omitempty" example:"2026-03-12T03:04:05Z"`
FailedAt string `json:"failed_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
TrashedAt string `json:"trashed_at,omitempty" example:"2026-03-12T03:04:05Z"`
PurgeAt string `json:"purge_at,omitempty" example:"2026-04-12T03:04:05Z"`
}
type FileManagerFileListAttributes struct {
Name string `json:"name" example:"policy.pdf"`
NameNormalized string `json:"name_normalized,omitempty" example:"policy.pdf"`
Extension string `json:"extension,omitempty" example:"pdf"`
SizeBytes int64 `json:"size_bytes" example:"1024"`
MimeType string `json:"mime_type" example:"application/pdf"`
IsTemplate bool `json:"is_template" example:"true"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
Status string `json:"status" example:"ready"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerFileLocationAttributes struct {
FolderID string `json:"folder_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerFileProcessingAttributes struct {
ProcessingStartedAt string `json:"processing_started_at,omitempty" example:"2026-03-12T03:04:05Z"`
ProcessedAt string `json:"processed_at,omitempty" example:"2026-03-12T03:04:05Z"`
ValidatedAt string `json:"validated_at,omitempty" example:"2026-03-12T03:04:05Z"`
FailedAt string `json:"failed_at,omitempty" example:"2026-03-12T03:04:05Z"`
FailureReason string `json:"failure_reason,omitempty" example:"processing validation failed"`
}
type FileManagerFileAuditAttributes struct {
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerFileDetailAttributes struct {
Name string `json:"name" example:"policy.pdf"`
NameNormalized string `json:"name_normalized,omitempty" example:"policy.pdf"`
Extension string `json:"extension,omitempty" example:"pdf"`
SizeBytes int64 `json:"size_bytes" example:"1024"`
MimeType string `json:"mime_type" example:"application/pdf"`
IsTemplate bool `json:"is_template" example:"true"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
PreviewURL string `json:"preview_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.preview.pdf?X-Amz-Signature=..."`
Status string `json:"status" example:"ready"`
Location *FileManagerFileLocationAttributes `json:"location,omitempty"`
Processing *FileManagerFileProcessingAttributes `json:"processing,omitempty"`
Audit FileManagerFileAuditAttributes `json:"audit"`
}
type FileManagerFileDownloadAttributes struct {
URL string `json:"url" example:"https://s3.localhost.localstack.cloud:4566/..."`
ExpiresInSecond int64 `json:"expires_in_second" example:"900"`
}
type FileManagerFileUploadIntentAttributes struct {
UploadIntentID string `json:"upload_intent_uuid" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UploadURL string `json:"upload_url" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/..."`
Method string `json:"method" example:"PUT"`
RequiredHeaders map[string]string `json:"required_headers,omitempty"`
Object FileUploadObject `json:"object"`
File FileUploadFile `json:"file"`
ExpiresAt string `json:"expires_at" example:"2026-04-05T07:29:34Z"`
ExpiresInSeconds int64 `json:"expires_in_seconds" example:"900"`
}
type FileUploadObject struct {
Bucket string `json:"bucket" example:"wucher-file-dev"`
Key string `json:"key" example:"files/2026/04/05/018f3a5c7a6f7c2a8d4a4b5b1f6c8e9d.pdf"`
}
type FileUploadFile struct {
Name string `json:"name" example:"policy-final.pdf"`
MimeType string `json:"mime_type" example:"application/pdf"`
SizeBytes int64 `json:"size_bytes" example:"1024"`
}

View File

@@ -0,0 +1,36 @@
package shared
type FileManagerFolderAttributes struct {
ParentID string `json:"parent_id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" example:"operations"`
NameNormalized string `json:"name_normalized,omitempty" example:"operations"`
Depth int `json:"depth" example:"1"`
PathCache string `json:"path_cache,omitempty" example:"/root/operations"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
PurgeAt string `json:"purge_at,omitempty" example:"2026-04-12T03:04:05Z"`
}
type FileManagerFolderLocationAttributes struct {
ParentID string `json:"parent_id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerFolderAuditAttributes struct {
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type FileManagerFolderDetailAttributes struct {
Name string `json:"name" example:"operations"`
NameNormalized string `json:"name_normalized,omitempty" example:"operations"`
Depth *int `json:"depth,omitempty" example:"1"`
PathCache string `json:"path_cache,omitempty" example:"/root/operations"`
Location *FileManagerFolderLocationAttributes `json:"location,omitempty"`
Audit FileManagerFolderAuditAttributes `json:"audit"`
}

View File

@@ -0,0 +1,174 @@
package shared
type FleetStatusHelicopter struct {
ID string `json:"id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Designation string `json:"designation,omitempty" example:"AIRBUS H145"`
Identifier string `json:"identifier,omitempty" example:"D-HEMS"`
Type string `json:"type,omitempty" example:"EC-135"`
Status string `json:"status,omitempty" example:"available" enums:"available,booked,aog,mcf"`
// FlightID — id of the aircraft's current active flight (present only when it is in
// use / booked). The fleet page uses it to create an EASA release scoped to the flight.
FlightID string `json:"flight_id,omitempty" example:"019d6774-98e8-7fba-9ef4-3795c0da8a13"`
StatusReasons []FleetStatusHelicopterReason `json:"status_reasons,omitempty"`
}
// FleetStatusMCF is the helicopter's maintenance check flight shown on the fleet page.
// When the helicopter has no MCF yet, id is empty and sign is false. af_hours /
// landing_count are the SIGNED check-flight values — present only once the MCF is signed.
type FleetStatusMCF struct {
// Active — true when the helicopter is currently in MCF (an open draft, or a signed
// but failed MCF still needing a re-flight); false when cleared/cancelled/none.
Active bool `json:"active"`
ID string `json:"id"`
Date *string `json:"date"`
// AFHours / LandingCount present only once the MCF is signed; omitted for none/draft/cancelled.
AFHours *string `json:"af_hours,omitempty"`
LandingCount *int `json:"landing_count,omitempty"`
// Sign — true once the MCF is completed/signed; false while it is still a draft.
Sign bool `json:"sign"`
// SignedBy / SignedByName / SignedByShort — who signed (completed) the MCF; empty while unsigned.
SignedBy string `json:"signed_by,omitempty"`
SignedByName *string `json:"signed_by_name,omitempty"`
SignedByShort *string `json:"signed_by_short,omitempty"`
}
type MaintenanceScheduleAttributes struct {
InspectionType string `json:"inspection_type" example:"A/F"`
Type string `json:"type" example:"100H"`
Due string `json:"due" validate:"omitempty,hours_or_date" example:"1200"`
Ext string `json:"ext" validate:"omitempty,hours_or_date" example:"1300"`
}
type FleetStatusFileAttributes struct {
FileID string `json:"file_id" validate:"required" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Category string `json:"category" validate:"required,oneof=wb bfi" example:"wb"`
// IsMandatory — when true, this file must be checked (is_done) during takeover.
IsMandatory *bool `json:"is_mandatory,omitempty" example:"true"`
}
type FleetStatusSummaryAirframe struct {
Hours *float64 `json:"hours"`
Cycles *float64 `json:"cycles"`
}
type FleetStatusSummaryEngine struct {
Hours *float64 `json:"hours"`
GpcNgN1 *float64 `json:"gpc_ng_n1"`
PtcNfN2 *float64 `json:"ptc_nf_n2"`
Ccc *float64 `json:"ccc"`
}
type FleetStatusSummary struct {
Airframe FleetStatusSummaryAirframe `json:"airframe"`
Engine1 FleetStatusSummaryEngine `json:"engine_1"`
Engine2 FleetStatusSummaryEngine `json:"engine_2"`
Landings *float64 `json:"landings"`
FlightReports *float64 `json:"flight_reports"`
RotorBrakeCycles *float64 `json:"rotor_brake_cycles"`
HookReleases *float64 `json:"hook_releases"`
}
type FleetStatusServiceLogAttributes struct {
FleetStatusID string `json:"fleet_status_id" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Helicopter *FleetStatusHelicopter `json:"helicopter,omitempty"`
FleetStatusStatus string `json:"fleet_status_status,omitempty" example:"serviced"`
InspectionType string `json:"inspection_type" example:"A/F"`
Type string `json:"type" example:"100H"`
Due string `json:"due" example:"1200 FH"`
Ext string `json:"ext" example:"1300 FH"`
ServicedAt string `json:"serviced_at" example:"2026-05-29T07:43:06Z"`
CreatedAt string `json:"created_at" example:"2026-05-29T07:43:06Z"`
CreatedBy string `json:"created_by" example:"John Doe"`
}
const (
ReasonCodeManualAOG = "manual_aog"
ReasonCodeMELExpired = "mel_expired"
ReasonCodeNonMELPending = "non_mel_pending"
ReasonCodeInspectionOverdue = "inspection_overdue"
)
type FleetStatusComplaint struct {
ID string `json:"id"`
FlightID string `json:"flight_id,omitempty"`
Description string `json:"description"`
Status string `json:"status" enums:"active_non_mel,active_mel,serviced"`
ReportedAt string `json:"reported_at"`
ReportedBy string `json:"reported_by,omitempty"`
ReportedByName string `json:"reported_by_name,omitempty"`
ReportedByShort string `json:"reported_by_short,omitempty"`
AircraftHoursAtReport *string `json:"aircraft_hours_at_report,omitempty"`
MEL FleetStatusComplaintMEL `json:"mel"`
NSR FleetStatusComplaintNSR `json:"nsr"`
Action FleetStatusComplaintAction `json:"action"`
}
type FleetStatusComplaintMEL struct {
Severity int8 `json:"severity" enums:"0,1,2,3,4"`
SeverityLabel *string `json:"severity_label"`
ClassifiedAt *string `json:"classified_at,omitempty"`
ClassifiedBy string `json:"classified_by,omitempty"`
ClassifiedByName string `json:"classified_by_name,omitempty"`
ClassifiedByShort string `json:"classified_by_short,omitempty"`
Deadline *string `json:"deadline,omitempty"`
}
type FleetStatusComplaintNSR struct {
IsNSR bool `json:"is_nsr"`
DecidedAt *string `json:"decided_at,omitempty"`
DecidedBy string `json:"decided_by,omitempty"`
DecidedByName string `json:"decided_by_name,omitempty"`
DecidedByShort string `json:"decided_by_short,omitempty"`
Reason *string `json:"reason,omitempty"`
}
type FleetStatusComplaintAction struct {
Taken *string `json:"taken"`
SignedAt *string `json:"signed_at,omitempty"`
SignedBy string `json:"signed_by,omitempty"`
SignedByName string `json:"signed_by_name,omitempty"`
SignedByShort string `json:"signed_by_short,omitempty"`
AircraftHoursAtFix *string `json:"aircraft_hours_at_fix,omitempty"`
}
type FleetStatusEASARelease struct {
ID string `json:"id"`
IsSigned bool `json:"is_signed"`
EASADate *string `json:"easa_date,omitempty"`
EASAACHours *string `json:"easa_ac_hours,omitempty"`
EASALocation *string `json:"easa_location,omitempty"`
EASAWONo *string `json:"easa_wo_no,omitempty"`
EASAMaintManualRevAirframe *string `json:"easa_maint_manual_rev_airframe,omitempty"`
EASAMaintManualRevEngine *string `json:"easa_maint_manual_rev_engine,omitempty"`
EASASignerID *string `json:"easa_signer_id,omitempty"`
EASAPermitNo *string `json:"easa_permit_no,omitempty"`
SignedAt *string `json:"signed_at,omitempty"`
SignedBy string `json:"signed_by,omitempty"`
SignedByName string `json:"signed_by_name,omitempty"`
SignedByShort string `json:"signed_by_short,omitempty"`
MissingSignFields []string `json:"missing_sign_fields,omitempty"`
}
func MELLabel(severity int8) string {
switch severity {
case 1:
return "A"
case 2:
return "B"
case 3:
return "C"
case 4:
return "D"
default:
return ""
}
}
type FleetStatusHelicopterReason struct {
Code string `json:"code" enums:"manual_aog,mel_expired,non_mel_pending,inspection_overdue"`
Detail string `json:"detail"`
Since *string `json:"since,omitempty"`
ComplaintID string `json:"complaint_id,omitempty"`
ScheduleID string `json:"schedule_id,omitempty"`
}

View File

@@ -0,0 +1,11 @@
package shared
type FlightInspectionFileChecklistAttributes struct {
IsDone bool `json:"is_done" example:"false"`
HelicopterFileID string `json:"helicopter_file_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
FlightInspectionID string `json:"flight_inspection_id" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-14T08:00:00Z"`
CreatedBy string `json:"created_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-14T08:00:00Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
}

View File

@@ -0,0 +1,18 @@
package shared
type HelicopterFileAttributes struct {
Section string `json:"section" example:"bfi"`
Position int `json:"position" example:"1"`
IsMandatory bool `json:"is_mandatory" example:"true"`
IsTemplate bool `json:"is_template" example:"true"`
HelicopterID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
HelicopterName string `json:"helicopter_name,omitempty" example:"AIRBUS H145"`
FileAttachmentID string `json:"file_attachment_id" example:"019d6771-6c8e-7a2d-a7b8-03bcb65fcb3e"`
AttachmentPending bool `json:"attachment_pending" example:"true"`
FileName string `json:"file_name,omitempty" example:"Before Flight Checklist.pdf"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-14T08:00:00Z"`
CreatedBy string `json:"created_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-14T08:00:00Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
}

View File

@@ -0,0 +1,45 @@
package shared
// Helicopter nested value objects reused as building blocks by helicopter
// response DTOs (and available to any other module that embeds helicopter
// summaries).
type HelicopterLastDailyInspection struct {
InspectionID string `json:"inspection_id,omitempty" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
Status string `json:"status" example:"inspected_cleared"`
Inspector HelicopterLastDailyInspector `json:"inspector"`
Base HelicopterLastDailyBase `json:"base"`
InspectedAt string `json:"inspected_at,omitempty" example:"2026-05-25T07:47:00Z"`
UTCTime string `json:"utc_time,omitempty" example:"07:47"`
InspectionFilesChecklist HelicopterInspectionFilesChecklist `json:"inspection_files_checklist"`
}
type HelicopterLastDailyInspector struct {
ID string `json:"id,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
Name string `json:"name,omitempty" example:"Teodora Lesmana"`
LicenseNo string `json:"license_no,omitempty" example:"4521"`
}
type HelicopterLastDailyBase struct {
ID string `json:"id,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
BaseAbbreviation string `json:"base_abbreviation,omitempty" example:"G1"`
BaseName string `json:"base_name,omitempty" example:"Gallus 1 - Zurs Lech"`
Type string `json:"type,omitempty" example:"hems"`
}
type HelicopterInspectionFilesChecklist struct {
Completed int `json:"completed" example:"47"`
Total int `json:"total" example:"47"`
}
type HelicopterFoto struct {
UUID string `json:"uuid" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DownloadURL string `json:"download_url" example:"https://storage.example.com/helicopter/foto.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=abc123"`
ThumbnailDownloadURL string `json:"thumbnail_download_url,omitempty" example:"https://storage.example.com/helicopter/foto.thumb.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=abc123"`
OriginalSizeBytes *int64 `json:"original_size_bytes,omitempty" example:"1024000"`
}
type HelicopterAuditUser struct {
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Name string `json:"name" example:"John Doe"`
}

View File

@@ -0,0 +1,120 @@
package shared
import "time"
type ReserveAcAttributes struct {
Status string `json:"status" example:"pending"`
AircraftID string `json:"helicopter_id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Aircraft *ReserveAcAircraftSummary `json:"helicopter,omitempty"`
LastDaily *ReserveAcLastDailyInfo `json:"last_daily_inspection,omitempty"`
FlightID string `json:"flight_id,omitempty" example:"019d6773-ccca-71dd-b9c7-8e6f531deb90"`
Inspection *ReserveAcInspectionSummary `json:"inspection,omitempty"`
CreatedAt string `json:"created_at,omitempty" example:"2026-04-14T08:00:00Z"`
CreatedBy string `json:"created_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-04-14T08:00:00Z"`
UpdatedBy string `json:"updated_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-04-14T08:00:00Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"019d61db-53c6-7280-87ec-e65f205c6d3b"`
}
type ReserveAcLastDailyInfo struct {
Name string `json:"name" example:"Ganahl Stefan"`
ShortName string `json:"short_name" example:"GAN"`
Date string `json:"date" example:"17.04.2026"`
UTCTime string `json:"utc_time" example:"10:57"`
}
type ReserveAcAircraftSummary struct {
ID string `json:"id" example:"019d6771-5fb5-7337-837f-bc5ed85181a1"`
Designation string `json:"designation" example:"AIRBUS H145"`
Identifier string `json:"identifier" example:"D-HYAR"`
Type string `json:"type" example:"H145"`
AOG bool `json:"aog" example:"false"`
IsActive bool `json:"is_active" example:"true"`
}
type ReserveAcInspectionSummary struct {
ID string `json:"id" example:"019d6772-471c-7b70-b42a-d94020ef61e4"`
InspectionDate string `json:"inspection_date,omitempty" example:"2026-04-15"`
Before ReserveAcBeforeSectionDTO `json:"before"`
Prepare ReserveAcPrepareSectionDTO `json:"prepare"`
FleetStatusFile ReserveAcFleetStatusFileSectionDTO `json:"fleet_status_file"`
}
type ReserveAcBeforeSectionDTO struct {
Total int `json:"total" example:"3"`
Completed int `json:"completed" example:"1"`
DoneFiles []ReserveAcFileChecklistItemDTO `json:"done_file_checklist,omitempty"`
Fields *ReserveAcBeforeInspectionFields `json:"fields,omitempty"`
FileChecklist []ReserveAcFileChecklistItemDTO `json:"file_checklist"`
}
type ReserveAcPrepareSectionDTO struct {
Total int `json:"total" example:"3"`
Completed int `json:"completed" example:"1"`
DoneFiles []ReserveAcFileChecklistItemDTO `json:"done_file_checklist,omitempty"`
PrepareFields *ReserveAcPrepareFields `json:"prepare_fields,omitempty"`
FileChecklist []ReserveAcFileChecklistItemDTO `json:"file_checklist"`
}
type ReserveAcFleetStatusFileSectionDTO struct {
Total int `json:"total" example:"2"`
Completed int `json:"completed" example:"1"`
DoneFiles []ReserveAcFileChecklistItemDTO `json:"done_file_checklist,omitempty"`
FileChecklist []ReserveAcFileChecklistItemDTO `json:"file_checklist"`
}
type ReserveAcFileChecklistItemDTO struct {
HelicopterFileID string `json:"helicopter_file_id" example:"019d7000-aaaa-7bbb-8ccc-111111111111"`
FileAttachmentID string `json:"file_attachment_id,omitempty" example:"019d7000-aaaa-7bbb-8ccc-222222222222"`
FileName string `json:"file_name,omitempty" example:"Before Flight Checklist.pdf"`
DownloadURL string `json:"download_url,omitempty" example:"https://s3.localhost.localstack.cloud:4566/wucher-file-dev/files/2026/04/05/policy.pdf?X-Amz-Signature=..."`
IsDone bool `json:"is_done" example:"false"`
IsMandatory bool `json:"is_mandatory" example:"true"`
}
type ReserveAcBeforeInspectionFields struct {
OilEngineNR1Checked *bool `json:"oil_engine_nr1_checked,omitempty"`
OilEngineNR2Checked *bool `json:"oil_engine_nr2_checked,omitempty"`
HydraulicLHChecked *bool `json:"hydraulic_lh_checked,omitempty"`
HydraulicRHChecked *bool `json:"hydraulic_rh_checked,omitempty"`
OilTransmissionMGBChecked *bool `json:"oil_transmission_mgb_checked,omitempty"`
OilTransmissionIGBChecked *bool `json:"oil_transmission_igb_checked,omitempty"`
OilTransmissionTGBChecked *bool `json:"oil_transmission_tgb_checked,omitempty"`
FuelAmount *float64 `json:"fuel_amount,omitempty"`
FuelUnit string `json:"fuel_unit,omitempty"`
FuelAddedAmount *float64 `json:"fuel_added_amount,omitempty"`
Note *string `json:"note,omitempty"`
}
type ReserveAcPrepareFields struct {
NotamBriefing *bool `json:"notam_briefing,omitempty"`
WeatherBriefing *bool `json:"weather_briefing,omitempty"`
OperationalFlightPlan *bool `json:"operational_flight_plan,omitempty"`
}
type LastDailyInspection struct {
ID []byte
Name string
ShortName string
At time.Time
}
type LastDailyInspectionSummary struct {
InspectionID string
Status string
InspectorID string
InspectorName string
InspectorLicenseNo string
BaseID string
BaseName string
BaseAbbreviation string
BaseType string
InspectedAt time.Time
InspectionFilesChecklistCompleted int
InspectionFilesChecklistTotal int
}

View File

@@ -0,0 +1,103 @@
package dto
import (
"bytes"
"encoding/json"
)
// JSON:API User DTOs
type UserResource struct {
Type string `json:"type" example:"user"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes UserAttributes `json:"attributes"`
}
type UserListResponse struct {
Data []UserResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type UserDataTableResponse struct {
Data []UserResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
// Create
type UserCreateAttributes struct {
Email string `json:"email" validate:"required,email" example:"user@example.com"`
Username string `json:"username" validate:"required,min=3,max=100" example:"john.doe"`
FirstName string `json:"first_name" validate:"required" example:"John"`
LastName string `json:"last_name" validate:"required" example:"Doe"`
MobilePhone string `json:"mobile_phone,omitempty" example:"+628123456789"`
Timezone *string `json:"timezone,omitempty" example:"Asia/Jakarta"`
ImageUUID *string `json:"image_uuid,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e90"`
RoleID string `json:"role_id" validate:"required" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
RoleIDs []string `json:"role_ids,omitempty" example:"[\"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d\",\"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9e\"]"`
}
type UserCreateData struct {
Type string `json:"type" validate:"required,oneof=user_create user" example:"user_create"`
Attributes UserCreateAttributes `json:"attributes" validate:"required"`
}
type UserCreateRequest struct {
Data UserCreateData `json:"data" validate:"required"`
}
// Update
type UserUpdateAttributes struct {
Email *string `json:"email,omitempty" validate:"omitempty,email" example:"user@example.com"`
FirstName *string `json:"first_name,omitempty" example:"John"`
LastName *string `json:"last_name,omitempty" example:"Doe"`
MobilePhone *string `json:"mobile_phone,omitempty" example:"+628123456789"`
Timezone *string `json:"timezone,omitempty" example:"Asia/Jakarta"`
ImageUUID NullString `json:"image_uuid,omitempty" swaggertype:"string" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e90"`
}
// NullString allows PATCH semantics:
// - field omitted => Set=false (no change)
// - value null => Set=true, Valid=false (clear value)
// - value string => Set=true, Valid=true, Value=<string>
type NullString struct {
Set bool
Valid bool
Value string
}
func (n *NullString) UnmarshalJSON(data []byte) error {
n.Set = true
trimmed := bytes.TrimSpace(data)
if bytes.Equal(trimmed, []byte("null")) {
n.Valid = false
n.Value = ""
return nil
}
var v string
if err := json.Unmarshal(trimmed, &v); err != nil {
return err
}
n.Valid = true
n.Value = v
return nil
}
type UserUpdateData struct {
Type string `json:"type" validate:"required,oneof=user_update user" example:"user_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes UserUpdateAttributes `json:"attributes" validate:"required"`
}
type UserUpdateRequest struct {
Data UserUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,77 @@
package dto
// JSON:API Vocation DTOs
type VocationAttributes struct {
Name string `json:"name" example:"Pilot"`
SortKey *int `json:"sortkey" example:"1"`
Note string `json:"note,omitempty" example:"Helicopter pilot vocation"`
IsActive bool `json:"is_active" example:"true"`
CreatedAt string `json:"created_at,omitempty" example:"2026-03-12T03:04:05Z"`
CreatedBy string `json:"created_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
UpdatedAt string `json:"updated_at,omitempty" example:"2026-03-12T03:04:05Z"`
UpdatedBy string `json:"updated_by" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
DeletedAt string `json:"deleted_at,omitempty" example:"2026-03-12T03:04:05Z"`
DeletedBy string `json:"deleted_by,omitempty" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
}
type VocationResource struct {
Type string `json:"type" example:"vocation"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes VocationAttributes `json:"attributes"`
}
type VocationResponse struct {
Data VocationResource `json:"data"`
}
type VocationListResponse struct {
Data []VocationResource `json:"data"`
Meta struct {
PageNumber int `json:"page_number" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"120"`
} `json:"meta"`
}
type VocationDataTableResponse struct {
Data []VocationResource `json:"data"`
Meta struct {
Draw int `json:"draw" example:"1"`
RecordsTotal int64 `json:"records_total" example:"120"`
RecordsFiltered int64 `json:"records_filtered" example:"12"`
} `json:"meta"`
}
type VocationCreateAttributes struct {
Name string `json:"name" validate:"required" example:"Pilot"`
SortKey *int `json:"sortkey" example:"1"`
Note *string `json:"note,omitempty" example:"Helicopter pilot vocation"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type VocationCreateData struct {
Type string `json:"type" validate:"required,oneof=vocation_create vocation" example:"vocation_create"`
Attributes VocationCreateAttributes `json:"attributes" validate:"required"`
}
type VocationCreateRequest struct {
Data VocationCreateData `json:"data" validate:"required"`
}
type VocationUpdateAttributes struct {
Name *string `json:"name,omitempty" example:"Pilot"`
SortKey NullInt `json:"sortkey,omitempty" swaggertype:"integer" example:"1"`
Note *string `json:"note,omitempty" example:"Helicopter pilot vocation"`
IsActive *bool `json:"is_active,omitempty" example:"true"`
}
type VocationUpdateData struct {
Type string `json:"type" validate:"required,oneof=vocation_update vocation" example:"vocation_update"`
ID string `json:"id" example:"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"`
Attributes VocationUpdateAttributes `json:"attributes" validate:"required"`
}
type VocationUpdateRequest struct {
Data VocationUpdateData `json:"data" validate:"required"`
}

View File

@@ -0,0 +1,256 @@
package handlers
import (
"context"
"errors"
"strings"
"time"
"github.com/gofiber/fiber/v2"
actionsignoff "wucher/internal/domain/action_signoff"
complaint "wucher/internal/domain/complaint"
"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"
)
// ActionSignoffHandler serves the corrective-action sign-off (sign/unsign) for a
// helicopter — both complaint-linked and complaint-independent.
type ActionSignoffHandler struct {
svc actionsignoff.Service
complaintSvc actionSignoffComplaintLookup
}
type actionSignoffComplaintLookup interface {
GetByID(ctx context.Context, id []byte) (*complaint.Complaint, error)
}
func NewActionSignoffHandler(svc actionsignoff.Service) *ActionSignoffHandler {
return &ActionSignoffHandler{svc: svc}
}
// WithComplaintService wires the complaint lookup used to validate a complaint-linked
// sign-off (complaint exists and has action_taken recorded).
func (h *ActionSignoffHandler) WithComplaintService(svc actionSignoffComplaintLookup) *ActionSignoffHandler {
h.complaintSvc = svc
return h
}
func (h *ActionSignoffHandler) resource(ctx context.Context, row *actionsignoff.ActionSignoff) responsedto.ActionSignoffResource {
if row == nil {
return responsedto.ActionSignoffResource{}
}
id, _ := uuidv7.BytesToString(row.ID)
out := responsedto.ActionSignoffResource{
Type: "action_signoff",
ID: id,
Attributes: responsedto.ActionSignoffAttributes{
HelicopterID: idString(row.HelicopterID),
FlightID: idString(row.FlightID),
Signed: row.IsSigned(),
SignedBy: idString(row.SignedBy),
},
}
if row.SignedAt != nil {
v := row.SignedAt.UTC().Format(time.RFC3339)
out.Attributes.SignedAt = &v
}
if n := userctx.GetDisplayName(ctx, row.SignedBy); n != "" {
out.Attributes.SignedByName = &n
}
if s := userctx.GetShortName(ctx, row.SignedBy); s != "" {
out.Attributes.SignedByShort = &s
}
if !row.CreatedAt.IsZero() {
out.Attributes.CreatedAt = row.CreatedAt.UTC().Format(time.RFC3339)
}
if !row.UpdatedAt.IsZero() {
out.Attributes.UpdatedAt = row.UpdatedAt.UTC().Format(time.RFC3339)
}
return out
}
func actionSignoffHelicopterID(c *fiber.Ctx) ([]byte, *apperrorsx.AppError) {
id, err := uuidv7.ParseString(strings.TrimSpace(c.Params("helicopter_id")))
if err != nil {
appErr := apperrorsx.New(apperrorsx.ErrActionSignoffInvalidUUID).WithCause(err)
appErr.Source = &jsonapi.ErrorSource{Pointer: "/path/helicopter_id"}
return nil, appErr
}
return id, nil
}
func actionSignoffFlightID(c *fiber.Ctx) ([]byte, *apperrorsx.AppError) {
id, err := uuidv7.ParseString(strings.TrimSpace(c.Params("flight_id")))
if err != nil {
appErr := apperrorsx.New(apperrorsx.ErrActionSignoffInvalidUUID).WithCause(err)
appErr.Source = &jsonapi.ErrorSource{Pointer: "/path/flight_id"}
return nil, appErr
}
return id, nil
}
// actionSignoffBodyFlightID parses the REQUIRED flight_id from the sign body — the
// flight the sign-off is attributed to (standalone) or performed in (complaint-linked).
func actionSignoffBodyFlightID(req requestdto.ActionSignoffSignRequest) ([]byte, *apperrorsx.AppError) {
raw := ""
if req.Data.Attributes.FlightID != nil {
raw = strings.TrimSpace(*req.Data.Attributes.FlightID)
}
if raw == "" {
appErr := apperrorsx.New(apperrorsx.ErrActionSignoffInvalidUUID)
appErr.Source = &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_id"}
return nil, appErr
}
id, err := uuidv7.ParseString(raw)
if err != nil {
appErr := apperrorsx.New(apperrorsx.ErrActionSignoffInvalidUUID).WithCause(err)
appErr.Source = &jsonapi.ErrorSource{Pointer: "/data/attributes/flight_id"}
return nil, appErr
}
return id, nil
}
// GetByHelicopter godoc
// @Summary Get the current corrective-action sign-off for a helicopter
// @Description Returns the fleet-level (complaint-independent) corrective-action sign-off state for a helicopter — `signed`, `signed_at`, `signed_by`. When none exists yet it returns an unsigned placeholder. A signed sign-off is what gates the EASA maintenance release.
// @Tags Complaints - Sign Off
// @Produce json
// @Param flight_id path string true "Flight ID (UUIDv7)"
// @Success 200 {object} responsedto.ActionSignoffResponse
// @Router /api/v1/action-signoffs/get-by-flight/{flight_id} [get]
func (h *ActionSignoffHandler) GetByFlight(c *fiber.Ctx) error {
flightID, aerr := actionSignoffFlightID(c)
if aerr != nil {
return apperrorsx.Write(c, aerr)
}
row, err := h.svc.GetByFlight(c.UserContext(), flightID)
if err != nil {
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrActionSignoffNotFound).WithCause(err))
}
if row == nil {
// No row yet: return an unsigned placeholder so the client can render.
return response.Write(c, fiber.StatusOK, responsedto.ActionSignoffResource{
Type: "action_signoff",
Attributes: responsedto.ActionSignoffAttributes{
FlightID: idString(flightID),
Signed: false,
},
})
}
return response.Write(c, fiber.StatusOK, h.resource(c.UserContext(), row))
}
// Sign godoc
// @Summary Toggle the corrective-action sign-off (per complaint or fleet-wide)
// @Description Toggles the corrective-action sign-off for a helicopter. `sign` (optional, default true) chooses the direction: true signs, false unsigns. Send `complaint_id` to target a specific complaint's sign-off — signing it requires `action_taken` recorded (else 422 `ACTION_SIGNOFF_ACTION_REQUIRED`), and 404 `ACTION_SIGNOFF_COMPLAINT_NOT_FOUND` if the complaint does not exist. Omit `complaint_id` for a fleet-wide (complaint-independent) sign-off. A signed sign-off is what allows an EASA maintenance release to be created for the helicopter.
// @Tags Complaints - Sign Off
// @Accept json
// @Produce json
// @Param helicopter_id path string true "Helicopter ID (UUIDv7)"
// @Param request body requestdto.ActionSignoffSignRequest false "Optional complaint_id + note"
// @Success 200 {object} responsedto.ActionSignoffResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/action-signoffs/sign/{helicopter_id} [post]
func (h *ActionSignoffHandler) Sign(c *fiber.Ctx) error {
helicopterID, err := actionSignoffHelicopterID(c)
if err != nil {
return apperrorsx.Write(c, err)
}
// Body is optional — the sign-off can be checked with nothing filled.
var req requestdto.ActionSignoffSignRequest
_ = c.BodyParser(&req)
actor := actorUserID(c)
// sign toggles the state: true (or omitted) signs, false unsigns.
signWanted := req.Data.Attributes.Sign == nil || *req.Data.Attributes.Sign
complaintIDRaw := ""
if req.Data.Attributes.ComplaintID != nil {
complaintIDRaw = strings.TrimSpace(*req.Data.Attributes.ComplaintID)
}
// Complaint-linked toggle: sign/unsign that complaint's corrective action.
if complaintIDRaw != "" {
complaintID, perr := uuidv7.ParseString(complaintIDRaw)
if perr != nil {
appErr := apperrorsx.New(apperrorsx.ErrActionSignoffInvalidUUID).WithCause(perr)
appErr.Source = &jsonapi.ErrorSource{Pointer: "/data/attributes/complaint_id"}
return apperrorsx.Write(c, appErr)
}
if !signWanted {
row, serr := h.svc.UnsignByComplaint(c.UserContext(), complaintID)
if serr != nil {
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrActionSignoffUnsignFailed).WithCause(serr))
}
if row == nil {
return response.Write(c, fiber.StatusOK, unsignedActionSignoffPlaceholder(helicopterID))
}
return response.Write(c, fiber.StatusOK, h.resource(c.UserContext(), row))
}
if h.complaintSvc == nil {
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrActionSignoffSignFailed))
}
comp, cerr := h.complaintSvc.GetByID(c.UserContext(), complaintID)
if cerr != nil {
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrActionSignoffSignFailed).WithCause(cerr))
}
if comp == nil {
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrActionSignoffComplaintNotFound))
}
// The complaint's corrective action must be recorded before it can be signed off.
if comp.ActionTaken == nil || strings.TrimSpace(*comp.ActionTaken) == "" {
appErr := apperrorsx.New(apperrorsx.ErrActionSignoffActionRequired)
appErr.Source = &jsonapi.ErrorSource{Pointer: "/data/attributes/complaint_id"}
return apperrorsx.Write(c, appErr)
}
flightID, ferr := actionSignoffBodyFlightID(req)
if ferr != nil {
return apperrorsx.Write(c, ferr)
}
row, serr := h.svc.SignForComplaint(c.UserContext(), complaintID, flightID, comp.HelicopterID, actor)
if serr != nil {
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrActionSignoffSignFailed).WithCause(serr))
}
return response.Write(c, fiber.StatusOK, h.resource(c.UserContext(), row))
}
// Complaint-independent (fleet) toggle — scoped to a flight.
flightID, ferr := actionSignoffBodyFlightID(req)
if ferr != nil {
return apperrorsx.Write(c, ferr)
}
if !signWanted {
row, serr := h.svc.Unsign(c.UserContext(), flightID, actor)
if errors.Is(serr, actionsignoff.ErrNotFound) {
return response.Write(c, fiber.StatusOK, unsignedActionSignoffPlaceholder(helicopterID))
}
if serr != nil {
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrActionSignoffUnsignFailed).WithCause(serr))
}
return response.Write(c, fiber.StatusOK, h.resource(c.UserContext(), row))
}
// Signing a fleet sign-off — this is what allows an EASA release for the flight.
row, serr := h.svc.Sign(c.UserContext(), flightID, helicopterID, actor)
if serr != nil {
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrActionSignoffSignFailed).WithCause(serr))
}
return response.Write(c, fiber.StatusOK, h.resource(c.UserContext(), row))
}
// unsignedActionSignoffPlaceholder renders an unsigned resource for a helicopter that
// has no sign-off row (e.g. unsigning something that was never signed).
func unsignedActionSignoffPlaceholder(helicopterID []byte) responsedto.ActionSignoffResource {
return responsedto.ActionSignoffResource{
Type: "action_signoff",
Attributes: responsedto.ActionSignoffAttributes{
HelicopterID: idString(helicopterID),
Signed: false,
},
}
}

View File

@@ -0,0 +1,367 @@
package handlers
import (
"context"
"encoding/json"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/gofiber/fiber/v2"
actionsignoff "wucher/internal/domain/action_signoff"
complaint "wucher/internal/domain/complaint"
"wucher/internal/shared/pkg/uuidv7"
)
type actionSignoffComplaintMock struct {
getByIDFn func(context.Context, []byte) (*complaint.Complaint, error)
}
func (m *actionSignoffComplaintMock) GetByID(ctx context.Context, id []byte) (*complaint.Complaint, error) {
if m.getByIDFn != nil {
return m.getByIDFn(ctx, id)
}
return nil, nil
}
type actionSignoffServiceMock struct {
getByHelicopterFn func(context.Context, []byte) (*actionsignoff.ActionSignoff, error)
getByComplaintFn func(context.Context, []byte) (*actionsignoff.ActionSignoff, error)
getByComplaintIDsFn func(context.Context, [][]byte) (map[string]*actionsignoff.ActionSignoff, error)
signFn func(context.Context, []byte, []byte) (*actionsignoff.ActionSignoff, error)
signForComplaintFn func(context.Context, []byte, []byte, []byte) (*actionsignoff.ActionSignoff, error)
unsignFn func(context.Context, []byte, []byte) (*actionsignoff.ActionSignoff, error)
unsignByComplaintFn func(context.Context, []byte) (*actionsignoff.ActionSignoff, error)
}
func (m *actionSignoffServiceMock) GetByFlight(ctx context.Context, flightID []byte) (*actionsignoff.ActionSignoff, error) {
if m.getByHelicopterFn != nil {
return m.getByHelicopterFn(ctx, flightID)
}
return nil, nil
}
func (m *actionSignoffServiceMock) GetByComplaint(ctx context.Context, complaintID []byte) (*actionsignoff.ActionSignoff, error) {
if m.getByComplaintFn != nil {
return m.getByComplaintFn(ctx, complaintID)
}
return nil, nil
}
func (m *actionSignoffServiceMock) GetByComplaintIDs(ctx context.Context, complaintIDs [][]byte) (map[string]*actionsignoff.ActionSignoff, error) {
if m.getByComplaintIDsFn != nil {
return m.getByComplaintIDsFn(ctx, complaintIDs)
}
return map[string]*actionsignoff.ActionSignoff{}, nil
}
func (m *actionSignoffServiceMock) SignForComplaint(ctx context.Context, complaintID, _ []byte, helicopterID []byte, signer []byte) (*actionsignoff.ActionSignoff, error) {
if m.signForComplaintFn != nil {
return m.signForComplaintFn(ctx, complaintID, helicopterID, signer)
}
return nil, nil
}
func (m *actionSignoffServiceMock) Sign(ctx context.Context, _ []byte, helicopterID []byte, signer []byte) (*actionsignoff.ActionSignoff, error) {
if m.signFn != nil {
return m.signFn(ctx, helicopterID, signer)
}
return nil, nil
}
func (m *actionSignoffServiceMock) Unsign(ctx context.Context, helicopterID []byte, actor []byte) (*actionsignoff.ActionSignoff, error) {
if m.unsignFn != nil {
return m.unsignFn(ctx, helicopterID, actor)
}
return nil, nil
}
func (m *actionSignoffServiceMock) UnsignByComplaint(ctx context.Context, complaintID []byte) (*actionsignoff.ActionSignoff, error) {
if m.unsignByComplaintFn != nil {
return m.unsignByComplaintFn(ctx, complaintID)
}
return nil, nil
}
func newActionSignoffApp(h *ActionSignoffHandler) *fiber.App {
app := fiber.New()
app.Get("/api/v1/action-signoffs/get-by-flight/:flight_id", h.GetByFlight)
app.Post("/api/v1/action-signoffs/sign/:helicopter_id", h.Sign)
return app
}
func TestActionSignoffSign_ComplaintLinked_RequiresActionTaken(t *testing.T) {
complaintID := uuidv7.MustBytes()
complaintIDStr, _ := uuidv7.BytesToString(complaintID)
heliID := uuidv7.MustBytes()
heliStr, _ := uuidv7.BytesToString(heliID)
svc := &actionSignoffServiceMock{
signForComplaintFn: func(context.Context, []byte, []byte, []byte) (*actionsignoff.ActionSignoff, error) {
t.Fatal("must not sign a complaint whose action_taken is empty")
return nil, nil
},
}
comp := &actionSignoffComplaintMock{
getByIDFn: func(context.Context, []byte) (*complaint.Complaint, error) {
// Complaint exists but action_taken not recorded yet.
return &complaint.Complaint{ID: complaintID, HelicopterID: heliID}, nil
},
}
app := newActionSignoffApp(NewActionSignoffHandler(svc).WithComplaintService(comp))
body := `{"data":{"attributes":{"complaint_id":"` + complaintIDStr + `"}}}`
req, _ := http.NewRequest(http.MethodPost, "/api/v1/action-signoffs/sign/"+heliStr, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
bodyBytes, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d body=%s", resp.StatusCode, string(bodyBytes))
}
var payload map[string]any
_ = json.Unmarshal(bodyBytes, &payload)
errs, _ := payload["errors"].([]any)
if len(errs) == 0 || errs[0].(map[string]any)["code"] != "ACTION_SIGNOFF_ACTION_REQUIRED" {
t.Fatalf("expected ACTION_SIGNOFF_ACTION_REQUIRED, got %s", string(bodyBytes))
}
}
func TestActionSignoffSign_ComplaintLinked_Signs(t *testing.T) {
complaintID := uuidv7.MustBytes()
complaintIDStr, _ := uuidv7.BytesToString(complaintID)
heliID := uuidv7.MustBytes()
heliStr, _ := uuidv7.BytesToString(heliID)
action := "tightened mount"
var signedComplaint []byte
svc := &actionSignoffServiceMock{
signForComplaintFn: func(_ context.Context, cid, hid []byte, _ []byte) (*actionsignoff.ActionSignoff, error) {
signedComplaint = cid
if string(hid) != string(heliID) {
t.Fatalf("unexpected helicopter id: %x", hid)
}
now := time.Now().UTC()
return &actionsignoff.ActionSignoff{HelicopterID: hid, ComplaintID: cid, SignedAt: &now}, nil
},
}
comp := &actionSignoffComplaintMock{
getByIDFn: func(context.Context, []byte) (*complaint.Complaint, error) {
return &complaint.Complaint{ID: complaintID, HelicopterID: heliID, ActionTaken: &action}, nil
},
}
app := newActionSignoffApp(NewActionSignoffHandler(svc).WithComplaintService(comp))
flightStr, _ := uuidv7.BytesToString(uuidv7.MustBytes())
body := `{"data":{"attributes":{"complaint_id":"` + complaintIDStr + `","flight_id":"` + flightStr + `"}}}`
req, _ := http.NewRequest(http.MethodPost, "/api/v1/action-signoffs/sign/"+heliStr, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
t.Fatalf("expected 200, got %d body=%s", resp.StatusCode, string(bodyBytes))
}
if string(signedComplaint) != string(complaintID) {
t.Fatalf("expected SignForComplaint with complaint id, got %x", signedComplaint)
}
}
func TestActionSignoffSign_Standalone_NoComplaint(t *testing.T) {
heliID := uuidv7.MustBytes()
heliStr, _ := uuidv7.BytesToString(heliID)
var standaloneSigned bool
svc := &actionSignoffServiceMock{
signFn: func(_ context.Context, hid []byte, _ []byte) (*actionsignoff.ActionSignoff, error) {
standaloneSigned = true
now := time.Now().UTC()
return &actionsignoff.ActionSignoff{HelicopterID: hid, SignedAt: &now}, nil
},
signForComplaintFn: func(context.Context, []byte, []byte, []byte) (*actionsignoff.ActionSignoff, error) {
t.Fatal("standalone sign must not call SignForComplaint")
return nil, nil
},
}
app := newActionSignoffApp(NewActionSignoffHandler(svc))
// No complaint_id → standalone fleet sign-off (scoped to a flight).
flightStr, _ := uuidv7.BytesToString(uuidv7.MustBytes())
body := `{"data":{"attributes":{"flight_id":"` + flightStr + `"}}}`
req, _ := http.NewRequest(http.MethodPost, "/api/v1/action-signoffs/sign/"+heliStr, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK || !standaloneSigned {
t.Fatalf("expected standalone sign, status=%d signed=%v", resp.StatusCode, standaloneSigned)
}
}
func TestActionSignoffSign_ComplaintLinked_Unsign(t *testing.T) {
complaintID := uuidv7.MustBytes()
complaintIDStr, _ := uuidv7.BytesToString(complaintID)
heliID := uuidv7.MustBytes()
heliStr, _ := uuidv7.BytesToString(heliID)
var unsigned []byte
svc := &actionSignoffServiceMock{
unsignByComplaintFn: func(_ context.Context, cid []byte) (*actionsignoff.ActionSignoff, error) {
unsigned = cid
return &actionsignoff.ActionSignoff{HelicopterID: heliID, ComplaintID: cid}, nil // SignedAt nil → unsigned
},
signForComplaintFn: func(context.Context, []byte, []byte, []byte) (*actionsignoff.ActionSignoff, error) {
t.Fatal("sign:false must not call SignForComplaint")
return nil, nil
},
}
app := newActionSignoffApp(NewActionSignoffHandler(svc))
body := `{"data":{"attributes":{"sign":false,"complaint_id":"` + complaintIDStr + `"}}}`
req, _ := http.NewRequest(http.MethodPost, "/api/v1/action-signoffs/sign/"+heliStr, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
bodyBytes, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", resp.StatusCode, string(bodyBytes))
}
if string(unsigned) != string(complaintID) {
t.Fatalf("expected UnsignByComplaint with complaint id, got %x", unsigned)
}
}
func TestActionSignoffSign_Standalone_Unsign(t *testing.T) {
heliID := uuidv7.MustBytes()
heliStr, _ := uuidv7.BytesToString(heliID)
flightID := uuidv7.MustBytes()
flightStr, _ := uuidv7.BytesToString(flightID)
var unsignedFlight []byte
svc := &actionSignoffServiceMock{
unsignFn: func(_ context.Context, fid []byte, _ []byte) (*actionsignoff.ActionSignoff, error) {
unsignedFlight = fid
return &actionsignoff.ActionSignoff{HelicopterID: heliID}, nil // unsigned
},
signFn: func(context.Context, []byte, []byte) (*actionsignoff.ActionSignoff, error) {
t.Fatal("sign:false must not call Sign")
return nil, nil
},
}
app := newActionSignoffApp(NewActionSignoffHandler(svc))
body := `{"data":{"attributes":{"sign":false,"flight_id":"` + flightStr + `"}}}`
req, _ := http.NewRequest(http.MethodPost, "/api/v1/action-signoffs/sign/"+heliStr, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK || string(unsignedFlight) != string(flightID) {
b, _ := io.ReadAll(resp.Body)
t.Fatalf("expected standalone unsign, status=%d body=%s", resp.StatusCode, string(b))
}
}
// The response must be single-`data` enveloped (response.Write/jsonapi adds the
// envelope). Wrapping the resource in ActionSignoffResponse here would double-nest
// it as {"data":{"data":...}}, which the frontend mapper reads as unsigned —
// leaving the SIGN checkbox unchecked even after a successful sign.
func TestActionSignoffGetByHelicopterIsSingleDataEnvelope(t *testing.T) {
signedAt := time.Date(2026, 6, 30, 13, 18, 58, 0, time.UTC)
heliID := uuidv7.MustBytes()
heliStr, _ := uuidv7.BytesToString(heliID)
svc := &actionSignoffServiceMock{
getByHelicopterFn: func(_ context.Context, _ []byte) (*actionsignoff.ActionSignoff, error) {
return &actionsignoff.ActionSignoff{
ID: uuidv7.MustBytes(),
HelicopterID: heliID,
SignedAt: &signedAt,
SignedBy: uuidv7.MustBytes(),
}, nil
},
}
app := newActionSignoffApp(NewActionSignoffHandler(svc))
req, _ := http.NewRequest(http.MethodGet, "/api/v1/action-signoffs/get-by-flight/"+heliStr, nil)
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
t.Fatalf("expected 200, got %d body=%s", resp.StatusCode, string(body))
}
var body map[string]any
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
t.Fatal(err)
}
data, ok := body["data"].(map[string]any)
if !ok {
t.Fatalf("expected object at data, got %#v", body["data"])
}
// Regression guard: there must be no nested data.data.
if _, nested := data["data"]; nested {
t.Fatalf("response is double-wrapped (data.data present): %#v", data)
}
attrs, ok := data["attributes"].(map[string]any)
if !ok {
t.Fatalf("expected data.attributes object, got %#v", data["attributes"])
}
if signed, _ := attrs["signed"].(bool); !signed {
t.Fatalf("expected attributes.signed=true, got %#v", attrs["signed"])
}
}
// Placeholder (no row yet) must also be single-`data` enveloped.
func TestActionSignoffGetByHelicopterPlaceholderIsSingleDataEnvelope(t *testing.T) {
heliID := uuidv7.MustBytes()
heliStr, _ := uuidv7.BytesToString(heliID)
svc := &actionSignoffServiceMock{
getByHelicopterFn: func(_ context.Context, _ []byte) (*actionsignoff.ActionSignoff, error) {
return nil, nil
},
}
app := newActionSignoffApp(NewActionSignoffHandler(svc))
req, _ := http.NewRequest(http.MethodGet, "/api/v1/action-signoffs/get-by-flight/"+heliStr, nil)
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var body map[string]any
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
t.Fatal(err)
}
data, ok := body["data"].(map[string]any)
if !ok {
t.Fatalf("expected object at data, got %#v", body["data"])
}
if _, nested := data["data"]; nested {
t.Fatalf("placeholder is double-wrapped (data.data present): %#v", data)
}
attrs, ok := data["attributes"].(map[string]any)
if !ok {
t.Fatalf("expected data.attributes object, got %#v", data["attributes"])
}
if signed, _ := attrs["signed"].(bool); signed {
t.Fatalf("expected placeholder attributes.signed=false, got %#v", attrs["signed"])
}
}

View File

@@ -0,0 +1,215 @@
package handlers
import (
"context"
"strings"
"time"
"github.com/gofiber/fiber/v2"
afterflightinspection "wucher/internal/domain/after_flight_inspection"
flightinspection "wucher/internal/domain/flight_inspection"
"wucher/internal/shared/pkg/apperrorsx"
"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 AfterFlightInspectionHandler struct {
svc afterflightinspection.Service
flightSvc flightinspection.FlightInspectionService
validate *validators.Validator
}
func NewAfterFlightInspectionHandler(svc afterflightinspection.Service, flightSvc flightinspection.FlightInspectionService) *AfterFlightInspectionHandler {
return &AfterFlightInspectionHandler{
svc: svc,
flightSvc: flightSvc,
validate: validators.New(),
}
}
// GetAfterFlightInspection godoc
// @Summary Get after flight inspection by flight inspection ID
// @Tags Flights - After Flight Inspection
// @Accept json
// @Produce json
// @Param id path string true "Flight Inspection ID"
// @Success 200 {object} dto.AfterFlightInspectionResponse
// @Failure 404 {object} dto.ErrorResponse
// @Failure 400 {object} dto.ErrorResponse
// Public route disabled (managed via reserve AC flow).
func (h *AfterFlightInspectionHandler) Get(c *fiber.Ctx) error {
flightInspectionID, err := uuidv7.ParseString(strings.TrimSpace(c.Params("id")))
if err != nil {
return writeAfterFlightAppError(c, apperrorsx.ErrAfterFlightInvalidID, &jsonapi.ErrorSource{Pointer: "/path/id"})
}
row, err := h.svc.GetByFlightInspectionID(c.Context(), flightInspectionID)
if err != nil {
return writeAfterFlightServiceError(c, err)
}
if row == nil {
return writeAfterFlightAppError(c, apperrorsx.ErrAfterFlightInspectionNotFound, nil)
}
flightInspectionData, err := h.getFlightInspectionResource(c.Context(), flightInspectionID)
if err != nil {
return writeAfterFlightServiceError(c, err)
}
resp := dto.AfterFlightInspectionResponse{
Data: dto.AfterFlightInspectionResource{
Type: "after_flight_inspection",
Attributes: dto.AfterFlightInspectionAttributes{
FlightInspection: flightInspectionData,
OilEngineNR1Checked: row.OilEngineNR1Checked,
OilEngineNR2Checked: row.OilEngineNR2Checked,
HydraulicLHChecked: row.HydraulicLHChecked,
HydraulicRHChecked: row.HydraulicRHChecked,
OilTransmissionMGBChecked: row.OilTransmissionMGBChecked,
OilTransmissionIGBChecked: row.OilTransmissionIGBChecked,
OilTransmissionTGBChecked: row.OilTransmissionTGBChecked,
Note: row.Note,
CreatedAt: row.CreatedAt.Format(time.RFC3339),
UpdatedAt: row.UpdatedAt.Format(time.RFC3339),
},
},
}
idStr, _ := uuidv7.BytesToString(row.ID)
flightInspectionIDStr, _ := uuidv7.BytesToString(row.FlightInspectionID)
resp.Data.ID = idStr
resp.Data.Attributes.ID = idStr
resp.Data.Attributes.FlightInspectionID = flightInspectionIDStr
return c.JSON(resp)
}
// UpsertAfterFlightInspection godoc
// @Summary Upsert after flight inspection by flight inspection ID
// @Tags Flights - After Flight Inspection
// @Accept json
// @Produce json
// @Param id path string true "Flight Inspection ID"
// @Param request body dto.AfterFlightInspectionUpsertRequest true "JSON:API after flight inspection upsert request"
// @Success 200 {object} dto.AfterFlightInspectionResponse
// @Failure 422 {object} dto.ErrorResponse
// @Failure 404 {object} dto.ErrorResponse
// @Failure 400 {object} dto.ErrorResponse
// Public route disabled (managed via reserve AC flow).
func (h *AfterFlightInspectionHandler) Upsert(c *fiber.Ctx) error {
flightInspectionID, err := uuidv7.ParseString(strings.TrimSpace(c.Params("id")))
if err != nil {
return writeAfterFlightAppError(c, apperrorsx.ErrAfterFlightInvalidID, &jsonapi.ErrorSource{Pointer: "/path/id"})
}
var req dto.AfterFlightInspectionUpsertRequest
if err := c.BodyParser(&req); err != nil {
return writeAfterFlightAppError(c, apperrorsx.ErrAfterFlightInvalidJSON, nil)
}
if errs := h.validate.ValidateStruct(req); errs != nil {
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, errs)
}
upsertReq := &afterflightinspection.UpsertRequest{
OilEngineNR1Checked: req.Data.Attributes.OilEngineNR1Checked,
OilEngineNR2Checked: req.Data.Attributes.OilEngineNR2Checked,
HydraulicLHChecked: req.Data.Attributes.HydraulicLHChecked,
HydraulicRHChecked: req.Data.Attributes.HydraulicRHChecked,
OilTransmissionMGBChecked: req.Data.Attributes.OilTransmissionMGBChecked,
OilTransmissionIGBChecked: req.Data.Attributes.OilTransmissionIGBChecked,
OilTransmissionTGBChecked: req.Data.Attributes.OilTransmissionTGBChecked,
Note: req.Data.Attributes.Note,
}
row, err := h.svc.Upsert(c.Context(), flightInspectionID, upsertReq)
if err != nil {
return writeAfterFlightServiceError(c, err)
}
flightInspectionData, err := h.getFlightInspectionResource(c.Context(), flightInspectionID)
if err != nil {
return writeAfterFlightServiceError(c, err)
}
idStr, _ := uuidv7.BytesToString(row.ID)
flightInspectionIDStr, _ := uuidv7.BytesToString(row.FlightInspectionID)
resp := dto.AfterFlightInspectionResponse{
Data: dto.AfterFlightInspectionResource{
Type: "after_flight_inspection",
ID: idStr,
Attributes: dto.AfterFlightInspectionAttributes{
ID: idStr,
FlightInspectionID: flightInspectionIDStr,
FlightInspection: flightInspectionData,
OilEngineNR1Checked: row.OilEngineNR1Checked,
OilEngineNR2Checked: row.OilEngineNR2Checked,
HydraulicLHChecked: row.HydraulicLHChecked,
HydraulicRHChecked: row.HydraulicRHChecked,
OilTransmissionMGBChecked: row.OilTransmissionMGBChecked,
OilTransmissionIGBChecked: row.OilTransmissionIGBChecked,
OilTransmissionTGBChecked: row.OilTransmissionTGBChecked,
Note: row.Note,
CreatedAt: row.CreatedAt.Format(time.RFC3339),
UpdatedAt: row.UpdatedAt.Format(time.RFC3339),
},
},
}
return c.JSON(resp)
}
func (h *AfterFlightInspectionHandler) getFlightInspectionResource(ctx context.Context, flightInspectionID []byte) (*dto.FlightInspectionResource, error) {
if h.flightSvc == nil {
return nil, nil
}
row, err := h.flightSvc.GetByID(ctx, flightInspectionID)
if err != nil {
return nil, err
}
if row == nil {
return nil, nil
}
return afterFlightInspectionResource(row), nil
}
// writeAfterFlightAppError writes a known apperrorsx definition, optionally
// attaching a JSON:API source pointer.
func writeAfterFlightAppError(c *fiber.Ctx, def apperrorsx.Def, source *jsonapi.ErrorSource) error {
appErr := apperrorsx.New(def)
if source != nil {
appErr.Source = source
}
return apperrorsx.Write(c, appErr)
}
// writeAfterFlightServiceError translates a domain/service error into its
// apperrorsx definition; unknown errors fall back to a generic internal error.
func writeAfterFlightServiceError(c *fiber.Ctx, err error) error {
if mapped := apperrorsx.Translate(apperrorsx.ModuleAfterFlightInspection, err); mapped != nil && mapped.Code != apperrorsx.CodeInternalServerError {
return apperrorsx.Write(c, mapped)
}
return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrInternal))
}
func afterFlightInspectionResource(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),
},
}
}

View File

@@ -0,0 +1,894 @@
package handlers
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/gofiber/fiber/v2"
airrescuechecklist "wucher/internal/domain/air_rescue_checklist"
basedomain "wucher/internal/domain/base"
"wucher/internal/shared/pkg/apperrorsx"
"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 AirRescuerChecklistHandler struct {
svc airrescuechecklist.Service
validate *validators.Validator
}
func NewAirRescuerChecklistHandler(svc airrescuechecklist.Service) *AirRescuerChecklistHandler {
return &AirRescuerChecklistHandler{svc: svc, validate: validators.New()}
}
// Create godoc
// @Summary Create air rescuer checklist (bulk)
// @Description Create one or more checklist headers for a single base.
// @Description Request uses `base_id`.
// @Description Each checklist can be created with or without initial `items`.
// @Description `scope_code` accepts persisted codes and English aliases. Stored codes remain: `TA`, `MO`, `DI`, `MI`, `DO`, `FR`, `SA`, `SO`, `M10`, `M15`, `M20`, `M25`, `DS`.
// @Description Example aliases: `DAILY` -> `TA`, `TU/TUE/TUESDAY` -> `DI`, `WE/WED/WEDNESDAY` -> `MI`, `TH/THU/THURSDAY` -> `DO`, `SU/SUN/SUNDAY` -> `SO`, `DEADLINE` -> `DS`.
// @Description `CAT` is a virtual overview tab and is not allowed for create/update payloads.
// @Description Duplicate `position` is allowed for both checklist headers and checklist items.
// @Description Display order is stable: checklist headers `position ASC, title ASC`; checklist_items `position ASC, name ASC`.
// @Description Request is transactional: if one checklist/item fails, the whole batch is rolled back.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param request body requestdto.AirRescuerChecklistCreateRequest true "Bulk checklist create payload"
// @Success 201 {object} responsedto.AirRescuerChecklistBulkResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/create [post]
func (h *AirRescuerChecklistHandler) Create(c *fiber.Ctx) error {
var req requestdto.AirRescuerChecklistCreateRequest
if err := c.BodyParser(&req); err != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
Status: "400",
Title: "Invalid JSON",
Detail: safeInternalDetail(),
}})
}
if errs := h.validate.ValidateStruct(req); errs != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, errs)
}
baseID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.Attributes.BaseID))
if err != nil {
return validationError(c, "/data/attributes/base_id", "base_id is invalid UUID")
}
baseRows, err := h.svc.ListHEMSBases(c.UserContext(), airrescuechecklist.HEMSBaseFilter{HEMSBaseID: baseID})
if err != nil {
status, errObj := buildServiceErrorObject("Create failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
if len(baseRows) == 0 {
return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
Status: "404",
Title: "Not found",
Detail: "base not found",
}})
}
rows := make([]airrescuechecklist.ChecklistCreateInput, 0, len(req.Data.Attributes.Checklists))
actor := actorUserID(c)
for i := range req.Data.Attributes.Checklists {
scopeCode := airrescuechecklist.CanonicalizeAirRescuerChecklistScope(req.Data.Attributes.Checklists[i].ScopeCode)
if scopeCode == "" {
return validationError(c, fmt.Sprintf("/data/attributes/checklists/%d/scope_code", i), "scope_code is required")
}
if !airrescuechecklist.IsAirRescuerChecklistPersistedScope(scopeCode) {
return validationError(c, fmt.Sprintf("/data/attributes/checklists/%d/scope_code", i), "scope_code is invalid")
}
title := strings.TrimSpace(req.Data.Attributes.Checklists[i].Title)
if title == "" {
return validationError(c, fmt.Sprintf("/data/attributes/checklists/%d/title", i), "title is required")
}
position := checklistPositionOrZero(req.Data.Attributes.Checklists[i].Position)
items := make([]airrescuechecklist.AirRescuerChecklistItem, 0, len(req.Data.Attributes.Checklists[i].Items))
for j := range req.Data.Attributes.Checklists[i].Items {
itemName := strings.TrimSpace(req.Data.Attributes.Checklists[i].Items[j].Name)
if itemName == "" {
return validationError(c, fmt.Sprintf("/data/attributes/checklists/%d/items/%d/name", i, j), "name is required")
}
items = append(items, airrescuechecklist.AirRescuerChecklistItem{
Name: itemName,
Position: checklistPositionOrZero(req.Data.Attributes.Checklists[i].Items[j].Position),
CreatedBy: actor,
UpdatedBy: actor,
})
}
rows = append(rows, airrescuechecklist.ChecklistCreateInput{
Checklist: airrescuechecklist.AirRescuerChecklist{
HEMSBaseID: baseID,
ScopeCode: scopeCode,
Title: title,
Position: position,
CreatedBy: actor,
UpdatedBy: actor,
},
Items: items,
})
}
if err := h.svc.CreateBulk(c.UserContext(), rows); err != nil {
status, errObj := buildServiceErrorObject("Create failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
data := make([]responsedto.AirRescuerChecklistResource, 0, len(rows))
for i := range rows {
resource, found, err := h.loadChecklistResource(c.UserContext(), rows[i].Checklist.ID)
if err != nil {
status, errObj := buildServiceErrorObject("Create failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
if found {
data = append(data, resource)
}
}
return response.Write(c, fiber.StatusCreated, data)
}
// Get godoc
// @Summary Get air rescuer checklist by UUID
// @Description Return one checklist detail by UUID.
// @Description Includes checklist header, base info, and `items` (`checklist_items`).
// @Tags Air Rescuer Checklist
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Success 200 {object} responsedto.AirRescuerChecklistResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/get/{uuid} [get]
func (h *AirRescuerChecklistHandler) Get(c *fiber.Ctx) error {
id, err := uuidv7.ParseString(c.Params("uuid"))
if err != nil {
return validationError(c, "/path/uuid", "uuid is invalid UUID")
}
return h.respondChecklistByID(c, fiber.StatusOK, id)
}
// Update godoc
// @Summary Update air rescuer checklist header and append new items
// @Description Update checklist header fields (`scope_code`, `title`, `position`) and/or append `new_items`.
// @Description `new_items` is append-only. Existing items are not updated/deleted by this endpoint.
// @Description Item update/delete lifecycle must use dedicated item endpoints.
// @Description `scope_code` accepts persisted codes and English aliases, but `CAT` (or `OVERVIEW`) is not allowed for update payload.
// @Description Duplicate `position` is allowed.
// @Description Request is transactional.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Param request body requestdto.AirRescuerChecklistUpdateRequest true "Checklist update payload"
// @Success 200 {object} responsedto.AirRescuerChecklistResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/update/{uuid} [patch]
func (h *AirRescuerChecklistHandler) Update(c *fiber.Ctx) error {
id, err := uuidv7.ParseString(c.Params("uuid"))
if err != nil {
return validationError(c, "/path/uuid", "uuid is invalid UUID")
}
var req requestdto.AirRescuerChecklistUpdateRequest
if err := c.BodyParser(&req); err != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
Status: "400",
Title: "Invalid JSON",
Detail: safeInternalDetail(),
}})
}
if errs := h.validate.ValidateStruct(req); errs != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, errs)
}
if strings.TrimSpace(req.Data.ID) != "" {
reqID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.ID))
if err != nil {
return validationError(c, "/data/id", "id is invalid UUID")
}
if !bytesEqualUUID(reqID, id) {
return validationError(c, "/data/id", "id does not match path uuid")
}
}
existing, err := h.svc.GetChecklistByID(c.UserContext(), id)
if err != nil {
status, errObj := buildServiceErrorObject("Update failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
if existing == nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
Status: "404",
Title: "Not found",
Detail: "air rescuer checklist not found",
}})
}
attrs := req.Data.Attributes
hasHeaderUpdate := attrs.ScopeCode != nil || attrs.Title != nil || attrs.Position != nil
if attrs.NewItems != nil && len(attrs.NewItems) == 0 {
return validationError(c, "/data/attributes/new_items", "new_items must contain at least 1 item")
}
hasNewItems := len(attrs.NewItems) > 0
if !hasHeaderUpdate && !hasNewItems {
return validationError(c, "/data/attributes", "at least one attribute must be provided")
}
actor := actorUserID(c)
var row *airrescuechecklist.AirRescuerChecklist
if hasHeaderUpdate {
scopeCode := airrescuechecklist.CanonicalizeAirRescuerChecklistScope(existing.ScopeCode)
if attrs.ScopeCode != nil {
scopeCode = airrescuechecklist.CanonicalizeAirRescuerChecklistScope(*attrs.ScopeCode)
if scopeCode == "" {
return validationError(c, "/data/attributes/scope_code", "scope_code is required")
}
if !airrescuechecklist.IsAirRescuerChecklistPersistedScope(scopeCode) {
return validationError(c, "/data/attributes/scope_code", "scope_code is invalid")
}
}
title := strings.TrimSpace(existing.Title)
if attrs.Title != nil {
title = strings.TrimSpace(*attrs.Title)
if title == "" {
return validationError(c, "/data/attributes/title", "title is required")
}
}
position := existing.Position
if attrs.Position != nil {
position = checklistPositionOrDefault(attrs.Position)
}
row = &airrescuechecklist.AirRescuerChecklist{
ID: id,
ScopeCode: scopeCode,
Title: title,
Position: position,
UpdatedBy: actor,
}
}
newItems := make([]airrescuechecklist.AirRescuerChecklistItem, 0, len(attrs.NewItems))
for i := range attrs.NewItems {
name := strings.TrimSpace(attrs.NewItems[i].Name)
if name == "" {
return validationError(c, fmt.Sprintf("/data/attributes/new_items/%d/name", i), "name is required")
}
newItems = append(newItems, airrescuechecklist.AirRescuerChecklistItem{
ChecklistID: id,
Name: name,
Position: checklistPositionOrZero(attrs.NewItems[i].Position),
CreatedBy: actor,
UpdatedBy: actor,
})
}
if err := h.svc.UpdateChecklistWithNewItems(c.UserContext(), row, newItems); err != nil {
status, errObj := buildServiceErrorObject("Update failed", err, "air rescuer checklist not found")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
return h.respondChecklistByID(c, fiber.StatusOK, id)
}
// Delete godoc
// @Summary Delete air rescuer checklist
// @Description Delete one checklist header by UUID.
// @Description Related checklist items are removed together following current implementation behavior.
// @Tags Air Rescuer Checklist
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Success 200 {object} responsedto.AirRescuerChecklistGroupedListResponse
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/delete/{uuid} [delete]
func (h *AirRescuerChecklistHandler) Delete(c *fiber.Ctx) error {
id, err := uuidv7.ParseString(c.Params("uuid"))
if err != nil {
return validationError(c, "/path/uuid", "uuid is invalid UUID")
}
if err := h.svc.DeleteChecklist(c.UserContext(), id, actorUserID(c)); err != nil {
status, errObj := buildServiceErrorObject("Delete failed", err, "air rescuer checklist not found")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
return response.Write(c, fiber.StatusOK, jsonapi.Resource{Type: "air_rescuer_checklist_delete", Attributes: map[string]any{"deleted": true}})
}
// List godoc
// @Summary List air rescuer checklist grouped by base
// @Description Grouped by base and returned in `data[].attributes.bases`.
// @Description Response includes `tabs[]` with default tab order: `CAT`, `TA`, `MO`, `DI`, `MI`, `DO`, `FR`, `SA`, `SO`, `M10`, `M15`, `M20`, `M25`, `DS`.
// @Description `CAT` is a virtual overview tab (not persisted in DB).
// @Description `CAT.groups[]` only includes non-empty persisted scopes and does not include `checklist_items`.
// @Description Normal tabs are always present. Empty normal tabs are explicitly returned as `checklists: []`.
// @Description Scope display names: `CAT=Category`, `TA=Daily`, `MO=Monday`, `DI=Tuesday`, `MI=Wednesday`, `DO=Thursday`, `FR=Friday`, `SA=Saturday`, `SO=Sunday`, `M10=M10`, `M15=M15`, `M20=M20`, `M25=M25`, `DS=Deadline`.
// @Description Duplicate `position` is allowed. Display order is stable: checklist headers `position ASC, title ASC`; checklist_items `position ASC, name ASC`.
// @Description Optional filters: `base_id`, `category_type` (`regular|hems`), `base_name`, `base_abbreviation`, `scope_code` (`CAT`/`OVERVIEW` or persisted scope / aliases).
// @Tags Air Rescuer Checklist
// @Produce json
// @Param base_id query string false "Filter by base UUID"
// @Param category_type query string false "Filter by base category" Enums(regular,hems)
// @Param base_name query string false "Filter by base name (contains, case-insensitive)"
// @Param base_abbreviation query string false "Filter by base abbreviation (contains, case-insensitive)"
// @Param scope_code query string false "Filter by scope code. Allowed canonical: CAT, TA, MO, DI, MI, DO, FR, SA, SO, M10, M15, M20, M25, DS. English aliases are also accepted."
// @Success 200 {object} responsedto.AirRescuerChecklistGroupedDataTableResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/get-all [get]
func (h *AirRescuerChecklistHandler) List(c *fiber.Ctx) error {
headerFilter, baseFilter, requestedScope, _ := parseAirRescuerChecklistFilters(c)
if c.Response().StatusCode() >= fiber.StatusBadRequest {
return nil
}
groups, err := h.loadChecklistGroups(c.UserContext(), headerFilter, baseFilter, requestedScope)
if err != nil {
status, errObj := buildServiceErrorObject("List failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
meta := map[string]any{"total_bases": len(groups)}
return response.WriteWithMeta(c, fiber.StatusOK, groups, meta)
}
// ListDatatable godoc
// @Summary List air rescuer checklist grouped by base (datatable)
// @Description Datatable variant for grouped air rescuer checklist data.
// @Description Semantic data is the same as `get-all`: grouped by base with `tabs[]`, virtual `CAT`, and normal tabs always present.
// @Description `CAT.groups[]` only includes non-empty persisted scopes and does not include `checklist_items`.
// @Description Empty normal tabs are explicitly returned as `checklists: []`.
// @Description Scope display names: `CAT=Category`, `TA=Daily`, `MO=Monday`, `DI=Tuesday`, `MI=Wednesday`, `DO=Thursday`, `FR=Friday`, `SA=Saturday`, `SO=Sunday`, `M10=M10`, `M15=M15`, `M20=M20`, `M25=M25`, `DS=Deadline`.
// @Description Duplicate `position` is allowed. Display order is stable: checklist headers `position ASC, title ASC`; checklist_items `position ASC, name ASC`.
// @Description Optional filters: `base_id`, `category_type` (`regular|hems`), `base_name`, `base_abbreviation`, `scope_code`.
// @Description Pagination/query controls: `page`, `size`, `limit`, `draw`.
// @Tags Air Rescuer Checklist
// @Produce json
// @Param base_id query string false "Filter by base UUID"
// @Param category_type query string false "Filter by base category" Enums(regular,hems)
// @Param base_name query string false "Filter by base name (contains, case-insensitive)"
// @Param base_abbreviation query string false "Filter by base abbreviation (contains, case-insensitive)"
// @Param scope_code query string false "Filter by scope code. Allowed canonical: CAT, TA, MO, DI, MI, DO, FR, SA, SO, M10, M15, M20, M25, DS. English aliases are also accepted."
// @Param page query int false "Page number (default 1)"
// @Param size query int false "Page size (default 20)"
// @Param limit query int false "Page size override (max 100)"
// @Param draw query int false "Datatable draw counter"
// @Success 200 {object} responsedto.AirRescuerChecklistGroupedDataTableResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/get-all/dt [get]
func (h *AirRescuerChecklistHandler) ListDatatable(c *fiber.Ctx) error {
headerFilter, baseFilter, requestedScope, _ := parseAirRescuerChecklistFilters(c)
if c.Response().StatusCode() >= fiber.StatusBadRequest {
return nil
}
groups, err := h.loadChecklistGroups(c.UserContext(), headerFilter, baseFilter, requestedScope)
if err != nil {
status, errObj := buildServiceErrorObject("List failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
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)
pagedGroups, total := paginateAirRescuerChecklistGroups(groups, start, length)
meta := map[string]any{"draw": draw, "records_total": total, "records_filtered": total}
return response.WriteWithMeta(c, fiber.StatusOK, pagedGroups, meta)
}
// CreateItem godoc
// @Summary Create checklist items (bulk)
// @Description Create one or more items for an existing checklist.
// @Description Parent checklist is taken from path `uuid`.
// @Description Duplicate `position` is allowed. Display order for checklist_items is `position ASC, name ASC`.
// @Description Request is transactional: if one item fails, all item inserts in the batch are rolled back.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Param request body requestdto.AirRescuerChecklistItemCreateRequest true "Bulk checklist item create payload"
// @Success 201 {object} responsedto.AirRescuerChecklistItemBulkResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/{uuid}/items/create [post]
func (h *AirRescuerChecklistHandler) CreateItem(c *fiber.Ctx) error {
checklistID, err := uuidv7.ParseString(c.Params("uuid"))
if err != nil {
return validationError(c, "/path/uuid", "uuid is invalid UUID")
}
var req requestdto.AirRescuerChecklistItemCreateRequest
if err := c.BodyParser(&req); err != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
Status: "400",
Title: "Invalid JSON",
Detail: safeInternalDetail(),
}})
}
if errs := h.validate.ValidateStruct(req); errs != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, errs)
}
actor := actorUserID(c)
rows := make([]airrescuechecklist.AirRescuerChecklistItem, 0, len(req.Data.Attributes.Items))
for i := range req.Data.Attributes.Items {
name := strings.TrimSpace(req.Data.Attributes.Items[i].Name)
if name == "" {
return validationError(c, fmt.Sprintf("/data/attributes/items/%d/name", i), "name is required")
}
rows = append(rows, airrescuechecklist.AirRescuerChecklistItem{
ChecklistID: checklistID,
Name: name,
Position: checklistPositionOrZero(req.Data.Attributes.Items[i].Position),
CreatedBy: actor,
UpdatedBy: actor,
})
}
if err := h.svc.CreateChecklistItems(c.UserContext(), checklistID, rows); err != nil {
status, errObj := buildServiceErrorObject("Create failed", err, "air rescuer checklist not found")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
data := make([]responsedto.AirRescuerChecklistItemData, 0, len(rows))
for i := range rows {
created, err := h.svc.GetChecklistItemByID(c.UserContext(), rows[i].ID)
if err != nil {
status, errObj := buildServiceErrorObject("Create failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
if created == nil {
data = append(data, responsedto.AirRescuerChecklistItemData{
Type: "air_rescuer_checklist_item",
ID: idString(rows[i].ID),
Attributes: responsedto.AirRescuerChecklistItemAttributes{
ChecklistID: idString(checklistID),
Name: rows[i].Name,
Position: rows[i].Position,
},
})
continue
}
data = append(data, airRescuerChecklistItemResponseData(*created))
}
return response.Write(c, fiber.StatusCreated, data)
}
// ReorderItems godoc
// @Summary Reorder checklist items in one request
// @Description Reorder one or more checklist items for the checklist in path `uuid`.
// @Description Use this endpoint to swap or reorder positions with a single PIN validation.
// @Description Duplicate `position` values remain allowed by domain rules.
// @Description Request is transactional: if one item fails, all position updates are rolled back.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param uuid path string true "Checklist UUID"
// @Param request body requestdto.AirRescuerChecklistItemReorderRequest true "Checklist item reorder payload"
// @Success 200 {object} responsedto.AirRescuerChecklistResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/{uuid}/items/reorder [patch]
func (h *AirRescuerChecklistHandler) ReorderItems(c *fiber.Ctx) error {
checklistID, err := uuidv7.ParseString(c.Params("uuid"))
if err != nil {
return validationError(c, "/path/uuid", "uuid is invalid UUID")
}
var req requestdto.AirRescuerChecklistItemReorderRequest
if err := c.BodyParser(&req); err != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
Status: "400",
Title: "Invalid JSON",
Detail: safeInternalDetail(),
}})
}
if errs := h.validate.ValidateStruct(req); errs != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, errs)
}
seen := make(map[string]struct{}, len(req.Data.Attributes.Items))
rows := make([]airrescuechecklist.ChecklistItemReorderInput, 0, len(req.Data.Attributes.Items))
for i := range req.Data.Attributes.Items {
itemUUID := strings.TrimSpace(req.Data.Attributes.Items[i].ItemUUID)
itemID, err := uuidv7.ParseString(itemUUID)
if err != nil {
return validationError(c, fmt.Sprintf("/data/attributes/items/%d/item_uuid", i), "item_uuid is invalid UUID")
}
itemKey := idString(itemID)
if _, exists := seen[itemKey]; exists {
return validationError(c, fmt.Sprintf("/data/attributes/items/%d/item_uuid", i), "item_uuid must be unique")
}
seen[itemKey] = struct{}{}
rows = append(rows, airrescuechecklist.ChecklistItemReorderInput{
ID: itemID,
Position: checklistPositionOrDefault(req.Data.Attributes.Items[i].Position),
})
}
if err := h.svc.ReorderChecklistItems(c.UserContext(), checklistID, rows, actorUserID(c)); err != nil {
status, errObj := buildServiceErrorObject("Reorder failed", err, "air rescuer checklist item not found")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
return h.respondChecklistByID(c, fiber.StatusOK, checklistID)
}
// UpdateItem godoc
// @Summary Update one checklist item
// @Description Update one existing checklist item by `item_uuid`.
// @Description This endpoint only affects the selected item.
// @Description Duplicate `position` is allowed.
// @Tags Air Rescuer Checklist
// @Accept json
// @Produce json
// @Param item_uuid path string true "Checklist item UUID"
// @Param request body requestdto.AirRescuerChecklistItemUpdateRequest true "Checklist item update payload"
// @Success 200 {object} responsedto.AirRescuerChecklistItemResponse
// @Failure 400 {object} jsonapi.ErrorDocument
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/items/update/{item_uuid} [patch]
func (h *AirRescuerChecklistHandler) UpdateItem(c *fiber.Ctx) error {
itemID, err := uuidv7.ParseString(c.Params("item_uuid"))
if err != nil {
return validationError(c, "/path/item_uuid", "item_uuid is invalid UUID")
}
var req requestdto.AirRescuerChecklistItemUpdateRequest
if err := c.BodyParser(&req); err != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
Status: "400",
Title: "Invalid JSON",
Detail: safeInternalDetail(),
}})
}
if errs := h.validate.ValidateStruct(req); errs != nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, errs)
}
if strings.TrimSpace(req.Data.ID) != "" {
reqID, err := uuidv7.ParseString(strings.TrimSpace(req.Data.ID))
if err != nil {
return validationError(c, "/data/id", "id is invalid UUID")
}
if !bytesEqualUUID(reqID, itemID) {
return validationError(c, "/data/id", "id does not match path item_uuid")
}
}
attrs := req.Data.Attributes
if attrs.Name == nil && attrs.Position == nil {
return validationError(c, "/data/attributes", "at least one attribute must be provided")
}
existing, err := h.svc.GetChecklistItemByID(c.UserContext(), itemID)
if err != nil {
status, errObj := buildServiceErrorObject("Update failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
if existing == nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
Status: "404",
Title: "Not found",
Detail: "air rescuer checklist item not found",
}})
}
name := strings.TrimSpace(existing.Name)
if attrs.Name != nil {
name = strings.TrimSpace(*attrs.Name)
if name == "" {
return validationError(c, "/data/attributes/name", "name is required")
}
}
position := existing.Position
if attrs.Position != nil {
position = checklistPositionOrDefault(attrs.Position)
}
row := &airrescuechecklist.AirRescuerChecklistItem{
ID: itemID,
Name: name,
Position: position,
UpdatedBy: actorUserID(c),
}
if err := h.svc.UpdateChecklistItem(c.UserContext(), row); err != nil {
status, errObj := buildServiceErrorObject("Update failed", err, "air rescuer checklist item not found")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
updated, err := h.svc.GetChecklistItemByID(c.UserContext(), itemID)
if err != nil {
status, errObj := buildServiceErrorObject("Update failed", err, "")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
if updated == nil {
return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
Status: "404",
Title: "Not found",
Detail: "air rescuer checklist item not found",
}})
}
return response.Write(c, fiber.StatusOK, airRescuerChecklistItemResponseData(*updated))
}
// DeleteItem godoc
// @Summary Delete one checklist item
// @Description Delete one existing checklist item by `item_uuid`.
// @Tags Air Rescuer Checklist
// @Produce json
// @Param item_uuid path string true "Checklist item UUID"
// @Success 200 {object} jsonapi.Document
// @Failure 409 {object} jsonapi.ErrorDocument
// @Failure 404 {object} jsonapi.ErrorDocument
// @Failure 422 {object} jsonapi.ErrorDocument
// @Router /api/v1/air-rescuer-checklist/items/delete/{item_uuid} [delete]
func (h *AirRescuerChecklistHandler) DeleteItem(c *fiber.Ctx) error {
itemID, err := uuidv7.ParseString(c.Params("item_uuid"))
if err != nil {
return validationError(c, "/path/item_uuid", "item_uuid is invalid UUID")
}
if err := h.svc.DeleteChecklistItem(c.UserContext(), itemID, actorUserID(c)); err != nil {
status, errObj := buildServiceErrorObject("Delete failed", err, "air rescuer checklist item not found")
return writeAirRescuerChecklistErrors(c, status, []jsonapi.ErrorObject{errObj})
}
return response.Write(c, fiber.StatusOK, jsonapi.Resource{Type: "air_rescuer_checklist_item_delete", Attributes: map[string]any{"deleted": true}})
}
func (h *AirRescuerChecklistHandler) respondChecklistByID(c *fiber.Ctx, status int, id []byte) error {
resource, found, err := h.loadChecklistResource(c.UserContext(), id)
if err != nil {
statusCode, errObj := buildServiceErrorObject("Get failed", err, "")
return writeAirRescuerChecklistErrors(c, statusCode, []jsonapi.ErrorObject{errObj})
}
if !found {
return writeAirRescuerChecklistErrors(c, fiber.StatusNotFound, []jsonapi.ErrorObject{{
Status: "404",
Title: "Not found",
Detail: "air rescuer checklist not found",
}})
}
return response.Write(c, status, resource)
}
func (h *AirRescuerChecklistHandler) loadChecklistResource(ctx context.Context, id []byte) (responsedto.AirRescuerChecklistResource, bool, error) {
head, err := h.svc.GetChecklistByID(ctx, id)
if err != nil {
return responsedto.AirRescuerChecklistResource{}, false, err
}
if head == nil {
return responsedto.AirRescuerChecklistResource{}, false, nil
}
itemRows, err := h.svc.ListChecklistItemsByChecklistIDs(ctx, [][]byte{id})
if err != nil {
return responsedto.AirRescuerChecklistResource{}, false, err
}
return airRescuerChecklistResource(*head, itemRows), true, nil
}
func (h *AirRescuerChecklistHandler) loadChecklistGroups(
ctx context.Context,
headerFilter airrescuechecklist.ChecklistHeaderFilter,
baseFilter airrescuechecklist.HEMSBaseFilter,
requestedScope string,
) ([]responsedto.AirRescuerChecklistBaseGroupResource, error) {
bases, err := h.svc.ListHEMSBases(ctx, baseFilter)
if err != nil {
return nil, err
}
headers, err := h.svc.ListChecklistHeaders(ctx, headerFilter)
if err != nil {
return nil, err
}
ids := make([][]byte, 0, len(headers))
for i := range headers {
ids = append(ids, headers[i].ID)
}
items, err := h.svc.ListChecklistItemsByChecklistIDs(ctx, ids)
if err != nil {
return nil, err
}
return buildAirRescuerChecklistGroups(bases, headers, items, requestedScope), nil
}
func parseAirRescuerChecklistFilters(c *fiber.Ctx) (airrescuechecklist.ChecklistHeaderFilter, airrescuechecklist.HEMSBaseFilter, string, error) {
headerFilter := airrescuechecklist.ChecklistHeaderFilter{}
baseFilter := airrescuechecklist.HEMSBaseFilter{}
requestedScope := ""
if categoryType, hasAnyCategory, ok := normalizeAirRescuerChecklistCategoryFromQuery(c); hasAnyCategory {
if !ok {
return headerFilter, baseFilter, requestedScope, validationError(c, "/query/category_type", "category_type/base_category must be one of: regular, hems")
}
headerFilter.CategoryType = categoryType
baseFilter.CategoryType = categoryType
}
baseRaw := strings.TrimSpace(c.Query("base_id"))
if baseRaw != "" {
baseID, err := uuidv7.ParseString(baseRaw)
if err != nil {
return headerFilter, baseFilter, requestedScope, validationError(c, "/query/base_id", "base_id is invalid UUID")
}
headerFilter.HEMSBaseID = baseID
baseFilter.HEMSBaseID = baseID
}
headerFilter.BaseName = strings.TrimSpace(c.Query("base_name"))
headerFilter.BaseAbbreviation = strings.TrimSpace(c.Query("base_abbreviation"))
baseFilter.BaseName = headerFilter.BaseName
baseFilter.BaseAbbreviation = headerFilter.BaseAbbreviation
scopeCode := airrescuechecklist.CanonicalizeAirRescuerChecklistScope(c.Query("scope_code"))
if scopeCode != "" {
if !airrescuechecklist.IsAirRescuerChecklistQueryScope(scopeCode) {
return headerFilter, baseFilter, requestedScope, validationError(c, "/query/scope_code", "scope_code is invalid")
}
requestedScope = scopeCode
if airrescuechecklist.IsAirRescuerChecklistPersistedScope(scopeCode) {
headerFilter.ScopeCode = scopeCode
}
}
return headerFilter, baseFilter, requestedScope, nil
}
func normalizeAirRescuerChecklistCategoryFromQuery(c *fiber.Ctx) (categoryType string, hasAny bool, ok bool) {
// Prefer new alias first, then backward-compatible query key.
candidates := []string{
strings.TrimSpace(c.Query("base_category")),
strings.TrimSpace(c.Query("category_type")),
}
for _, raw := range candidates {
if raw == "" {
continue
}
hasAny = true
if normalized, valid := basedomain.NormalizeCategoryType(raw); valid {
return normalized, true, true
}
}
return "", hasAny, false
}
func checklistPositionOrDefault(v *int) int {
if v == nil || *v <= 0 {
return 1
}
return *v
}
func checklistPositionOrZero(v *int) int {
if v == nil || *v <= 0 {
return 0
}
return *v
}
func writeAirRescuerChecklistErrors(c *fiber.Ctx, status int, errs []jsonapi.ErrorObject) error {
enriched := make([]jsonapi.ErrorObject, 0, len(errs))
statusStr := strconv.Itoa(status)
for i := range errs {
e := errs[i]
e.Status = statusStr
if strings.TrimSpace(e.Code) == "" || strings.TrimSpace(e.ErrorCode) == "" {
code, errorCode := inferAirRescuerChecklistErrorCode(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 response.WriteErrors(c, status, enriched)
}
func inferAirRescuerChecklistErrorCode(status int, title, detail string) (string, string) {
lowerTitle := strings.ToLower(strings.TrimSpace(title))
lowerDetail := strings.ToLower(strings.TrimSpace(detail))
switch status {
case fiber.StatusNotFound:
switch {
case strings.Contains(lowerDetail, "item not found"):
return apperrorsx.CodeAirRescuerChecklistItemNotFound, apperrorsx.ErrAirRescuerChecklistItemNotFound.ErrorCode
case strings.Contains(lowerDetail, "base not found"):
return apperrorsx.CodeAirRescuerChecklistBaseNotFound, apperrorsx.ErrAirRescuerChecklistBaseNotFound.ErrorCode
default:
return apperrorsx.CodeAirRescuerChecklistNotFound, apperrorsx.ErrAirRescuerChecklistNotFound.ErrorCode
}
case fiber.StatusUnprocessableEntity:
switch {
case strings.Contains(lowerDetail, "scope_code is required"):
return apperrorsx.CodeAirRescuerChecklistScopeRequired, apperrorsx.ErrAirRescuerChecklistScopeRequired.ErrorCode
case strings.Contains(lowerDetail, "scope_code is invalid"):
return apperrorsx.CodeAirRescuerChecklistScopeInvalid, apperrorsx.ErrAirRescuerChecklistScopeInvalid.ErrorCode
case strings.Contains(lowerDetail, "title is required"):
return apperrorsx.CodeAirRescuerChecklistTitleRequired, apperrorsx.ErrAirRescuerChecklistTitleRequired.ErrorCode
case strings.Contains(lowerDetail, "name is required"):
return apperrorsx.CodeAirRescuerChecklistNameRequired, apperrorsx.ErrAirRescuerChecklistNameRequired.ErrorCode
case strings.Contains(lowerDetail, "id does not match path"):
return apperrorsx.CodeAirRescuerChecklistIDMismatch, apperrorsx.ErrAirRescuerChecklistIDMismatch.ErrorCode
case strings.Contains(lowerDetail, "invalid uuid"):
return apperrorsx.CodeAirRescuerChecklistInvalidUUID, apperrorsx.ErrAirRescuerChecklistInvalidUUID.ErrorCode
default:
return apperrorsx.CodeAirRescuerChecklistValidationFailed, apperrorsx.ErrAirRescuerChecklistValidationFailed.ErrorCode
}
case fiber.StatusBadRequest:
switch {
case strings.Contains(lowerTitle, "invalid json"):
return apperrorsx.CodeAirRescuerChecklistInvalidJSON, apperrorsx.ErrAirRescuerChecklistInvalidJSON.ErrorCode
case strings.Contains(lowerTitle, "create failed"):
return apperrorsx.CodeAirRescuerChecklistCreateFailed, apperrorsx.ErrAirRescuerChecklistCreateFailed.ErrorCode
case strings.Contains(lowerTitle, "update failed"):
return apperrorsx.CodeAirRescuerChecklistUpdateFailed, apperrorsx.ErrAirRescuerChecklistUpdateFailed.ErrorCode
case strings.Contains(lowerTitle, "delete failed"):
return apperrorsx.CodeAirRescuerChecklistDeleteFailed, apperrorsx.ErrAirRescuerChecklistDeleteFailed.ErrorCode
case strings.Contains(lowerTitle, "list failed"), strings.Contains(lowerTitle, "get failed"):
return apperrorsx.CodeAirRescuerChecklistListFailed, apperrorsx.ErrAirRescuerChecklistListFailed.ErrorCode
case strings.Contains(lowerTitle, "reorder failed"):
return apperrorsx.CodeAirRescuerChecklistReorderFailed, apperrorsx.ErrAirRescuerChecklistReorderFailed.ErrorCode
default:
return apperrorsx.CodeAirRescuerChecklistInvalidPayload, apperrorsx.ErrAirRescuerChecklistInvalidPayload.ErrorCode
}
default:
return apperrorsx.CodeInternalServerError, apperrorsx.ErrInternal.ErrorCode
}
}
func validationError(c *fiber.Ctx, pointer, detail string) error {
return writeAirRescuerChecklistErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
Status: "422",
Title: "Validation error",
Detail: detail,
Source: &jsonapi.ErrorSource{Pointer: pointer},
}})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
package handlers
import (
"strings"
"time"
airrescuechecklist "wucher/internal/domain/air_rescue_checklist"
responsedto "wucher/internal/transport/http/dto/response"
shareddto "wucher/internal/transport/http/dto/shared"
)
func airRescuerChecklistResource(header airrescuechecklist.ChecklistHeaderView, items []airrescuechecklist.ChecklistItemView) responsedto.AirRescuerChecklistResource {
return responsedto.AirRescuerChecklistResource{
Type: "air_rescuer_checklist",
ID: idString(header.ID),
Attributes: responsedto.AirRescuerChecklistAttributes{
Bases: shareddto.AirRescuerChecklistBaseInfo{
ID: idString(header.HEMSBaseID),
BaseName: strings.TrimSpace(header.BaseName),
BaseAbbreviation: strings.TrimSpace(header.BaseAbbreviation),
BaseCategory: strings.TrimSpace(header.BaseCategory),
},
ScopeCode: airrescuechecklist.AirRescuerChecklistScopeCodeEN(header.ScopeCode),
ScopeCodeDe: airrescuechecklist.AirRescuerChecklistScopeCodeDE(header.ScopeCode),
Title: strings.TrimSpace(header.Title),
Position: header.Position,
Items: mapAirRescuerChecklistItemResources(items),
CreatedBy: uuidStringOrEmpty(header.CreatedBy),
CreatedAt: header.CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: header.UpdatedAt.UTC().Format(time.RFC3339),
},
}
}
func airRescuerChecklistItemResponseData(item airrescuechecklist.ChecklistItemView) responsedto.AirRescuerChecklistItemData {
return responsedto.AirRescuerChecklistItemData{
Type: "air_rescuer_checklist_item",
ID: idString(item.ID),
Attributes: responsedto.AirRescuerChecklistItemAttributes{
ChecklistID: idString(item.ChecklistID),
Name: strings.TrimSpace(item.Name),
Position: item.Position,
CreatedBy: uuidStringOrEmpty(item.CreatedBy),
CreatedAt: item.CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: item.UpdatedAt.UTC().Format(time.RFC3339),
},
}
}
func buildAirRescuerChecklistGroups(
bases []airrescuechecklist.HEMSBaseView,
headers []airrescuechecklist.ChecklistHeaderView,
items []airrescuechecklist.ChecklistItemView,
requestedScope string,
) []responsedto.AirRescuerChecklistBaseGroupResource {
itemsByChecklist := make(map[string][]airrescuechecklist.ChecklistItemView, len(items))
for i := range items {
key := idString(items[i].ChecklistID)
itemsByChecklist[key] = append(itemsByChecklist[key], items[i])
}
headersByBaseScope := make(map[string]map[string][]airrescuechecklist.ChecklistHeaderView)
for i := range headers {
baseKey := idString(headers[i].HEMSBaseID)
scopeCode := strings.ToUpper(strings.TrimSpace(headers[i].ScopeCode))
if headersByBaseScope[baseKey] == nil {
headersByBaseScope[baseKey] = map[string][]airrescuechecklist.ChecklistHeaderView{}
}
headersByBaseScope[baseKey][scopeCode] = append(headersByBaseScope[baseKey][scopeCode], headers[i])
}
groups := make([]responsedto.AirRescuerChecklistBaseGroupResource, 0, len(bases))
for i := range bases {
baseID := idString(bases[i].ID)
groups = append(groups, responsedto.AirRescuerChecklistBaseGroupResource{
Type: "air_rescuer_checklist_group",
ID: "base-" + baseID,
Attributes: responsedto.AirRescuerChecklistBaseGroupAttributes{
Bases: shareddto.AirRescuerChecklistBaseInfo{
ID: baseID,
BaseName: strings.TrimSpace(bases[i].BaseName),
BaseAbbreviation: strings.TrimSpace(bases[i].BaseAbbreviation),
BaseCategory: strings.TrimSpace(bases[i].BaseCategory),
},
Tabs: buildAirRescuerChecklistTabs(headersByBaseScope[baseID], itemsByChecklist, requestedScope),
},
})
}
return groups
}
func buildAirRescuerChecklistTabs(
headersByScope map[string][]airrescuechecklist.ChecklistHeaderView,
itemsByChecklist map[string][]airrescuechecklist.ChecklistItemView,
requestedScope string,
) []responsedto.AirRescuerChecklistTabResource {
normalizedScope := strings.ToUpper(strings.TrimSpace(requestedScope))
if normalizedScope == airrescuechecklist.AirRescuerChecklistScopeCAT {
return []responsedto.AirRescuerChecklistTabResource{
buildAirRescuerChecklistCATTab(headersByScope),
}
}
if airrescuechecklist.IsAirRescuerChecklistPersistedScope(normalizedScope) {
return []responsedto.AirRescuerChecklistTabResource{
buildAirRescuerChecklistDetailTab(headersByScope, itemsByChecklist, normalizedScope),
}
}
scopeOrder := airrescuechecklist.AirRescuerChecklistPersistedScopeOrder()
tabs := make([]responsedto.AirRescuerChecklistTabResource, 0, 1+len(scopeOrder))
tabs = append(tabs, buildAirRescuerChecklistCATTab(headersByScope))
for i := range scopeOrder {
tabs = append(tabs, buildAirRescuerChecklistDetailTab(headersByScope, itemsByChecklist, scopeOrder[i]))
}
return tabs
}
func buildAirRescuerChecklistCATTab(
headersByScope map[string][]airrescuechecklist.ChecklistHeaderView,
) responsedto.AirRescuerChecklistTabResource {
scopeOrder := airrescuechecklist.AirRescuerChecklistPersistedScopeOrder()
groups := make([]responsedto.AirRescuerChecklistCATGroup, 0, len(scopeOrder))
for i := range scopeOrder {
scopeCode := scopeOrder[i]
headers := headersByScope[scopeCode]
if len(headers) == 0 {
continue
}
checklists := make([]responsedto.AirRescuerChecklistCATChecklistSummary, 0, len(headers))
for j := range headers {
checklists = append(checklists, responsedto.AirRescuerChecklistCATChecklistSummary{
ID: idString(headers[j].ID),
Title: strings.TrimSpace(headers[j].Title),
Position: headers[j].Position,
CreatedBy: uuidStringOrEmpty(headers[j].CreatedBy),
CreatedAt: headers[j].CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: headers[j].UpdatedAt.UTC().Format(time.RFC3339),
})
}
groups = append(groups, responsedto.AirRescuerChecklistCATGroup{
ScopeCode: airrescuechecklist.AirRescuerChecklistScopeCodeEN(scopeCode),
ScopeCodeDe: airrescuechecklist.AirRescuerChecklistScopeCodeDE(scopeCode),
ScopeCodeName: airrescuechecklist.AirRescuerChecklistScopeDisplayName(scopeCode),
ScopeCodeNameDe: airrescuechecklist.AirRescuerChecklistScopeDisplayNameDE(scopeCode),
Checklists: checklists,
})
}
return responsedto.AirRescuerChecklistTabResource{
ScopeCode: airrescuechecklist.AirRescuerChecklistScopeCodeEN(airrescuechecklist.AirRescuerChecklistScopeCAT),
ScopeCodeDe: airrescuechecklist.AirRescuerChecklistScopeCodeDE(airrescuechecklist.AirRescuerChecklistScopeCAT),
ScopeCodeName: airrescuechecklist.AirRescuerChecklistScopeDisplayName(airrescuechecklist.AirRescuerChecklistScopeCAT),
ScopeCodeNameDe: airrescuechecklist.AirRescuerChecklistScopeDisplayNameDE(airrescuechecklist.AirRescuerChecklistScopeCAT),
Groups: groups,
}
}
func buildAirRescuerChecklistDetailTab(
headersByScope map[string][]airrescuechecklist.ChecklistHeaderView,
itemsByChecklist map[string][]airrescuechecklist.ChecklistItemView,
scopeCode string,
) responsedto.AirRescuerChecklistTabResource {
headers := headersByScope[scopeCode]
checklists := make([]responsedto.AirRescuerChecklistTabChecklistDetail, 0, len(headers))
for i := range headers {
key := idString(headers[i].ID)
checklists = append(checklists, responsedto.AirRescuerChecklistTabChecklistDetail{
ID: idString(headers[i].ID),
Title: strings.TrimSpace(headers[i].Title),
Position: headers[i].Position,
ChecklistItems: mapAirRescuerChecklistItemResources(itemsByChecklist[key]),
CreatedBy: uuidStringOrEmpty(headers[i].CreatedBy),
CreatedAt: headers[i].CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: headers[i].UpdatedAt.UTC().Format(time.RFC3339),
})
}
return responsedto.AirRescuerChecklistTabResource{
ScopeCode: airrescuechecklist.AirRescuerChecklistScopeCodeEN(scopeCode),
ScopeCodeDe: airrescuechecklist.AirRescuerChecklistScopeCodeDE(scopeCode),
ScopeCodeName: airrescuechecklist.AirRescuerChecklistScopeDisplayName(scopeCode),
ScopeCodeNameDe: airrescuechecklist.AirRescuerChecklistScopeDisplayNameDE(scopeCode),
Checklists: &checklists,
}
}
func mapAirRescuerChecklistItemResources(items []airrescuechecklist.ChecklistItemView) []responsedto.AirRescuerChecklistItemResource {
out := make([]responsedto.AirRescuerChecklistItemResource, 0, len(items))
for i := range items {
out = append(out, responsedto.AirRescuerChecklistItemResource{
ID: idString(items[i].ID),
Name: strings.TrimSpace(items[i].Name),
Position: items[i].Position,
CreatedBy: uuidStringOrEmpty(items[i].CreatedBy),
CreatedAt: items[i].CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: items[i].UpdatedAt.UTC().Format(time.RFC3339),
})
}
return out
}
func paginateAirRescuerChecklistGroups(
groups []responsedto.AirRescuerChecklistBaseGroupResource,
start,
length int,
) ([]responsedto.AirRescuerChecklistBaseGroupResource, int) {
total := len(groups)
if start > total {
start = total
}
end := start + length
if end > total {
end = total
}
return groups[start:end], total
}

View File

@@ -0,0 +1,359 @@
package handlers
import (
"bytes"
"encoding/json"
"testing"
"time"
airrescuechecklist "wucher/internal/domain/air_rescue_checklist"
"wucher/internal/shared/pkg/uuidv7"
responsedto "wucher/internal/transport/http/dto/response"
)
func TestBuildAirRescuerChecklistGroups_DefaultTabsIncludeCATAndDetail(t *testing.T) {
baseID := uuidv7.MustBytes()
checklistTA := uuidv7.MustBytes()
checklistMO := uuidv7.MustBytes()
itemTA := uuidv7.MustBytes()
now := time.Now().UTC()
bases := []airrescuechecklist.HEMSBaseView{
{ID: baseID, BaseName: "Gallus 1 - Zurs Lech", BaseAbbreviation: "G1"},
}
headers := []airrescuechecklist.ChecklistHeaderView{
{ID: checklistTA, HEMSBaseID: baseID, ScopeCode: airrescuechecklist.AirRescuerChecklistScopeTA, Title: "Cockpit Flugretter", Position: 1, CreatedAt: now, UpdatedAt: now},
{ID: checklistMO, HEMSBaseID: baseID, ScopeCode: airrescuechecklist.AirRescuerChecklistScopeMO, Title: "Monday Checklist", Position: 2, CreatedAt: now, UpdatedAt: now},
}
items := []airrescuechecklist.ChecklistItemView{
{ID: itemTA, ChecklistID: checklistTA, Name: "Kartenmaterial", Position: 1, CreatedAt: now, UpdatedAt: now},
}
groups := buildAirRescuerChecklistGroups(bases, headers, items, "")
if len(groups) != 1 {
t.Fatalf("expected 1 base group, got %d", len(groups))
}
tabs := groups[0].Attributes.Tabs
if len(tabs) != 1+len(airrescuechecklist.AirRescuerChecklistPersistedScopeOrder()) {
t.Fatalf("unexpected tabs count: %d", len(tabs))
}
cat := mustFindChecklistTab(t, tabs, airrescuechecklist.AirRescuerChecklistScopeCAT)
if cat.ScopeCodeName != "Category" || cat.ScopeCodeNameDe != "Category" {
t.Fatalf("unexpected CAT scope names: en=%q de=%q", cat.ScopeCodeName, cat.ScopeCodeNameDe)
}
if len(cat.Groups) != 2 {
t.Fatalf("expected CAT to include only non-empty scopes, got %d groups", len(cat.Groups))
}
for i := range cat.Groups {
if len(cat.Groups[i].Checklists) == 0 {
t.Fatalf("CAT group must have checklists")
}
}
if cat.Groups[0].ScopeCodeDe == airrescuechecklist.AirRescuerChecklistScopeTA &&
(cat.Groups[0].ScopeCodeName != "Daily" || cat.Groups[0].ScopeCodeNameDe != "Taeglich") {
t.Fatalf("unexpected TA group scope names: en=%q de=%q", cat.Groups[0].ScopeCodeName, cat.Groups[0].ScopeCodeNameDe)
}
ta := mustFindChecklistTab(t, tabs, airrescuechecklist.AirRescuerChecklistScopeTA)
if ta.ScopeCodeName != "Daily" || ta.ScopeCodeNameDe != "Taeglich" {
t.Fatalf("unexpected TA scope names: en=%q de=%q", ta.ScopeCodeName, ta.ScopeCodeNameDe)
}
if ta.Checklists == nil || len(*ta.Checklists) != 1 {
t.Fatalf("expected TA detail to have 1 checklist")
}
if len((*ta.Checklists)[0].ChecklistItems) != 1 {
t.Fatalf("expected TA detail checklist to include items")
}
di := mustFindChecklistTab(t, tabs, airrescuechecklist.AirRescuerChecklistScopeDI)
if di.Checklists == nil || len(*di.Checklists) != 0 {
t.Fatalf("expected empty scope detail tab to return checklists: []")
}
raw, err := json.Marshal(groups)
if err != nil {
t.Fatalf("marshal groups: %v", err)
}
if !jsonContainsTabWithEmptyChecklists(raw, airrescuechecklist.AirRescuerChecklistScopeDI) {
t.Fatalf("expected DI tab serialized with checklists: []")
}
}
func TestBuildAirRescuerChecklistGroups_CATOnlyRequest(t *testing.T) {
baseID := uuidv7.MustBytes()
checklistTA := uuidv7.MustBytes()
now := time.Now().UTC()
bases := []airrescuechecklist.HEMSBaseView{
{ID: baseID, BaseName: "Gallus 1 - Zurs Lech", BaseAbbreviation: "G1"},
}
headers := []airrescuechecklist.ChecklistHeaderView{
{ID: checklistTA, HEMSBaseID: baseID, ScopeCode: airrescuechecklist.AirRescuerChecklistScopeTA, Title: "Cockpit Flugretter", Position: 1, CreatedAt: now, UpdatedAt: now},
}
groups := buildAirRescuerChecklistGroups(bases, headers, nil, airrescuechecklist.AirRescuerChecklistScopeCAT)
if len(groups) != 1 {
t.Fatalf("expected 1 base group, got %d", len(groups))
}
if len(groups[0].Attributes.Tabs) != 1 {
t.Fatalf("expected CAT query to return only CAT tab")
}
cat := groups[0].Attributes.Tabs[0]
if cat.ScopeCodeDe != airrescuechecklist.AirRescuerChecklistScopeCAT {
t.Fatalf("expected CAT tab")
}
if cat.ScopeCodeName != "Category" || cat.ScopeCodeNameDe != "Category" {
t.Fatalf("unexpected CAT scope names: en=%q de=%q", cat.ScopeCodeName, cat.ScopeCodeNameDe)
}
if len(cat.Groups) != 1 || cat.Groups[0].ScopeCodeDe != airrescuechecklist.AirRescuerChecklistScopeTA {
t.Fatalf("expected CAT to include only non-empty TA group")
}
if cat.Groups[0].ScopeCodeName != "Daily" || cat.Groups[0].ScopeCodeNameDe != "Taeglich" {
t.Fatalf("unexpected TA group scope names: en=%q de=%q", cat.Groups[0].ScopeCodeName, cat.Groups[0].ScopeCodeNameDe)
}
if cat.Checklists != nil {
t.Fatalf("expected CAT tab to omit detail checklists")
}
}
func TestBuildAirRescuerChecklistGroups_RequestedPersistedScopeCanBeEmpty(t *testing.T) {
baseID := uuidv7.MustBytes()
bases := []airrescuechecklist.HEMSBaseView{
{ID: baseID, BaseName: "Gallus 1 - Zurs Lech", BaseAbbreviation: "G1"},
}
groups := buildAirRescuerChecklistGroups(bases, nil, nil, airrescuechecklist.AirRescuerChecklistScopeM10)
if len(groups) != 1 {
t.Fatalf("expected 1 base group, got %d", len(groups))
}
if len(groups[0].Attributes.Tabs) != 1 {
t.Fatalf("expected one requested tab")
}
tab := groups[0].Attributes.Tabs[0]
if tab.ScopeCodeDe != airrescuechecklist.AirRescuerChecklistScopeM10 {
t.Fatalf("expected M10 tab, got %s", tab.ScopeCodeDe)
}
if tab.ScopeCodeName != "M10" || tab.ScopeCodeNameDe != "M10" {
t.Fatalf("unexpected M10 scope names: en=%q de=%q", tab.ScopeCodeName, tab.ScopeCodeNameDe)
}
if tab.Checklists == nil || len(*tab.Checklists) != 0 {
t.Fatalf("expected requested empty persisted tab to return checklists: []")
}
}
func TestBuildAirRescuerChecklistGroups_CATAndDetailOrderingWithDuplicatePosition(t *testing.T) {
baseID := uuidv7.MustBytes()
taChecklistAlpha := uuidv7.MustBytes()
taChecklistBravo := uuidv7.MustBytes()
taChecklistCharlie := uuidv7.MustBytes()
moChecklist := uuidv7.MustBytes()
now := time.Now().UTC()
bases := []airrescuechecklist.HEMSBaseView{
{ID: baseID, BaseName: "Gallus 1 - Zurs Lech", BaseAbbreviation: "G1"},
}
// Header order here follows repository contract: position ASC, title ASC.
headers := []airrescuechecklist.ChecklistHeaderView{
{ID: taChecklistAlpha, HEMSBaseID: baseID, ScopeCode: airrescuechecklist.AirRescuerChecklistScopeTA, Title: "Alpha", Position: 1, CreatedAt: now, UpdatedAt: now},
{ID: taChecklistBravo, HEMSBaseID: baseID, ScopeCode: airrescuechecklist.AirRescuerChecklistScopeTA, Title: "Bravo", Position: 1, CreatedAt: now, UpdatedAt: now},
{ID: moChecklist, HEMSBaseID: baseID, ScopeCode: airrescuechecklist.AirRescuerChecklistScopeMO, Title: "Lagerbestand", Position: 1, CreatedAt: now, UpdatedAt: now},
{ID: taChecklistCharlie, HEMSBaseID: baseID, ScopeCode: airrescuechecklist.AirRescuerChecklistScopeTA, Title: "Charlie", Position: 2, CreatedAt: now, UpdatedAt: now},
}
// Item order here follows repository contract: position ASC, name ASC.
items := []airrescuechecklist.ChecklistItemView{
{ID: uuidv7.MustBytes(), ChecklistID: taChecklistAlpha, Name: "Oxygen", Position: 1, CreatedAt: now, UpdatedAt: now},
{ID: uuidv7.MustBytes(), ChecklistID: taChecklistAlpha, Name: "Battery", Position: 2, CreatedAt: now, UpdatedAt: now},
{ID: uuidv7.MustBytes(), ChecklistID: taChecklistAlpha, Name: "Radio", Position: 2, CreatedAt: now, UpdatedAt: now},
}
groups := buildAirRescuerChecklistGroups(bases, headers, items, "")
if len(groups) != 1 {
t.Fatalf("expected 1 base group, got %d", len(groups))
}
tabs := groups[0].Attributes.Tabs
cat := mustFindChecklistTab(t, tabs, airrescuechecklist.AirRescuerChecklistScopeCAT)
if cat.Checklists != nil {
t.Fatalf("expected CAT tab to omit detail checklists")
}
if len(cat.Groups) != 2 {
t.Fatalf("expected CAT to include only non-empty TA and MO groups, got %d groups", len(cat.Groups))
}
if cat.Groups[0].ScopeCodeDe != airrescuechecklist.AirRescuerChecklistScopeTA || cat.Groups[1].ScopeCodeDe != airrescuechecklist.AirRescuerChecklistScopeMO {
t.Fatalf("unexpected CAT group order: %+v", cat.Groups)
}
if len(cat.Groups[0].Checklists) != 3 {
t.Fatalf("expected CAT TA group to include 3 checklist summaries")
}
if cat.Groups[0].Checklists[0].Title != "Alpha" || cat.Groups[0].Checklists[1].Title != "Bravo" || cat.Groups[0].Checklists[2].Title != "Charlie" {
t.Fatalf("unexpected CAT TA checklist order: %+v", cat.Groups[0].Checklists)
}
catJSON, err := json.Marshal(cat)
if err != nil {
t.Fatalf("marshal CAT tab: %v", err)
}
if jsonContainsChecklistItemsField(catJSON) {
t.Fatalf("expected CAT summary payload without checklist_items")
}
ta := mustFindChecklistTab(t, tabs, airrescuechecklist.AirRescuerChecklistScopeTA)
if ta.Checklists == nil || len(*ta.Checklists) != 3 {
t.Fatalf("expected TA detail to include 3 checklists")
}
if (*ta.Checklists)[0].Title != "Alpha" || (*ta.Checklists)[1].Title != "Bravo" || (*ta.Checklists)[2].Title != "Charlie" {
t.Fatalf("unexpected TA detail checklist order: %+v", *ta.Checklists)
}
if len((*ta.Checklists)[0].ChecklistItems) != 3 {
t.Fatalf("expected Alpha checklist to include 3 items")
}
if (*ta.Checklists)[0].ChecklistItems[0].Name != "Oxygen" || (*ta.Checklists)[0].ChecklistItems[1].Name != "Battery" || (*ta.Checklists)[0].ChecklistItems[2].Name != "Radio" {
t.Fatalf("unexpected TA checklist item order: %+v", (*ta.Checklists)[0].ChecklistItems)
}
}
func mustFindChecklistTab(t *testing.T, tabs []responsedto.AirRescuerChecklistTabResource, scopeCode string) responsedto.AirRescuerChecklistTabResource {
t.Helper()
for i := range tabs {
if tabs[i].ScopeCodeDe == scopeCode {
return tabs[i]
}
}
t.Fatalf("scope tab %s not found", scopeCode)
return responsedto.AirRescuerChecklistTabResource{}
}
func jsonContainsTabWithEmptyChecklists(raw []byte, scopeCode string) bool {
var payload []map[string]any
if err := json.Unmarshal(raw, &payload); err != nil {
return false
}
for _, group := range payload {
attrsRaw, ok := group["attributes"].(map[string]any)
if !ok {
continue
}
tabsRaw, ok := attrsRaw["tabs"].([]any)
if !ok {
continue
}
for _, tab := range tabsRaw {
tabMap, ok := tab.(map[string]any)
if !ok {
continue
}
if tabMap["scope_code_de"] != scopeCode {
continue
}
checklists, ok := tabMap["checklists"].([]any)
return ok && len(checklists) == 0
}
}
return false
}
func jsonContainsChecklistItemsField(raw []byte) bool {
return bytes.Contains(raw, []byte(`"checklist_items"`))
}
func TestAirRescuerChecklistMapperResourceAndItemData(t *testing.T) {
now := time.Date(2026, 3, 25, 8, 0, 0, 0, time.UTC)
baseID := []byte("1234567890123456")
checklistID := []byte("abcdefghijklmnop")
itemID := []byte("itemitemitemitem")
creatorID := uuidv7.MustBytes()
resource := airRescuerChecklistResource(airrescuechecklist.ChecklistHeaderView{
ID: checklistID,
HEMSBaseID: baseID,
BaseName: " Gallus 1 - Zurs Lech ",
BaseAbbreviation: " G1 ",
BaseCategory: " regular ",
ScopeCode: " TA ",
Title: " Daily Tasks ",
Position: 2,
CreatedBy: creatorID,
CreatedAt: now,
UpdatedAt: now,
}, []airrescuechecklist.ChecklistItemView{{
ID: itemID,
ChecklistID: checklistID,
Name: " Battery ",
Position: 1,
CreatedBy: creatorID,
CreatedAt: now,
UpdatedAt: now,
}})
if resource.Type != "air_rescuer_checklist" {
t.Fatalf("unexpected type: %s", resource.Type)
}
if resource.Attributes.Bases.BaseName != "Gallus 1 - Zurs Lech" {
t.Fatalf("expected trimmed base name, got %q", resource.Attributes.Bases.BaseName)
}
if resource.Attributes.Bases.BaseCategory != "regular" {
t.Fatalf("expected base_category regular, got %q", resource.Attributes.Bases.BaseCategory)
}
if resource.Attributes.ScopeCode != "DA" || resource.Attributes.ScopeCodeDe != "TA" || resource.Attributes.Title != "Daily Tasks" {
t.Fatalf("expected mapped scope/title, got scope=%q scope_de=%q title=%q", resource.Attributes.ScopeCode, resource.Attributes.ScopeCodeDe, resource.Attributes.Title)
}
if len(resource.Attributes.Items) != 1 || resource.Attributes.Items[0].Name != "Battery" {
t.Fatalf("expected mapped and trimmed items")
}
if resource.Attributes.CreatedAt != "2026-03-25T08:00:00Z" {
t.Fatalf("unexpected created_at: %s", resource.Attributes.CreatedAt)
}
if resource.Attributes.CreatedBy == "" || resource.Attributes.Items[0].CreatedBy == "" {
t.Fatalf("expected created_by on checklist resource and item")
}
itemData := airRescuerChecklistItemResponseData(airrescuechecklist.ChecklistItemView{
ID: itemID,
ChecklistID: checklistID,
Name: " Radio ",
Position: 3,
CreatedBy: creatorID,
CreatedAt: now,
UpdatedAt: now,
})
if itemData.Type != "air_rescuer_checklist_item" {
t.Fatalf("unexpected item type: %s", itemData.Type)
}
if itemData.Attributes.Name != "Radio" {
t.Fatalf("expected trimmed item name, got %q", itemData.Attributes.Name)
}
if itemData.Attributes.Position != 3 {
t.Fatalf("expected position=3, got %d", itemData.Attributes.Position)
}
if itemData.Attributes.CreatedBy == "" {
t.Fatalf("expected created_by on item response")
}
}
func TestPaginateAirRescuerChecklistGroups(t *testing.T) {
groups := []responsedto.AirRescuerChecklistBaseGroupResource{
{ID: "base-1"},
{ID: "base-2"},
{ID: "base-3"},
}
got, total := paginateAirRescuerChecklistGroups(groups, 10, 2)
if total != 3 {
t.Fatalf("expected total=3, got %d", total)
}
if len(got) != 0 {
t.Fatalf("expected empty page when start exceeds total")
}
got, total = paginateAirRescuerChecklistGroups(groups, 1, 5)
if total != 3 {
t.Fatalf("expected total=3, got %d", total)
}
if len(got) != 2 || got[0].ID != "base-2" || got[1].ID != "base-3" {
t.Fatalf("unexpected paged groups: %+v", got)
}
}

View File

@@ -0,0 +1,85 @@
package handlers
import (
"errors"
"fmt"
"strings"
"github.com/gofiber/fiber/v2"
"wucher/internal/shared/pkg/attachmentresolver"
"wucher/internal/transport/http/jsonapi"
"wucher/internal/transport/http/response"
)
func resolveAttachmentIDFromInput(c *fiber.Ctx, params attachmentresolver.Params, errorTitle string) ([]byte, bool) {
attachmentID, err := attachmentresolver.Resolve(c.UserContext(), params)
if err == nil {
return attachmentID, false
}
var resolveErr *attachmentresolver.ResolveError
if errors.As(err, &resolveErr) {
switch resolveErr.Code {
case attachmentresolver.ErrorInvalidAttachmentUUID:
writeValidationError(c, fmt.Sprintf("%s is invalid UUID", fieldOrDefault(resolveErr.AttachmentField, "attachment_id")), "/data/attributes/"+fieldOrDefault(resolveErr.AttachmentField, "attachment_id"))
return nil, true
case attachmentresolver.ErrorInvalidFileUUID:
writeValidationError(c, fmt.Sprintf("%s is invalid UUID", fieldOrDefault(resolveErr.FileField, "file_id")), "/data/attributes/"+fieldOrDefault(resolveErr.FileField, "file_id"))
return nil, true
case attachmentresolver.ErrorAttachmentNotFound:
writeValidationError(c, fmt.Sprintf("%s not found", fieldOrDefault(resolveErr.AttachmentField, "attachment_id")), "/data/attributes/"+fieldOrDefault(resolveErr.AttachmentField, "attachment_id"))
return nil, true
case attachmentresolver.ErrorRequireValue:
writeValidationError(c, fmt.Sprintf("%s or %s is required", fieldOrDefault(resolveErr.AttachmentField, "attachment_id"), fieldOrDefault(resolveErr.FileField, "file_id")), "/data/attributes")
return nil, true
case attachmentresolver.ErrorCannotResolveFromFile:
writeValidationError(c, fmt.Sprintf("cannot resolve attachment from %s", fieldOrDefault(resolveErr.FileField, "file_id")), "/data/attributes/"+fieldOrDefault(resolveErr.FileField, "file_id"))
return nil, true
case attachmentresolver.ErrorFileNotReadyForAttachment:
writeValidationError(c, fmt.Sprintf("%s is not ready yet; wait for file.updated SSE event", fieldOrDefault(resolveErr.FileField, "file_id")), "/data/attributes/"+fieldOrDefault(resolveErr.FileField, "file_id"))
return nil, true
case attachmentresolver.ErrorFileManagerUnavailable:
writeBadRequestError(c, errorTitle, resolveErr.Error())
return nil, true
}
}
writeBadRequestError(c, errorTitle, err.Error())
return nil, true
}
func writeValidationError(c *fiber.Ctx, detail, pointer string) {
_ = response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{{
Status: "422",
Title: "Validation error",
Detail: detail,
Source: &jsonapi.ErrorSource{Pointer: pointer},
}})
}
func writeBadRequestError(c *fiber.Ctx, title, detail string) {
if title == "" {
title = "Request failed"
}
_ = response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
Status: "400",
Title: title,
Detail: detail,
}})
}
func fieldOrDefault(v string, fallback string) string {
if v == "" {
return fallback
}
return v
}
func hasNonEmptyOptionalString(v *string) bool {
return v != nil && strings.TrimSpace(*v) != ""
}
func hasBlankOptionalString(v *string) bool {
return v != nil && strings.TrimSpace(*v) == ""
}

View File

@@ -0,0 +1,119 @@
package handlers
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
filemanager "wucher/internal/domain/file_manager"
"wucher/internal/shared/pkg/attachmentresolver"
"wucher/internal/shared/pkg/uuidv7"
)
func TestAttachmentResolverHelpers(t *testing.T) {
app := fiber.New()
app.Get("/invalid-attachment", func(c *fiber.Ctx) error {
raw := "bad"
_, handled := resolveAttachmentIDFromInput(c, attachmentresolver.Params{
RawAttachmentID: &raw,
AttachmentField: "attachment_id",
FileField: "file_uuid",
}, "Update failed")
if !handled {
t.Fatalf("expected handled invalid attachment")
}
return nil
})
app.Get("/invalid-file", func(c *fiber.Ctx) error {
raw := "bad"
_, handled := resolveAttachmentIDFromInput(c, attachmentresolver.Params{
RawFileID: &raw,
FileManager: &fileManagerAttachmentHandlerServiceMock{},
}, "Update failed")
if !handled {
t.Fatalf("expected handled invalid file")
}
return nil
})
app.Get("/cannot-resolve", func(c *fiber.Ctx) error {
raw := mustUUIDStringContact(t, uuidv7.MustBytes())
_, handled := resolveAttachmentIDFromInput(c, attachmentresolver.Params{
RawFileID: &raw,
FileManager: &fileManagerAttachmentHandlerServiceMock{
createAttachmentFn: func(context.Context, filemanager.CreateAttachmentParams) (*filemanager.Attachment, error) {
return nil, filemanager.ErrAttachmentAlreadyExists
},
listAttachmentsFn: func(context.Context, filemanager.ListAttachmentsParams) ([]filemanager.Attachment, int64, error) {
return nil, 0, nil
},
},
}, "Update failed")
if !handled {
t.Fatalf("expected handled cannot resolve")
}
return nil
})
app.Get("/not-ready", func(c *fiber.Ctx) error {
raw := mustUUIDStringContact(t, uuidv7.MustBytes())
_, handled := resolveAttachmentIDFromInput(c, attachmentresolver.Params{
RawFileID: &raw,
FileManager: &fileManagerAttachmentHandlerServiceMock{
createAttachmentFn: func(context.Context, filemanager.CreateAttachmentParams) (*filemanager.Attachment, error) {
return nil, filemanager.ErrFileNotReadyForAttachment
},
getFileFn: func(context.Context, []byte) (*filemanager.File, error) {
return &filemanager.File{Status: filemanager.FileStatusUploaded}, nil
},
},
}, "Update failed")
if !handled {
t.Fatalf("expected handled file-not-ready")
}
return nil
})
app.Get("/bad-request", func(c *fiber.Ctx) error {
raw := mustUUIDStringContact(t, uuidv7.MustBytes())
_, handled := resolveAttachmentIDFromInput(c, attachmentresolver.Params{
RawAttachmentID: &raw,
ValidateAttachmentExists: true,
AttachmentExists: func(context.Context, []byte) (bool, error) {
return false, errors.New("db down")
},
}, "Update failed")
if !handled {
t.Fatalf("expected handled bad request")
}
return nil
})
for _, path := range []string{"/invalid-attachment", "/invalid-file", "/cannot-resolve", "/not-ready", "/bad-request"} {
resp, err := app.Test(httptest.NewRequest(http.MethodGet, path, nil), 30000)
if err != nil {
t.Fatalf("%s err: %v", path, err)
}
if resp.StatusCode == http.StatusOK {
t.Fatalf("expected non-200 for %s", path)
}
}
}
func TestOptionalStringHelpers(t *testing.T) {
if hasNonEmptyOptionalString(nil) || hasBlankOptionalString(nil) {
t.Fatalf("nil optional helpers should be false")
}
blank := " "
if hasNonEmptyOptionalString(&blank) || !hasBlankOptionalString(&blank) {
t.Fatalf("blank optional helper mismatch")
}
val := "x"
if !hasNonEmptyOptionalString(&val) || hasBlankOptionalString(&val) {
t.Fatalf("non-blank optional helper mismatch")
}
if fieldOrDefault("", "a") != "a" || fieldOrDefault("x", "a") != "x" {
t.Fatalf("fieldOrDefault mismatch")
}
}

View File

@@ -0,0 +1,93 @@
package handlers
import (
"context"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"wucher/internal/shared/pkg/userctx"
"wucher/internal/shared/pkg/uuidv7"
responsedto "wucher/internal/transport/http/dto/response"
)
func userRef(id []byte, name string) *responsedto.UserRef {
trimmed := strings.TrimSpace(name)
if len(id) == 0 && trimmed == "" {
return nil
}
if trimmed == "" && len(id) > 0 {
trimmed = userctx.GetDisplayName(context.TODO(), id)
}
return &responsedto.UserRef{
UUID: idString(id),
Name: trimmed,
}
}
// updatedAuditOrCreated returns the "last modified" timestamp (RFC3339) and actor for an
// audited row, falling back to the creation timestamp/actor when the row was never
// updated after creation (updated_by unset — e.g. seed/legacy rows). This guarantees an
// updated_at/updated_by is always presentable instead of silently omitted.
func updatedAuditOrCreated(updatedAt, createdAt time.Time, updatedBy []byte, updatedByName string, createdBy []byte, createdByName string) (string, *responsedto.UserRef) {
if len(updatedBy) == 0 {
updatedBy, updatedByName = createdBy, createdByName
}
if updatedAt.IsZero() {
updatedAt = createdAt
}
at := ""
if !updatedAt.IsZero() {
at = updatedAt.UTC().Format(time.RFC3339)
}
return at, userRef(updatedBy, updatedByName)
}
func actorUserID(c *fiber.Ctx) []byte {
raw := c.Locals("user_id")
userID, ok := raw.([]byte)
if !ok || len(userID) == 0 {
return nil
}
// Return a copy to avoid accidental mutation of local context values.
return append([]byte(nil), userID...)
}
// uuidStringOrEmpty resolves a user ID to their last name using the global userctx getter
// if set; otherwise falls back to the UUID string representation.
func uuidStringOrEmpty(id []byte) string {
if len(id) == 0 {
return ""
}
if name := userctx.GetDisplayName(context.TODO(), id); name != "" {
return name
}
// Fallback to UUID string when no getter is available or user not found.
val, err := uuidv7.BytesToString(id)
if err != nil {
return ""
}
return val
}
// idString converts a resource ID byte slice to a UUID string.
// Unlike uuidStringOrEmpty, this does not attempt user name resolution.
// Use this for primary keys and resource IDs, not for audit/user reference fields.
func idString(id []byte) string {
if len(id) == 0 {
return ""
}
val, err := uuidv7.BytesToString(id)
if err != nil {
return ""
}
return val
}
func timeStringOrEmpty(t *time.Time) string {
if t == nil || t.IsZero() {
return ""
}
return t.UTC().Format(time.RFC3339)
}

View File

@@ -0,0 +1,359 @@
package handlers
import (
"encoding/json"
"fmt"
"slices"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"wucher/internal/domain/audit"
"wucher/internal/service"
sharedconst "wucher/internal/shared/const"
"wucher/internal/shared/pkg/uuidv7"
"wucher/internal/transport/http/dto"
"wucher/internal/transport/http/jsonapi"
"wucher/internal/transport/http/response"
)
type AuditHandler struct {
svc *service.AuditLogService
}
func NewAuditHandler(svc *service.AuditLogService) *AuditHandler {
return &AuditHandler{svc: svc}
}
// ListAuditLogs godoc
// @Summary List audit logs
// @Description JSON:API list with pagination, filtering and sorting.
// @Tags Audit Logs
// @Produce json
// @Param filter[search] query string false "Search by request_id/layer/action/method/path/message (contains)"
// @Param filter[method] query string false "Method filter; single/comma-separated values or ALL/*" Enums(POST,PUT,PATCH,DELETE,UPDATE,ALL,*)
// @Param method query string false "Alias for filter[method]" Enums(POST,PUT,PATCH,DELETE,UPDATE,ALL,*)
// @Param filter[module] query string false "Module filter; single/comma-separated values or ALL/*" Enums(air_rescuer_checklist,audit_log,auth,base,base_regular,base_hems,base_roster,branding,contact,duty_roster,facility,federal_state,file_manager,flight,flight_data,forces_present,health_insurance_company,helicopter,helicopter_file,hems_roster,hospital,icao,insurance_patient_data,land,master_setting,medicine,mission,opc,patient_data,reserve_ac,role,user,vocation,unknown,ALL,*)
// @Param module query string false "Alias for filter[module]" Enums(air_rescuer_checklist,audit_log,auth,base,base_regular,base_hems,base_roster,branding,contact,duty_roster,facility,federal_state,file_manager,flight,flight_data,forces_present,health_insurance_company,helicopter,helicopter_file,hems_roster,hospital,icao,insurance_patient_data,land,master_setting,medicine,mission,opc,patient_data,reserve_ac,role,user,vocation,unknown,ALL,*)
// @Param filter[action] query string false "Action filter; single/comma-separated values or ALL/*" Enums(list,get,create,update,delete,export,custom,ALL,*)
// @Param action query string false "Alias for filter[action]" Enums(list,get,create,update,delete,export,custom,ALL,*)
// @Param page[number] query int false "Page number (default 1)"
// @Param page[size] query int false "Page size (default 20, max 200)"
// @Param sort query string false "Sort order" Enums(created_at,-created_at,module,-module,action,-action,layer,-layer,status_code,-status_code,success,-success)
// @Success 200 {object} jsonapi.Document
// @Failure 400 {object} dto.ErrorResponse
// @Failure 422 {object} dto.ErrorResponse
// @Router /api/v1/audit-logs/get-all [get]
func (h *AuditHandler) List(c *fiber.Ctx) error {
filter := c.Query("filter[search]")
if filter == "" {
filter = c.Query("filter[action]")
}
if filter == "" {
filter = c.Query("filter[layer]")
}
methodFilter := c.Query("filter[method]")
if methodFilter == "" {
methodFilter = c.Query("method")
}
methods := parseAuditMethodFilter(methodFilter)
moduleFilter := c.Query("filter[module]")
if moduleFilter == "" {
moduleFilter = c.Query("module")
}
modules := parseAuditTokenFilter(moduleFilter)
actionFilter := c.Query("filter[action]")
if actionFilter == "" {
actionFilter = c.Query("action")
}
actions := parseAuditTokenFilter(actionFilter)
pageNumber := parseIntDefault(c.Query("page[number]"), 1)
pageSize := parseIntDefault(c.Query("page[size]"), 20)
if pageSize > 200 {
pageSize = 200
}
if pageNumber < 1 {
pageNumber = 1
}
offset := (pageNumber - 1) * pageSize
_ = offset
sort := c.Query("sort")
if errObj := validateAuditListQuery(methods, modules, actions, sort); errObj != nil {
return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{*errObj})
}
logs, total, err := h.svc.List(c.UserContext(), filter, methods, modules, actions, sort, 0, 0)
if err != nil {
return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
Status: "400",
Title: "List failed",
Detail: safeInternalDetail(),
}})
}
data := make([]dto.AuditLogResource, 0, len(logs))
for i := range logs {
data = append(data, auditLogResource(&logs[i]))
}
meta := map[string]any{
"total": total,
}
return response.WriteWithMeta(c, fiber.StatusOK, data, meta)
}
func parseAuditMethodFilter(raw string) []string {
v := strings.TrimSpace(raw)
if v == "" {
return nil
}
parts := strings.Split(v, ",")
out := make([]string, 0, len(parts))
for _, part := range parts {
token := strings.TrimSpace(part)
if token == "" {
continue
}
out = append(out, token)
}
return out
}
func auditLogResource(v *audit.AuditLog) dto.AuditLogResource {
id, _ := uuidv7.BytesToString(v.ID)
actorID := ""
if len(v.ActorUserID) == 16 {
actorID, _ = uuidv7.BytesToString(v.ActorUserID)
}
var metadata any
if len(v.Metadata) > 0 {
_ = json.Unmarshal(v.Metadata, &metadata)
}
module := strings.TrimSpace(v.Module)
if module == "" {
module = inferAuditModuleFromPath(strings.TrimSpace(v.Path))
}
return dto.AuditLogResource{
Type: "audit_log",
ID: id,
Attributes: dto.AuditLogAttributes{
RequestID: strings.TrimSpace(v.RequestID),
ActorUserID: actorID,
Layer: strings.TrimSpace(v.Layer),
Module: module,
Action: strings.TrimSpace(v.Action),
Method: strings.TrimSpace(v.Method),
Path: strings.TrimSpace(v.Path),
StatusCode: v.StatusCode,
Success: v.Success,
IP: strings.TrimSpace(v.IP),
UserAgent: strings.TrimSpace(v.UserAgent),
Message: strings.TrimSpace(v.Message),
Metadata: metadata,
CreatedAt: v.CreatedAt.UTC().Format(time.RFC3339),
},
}
}
func inferAuditModuleFromPath(path string) string {
parts := strings.Split(strings.Trim(strings.ToLower(strings.TrimSpace(path)), "/"), "/")
if len(parts) < 3 || parts[0] != "api" || parts[1] != "v1" {
return sharedconst.AuditModuleUnknown
}
switch parts[2] {
case "air-rescuer-checklist":
return sharedconst.AuditModuleAirRescuerChecklist
case "audit-logs":
return sharedconst.AuditModuleAuditLog
case "auth":
return sharedconst.AuditModuleAuth
case "bases":
return sharedconst.AuditModuleBase
case "branding", "public":
return sharedconst.AuditModuleBranding
case "contacts":
return sharedconst.AuditModuleContact
case "duty-roster":
return sharedconst.AuditModuleDutyRoster
case "dul":
return sharedconst.AuditModuleDUL
case "facilities":
return sharedconst.AuditModuleFacility
case "federal-state":
return sharedconst.AuditModuleFederalState
case "file-manager":
return sharedconst.AuditModuleFileManager
case "flight-data":
return sharedconst.AuditModuleFlightData
case "flights":
return sharedconst.AuditModuleFlight
case "forces-present":
return sharedconst.AuditModuleForcesPresent
case "health-insurance-companies":
return sharedconst.AuditModuleHealthInsuranceCompany
case "helicopter-files":
return sharedconst.AuditModuleHelicopterFile
case "helicopters":
return sharedconst.AuditModuleHelicopter
case "hems-operation", "hems-operation-category", "hems-operational-data":
return sharedconst.AuditModuleUnknown
case "hospital":
return sharedconst.AuditModuleHospital
case "icao":
return sharedconst.AuditModuleICAO
case "insurance-patient-data":
return sharedconst.AuditModuleInsurancePatientData
case "land":
return sharedconst.AuditModuleLand
case "master-settings":
return sharedconst.AuditModuleMasterSetting
case "medicine":
return sharedconst.AuditModuleMedicine
case "mission":
return sharedconst.AuditModuleMission
case "opc":
return sharedconst.AuditModuleOPC
case "patient-data":
return sharedconst.AuditModulePatientData
case "reserve-acs":
return sharedconst.AuditModuleReserveAC
case "roles":
return sharedconst.AuditModuleRole
case "users":
return sharedconst.AuditModuleUser
case "vocation":
return sharedconst.AuditModuleVocation
default:
return sharedconst.AuditModuleUnknown
}
}
func parseAuditTokenFilter(raw string) []string {
v := strings.TrimSpace(raw)
if v == "" {
return nil
}
parts := strings.Split(v, ",")
out := make([]string, 0, len(parts))
for _, part := range parts {
token := strings.TrimSpace(part)
if token == "" {
continue
}
out = append(out, token)
}
return out
}
func validateAuditListQuery(methods, modules, actions []string, sort string) *jsonapi.ErrorObject {
for _, method := range methods {
v := strings.ToUpper(strings.TrimSpace(method))
if v == "" || v == "ALL" || v == "*" {
continue
}
if !slices.Contains([]string{"POST", "PUT", "PATCH", "DELETE", "UPDATE"}, v) {
return &jsonapi.ErrorObject{
Status: "422",
Title: "Validation error",
Detail: fmt.Sprintf("invalid method filter: %s", method),
Source: &jsonapi.ErrorSource{Pointer: "/query/method"},
}
}
}
validModules := []string{
sharedconst.AuditModuleAirRescuerChecklist,
sharedconst.AuditModuleAuditLog,
sharedconst.AuditModuleAuth,
sharedconst.AuditModuleBase,
sharedconst.AuditModuleBaseRegular,
sharedconst.AuditModuleBaseHEMS,
sharedconst.AuditModuleBranding,
sharedconst.AuditModuleContact,
sharedconst.AuditModuleDutyRoster,
sharedconst.AuditModuleDUL,
sharedconst.AuditModuleFacility,
sharedconst.AuditModuleFederalState,
sharedconst.AuditModuleFileManager,
sharedconst.AuditModuleFlightData,
sharedconst.AuditModuleFlight,
sharedconst.AuditModuleForcesPresent,
sharedconst.AuditModuleHealthInsuranceCompany,
sharedconst.AuditModuleHelicopterFile,
sharedconst.AuditModuleHelicopter,
sharedconst.AuditModuleHospital,
sharedconst.AuditModuleICAO,
sharedconst.AuditModuleInsurancePatientData,
sharedconst.AuditModuleLand,
sharedconst.AuditModuleMasterSetting,
sharedconst.AuditModuleMedicine,
sharedconst.AuditModuleMission,
sharedconst.AuditModuleOPC,
sharedconst.AuditModulePatientData,
sharedconst.AuditModuleReserveAC,
sharedconst.AuditModuleRole,
sharedconst.AuditModuleUser,
sharedconst.AuditModuleVocation,
sharedconst.AuditModuleUnknown,
}
for _, module := range modules {
v := strings.ToLower(strings.TrimSpace(module))
if v == "" || v == "all" || v == "*" {
continue
}
if !slices.Contains(validModules, v) {
return &jsonapi.ErrorObject{
Status: "422",
Title: "Validation error",
Detail: fmt.Sprintf("invalid module filter: %s", module),
Source: &jsonapi.ErrorSource{Pointer: "/query/module"},
}
}
}
validActions := []string{
sharedconst.AuditActionList,
sharedconst.AuditActionGet,
sharedconst.AuditActionCreate,
sharedconst.AuditActionUpdate,
sharedconst.AuditActionDelete,
sharedconst.AuditActionExport,
sharedconst.AuditActionCustom,
}
for _, action := range actions {
v := strings.ToLower(strings.TrimSpace(action))
if v == "" || v == "all" || v == "*" {
continue
}
if !slices.Contains(validActions, v) {
return &jsonapi.ErrorObject{
Status: "422",
Title: "Validation error",
Detail: fmt.Sprintf("invalid action filter: %s", action),
Source: &jsonapi.ErrorSource{Pointer: "/query/action"},
}
}
}
if strings.TrimSpace(sort) != "" {
allowed := map[string]struct{}{
"created_at": {}, "-created_at": {},
"module": {}, "-module": {},
"action": {}, "-action": {},
"layer": {}, "-layer": {},
"status_code": {}, "-status_code": {},
"success": {}, "-success": {},
}
if _, ok := allowed[strings.TrimSpace(sort)]; !ok {
return &jsonapi.ErrorObject{
Status: "422",
Title: "Validation error",
Detail: fmt.Sprintf("invalid sort: %s", sort),
Source: &jsonapi.ErrorSource{Pointer: "/query/sort"},
}
}
}
return nil
}

View File

@@ -0,0 +1,286 @@
package handlers
import (
"context"
"errors"
"io"
"net/http"
"strings"
"sync"
"testing"
"time"
"github.com/gofiber/fiber/v2"
"wucher/internal/domain/audit"
"wucher/internal/service"
"wucher/internal/shared/pkg/uuidv7"
)
type mockAuditListRepo struct {
mu sync.Mutex
rows []audit.AuditLog
total int64
err error
lastFilter string
lastMethods []string
lastModules []string
lastActions []string
lastSort string
lastLimit int
lastOffset int
}
func (m *mockAuditListRepo) Create(_ context.Context, _ *audit.AuditLog) error { return nil }
func (m *mockAuditListRepo) List(_ context.Context, filter string, methods []string, modules []string, actions []string, sort string, limit, offset int) ([]audit.AuditLog, int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.lastFilter = filter
m.lastMethods = append([]string(nil), methods...)
m.lastModules = append([]string(nil), modules...)
m.lastActions = append([]string(nil), actions...)
m.lastSort = sort
m.lastLimit = limit
m.lastOffset = offset
if m.err != nil {
return nil, 0, m.err
}
out := make([]audit.AuditLog, len(m.rows))
copy(out, m.rows)
return out, m.total, nil
}
func TestAuditHandler_List(t *testing.T) {
repo := &mockAuditListRepo{
rows: []audit.AuditLog{
{
ID: uuidv7.MustBytes(),
Layer: "api",
Module: "users",
Action: "list",
Method: "GET",
Path: "/api/v1/users/get-all",
StatusCode: 200,
Success: true,
IP: "127.0.0.1",
CreatedAt: time.Now().UTC(),
},
},
total: 1,
}
svc := service.NewAuditLogService(repo, service.AuditLogServiceDependencies{QueueSize: 8, Workers: 1})
h := NewAuditHandler(svc)
app := fiber.New()
app.Get("/api/v1/audit-logs/get-all", h.List)
req, _ := http.NewRequest(http.MethodGet, "/api/v1/audit-logs/get-all?filter[search]=http&method=UPDATE&filter[module]=user&filter[action]=list&sort=-created_at&page[number]=1&page[size]=20", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("app.Test: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
bodyStr := string(body)
if !containsAll(bodyStr, `"type":"audit_log"`, `"total":1`, `"ip":"127.0.0.1"`) {
t.Fatalf("unexpected body: %s", bodyStr)
}
repo.mu.Lock()
defer repo.mu.Unlock()
if repo.lastFilter != "http" {
t.Fatalf("expected filter to be propagated, got %q", repo.lastFilter)
}
if repo.lastSort != "created_at DESC" {
t.Fatalf("expected normalized sort, got %q", repo.lastSort)
}
if len(repo.lastMethods) != 2 || repo.lastMethods[0] != "PUT" || repo.lastMethods[1] != "PATCH" {
t.Fatalf("expected method filter [PUT PATCH], got %#v", repo.lastMethods)
}
if len(repo.lastModules) != 1 || repo.lastModules[0] != "user" {
t.Fatalf("expected module filter [user], got %#v", repo.lastModules)
}
if len(repo.lastActions) != 3 || repo.lastActions[0] != "create" || repo.lastActions[1] != "update" || repo.lastActions[2] != "delete" {
t.Fatalf("expected action filter [create update delete], got %#v", repo.lastActions)
}
if repo.lastLimit != 0 || repo.lastOffset != 0 {
t.Fatalf("unexpected paging limit=%d offset=%d", repo.lastLimit, repo.lastOffset)
}
}
func containsAll(v string, terms ...string) bool {
for i := range terms {
if !strings.Contains(v, terms[i]) {
return false
}
}
return true
}
func TestAuditHandler_List_Branches(t *testing.T) {
t.Run("fallback filter action and clamp page", func(t *testing.T) {
repo := &mockAuditListRepo{
rows: []audit.AuditLog{{ID: uuidv7.MustBytes(), Action: "create", CreatedAt: time.Now().UTC()}},
total: 1,
}
svc := service.NewAuditLogService(repo, service.AuditLogServiceDependencies{QueueSize: 8, Workers: 1})
h := NewAuditHandler(svc)
app := fiber.New()
app.Get("/api/v1/audit-logs/get-all", h.List)
req, _ := http.NewRequest(http.MethodGet, "/api/v1/audit-logs/get-all?filter[action]=create&page[number]=0&page[size]=500&sort=action", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("app.Test: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
repo.mu.Lock()
defer repo.mu.Unlock()
if repo.lastFilter != "create" {
t.Fatalf("expected action fallback filter, got %q", repo.lastFilter)
}
if repo.lastSort != "action ASC" {
t.Fatalf("expected normalized sort, got %q", repo.lastSort)
}
if repo.lastLimit != 0 || repo.lastOffset != 0 {
t.Fatalf("unexpected paging limit=%d offset=%d", repo.lastLimit, repo.lastOffset)
}
})
t.Run("fallback filter layer", func(t *testing.T) {
repo := &mockAuditListRepo{
rows: []audit.AuditLog{{ID: uuidv7.MustBytes(), Layer: "service", CreatedAt: time.Now().UTC()}},
total: 1,
}
svc := service.NewAuditLogService(repo, service.AuditLogServiceDependencies{QueueSize: 8, Workers: 1})
h := NewAuditHandler(svc)
app := fiber.New()
app.Get("/api/v1/audit-logs/get-all", h.List)
req, _ := http.NewRequest(http.MethodGet, "/api/v1/audit-logs/get-all?filter[layer]=service&page[number]=1&page[size]=20", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("app.Test: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
repo.mu.Lock()
defer repo.mu.Unlock()
if repo.lastFilter != "service" {
t.Fatalf("expected layer fallback filter, got %q", repo.lastFilter)
}
})
t.Run("list error", func(t *testing.T) {
repo := &mockAuditListRepo{err: errors.New("list failed")}
svc := service.NewAuditLogService(repo, service.AuditLogServiceDependencies{QueueSize: 8, Workers: 1})
h := NewAuditHandler(svc)
app := fiber.New()
app.Get("/api/v1/audit-logs/get-all", h.List)
req, _ := http.NewRequest(http.MethodGet, "/api/v1/audit-logs/get-all", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("app.Test: %v", err)
}
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", resp.StatusCode)
}
})
t.Run("default mutation filter applied when query is empty", func(t *testing.T) {
repo := &mockAuditListRepo{
rows: []audit.AuditLog{{ID: uuidv7.MustBytes(), Action: "update", Method: "PATCH", CreatedAt: time.Now().UTC()}},
total: 1,
}
svc := service.NewAuditLogService(repo, service.AuditLogServiceDependencies{QueueSize: 8, Workers: 1})
h := NewAuditHandler(svc)
app := fiber.New()
app.Get("/api/v1/audit-logs/get-all", h.List)
req, _ := http.NewRequest(http.MethodGet, "/api/v1/audit-logs/get-all", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("app.Test: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
repo.mu.Lock()
defer repo.mu.Unlock()
if len(repo.lastMethods) != 4 || repo.lastMethods[0] != "POST" || repo.lastMethods[1] != "PUT" || repo.lastMethods[2] != "PATCH" || repo.lastMethods[3] != "DELETE" {
t.Fatalf("expected default method filter [POST PUT PATCH DELETE], got %#v", repo.lastMethods)
}
if len(repo.lastActions) != 3 || repo.lastActions[0] != "create" || repo.lastActions[1] != "update" || repo.lastActions[2] != "delete" {
t.Fatalf("expected default action filter [create update delete], got %#v", repo.lastActions)
}
})
t.Run("invalid module query returns 422", func(t *testing.T) {
repo := &mockAuditListRepo{}
svc := service.NewAuditLogService(repo, service.AuditLogServiceDependencies{QueueSize: 8, Workers: 1})
h := NewAuditHandler(svc)
app := fiber.New()
app.Get("/api/v1/audit-logs/get-all", h.List)
req, _ := http.NewRequest(http.MethodGet, "/api/v1/audit-logs/get-all?module=bad-module", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("app.Test: %v", err)
}
if resp.StatusCode != http.StatusUnprocessableEntity {
t.Fatalf("expected 422, got %d", resp.StatusCode)
}
})
}
func TestAuditLogResource_Branches(t *testing.T) {
now := time.Now().UTC().Truncate(time.Second)
row := &audit.AuditLog{
ID: uuidv7.MustBytes(),
RequestID: " req-1 ",
ActorUserID: uuidv7.MustBytes(),
Layer: " service ",
Action: " create ",
Method: " POST ",
Path: " /api/v1/reserve-acs/get-all ",
StatusCode: 201,
Success: true,
IP: " 127.0.0.1 ",
UserAgent: " ua ",
Message: " ok ",
Metadata: []byte(`{"module":"auth","count":2}`),
CreatedAt: now,
}
res := auditLogResource(row)
if res.Type != "audit_log" || res.ID == "" {
t.Fatalf("expected audit resource type/id")
}
if res.Attributes.ActorUserID == "" {
t.Fatalf("expected actor_user_id to be encoded")
}
if res.Attributes.RequestID != "req-1" || res.Attributes.Layer != "service" || res.Attributes.Action != "create" {
t.Fatalf("expected trimmed string attributes")
}
if res.Attributes.Module != "reserve_ac" {
t.Fatalf("expected inferred module reserve_ac, got %q", res.Attributes.Module)
}
md, ok := res.Attributes.Metadata.(map[string]any)
if !ok || md["module"] != "auth" {
t.Fatalf("expected metadata map decoded, got %#v", res.Attributes.Metadata)
}
if res.Attributes.CreatedAt == "" {
t.Fatalf("expected created_at in resource")
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More