feat: 接入真实登录与成员权限
- auth/members/messages: 新增超级管理员登录、成员权限和密码消息流程 - agent-config/agent: 支持服务端保存 Agent 配置并取消未配置 mock 成功结果 - projects/deploy-runs/settings: 按当前用户权限保护真实接口 - prisma: 新增用户、项目权限和平台消息表结构
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
CREATE TABLE `users` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`account` VARCHAR(191) NOT NULL,
|
||||
`displayName` VARCHAR(191) NOT NULL,
|
||||
`role` ENUM('SUPER_ADMIN', 'MEMBER') NOT NULL DEFAULT 'MEMBER',
|
||||
`status` ENUM('ACTIVE', 'DISABLED') NOT NULL DEFAULT 'ACTIVE',
|
||||
`passwordHash` TEXT NOT NULL,
|
||||
`mustChangePassword` BOOLEAN NOT NULL DEFAULT false,
|
||||
`passwordVersion` INTEGER NOT NULL DEFAULT 1,
|
||||
`lastLoginAt` DATETIME(3) NULL,
|
||||
`createdById` VARCHAR(191) NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `users_account_key`(`account`),
|
||||
INDEX `users_role_status_idx`(`role`, `status`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `project_member_permissions` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`userId` VARCHAR(191) NOT NULL,
|
||||
`projectId` VARCHAR(191) NOT NULL,
|
||||
`level` ENUM('READ', 'BUILD') NOT NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `project_member_permissions_userId_projectId_key`(`userId`, `projectId`),
|
||||
INDEX `project_member_permissions_projectId_idx`(`projectId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `platform_messages` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`type` ENUM('PASSWORD_RESET_REQUEST') NOT NULL,
|
||||
`status` ENUM('PENDING', 'HANDLED') NOT NULL DEFAULT 'PENDING',
|
||||
`title` VARCHAR(191) NOT NULL,
|
||||
`content` TEXT NOT NULL,
|
||||
`requesterId` VARCHAR(191) NULL,
|
||||
`requesterAccount` VARCHAR(191) NOT NULL,
|
||||
`targetResourceType` VARCHAR(191) NULL,
|
||||
`targetResourceId` VARCHAR(191) NULL,
|
||||
`metadata` JSON NULL,
|
||||
`handledById` VARCHAR(191) NULL,
|
||||
`handledAt` DATETIME(3) NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
|
||||
INDEX `platform_messages_type_status_createdAt_idx`(`type`, `status`, `createdAt`),
|
||||
INDEX `platform_messages_requesterId_status_idx`(`requesterId`, `status`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `project_member_permissions`
|
||||
ADD CONSTRAINT `project_member_permissions_userId_fkey`
|
||||
FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE `project_member_permissions`
|
||||
ADD CONSTRAINT `project_member_permissions_projectId_fkey`
|
||||
FOREIGN KEY (`projectId`) REFERENCES `projects`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE `platform_messages`
|
||||
ADD CONSTRAINT `platform_messages_requesterId_fkey`
|
||||
FOREIGN KEY (`requesterId`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE `platform_messages`
|
||||
ADD CONSTRAINT `platform_messages_handledById_fkey`
|
||||
FOREIGN KEY (`handledById`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -1,12 +1,12 @@
|
||||
# Prisma Migration Structure
|
||||
# Prisma 迁移目录
|
||||
|
||||
This project uses Prisma as the default data access layer.
|
||||
本项目默认使用 Prisma 作为数据访问层。
|
||||
|
||||
Migration policy:
|
||||
迁移规则:
|
||||
|
||||
- Create schema changes with `pnpm prisma:migrate:dev --name <change-name>` after the final MySQL connection details are confirmed.
|
||||
- Commit generated folders under `prisma/migrations/<timestamp>_<change-name>/migration.sql`.
|
||||
- Run migration checks in deployment before starting the API.
|
||||
- Keep raw SQL exceptional. If native SQL is required for performance or vendor-specific behavior, place it behind a Repository method and document why Prisma Client is not sufficient.
|
||||
- 确认本地或测试环境 MySQL 连接后,使用 `pnpm prisma:migrate:dev --name <变更名称>` 生成迁移。
|
||||
- 需要提交 `prisma/migrations/<时间戳>_<变更名称>/migration.sql`。
|
||||
- 部署前先执行迁移检查,再启动 API 服务。
|
||||
- 原生 SQL 只允许作为例外使用,必须放在 Repository 方法中并说明原因。
|
||||
|
||||
The first-stage schema is defined in `prisma/schema.prisma`; no live migration is executed until a local or staging MySQL database is explicitly provided.
|
||||
首阶段 schema 定义在 `prisma/schema.prisma`。没有明确数据库连接前,不要求本地自动执行迁移。
|
||||
|
||||
@@ -64,6 +64,30 @@ enum OutboxStatus {
|
||||
DEAD
|
||||
}
|
||||
|
||||
enum UserRole {
|
||||
SUPER_ADMIN
|
||||
MEMBER
|
||||
}
|
||||
|
||||
enum UserStatus {
|
||||
ACTIVE
|
||||
DISABLED
|
||||
}
|
||||
|
||||
enum ProjectPermissionLevel {
|
||||
READ
|
||||
BUILD
|
||||
}
|
||||
|
||||
enum PlatformMessageType {
|
||||
PASSWORD_RESET_REQUEST
|
||||
}
|
||||
|
||||
enum PlatformMessageStatus {
|
||||
PENDING
|
||||
HANDLED
|
||||
}
|
||||
|
||||
model Project {
|
||||
id String @id @default(cuid())
|
||||
key String @unique
|
||||
@@ -78,6 +102,7 @@ model Project {
|
||||
environments Environment[]
|
||||
deployJobs DeployJob[]
|
||||
deployRuns DeployRun[]
|
||||
permissions ProjectMemberPermission[]
|
||||
|
||||
@@map("projects")
|
||||
}
|
||||
@@ -194,6 +219,68 @@ model AuditLog {
|
||||
@@map("audit_logs")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
account String @unique
|
||||
displayName String
|
||||
role UserRole @default(MEMBER)
|
||||
status UserStatus @default(ACTIVE)
|
||||
passwordHash String @db.Text
|
||||
mustChangePassword Boolean @default(false)
|
||||
passwordVersion Int @default(1)
|
||||
lastLoginAt DateTime?
|
||||
createdById String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
projectPermissions ProjectMemberPermission[]
|
||||
requestedMessages PlatformMessage[] @relation("message_requester")
|
||||
handledMessages PlatformMessage[] @relation("message_handler")
|
||||
|
||||
@@index([role, status])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model ProjectMemberPermission {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
projectId String
|
||||
level ProjectPermissionLevel
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, projectId])
|
||||
@@index([projectId])
|
||||
@@map("project_member_permissions")
|
||||
}
|
||||
|
||||
model PlatformMessage {
|
||||
id String @id @default(cuid())
|
||||
type PlatformMessageType
|
||||
status PlatformMessageStatus @default(PENDING)
|
||||
title String
|
||||
content String @db.Text
|
||||
requesterId String?
|
||||
requesterAccount String
|
||||
targetResourceType String?
|
||||
targetResourceId String?
|
||||
metadata Json?
|
||||
handledById String?
|
||||
handledAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
requester User? @relation("message_requester", fields: [requesterId], references: [id], onDelete: SetNull)
|
||||
handler User? @relation("message_handler", fields: [handledById], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([type, status, createdAt])
|
||||
@@index([requesterId, status])
|
||||
@@map("platform_messages")
|
||||
}
|
||||
|
||||
model Secret {
|
||||
id String @id @default(cuid())
|
||||
provider SecretProvider
|
||||
|
||||
+56
-1
@@ -1,10 +1,22 @@
|
||||
/**
|
||||
* 将代码内的纳管项目清单同步到数据库,保持项目、环境和 Jenkins Job 可重复初始化。
|
||||
*/
|
||||
const { EnvironmentKind, PrismaClient, ProjectStatus } = require('@prisma/client');
|
||||
const {
|
||||
EnvironmentKind,
|
||||
PrismaClient,
|
||||
ProjectStatus,
|
||||
UserRole,
|
||||
UserStatus,
|
||||
} = require('@prisma/client');
|
||||
const {
|
||||
LOCAL_SUPER_ADMIN_INITIAL_PASSWORD,
|
||||
SUPER_ADMIN_SEED,
|
||||
} = require('../dist/auth/auth.constants');
|
||||
const { PasswordService } = require('../dist/auth/password.service');
|
||||
const { seedProjects } = require('../dist/projects/project.seed');
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const passwordService = new PasswordService();
|
||||
|
||||
async function main() {
|
||||
for (const project of seedProjects) {
|
||||
@@ -44,6 +56,49 @@ async function main() {
|
||||
await upsertEnvironment(projectRecord.id, environment);
|
||||
}
|
||||
}
|
||||
|
||||
await upsertSuperAdmin();
|
||||
}
|
||||
|
||||
async function upsertSuperAdmin() {
|
||||
const passwordHash = await passwordService.hashPassword(
|
||||
resolveSuperAdminInitialPassword(),
|
||||
);
|
||||
|
||||
await prisma.user.upsert({
|
||||
where: {
|
||||
account: SUPER_ADMIN_SEED.account,
|
||||
},
|
||||
create: {
|
||||
id: SUPER_ADMIN_SEED.id,
|
||||
account: SUPER_ADMIN_SEED.account,
|
||||
displayName: SUPER_ADMIN_SEED.displayName,
|
||||
role: UserRole.SUPER_ADMIN,
|
||||
status: UserStatus.ACTIVE,
|
||||
passwordHash,
|
||||
mustChangePassword: false,
|
||||
},
|
||||
update: {
|
||||
displayName: SUPER_ADMIN_SEED.displayName,
|
||||
role: UserRole.SUPER_ADMIN,
|
||||
status: UserStatus.ACTIVE,
|
||||
mustChangePassword: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function resolveSuperAdminInitialPassword() {
|
||||
if (process.env.SUPER_ADMIN_INITIAL_PASSWORD) {
|
||||
return process.env.SUPER_ADMIN_INITIAL_PASSWORD;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
throw new Error(
|
||||
'Missing SUPER_ADMIN_INITIAL_PASSWORD in production seed run',
|
||||
);
|
||||
}
|
||||
|
||||
return LOCAL_SUPER_ADMIN_INITIAL_PASSWORD;
|
||||
}
|
||||
|
||||
async function upsertEnvironment(projectId, environment) {
|
||||
|
||||
Reference in New Issue
Block a user