Documentation
¶
Index ¶
- Variables
- func GetCheckInterval() time.Duration
- type AircraftService
- func (s *AircraftService) CountAircraft(ctx context.Context, userID uuid.UUID) (int, error)
- func (s *AircraftService) CreateAircraft(ctx context.Context, aircraft *models.Aircraft) error
- func (s *AircraftService) DeleteAircraft(ctx context.Context, id, userID uuid.UUID) error
- func (s *AircraftService) GetAircraft(ctx context.Context, id, userID uuid.UUID) (*models.Aircraft, error)
- func (s *AircraftService) ListAircraft(ctx context.Context, userID uuid.UUID) ([]*models.Aircraft, error)
- func (s *AircraftService) UpdateAircraft(ctx context.Context, aircraft *models.Aircraft, userID uuid.UUID) error
- type AuthService
- func (s *AuthService) ChangePassword(ctx context.Context, userID uuid.UUID, currentPassword, newPassword string) error
- func (s *AuthService) DeleteUser(ctx context.Context, userID uuid.UUID, password string) error
- func (s *AuthService) GenerateTokensForUser(ctx context.Context, userID uuid.UUID) (*TokenPair, error)
- func (s *AuthService) GetUserByID(ctx context.Context, userID uuid.UUID) (*models.User, error)
- func (s *AuthService) Login(ctx context.Context, input LoginInput) (*models.User, *TokenPair, error)
- func (s *AuthService) Logout(ctx context.Context, refreshToken string) error
- func (s *AuthService) RefreshToken(ctx context.Context, refreshToken string) (*TokenPair, error)
- func (s *AuthService) Register(ctx context.Context, input RegisterInput) (*models.User, *TokenPair, error)
- func (s *AuthService) RequestPasswordReset(ctx context.Context, email string) (token, userEmail string, err error)
- func (s *AuthService) ResetPassword(ctx context.Context, token, newPassword string) error
- func (s *AuthService) UpdateUser(ctx context.Context, user *models.User) error
- type ClassRatingRepository
- type ClassRatingService
- func (s *ClassRatingService) CreateClassRating(ctx context.Context, cr *models.ClassRating, userID uuid.UUID) error
- func (s *ClassRatingService) DeleteClassRating(ctx context.Context, ratingID, userID uuid.UUID) error
- func (s *ClassRatingService) ListClassRatings(ctx context.Context, licenseID, userID uuid.UUID) ([]*models.ClassRating, error)
- func (s *ClassRatingService) UpdateClassRating(ctx context.Context, cr *models.ClassRating, userID uuid.UUID) error
- type ContactService
- func (s *ContactService) CreateContact(ctx context.Context, contact *models.Contact) error
- func (s *ContactService) DeleteContact(ctx context.Context, id, userID uuid.UUID) error
- func (s *ContactService) FindOrCreateContact(ctx context.Context, userID uuid.UUID, name string) (*models.Contact, bool, error)
- func (s *ContactService) GetContact(ctx context.Context, id, userID uuid.UUID) (*models.Contact, error)
- func (s *ContactService) ListContacts(ctx context.Context, userID uuid.UUID) ([]*models.Contact, error)
- func (s *ContactService) SearchContacts(ctx context.Context, userID uuid.UUID, query string, limit int) ([]*models.Contact, error)
- func (s *ContactService) UpdateContact(ctx context.Context, contact *models.Contact, userID uuid.UUID) error
- type CredentialService
- func (s *CredentialService) CreateCredential(ctx context.Context, credential *models.Credential) error
- func (s *CredentialService) DeleteCredential(ctx context.Context, id, userID uuid.UUID) error
- func (s *CredentialService) GetCredential(ctx context.Context, id, userID uuid.UUID) (*models.Credential, error)
- func (s *CredentialService) ListCredentials(ctx context.Context, userID uuid.UUID) ([]*models.Credential, error)
- func (s *CredentialService) UpdateCredential(ctx context.Context, credential *models.Credential, userID uuid.UUID) error
- type CurrencyResult
- type FlightService
- func (s *FlightService) CountFlights(ctx context.Context, userID uuid.UUID, opts *repository.FlightQueryOptions) (int, error)
- func (s *FlightService) CreateFlight(ctx context.Context, flight *models.Flight) error
- func (s *FlightService) DeleteAllFlights(ctx context.Context, userID uuid.UUID) (int64, error)
- func (s *FlightService) DeleteFlight(ctx context.Context, flightID, userID uuid.UUID) error
- func (s *FlightService) GetCurrency(ctx context.Context, userID uuid.UUID) (*CurrencyResult, error)
- func (s *FlightService) GetFlight(ctx context.Context, flightID, userID uuid.UUID) (*models.Flight, error)
- func (s *FlightService) GetStatsByUserID(ctx context.Context, userID uuid.UUID, startDate, endDate *time.Time) (*models.FlightStatistics, error)
- func (s *FlightService) ListFlights(ctx context.Context, userID uuid.UUID, opts *repository.FlightQueryOptions) ([]*models.Flight, error)
- func (s *FlightService) UpdateFlight(ctx context.Context, flight *models.Flight, userID uuid.UUID) error
- type LicenseService
- func (s *LicenseService) CreateLicense(ctx context.Context, license *models.License) error
- func (s *LicenseService) DeleteLicense(ctx context.Context, licenseID, userID uuid.UUID) error
- func (s *LicenseService) GetLicense(ctx context.Context, licenseID, userID uuid.UUID) (*models.License, error)
- func (s *LicenseService) ListLicenses(ctx context.Context, userID uuid.UUID) ([]*models.License, error)
- func (s *LicenseService) UpdateLicense(ctx context.Context, license *models.License, userID uuid.UUID) error
- type LoginInput
- type NotificationService
- func (s *NotificationService) GetNotificationHistory(ctx context.Context, userID uuid.UUID, limit, offset int) ([]*models.NotificationLog, int, error)
- func (s *NotificationService) GetPreferences(ctx context.Context, userID uuid.UUID) (*models.NotificationPreferences, error)
- func (s *NotificationService) StartBackgroundChecker(ctx context.Context, interval time.Duration)
- func (s *NotificationService) TriggerCheck(ctx context.Context)
- func (s *NotificationService) UpdatePreferences(ctx context.Context, prefs *models.NotificationPreferences) error
- type RegisterInput
- type TokenPair
- type TwoFactorService
- func (s *TwoFactorService) Disable(ctx context.Context, userID uuid.UUID, password string) error
- func (s *TwoFactorService) IsEnabled(ctx context.Context, userID uuid.UUID) (bool, error)
- func (s *TwoFactorService) SetupTOTP(ctx context.Context, userID uuid.UUID) (string, string, error)
- func (s *TwoFactorService) ValidateTOTP(ctx context.Context, userID uuid.UUID, code string) (bool, error)
- func (s *TwoFactorService) VerifyAndEnable(ctx context.Context, userID uuid.UUID, code string) ([]string, error)
- type WebAuthnService
- func (s *WebAuthnService) BeginLogin(ctx context.Context, email string) (sessionID string, options *protocol.CredentialAssertion, err error)
- func (s *WebAuthnService) BeginRegistration(ctx context.Context, userID uuid.UUID) (sessionID string, options *protocol.CredentialCreation, err error)
- func (s *WebAuthnService) DeleteCredential(ctx context.Context, userID, credentialID uuid.UUID) error
- func (s *WebAuthnService) FinishLogin(ctx context.Context, sessionID string, responseJSON []byte) (*models.User, *TokenPair, error)
- func (s *WebAuthnService) FinishRegistration(ctx context.Context, userID uuid.UUID, sessionID string, label *string, ...) (*models.WebAuthnCredential, error)
- func (s *WebAuthnService) ListCredentials(ctx context.Context, userID uuid.UUID) ([]*models.WebAuthnCredential, error)
Constants ¶
This section is empty.
Variables ¶
var ( ErrAircraftNotFound = errors.New("aircraft not found") ErrDuplicateRegistration = errors.New("aircraft registration already exists") )
var ( ErrUserAlreadyExists = errors.New("user already exists") ErrInvalidCredentials = errors.New("invalid credentials") ErrInvalidToken = errors.New("invalid token") ErrTokenExpired = errors.New("token expired") ErrTokenRevoked = errors.New("token revoked") ErrTokenUsed = errors.New("token already used") ErrAccountLocked = errors.New("account temporarily locked due to too many failed login attempts") ErrAccountDisabled = errors.New("account disabled by administrator") ErrPasswordTooShort = errors.New("password must be at least 12 characters") ErrPasswordTooLong = errors.New("password must not exceed 72 characters") ErrEmailRequired = errors.New("email is required") ErrPasswordRequired = errors.New("password is required") ErrNameRequired = errors.New("name is required") ErrInvalidEmail = errors.New("invalid email format") ErrEmailTooLong = errors.New("email must not exceed 255 characters") )
var ( ErrClassRatingNotFound = errors.New("class rating not found") ErrInvalidClassType = errors.New("invalid class type") )
var ( ErrContactNotFound = errors.New("contact not found") )
var ( ErrCredentialNotFound = errors.New("credential not found") ErrExpiryBeforeIssue = errors.New("expiry date must be after issue date") )
var ( ErrFlightNotFound = errors.New("flight not found") ErrInvalidFlight = errors.New("invalid flight data") )
var ( ErrLicenseNotFound = errors.New("license not found") ErrInvalidLicense = errors.New("invalid license data") ErrLicenseAlreadyExists = errors.New("license already exists") )
var ( // NotificationCheckRunsTotal counts total background notification check runs. NotificationCheckRunsTotal = prometheus.NewCounter( prometheus.CounterOpts{ Name: "notification_check_runs_total", Help: "Total number of background notification check runs.", }, ) // NotificationCheckDurationSeconds tracks how long each notification check takes. NotificationCheckDurationSeconds = prometheus.NewHistogram( prometheus.HistogramOpts{ Name: "notification_check_duration_seconds", Help: "Duration of each background notification check run in seconds.", Buckets: prometheus.DefBuckets, }, ) // NotificationsSentTotal counts notifications sent by type. NotificationsSentTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "notifications_sent_total", Help: "Total number of notifications sent.", }, []string{"type"}, ) )
var ( ErrTwoFactorAlreadyEnabled = errors.New("two-factor authentication is already enabled") ErrTwoFactorNotEnabled = errors.New("two-factor authentication is not enabled") ErrInvalidTOTPCode = errors.New("invalid TOTP code") ErrInvalid2FAToken = errors.New("invalid two-factor token") )
var ( ErrWebAuthnNotConfigured = errors.New("webauthn is not configured") ErrWebAuthnSessionNotFound = errors.New("webauthn session not found or expired") ErrWebAuthnSessionExpired = errors.New("webauthn session expired") ErrWebAuthnInvalidResponse = errors.New("invalid webauthn response") ErrWebAuthnUnknownCredential = errors.New("unknown webauthn credential") ErrWebAuthnVerification = errors.New("webauthn verification failed") )
Functions ¶
func GetCheckInterval ¶
GetCheckInterval reads NOTIFICATION_CHECK_INTERVAL env var, defaults to 1h
Types ¶
type AircraftService ¶
type AircraftService struct {
// contains filtered or unexported fields
}
func NewAircraftService ¶
func NewAircraftService(aircraftRepo repository.AircraftRepository) *AircraftService
func (*AircraftService) CountAircraft ¶
func (*AircraftService) CreateAircraft ¶
func (*AircraftService) DeleteAircraft ¶
func (*AircraftService) GetAircraft ¶
func (*AircraftService) ListAircraft ¶
func (*AircraftService) UpdateAircraft ¶
type AuthService ¶
type AuthService struct {
// contains filtered or unexported fields
}
func NewAuthService ¶
func NewAuthService( userRepo repository.UserRepository, refreshTokenRepo repository.RefreshTokenRepository, passwordResetRepo repository.PasswordResetTokenRepository, jwtManager *jwt.Manager, ) *AuthService
func (*AuthService) ChangePassword ¶
func (s *AuthService) ChangePassword(ctx context.Context, userID uuid.UUID, currentPassword, newPassword string) error
ChangePassword changes the user's password after verifying the current password
func (*AuthService) DeleteUser ¶
DeleteUser permanently deletes a user account after verifying the password
func (*AuthService) GenerateTokensForUser ¶
func (s *AuthService) GenerateTokensForUser(ctx context.Context, userID uuid.UUID) (*TokenPair, error)
GenerateTokensForUser generates access and refresh tokens for a user (used after 2FA verification)
func (*AuthService) GetUserByID ¶
GetUserByID retrieves a user by ID
func (*AuthService) Login ¶
func (s *AuthService) Login(ctx context.Context, input LoginInput) (*models.User, *TokenPair, error)
Login authenticates a user and returns tokens
func (*AuthService) Logout ¶
func (s *AuthService) Logout(ctx context.Context, refreshToken string) error
Logout revokes a refresh token
func (*AuthService) RefreshToken ¶
RefreshToken generates a new access token using a refresh token
func (*AuthService) Register ¶
func (s *AuthService) Register(ctx context.Context, input RegisterInput) (*models.User, *TokenPair, error)
Register creates a new user account
func (*AuthService) RequestPasswordReset ¶
func (s *AuthService) RequestPasswordReset(ctx context.Context, email string) (token, userEmail string, err error)
RequestPasswordReset creates a password reset token.
On success it returns both the reset token and the recipient email address loaded from the database. The handler uses the database-sourced email (rather than the HTTP request) when sending the reset mail, which keeps untrusted input out of the SMTP message (CWE-640).
func (*AuthService) ResetPassword ¶
func (s *AuthService) ResetPassword(ctx context.Context, token, newPassword string) error
ResetPassword resets a user's password using a reset token
func (*AuthService) UpdateUser ¶
UpdateUser updates user information
type ClassRatingRepository ¶
type ClassRatingRepository interface {
Create(ctx context.Context, cr *models.ClassRating) error
GetByID(ctx context.Context, id uuid.UUID) (*models.ClassRating, error)
GetByLicenseID(ctx context.Context, licenseID uuid.UUID) ([]*models.ClassRating, error)
Update(ctx context.Context, cr *models.ClassRating) error
Delete(ctx context.Context, id uuid.UUID) error
}
ClassRatingRepository defines the interface for class rating data access
type ClassRatingService ¶
type ClassRatingService struct {
// contains filtered or unexported fields
}
func NewClassRatingService ¶
func NewClassRatingService(crRepo ClassRatingRepository, licRepo repository.LicenseRepository) *ClassRatingService
func (*ClassRatingService) CreateClassRating ¶
func (s *ClassRatingService) CreateClassRating(ctx context.Context, cr *models.ClassRating, userID uuid.UUID) error
func (*ClassRatingService) DeleteClassRating ¶
func (*ClassRatingService) ListClassRatings ¶
func (s *ClassRatingService) ListClassRatings(ctx context.Context, licenseID, userID uuid.UUID) ([]*models.ClassRating, error)
func (*ClassRatingService) UpdateClassRating ¶
func (s *ClassRatingService) UpdateClassRating(ctx context.Context, cr *models.ClassRating, userID uuid.UUID) error
type ContactService ¶
type ContactService struct {
// contains filtered or unexported fields
}
func NewContactService ¶
func NewContactService(contactRepo repository.ContactRepository) *ContactService
func (*ContactService) CreateContact ¶
func (*ContactService) DeleteContact ¶
func (*ContactService) FindOrCreateContact ¶
func (s *ContactService) FindOrCreateContact(ctx context.Context, userID uuid.UUID, name string) (*models.Contact, bool, error)
FindOrCreateContact finds an existing contact by exact name (case-insensitive) or creates a new one. Returns the contact and whether it was newly created.
func (*ContactService) GetContact ¶
func (*ContactService) ListContacts ¶
func (*ContactService) SearchContacts ¶
func (*ContactService) UpdateContact ¶
type CredentialService ¶
type CredentialService struct {
// contains filtered or unexported fields
}
func NewCredentialService ¶
func NewCredentialService(credentialRepo repository.CredentialRepository) *CredentialService
func (*CredentialService) CreateCredential ¶
func (s *CredentialService) CreateCredential(ctx context.Context, credential *models.Credential) error
func (*CredentialService) DeleteCredential ¶
func (*CredentialService) GetCredential ¶
func (s *CredentialService) GetCredential(ctx context.Context, id, userID uuid.UUID) (*models.Credential, error)
func (*CredentialService) ListCredentials ¶
func (s *CredentialService) ListCredentials(ctx context.Context, userID uuid.UUID) ([]*models.Credential, error)
func (*CredentialService) UpdateCredential ¶
func (s *CredentialService) UpdateCredential(ctx context.Context, credential *models.Credential, userID uuid.UUID) error
type CurrencyResult ¶
type CurrencyResult struct {
IsCurrent bool
DaysCurrent bool
NightsCurrent bool
Flights90Days int
TotalLandings int
DayLandings int
NightLandings int
RequiredDay int
RequiredNight int
}
CurrencyResult holds the calculated currency status
type FlightService ¶
type FlightService struct {
// contains filtered or unexported fields
}
func NewFlightService ¶
func NewFlightService(flightRepo repository.FlightRepository) *FlightService
func (*FlightService) CountFlights ¶
func (s *FlightService) CountFlights(ctx context.Context, userID uuid.UUID, opts *repository.FlightQueryOptions) (int, error)
CountFlights counts flights for a user with optional filters
func (*FlightService) CreateFlight ¶
CreateFlight creates a new flight log entry
func (*FlightService) DeleteAllFlights ¶
DeleteAllFlights deletes all flights for a user
func (*FlightService) DeleteFlight ¶
DeleteFlight deletes a flight and verifies user ownership
func (*FlightService) GetCurrency ¶
func (s *FlightService) GetCurrency(ctx context.Context, userID uuid.UUID) (*CurrencyResult, error)
GetCurrency calculates currency status for a user based on EASA/FAA rules. FAA 14 CFR 61.57: 3 takeoffs and landings in the preceding 90 days EASA FCL.060: Recent experience — 3 takeoffs and landings in the preceding 90 days (simplified)
func (*FlightService) GetFlight ¶
func (s *FlightService) GetFlight(ctx context.Context, flightID, userID uuid.UUID) (*models.Flight, error)
GetFlight retrieves a flight by ID and verifies user ownership
func (*FlightService) GetStatsByUserID ¶
func (s *FlightService) GetStatsByUserID(ctx context.Context, userID uuid.UUID, startDate, endDate *time.Time) (*models.FlightStatistics, error)
GetStatsByUserID returns aggregated statistics for a user
func (*FlightService) ListFlights ¶
func (s *FlightService) ListFlights(ctx context.Context, userID uuid.UUID, opts *repository.FlightQueryOptions) ([]*models.Flight, error)
ListFlights retrieves flights for a user with optional filters
func (*FlightService) UpdateFlight ¶
func (s *FlightService) UpdateFlight(ctx context.Context, flight *models.Flight, userID uuid.UUID) error
UpdateFlight updates a flight and verifies user ownership
type LicenseService ¶
type LicenseService struct {
// contains filtered or unexported fields
}
func NewLicenseService ¶
func NewLicenseService(licenseRepo repository.LicenseRepository) *LicenseService
func (*LicenseService) CreateLicense ¶
CreateLicense creates a new license for a user
func (*LicenseService) DeleteLicense ¶
DeleteLicense deletes a license and verifies user ownership
func (*LicenseService) GetLicense ¶
func (s *LicenseService) GetLicense(ctx context.Context, licenseID, userID uuid.UUID) (*models.License, error)
GetLicense retrieves a license by ID and verifies user ownership
func (*LicenseService) ListLicenses ¶
func (s *LicenseService) ListLicenses(ctx context.Context, userID uuid.UUID) ([]*models.License, error)
ListLicenses retrieves all licenses for a user
func (*LicenseService) UpdateLicense ¶
func (s *LicenseService) UpdateLicense(ctx context.Context, license *models.License, userID uuid.UUID) error
UpdateLicense updates a license and verifies user ownership
type LoginInput ¶
type NotificationService ¶
type NotificationService struct {
// contains filtered or unexported fields
}
func NewNotificationService ¶
func NewNotificationService( notifRepo repository.NotificationRepository, credentialRepo repository.CredentialRepository, flightRepo repository.FlightRepository, licenseRepo repository.LicenseRepository, userRepo repository.UserRepository, emailSender *email.Sender, currencyService *currency.Service, ) *NotificationService
func (*NotificationService) GetNotificationHistory ¶
func (s *NotificationService) GetNotificationHistory(ctx context.Context, userID uuid.UUID, limit, offset int) ([]*models.NotificationLog, int, error)
GetNotificationHistory returns paginated notification history for a user
func (*NotificationService) GetPreferences ¶
func (s *NotificationService) GetPreferences(ctx context.Context, userID uuid.UUID) (*models.NotificationPreferences, error)
GetPreferences returns notification preferences for a user
func (*NotificationService) StartBackgroundChecker ¶
func (s *NotificationService) StartBackgroundChecker(ctx context.Context, interval time.Duration)
StartBackgroundChecker starts a goroutine that checks for notifications periodically
func (*NotificationService) TriggerCheck ¶
func (s *NotificationService) TriggerCheck(ctx context.Context)
TriggerCheck runs the notification check immediately for all users, bypassing the check-hour gating. Used by admin maintenance endpoint and E2E tests.
func (*NotificationService) UpdatePreferences ¶
func (s *NotificationService) UpdatePreferences(ctx context.Context, prefs *models.NotificationPreferences) error
UpdatePreferences updates notification preferences for a user
type RegisterInput ¶
type TwoFactorService ¶
type TwoFactorService struct {
// contains filtered or unexported fields
}
func NewTwoFactorService ¶
func NewTwoFactorService(userRepo repository.UserRepository, jwtManager *jwt.Manager) *TwoFactorService
func (*TwoFactorService) SetupTOTP ¶
SetupTOTP generates a new TOTP secret for a user (does not enable 2FA yet)
func (*TwoFactorService) ValidateTOTP ¶
func (s *TwoFactorService) ValidateTOTP(ctx context.Context, userID uuid.UUID, code string) (bool, error)
ValidateTOTP validates a TOTP code or recovery code for a user
func (*TwoFactorService) VerifyAndEnable ¶
func (s *TwoFactorService) VerifyAndEnable(ctx context.Context, userID uuid.UUID, code string) ([]string, error)
VerifyAndEnable verifies a TOTP code and enables 2FA, returning recovery codes
type WebAuthnService ¶ added in v1.2.0
type WebAuthnService struct {
// contains filtered or unexported fields
}
WebAuthnService implements passkey registration & login flows.
func NewWebAuthnService ¶ added in v1.2.0
func NewWebAuthnService( rpID, rpName string, rpOrigins []string, credRepo repository.WebAuthnCredentialRepository, sessionRepo repository.WebAuthnSessionRepository, userRepo repository.UserRepository, authService *AuthService, ) (*WebAuthnService, error)
NewWebAuthnService creates a new WebAuthnService. Returns nil and an error if the webauthn library cannot be initialized with the given config (e.g. invalid origins).
func (*WebAuthnService) BeginLogin ¶ added in v1.2.0
func (s *WebAuthnService) BeginLogin(ctx context.Context, email string) (sessionID string, options *protocol.CredentialAssertion, err error)
BeginLogin starts a passkey login ceremony. If email is non-empty, the user's existing credentials are advertised; otherwise a discoverable-credential challenge is issued.
func (*WebAuthnService) BeginRegistration ¶ added in v1.2.0
func (s *WebAuthnService) BeginRegistration(ctx context.Context, userID uuid.UUID) (sessionID string, options *protocol.CredentialCreation, err error)
BeginRegistration starts a passkey registration ceremony for an authenticated user.
func (*WebAuthnService) DeleteCredential ¶ added in v1.2.0
func (s *WebAuthnService) DeleteCredential(ctx context.Context, userID, credentialID uuid.UUID) error
DeleteCredential revokes a passkey owned by the given user.
func (*WebAuthnService) FinishLogin ¶ added in v1.2.0
func (s *WebAuthnService) FinishLogin(ctx context.Context, sessionID string, responseJSON []byte) (*models.User, *TokenPair, error)
FinishLogin verifies an assertion and returns the authenticated user with a new TokenPair.
func (*WebAuthnService) FinishRegistration ¶ added in v1.2.0
func (s *WebAuthnService) FinishRegistration(ctx context.Context, userID uuid.UUID, sessionID string, label *string, responseJSON []byte) (*models.WebAuthnCredential, error)
FinishRegistration verifies an attestation and stores the new credential.
func (*WebAuthnService) ListCredentials ¶ added in v1.2.0
func (s *WebAuthnService) ListCredentials(ctx context.Context, userID uuid.UUID) ([]*models.WebAuthnCredential, error)
ListCredentials returns the registered passkeys for a user.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package flightrules is the single source of truth for "who/what counts as PIC, Dual, FI, Night, IFR, MP/SP …" across the codebase.
|
Package flightrules is the single source of truth for "who/what counts as PIC, Dual, FI, Night, IFR, MP/SP …" across the codebase. |