package model import ( "time" "github.com/google/uuid" "gorm.io/gorm" ) // Base gives every persisted model a PostgreSQL uuid primary key. // GORM's naming strategy maps Go fields to snake_case columns. type Base struct { ID string `gorm:"type:uuid;primaryKey" json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (b *Base) BeforeCreate(_ *gorm.DB) error { if b.ID == "" { b.ID = uuid.NewString() } return nil } type File struct { Base Bucket string `gorm:"size:128;not null" json:"bucket"` ObjectKey string `gorm:"size:512;uniqueIndex;not null" json:"object_key"` MD5 string `gorm:"column:md5;size:32;uniqueIndex;not null" json:"md5"` URL string `gorm:"size:1024;not null" json:"url"` Name string `gorm:"size:255;not null" json:"name"` ContentType string `gorm:"size:128;not null" json:"content_type"` Size int64 `gorm:"not null" json:"size"` } type User struct { Base OpenID string `gorm:"size:128;uniqueIndex;not null" json:"-"` Nickname string `gorm:"size:64;not null" json:"nickname"` AvatarFileID *string `gorm:"type:uuid;index" json:"avatar_file_id"` Disabled bool `gorm:"not null;default:false" json:"disabled"` } type Admin struct { Base Username string `gorm:"size:64;uniqueIndex;not null" json:"username"` PasswordHash string `gorm:"size:255;not null" json:"-"` } // RefreshSession stores only a hash of the rotating refresh credential. type RefreshSession struct { Base ActorID string `gorm:"type:uuid;index;not null" json:"-"` Role string `gorm:"size:16;index;not null" json:"-"` TokenHash string `gorm:"size:64;uniqueIndex;not null" json:"-"` ExpiresAt time.Time `gorm:"index;not null" json:"-"` Revoked bool `gorm:"not null;default:false" json:"-"` } type Breed struct { Base LifeSpan string `gorm:"size:128" json:"life_span"` Exercise string `gorm:"size:255" json:"exercise"` Grooming string `gorm:"size:255" json:"grooming"` EditorialLocked bool `gorm:"not null;default:false" json:"editorial_locked"` FieldSources map[string]string `gorm:"serializer:json;type:jsonb" json:"field_sources"` References []BreedReference `gorm:"foreignKey:BreedID" json:"references,omitempty"` Origin string `gorm:"size:128" json:"origin"` BreedGroup string `gorm:"size:255" json:"breed_group"` FCINumber string `gorm:"size:16" json:"fci_number"` Height string `gorm:"size:128" json:"height"` Weight string `gorm:"size:128" json:"weight"` Summary string `gorm:"type:text" json:"summary"` Temperament string `gorm:"size:255" json:"temperament"` Traits string `gorm:"type:text" json:"traits"` CareNotes string `gorm:"type:text" json:"care_notes"` EncyclopediaUpdatedAt string `gorm:"size:10" json:"encyclopedia_updated_at"` Name string `gorm:"size:128;uniqueIndex;not null" json:"name"` EnglishName string `gorm:"size:128" json:"english_name"` Aliases string `gorm:"size:512" json:"aliases"` Pinyin string `gorm:"size:1024;index;not null" json:"pinyin"` PinyinInitials string `gorm:"size:128" json:"pinyin_initials"` Initial string `gorm:"size:1;index;not null" json:"initial"` Enabled bool `gorm:"not null;default:true" json:"enabled"` Source string `gorm:"size:128" json:"source"` SourceKey *string `gorm:"size:128;uniqueIndex" json:"source_key"` SourceURL string `gorm:"size:512" json:"source_url"` } // Facts contain short structured facts, never copied articles or remote images. type BreedReference struct { Base BreedID string `gorm:"type:uuid;uniqueIndex:idx_breed_provider;not null" json:"breed_id"` Provider string `gorm:"size:32;uniqueIndex:idx_breed_provider;not null" json:"provider"` ExternalID string `gorm:"size:128;not null" json:"external_id"` URL string `gorm:"size:512;not null" json:"url"` RetrievedAt string `gorm:"size:10;not null" json:"retrieved_at"` Facts map[string]string `gorm:"serializer:json;type:jsonb;not null" json:"facts"` } type Pet struct { Base UserID string `gorm:"type:uuid;index;not null" json:"user_id"` Name string `gorm:"size:64;not null" json:"name"` Species string `gorm:"size:16;not null;default:dog" json:"species"` Breed string `gorm:"size:128" json:"breed"` BreedID *string `gorm:"type:uuid;index" json:"breed_id"` Gender string `gorm:"size:16" json:"gender"` BirthDate *time.Time `gorm:"type:date" json:"birth_date"` WeightKg float64 `gorm:"type:numeric(6,2)" json:"weight_kg"` AvatarFileID *string `gorm:"type:uuid;index" json:"avatar_file_id"` Coat string `gorm:"size:64" json:"coat"` Allergies string `gorm:"size:255" json:"allergies"` CareNotes string `gorm:"size:1000" json:"care_notes"` Archived bool `gorm:"not null;default:false" json:"archived"` } type CareTask struct { Base UserID string `gorm:"type:uuid;index;not null" json:"user_id"` PetID string `gorm:"type:uuid;index;not null" json:"pet_id"` Title string `gorm:"size:128;not null" json:"title"` Category string `gorm:"size:32;not null" json:"category"` TimeOfDay string `gorm:"size:5;not null" json:"time_of_day"` RepeatRule string `gorm:"size:16;not null;default:daily" json:"repeat_rule"` ReminderEnabled bool `gorm:"not null;default:false" json:"reminder_enabled"` } type TaskCompletion struct { Base TaskID string `gorm:"type:uuid;uniqueIndex:idx_task_date;not null" json:"task_id"` UserID string `gorm:"type:uuid;index;not null" json:"user_id"` Date time.Time `gorm:"type:date;uniqueIndex:idx_task_date;not null" json:"date"` CompletedAt time.Time `gorm:"not null" json:"completed_at"` } type Walk struct { Base UserID string `gorm:"type:uuid;index;not null" json:"user_id"` Status string `gorm:"size:16;index;not null" json:"status"` StartedAt time.Time `gorm:"not null" json:"started_at"` SegmentStartedAt time.Time `gorm:"not null" json:"segment_started_at"` PausedAt *time.Time `json:"paused_at"` PausedSeconds int `gorm:"not null;default:0" json:"paused_seconds"` EndedAt *time.Time `json:"ended_at"` DurationSeconds int `gorm:"not null;default:0" json:"duration_seconds"` DistanceMeters float64 `gorm:"type:numeric(12,2);not null;default:0" json:"distance_meters"` ShareEnabled bool `gorm:"not null;default:false" json:"share_enabled"` ShareToken string `gorm:"type:uuid;uniqueIndex" json:"-"` ShareExpiresAt *time.Time `json:"share_expires_at"` } type WalkPet struct { Base WalkID string `gorm:"type:uuid;uniqueIndex:idx_walk_pet;not null" json:"walk_id"` PetID string `gorm:"type:uuid;uniqueIndex:idx_walk_pet;index;not null" json:"pet_id"` } type WalkPoint struct { Base WalkID string `gorm:"type:uuid;index;not null" json:"walk_id"` Latitude float64 `gorm:"type:numeric(10,7);not null" json:"latitude"` Longitude float64 `gorm:"type:numeric(10,7);not null" json:"longitude"` RecordedAt time.Time `gorm:"index;not null" json:"recorded_at"` } type Meetup struct { Base HostUserID string `gorm:"type:uuid;index;not null" json:"host_user_id"` Title string `gorm:"size:128;not null" json:"title"` Description string `gorm:"size:1000" json:"description"` PlaceName string `gorm:"size:255;not null" json:"place_name"` Latitude float64 `gorm:"type:numeric(10,7)" json:"latitude"` Longitude float64 `gorm:"type:numeric(10,7)" json:"longitude"` StartsAt time.Time `gorm:"index;not null" json:"starts_at"` MaxPeople int `gorm:"not null;default:8" json:"max_people"` Status string `gorm:"size:16;index;not null;default:open" json:"status"` CoverFileID *string `gorm:"type:uuid;index" json:"cover_file_id"` } type MeetupParticipant struct { Base MeetupID string `gorm:"type:uuid;uniqueIndex:idx_meetup_user;not null" json:"meetup_id"` UserID string `gorm:"type:uuid;uniqueIndex:idx_meetup_user;index;not null" json:"user_id"` } type MeetupPet struct { Base ParticipantID string `gorm:"type:uuid;uniqueIndex:idx_participant_pet;not null" json:"participant_id"` PetID string `gorm:"type:uuid;uniqueIndex:idx_participant_pet;index;not null" json:"pet_id"` } func All() []any { return []any{ &File{}, &User{}, &Admin{}, &RefreshSession{}, &Breed{}, &BreedReference{}, &Pet{}, &CareTask{}, &TaskCompletion{}, &Walk{}, &WalkPet{}, &WalkPoint{}, &Meetup{}, &MeetupParticipant{}, &MeetupPet{}, } }