forked from bai/curriculum-project-hub
feat(hub): 支持教师团队权限
This commit is contained in:
+96
-21
@@ -39,6 +39,8 @@ model User {
|
||||
requestedRuns AgentRun[] @relation("runRequester")
|
||||
heldLocks ProjectAgentLock[] @relation("lockHolder")
|
||||
feishuBindings ProjectGroupBinding[] @relation("bindingCreator")
|
||||
teamMemberships TeamMembership[]
|
||||
externalPrincipalMemberships ExternalPrincipalMembership[]
|
||||
permissionGrants PermissionGrant[] @relation("grantCreator")
|
||||
roleTriggerGrants RoleTriggerGrant[] @relation("roleGrantCreator")
|
||||
auditEntries AuditEntry[] @relation("auditActor")
|
||||
@@ -64,6 +66,86 @@ enum PlatformRole {
|
||||
TEACHER
|
||||
}
|
||||
|
||||
/// ADR-0019: typed principals for permission grants and actor resolution.
|
||||
/// USER is identified by Feishu open_id; TEAM by Hub Team.id; Feishu external
|
||||
/// principals by their Feishu ids.
|
||||
enum PrincipalType {
|
||||
USER
|
||||
TEAM
|
||||
FEISHU_CHAT
|
||||
FEISHU_DEPARTMENT
|
||||
FEISHU_USER_GROUP
|
||||
APP
|
||||
}
|
||||
|
||||
/// Hub-managed teacher team. A team is a first-class permission principal.
|
||||
model Team {
|
||||
id String @id @default(cuid())
|
||||
slug String @unique
|
||||
name String
|
||||
description String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
archivedAt DateTime?
|
||||
|
||||
memberships TeamMembership[]
|
||||
externalBindings TeamExternalBinding[]
|
||||
|
||||
@@index([archivedAt])
|
||||
}
|
||||
|
||||
/// Direct Hub team membership. External Feishu groups can also map to teams via
|
||||
/// TeamExternalBinding; both sources are resolved at authorization time.
|
||||
model TeamMembership {
|
||||
id String @id @default(cuid())
|
||||
teamId String
|
||||
userId String
|
||||
createdAt DateTime @default(now())
|
||||
revokedAt DateTime?
|
||||
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([teamId, userId, revokedAt])
|
||||
@@index([teamId, revokedAt])
|
||||
@@index([userId, revokedAt])
|
||||
}
|
||||
|
||||
/// Bind a Hub team to an external Feishu principal. When an actor resolves to
|
||||
/// the external principal, the actor also resolves to this team.
|
||||
model TeamExternalBinding {
|
||||
id String @id @default(cuid())
|
||||
teamId String
|
||||
principalType PrincipalType
|
||||
principalId String
|
||||
createdAt DateTime @default(now())
|
||||
revokedAt DateTime?
|
||||
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([teamId, principalType, principalId, revokedAt])
|
||||
@@index([principalType, principalId, revokedAt])
|
||||
@@index([teamId, revokedAt])
|
||||
}
|
||||
|
||||
/// Locally synchronized Feishu external principal membership.
|
||||
model ExternalPrincipalMembership {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
principalType PrincipalType
|
||||
principalId String
|
||||
source String
|
||||
syncedAt DateTime @default(now())
|
||||
revokedAt DateTime?
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, principalType, principalId, source, revokedAt])
|
||||
@@index([userId, revokedAt])
|
||||
@@index([principalType, principalId, revokedAt])
|
||||
@@index([source, syncedAt])
|
||||
}
|
||||
|
||||
// --- Project & Feishu binding (ADR-0001) ---------------------------------
|
||||
|
||||
model Project {
|
||||
@@ -80,8 +162,6 @@ model Project {
|
||||
agentSessions AgentSession[]
|
||||
agentRuns AgentRun[]
|
||||
agentLock ProjectAgentLock?
|
||||
permissionGrants PermissionGrant[] @relation("projectGrants")
|
||||
permissionSettings PermissionSettings[] @relation("projectSettings")
|
||||
roleTriggerGrants RoleTriggerGrant[] @relation("projectRoleGrants")
|
||||
auditEntries AuditEntry[] @relation("projectAudit")
|
||||
fileChanges AgentFileChange[] @relation("projectFileChanges")
|
||||
@@ -224,32 +304,29 @@ enum PermissionResourceType {
|
||||
}
|
||||
|
||||
/// ADR-0004 PermissionGrant: resource × principal × role.
|
||||
/// `principal` is an opaque string (principal sub-typology OPEN, ADR-0004).
|
||||
/// The compound unique covers "one active grant per (resource, principal, role)"
|
||||
/// — revoked rows keep `revokedAt` set, so a re-grant after revocation is a new
|
||||
/// row, not a conflict.
|
||||
/// ADR-0019 replaces the old opaque `principal` with typed
|
||||
/// `principalType/principalId` so user/team/Feishu principals compose through
|
||||
/// the same authorization path.
|
||||
model PermissionGrant {
|
||||
id String @id @default(cuid())
|
||||
resourceType PermissionResourceType
|
||||
resourceId String
|
||||
principal String
|
||||
principalType PrincipalType
|
||||
principalId String
|
||||
role PermissionRole
|
||||
createdByUserId String?
|
||||
createdAt DateTime @default(now())
|
||||
revokedAt DateTime?
|
||||
|
||||
project Project? @relation("projectGrants", fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
createdBy User? @relation("grantCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
|
||||
createdBy User? @relation("grantCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@unique([resourceType, resourceId, principal, role, revokedAt])
|
||||
@@index([resourceType, resourceId, revokedAt])
|
||||
@@index([principal, revokedAt])
|
||||
@@index([principalType, principalId, revokedAt])
|
||||
}
|
||||
|
||||
/// ADR-0004 PermissionSettings: six policy knobs, values OPEN. Stored as one
|
||||
/// opaque string column each; the enum/value-domain is decided by admin policy,
|
||||
/// not by this schema. `resourceType`+`resourceId` identify the resource.
|
||||
/// Related to Project only when resourceType=PROJECT (null otherwise).
|
||||
/// opaque string column each. ADR-0019 pins `agentTrigger` values used by the
|
||||
/// authorizer: ROLE, MANAGE_ONLY, DISABLED.
|
||||
model PermissionSettings {
|
||||
id String @id @default(cuid())
|
||||
resourceType PermissionResourceType
|
||||
@@ -263,8 +340,6 @@ model PermissionSettings {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
project Project? @relation("projectSettings", fields: [resourceId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([resourceType, resourceId])
|
||||
@@index([resourceType, resourceId])
|
||||
}
|
||||
@@ -276,15 +351,16 @@ model PermissionSettings {
|
||||
/// /review vs /draft). Two gates in series — both must pass.
|
||||
///
|
||||
/// `roleId` is an opaque string matching RoleEntry.id (ADR-0017: roles are
|
||||
/// data, not a code enum). `principal` mirrors ADR-0004's opaque principal
|
||||
/// (sub-typology OPEN). A project-scoped row grants the role on that project;
|
||||
/// data, not a code enum). ADR-0019 makes the principal typed. A project-scoped
|
||||
/// row grants the role on that project;
|
||||
/// the compound unique covers "one active grant per (project, principal, role)".
|
||||
/// Revocation via `revokedAt`, same pattern as PermissionGrant.
|
||||
model RoleTriggerGrant {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
roleId String
|
||||
principal String
|
||||
principalType PrincipalType
|
||||
principalId String
|
||||
createdByUserId String?
|
||||
createdAt DateTime @default(now())
|
||||
revokedAt DateTime?
|
||||
@@ -292,9 +368,8 @@ model RoleTriggerGrant {
|
||||
project Project @relation("projectRoleGrants", fields: [projectId], references: [id], onDelete: Cascade)
|
||||
createdBy User? @relation("roleGrantCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@unique([projectId, roleId, principal, revokedAt])
|
||||
@@index([projectId, roleId, revokedAt])
|
||||
@@index([principal, revokedAt])
|
||||
@@index([principalType, principalId, revokedAt])
|
||||
}
|
||||
|
||||
// --- Audit (ADR Audit, content OPEN) -------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user