Changeset 0.29.0 (#195)
This commit is contained in:
@@ -2,6 +2,7 @@ package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
@@ -55,6 +56,11 @@ type Stores struct {
|
||||
Packages PackageStore // v0.28.7: Unified package registry (surfaces + extensions)
|
||||
Workflows WorkflowStore // v0.26.1: Workflow definitions + stages
|
||||
Tasks TaskStore // v0.27.1: Task scheduling + run history
|
||||
Presence PresenceStore // v0.29.0: User online/offline status
|
||||
PersonaGroups PersonaGroupStore // v0.29.0: Persona group (roster template) CRUD
|
||||
Folders FolderStore // v0.29.0: Chat folder CRUD
|
||||
Health HealthStore // v0.29.0: Provider health window management
|
||||
ExtPermissions ExtensionPermissionStore // v0.29.0: Extension declared/granted capabilities
|
||||
}
|
||||
|
||||
// =========================================
|
||||
@@ -75,6 +81,31 @@ type ProviderStore interface {
|
||||
|
||||
// Access check
|
||||
UserCanAccess(ctx context.Context, userID, configID string) (bool, error)
|
||||
|
||||
// ── CS4 additions (v0.29.0) ──
|
||||
|
||||
// DeletePersonalByOwner removes all personal-scope provider configs for a user.
|
||||
DeletePersonalByOwner(ctx context.Context, ownerID string) (int64, error)
|
||||
|
||||
// ── CS6 additions (v0.29.0) ──
|
||||
|
||||
// ListAllForTeam returns all provider configs (including inactive) for a team.
|
||||
ListAllForTeam(ctx context.Context, teamID string) ([]models.ProviderConfig, error)
|
||||
|
||||
// DeleteByIDAndTeam deletes a team-scoped provider by ID and team. Returns rows affected.
|
||||
DeleteByIDAndTeam(ctx context.Context, id, teamID string) (int64, error)
|
||||
|
||||
// ── CS7a additions (v0.29.0) ──
|
||||
|
||||
// FindFirstForUser returns the ID of the first active provider config
|
||||
// accessible to a user (personal first, then global; excludes team).
|
||||
// Returns sql.ErrNoRows if none found.
|
||||
FindFirstForUser(ctx context.Context, userID string) (string, error)
|
||||
|
||||
// LoadAccessible loads a provider config by ID, verifying the user has access
|
||||
// (global, personal owned by user, or team the user belongs to).
|
||||
// Returns sql.ErrNoRows if not found or not accessible.
|
||||
LoadAccessible(ctx context.Context, configID, userID string) (*models.ProviderConfig, error)
|
||||
}
|
||||
|
||||
// =========================================
|
||||
@@ -103,6 +134,41 @@ type CatalogStore interface {
|
||||
// Delete
|
||||
Delete(ctx context.Context, id string) error
|
||||
DeleteForProvider(ctx context.Context, providerConfigID string) error
|
||||
|
||||
// ── Mention resolution (v0.29.0 — moved from handler raw SQL) ──
|
||||
|
||||
// FindEnabledByModelID finds an enabled catalog entry by exact model_id
|
||||
// (case-insensitive). Returns (modelID, providerConfigID, nil).
|
||||
// Prefers global > team > personal scope providers.
|
||||
FindEnabledByModelID(ctx context.Context, modelID string) (string, string, error)
|
||||
|
||||
// FindEnabledByModelIDPrefix finds by unambiguous model_id prefix.
|
||||
// Returns (modelID, configID, matchCount, nil).
|
||||
FindEnabledByModelIDPrefix(ctx context.Context, prefix string) (string, string, int, error)
|
||||
|
||||
// ── CS2 additions (v0.29.0) ──
|
||||
|
||||
// GetCapabilities returns the raw capabilities JSON for a specific provider+model.
|
||||
GetCapabilities(ctx context.Context, modelID, configID string) ([]byte, error)
|
||||
|
||||
// GetCapabilitiesAny returns capabilities for a model from any provider (most recent sync).
|
||||
GetCapabilitiesAny(ctx context.Context, modelID string) ([]byte, error)
|
||||
|
||||
// ── CS6 additions (v0.29.0) ──
|
||||
|
||||
// ListTeamAvailable returns catalog entries with visibility 'enabled' or 'team'
|
||||
// from active global providers. Used by team model selector.
|
||||
ListTeamAvailable(ctx context.Context) ([]TeamAvailableModel, error)
|
||||
}
|
||||
|
||||
// TeamAvailableModel is returned by CatalogStore.ListTeamAvailable.
|
||||
type TeamAvailableModel struct {
|
||||
ID string
|
||||
ModelID string
|
||||
DisplayName *string
|
||||
Visibility string
|
||||
Provider string
|
||||
ProviderName string
|
||||
}
|
||||
|
||||
// CatalogSyncEntry is the input format from provider FetchModels.
|
||||
@@ -141,6 +207,25 @@ type PersonaStore interface {
|
||||
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)
|
||||
|
||||
// ── Mention resolution (v0.29.0 — moved from handler raw SQL) ──
|
||||
|
||||
// FindActiveByHandle returns an active persona's ID by exact handle match (case-insensitive).
|
||||
// Returns "" if not found.
|
||||
FindActiveByHandle(ctx context.Context, handle string) (string, error)
|
||||
|
||||
// FindActiveByHandlePrefix returns a persona ID by unambiguous handle prefix.
|
||||
// Returns ("", count, nil) if ambiguous or no match.
|
||||
FindActiveByHandlePrefix(ctx context.Context, prefix string) (string, int, error)
|
||||
|
||||
// GetNameByID returns the persona's display name. Returns "" if not found.
|
||||
GetNameByID(ctx context.Context, id string) (string, error)
|
||||
|
||||
// GetNamesByIDs returns a map of persona ID → name for batch resolution.
|
||||
GetNamesByIDs(ctx context.Context, ids []string) (map[string]string, error)
|
||||
|
||||
// GetDisplayInfoByIDs returns name + avatar for batch sender resolution.
|
||||
GetDisplayInfoByIDs(ctx context.Context, ids []string) (map[string]UserDisplayInfo, error)
|
||||
}
|
||||
|
||||
// =========================================
|
||||
@@ -190,6 +275,61 @@ type UserStore interface {
|
||||
RevokeRefreshToken(ctx context.Context, tokenHash string) error
|
||||
RevokeAllRefreshTokens(ctx context.Context, userID string) error
|
||||
CleanExpiredTokens(ctx context.Context) error
|
||||
|
||||
// ── Mention resolution (v0.29.0 — moved from handler raw SQL) ──
|
||||
|
||||
// FindActiveByHandle returns a user ID by exact handle match (case-insensitive).
|
||||
// excludeUserID prevents self-mention resolution.
|
||||
FindActiveByHandle(ctx context.Context, handle, excludeUserID string) (string, error)
|
||||
|
||||
// FindActiveByHandlePrefix returns a user ID by unambiguous handle prefix.
|
||||
// Returns ("", count, nil) if ambiguous or no match.
|
||||
FindActiveByHandlePrefix(ctx context.Context, prefix, excludeUserID string) (string, int, error)
|
||||
|
||||
// GetDisplayInfoByIDs returns name + avatar for batch sender resolution.
|
||||
GetDisplayInfoByIDs(ctx context.Context, ids []string) (map[string]UserDisplayInfo, error)
|
||||
|
||||
// ── CS1 additions (v0.29.0) ──
|
||||
|
||||
// Exists returns true if a user with the given ID exists and is active.
|
||||
Exists(ctx context.Context, userID string) (bool, error)
|
||||
|
||||
// SearchActive returns users matching a query (username, display_name, handle).
|
||||
// Excludes the calling user. Max 20 results.
|
||||
SearchActive(ctx context.Context, excludeUserID, query string) ([]UserSearchResult, error)
|
||||
|
||||
// ── CS2 additions (v0.29.0) ──
|
||||
|
||||
// CountByRole returns the number of users with a given role.
|
||||
CountByRole(ctx context.Context, role string) (int, error)
|
||||
|
||||
// CountAll returns the total number of users.
|
||||
CountAll(ctx context.Context) (int, error)
|
||||
|
||||
// MergeSettings performs a dialect-aware JSON merge into the user's settings column.
|
||||
MergeSettings(ctx context.Context, userID string, patch []byte) error
|
||||
|
||||
// GetVaultKeys returns the user's vault encryption state.
|
||||
GetVaultKeys(ctx context.Context, userID string) (vaultSet bool, encUEK, salt, nonce []byte, err error)
|
||||
|
||||
// UpdateVaultKeys re-wraps the user's vault keys (password change).
|
||||
UpdateVaultKeys(ctx context.Context, userID string, encUEK, salt, nonce []byte) error
|
||||
|
||||
// ── CS4 additions (v0.29.0) ──
|
||||
|
||||
// ClearVaultKeys nulls out vault columns and sets vault_set = false.
|
||||
ClearVaultKeys(ctx context.Context, userID string) error
|
||||
|
||||
// InitVaultKeys stores vault keys and sets vault_set = true (first-time init).
|
||||
InitVaultKeys(ctx context.Context, userID string, encUEK, salt, nonce []byte) error
|
||||
}
|
||||
|
||||
// UserSearchResult is a lightweight result for user search.
|
||||
type UserSearchResult struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Handle string `json:"handle"`
|
||||
}
|
||||
|
||||
// =========================================
|
||||
@@ -213,6 +353,37 @@ type TeamStore interface {
|
||||
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)
|
||||
|
||||
// ── CS1 additions (v0.29.0) ──
|
||||
|
||||
// Exists returns true if a team with the given ID exists.
|
||||
Exists(ctx context.Context, teamID string) (bool, error)
|
||||
|
||||
// UpdateMemberRoleByID updates a member's role using the member row ID.
|
||||
UpdateMemberRoleByID(ctx context.Context, memberID, teamID, role string) (int64, error)
|
||||
|
||||
// DeleteMemberByID deletes a member using the member row ID.
|
||||
DeleteMemberByID(ctx context.Context, memberID, teamID string) (int64, error)
|
||||
|
||||
// ListTeamAuditActions returns distinct audit actions for a team's members.
|
||||
ListTeamAuditActions(ctx context.Context, teamID string) ([]string, error)
|
||||
|
||||
// ── CS5b additions (v0.29.0) ──
|
||||
|
||||
// GetFirstTeamIDForUser returns the first team_id the user belongs to, or "" if none.
|
||||
GetFirstTeamIDForUser(ctx context.Context, userID string) (string, error)
|
||||
|
||||
// ── CS6 additions (v0.29.0) ──
|
||||
|
||||
// AddMemberReturningID inserts a team member and returns the row ID.
|
||||
AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error)
|
||||
|
||||
// HasPrivateProviderRequirement checks if a user belongs to any active team
|
||||
// with the require_private_providers setting enabled.
|
||||
HasPrivateProviderRequirement(ctx context.Context, userID string) (bool, error)
|
||||
|
||||
// MergeSettings merges a JSON string into the team's settings column.
|
||||
MergeSettings(ctx context.Context, teamID, settingsJSON string) error
|
||||
}
|
||||
|
||||
// =========================================
|
||||
@@ -260,6 +431,169 @@ type ChannelStore interface {
|
||||
// Admin: archived channel management (v0.23.2)
|
||||
ListArchived(ctx context.Context, opts ListOptions) ([]ArchivedChannel, int, error)
|
||||
Purge(ctx context.Context, id string) error // hard-delete; channel must be archived
|
||||
|
||||
// ── Single-field helpers (v0.29.0 — moved from handler raw SQL) ──
|
||||
|
||||
// GetAIMode returns the channel's ai_mode ("auto", "off", "mention_only").
|
||||
GetAIMode(ctx context.Context, channelID string) (string, error)
|
||||
|
||||
// GetTypeAndTeamID returns (channel_type, team_id) in one query.
|
||||
GetTypeAndTeamID(ctx context.Context, channelID string) (string, *string, error)
|
||||
|
||||
// GetSystemPrompt returns the channel's system_prompt (may be nil).
|
||||
GetSystemPrompt(ctx context.Context, channelID string) (*string, error)
|
||||
|
||||
// GetDefaultModel returns the channel's model field (may be nil).
|
||||
GetDefaultModel(ctx context.Context, channelID string) (*string, error)
|
||||
|
||||
// TouchUpdatedAt bumps the channel's updated_at to now.
|
||||
TouchUpdatedAt(ctx context.Context, channelID string) error
|
||||
|
||||
// ListUserParticipantIDs returns user participant IDs, excluding excludeUserID.
|
||||
ListUserParticipantIDs(ctx context.Context, channelID, excludeUserID string) ([]string, error)
|
||||
|
||||
// ListPersonaParticipantIDs returns persona participant IDs ordered by created_at.
|
||||
ListPersonaParticipantIDs(ctx context.Context, channelID string) ([]string, error)
|
||||
|
||||
// GetLeaderPersonaID returns the persona group leader for a channel.
|
||||
// Falls back to the first persona participant if no leader flag is set.
|
||||
// Returns empty string if no persona participants exist.
|
||||
GetLeaderPersonaID(ctx context.Context, channelID string) (string, error)
|
||||
|
||||
// GetWorkflowInfo returns (workflow_id, current_stage) for workflow-type channels.
|
||||
// Returns nil workflow_id if the channel is not a workflow channel.
|
||||
GetWorkflowInfo(ctx context.Context, channelID string) (*string, int, error)
|
||||
|
||||
// ── DM + lifecycle helpers (v0.29.0-cs1) ──
|
||||
|
||||
// FindExistingDM returns the channel ID of an existing DM between two users.
|
||||
// Returns "" if no DM exists.
|
||||
FindExistingDM(ctx context.Context, userID1, userID2 string) (string, error)
|
||||
|
||||
// GetUnreadCount returns the number of unread messages for a user in a channel.
|
||||
GetUnreadCount(ctx context.Context, channelID, userID string) (int, error)
|
||||
|
||||
// DeleteByOwner deletes a channel if the user is the owner.
|
||||
// Returns rows affected (0 = not found or not owner).
|
||||
DeleteByOwner(ctx context.Context, channelID, userID string) (int64, error)
|
||||
|
||||
// MarkRead updates last_read_at and last_read_message_id for a user.
|
||||
MarkRead(ctx context.Context, channelID, userID string) error
|
||||
|
||||
// CountParticipantsByType returns count of participants of a given type.
|
||||
CountParticipantsByType(ctx context.Context, channelID, pType string) (int, error)
|
||||
|
||||
// CountAll returns the total number of channels.
|
||||
CountAll(ctx context.Context) (int, error)
|
||||
|
||||
// ── Workflow instance state (v0.29.0-cs3) ──
|
||||
|
||||
// SetWorkflowInstance initializes workflow columns on a channel.
|
||||
SetWorkflowInstance(ctx context.Context, channelID, workflowID string, version int, stageData json.RawMessage, status string) error
|
||||
|
||||
// GetWorkflowStatus returns the full workflow state for a workflow channel.
|
||||
GetWorkflowStatus(ctx context.Context, channelID string) (*WorkflowChannelStatus, error)
|
||||
|
||||
// AdvanceWorkflowStage updates current_stage + stage_data + last_activity_at.
|
||||
AdvanceWorkflowStage(ctx context.Context, channelID string, nextStage int, stageData json.RawMessage) error
|
||||
|
||||
// CompleteWorkflow sets workflow_status='completed' and ai_mode='off'.
|
||||
CompleteWorkflow(ctx context.Context, channelID string, finalStage int, stageData json.RawMessage) error
|
||||
|
||||
// RejectWorkflowToStage resets current_stage (no stage_data change).
|
||||
RejectWorkflowToStage(ctx context.Context, channelID string, stage int) error
|
||||
|
||||
// GetStageData returns the current stage_data JSON from a channel.
|
||||
GetStageData(ctx context.Context, channelID string) (json.RawMessage, error)
|
||||
|
||||
// ── Background job helpers (v0.29.0-cs4) ──
|
||||
|
||||
// MarkStaleWorkflows sets workflow_status='stale' on active workflow channels
|
||||
// with last_activity_at before cutoff. Returns rows affected.
|
||||
MarkStaleWorkflows(ctx context.Context, cutoff time.Time) (int64, error)
|
||||
|
||||
// EnforceWorkflowRetention deletes completed workflow channels whose parent
|
||||
// workflow has retention.mode="delete" and the retention period has elapsed.
|
||||
// PG-only (uses JSON operators). Returns rows affected. No-op on SQLite.
|
||||
EnforceWorkflowRetention(ctx context.Context) (int64, error)
|
||||
|
||||
// GetTypeAndAllowAnonymous returns (type, allow_anonymous) for session auth.
|
||||
GetTypeAndAllowAnonymous(ctx context.Context, channelID string) (string, bool, error)
|
||||
|
||||
// ── CS5b additions (v0.29.0) ──
|
||||
|
||||
// FindCompactionCandidates returns direct channels eligible for auto-compaction.
|
||||
// Filters by activity gap, max age, minimum message count, and minimum total chars.
|
||||
// PG-only (uses settings::text cast). Returns empty slice on SQLite.
|
||||
FindCompactionCandidates(ctx context.Context, activityBefore, createdAfter time.Time, minMessages, minChars, limit int) ([]models.Channel, error)
|
||||
|
||||
// ── CS7a additions (v0.29.0) ──
|
||||
|
||||
// GetProviderConfigID returns the channel's provider_config_id (may be nil).
|
||||
GetProviderConfigID(ctx context.Context, channelID string) (*string, error)
|
||||
|
||||
// UserCanAccess checks if a user is the channel owner or a participant.
|
||||
UserCanAccess(ctx context.Context, channelID, userID string) (bool, error)
|
||||
|
||||
// ── CS7b additions (v0.29.0) ──
|
||||
|
||||
// ListFiltered returns paginated channels with message counts, applying
|
||||
// complex filters (types, folder, search, project). Includes channels
|
||||
// owned by the user or where the user is a participant.
|
||||
ListFiltered(ctx context.Context, userID string, filter ChannelListFilter) ([]ChannelListItem, int, error)
|
||||
|
||||
// GetForUser loads a single channel with message count, verifying the
|
||||
// user is the owner or a participant. Returns sql.ErrNoRows if not found.
|
||||
GetForUser(ctx context.Context, channelID, userID string) (*ChannelListItem, error)
|
||||
|
||||
// CreateFull atomically creates a channel, adds the owner as a participant,
|
||||
// optionally adds DM partner participants, and creates a default channel_model.
|
||||
// The channel model (ch) is populated with ID, CreatedAt, UpdatedAt on return.
|
||||
// folder, tags, and aiMode are passed separately as they're not in models.Channel.
|
||||
CreateFull(ctx context.Context, ch *models.Channel, folder string, tags []string, aiMode string,
|
||||
ownerUserID string, dmPartnerIDs []string, defaultModel, defaultConfigID string) error
|
||||
|
||||
// MergeSettings merges a JSON object into the channel's settings column.
|
||||
MergeSettings(ctx context.Context, channelID string, settingsJSON json.RawMessage) error
|
||||
}
|
||||
|
||||
// ChannelListFilter holds filter options for ChannelStore.ListFiltered.
|
||||
type ChannelListFilter struct {
|
||||
ListOptions
|
||||
Archived bool
|
||||
Types []string // empty = all
|
||||
Folder string
|
||||
FolderID string
|
||||
Search string // title ILIKE/LIKE
|
||||
ProjectID string // "none" = NULL, uuid = specific, "" = no filter
|
||||
}
|
||||
|
||||
// ChannelListItem is returned by ListFiltered and GetForUser.
|
||||
type ChannelListItem struct {
|
||||
ID string
|
||||
UserID string
|
||||
Title string
|
||||
Type string
|
||||
AiMode string
|
||||
Topic *string
|
||||
Description *string
|
||||
Model *string
|
||||
ProviderConfigID *string
|
||||
SystemPrompt *string
|
||||
IsArchived bool
|
||||
IsPinned bool
|
||||
Folder *string
|
||||
FolderID *string
|
||||
ProjectID *string
|
||||
WorkspaceID *string
|
||||
Tags []string
|
||||
Settings json.RawMessage
|
||||
MessageCount int
|
||||
UnreadCount int
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
CreatedAtTime time.Time `json:"-"` // SQLite scan target
|
||||
UpdatedAtTime time.Time `json:"-"` // SQLite scan target
|
||||
}
|
||||
|
||||
// ArchivedChannel is a view model for the admin archived channels list.
|
||||
@@ -291,6 +625,89 @@ type MessageStore interface {
|
||||
|
||||
// Count
|
||||
CountForChannel(ctx context.Context, channelID string) (int, error)
|
||||
|
||||
// ── Tree operations (v0.29.0 — moved from treepath package) ──
|
||||
|
||||
// GetActiveLeaf returns the cursor's active_leaf_id for a user in a channel.
|
||||
// Falls back to the chronologically latest live message if no cursor exists.
|
||||
GetActiveLeaf(ctx context.Context, channelID, userID string) (*string, error)
|
||||
|
||||
// GetPathToLeaf walks from a leaf up to root via parent_id, returning
|
||||
// root-first order with sibling counts and sender info resolved.
|
||||
GetPathToLeaf(ctx context.Context, channelID, leafID string) ([]PathMessage, error)
|
||||
|
||||
// GetActivePath is GetActiveLeaf + GetPathToLeaf combined.
|
||||
GetActivePath(ctx context.Context, channelID, userID string) ([]PathMessage, error)
|
||||
|
||||
// GetSiblingsList returns all live siblings of a message with the current index.
|
||||
GetSiblingsList(ctx context.Context, messageID string) ([]SiblingInfo, int, error)
|
||||
|
||||
// GetSiblingCount returns how many live siblings share the same parent.
|
||||
GetSiblingCount(ctx context.Context, channelID string, parentID *string) (int, error)
|
||||
|
||||
// FindLeafFromMessage walks down from a message to find the deepest descendant.
|
||||
FindLeafFromMessage(ctx context.Context, messageID string) (string, error)
|
||||
|
||||
// NextSiblingIndexForParent returns the next sibling_index for children of parentID.
|
||||
NextSiblingIndexForParent(ctx context.Context, channelID string, parentID *string) (int, error)
|
||||
|
||||
// HasPersonaMessages checks if any assistant message in the channel was
|
||||
// generated by a persona.
|
||||
HasPersonaMessages(ctx context.Context, channelID string) (bool, error)
|
||||
|
||||
// CreateWithCursor inserts a message, updates the cursor, and touches
|
||||
// channel.updated_at. Handles dialect-specific ID generation internally.
|
||||
CreateWithCursor(ctx context.Context, m *models.Message, cursorUserID string) error
|
||||
|
||||
// ResolveSenderInfo batch-resolves sender_name and sender_avatar for
|
||||
// user and persona participants. Updates the PathMessage slice in-place.
|
||||
ResolveSenderInfo(ctx context.Context, path []PathMessage) error
|
||||
|
||||
// CountAll returns the total number of messages (for admin stats).
|
||||
CountAll(ctx context.Context) (int, error)
|
||||
|
||||
// ── CS5c additions (v0.29.0) ──
|
||||
|
||||
// SearchInChannel performs full-text search within a single channel's messages.
|
||||
// PG uses to_tsvector/ts_headline; SQLite falls back to LIKE.
|
||||
// roleFilter is "user", "assistant", or "" for all.
|
||||
SearchInChannel(ctx context.Context, channelID, query, roleFilter string, limit int) ([]ChannelSearchResult, error)
|
||||
|
||||
// ── CS7a additions (v0.29.0) ──
|
||||
|
||||
// ListWithSenderInfo returns paginated messages with sender name/avatar
|
||||
// resolved via JOINs to users and personas tables.
|
||||
ListWithSenderInfo(ctx context.Context, channelID string, limit, offset int) ([]MessageWithSender, int, error)
|
||||
|
||||
// GetParentAndRole loads just the parent_id and role of a message,
|
||||
// verifying it belongs to the given channel and is not deleted.
|
||||
GetParentAndRole(ctx context.Context, messageID, channelID string) (parentID *string, role string, err error)
|
||||
}
|
||||
|
||||
// MessageWithSender is returned by MessageStore.ListWithSenderInfo.
|
||||
type MessageWithSender struct {
|
||||
ID string
|
||||
ChannelID string
|
||||
Role string
|
||||
Content string
|
||||
Model *string
|
||||
TokensUsed *int
|
||||
ParentID *string
|
||||
SiblingIndex int
|
||||
ParticipantType *string
|
||||
ParticipantID *string
|
||||
SenderName *string
|
||||
SenderAvatar *string
|
||||
CreatedAt string
|
||||
}
|
||||
|
||||
// ChannelSearchResult is returned by MessageStore.SearchInChannel.
|
||||
type ChannelSearchResult struct {
|
||||
MessageID string
|
||||
Role string
|
||||
Excerpt string // ts_headline or content substring
|
||||
Rank float64
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// =========================================
|
||||
@@ -311,6 +728,7 @@ type AuditListOptions struct {
|
||||
ResourceID string
|
||||
Since *time.Time
|
||||
Until *time.Time
|
||||
TeamID string // CS6: scope to team members
|
||||
}
|
||||
|
||||
// =========================================
|
||||
@@ -326,6 +744,22 @@ type NoteStore interface {
|
||||
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)
|
||||
|
||||
// ── CS5c additions (v0.29.0) ──
|
||||
|
||||
// SetEmbedding stores a pgvector embedding on a note. PG-only; SQLite no-ops.
|
||||
SetEmbedding(ctx context.Context, noteID, vecStr string) error
|
||||
|
||||
// SearchKeyword performs full-text keyword search with ranking and headlines.
|
||||
// PG uses ts_rank/ts_headline; SQLite falls back to LIKE.
|
||||
SearchKeyword(ctx context.Context, userID, query string, limit int) ([]NoteSearchResult, error)
|
||||
|
||||
// SearchSemantic performs vector similarity search against note embeddings.
|
||||
// PG-only; SQLite returns empty results.
|
||||
SearchSemantic(ctx context.Context, userID, vecStr string, limit int) ([]NoteSearchResult, error)
|
||||
|
||||
// ListFolders returns distinct folder paths and note counts for a user.
|
||||
ListFolders(ctx context.Context, userID string) ([]FolderInfo, error)
|
||||
}
|
||||
|
||||
type NoteListOptions struct {
|
||||
@@ -335,6 +769,23 @@ type NoteListOptions struct {
|
||||
TeamID string
|
||||
}
|
||||
|
||||
// NoteSearchResult is returned by NoteStore.SearchKeyword and SearchSemantic.
|
||||
type NoteSearchResult struct {
|
||||
ID string
|
||||
Title string
|
||||
FolderPath string
|
||||
Tags []string
|
||||
Excerpt string // truncated content preview
|
||||
Headline string // ts_headline markup (empty on SQLite)
|
||||
Rank float64 // ts_rank or similarity score
|
||||
}
|
||||
|
||||
// FolderInfo is returned by NoteStore.ListFolders.
|
||||
type FolderInfo struct {
|
||||
Path string
|
||||
Count int
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// NOTE LINK STORE
|
||||
// =========================================
|
||||
@@ -362,6 +813,24 @@ 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)
|
||||
|
||||
// ── OIDC state (v0.29.0-cs4) ──
|
||||
|
||||
// SaveOIDCState stores a state+nonce pair for OIDC callback verification.
|
||||
SaveOIDCState(ctx context.Context, state, nonce, redirectTo string) error
|
||||
|
||||
// ConsumeOIDCState retrieves and deletes an OIDC state (one-time use).
|
||||
// Returns (nonce, redirectTo, error). Returns sql.ErrNoRows if not found.
|
||||
ConsumeOIDCState(ctx context.Context, state string) (nonce, redirectTo string, err error)
|
||||
|
||||
// CleanupOIDCState removes stale OIDC states older than 10 minutes.
|
||||
CleanupOIDCState(ctx context.Context) error
|
||||
|
||||
// ── CS6 additions (v0.29.0) ──
|
||||
|
||||
// GetString returns the raw value column as a string for simple settings
|
||||
// (e.g. bare JSON booleans like "true"/"false").
|
||||
GetString(ctx context.Context, key string) (string, error)
|
||||
}
|
||||
|
||||
// =========================================
|
||||
@@ -419,6 +888,11 @@ type FileStore interface {
|
||||
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.File, error)
|
||||
|
||||
// ── CS5c additions (v0.29.0) ──
|
||||
|
||||
// UpdateStorageKey sets the storage key after initial file creation.
|
||||
UpdateStorageKey(ctx context.Context, id, key string) error
|
||||
}
|
||||
|
||||
|
||||
@@ -652,6 +1126,70 @@ type SessionStore interface {
|
||||
DeleteExpired(ctx context.Context, olderThan time.Time) (int64, error)
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// PERSONA GROUP STORE (v0.29.0)
|
||||
// =========================================
|
||||
|
||||
// PersonaGroupStore manages persona_groups and their members.
|
||||
type PersonaGroupStore interface {
|
||||
// CRUD
|
||||
List(ctx context.Context, ownerID string) ([]models.PersonaGroup, error)
|
||||
Get(ctx context.Context, id, ownerID string) (*models.PersonaGroup, error)
|
||||
Create(ctx context.Context, g *models.PersonaGroup) error
|
||||
Update(ctx context.Context, id string, fields map[string]interface{}) error
|
||||
Delete(ctx context.Context, id, ownerID string) (int64, error)
|
||||
|
||||
// Ownership check
|
||||
GetOwnerID(ctx context.Context, id string) (string, error)
|
||||
|
||||
// Members
|
||||
AddMember(ctx context.Context, groupID, personaID string, isLeader bool) error
|
||||
RemoveMember(ctx context.Context, memberID, groupID string) error
|
||||
ListMembers(ctx context.Context, groupID string) ([]models.PersonaGroupMember, error)
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// FOLDER STORE (v0.29.0)
|
||||
// =========================================
|
||||
|
||||
// FolderStore manages user chat folders.
|
||||
type FolderStore interface {
|
||||
List(ctx context.Context, userID string) ([]models.Folder, error)
|
||||
Create(ctx context.Context, f *models.Folder) error
|
||||
Update(ctx context.Context, folderID, userID string, name string, sortOrder *int) (int64, error)
|
||||
Delete(ctx context.Context, folderID, userID string) (int64, error)
|
||||
// UnassignChannels removes folder_id from all channels in this folder.
|
||||
UnassignChannels(ctx context.Context, folderID, userID string) error
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// HEALTH STORE (v0.29.0)
|
||||
// =========================================
|
||||
|
||||
// HealthStore manages provider health windows. The full interface is used
|
||||
// by the health accumulator and status querier; the scheduler only needs Prune.
|
||||
type HealthStore interface {
|
||||
// Prune deletes health windows older than the given time. Returns rows affected.
|
||||
Prune(ctx context.Context, before time.Time) (int64, error)
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// PRESENCE STORE (v0.29.0)
|
||||
// =========================================
|
||||
|
||||
// PresenceStore manages user online/offline status.
|
||||
type PresenceStore interface {
|
||||
// Heartbeat upserts the user's last_seen timestamp to now.
|
||||
Heartbeat(ctx context.Context, userID string) error
|
||||
|
||||
// GetLastSeen returns the user's last heartbeat time, or nil if never seen.
|
||||
GetLastSeen(ctx context.Context, userID string) (*time.Time, error)
|
||||
|
||||
// GetStatuses returns online/offline status for a list of user IDs.
|
||||
// Users with last_seen after threshold are "online", otherwise "offline".
|
||||
GetStatuses(ctx context.Context, userIDs []string, threshold time.Time) (map[string]string, error)
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// SHARED TYPES
|
||||
// =========================================
|
||||
|
||||
Reference in New Issue
Block a user