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/models/models.go
Jeffrey Smith df310d9aa5
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 21s
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Successful in 2m9s
CI/CD / test-sqlite (pull_request) Successful in 2m50s
CI/CD / build-and-deploy (pull_request) Successful in 1m50s
Bump VERSION to 0.2.8, finalize changelog
Team admin settings audit (pass 1): remove dead BYOK/provider/persona
vestiges from team store, models, policy defaults, workflow stage UI,
and ICD tests. Fix stale chat_only stage mode in frontend to match
backend CHECK constraint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:13:10 +00:00

304 lines
10 KiB
Go

package models
import (
"database/sql"
"encoding/json"
"strings"
"time"
)
// ── Base ────────────────────────────────────
type BaseModel struct {
ID string `json:"id" db:"id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// ── Scope Constants ─────────────────────────
const (
ScopeGlobal = "global"
ScopeTeam = "team"
ScopePersonal = "personal"
)
// ── Team Role Constants ─────────────────────
const (
TeamRoleAdmin = "admin"
TeamRoleMember = "member"
)
// ── Extension Tier Constants ────────────────
const (
ExtTierBrowser = "browser"
ExtTierStarlark = "starlark"
ExtTierSidecar = "sidecar"
)
type User struct {
BaseModel
Username string `json:"username" db:"username"`
Email string `json:"email" db:"email"`
PasswordHash string `json:"-" db:"password_hash"`
DisplayName string `json:"display_name,omitempty" db:"display_name"`
AvatarURL string `json:"avatar_url,omitempty" db:"avatar_url"`
IsActive bool `json:"is_active" db:"is_active"`
Settings JSONMap `json:"settings,omitempty" db:"settings"`
LastLoginAt *time.Time `json:"last_login_at,omitempty" db:"last_login_at"`
AuthSource string `json:"auth_source" db:"auth_source"`
ExternalID *string `json:"external_id,omitempty" db:"external_id"`
Handle string `json:"handle" db:"handle"`
}
// =========================================
// TEAMS
// =========================================
type Team struct {
BaseModel
Name string `json:"name" db:"name"`
Description string `json:"description,omitempty" db:"description"`
CreatedBy string `json:"created_by" db:"created_by"`
IsActive bool `json:"is_active" db:"is_active"`
Settings JSONMap `json:"settings,omitempty" db:"settings"`
MemberCount int `json:"member_count,omitempty"` // computed
MyRole string `json:"my_role,omitempty"` // computed, user context
}
type TeamMember struct {
ID string `json:"id" db:"id"`
TeamID string `json:"team_id" db:"team_id"`
UserID string `json:"user_id" db:"user_id"`
Role string `json:"role" db:"role"`
JoinedAt string `json:"joined_at" db:"joined_at"`
// Joined fields from users table
Email string `json:"email,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Username string `json:"username,omitempty"`
}
// PLATFORM POLICIES
var PolicyDefaults = map[string]string{
"allow_registration": "true",
"default_user_active": "false",
}
// HandleFromName generates a URL-safe @mention handle from a display name.
// "Veronica Sharpe" → "veronica-sharpe"
func HandleFromName(name string) string {
h := strings.ToLower(strings.TrimSpace(name))
h = strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
return r
}
if r == ' ' || r == '_' {
return '-'
}
return -1
}, h)
// Collapse multiple hyphens
for strings.Contains(h, "--") {
h = strings.ReplaceAll(h, "--", "-")
}
h = strings.Trim(h, "-")
if len(h) > 50 {
h = h[:50]
}
return h
}
// =========================================
// AUDIT LOG
// =========================================
type AuditEntry struct {
ID string `json:"id" db:"id"`
ActorID *string `json:"actor_id,omitempty" db:"actor_id"`
ActorName *string `json:"actor_name,omitempty" db:"-"` // resolved via JOIN, not a column
Action string `json:"action" db:"action"`
ResourceType string `json:"resource_type" db:"resource_type"`
ResourceID string `json:"resource_id,omitempty" db:"resource_id"`
Metadata JSONMap `json:"metadata,omitempty" db:"metadata"`
IPAddress string `json:"ip_address,omitempty" db:"ip_address"`
UserAgent string `json:"user_agent,omitempty" db:"user_agent"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// =========================================
// JSON HELPERS
// =========================================
// JSONMap scans from/to JSONB columns.
type JSONMap map[string]interface{}
func (m *JSONMap) Scan(src interface{}) error {
if src == nil {
*m = nil
return nil
}
var source []byte
switch v := src.(type) {
case []byte:
source = v
case string:
source = []byte(v)
default:
return nil
}
result := make(JSONMap)
if err := json.Unmarshal(source, &result); err != nil {
return err
}
*m = result
return nil
}
// Extension tier constants
type Extension struct {
ID string `json:"id" db:"id"`
ExtID string `json:"ext_id" db:"ext_id"` // manifest id
Name string `json:"name" db:"name"`
Version string `json:"version" db:"version"`
Tier string `json:"tier" db:"tier"`
Description string `json:"description" db:"description"`
Author string `json:"author" db:"author"`
Manifest json.RawMessage `json:"manifest" db:"manifest"`
IsSystem bool `json:"is_system" db:"is_system"`
IsEnabled bool `json:"is_enabled" db:"is_enabled"`
Scope string `json:"scope" db:"scope"`
TeamID *string `json:"team_id,omitempty" db:"team_id"`
InstalledBy *string `json:"installed_by,omitempty" db:"installed_by"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// ExtensionUserSettings stores per-user overrides for an extension.
type ExtensionUserSettings struct {
ExtensionID string `json:"extension_id" db:"extension_id"`
UserID string `json:"user_id" db:"user_id"`
Settings json.RawMessage `json:"settings" db:"settings"`
IsEnabled bool `json:"is_enabled" db:"is_enabled"`
}
// UserExtension combines extension info with user-specific settings for API responses.
type UserExtension struct {
Extension
UserEnabled *bool `json:"user_enabled,omitempty"`
UserSettings *json.RawMessage `json:"user_settings,omitempty"`
}
func NullString(s *string) sql.NullString {
if s == nil {
return sql.NullString{}
}
return sql.NullString{String: *s, Valid: true}
}
func NullFloat(f *float64) sql.NullFloat64 {
if f == nil {
return sql.NullFloat64{}
}
return sql.NullFloat64{Float64: *f, Valid: true}
}
func NullInt(i *int) sql.NullInt64 {
if i == nil {
return sql.NullInt64{}
}
return sql.NullInt64{Int64: int64(*i), Valid: true}
}
func StringPtr(s string) *string { return &s }
func Float64Ptr(f float64) *float64 { return &f }
func IntPtr(i int) *int { return &i }
func BoolPtr(b bool) *bool { return &b }
// =========================================
// GROUPS (v0.16.0)
// =========================================
// Group scope constants (reuses ScopeGlobal, ScopeTeam from above)
// ResourceType constants for resource_grants
const (
ResourceTypePersona = "persona"
ResourceTypeKnowledgeBase = "knowledge_base"
)
// GrantScope constants for resource_grants
const (
GrantScopeTeamOnly = "team_only"
GrantScopeGlobal = "global"
GrantScopeGroups = "groups"
)
// Group is an access-control list. Decouples resource visibility from teams.
type Group struct {
BaseModel
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Scope string `json:"scope" db:"scope"` // global, team
TeamID *string `json:"team_id,omitempty" db:"team_id"`
CreatedBy *string `json:"created_by,omitempty" db:"created_by"`
Source string `json:"source" db:"source"` // manual (default), oidc, system
Permissions []string `json:"permissions" db:"permissions"`
MemberCount int `json:"member_count,omitempty"` // computed, not a DB column
}
// GroupPatch holds optional fields for updating a group.
type GroupPatch struct {
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Permissions *[]string `json:"permissions,omitempty"`
}
// GroupMember links a user to a group.
type GroupMember struct {
ID string `json:"id" db:"id"`
GroupID string `json:"group_id" db:"group_id"`
UserID string `json:"user_id" db:"user_id"`
AddedBy string `json:"added_by" db:"added_by"`
AddedAt time.Time `json:"added_at" db:"added_at"`
// Joined fields from users table (for list responses)
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
DisplayName string `json:"display_name,omitempty"`
}
// ResourceGrant controls who can USE a resource (personas, KBs) via groups.
// One row per resource. Not to be confused with persona_grants (what a Persona can DO).
type ResourceGrant struct {
BaseModel
ResourceType string `json:"resource_type" db:"resource_type"`
ResourceID string `json:"resource_id" db:"resource_id"`
GrantScope string `json:"grant_scope" db:"grant_scope"`
GrantedGroups []string `json:"granted_groups" db:"granted_groups"` // UUID array
CreatedBy string `json:"created_by" db:"created_by"`
}
// KnowledgeBase is a named collection of documents with vector embeddings.
// PersonaKB binds a knowledge base to a persona.
// KBDocument is a single uploaded file within a knowledge base.
// KBChunk is a text segment from a document with its embedding vector.
// KBSearchResult is a single result from similarity search.
// ChannelKB represents a knowledge base linked to a channel.
// PROVIDER HEALTH (v0.22.0)
// ProviderStatus represents the derived health state.
// AvgLatencyMs returns the average latency, or 0 if no requests.
// ErrorRate returns the error fraction, or 0 if no requests.
// CAPABILITY OVERRIDES (v0.22.0)
// CapabilityOverride is an admin correction for a model's capabilities.
// ROUTING POLICIES (v0.22.2)
// RoutingPolicy is one routing rule that controls how requests are
// dispatched to provider configs. Stored in the routing_policies table.