Merge pull request #1144 from trheyi/main

Refactor user and model structures to enhance organization and role m…
This commit is contained in:
Max 2025-09-16 09:36:30 +08:00 committed by GitHub
commit c145a14606
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 602 additions and 468 deletions

File diff suppressed because one or more lines are too long

View file

@ -20,25 +20,25 @@ import (
// SystemModels system models
var systemModels = map[string]string{
"__yao.assistant": "yao/models/assistant.mod.yao",
"__yao.agent.assistant": "yao/models/agent/assistant.mod.yao",
"__yao.agent.chat": "yao/models/agent/chat.mod.yao",
"__yao.agent.history": "yao/models/agent/history.mod.yao",
"__yao.attachment": "yao/models/attachment.mod.yao",
"__yao.audit": "yao/models/audit.mod.yao",
"__yao.chat": "yao/models/chat.mod.yao",
"__yao.config": "yao/models/config.mod.yao",
"__yao.dsl": "yao/models/dsl.mod.yao",
"__yao.history": "yao/models/history.mod.yao",
"__yao.job.category": "yao/models/job/category.mod.yao",
"__yao.job": "yao/models/job/job.mod.yao",
"__yao.job.execution": "yao/models/job/execution.mod.yao",
"__yao.job.log": "yao/models/job/log.mod.yao",
"__yao.kb.collection": "yao/models/kb/collection.mod.yao",
"__yao.kb.document": "yao/models/kb/document.mod.yao",
"__yao.organization": "yao/models/organization.mod.yao",
"__yao.organization_user": "yao/models/organization_user.mod.yao",
"__yao.team": "yao/models/team.mod.yao",
"__yao.member": "yao/models/member.mod.yao",
"__yao.user": "yao/models/user.mod.yao",
"__yao.user_role": "yao/models/user_role.mod.yao",
"__yao.user_type": "yao/models/user_type.mod.yao",
"__yao.user_oauth_account": "yao/models/user_oauth_account.mod.yao",
"__yao.role": "yao/models/role.mod.yao",
"__yao.user.type": "yao/models/user/type.mod.yao",
"__yao.user.oauth_account": "yao/models/user/oauth_account.mod.yao",
}
// Load load models

View file

@ -217,17 +217,17 @@ func NewDefaultUser(options *DefaultUserOptions) *DefaultUser {
roleModel := options.RoleModel
if roleModel == "" {
roleModel = "__yao.user_role"
roleModel = "__yao.role"
}
typeModel := options.TypeModel
if typeModel == "" {
typeModel = "__yao.user_type"
typeModel = "__yao.user.type"
}
oauthAccountModel := options.OAuthAccountModel
if oauthAccountModel == "" {
oauthAccountModel = "__yao.user_oauth_account"
oauthAccountModel = "__yao.user.oauth_account"
}
// Set ID generation strategy with defaults

View file

@ -98,7 +98,7 @@ func cleanupTestData() {
// Use DestroyWhere (hard delete) to avoid soft delete complications
// Clean OAuth accounts first (due to foreign key constraints)
oauthModel := model.Select("__yao.user_oauth_account")
oauthModel := model.Select("__yao.user.oauth_account")
oauthPatterns := []string{
"%oauth_test%", "%_list_%", "%oauthlist%", "%oautherror%",
"%google_%", "%github_%", "%apple_%", "%_delete_%",
@ -113,7 +113,7 @@ func cleanupTestData() {
}
// Clean roles (should be done before users due to potential role_id references)
roleModel := model.Select("__yao.user_role")
roleModel := model.Select("__yao.role")
rolePatterns := []string{
"test%", "%testrole%", "%listrole%", "%permrole%", "%adminrole%", "%userrole%",
"%inactiverole%", "%systemrole%", "%validrole%", "%emptyupdate%", "%emptyperm%",
@ -128,7 +128,7 @@ func cleanupTestData() {
}
// Clean types (should be done before users due to potential type_id references)
typeModel := model.Select("__yao.user_type")
typeModel := model.Select("__yao.user.type")
typePatterns := []string{
"test%", "%testtype%", "%listtype%", "%configtype%", "%basictype%", "%premiumtype%",
"%inactivetype%", "%validtype%", "%emptyupdate%", "%emptyconfig%", "%scopetype%",

View file

@ -36,25 +36,25 @@ var testServer *http.Server = nil
// SystemModels system models for testing
var testSystemModels = map[string]string{
"__yao.assistant": "yao/models/assistant.mod.yao",
"__yao.agent.assistant": "yao/models/agent/assistant.mod.yao",
"__yao.agent.chat": "yao/models/agent/chat.mod.yao",
"__yao.agent.history": "yao/models/agent/history.mod.yao",
"__yao.attachment": "yao/models/attachment.mod.yao",
"__yao.audit": "yao/models/audit.mod.yao",
"__yao.chat": "yao/models/chat.mod.yao",
"__yao.config": "yao/models/config.mod.yao",
"__yao.dsl": "yao/models/dsl.mod.yao",
"__yao.history": "yao/models/history.mod.yao",
"__yao.job.category": "yao/models/job/category.mod.yao",
"__yao.job": "yao/models/job/job.mod.yao",
"__yao.job.execution": "yao/models/job/execution.mod.yao",
"__yao.job.log": "yao/models/job/log.mod.yao",
"__yao.kb.collection": "yao/models/kb/collection.mod.yao",
"__yao.kb.document": "yao/models/kb/document.mod.yao",
"__yao.organization": "yao/models/organization.mod.yao",
"__yao.organization_user": "yao/models/organization_user.mod.yao",
"__yao.team": "yao/models/team.mod.yao",
"__yao.member": "yao/models/member.mod.yao",
"__yao.user": "yao/models/user.mod.yao",
"__yao.user_role": "yao/models/user_role.mod.yao",
"__yao.user_type": "yao/models/user_type.mod.yao",
"__yao.user_oauth_account": "yao/models/user_oauth_account.mod.yao",
"__yao.role": "yao/models/role.mod.yao",
"__yao.user.type": "yao/models/user/type.mod.yao",
"__yao.user.oauth_account": "yao/models/user/oauth_account.mod.yao",
}
var testSystemStores = map[string]string{

View file

@ -1,14 +1,14 @@
{
"name": "assistant",
"name": "Assistant",
"label": "Assistant",
"description": "Assistant table for storing AI assistant configurations and metadata",
"tags": ["system"],
"tags": ["agent", "system"],
"builtin": true,
"readonly": true,
"sort": 9999,
"table": {
"name": "assistant",
"comment": "Assistant table"
"name": "agent_assistant",
"comment": "Agent assistant table"
},
"columns": [
{
@ -181,7 +181,27 @@
"index": true
}
],
"relations": {},
"indexes": [],
"relations": {
"chats": {
"type": "hasMany",
"model": "__yao.agent.chat",
"key": "assistant_id",
"foreign": "assistant_id"
}
},
"indexes": [
{
"name": "idx_agent_assistant_type_builtin",
"columns": ["type", "built_in"],
"type": "index",
"comment": "Index for assistant type and built-in status"
},
{
"name": "idx_agent_assistant_sort",
"columns": ["sort", "automated"],
"type": "index",
"comment": "Index for assistant sorting and automation"
}
],
"option": { "timestamps": true, "soft_deletes": false }
}

View file

@ -1,14 +1,14 @@
{
"name": "chat",
"name": "Chat",
"label": "Chat",
"description": "Chat table for storing chat metadata and information",
"tags": ["system"],
"tags": ["agent", "system"],
"builtin": true,
"readonly": true,
"sort": 9999,
"table": {
"name": "chat",
"comment": "Chat table"
"name": "agent_chat",
"comment": "Agent chat table"
},
"columns": [
{
@ -62,7 +62,33 @@
"index": true
}
],
"relations": {},
"indexes": [],
"relations": {
"assistant": {
"type": "hasOne",
"model": "__yao.agent.assistant",
"key": "assistant_id",
"foreign": "assistant_id"
},
"history": {
"type": "hasMany",
"model": "__yao.agent.history",
"key": "chat_id",
"foreign": "cid"
}
},
"indexes": [
{
"name": "idx_agent_chat_session_assistant",
"columns": ["sid", "assistant_id"],
"type": "index",
"comment": "Index for session and assistant queries"
},
{
"name": "idx_agent_chat_silent",
"columns": ["silent", "created_at"],
"type": "index",
"comment": "Index for silent mode filtering"
}
],
"option": { "timestamps": true, "soft_deletes": false }
}

View file

@ -1,14 +1,14 @@
{
"name": "history",
"name": "History",
"label": "Chat History",
"description": "Chat history table for storing detailed chat message information",
"tags": ["system"],
"tags": ["agent", "system"],
"builtin": true,
"readonly": true,
"sort": 9999,
"table": {
"name": "history",
"comment": "Chat history table"
"name": "agent_history",
"comment": "Agent chat history table"
},
"columns": [
{
@ -124,7 +124,51 @@
"index": true
}
],
"relations": {},
"indexes": [],
"relations": {
"chat": {
"type": "hasOne",
"model": "__yao.agent.chat",
"key": "cid",
"foreign": "chat_id"
},
"assistant": {
"type": "hasOne",
"model": "__yao.agent.assistant",
"key": "assistant_id",
"foreign": "assistant_id"
},
"user": {
"type": "hasOne",
"model": "__yao.user",
"key": "uid",
"foreign": "user_id"
}
},
"indexes": [
{
"name": "idx_agent_history_session_chat",
"columns": ["sid", "cid"],
"type": "index",
"comment": "Index for session and chat queries"
},
{
"name": "idx_agent_history_user_role",
"columns": ["uid", "role"],
"type": "index",
"comment": "Index for user and role queries"
},
{
"name": "idx_agent_history_assistant",
"columns": ["assistant_id", "created_at"],
"type": "index",
"comment": "Index for assistant history queries"
},
{
"name": "idx_agent_history_expired",
"columns": ["expired_at", "silent"],
"type": "index",
"comment": "Index for expiration and cleanup"
}
],
"option": { "timestamps": true, "soft_deletes": false }
}

View file

@ -1,11 +1,11 @@
{
"name": "Organization User",
"label": "Organization User",
"description": "Association table for organization members and their roles",
"tags": ["organization", "user", "association", "membership", "roles"],
"name": "Member",
"label": "Member",
"description": "Association table for team members and their roles",
"tags": ["team", "user", "association", "membership", "roles"],
"table": {
"name": "organization_user",
"comment": "Association table for organization members and their roles"
"name": "member",
"comment": "Association table for team members and their roles"
},
"columns": [
// ============================================================================
@ -19,10 +19,10 @@
"primary": true
},
{
"name": "org_id",
"name": "team_id",
"type": "string",
"label": "Organization ID",
"comment": "Organization identifier (references organization.org_id)",
"label": "Team ID",
"comment": "Team identifier (references team.team_id)",
"length": 255,
"nullable": false,
"index": true
@ -41,18 +41,13 @@
// Role & Permission Fields
// ============================================================================
{
"name": "role",
"type": "enum",
"label": "Role",
"comment": "User role within the organization",
"option": [
"owner", // Organization owner (full control, billing, delete org)
"admin", // Administrator (manage users, settings, but not billing)
"member" // Regular member (basic access according to org permissions)
],
"default": "member",
"index": true,
"nullable": false
"name": "role_id",
"type": "string",
"label": "Role ID",
"comment": "User role within the team (references role.role_id)",
"length": 50,
"nullable": false,
"index": true
},
{
"name": "status",
@ -93,7 +88,7 @@
"name": "joined_at",
"type": "timestamp",
"label": "Joined At",
"comment": "When the user accepted and joined the organization",
"comment": "When the user accepted and joined the team",
"nullable": true,
"index": true
},
@ -122,14 +117,14 @@
"name": "permissions",
"type": "json",
"label": "Permissions",
"comment": "Custom permissions override for this user in this organization",
"comment": "Custom permissions override for this user in this team",
"nullable": true
},
{
"name": "restrictions",
"type": "json",
"label": "Restrictions",
"comment": "Additional restrictions for this user in this organization",
"comment": "Additional restrictions for this user in this team",
"nullable": true
},
@ -140,7 +135,7 @@
"name": "last_active_at",
"type": "timestamp",
"label": "Last Active At",
"comment": "When the user was last active in this organization",
"comment": "When the user was last active in this team",
"nullable": true,
"index": true
},
@ -148,7 +143,7 @@
"name": "login_count",
"type": "integer",
"label": "Login Count",
"comment": "Number of times user has accessed this organization",
"comment": "Number of times user has accessed this team",
"default": 0,
"nullable": true
},
@ -173,26 +168,26 @@
],
"indexes": [
{
"name": "idx_org_user_unique",
"columns": ["org_id", "user_id"],
"name": "idx_team_user_unique",
"columns": ["team_id", "user_id"],
"type": "unique",
"comment": "Unique constraint: one user can have only one membership per organization"
"comment": "Unique constraint: one user can have only one membership per team"
},
{
"name": "idx_org_user_role",
"columns": ["org_id", "role", "status"],
"name": "idx_team_user_role",
"columns": ["team_id", "role_id", "status"],
"type": "index",
"comment": "Index for finding users by role and status within organization"
"comment": "Index for finding users by role and status within team"
},
{
"name": "idx_user_orgs",
"columns": ["user_id", "status", "role"],
"name": "idx_user_teams",
"columns": ["user_id", "status", "role_id"],
"type": "index",
"comment": "Index for finding all organizations for a user"
"comment": "Index for finding all teams for a user"
},
{
"name": "idx_org_invitations",
"columns": ["org_id", "status", "invited_at"],
"name": "idx_team_invitations",
"columns": ["team_id", "status", "invited_at"],
"type": "index",
"comment": "Index for managing invitations"
},
@ -203,18 +198,18 @@
"comment": "Index for invitation token lookup and expiration"
},
{
"name": "idx_org_activity",
"columns": ["org_id", "last_active_at"],
"name": "idx_team_activity",
"columns": ["team_id", "last_active_at"],
"type": "index",
"comment": "Index for organization activity tracking"
"comment": "Index for team activity tracking"
}
],
"relations": {
"organization": {
"team": {
"type": "hasOne",
"model": "__yao.organization",
"key": "org_id",
"foreign": "org_id"
"model": "__yao.team",
"key": "team_id",
"foreign": "team_id"
},
"user": {
"type": "hasOne",
@ -227,6 +222,12 @@
"model": "__yao.user",
"key": "invited_by",
"foreign": "user_id"
},
"role": {
"type": "hasOne",
"model": "__yao.role",
"key": "role_id",
"foreign": "role_id"
}
},
"values": [],

View file

@ -1,11 +1,11 @@
{
"name": "User Role",
"label": "User Role",
"description": "User role and permission management",
"tags": ["user", "role", "permission", "auth"],
"name": "Role",
"label": "Role",
"description": "Role and permission management",
"tags": ["role", "permission", "auth", "rbac"],
"table": {
"name": "user_role",
"comment": "User role and permission management"
"name": "role",
"comment": "Role and permission management"
},
"columns": [
// ============================================================================
@ -187,31 +187,44 @@
],
"indexes": [
{
"name": "idx_user_role_active",
"name": "idx_role_active",
"columns": ["is_active", "sort_order"],
"type": "index",
"comment": "Index for active roles with ordering"
},
{
"name": "idx_user_role_hierarchy",
"name": "idx_role_hierarchy",
"columns": ["parent_role_id", "level"],
"type": "index",
"comment": "Index for role hierarchy queries"
},
{
"name": "idx_user_role_system",
"name": "idx_role_system",
"columns": ["is_system", "is_active"],
"type": "index",
"comment": "Index for system role queries"
},
{
"name": "idx_user_role_default",
"name": "idx_role_default",
"columns": ["is_default", "is_active"],
"type": "index",
"comment": "Index for finding default active role"
}
],
"relations": {},
"relations": {
"members": {
"type": "hasMany",
"model": "__yao.member",
"key": "role_id",
"foreign": "role_id"
},
"users": {
"type": "hasMany",
"model": "__yao.user",
"key": "role_id",
"foreign": "role_id"
}
},
"values": [],
"option": { "timestamps": true, "soft_deletes": true, "permission": true }
}

View file

@ -1,11 +1,11 @@
{
"name": "Organization",
"label": "Organization",
"description": "Organization structure and information storage",
"tags": ["organization", "structure", "business", "verification"],
"name": "Team",
"label": "Team",
"description": "Team structure and information storage",
"tags": ["team", "structure", "business", "verification"],
"table": {
"name": "organization",
"comment": "Organization structure and information storage"
"name": "team",
"comment": "Team structure and information storage"
},
"columns": [
// ============================================================================
@ -19,23 +19,23 @@
"primary": true
},
{
"name": "org_id",
"name": "team_id",
"type": "string",
"label": "Organization ID",
"comment": "Global unique organization identifier",
"label": "Team ID",
"comment": "Global unique team identifier",
"length": 255,
"unique": true,
"index": true
},
// ============================================================================
// Organization Basic Information
// Team Basic Information
// ============================================================================
{
"name": "name",
"type": "string",
"label": "Organization Name",
"comment": "Official organization name",
"label": "Team Name",
"comment": "Official team name",
"length": 200,
"nullable": false
},
@ -43,7 +43,7 @@
"name": "display_name",
"type": "string",
"label": "Display Name",
"comment": "Organization display name or brand name",
"comment": "Team display name or brand name",
"length": 200,
"nullable": true
},
@ -51,14 +51,14 @@
"name": "description",
"type": "text",
"label": "Description",
"comment": "Organization description",
"comment": "Team description",
"nullable": true
},
{
"name": "website",
"type": "string",
"label": "Website",
"comment": "Organization website URL",
"comment": "Team website URL",
"length": 500,
"nullable": true
},
@ -66,19 +66,19 @@
"name": "logo",
"type": "string",
"label": "Logo",
"comment": "Organization logo URL",
"comment": "Team logo URL",
"length": 500,
"nullable": true
},
// ============================================================================
// Organization Ownership
// Team Ownership
// ============================================================================
{
"name": "owner_id",
"type": "string",
"label": "Owner ID",
"comment": "Organization owner user identifier (references user.user_id)",
"comment": "Team owner user identifier (references user.user_id)",
"length": 255,
"nullable": false,
"index": true
@ -91,7 +91,7 @@
"name": "contact_email",
"type": "string",
"label": "Contact Email",
"comment": "Organization contact email address",
"comment": "Team contact email address",
"length": 255,
"nullable": true,
"index": true
@ -100,20 +100,20 @@
"name": "contact_phone",
"type": "string",
"label": "Contact Phone",
"comment": "Organization contact phone number",
"comment": "Team contact phone number",
"length": 50,
"nullable": true,
"index": true
},
// ============================================================================
// Organization Verification
// Team Verification
// ============================================================================
{
"name": "is_verified",
"type": "boolean",
"label": "Is Verified",
"comment": "Whether organization is verified",
"comment": "Whether team is verified",
"default": false,
"index": true
},
@ -121,7 +121,7 @@
"name": "verified_at",
"type": "timestamp",
"label": "Verified At",
"comment": "When organization was verified",
"comment": "When team was verified",
"nullable": true,
"index": true
},
@ -129,29 +129,29 @@
"name": "verified_by",
"type": "string",
"label": "Verified By",
"comment": "Who verified this organization",
"comment": "Who verified this team",
"length": 255,
"nullable": true
},
// ============================================================================
// Organization Unique Code & Type
// Team Unique Code & Type
// ============================================================================
{
"name": "org_code",
"name": "team_code",
"type": "string",
"label": "Organization Code",
"comment": "Unique organization identification code",
"label": "Team Code",
"comment": "Unique team identification code",
"length": 100,
"nullable": true,
"unique": true,
"index": true
},
{
"name": "org_code_type",
"name": "team_code_type",
"type": "enum",
"label": "Organization Code Type",
"comment": "Type of organization identification code",
"label": "Team Code Type",
"comment": "Type of team identification code",
"option": [
// ============================================================================
// Global Universal Standards (Apple & International Compatible)
@ -252,19 +252,19 @@
},
// ============================================================================
// Organization Status & Management
// Team Status & Management
// ============================================================================
{
"name": "status",
"type": "enum",
"label": "Status",
"comment": "Organization status",
"comment": "Team status",
"option": [
"pending", // New organization awaiting verification
"active", // Active organization with full access
"pending", // New team awaiting verification
"active", // Active team with full access
"inactive", // Temporarily inactive
"suspended", // Suspended due to policy violations
"archived" // Archived organization
"archived" // Archived team
],
"default": "pending",
"index": true,
@ -274,7 +274,7 @@
"name": "type_id",
"type": "string",
"label": "Type ID",
"comment": "Organization type identifier for limits and permissions (references user_type.type_id)",
"comment": "Team type identifier for limits and permissions (references user.type.type_id)",
"length": 50,
"nullable": true,
"index": true
@ -282,8 +282,8 @@
{
"name": "type",
"type": "enum",
"label": "Organization Type",
"comment": "Type of organization",
"label": "Team Type",
"comment": "Type of team",
"option": [
"corporation",
"llc",
@ -305,7 +305,7 @@
"name": "address",
"type": "json",
"label": "Address",
"comment": "Organization physical address (structured JSON with full details)",
"comment": "Team physical address (structured JSON with full details)",
"nullable": true
},
{
@ -320,7 +320,7 @@
"name": "city",
"type": "string",
"label": "City",
"comment": "Organization city",
"comment": "Team city",
"length": 100,
"nullable": true,
"index": true
@ -347,7 +347,7 @@
"name": "country",
"type": "string",
"label": "Country",
"comment": "Organization country (ISO 3166-1 alpha-2)",
"comment": "Team country (ISO 3166-1 alpha-2)",
"length": 2,
"nullable": true,
"index": true
@ -373,72 +373,72 @@
"name": "zoneinfo",
"type": "string",
"label": "Zone Info",
"comment": "Organization primary timezone (IANA Time Zone Database format)",
"comment": "Team primary timezone (IANA Time Zone Database format)",
"length": 50,
"nullable": true,
"index": true
},
// ============================================================================
// Organization Settings
// Team Settings
// ============================================================================
{
"name": "settings",
"type": "json",
"label": "Settings",
"comment": "Organization configuration settings",
"comment": "Team configuration settings",
"nullable": true
},
{
"name": "metadata",
"type": "json",
"label": "Metadata",
"comment": "Extended organization metadata and custom fields",
"comment": "Extended team metadata and custom fields",
"nullable": true
}
],
"indexes": [
{
"name": "idx_org_owner_status",
"name": "idx_team_owner_status",
"columns": ["owner_id", "status"],
"type": "index",
"comment": "Index on owner and status for filtering"
},
{
"name": "idx_org_verification",
"name": "idx_team_verification",
"columns": ["is_verified", "verified_at"],
"type": "index",
"comment": "Index on verification status and time"
},
{
"name": "idx_org_code_type",
"columns": ["org_code_type", "org_code"],
"name": "idx_team_code_type",
"columns": ["team_code_type", "team_code"],
"type": "index",
"comment": "Index on organization code type and code"
"comment": "Index on team code type and code"
},
{
"name": "idx_org_location",
"name": "idx_team_location",
"columns": ["country", "state_province", "city"],
"type": "index",
"comment": "Index on country, state/province and city for geographic queries"
},
{
"name": "idx_org_region_type",
"name": "idx_team_region_type",
"columns": ["region", "type"],
"type": "index",
"comment": "Index on geographic region and organization type"
"comment": "Index on geographic region and team type"
},
{
"name": "idx_org_postal_zoneinfo",
"name": "idx_team_postal_zoneinfo",
"columns": ["postal_code", "zoneinfo"],
"type": "index",
"comment": "Index on postal code and zoneinfo for location services"
},
{
"name": "idx_org_type_status",
"name": "idx_team_type_status",
"columns": ["type_id", "status"],
"type": "index",
"comment": "Index on organization type and status for limits and permissions"
"comment": "Index on team type and status for limits and permissions"
}
],
"relations": {
@ -450,22 +450,22 @@
},
"user_type": {
"type": "hasOne",
"model": "__yao.user_type",
"model": "__yao.user.type",
"key": "type_id",
"foreign": "type_id"
},
"members": {
"type": "hasMany",
"model": "__yao.organization_user",
"key": "org_id",
"foreign": "org_id"
"model": "__yao.member",
"key": "team_id",
"foreign": "team_id"
},
"users": {
"type": "hasManyThrough",
"model": "__yao.user",
"through": "__yao.organization_user",
"key": "org_id",
"foreign": "org_id",
"through": "__yao.member",
"key": "team_id",
"foreign": "team_id",
"throughKey": "user_id",
"throughForeign": "user_id"
}

View file

@ -243,7 +243,7 @@
"name": "role_id",
"type": "string",
"label": "Role ID",
"comment": "User role identifier (references user_role.role_id)",
"comment": "User role identifier (references role.role_id)",
"length": 50,
"nullable": true,
"index": true
@ -252,7 +252,7 @@
"name": "type_id",
"type": "string",
"label": "Type ID",
"comment": "User type identifier (references user_type.type_id)",
"comment": "User type identifier (references user.type.type_id)",
"length": 50,
"nullable": true,
"index": true
@ -387,36 +387,42 @@
"relations": {
"role": {
"type": "hasOne",
"model": "__yao.user_role",
"model": "__yao.role",
"key": "role_id",
"foreign": "role_id"
},
"type": {
"type": "hasOne",
"model": "__yao.user_type",
"model": "__yao.user.type",
"key": "type_id",
"foreign": "type_id"
},
"owned_organizations": {
"oauth_accounts": {
"type": "hasMany",
"model": "__yao.organization",
"key": "user_id",
"foreign": "owner_id"
},
"organization_memberships": {
"type": "hasMany",
"model": "__yao.organization_user",
"model": "__yao.user.oauth_account",
"key": "user_id",
"foreign": "user_id"
},
"organizations": {
"owned_teams": {
"type": "hasMany",
"model": "__yao.team",
"key": "user_id",
"foreign": "owner_id"
},
"team_memberships": {
"type": "hasMany",
"model": "__yao.member",
"key": "user_id",
"foreign": "user_id"
},
"teams": {
"type": "hasManyThrough",
"model": "__yao.organization",
"through": "__yao.organization_user",
"model": "__yao.team",
"through": "__yao.member",
"key": "user_id",
"foreign": "user_id",
"throughKey": "org_id",
"throughForeign": "org_id"
"throughKey": "team_id",
"throughForeign": "team_id"
}
},
"values": [],

View file

@ -1,6 +1,6 @@
{
"name": "User OAuth Account",
"label": "User OAuth Account",
"name": "OAuth Account",
"label": "OAuth Account",
"description": "User's third-party OAuth account information storage",
"tags": ["user", "oauth", "social", "external", "provider"],
"table": {
@ -225,31 +225,38 @@
],
"indexes": [
{
"name": "idx_user_oauth_user_provider",
"name": "idx_oauth_account_user_provider",
"columns": ["user_id", "provider"],
"type": "unique",
"comment": "Unique constraint: one account per provider per user"
},
{
"name": "idx_user_oauth_provider_sub",
"name": "idx_oauth_account_provider_sub",
"columns": ["provider", "sub"],
"type": "unique",
"comment": "Unique constraint: one record per provider sub claim"
},
{
"name": "idx_user_oauth_email",
"name": "idx_oauth_account_email",
"columns": ["provider", "email"],
"type": "index",
"comment": "Index for email lookups by provider"
},
{
"name": "idx_user_oauth_active",
"name": "idx_oauth_account_active",
"columns": ["is_active", "last_login_at"],
"type": "index",
"comment": "Index for active account queries"
}
],
"relations": {},
"relations": {
"user": {
"type": "hasOne",
"model": "__yao.user",
"key": "user_id",
"foreign": "user_id"
}
},
"values": [],
"option": { "timestamps": true, "soft_deletes": true, "permission": true }
}

View file

@ -1,6 +1,6 @@
{
"name": "User Type",
"label": "User Type",
"name": "Type",
"label": "Type",
"description": "User type classification and configuration",
"tags": ["user", "type", "classification", "config"],
"table": {
@ -51,7 +51,7 @@
"name": "default_role_id",
"type": "string",
"label": "Default Role ID",
"comment": "Default role assigned to users of this type",
"comment": "Default role assigned to users of this type (references role.role_id)",
"length": 50,
"nullable": true,
"index": true
@ -145,19 +145,32 @@
],
"indexes": [
{
"name": "idx_user_type_active",
"name": "idx_type_active",
"columns": ["is_active", "sort_order"],
"type": "index",
"comment": "Index for active user types with ordering"
},
{
"name": "idx_user_type_default",
"name": "idx_type_default",
"columns": ["is_default", "is_active"],
"type": "index",
"comment": "Index for finding default active user type"
}
],
"relations": {},
"relations": {
"default_role": {
"type": "hasOne",
"model": "__yao.role",
"key": "default_role_id",
"foreign": "role_id"
},
"users": {
"type": "hasMany",
"model": "__yao.user",
"key": "type_id",
"foreign": "type_id"
}
},
"values": [],
"option": { "timestamps": true, "soft_deletes": true, "permission": true }
}