111 lines
5.8 KiB
Go
111 lines
5.8 KiB
Go
// Package store defines the persistence layer for CarrotAssistant.
|
|
//
|
|
// All persistent structured state lives here: admin accounts, applications,
|
|
// model configurations, skill metadata, chat sessions and messages, and
|
|
// audit logs. Skill *content* itself (the markdown file) lives on disk and
|
|
// is managed by the skill package; this package only stores the metadata
|
|
// needed to locate and govern that file (status, version, ownership).
|
|
package store
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gorm.io/datatypes"
|
|
)
|
|
|
|
// AdminUser is an operator who can log in to the admin console.
|
|
type AdminUser struct {
|
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
Username string `gorm:"uniqueIndex;size:64;not null" json:"username"`
|
|
PasswordHash string `gorm:"size:255;not null" json:"-"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// App is a target application (a forum, a resource site, ...). Each App owns
|
|
// a set of Skills and exposes a Chat API guarded by its access token.
|
|
type App struct {
|
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
Slug string `gorm:"uniqueIndex;size:64;not null" json:"slug"`
|
|
Name string `gorm:"size:128;not null" json:"name"`
|
|
Description string `gorm:"size:512" json:"description"`
|
|
TokenHash string `gorm:"size:255;not null" json:"-"` // SHA-256 hash of the access token; never store plaintext
|
|
ModelConfigID *int64 `gorm:"index" json:"model_config_id,omitempty"`
|
|
Status string `gorm:"size:32;not null;default:'active'" json:"status"` // active | disabled
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// ModelConfig describes an OpenAI-compatible LLM endpoint. Any provider
|
|
// speaking the OpenAI Chat Completions + tool-calling protocol (OpenAI,
|
|
// DeepSeek, Moonshot, vLLM, Ollama, ...) can be wired in here.
|
|
type ModelConfig struct {
|
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
Name string `gorm:"uniqueIndex;size:64;not null" json:"name"`
|
|
BaseURL string `gorm:"size:255;not null" json:"base_url"`
|
|
APIKeyEncrypted string `gorm:"size:512;not null" json:"-"` // AES-GCM ciphertext, base64
|
|
APIKeyPreview string `gorm:"size:16" json:"api_key_preview"` // last 4 chars, display only
|
|
DefaultModel string `gorm:"size:64;not null" json:"default_model"`
|
|
SupportsTools bool `gorm:"not null;default:true" json:"supports_tools"`
|
|
MaxTokens int `gorm:"default:0" json:"max_tokens"` // 0 = leave unset
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// Skill is the metadata row for a skill markdown file. The markdown content
|
|
// (system prompt + tool definitions) lives at FilePath; this row only governs
|
|
// lifecycle (draft/published/archived), versioning, and ownership.
|
|
type Skill struct {
|
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
AppID int64 `gorm:"index;not null" json:"app_id"`
|
|
Slug string `gorm:"size:64;not null" json:"slug"` // unique within app
|
|
Name string `gorm:"size:128;not null" json:"name"`
|
|
Description string `gorm:"size:512" json:"description"`
|
|
FilePath string `gorm:"size:512;not null" json:"file_path"` // path under SkillsDir
|
|
Version int `gorm:"not null;default:1" json:"version"`
|
|
Status string `gorm:"size:32;not null;default:'draft'" json:"status"` // draft | published | archived
|
|
Source string `gorm:"size:32" json:"source"` // openapi | markdown | source | manual
|
|
PublishedAt *time.Time `json:"published_at,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// Session is a chat conversation between an end-user and an App's assistant.
|
|
type Session struct {
|
|
ID string `gorm:"primaryKey;type:text" json:"id"` // UUID
|
|
AppID int64 `gorm:"index;not null" json:"app_id"`
|
|
Title string `gorm:"size:255" json:"title"`
|
|
MessageCount int `gorm:"not null;default:0" json:"message_count"`
|
|
// PendingConfirmation, when non-empty, holds a JSON blob describing a
|
|
// mutating tool call awaiting the user's approval. The chat stream pauses
|
|
// until POST /chat/confirm resolves it. Empty = no pending call.
|
|
PendingConfirmation datatypes.JSON `json:"pending_confirmation,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// Message is a single message within a Session. Role follows the OpenAI
|
|
// convention: system | user | assistant | tool. ToolCalls stores the
|
|
// assistant's tool call payload (JSON) when present.
|
|
type Message struct {
|
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
SessionID string `gorm:"index;not null" json:"session_id"`
|
|
Role string `gorm:"size:32;not null" json:"role"` // system | user | assistant | tool
|
|
Content string `gorm:"type:text" json:"content"`
|
|
ToolCalls datatypes.JSON `json:"tool_calls,omitempty"`
|
|
ToolCallID string `gorm:"size:64" json:"tool_call_id,omitempty"` // for role=tool, the call id it answers
|
|
Tokens int `gorm:"default:0" json:"tokens"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// AuditLog records security-relevant actions for traceability.
|
|
type AuditLog struct {
|
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
|
AppID *int64 `gorm:"index" json:"app_id,omitempty"`
|
|
SessionID *string `gorm:"index" json:"session_id,omitempty"`
|
|
Actor string `gorm:"size:128" json:"actor"` // admin username, "system", app slug, ...
|
|
Action string `gorm:"size:64;not null;index" json:"action"`
|
|
Detail datatypes.JSON `json:"detail"`
|
|
CreatedAt time.Time `gorm:"index" json:"created_at"`
|
|
}
|