All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
304 lines
10 KiB
Go
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
|
|
// =========================================
|
|
|
|
// 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
|
|
|
|
// 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
|
|
|
|
// CapabilityOverride is an admin correction for a model's capabilities.
|
|
// ROUTING POLICIES
|
|
|
|
// RoutingPolicy is one routing rule that controls how requests are
|
|
// dispatched to provider configs. Stored in the routing_policies table.
|