Files
backend/internal/store/models.go

111 lines
5.8 KiB
Go
Raw Normal View History

// 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"`
}