This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/store/interfaces.go
2026-02-24 22:01:20 +00:00

338 lines
13 KiB
Go

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
ModelType string // "chat", "embedding", "image" — from provider API
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
CountRecentByRole(ctx context.Context, userID, role string, since time.Time) (int, 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",
}
}