569 lines
24 KiB
Go
569 lines
24 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
|
|
NoteLinks NoteLinkStore
|
|
GlobalConfig GlobalConfigStore
|
|
Usage UsageStore
|
|
Pricing PricingStore
|
|
Extensions ExtensionStore
|
|
Attachments AttachmentStore
|
|
KnowledgeBases KnowledgeBaseStore
|
|
Groups GroupStore
|
|
ResourceGrants ResourceGrantStore
|
|
Memories MemoryStore
|
|
Projects ProjectStore
|
|
Notifications NotificationStore
|
|
NotifPrefs NotificationPreferenceStore
|
|
Workspaces WorkspaceStore
|
|
}
|
|
|
|
// =========================================
|
|
// 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)
|
|
|
|
// Knowledge base bindings
|
|
SetKBs(ctx context.Context, personaID string, kbIDs []string, autoSearch map[string]bool) error
|
|
GetKBs(ctx context.Context, personaID string) ([]models.PersonaKB, error)
|
|
GetKBIDs(ctx context.Context, personaID string) ([]string, 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)
|
|
GetModelByID(ctx context.Context, id string) (*models.ChannelModel, error)
|
|
UpdateModel(ctx context.Context, id string, fields map[string]interface{}) error
|
|
DeleteModel(ctx context.Context, id string) error
|
|
|
|
// Ownership check
|
|
UserOwns(ctx context.Context, channelID, userID string) (bool, error)
|
|
|
|
// Workspace resolution: channel workspace_id > project workspace_id
|
|
ResolveWorkspaceID(ctx context.Context, channelID string) (string, 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)
|
|
SearchTitles(ctx context.Context, userID, query string, limit int) ([]models.Note, error)
|
|
BulkDelete(ctx context.Context, ids []string, userID string) (int, error)
|
|
}
|
|
|
|
type NoteListOptions struct {
|
|
ListOptions
|
|
FolderPath string
|
|
Tag string
|
|
TeamID string
|
|
}
|
|
|
|
// =========================================
|
|
// NOTE LINK STORE
|
|
// =========================================
|
|
|
|
// NoteLinkStore manages wikilink edges between notes.
|
|
type NoteLinkStore interface {
|
|
// ReplaceLinks deletes existing links for sourceNoteID and inserts new ones.
|
|
ReplaceLinks(ctx context.Context, sourceNoteID string, links []models.NoteLink) error
|
|
|
|
// ResolveByTitle sets target_note_id on dangling links matching the title for a user.
|
|
ResolveByTitle(ctx context.Context, userID, targetNoteID, title string) error
|
|
|
|
// Backlinks returns notes that link to the given note.
|
|
Backlinks(ctx context.Context, noteID string) ([]models.NoteLinkResult, error)
|
|
|
|
// Graph returns all nodes and edges for a user's note graph.
|
|
Graph(ctx context.Context, userID string) (*models.NoteGraph, error)
|
|
}
|
|
|
|
// =========================================
|
|
// 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
|
|
}
|
|
|
|
// =========================================
|
|
// EXTENSION STORE
|
|
// =========================================
|
|
|
|
type ExtensionStore interface {
|
|
// Admin CRUD
|
|
Create(ctx context.Context, ext *models.Extension) error
|
|
GetByID(ctx context.Context, id string) (*models.Extension, error)
|
|
GetByExtID(ctx context.Context, extID string) (*models.Extension, error)
|
|
Update(ctx context.Context, id string, ext *models.Extension) error
|
|
Delete(ctx context.Context, id string) error
|
|
|
|
// Listing
|
|
ListAll(ctx context.Context) ([]models.Extension, error)
|
|
ListEnabled(ctx context.Context) ([]models.Extension, error)
|
|
ListForUser(ctx context.Context, userID string) ([]models.UserExtension, error)
|
|
|
|
// User settings
|
|
GetUserSettings(ctx context.Context, extID, userID string) (*models.ExtensionUserSettings, error)
|
|
SetUserSettings(ctx context.Context, s *models.ExtensionUserSettings) error
|
|
DeleteUserSettings(ctx context.Context, extID, userID string) error
|
|
}
|
|
|
|
// =========================================
|
|
// ATTACHMENT STORE
|
|
// =========================================
|
|
|
|
type AttachmentStore interface {
|
|
Create(ctx context.Context, a *models.Attachment) error
|
|
GetByID(ctx context.Context, id string) (*models.Attachment, error)
|
|
GetByChannel(ctx context.Context, channelID string) ([]models.Attachment, error)
|
|
GetByMessage(ctx context.Context, messageID string) ([]models.Attachment, error)
|
|
SetMessageID(ctx context.Context, attachmentID, messageID string) error
|
|
UpdateMetadata(ctx context.Context, id string, metadata map[string]interface{}) error
|
|
SetExtractedText(ctx context.Context, id string, text string) error
|
|
Delete(ctx context.Context, id string) (*models.Attachment, error) // returns deleted row for storage cleanup
|
|
DeleteByChannel(ctx context.Context, channelID string) ([]string, error) // returns storage_keys
|
|
UserUsageBytes(ctx context.Context, userID string) (int64, error)
|
|
ListOrphans(ctx context.Context, olderThan time.Duration) ([]models.Attachment, error)
|
|
}
|
|
|
|
// =========================================
|
|
// KNOWLEDGE BASE STORE
|
|
// =========================================
|
|
|
|
type KnowledgeBaseStore interface {
|
|
// KB CRUD
|
|
Create(ctx context.Context, kb *models.KnowledgeBase) error
|
|
GetByID(ctx context.Context, id string) (*models.KnowledgeBase, error)
|
|
Update(ctx context.Context, id string, fields map[string]interface{}) error
|
|
Delete(ctx context.Context, id string) error
|
|
|
|
// Scoped listing
|
|
ListForUser(ctx context.Context, userID string, teamIDs []string) ([]models.KnowledgeBase, error)
|
|
ListGlobal(ctx context.Context) ([]models.KnowledgeBase, error)
|
|
ListForTeam(ctx context.Context, teamID string) ([]models.KnowledgeBase, error)
|
|
ListPersonal(ctx context.Context, userID string) ([]models.KnowledgeBase, error)
|
|
|
|
// Documents
|
|
CreateDocument(ctx context.Context, doc *models.KBDocument) error
|
|
GetDocument(ctx context.Context, id string) (*models.KBDocument, error)
|
|
ListDocuments(ctx context.Context, kbID string) ([]models.KBDocument, error)
|
|
UpdateDocumentStatus(ctx context.Context, id string, status string, errMsg *string) error
|
|
UpdateDocumentText(ctx context.Context, id string, text string, chunkCount int) error
|
|
UpdateDocumentStorageKey(ctx context.Context, id string, storageKey string) error
|
|
DeleteDocument(ctx context.Context, id string) (*models.KBDocument, error) // returns for storage cleanup
|
|
|
|
// Chunks
|
|
InsertChunks(ctx context.Context, chunks []models.KBChunk) error
|
|
DeleteChunksForDocument(ctx context.Context, documentID string) error
|
|
SimilaritySearch(ctx context.Context, kbIDs []string, queryVec []float64,
|
|
threshold float64, limit int) ([]models.KBSearchResult, error)
|
|
|
|
// Channel links
|
|
SetChannelKBs(ctx context.Context, channelID string, kbIDs []string) error
|
|
GetChannelKBs(ctx context.Context, channelID string) ([]models.ChannelKB, error)
|
|
GetActiveKBIDs(ctx context.Context, channelID string, userID string,
|
|
teamIDs []string) ([]string, error) // enabled + user has access
|
|
GetActiveKBIDsWithPersona(ctx context.Context, channelID string, userID string,
|
|
teamIDs []string, personaID string) ([]string, error) // includes persona-bound KBs
|
|
ListDiscoverable(ctx context.Context, userID string, teamIDs []string) ([]models.KnowledgeBase, error)
|
|
|
|
// Stats — recount document_count/chunk_count/total_bytes from child tables
|
|
UpdateStats(ctx context.Context, kbID string) error
|
|
|
|
// Discoverable management
|
|
SetDiscoverable(ctx context.Context, kbID string, discoverable bool) error
|
|
}
|
|
|
|
// =========================================
|
|
// GROUP STORE (v0.16.0)
|
|
// =========================================
|
|
|
|
type GroupStore interface {
|
|
Create(ctx context.Context, g *models.Group) error
|
|
GetByID(ctx context.Context, id string) (*models.Group, error)
|
|
Update(ctx context.Context, id string, name, description *string) error
|
|
Delete(ctx context.Context, id string) error
|
|
|
|
// Scoped listing
|
|
ListAll(ctx context.Context) ([]models.Group, error) // admin
|
|
ListForTeam(ctx context.Context, teamID string) ([]models.Group, error) // team admin
|
|
ListForUser(ctx context.Context, userID string) ([]models.Group, error) // groups I belong to
|
|
|
|
// Members
|
|
AddMember(ctx context.Context, groupID, userID, addedBy string) error
|
|
RemoveMember(ctx context.Context, groupID, userID string) error
|
|
ListMembers(ctx context.Context, groupID string) ([]models.GroupMember, error)
|
|
IsMember(ctx context.Context, groupID, userID string) (bool, error)
|
|
|
|
// For access resolution: returns group IDs a user belongs to
|
|
GetUserGroupIDs(ctx context.Context, userID string) ([]string, error)
|
|
}
|
|
|
|
// =========================================
|
|
// RESOURCE GRANT STORE (v0.16.0)
|
|
// =========================================
|
|
|
|
type ResourceGrantStore interface {
|
|
Set(ctx context.Context, grant *models.ResourceGrant) error
|
|
Get(ctx context.Context, resourceType, resourceID string) (*models.ResourceGrant, error)
|
|
Delete(ctx context.Context, resourceType, resourceID string) error
|
|
|
|
// Access check: does userID have group-based access to this resource?
|
|
UserHasGroupAccess(ctx context.Context, userID, resourceType, resourceID string) (bool, error)
|
|
}
|
|
|
|
// =========================================
|
|
// NOTIFICATION STORE (v0.20.0)
|
|
// =========================================
|
|
|
|
type NotificationStore interface {
|
|
Create(ctx context.Context, n *models.Notification) error
|
|
ListByUser(ctx context.Context, userID string, limit, offset int, unreadOnly bool) ([]models.Notification, int, error)
|
|
MarkRead(ctx context.Context, id, userID string) error
|
|
MarkAllRead(ctx context.Context, userID string) error
|
|
Delete(ctx context.Context, id, userID string) error
|
|
UnreadCount(ctx context.Context, userID string) (int, error)
|
|
DeleteOlderThan(ctx context.Context, before time.Time) (int64, error)
|
|
}
|
|
|
|
// =========================================
|
|
// NOTIFICATION PREFERENCES STORE (v0.20.0)
|
|
// =========================================
|
|
|
|
type NotificationPreferenceStore interface {
|
|
// Get returns the preference for a specific user + type. Returns nil if not set.
|
|
Get(ctx context.Context, userID, notifType string) (*models.NotificationPreference, error)
|
|
// ListForUser returns all preferences set by a user.
|
|
ListForUser(ctx context.Context, userID string) ([]models.NotificationPreference, error)
|
|
// Upsert creates or updates a preference row.
|
|
Upsert(ctx context.Context, pref *models.NotificationPreference) error
|
|
// Delete removes a preference (falls back to default).
|
|
Delete(ctx context.Context, userID, notifType string) error
|
|
}
|
|
|
|
// =========================================
|
|
// WORKSPACE STORE (v0.21.0)
|
|
// =========================================
|
|
|
|
type WorkspaceStore interface {
|
|
// CRUD
|
|
Create(ctx context.Context, w *models.Workspace) error
|
|
GetByID(ctx context.Context, id string) (*models.Workspace, error)
|
|
Update(ctx context.Context, id string, patch models.WorkspacePatch) error
|
|
Delete(ctx context.Context, id string) error
|
|
|
|
// Ownership lookup
|
|
GetByOwner(ctx context.Context, ownerType, ownerID string) (*models.Workspace, error)
|
|
ListByOwner(ctx context.Context, ownerType, ownerID string) ([]models.Workspace, error)
|
|
|
|
// File index
|
|
UpsertFile(ctx context.Context, f *models.WorkspaceFile) error
|
|
DeleteFile(ctx context.Context, workspaceID, path string) error
|
|
DeleteFilesByPrefix(ctx context.Context, workspaceID, prefix string) error
|
|
GetFile(ctx context.Context, workspaceID, path string) (*models.WorkspaceFile, error)
|
|
ListFiles(ctx context.Context, workspaceID, prefix string, recursive bool) ([]models.WorkspaceFile, error)
|
|
DeleteAllFiles(ctx context.Context, workspaceID string) error
|
|
|
|
// Stats
|
|
GetStats(ctx context.Context, workspaceID string) (*models.WorkspaceStats, error)
|
|
|
|
// Chunks (v0.21.2 — workspace indexing)
|
|
InsertChunks(ctx context.Context, chunks []models.WorkspaceChunk) error
|
|
DeleteChunksByFile(ctx context.Context, fileID string) error
|
|
SimilaritySearch(ctx context.Context, workspaceID string, queryVec []float64, threshold float64, limit int) ([]models.WorkspaceChunkResult, error)
|
|
UpdateFileIndexStatus(ctx context.Context, fileID, status string, chunkCount int) 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",
|
|
}
|
|
}
|