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"` } // TeamUserRole is a many-to-many join: one user can hold multiple roles in a team. type TeamUserRole 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"` AssignedBy string `json:"assigned_by,omitempty" db:"assigned_by"` AssignedAt string `json:"assigned_at" db:"assigned_at"` } // 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.