package model import ( "encoding/json" "time" ) // User represents a row in the `users` table. // // PasswordHash is tagged `json:"-"` so it never leaks via JSON marshalling // even if a caller accidentally passes a User through response.JSON. It's a // pointer because OAuth-only users have no password — *PasswordHash == nil // means "this account can only sign in via a linked OAuth provider". // // EmailVerifiedAt is a pointer so JSON-encoded users carry an explicit `null` // when the user has not yet verified, instead of an omitted field — the // frontend uses this to decide whether to show the "verify your email" ribbon. type User struct { ID string `json:"id"` // Email is nullable: OAuth users (typically Facebook accounts with no // verified email on file) sign up without one and can fill it in later // from their profile. Email *string `json:"email"` Handle string `json:"handle"` Name string `json:"name"` AvatarURL *string `json:"avatar_url"` Role string `json:"role"` Plan string `json:"plan"` // "free" | "premium" EmailVerifiedAt *time.Time `json:"email_verified_at"` PasswordHash *string `json:"-"` TokenVersion int `json:"-"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // MarshalJSON adds a derived `has_password` flag so the frontend can pick // "Change password" vs "Set a password" for OAuth-only accounts, without // the password hash itself ever leaving the server. func (u User) MarshalJSON() ([]byte, error) { type alias User return json.Marshal(&struct { alias HasPassword bool `json:"has_password"` }{ alias: alias(u), HasPassword: u.PasswordHash != nil, }) } // AuthToken represents a row in the `auth_tokens` table. // Used for both password reset and email verification flows. type AuthToken struct { ID string `json:"id"` UserID string `json:"user_id"` Purpose string `json:"purpose"` // "password_reset" | "email_verification" TokenHash string `json:"-"` ExpiresAt time.Time `json:"expires_at"` UsedAt *time.Time `json:"used_at,omitempty"` CreatedAt time.Time `json:"created_at"` } const ( PurposePasswordReset = "password_reset" PurposeEmailVerification = "email_verification" ) // OAuthAccount represents a row in the `oauth_accounts` table — one entry // per (provider, provider_user_id). A single user can have several linked // providers. type OAuthAccount struct { ID string `json:"id"` UserID string `json:"user_id"` Provider string `json:"provider"` ProviderUserID string `json:"provider_user_id"` Email *string `json:"email"` Name *string `json:"name"` AvatarURL *string `json:"avatar_url"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // ShortURL represents a shortened URL. type ShortURL struct { ID string `json:"id"` Slug string `json:"slug"` TargetURL string `json:"target_url"` CreatedBy *string `json:"created_by,omitempty"` ClickCount int64 `json:"click_count"` CreatedAt time.Time `json:"created_at"` ExpiresAt *time.Time `json:"expires_at,omitempty"` }