package store import ( "context" "time" "git.gobha.me/xcaliber/chat-switchboard/models" ) // ========================================= // STORES — Data Access Layer // ========================================= // Every database operation goes through these interfaces. // Handlers never touch SQL directly. This makes the DB // portable (Postgres today, SQLite/MySQL later) and // handlers testable with in-memory implementations. // ========================================= // Stores bundles all store interfaces for dependency injection. type Stores struct { Providers ProviderStore Catalog CatalogStore Personas PersonaStore Policies PolicyStore UserSettings UserModelSettingsStore Users UserStore Teams TeamStore Channels ChannelStore Messages MessageStore Audit AuditStore Notes NoteStore GlobalConfig GlobalConfigStore Usage UsageStore Pricing PricingStore } // ========================================= // PROVIDER STORE // ========================================= type ProviderStore interface { Create(ctx context.Context, cfg *models.ProviderConfig) error GetByID(ctx context.Context, id string) (*models.ProviderConfig, error) Update(ctx context.Context, id string, patch models.ProviderConfigPatch) error Delete(ctx context.Context, id string) error // Scoped queries ListGlobal(ctx context.Context) ([]models.ProviderConfig, error) ListForTeam(ctx context.Context, teamID string) ([]models.ProviderConfig, error) ListForUser(ctx context.Context, userID string) ([]models.ProviderConfig, error) // personal scope ListAccessible(ctx context.Context, userID string) ([]models.ProviderConfig, error) // all user can access // Access check UserCanAccess(ctx context.Context, userID, configID string) (bool, error) } // ========================================= // CATALOG STORE // ========================================= type CatalogStore interface { // Sync from provider API UpsertFromSync(ctx context.Context, providerConfigID string, entries []CatalogSyncEntry) (added, updated int, err error) // Queries GetByID(ctx context.Context, id string) (*models.CatalogEntry, error) GetByModelID(ctx context.Context, providerConfigID, modelID string) (*models.CatalogEntry, error) GetByModelIDAny(ctx context.Context, modelID string) (*models.CatalogEntry, error) // any provider, most recently synced ListVisible(ctx context.Context) ([]models.CatalogEntry, error) ListEnabledForProvider(ctx context.Context, providerConfigID string) ([]models.CatalogEntry, error) ListForProvider(ctx context.Context, providerConfigID string) ([]models.CatalogEntry, error) ListAll(ctx context.Context) ([]models.CatalogEntry, error) // everything (internal use) ListAllGlobal(ctx context.Context) ([]models.CatalogEntry, error) // admin view — global-scope providers only // Visibility management SetVisibility(ctx context.Context, id string, visibility string) error BulkSetVisibility(ctx context.Context, providerConfigID string, visibility string) error BulkSetVisibilityAll(ctx context.Context, visibility string) error // Delete Delete(ctx context.Context, id string) error DeleteForProvider(ctx context.Context, providerConfigID string) error } // CatalogSyncEntry is the input format from provider FetchModels. type CatalogSyncEntry struct { ModelID string DisplayName string Capabilities models.ModelCapabilities Pricing *models.ModelPricing } // ========================================= // PERSONA STORE // ========================================= type PersonaStore interface { Create(ctx context.Context, p *models.Persona) error GetByID(ctx context.Context, id string) (*models.Persona, error) Update(ctx context.Context, id string, patch models.PersonaPatch) error Delete(ctx context.Context, id string) error // Scoped queries ListForUser(ctx context.Context, userID string) ([]models.Persona, error) // all visible to user ListForTeam(ctx context.Context, teamID string) ([]models.Persona, error) ListGlobal(ctx context.Context) ([]models.Persona, error) ListPersonal(ctx context.Context, userID string) ([]models.Persona, error) // user's own // Grants SetGrants(ctx context.Context, personaID string, grants []models.Grant) error GetGrants(ctx context.Context, personaID string) ([]models.Grant, error) GetToolGrants(ctx context.Context, personaID string) ([]string, error) // Access check UserCanAccess(ctx context.Context, userID, personaID string) (bool, error) } // ========================================= // POLICY STORE // ========================================= type PolicyStore interface { Get(ctx context.Context, key string) (string, error) GetBool(ctx context.Context, key string) (bool, error) Set(ctx context.Context, key, value string, updatedBy string) error GetAll(ctx context.Context) (map[string]string, error) } // ========================================= // USER MODEL SETTINGS STORE // ========================================= type UserModelSettingsStore interface { GetForUser(ctx context.Context, userID string) ([]models.UserModelSetting, error) GetHiddenModelIDs(ctx context.Context, userID string) (map[string]bool, error) Set(ctx context.Context, userID, modelID string, patch models.UserModelSettingPatch) error BulkSetHidden(ctx context.Context, userID string, modelIDs []string, hidden bool) error } // ========================================= // USER STORE // ========================================= type UserStore interface { Create(ctx context.Context, u *models.User) error GetByID(ctx context.Context, id string) (*models.User, error) GetByUsername(ctx context.Context, username string) (*models.User, error) GetByEmail(ctx context.Context, email string) (*models.User, error) GetByLogin(ctx context.Context, login string) (*models.User, error) // username or email Update(ctx context.Context, id string, fields map[string]interface{}) error Delete(ctx context.Context, id string) error List(ctx context.Context, opts ListOptions) ([]models.User, int, error) UpdateLastLogin(ctx context.Context, id string) error SetActive(ctx context.Context, id string, active bool) error // Refresh tokens CreateRefreshToken(ctx context.Context, userID, tokenHash string, expiresAt time.Time) error GetRefreshToken(ctx context.Context, tokenHash string) (userID string, err error) RevokeRefreshToken(ctx context.Context, tokenHash string) error RevokeAllRefreshTokens(ctx context.Context, userID string) error CleanExpiredTokens(ctx context.Context) error } // ========================================= // TEAM STORE // ========================================= type TeamStore interface { Create(ctx context.Context, t *models.Team) error GetByID(ctx context.Context, id string) (*models.Team, error) Update(ctx context.Context, id string, fields map[string]interface{}) error Delete(ctx context.Context, id string) error List(ctx context.Context) ([]models.Team, error) // Members AddMember(ctx context.Context, teamID, userID, role string) error RemoveMember(ctx context.Context, teamID, userID string) error UpdateMemberRole(ctx context.Context, teamID, userID, role string) error ListMembers(ctx context.Context, teamID string) ([]models.TeamMember, error) GetMember(ctx context.Context, teamID, userID string) (*models.TeamMember, error) GetUserTeamIDs(ctx context.Context, userID string) ([]string, error) IsTeamAdmin(ctx context.Context, teamID, userID string) (bool, error) IsMember(ctx context.Context, teamID, userID string) (bool, error) } // ========================================= // CHANNEL STORE // ========================================= type ChannelStore interface { Create(ctx context.Context, ch *models.Channel) error GetByID(ctx context.Context, id string) (*models.Channel, error) Update(ctx context.Context, id string, fields map[string]interface{}) error Delete(ctx context.Context, id string) error ListForUser(ctx context.Context, userID string, opts ListOptions) ([]models.Channel, int, error) Search(ctx context.Context, userID, query string, opts ListOptions) ([]models.Channel, int, error) // Cursor management (conversation forking) GetCursor(ctx context.Context, channelID, userID string) (*models.ChannelCursor, error) SetCursor(ctx context.Context, channelID, userID, leafID string) error // Channel models SetModel(ctx context.Context, cm *models.ChannelModel) error GetModels(ctx context.Context, channelID string) ([]models.ChannelModel, error) // Ownership check UserOwns(ctx context.Context, channelID, userID string) (bool, error) } // ========================================= // MESSAGE STORE // ========================================= type MessageStore interface { Create(ctx context.Context, m *models.Message) error GetByID(ctx context.Context, id string) (*models.Message, error) Update(ctx context.Context, id string, fields map[string]interface{}) error Delete(ctx context.Context, id string) error // soft delete ListForChannel(ctx context.Context, channelID string, opts ListOptions) ([]models.Message, error) // Tree operations GetChildren(ctx context.Context, parentID string) ([]models.Message, error) GetSiblings(ctx context.Context, messageID string) ([]models.Message, error) GetPathToRoot(ctx context.Context, messageID string) ([]models.Message, error) GetNextSiblingIndex(ctx context.Context, parentID string) (int, error) // Count CountForChannel(ctx context.Context, channelID string) (int, error) } // ========================================= // AUDIT STORE // ========================================= type AuditStore interface { Log(ctx context.Context, entry *models.AuditEntry) error List(ctx context.Context, opts AuditListOptions) ([]models.AuditEntry, int, error) } type AuditListOptions struct { ListOptions ActorID string Action string ResourceType string ResourceID string Since *time.Time Until *time.Time } // ========================================= // NOTE STORE // ========================================= type NoteStore interface { Create(ctx context.Context, n *models.Note) error GetByID(ctx context.Context, id string) (*models.Note, error) Update(ctx context.Context, id string, fields map[string]interface{}) error Delete(ctx context.Context, id string) error ListForUser(ctx context.Context, userID string, opts NoteListOptions) ([]models.Note, int, error) Search(ctx context.Context, userID, query string, opts ListOptions) ([]models.Note, int, error) BulkDelete(ctx context.Context, ids []string, userID string) (int, error) } type NoteListOptions struct { ListOptions FolderPath string Tag string TeamID string } // ========================================= // GLOBAL CONFIG STORE // ========================================= type GlobalConfigStore interface { Get(ctx context.Context, key string) (models.JSONMap, error) Set(ctx context.Context, key string, value models.JSONMap, updatedBy string) error GetAll(ctx context.Context) (map[string]models.JSONMap, error) } // ========================================= // USAGE STORE // ========================================= type UsageStore interface { Log(ctx context.Context, entry *models.UsageEntry) error QueryByUser(ctx context.Context, userID string, opts UsageQueryOptions) ([]models.UsageAggregate, error) QueryByUserPersonal(ctx context.Context, userID string, opts UsageQueryOptions) ([]models.UsageAggregate, error) QueryByTeam(ctx context.Context, teamID string, opts UsageQueryOptions) ([]models.UsageAggregate, error) QueryByTeamProviders(ctx context.Context, teamID string, opts UsageQueryOptions) ([]models.UsageAggregate, error) QueryByModel(ctx context.Context, opts UsageQueryOptions) ([]models.UsageAggregate, error) GetTotals(ctx context.Context, opts UsageQueryOptions) (*models.UsageTotals, error) GetTeamProviderTotals(ctx context.Context, teamID string, opts UsageQueryOptions) (*models.UsageTotals, error) GetPersonalTotals(ctx context.Context, userID string, opts UsageQueryOptions) (*models.UsageTotals, error) } type UsageQueryOptions struct { Since *time.Time Until *time.Time GroupBy string // "day", "model", "user", "provider" ExcludeBYOK bool // true for admin views — filters provider_scope != 'personal' Limit int } // ========================================= // PRICING STORE // ========================================= type PricingStore interface { GetForModel(ctx context.Context, providerConfigID, modelID string) (*models.PricingEntry, error) Upsert(ctx context.Context, entry *models.PricingEntry) error UpsertFromCatalog(ctx context.Context, providerConfigID, modelID string, pricing *models.ModelPricing) error List(ctx context.Context) ([]models.PricingEntry, error) Delete(ctx context.Context, providerConfigID, modelID string) error } // ========================================= // SHARED TYPES // ========================================= // ListOptions provides standard pagination/sort for list queries. type ListOptions struct { Limit int Offset int Sort string // column name Order string // "asc" or "desc" } // DefaultListOptions returns sensible defaults. func DefaultListOptions() ListOptions { return ListOptions{ Limit: 50, Order: "desc", } }