// Package models defines the domain types for the car maintenance tracker. // // The shapes mirror the original "Car Service.xlsx": one Car per sheet, a log // of ServiceRecords (date + km, plus which parts were changed), and a per-car // catalog of Parts (cols M/N). Derived fields follow the spreadsheet formulas: // // Next Service Date = Service Date + ServiceIntervalDays (Excel: A + 365) // Next Service Km = Service Km + ServiceIntervalKm (Excel: B + 15000) package models import "time" // Car corresponds to one worksheet in the original spreadsheet. type Car struct { ID string `json:"id"` Name string `json:"name"` // e.g. "Toyota Yaris" Make string `json:"make"` // e.g. "Toyota" Model string `json:"model"` // e.g. "Yaris" Year int `json:"year"` // optional Registration string `json:"registration"` // optional plate RegistrationCountry string `json:"registrationCountry"` // optional (country of registration) VIN string `json:"vin"` // optional // Maintenance intervals, configurable per car. The spreadsheet hard-coded // 365 days and 15000 km; here they are stored so each car can differ. ServiceIntervalDays int `json:"serviceIntervalDays"` ServiceIntervalKm int `json:"serviceIntervalKm"` // CurrentKm is the car's present odometer reading, updated by the user. Used // to flag km-based overdue service (current_km >= last service km + interval). CurrentKm int `json:"currentKm"` OilSpec string `json:"oilSpec"` // e.g. "Toyota Advanced Fuel Economy 0W20" TransmissionOilSpec string `json:"transmissionOilSpec"` // e.g. "Toyota WS" DifferentialOilSpec string `json:"differentialOilSpec"` // e.g. "SAE 75W-90 GL-5" BrakeFluidSpec string `json:"brakeFluidSpec"` // e.g. "DOT 4" CoolantSpec string `json:"coolantSpec"` // e.g. "Toyota Super Long Life Coolant" FuelType string `json:"fuelType"` // petrol | diesel | hybrid | electric BuildDate string `json:"buildDate"` // ISO YYYY-MM-DD (date-only) FirstRegistrationDate string `json:"firstRegistrationDate"` // ISO YYYY-MM-DD (date-only) // Owner is the user id that owns this car. Access is the requesting user's // permission on it — "owner", "write", or "read" — computed by the API at // read time and never persisted (omitempty; not part of the write payload). Owner string `json:"owner,omitempty"` Access string `json:"access,omitempty"` Created string `json:"created,omitempty"` Updated string `json:"updated,omitempty"` } // ServiceRecord is one row of the Service log for a car. type ServiceRecord struct { ID string `json:"id"` Car string `json:"car"` // relation -> Car.ID Date time.Time `json:"date"` // service date (Excel col A) Km int `json:"km"` // odometer at service (Excel col B) // "Changed Parts" checkboxes (Excel cols E/F/G). ChangedOil bool `json:"changedOil"` // Oil & Oil Filter ChangedEngineAirFilter bool `json:"changedEngineAirFilter"` // Engine Air Filter ChangedCabinAirFilter bool `json:"changedCabinAirFilter"` // Cabin Air Filter Notes string `json:"notes,omitempty"` // Derived (not stored): filled in by the API on read. NextServiceDate *time.Time `json:"nextServiceDate,omitempty"` // Excel col C NextServiceKm *int `json:"nextServiceKm,omitempty"` // Excel col D Created string `json:"created,omitempty"` Updated string `json:"updated,omitempty"` } // Part is one entry in a car's parts catalog (Excel cols M/N). type Part struct { ID string `json:"id"` Car string `json:"car"` // relation -> Car.ID Name string `json:"name"` // e.g. "Oil Filter" PartNumber string `json:"partNumber"` // e.g. "04152-YZZA7" Category string `json:"category"` // optional: oil|filter|wiper|other Created string `json:"created,omitempty"` Updated string `json:"updated,omitempty"` } // User is the authenticated account's profile, covering the Settings panel's // Account/Profile/Appearance sections. type User struct { ID string `json:"id"` Email string `json:"email"` Verified bool `json:"verified"` Name string `json:"name"` Bio string `json:"bio"` HasAvatar bool `json:"hasAvatar"` Theme string `json:"theme"` // light | dark | system Locale string `json:"locale"` // e.g. "en-US" DateFormat string `json:"dateFormat"` // YMD | DMY | MDY FontSize string `json:"fontSize"` // small | medium | large Role string `json:"role"` // user | admin // Non-empty while an account-deletion request is pending its cooldown. DeletionRequestedAt *time.Time `json:"deletionRequestedAt,omitempty"` Created string `json:"created,omitempty"` } // Session is one active login (device) for the current user. type Session struct { ID string `json:"id"` DeviceLabel string `json:"deviceLabel"` IP string `json:"ip"` Current bool `json:"current"` Created time.Time `json:"created"` ExpiresAt time.Time `json:"expiresAt"` } // ComputeDerived fills NextServiceDate / NextServiceKm from the car's intervals, // reproducing the spreadsheet formulas. Intervals of 0 fall back to the // spreadsheet defaults (365 days, 15000 km). func (r *ServiceRecord) ComputeDerived(c *Car) { days := c.ServiceIntervalDays if days <= 0 { days = 365 } km := c.ServiceIntervalKm if km <= 0 { km = 15000 } if !r.Date.IsZero() { d := r.Date.AddDate(0, 0, days) r.NextServiceDate = &d } if r.Km > 0 { n := r.Km + km r.NextServiceKm = &n } }