models

package
v1.3.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// MaxDocumentFileBytes caps a single upload. Phone camera JPEGs of a
	// licence booklet, and the PDFs an authority emails, both land well under
	// this; anything larger is a scan nobody needs at full resolution.
	MaxDocumentFileBytes = 5 * 1024 * 1024

	// MaxDocumentFilesPerSubject caps how many files one licence or
	// credential can carry — front, back, and a couple of ratings pages.
	MaxDocumentFilesPerSubject = 5

	// MaxDocumentFilePixels bounds the decoded pixel count independently of
	// the byte size. A 5 MB PNG can declare a 40000×40000 canvas that costs
	// gigabytes to rasterize; the byte cap alone does not stop a
	// decompression bomb, and DecodeConfig gives us the dimensions without
	// allocating the pixel buffer.
	MaxDocumentFilePixels = 50_000_000

	// MaxDocumentFileFilenameLen / MaxDocumentFileCaptionLen bound the
	// display-only text fields.
	MaxDocumentFileFilenameLen = 255
	MaxDocumentFileCaptionLen  = 200
)
View Source
const (
	ContentTypeJPEG = "image/jpeg"
	ContentTypePNG  = "image/png"
	ContentTypePDF  = "application/pdf"
)

Content types accepted for upload.

JPEG and PNG are decodable by the standard library, so an upload can be re-verified server-side rather than trusted from its declared Content-Type. WebP is absent because there is no stdlib decoder, so it could not be verified the same way. SVG is deliberately absent and always will be: it is a document format that executes script, and serving one back from our own origin would be stored XSS.

PDF is accepted because it is what authorities actually issue — but it cannot be verified by decoding, and it is an active format in its own right (scripts, embedded files). It earns its place by never being rendered in our origin: ContentTypeIsInlineSafe is false for it, so it is served as an attachment.

View Source
const (
	FlightSessionStatusOpen      = "open"
	FlightSessionStatusCompleted = "completed"
	FlightSessionStatusDiscarded = "discarded"
)

Flight session status values

View Source
const (
	FlightSessionEventOffBlock = "offblock"
	FlightSessionEventTakeoff  = "takeoff"
	FlightSessionEventLanding  = "landing"
	FlightSessionEventOnBlock  = "onblock"
)

Flight session event types

View Source
const (
	MinSignatureRequestExpiryHours     = 1
	MaxSignatureRequestExpiryHours     = 24 * 30 // 30 days
	DefaultSignatureRequestExpiryHours = 24 * 7  // 7 days
)

MinSignatureRequestExpiryHours / MaxSignatureRequestExpiryHours clamp the caller-supplied expiry window for a deferred signature request.

View Source
const (
	// IdempotencyStateInProgress marks a key claimed by a request that has
	// not produced a response yet.
	IdempotencyStateInProgress = "in_progress"
	// IdempotencyStateCompleted marks a key whose request finished. The
	// captured response is replayed verbatim on retry.
	IdempotencyStateCompleted = "completed"
)

Idempotency record states. Enforced by a CHECK constraint on the idempotency_keys table.

View Source
const (
	FlightListColumnModeAuto   = "auto"
	FlightListColumnModeCustom = "custom"
)

Flight list column modes.

View Source
const (
	WebAuthnCeremonyRegistration = "registration"
	WebAuthnCeremonyLogin        = "login"
)

WebAuthn ceremony kinds. Stored in WebAuthnSession.Ceremony and enforced by a CHECK constraint on the webauthn_sessions table.

View Source
const MaxFlightSessionDuration = 24 * time.Hour

MaxFlightSessionDuration is the longest off-block → on-block span accepted when completing a session. Anything longer is almost certainly a forgotten session that should be discarded instead.

View Source
const MaxSignatureImageBytes = 500 * 1024

MaxSignatureImageBytes bounds the size of an uploaded signature image (decoded PNG bytes) to keep the flight_signatures table well-behaved.

Variables

View Source
var (
	PDFMagic   = []byte("%PDF-")
	PDFTrailer = []byte("%%EOF")
)

PDFMagic and PDFTrailer are the structural markers a PDF must carry. They are not a validation of the document — nothing in the standard library parses PDF — but together with the size cap they reject the obvious cases: a truncated download, a renamed ZIP, a text file with a .pdf extension.

View Source
var (
	// Flight validation errors
	ErrInvalidTimeDistribution = errors.New("PIC and dual time are mutually exclusive")
	ErrInvalidNightTime        = errors.New("night time exceeds total time")
	ErrInvalidIFRTime          = errors.New("IFR time exceeds total time")
	ErrNegativeTime            = errors.New("flight times cannot be negative")
	ErrNegativeLandings        = errors.New("landings cannot be negative")

	// Aircraft validation errors
	ErrAircraftRegistrationRequired = errors.New("aircraft registration is required")
	ErrAircraftTypeRequired         = errors.New("aircraft type is required")
	ErrAircraftMakeRequired         = errors.New("aircraft make is required")
	ErrAircraftModelRequired        = errors.New("aircraft model is required")
	ErrAircraftInvalidEngineType    = errors.New("invalid engine type")
)
View Source
var (
	ErrNoOpenFlightSession      = errors.New("no open flight session")
	ErrInvalidFlightSessionData = errors.New("invalid flight session data")
	ErrFlightSessionTimeOrder   = errors.New("flight session event times are out of order")
	ErrFlightSessionTooLong     = errors.New("flight session exceeds maximum duration")
	ErrFlightSessionMissingReg  = errors.New("aircraft registration is required to complete a flight session")
)

Flight session errors

AllNotificationCategories is the default set of all categories (all enabled)

View Source
var AllowedDocumentFileContentTypes = []string{ContentTypeJPEG, ContentTypePNG, ContentTypePDF}
View Source
var CountMetrics = map[string]bool{
	"flights":        true,
	"landings":       true,
	"day_landings":   true,
	"night_landings": true,
	"takeoffs":       true,
	"day_takeoffs":   true,
	"night_takeoffs": true,
	"approaches":     true,
	"holds":          true,
}

CountMetrics are integer-valued metrics (counts of flights, landings, etc.).

View Source
var ErrFieldTooLong = errors.New("field exceeds maximum length")

ErrFieldTooLong is returned when a text field exceeds its maximum allowed length.

View Source
var ErrInvalidFlightBaseline = errors.New("invalid flight baseline")

ErrInvalidFlightBaseline indicates a baseline failed validation.

View Source
var FlightListColumns = []string{
	"offOnBlock",
	"picTime",
	"nightTime",
	"dualTime",
	"ifrTime",
	"crossCountryTime",
	"sicTime",
	"dualGivenTime",
	"multiPilotTime",
	"soloTime",
	"simulatedFlightTime",
	"function",
	"landings",
	"remarks",
}

FlightListColumns is the set of flights-list columns a user may switch on or off. Date, route, aircraft and total time are the identity of a logbook row and are always shown, so they are deliberately absent here.

The order is the display order the client uses, and — for the time columns — the priority order in which they survive on a narrow screen. Keep it in sync with the enum in api-spec/openapi.yaml and the registry in the frontend's src/components/flights/flightTableColumns.ts.

View Source
var TimeMetrics = map[string]bool{
	"total_time":         true,
	"pic_time":           true,
	"dual_time":          true,
	"night_time":         true,
	"ifr_time":           true,
	"cross_country_time": true,
}

TimeMetrics are metrics measured in minutes in the database and expressed to the user in hours by default. Membership also drives Unit handling.

View Source
var ValidApproachTypes = map[string]bool{
	"ILS": true, "LOC": true, "VOR": true, "RNAV/GPS": true, "NDB": true,
	"LDA": true, "SDF": true, "PAR": true, "ASR": true, "Visual": true,
	"Circling": true, "Other": true, "Unknown": true,
}

ValidApproachTypes lists all valid approach type values

View Source
var ValidFilterFields = map[string]map[string]bool{
	"aircraft_class":            {"eq": true, "in": true},
	"aircraft_type":             {"eq": true, "in": true},
	"aircraft_registration":     {"eq": true, "in": true},
	"launch_method":             {"eq": true, "in": true},
	"aircraft_complex":          {"is_true": true},
	"aircraft_high_performance": {"is_true": true},
	"aircraft_tailwheel":        {"is_true": true},
	"is_pic":                    {"is_true": true},
	"is_dual":                   {"is_true": true},
	"has_night":                 {"is_true": true},
	"has_ifr":                   {"is_true": true},
	"is_cross_country":          {"is_true": true},
}

ValidFilterFields enumerates the fields a filter may reference, mapped to the operators each supports. "value"/"values" fields use eq/in; boolean/derived fields use is_true.

View Source
var ValidWindowUnits = map[string]bool{
	"days": true, "weeks": true, "months": true, "years": true,
}

ValidWindowUnits enumerates the accepted lookback units.

Functions

func ContainsControlChar added in v1.3.1

func ContainsControlChar(s string) bool

ContainsControlChar reports whether s contains any control character (C0/C1, including CR, LF, NUL and tab). User-facing rule text must not: rejecting it at the input boundary stops email-header/log injection and stray control bytes reaching any downstream sink, independent of per-sink escaping.

func ContentTypeIsImage added in v1.3.2

func ContentTypeIsImage(ct string) bool

ContentTypeIsImage reports whether a stored file is a raster image, i.e. one the clients can render as a thumbnail and the server can verify by decoding.

func ContentTypeIsInlineSafe added in v1.3.2

func ContentTypeIsInlineSafe(ct string) bool

ContentTypeIsInlineSafe reports whether a stored file may be served with Content-Disposition: inline.

Only the verified raster formats are. A PDF is an active document — it can carry JavaScript and embedded attachments — and we cannot parse it to find out what is inside, so it is always served as an attachment and never rendered inside the application's own origin.

func IsAllowedDocumentFileContentType added in v1.3.2

func IsAllowedDocumentFileContentType(ct string) bool

IsAllowedDocumentFileContentType reports whether ct is an accepted upload format.

func IsTimeMetric added in v1.3.1

func IsTimeMetric(metric string) bool

IsTimeMetric reports whether a metric is measured in time (minutes).

func IsValidClassType

func IsValidClassType(ct ClassType) bool

IsValidClassType checks if a class type is valid

func IsValidCrewRole

func IsValidCrewRole(role CrewRole) bool

IsValidCrewRole checks if a crew role is valid

func IsValidMetric added in v1.3.1

func IsValidMetric(metric string) bool

IsValidMetric reports whether a metric is part of the controlled vocabulary.

func NormalizeFlightListColumnMode added in v1.3.2

func NormalizeFlightListColumnMode(mode string) string

NormalizeFlightListColumnMode maps an unrecognised mode back to the default.

func NormalizeFlightListColumns added in v1.3.2

func NormalizeFlightListColumns(columns []string) pq.StringArray

NormalizeFlightListColumns drops unknown and duplicate column keys and returns the remainder in canonical display order. Unknown keys are ignored rather than rejected, matching how the other display preferences treat a value they do not recognise.

func ValidateAircraftTextFields

func ValidateAircraftTextFields(a *Aircraft) error

ValidateAircraftTextFields validates all text field lengths on an Aircraft.

func ValidateContactTextFields

func ValidateContactTextFields(ct *Contact) error

ValidateContactTextFields validates all text field lengths on a Contact.

func ValidateCredentialTextFields

func ValidateCredentialTextFields(cr *Credential) error

ValidateCredentialTextFields validates all text field lengths on a Credential.

func ValidateFlightTextFields

func ValidateFlightTextFields(f *Flight) error

ValidateFlightTextFields validates all text field lengths on a Flight.

func ValidateLicenseTextFields

func ValidateLicenseTextFields(l *License) error

ValidateLicenseTextFields validates all text field lengths on a License.

func ValidateOptionalStringLength

func ValidateOptionalStringLength(fieldName string, value *string, maxLen int) error

ValidateOptionalStringLength checks an optional string field.

func ValidateStringLength

func ValidateStringLength(fieldName, value string, maxLen int) error

ValidateStringLength checks that a string field does not exceed the given max length.

Types

type Aircraft

type Aircraft struct {
	ID                uuid.UUID `json:"id"`
	UserID            uuid.UUID `json:"userId"`
	Registration      string    `json:"registration"`
	Type              string    `json:"type"`
	Make              string    `json:"make"`
	Model             string    `json:"model"`
	IsComplex         bool      `json:"isComplex"`
	IsHighPerformance bool      `json:"isHighPerformance"`
	IsTailwheel       bool      `json:"isTailwheel"`
	Notes             *string   `json:"notes,omitempty"`
	IsActive          bool      `json:"isActive"`
	AircraftClass     *string   `json:"aircraftClass,omitempty"`
	// Logging defaults, prefilled when a flight with this aircraft is logged
	DefaultDepartureICAO *string   `json:"defaultDepartureIcao,omitempty"`
	DefaultArrivalICAO   *string   `json:"defaultArrivalIcao,omitempty"`
	CreatedAt            time.Time `json:"createdAt"`
	UpdatedAt            time.Time `json:"updatedAt"`
}

Aircraft represents an aircraft in a user's fleet

func (*Aircraft) Validate

func (a *Aircraft) Validate() error

Validate checks basic Aircraft field requirements

type AircraftRecencyRow added in v1.3.1

type AircraftRecencyRow struct {
	Registration string
	AircraftType string
	Date         time.Time
	Landings     int
}

AircraftRecencyRow is one day of landings for one registration/type pair, used to derive 90-day recency counts and lapse dates

type AircraftStats added in v1.3.1

type AircraftStats struct {
	Registration    string     `json:"registration"`
	TotalFlights    int        `json:"totalFlights"`
	TotalMinutes    int        `json:"totalMinutes"`
	LandingsDay     int        `json:"landingsDay"`
	LandingsNight   int        `json:"landingsNight"`
	FirstFlightDate *time.Time `json:"firstFlightDate,omitempty"`
	LastFlightDate  *time.Time `json:"lastFlightDate,omitempty"`
	// Informational 90-day recency (EASA FCL.060(b)-style day recency:
	// 3 landings in the preceding 90 days)
	LandingsLast90Days int        `json:"landingsLast90Days"`
	RecencyLapsesOn    *time.Time `json:"recencyLapsesOn,omitempty"`
}

AircraftStats holds flight statistics aggregated per aircraft registration

type AircraftTypeStats added in v1.3.1

type AircraftTypeStats struct {
	AircraftType       string     `json:"aircraftType"`
	TotalFlights       int        `json:"totalFlights"`
	TotalMinutes       int        `json:"totalMinutes"`
	LandingsDay        int        `json:"landingsDay"`
	LandingsNight      int        `json:"landingsNight"`
	FirstFlightDate    *time.Time `json:"firstFlightDate,omitempty"`
	LastFlightDate     *time.Time `json:"lastFlightDate,omitempty"`
	LandingsLast90Days int        `json:"landingsLast90Days"`
	RecencyLapsesOn    *time.Time `json:"recencyLapsesOn,omitempty"`
}

AircraftTypeStats holds flight statistics aggregated per aircraft type

type ApproachEntry

type ApproachEntry struct {
	Type    string  `json:"type"`
	Airport *string `json:"airport,omitempty"`
	Runway  *string `json:"runway,omitempty"`
}

ApproachEntry represents a single instrument approach with type and location

type BackupDestination added in v1.2.4

type BackupDestination struct {
	ID                  uuid.UUID
	UserID              uuid.UUID
	Provider            string
	DisplayName         string
	Config              map[string]any
	CredentialHint      string
	CredentialsEnc      []byte
	CredentialsNonce    []byte
	Schedule            BackupSchedule
	ScheduleHourUTC     int
	ScheduleDayOfWeek   *int
	ScheduleDayOfMonth  *int
	RetentionCount      int
	Status              BackupDestinationStatus
	LastError           string
	Enabled             bool
	ConsecutiveFailures int
	LastRunAt           *time.Time
	LastSuccessAt       *time.Time
	LastSuccessSHA256   string
	CreatedAt           time.Time
	UpdatedAt           time.Time
}

BackupDestination is a user-owned cloud-storage target where NinerLog will upload JSON backups. Each destination is bound to one provider plugin (e.g. "s3") and carries provider-specific non-secret config in Config plus an encrypted credential blob.

type BackupDestinationStatus added in v1.2.4

type BackupDestinationStatus string

BackupDestinationStatus is the operational state of a destination.

const (
	BackupStatusActive BackupDestinationStatus = "active"
	BackupStatusPaused BackupDestinationStatus = "paused"
	BackupStatusError  BackupDestinationStatus = "error"
)

func (BackupDestinationStatus) IsValid added in v1.2.4

func (s BackupDestinationStatus) IsValid() bool

IsValid reports whether the status is a known value.

type BackupRun added in v1.2.4

type BackupRun struct {
	ID              uuid.UUID
	DestinationID   uuid.UUID
	UserID          uuid.UUID
	Status          BackupRunStatus
	Trigger         BackupRunTrigger
	StartedAt       time.Time
	CompletedAt     *time.Time
	DurationMs      *int
	SizeBytes       *int64
	SHA256          string
	FlightCount     *int
	AircraftCount   *int
	LicenseCount    *int
	CredentialCount *int
	RemotePath      string
	ErrorMessage    string
	CreatedAt       time.Time
}

BackupRun is the audit record of a single backup attempt.

type BackupRunStatus added in v1.2.4

type BackupRunStatus string

BackupRunStatus is the outcome of a single backup run.

const (
	BackupRunStatusSuccess BackupRunStatus = "success"
	BackupRunStatusFailed  BackupRunStatus = "failed"
	BackupRunStatusSkipped BackupRunStatus = "skipped"
)

type BackupRunTrigger added in v1.2.4

type BackupRunTrigger string

BackupRunTrigger captures whether a run was scheduled or user-triggered.

const (
	BackupRunTriggerScheduled BackupRunTrigger = "scheduled"
	BackupRunTriggerManual    BackupRunTrigger = "manual"
)

type BackupSchedule added in v1.2.4

type BackupSchedule string

BackupSchedule controls how often the scheduler runs a destination.

const (
	BackupScheduleManual  BackupSchedule = "manual"
	BackupScheduleDaily   BackupSchedule = "daily"
	BackupScheduleWeekly  BackupSchedule = "weekly"
	BackupScheduleMonthly BackupSchedule = "monthly"
)

func (BackupSchedule) IsValid added in v1.2.4

func (s BackupSchedule) IsValid() bool

IsValid reports whether the schedule is a known value.

type ClassRating

type ClassRating struct {
	ID         uuid.UUID  `json:"id"`
	LicenseID  uuid.UUID  `json:"licenseId"`
	ClassType  ClassType  `json:"classType"`
	IssueDate  time.Time  `json:"issueDate"`
	ExpiryDate *time.Time `json:"expiryDate,omitempty"`
	Notes      *string    `json:"notes,omitempty"`
	CreatedAt  time.Time  `json:"createdAt"`
	UpdatedAt  time.Time  `json:"updatedAt"`
}

ClassRating represents a class rating attached to a license

func (*ClassRating) IsExpired

func (cr *ClassRating) IsExpired() bool

IsExpired checks if the class rating has expired

func (*ClassRating) IsExpiringSoon

func (cr *ClassRating) IsExpiringSoon(days int) bool

IsExpiringSoon checks if the class rating expires within N days

type ClassType

type ClassType string

ClassType represents the type of class rating

const (
	ClassTypeSEPLand ClassType = "SEP_LAND"
	ClassTypeSEPSea  ClassType = "SEP_SEA"
	ClassTypeMEPLand ClassType = "MEP_LAND"
	ClassTypeMEPSea  ClassType = "MEP_SEA"
	ClassTypeSETLand ClassType = "SET_LAND"
	ClassTypeSETSea  ClassType = "SET_SEA"
	ClassTypeTMG     ClassType = "TMG"
	ClassTypeIR      ClassType = "IR"
	ClassTypeOther   ClassType = "OTHER"
)

func ValidClassTypes

func ValidClassTypes() []ClassType

ValidClassTypes returns all valid class types

type Contact

type Contact struct {
	ID        uuid.UUID `json:"id"`
	UserID    uuid.UUID `json:"userId"`
	Name      string    `json:"name"`
	Email     *string   `json:"email,omitempty"`
	Phone     *string   `json:"phone,omitempty"`
	Notes     *string   `json:"notes,omitempty"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}

Contact represents a reusable person that a user can reference across flights

type Credential

type Credential struct {
	ID               uuid.UUID      `json:"id"`
	UserID           uuid.UUID      `json:"userId"`
	CredentialType   CredentialType `json:"credentialType"`
	CredentialNumber *string        `json:"credentialNumber,omitempty"`
	IssueDate        time.Time      `json:"issueDate"`
	ExpiryDate       *time.Time     `json:"expiryDate,omitempty"`
	IssuingAuthority string         `json:"issuingAuthority"`
	Notes            *string        `json:"notes,omitempty"`
	CreatedAt        time.Time      `json:"createdAt"`
	UpdatedAt        time.Time      `json:"updatedAt"`
}

Credential represents a pilot credential (medical, language proficiency, security clearance)

func (*Credential) IsExpired

func (c *Credential) IsExpired() bool

IsExpired checks if the credential has expired

func (*Credential) IsExpiringSoon

func (c *Credential) IsExpiringSoon(days int) bool

IsExpiringSoon checks if the credential expires within the given number of days

type CredentialType

type CredentialType string
const (
	CredentialTypeEASAClass1Medical CredentialType = "EASA_CLASS1_MEDICAL"
	CredentialTypeEASAClass2Medical CredentialType = "EASA_CLASS2_MEDICAL"
	CredentialTypeEASALAPLMedical   CredentialType = "EASA_LAPL_MEDICAL"
	CredentialTypeFAAClass1Medical  CredentialType = "FAA_CLASS1_MEDICAL"
	CredentialTypeFAAClass2Medical  CredentialType = "FAA_CLASS2_MEDICAL"
	CredentialTypeFAAClass3Medical  CredentialType = "FAA_CLASS3_MEDICAL"
	CredentialTypeLangICAOLevel4    CredentialType = "LANG_ICAO_LEVEL4"
	CredentialTypeLangICAOLevel5    CredentialType = "LANG_ICAO_LEVEL5"
	CredentialTypeLangICAOLevel6    CredentialType = "LANG_ICAO_LEVEL6"
	CredentialTypeSecClearanceZUP   CredentialType = "SEC_CLEARANCE_ZUP"
	CredentialTypeSecClearanceZUBB  CredentialType = "SEC_CLEARANCE_ZUBB"
	// German radio operator certificates (Flugfunkzeugnisse). Three separate
	// certificates, not three levels of one: BZF II covers VFR radio in German
	// only, BZF I adds English, and AZF is the full certificate required for
	// IFR. They do not expire.
	CredentialTypeRadioBZF2 CredentialType = "RADIO_BZF2"
	CredentialTypeRadioBZF1 CredentialType = "RADIO_BZF1"
	CredentialTypeRadioAZF  CredentialType = "RADIO_AZF"
	CredentialTypeOther     CredentialType = "OTHER"
)

#nosec G101 -- enum identifiers describe credential types, not secrets.

type CrewRole

type CrewRole string

CrewRole represents a person's role on a flight

const (
	CrewRolePIC         CrewRole = "PIC"
	CrewRoleSIC         CrewRole = "SIC"
	CrewRoleInstructor  CrewRole = "Instructor"
	CrewRoleStudent     CrewRole = "Student"
	CrewRolePassenger   CrewRole = "Passenger"
	CrewRoleSafetyPilot CrewRole = "SafetyPilot"
	CrewRoleExaminer    CrewRole = "Examiner"
)

func ValidCrewRoles

func ValidCrewRoles() []CrewRole

ValidCrewRoles returns all valid crew roles

type CurrencyData

type CurrencyData struct {
	Flights       int
	TotalLandings int
	DayLandings   int
	NightLandings int
}

CurrencyData holds landing/flight counts for currency calculation

type CurrencyFilter added in v1.3.1

type CurrencyFilter struct {
	Field  string   `json:"field"`
	Op     string   `json:"op"`
	Value  string   `json:"value,omitempty"`
	Values []string `json:"values,omitempty"`
}

CurrencyFilter narrows the set of flights a rule counts. Op determines how Value/Values are interpreted:

  • "eq": the flight's field equals Value
  • "in": the flight's field is one of Values
  • "is_true": the (boolean/derived) field is true; Value/Values ignored

type CurrencyRequirement added in v1.3.1

type CurrencyRequirement struct {
	Metric string  `json:"metric"`
	Min    float64 `json:"min"`
	Unit   string  `json:"unit,omitempty"` // hours | minutes (time metrics only)
	Label  string  `json:"label,omitempty"`
}

CurrencyRequirement is a single "at least N" threshold on an aggregated metric over the filtered, windowed flight set. For time metrics, Unit selects how Min is expressed (hours or minutes); count metrics ignore Unit.

type CurrencyWindow added in v1.3.1

type CurrencyWindow struct {
	Amount int    `json:"amount"`
	Unit   string `json:"unit"` // days | weeks | months | years
}

CurrencyWindow is a rolling lookback anchored at the moment of evaluation: only flights on or after (now − Amount×Unit) count. Expiry-anchored windows (as used by some regulatory revalidation rules) are intentionally out of scope for user-authored rules in this version.

type CustomCurrencyRule added in v1.3.1

type CustomCurrencyRule struct {
	ID          uuid.UUID              `json:"id"`
	UserID      uuid.UUID              `json:"userId"`
	Name        string                 `json:"name"`
	Description *string                `json:"description,omitempty"`
	Emoji       *string                `json:"emoji,omitempty"`
	Definition  CustomCurrencyRuleBody `json:"definition"`
	// Enabled is false when the rule is paused: it is kept and listed but not
	// evaluated or surfaced as active currency.
	Enabled bool `json:"enabled"`
	// Notify opts this rule into expiry/lapse email notifications (per-rule).
	Notify       bool       `json:"notify"`
	IsShared     bool       `json:"isShared"`
	ShareToken   *string    `json:"shareToken,omitempty"`
	ImportedFrom *uuid.UUID `json:"importedFrom,omitempty"`
	CreatedAt    time.Time  `json:"createdAt"`
	UpdatedAt    time.Time  `json:"updatedAt"`
}

CustomCurrencyRule is a user-authored, modular currency definition. The rule body (window + filters + requirements) lives in Definition and is evaluated against the user's flights by the currency engine. Rules are private by default; enabling sharing exposes a read-only copy via ShareToken that other users can import.

type CustomCurrencyRuleBody added in v1.3.1

type CustomCurrencyRuleBody struct {
	Window       CurrencyWindow        `json:"window"`
	Filters      []CurrencyFilter      `json:"filters,omitempty"`
	Requirements []CurrencyRequirement `json:"requirements"`
}

CustomCurrencyRuleBody is the declarative rule document. It mirrors the shape of the built-in engine's rules: a lookback Window, a set of Filters that select which flights count, and one or more Requirements measured against aggregated flight metrics. Requirements are combined with logical AND — the rule is "current" only when every requirement is met.

func (*CustomCurrencyRuleBody) Scan added in v1.3.1

func (b *CustomCurrencyRuleBody) Scan(src interface{}) error

Scan implements sql.Scanner so the body can be read from a JSONB column.

func (*CustomCurrencyRuleBody) Validate added in v1.3.1

func (b *CustomCurrencyRuleBody) Validate() error

Validate checks a rule body against the controlled vocabulary and structural limits. It returns a descriptive error suitable for surfacing to the author.

func (CustomCurrencyRuleBody) Value added in v1.3.1

Value implements driver.Valuer so the body can be stored directly in a JSONB column. It returns the JSON as a string (text) so lib/pq sends it in a form Postgres accepts for jsonb.

type Deletion added in v1.3.2

type Deletion struct {
	UserID     uuid.UUID          `json:"-"`
	EntityType DeletionEntityType `json:"entity"`
	EntityID   uuid.UUID          `json:"id"`
	DeletedAt  time.Time          `json:"deletedAt"`
}

Deletion is a tombstone: the record identified by EntityType/EntityID no longer exists. It carries no payload beyond the identity and the timestamp — a client only needs to know what to drop from its local store.

type DeletionEntityType added in v1.3.2

type DeletionEntityType string

DeletionEntityType names the collection a tombstoned record belonged to. The values are the wire format and are also written by the database trigger in migration 000054 — they must not drift apart.

const (
	DeletionEntityFlight     DeletionEntityType = "flight"
	DeletionEntityAircraft   DeletionEntityType = "aircraft"
	DeletionEntityContact    DeletionEntityType = "contact"
	DeletionEntityCredential DeletionEntityType = "credential"
	DeletionEntityLicense    DeletionEntityType = "license"
)

func ValidDeletionEntityTypes added in v1.3.2

func ValidDeletionEntityTypes() []DeletionEntityType

ValidDeletionEntityTypes lists every entity a sync client can be told about.

func (DeletionEntityType) IsValid added in v1.3.2

func (e DeletionEntityType) IsValid() bool

IsValid reports whether the value is one the trigger can produce. Anything else in a request is a client error, not an empty result.

type DocumentFile added in v1.3.2

type DocumentFile struct {
	ID     uuid.UUID `json:"id"`
	UserID uuid.UUID `json:"userId"`

	// Exactly one of LicenseID / CredentialID is set.
	LicenseID    *uuid.UUID `json:"licenseId,omitempty"`
	CredentialID *uuid.UUID `json:"credentialId,omitempty"`

	ContentType string  `json:"contentType"`
	ByteSize    int     `json:"byteSize"`
	Width       *int    `json:"width,omitempty"`
	Height      *int    `json:"height,omitempty"`
	Filename    *string `json:"filename,omitempty"`
	Caption     *string `json:"caption,omitempty"`

	Data []byte `json:"-"`

	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}

DocumentFile is a reference photo or scan attached to a licence or a credential. Data carries the raw bytes and is only populated on the single-image download path — list and create responses leave it nil so a listing never drags megabytes through the service layer.

func (*DocumentFile) SubjectID added in v1.3.2

func (d *DocumentFile) SubjectID() uuid.UUID

SubjectID returns the id of the licence or credential this image belongs to, and uuid.Nil for a malformed row with neither set.

func (*DocumentFile) SubjectType added in v1.3.2

func (d *DocumentFile) SubjectType() DocumentSubjectType

SubjectType reports which kind of document this image belongs to.

type DocumentSubjectType added in v1.3.2

type DocumentSubjectType string

DocumentSubjectType names the kind of document an image is attached to. A DocumentFile always has exactly one subject.

const (
	DocumentSubjectLicense    DocumentSubjectType = "license"
	DocumentSubjectCredential DocumentSubjectType = "credential"
)

type EmailDeliveryEvent added in v1.3.2

type EmailDeliveryEvent struct {
	ID uuid.UUID `json:"id"`
	// UserID is nil once the account has been removed, or when the address was
	// not tied to an account at the time of sending.
	UserID    *uuid.UUID `json:"userId,omitempty"`
	Recipient string     `json:"recipient"`
	EmailType string     `json:"emailType"`
	Status    string     `json:"status"`
	// SMTPCode is the server's reply code, nil when there was no conversation
	// (an unparseable address, a dropped connection).
	SMTPCode  *int      `json:"smtpCode,omitempty"`
	Detail    *string   `json:"detail,omitempty"`
	CreatedAt time.Time `json:"createdAt"`
}

EmailDeliveryEvent is one recorded send attempt. The table is append-only: it is the history of what the SMTP conversation said, not current state.

type EmailSuppression added in v1.3.2

type EmailSuppression struct {
	Email          string    `json:"email"`
	Reason         string    `json:"reason"`
	SMTPCode       *int      `json:"smtpCode,omitempty"`
	Detail         *string   `json:"detail,omitempty"`
	FirstBouncedAt time.Time `json:"firstBouncedAt"`
	LastBouncedAt  time.Time `json:"lastBouncedAt"`
	BounceCount    int       `json:"bounceCount"`
}

EmailSuppression is an address we have stopped mailing because it refused mail permanently. Only a recipient-level refusal creates one — see pkg/email.DeliveryStatus.

type EmailVerificationToken added in v1.2.5

type EmailVerificationToken struct {
	ID        uuid.UUID `json:"id"`
	UserID    uuid.UUID `json:"user_id"`
	TokenHash string    `json:"-"`
	ExpiresAt time.Time `json:"expires_at"`
	Used      bool      `json:"used"`
	CreatedAt time.Time `json:"created_at"`
}

EmailVerificationToken represents a single-use email-verification token sent to a user after registration (or via the resend endpoint).

type Flight

type Flight struct {
	ID     uuid.UUID `json:"id"`
	UserID uuid.UUID `json:"userId"`
	Date   time.Time `json:"date"`

	// Aircraft information
	AircraftReg  string `json:"aircraftReg"`
	AircraftType string `json:"aircraftType"`

	// Route information
	DepartureICAO *string `json:"departureIcao,omitempty"`
	ArrivalICAO   *string `json:"arrivalIcao,omitempty"`
	OffBlockTime  *string `json:"offBlockTime,omitempty"`  // HH:MM:SS format - chocks off / engine start (UTC)
	OnBlockTime   *string `json:"onBlockTime,omitempty"`   // HH:MM:SS format - chocks on / engine shutdown (UTC)
	DepartureTime *string `json:"departureTime,omitempty"` // HH:MM:SS format - takeoff time (UTC)
	ArrivalTime   *string `json:"arrivalTime,omitempty"`   // HH:MM:SS format - landing time (UTC)

	// Flight times (in minutes)
	TotalTime int  `json:"totalTime"`
	IsPIC     bool `json:"isPic"`
	IsDual    bool `json:"isDual"`
	PICTime   int  `json:"picTime"`
	DualTime  int  `json:"dualTime"`
	NightTime int  `json:"nightTime"`
	IFRTime   int  `json:"ifrTime"`

	// Landings
	LandingsDay   int `json:"landingsDay"`
	LandingsNight int `json:"landingsNight"`
	AllLandings   int `json:"allLandings"` // Auto-calculated: day + night

	// Takeoffs
	TakeoffsDay   int `json:"takeoffsDay"`   // Auto-calculated from sunset/sunrise at departure (with manual override)
	TakeoffsNight int `json:"takeoffsNight"` // Auto-calculated from sunset/sunrise at departure (with manual override)

	// Route
	Route *string `json:"route,omitempty"` // Comma-separated ICAO waypoints

	// Auto-calculated fields
	SoloTime         int     `json:"soloTime"`         // Auto-calculated when not dual and not PIC with crew
	CrossCountryTime int     `json:"crossCountryTime"` // Auto-calculated when departure ≠ arrival
	Distance         float64 `json:"distance"`         // Auto-calculated from airport coordinates (NM)

	// Manual override flags
	TakeoffsDayOverride   bool `json:"-"` // When true, takeoffsDay is not auto-calculated
	TakeoffsNightOverride bool `json:"-"` // When true, takeoffsNight is not auto-calculated
	LandingsDayOverride   bool `json:"-"` // When true, landingsDay is not auto-calculated
	LandingsNightOverride bool `json:"-"` // When true, landingsNight is not auto-calculated

	// Instructor & comments
	InstructorName     *string `json:"instructorName,omitempty"`
	InstructorComments *string `json:"instructorComments,omitempty"`

	// PIC name (EASA AMC1 FCL.050 Col 12)
	PICName *string `json:"picName,omitempty"`

	// Multi-crew / advanced times
	SICTime             int `json:"sicTime"`
	DualGivenTime       int `json:"dualGivenTime"`
	SimulatedFlightTime int `json:"simulatedFlightTime"`
	GroundTrainingTime  int `json:"groundTrainingTime"`
	MultiPilotTime      int `json:"multiPilotTime"` // EASA AMC1 FCL.050 Col 10

	// FSTD type designation (EASA AMC1 FCL.050 Col 22, FAA §61.51(b)(1)(iv))
	FSTDType *string `json:"fstdType,omitempty"`

	// Instrument tracking
	ActualInstrumentTime    int             `json:"actualInstrumentTime"`
	SimulatedInstrumentTime int             `json:"simulatedInstrumentTime"`
	Holds                   int             `json:"holds"`
	ApproachesCount         int             `json:"approachesCount"`
	Approaches              []ApproachEntry `json:"approaches,omitempty"` // Structured approach data (FAA §61.51(g)(3))
	IsIPC                   bool            `json:"isIpc"`
	IsFlightReview          bool            `json:"isFlightReview"`
	IsProficiencyCheck      bool            `json:"isProficiencyCheck"`

	// Endorsements (EASA AMC1 FCL.050 Col 24, FAA §61.51(h))
	Endorsements *string `json:"endorsements,omitempty"`

	// SPL / Glider
	LaunchMethod *string `json:"launchMethod,omitempty"` // winch, aerotow, self-launch

	// Crew members on board (populated from flight_crew_members table)
	CrewMembers []FlightCrewMember `json:"crewMembers,omitempty"`

	// Additional information
	Remarks *string `json:"remarks,omitempty"`

	// Instructor sign-off lock. Non-nil iff the flight is locked by a
	// completed, non-voided FlightSignature (see flight_signature.go).
	SignatureID *uuid.UUID `json:"signatureId,omitempty"`

	// Metadata
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}

Flight represents a flight log entry

func (*Flight) IsValid

func (f *Flight) IsValid() bool

IsValid checks if all required fields are set

func (*Flight) ValidateTimeDistribution

func (f *Flight) ValidateTimeDistribution() error

ValidateTimeDistribution checks if time distribution is valid

type FlightBaseline added in v1.2.3

type FlightBaseline struct {
	UserID              uuid.UUID
	BaselineDate        time.Time
	TotalFlights        int
	TotalMinutes        int
	PICMinutes          int
	SICMinutes          int
	DualMinutes         int
	DualGivenMinutes    int
	MultiPilotMinutes   int
	NightMinutes        int
	IFRMinutes          int
	SoloMinutes         int
	CrossCountryMinutes int
	LandingsDay         int
	LandingsNight       int
	Notes               *string
	CreatedAt           time.Time
	UpdatedAt           time.Time
}

FlightBaseline represents a per-user "initial hours snapshot": a carried-forward total of pre-existing flying experience that is added on top of logged flights when computing aggregated user-level statistics.

func (*FlightBaseline) Validate added in v1.2.3

func (b *FlightBaseline) Validate() error

Validate enforces the basic invariants that aren't covered by DB CHECK constraints (non-negative values are enforced in the database, but we still fail fast at the API boundary).

type FlightCrewMember

type FlightCrewMember struct {
	ID        uuid.UUID  `json:"id"`
	FlightID  uuid.UUID  `json:"flightId"`
	ContactID *uuid.UUID `json:"contactId,omitempty"`
	Name      string     `json:"name"`
	Role      CrewRole   `json:"role"`
}

FlightCrewMember represents a person on board a specific flight

type FlightSession added in v1.2.8

type FlightSession struct {
	ID     uuid.UUID `json:"id"`
	UserID uuid.UUID `json:"userId"`
	Status string    `json:"status"` // open | completed | discarded

	AircraftReg   *string `json:"aircraftReg,omitempty"`
	DepartureICAO *string `json:"departureIcao,omitempty"`
	ArrivalICAO   *string `json:"arrivalIcao,omitempty"`

	OffBlockAt *time.Time `json:"offBlockAt,omitempty"`
	TakeoffAt  *time.Time `json:"takeoffAt,omitempty"`
	LandingAt  *time.Time `json:"landingAt,omitempty"`
	OnBlockAt  *time.Time `json:"onBlockAt,omitempty"`

	// FlightID is set once the session has been completed and converted
	// into a regular flight log entry.
	FlightID *uuid.UUID `json:"flightId,omitempty"`

	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}

FlightSession represents an in-progress flight captured live via tap-to-log from a mobile device. Event instants are full UTC timestamps (not bare times of day) so flights crossing midnight stay unambiguous; they are split into date + HH:MM:SS when the session is converted into a Flight.

func (*FlightSession) BlockDuration added in v1.2.8

func (s *FlightSession) BlockDuration() time.Duration

BlockDuration returns the off-block → on-block duration, or 0 when either instant is missing.

func (*FlightSession) ValidateEventOrder added in v1.2.8

func (s *FlightSession) ValidateEventOrder() error

ValidateEventOrder checks that whichever event instants are present occur in the physical order off block ≤ takeoff ≤ landing ≤ on block.

type FlightSignature added in v1.2.9

type FlightSignature struct {
	ID       uuid.UUID
	FlightID uuid.UUID
	UserID   uuid.UUID

	Method SignatureMethod
	Status SignatureStatus

	// Deferred token fields; nil for SignatureMethodLive.
	TokenHash      *string
	TokenExpiresAt *time.Time

	InstructorEmail *string
	EmailSentAt     *time.Time
	EmailSendCount  int

	ContactID *uuid.UUID

	// Captured at completion time.
	InstructorName         *string
	InstructorCredentialNo *string
	SignatureImage         []byte
	SignedAt               *time.Time
	SignerIP               *string
	SignerUserAgent        *string

	VoidedAt     *time.Time
	VoidedReason *string

	CreatedAt time.Time
	UpdatedAt time.Time
}

FlightSignature is an instructor sign-off request/record for a single flight log entry. See db/migrations/000042_create_flight_signatures for the full column rationale.

type FlightStatistics

type FlightStatistics struct {
	TotalFlights        int
	TotalMinutes        int
	PICMinutes          int
	DualMinutes         int
	NightMinutes        int
	IFRMinutes          int
	SoloMinutes         int
	CrossCountryMinutes int
	LandingsDay         int
	LandingsNight       int
	SICMinutes          int
	DualGivenMinutes    int
}

FlightStatistics holds aggregated flight statistics for a license

type IdempotencyRecord added in v1.3.2

type IdempotencyRecord struct {
	UserID uuid.UUID
	Key    string

	// RequestHash is the SHA-256 over method, path+query and (for
	// non-multipart requests) the request body.
	RequestHash []byte

	State string

	// ResponseStatus is nil while in progress, and stays nil on a completed
	// record whose response was too large to store.
	ResponseStatus      *int
	ResponseBody        []byte
	ResponseContentType string

	CreatedAt   time.Time
	CompletedAt *time.Time
	ExpiresAt   time.Time
}

IdempotencyRecord is one server-side replay record for a mutating request that carried an `Idempotency-Key` header, scoped to the user who sent it.

CreatedAt is the claim timestamp and doubles as a fencing token: only the request that took the claim may finalize or release it, so a request that took over an abandoned claim cannot have its record clobbered by the original straggler.

func (*IdempotencyRecord) Replayable added in v1.3.2

func (r *IdempotencyRecord) Replayable() bool

Replayable reports whether a stored record carries a response that can be returned verbatim to a retry.

type License

type License struct {
	ID                      uuid.UUID `json:"id"`
	UserID                  uuid.UUID `json:"userId"`
	RegulatoryAuthority     string    `json:"regulatoryAuthority" binding:"required"`
	LicenseType             string    `json:"licenseType" binding:"required"`
	LicenseNumber           string    `json:"licenseNumber" binding:"required"`
	IssueDate               time.Time `json:"issueDate" binding:"required"`
	IssuingAuthority        string    `json:"issuingAuthority" binding:"required"`
	RequiresSeparateLogbook bool      `json:"requiresSeparateLogbook"`
	CreatedAt               time.Time `json:"createdAt"`
	UpdatedAt               time.Time `json:"updatedAt"`
}

License represents a pilot license

func (*License) IsValid

func (l *License) IsValid() bool

IsValid checks if all required fields are set

func (*License) Validate

func (l *License) Validate() error

Validate checks all required fields and returns a descriptive error

type NotificationCategory

type NotificationCategory string

NotificationCategory represents a granular notification category

const (
	NotifCategoryCredentialMedical    NotificationCategory = "credential_medical"
	NotifCategoryCredentialLanguage   NotificationCategory = "credential_language"
	NotifCategoryCredentialSecurity   NotificationCategory = "credential_security"
	NotifCategoryCredentialOther      NotificationCategory = "credential_other"
	NotifCategoryRatingExpiry         NotificationCategory = "rating_expiry"
	NotifCategoryCurrencyPassenger    NotificationCategory = "currency_passenger"
	NotifCategoryCurrencyNight        NotificationCategory = "currency_night"
	NotifCategoryCurrencyInstrument   NotificationCategory = "currency_instrument"
	NotifCategoryCurrencyFlightReview NotificationCategory = "currency_flight_review"
	NotifCategoryCurrencyRevalidation NotificationCategory = "currency_revalidation"
	// NotifCategoryCustomCurrency labels notifications for user-authored custom
	// currency rules. It is used as the log/dedup type; opt-in is per-rule
	// (custom_currency_rules.notify) rather than via enabled_categories.
	NotifCategoryCustomCurrency NotificationCategory = "currency_custom"
)

#nosec G101 -- notification category identifiers describe credential reminders, not secrets.

func CredentialCategoryForType

func CredentialCategoryForType(credType CredentialType) NotificationCategory

CredentialCategoryForType maps a credential type to a notification category

type NotificationLog

type NotificationLog struct {
	ID                  uuid.UUID  `json:"id"`
	UserID              uuid.UUID  `json:"userId"`
	NotificationType    string     `json:"notificationType"`
	ReferenceID         *uuid.UUID `json:"referenceId,omitempty"`
	ReferenceType       *string    `json:"referenceType,omitempty"`
	DaysBeforeExpiry    *int       `json:"daysBeforeExpiry,omitempty"`
	ExpiryReferenceDate *time.Time `json:"expiryReferenceDate,omitempty"`
	Subject             *string    `json:"subject,omitempty"`
	SentAt              time.Time  `json:"sentAt"`
}

NotificationLog tracks sent notifications to avoid duplicates

type NotificationPreferences

type NotificationPreferences struct {
	ID                uuid.UUID      `json:"id"`
	UserID            uuid.UUID      `json:"userId"`
	EmailEnabled      bool           `json:"emailEnabled"`
	EnabledCategories pq.StringArray `json:"enabledCategories"`
	WarningDays       pq.Int64Array  `json:"warningDays"`
	CheckHour         int            `json:"checkHour"`
	CreatedAt         time.Time      `json:"createdAt"`
	UpdatedAt         time.Time      `json:"updatedAt"`
}

NotificationPreferences holds a user's notification settings

func (*NotificationPreferences) IsCategoryEnabled

func (p *NotificationPreferences) IsCategoryEnabled(category NotificationCategory) bool

IsCategoryEnabled checks if a specific notification category is enabled

type OIDCHandoffCode added in v1.3.2

type OIDCHandoffCode struct {
	CodeHash  []byte
	UserID    uuid.UUID
	CreatedAt time.Time
	ExpiresAt time.Time
}

OIDCHandoffCode is a single-use, short-lived credential the callback hands to the browser so the SPA can fetch the real token pair over POST.

type OIDCIdentity added in v1.3.2

type OIDCIdentity struct {
	ID          uuid.UUID  `json:"id"`
	UserID      uuid.UUID  `json:"userId"`
	Issuer      string     `json:"issuer"`
	Subject     string     `json:"subject"`
	Email       string     `json:"email"`
	CreatedAt   time.Time  `json:"createdAt"`
	LastLoginAt *time.Time `json:"lastLoginAt,omitempty"`
}

OIDCIdentity links an external OpenID Connect identity to a local user.

(Issuer, Subject) is the identity's primary key at the provider and the only value lookups use; Email is informational and may change at any time.

type OIDCLoginState added in v1.3.2

type OIDCLoginState struct {
	StateHash []byte
	// BrowserHash is the SHA-256 of the value held in the browser's
	// short-lived OIDC cookie, binding this pending login to the browser that
	// started it.
	BrowserHash  []byte
	Nonce        string
	CodeVerifier string
	CreatedAt    time.Time
	ExpiresAt    time.Time
}

OIDCLoginState is the server-side half of one authorization request: the nonce that binds the ID token to this login, and the PKCE verifier whose challenge was sent to the provider. Keyed by the SHA-256 of the state parameter, never the state itself.

type PasswordResetToken

type PasswordResetToken struct {
	ID        uuid.UUID `json:"id"`
	UserID    uuid.UUID `json:"user_id"`
	TokenHash string    `json:"-"`
	ExpiresAt time.Time `json:"expires_at"`
	Used      bool      `json:"used"`
	CreatedAt time.Time `json:"created_at"`
}

PasswordResetToken represents a password reset token

type RefreshToken

type RefreshToken struct {
	ID        uuid.UUID `json:"id"`
	UserID    uuid.UUID `json:"user_id"`
	TokenHash string    `json:"-"`
	ExpiresAt time.Time `json:"expires_at"`
	Revoked   bool      `json:"revoked"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

RefreshToken represents a refresh token in the system

type SignatureMethod added in v1.2.9

type SignatureMethod string

SignatureMethod describes how a FlightSignature was (or will be) captured.

const (
	// SignatureMethodLive is captured in person on the flight owner's own
	// device while the instructor is physically present. Completes
	// synchronously; no token involved.
	SignatureMethodLive SignatureMethod = "live"
	// SignatureMethodDeferred is a token-based flow: the owner creates a
	// request, and the instructor completes it later via the public
	// /sign/{token} link, delivered by email and/or a shareable link/QR.
	SignatureMethodDeferred SignatureMethod = "deferred"
)

type SignatureStatus added in v1.2.9

type SignatureStatus string

SignatureStatus is the lifecycle state of a FlightSignature row.

const (
	// SignatureStatusPending is a deferred request awaiting completion.
	SignatureStatusPending SignatureStatus = "pending"
	// SignatureStatusCompleted means the signature was captured and (for
	// deferred flows) the token has been consumed. The flight is locked.
	SignatureStatusCompleted SignatureStatus = "completed"
	// SignatureStatusRevoked means the owner cancelled a pending request
	// before anyone signed it.
	SignatureStatusRevoked SignatureStatus = "revoked"
	// SignatureStatusVoided means a completed signature was later
	// invalidated by the owner (with a reason), unlocking the flight again.
	SignatureStatusVoided SignatureStatus = "voided"
	// SignatureStatusExpired means a pending request's token lapsed before
	// anyone signed it.
	SignatureStatusExpired SignatureStatus = "expired"
)

type User

type User struct {
	ID                  uuid.UUID      `json:"id"`
	Email               string         `json:"email"`
	PasswordHash        string         `json:"-"`
	Name                string         `json:"name"`
	EmailVerified       bool           `json:"emailVerified"`
	TwoFactorEnabled    bool           `json:"twoFactorEnabled"`
	TwoFactorSecret     *string        `json:"-"` // never exposed in JSON
	RecoveryCodes       pq.StringArray `json:"-"` // never exposed in JSON
	FailedLoginAttempts int            `json:"-"`
	LockedUntil         *time.Time     `json:"-"`
	Disabled            bool           `json:"disabled"`
	LastLoginAt         *time.Time     `json:"lastLoginAt,omitempty"`
	TimeDisplayFormat   string         `json:"timeDisplayFormat"`
	DateFormat          string         `json:"dateFormat"`
	DecimalSeparator    string         `json:"decimalSeparator"`
	PreferredLocale     string         `json:"preferredLocale"`
	// Informational 90-day recency indicator preferences (FCL.060(b)-style)
	RecencyPerModel        bool `json:"recencyPerModel"`
	RecencyPerRegistration bool `json:"recencyPerRegistration"`
	// Which optional columns the flights list shows. In FlightListColumnModeAuto
	// the client picks them from the data on the page; in
	// FlightListColumnModeCustom, FlightListColumns is the user's own choice and
	// an empty list means "none of the optional columns".
	FlightListColumnMode string         `json:"flightListColumnMode"`
	FlightListColumns    pq.StringArray `json:"flightListColumns"`
	CreatedAt            time.Time      `json:"created_at"`
	UpdatedAt            time.Time      `json:"updated_at"`
}

User represents a user in the system

func (*User) HasPassword added in v1.3.2

func (u *User) HasPassword() bool

HasPassword reports whether the account has a usable local password.

Accounts provisioned through OIDC carry an empty hash: there is no password to verify, and no endpoint that could set one while OIDC mode is active. Callers must treat a passwordless account as "cannot authenticate locally" rather than "any password matches".

type WebAuthnCredential added in v1.2.0

type WebAuthnCredential struct {
	ID              uuid.UUID
	UserID          uuid.UUID
	CredentialID    []byte // raw credential id from the authenticator
	PublicKey       []byte // CBOR-encoded COSE public key
	AttestationType string
	AAGUID          []byte
	SignCount       uint32
	Transports      pq.StringArray
	Label           *string
	UserPresent     bool
	UserVerified    bool
	BackupEligible  bool
	BackupState     bool
	CreatedAt       time.Time
	LastUsedAt      *time.Time
}

WebAuthnCredential represents a passkey / WebAuthn credential registered by a user.

type WebAuthnSession added in v1.2.0

type WebAuthnSession struct {
	IDHash    []byte     // sha256 of the raw handle issued to the client
	UserID    *uuid.UUID // nil for usernameless / discoverable login
	Ceremony  string     // WebAuthnCeremonyRegistration | WebAuthnCeremonyLogin
	Data      []byte     // serialized webauthn.SessionData
	ExpiresAt time.Time
	CreatedAt time.Time
}

WebAuthnSession holds the transient state of an in-flight WebAuthn ceremony (registration or authentication) until the client returns its assertion.

The raw handle that identifies a session is never stored: IDHash holds its SHA-256, so a database dump does not yield usable ceremony state.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL