chore: 接入 Prisma 迁移部署基线
This commit is contained in:
@@ -31,6 +31,14 @@ pnpm prisma:migrate:dev --name init
|
|||||||
pnpm prisma:seed
|
pnpm prisma:seed
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 数据库迁移与 Jenkins 部署
|
||||||
|
|
||||||
|
- 迁移历史从 `prisma/migrations/20260611000000_baseline_current_schema` 开始,空库可以直接执行 `pnpm prisma migrate deploy` 初始化当前 schema。
|
||||||
|
- 生产库在启用 Prisma 迁移历史前已经存在表结构;`deploy/deploy-devops-platform-api-from-jenkins.sh` 会检测完整表结构并自动将基线迁移标记为已应用,再执行 `pnpm prisma migrate deploy`。
|
||||||
|
- Jenkins 服务器使用 `/usr/local/bin/deploy-devops-platform-api-from-jenkins`,该脚本应与仓库 `deploy/deploy-devops-platform-api-from-jenkins.sh` 保持一致。
|
||||||
|
- 如需紧急跳过迁移,可在服务器环境中设置 `PRISMA_MIGRATE_DEPLOY_ENABLED=false`,但不能在存在 schema 变更的版本长期使用该开关。
|
||||||
|
- 新增表或字段时必须提交新的 `prisma/migrations/<timestamp>_<name>/migration.sql`,不要再用手工 SQL 直接改生产库。
|
||||||
|
|
||||||
## 内置账号
|
## 内置账号
|
||||||
|
|
||||||
- 超级管理员账号:`zhanxi`
|
- 超级管理员账号:`zhanxi`
|
||||||
|
|||||||
@@ -50,3 +50,9 @@ pnpm lint
|
|||||||
pnpm test
|
pnpm test
|
||||||
pnpm build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 部署迁移
|
||||||
|
|
||||||
|
- Jenkins API 部署脚本的仓库版本在 `deploy/deploy-devops-platform-api-from-jenkins.sh`。
|
||||||
|
- 脚本会在切换 `/srv/www/devops-platform-api` 前运行 `pnpm prisma migrate deploy`,迁移失败时不会替换旧服务目录。
|
||||||
|
- 生产库早期通过手工 SQL 初始化,首次启用迁移时脚本会在确认 12 张核心表都存在后把基线迁移标记为 applied。
|
||||||
|
|||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SRC="${1:-}"
|
||||||
|
APP_DIR="/srv/www/devops-platform-api"
|
||||||
|
BACKUP_DIR="/srv/www/devops-platform-api-backups"
|
||||||
|
ENV_FILE="/etc/devops-platform-api.env"
|
||||||
|
PM2_NAME="devops-platform-api"
|
||||||
|
HEALTH_URL="http://127.0.0.1:4300/health"
|
||||||
|
TS="$(date +%Y%m%d%H%M%S)"
|
||||||
|
|
||||||
|
if [ -z "$SRC" ] || [ ! -d "$SRC" ]; then
|
||||||
|
echo "Usage: deploy-devops-platform-api-from-jenkins <jenkins-workspace>"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
for required in "$SRC/dist/main.js" "$SRC/package.json" "$SRC/pnpm-lock.yaml" "$SRC/prisma" "$ENV_FILE"; do
|
||||||
|
if [ ! -e "$required" ]; then
|
||||||
|
echo "Missing $required"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
run_prisma_migrations() {
|
||||||
|
if [ "${PRISMA_MIGRATE_DEPLOY_ENABLED:-true}" != "true" ]; then
|
||||||
|
echo "Skipping Prisma migrations because PRISMA_MIGRATE_DEPLOY_ENABLED is not true"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -a
|
||||||
|
. ./.env.production
|
||||||
|
set +a
|
||||||
|
|
||||||
|
local migration_state
|
||||||
|
migration_state="$(node <<'NODE'
|
||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
|
||||||
|
const requiredTables = [
|
||||||
|
'agent_invocations',
|
||||||
|
'audit_logs',
|
||||||
|
'deploy_jobs',
|
||||||
|
'deploy_runs',
|
||||||
|
'environments',
|
||||||
|
'outbox_messages',
|
||||||
|
'platform_messages',
|
||||||
|
'project_member_permissions',
|
||||||
|
'projects',
|
||||||
|
'run_steps',
|
||||||
|
'secrets',
|
||||||
|
'users',
|
||||||
|
];
|
||||||
|
const baselineMigrations = [
|
||||||
|
'20260611000000_baseline_current_schema',
|
||||||
|
'20260611120000_auth_members_messages',
|
||||||
|
];
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const tables = await prisma.$queryRaw`
|
||||||
|
select table_name
|
||||||
|
from information_schema.tables
|
||||||
|
where table_schema = database()
|
||||||
|
`;
|
||||||
|
const existingTables = new Set(tables.map((row) => row.TABLE_NAME || row.table_name));
|
||||||
|
|
||||||
|
if (existingTables.size === 0) {
|
||||||
|
console.log('fresh');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const missingTables = requiredTables.filter((name) => !existingTables.has(name));
|
||||||
|
if (missingTables.length > 0) {
|
||||||
|
console.error(`Existing database schema is incomplete; missing tables: ${missingTables.join(', ')}`);
|
||||||
|
process.exit(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasMigrationTable = existingTables.has('_prisma_migrations');
|
||||||
|
const appliedMigrations = hasMigrationTable
|
||||||
|
? await prisma.$queryRaw`select migration_name from _prisma_migrations`
|
||||||
|
: [];
|
||||||
|
const appliedNames = new Set(
|
||||||
|
appliedMigrations.map((row) => row.MIGRATION_NAME || row.migration_name),
|
||||||
|
);
|
||||||
|
const missingMigrations = baselineMigrations.filter((name) => !appliedNames.has(name));
|
||||||
|
|
||||||
|
if (missingMigrations.length > 0) {
|
||||||
|
console.log(`bootstrap:${missingMigrations.join(',')}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.finally(() => prisma.$disconnect());
|
||||||
|
NODE
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ "$migration_state" == bootstrap:* ]]; then
|
||||||
|
local migrations_csv="${migration_state#bootstrap:}"
|
||||||
|
IFS=',' read -r -a missing_migrations <<< "$migrations_csv"
|
||||||
|
for migration in "${missing_migrations[@]}"; do
|
||||||
|
echo "Marking existing production schema migration as applied: $migration"
|
||||||
|
pnpm prisma migrate resolve --schema prisma/schema.prisma --applied "$migration"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "Prisma migration bootstrap state: $migration_state"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Running Prisma migrate deploy"
|
||||||
|
pnpm prisma migrate deploy --schema prisma/schema.prisma
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
TMP="$(mktemp -d /srv/www/devops-platform-api.deploy.XXXXXX)"
|
||||||
|
OLD="$APP_DIR.old.$TS"
|
||||||
|
|
||||||
|
cp -a "$SRC/dist" "$TMP/dist"
|
||||||
|
cp -a "$SRC/prisma" "$TMP/prisma"
|
||||||
|
cp -a "$SRC/package.json" "$TMP/package.json"
|
||||||
|
cp -a "$SRC/pnpm-lock.yaml" "$TMP/pnpm-lock.yaml"
|
||||||
|
cp -a "$ENV_FILE" "$TMP/.env.production"
|
||||||
|
|
||||||
|
cd "$TMP"
|
||||||
|
corepack enable
|
||||||
|
corepack prepare pnpm@9.15.4 --activate
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm prisma:generate
|
||||||
|
run_prisma_migrations
|
||||||
|
|
||||||
|
if [ -d "$APP_DIR" ]; then
|
||||||
|
tar -C "$APP_DIR" -czf "$BACKUP_DIR/$TS.tgz" .
|
||||||
|
mv "$APP_DIR" "$OLD"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$TMP" "$APP_DIR"
|
||||||
|
cd "$APP_DIR"
|
||||||
|
set -a
|
||||||
|
. ./.env.production
|
||||||
|
set +a
|
||||||
|
|
||||||
|
if ! pm2 restart "$PM2_NAME" --update-env; then
|
||||||
|
pm2 start dist/main.js --name "$PM2_NAME" --update-env
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 8
|
||||||
|
if ! curl -fsS "$HEALTH_URL" >/dev/null; then
|
||||||
|
echo "Health check failed; rolling back $PM2_NAME"
|
||||||
|
pm2 stop "$PM2_NAME" || true
|
||||||
|
rm -rf "$APP_DIR.failed.$TS"
|
||||||
|
mv "$APP_DIR" "$APP_DIR.failed.$TS"
|
||||||
|
if [ -d "$OLD" ]; then
|
||||||
|
mv "$OLD" "$APP_DIR"
|
||||||
|
cd "$APP_DIR"
|
||||||
|
set -a
|
||||||
|
. ./.env.production
|
||||||
|
set +a
|
||||||
|
pm2 restart "$PM2_NAME" --update-env || pm2 start dist/main.js --name "$PM2_NAME" --update-env
|
||||||
|
fi
|
||||||
|
exit 5
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$OLD"
|
||||||
|
pm2 save >/dev/null
|
||||||
|
echo "Deployed devops-platform-api from $SRC to $APP_DIR"
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
-- Historical SQL executed manually on production before `_prisma_migrations`
|
||||||
|
-- was bootstrapped. Kept for audit/reference only; Prisma migrate ignores this
|
||||||
|
-- directory.
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
-- Baseline migration for the current DevOps platform schema.
|
||||||
|
-- Existing production tables are marked as applied by the Jenkins deploy script
|
||||||
|
-- before running `prisma migrate deploy`; fresh databases execute this file.
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `projects` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`key` VARCHAR(191) NOT NULL,
|
||||||
|
`name` VARCHAR(191) NOT NULL,
|
||||||
|
`repositoryUrl` VARCHAR(512) NULL,
|
||||||
|
`defaultBranch` VARCHAR(191) NOT NULL DEFAULT 'develop',
|
||||||
|
`status` ENUM('ACTIVE', 'ARCHIVED') NOT NULL DEFAULT 'ACTIVE',
|
||||||
|
`metadata` JSON NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE INDEX `projects_key_key`(`key`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `environments` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`projectId` VARCHAR(191) NOT NULL,
|
||||||
|
`name` ENUM('TEST', 'PRODUCTION') NOT NULL,
|
||||||
|
`displayName` VARCHAR(191) NOT NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
INDEX `environments_projectId_idx`(`projectId`),
|
||||||
|
UNIQUE INDEX `environments_projectId_name_key`(`projectId`, `name`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `deploy_jobs` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`projectId` VARCHAR(191) NOT NULL,
|
||||||
|
`environmentId` VARCHAR(191) NOT NULL,
|
||||||
|
`jenkinsJobPath` VARCHAR(191) NOT NULL,
|
||||||
|
`jenkinsParameters` JSON NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
INDEX `deploy_jobs_environmentId_idx`(`environmentId`),
|
||||||
|
UNIQUE INDEX `deploy_jobs_projectId_environmentId_key`(`projectId`, `environmentId`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `deploy_runs` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`projectId` VARCHAR(191) NOT NULL,
|
||||||
|
`environmentId` VARCHAR(191) NOT NULL,
|
||||||
|
`jobId` VARCHAR(191) NULL,
|
||||||
|
`status` ENUM('PENDING', 'QUEUED', 'RUNNING', 'SUCCESS', 'FAILED', 'CANCELED') NOT NULL DEFAULT 'PENDING',
|
||||||
|
`trigger` ENUM('MANUAL', 'GITEA_WEBHOOK', 'RETRY') NOT NULL DEFAULT 'MANUAL',
|
||||||
|
`ref` VARCHAR(191) NOT NULL,
|
||||||
|
`operator` VARCHAR(191) NOT NULL,
|
||||||
|
`idempotencyKey` VARCHAR(191) NOT NULL,
|
||||||
|
`jenkinsQueueId` VARCHAR(191) NULL,
|
||||||
|
`jenkinsBuildNumber` INTEGER NULL,
|
||||||
|
`failureSummary` TEXT NULL,
|
||||||
|
`metadata` JSON NULL,
|
||||||
|
`version` INTEGER NOT NULL DEFAULT 1,
|
||||||
|
`startedAt` DATETIME(3) NULL,
|
||||||
|
`finishedAt` DATETIME(3) NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE INDEX `deploy_runs_idempotencyKey_key`(`idempotencyKey`),
|
||||||
|
INDEX `deploy_runs_projectId_environmentId_createdAt_idx`(`projectId`, `environmentId`, `createdAt`),
|
||||||
|
INDEX `deploy_runs_status_createdAt_idx`(`status`, `createdAt`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `run_steps` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`deployRunId` VARCHAR(191) NOT NULL,
|
||||||
|
`bpmnNodeId` VARCHAR(191) NOT NULL,
|
||||||
|
`name` VARCHAR(191) NOT NULL,
|
||||||
|
`status` ENUM('PENDING', 'RUNNING', 'SUCCESS', 'FAILED', 'SKIPPED', 'CANCELED') NOT NULL DEFAULT 'PENDING',
|
||||||
|
`order` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`errorSummary` TEXT NULL,
|
||||||
|
`metadata` JSON NULL,
|
||||||
|
`startedAt` DATETIME(3) NULL,
|
||||||
|
`finishedAt` DATETIME(3) NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
INDEX `run_steps_deployRunId_order_idx`(`deployRunId`, `order`),
|
||||||
|
UNIQUE INDEX `run_steps_deployRunId_bpmnNodeId_key`(`deployRunId`, `bpmnNodeId`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `audit_logs` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`deployRunId` VARCHAR(191) NULL,
|
||||||
|
`actorId` VARCHAR(191) NULL,
|
||||||
|
`actorName` VARCHAR(191) NULL,
|
||||||
|
`action` VARCHAR(191) NOT NULL,
|
||||||
|
`resourceType` VARCHAR(191) NOT NULL,
|
||||||
|
`resourceId` VARCHAR(191) NULL,
|
||||||
|
`requestId` VARCHAR(191) NULL,
|
||||||
|
`sourceIp` VARCHAR(191) NULL,
|
||||||
|
`userAgent` VARCHAR(191) NULL,
|
||||||
|
`before` JSON NULL,
|
||||||
|
`after` JSON NULL,
|
||||||
|
`parameterDigest` JSON NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
|
||||||
|
INDEX `audit_logs_resourceType_resourceId_createdAt_idx`(`resourceType`, `resourceId`, `createdAt`),
|
||||||
|
INDEX `audit_logs_actorId_createdAt_idx`(`actorId`, `createdAt`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
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,
|
||||||
|
|
||||||
|
INDEX `project_member_permissions_projectId_idx`(`projectId`),
|
||||||
|
UNIQUE INDEX `project_member_permissions_userId_projectId_key`(`userId`, `projectId`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `secrets` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`provider` ENUM('JENKINS', 'GITEA', 'WECOM', 'LLM', 'INTERNAL') NOT NULL,
|
||||||
|
`scopeType` VARCHAR(191) NOT NULL,
|
||||||
|
`scopeId` VARCHAR(191) NOT NULL,
|
||||||
|
`name` VARCHAR(191) NOT NULL,
|
||||||
|
`encryptedValue` TEXT NOT NULL,
|
||||||
|
`version` INTEGER NOT NULL DEFAULT 1,
|
||||||
|
`rotatedAt` DATETIME(3) NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
INDEX `secrets_scopeType_scopeId_idx`(`scopeType`, `scopeId`),
|
||||||
|
UNIQUE INDEX `secrets_provider_scopeType_scopeId_name_key`(`provider`, `scopeType`, `scopeId`, `name`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `agent_invocations` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`deployRunId` VARCHAR(191) NULL,
|
||||||
|
`purpose` ENUM('RISK_SUMMARY', 'FAILURE_DIAGNOSIS', 'RUNBOOK_QA', 'RELEASE_NOTES', 'INCIDENT_REVIEW') NOT NULL,
|
||||||
|
`actorId` VARCHAR(191) NULL,
|
||||||
|
`actorName` VARCHAR(191) NULL,
|
||||||
|
`model` VARCHAR(191) NULL,
|
||||||
|
`promptVersion` VARCHAR(191) NOT NULL,
|
||||||
|
`inputDigest` JSON NOT NULL,
|
||||||
|
`outputDigest` JSON NULL,
|
||||||
|
`tokenEstimate` INTEGER NULL,
|
||||||
|
`status` VARCHAR(191) NOT NULL,
|
||||||
|
`errorSummary` TEXT NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`completedAt` DATETIME(3) NULL,
|
||||||
|
|
||||||
|
INDEX `agent_invocations_purpose_createdAt_idx`(`purpose`, `createdAt`),
|
||||||
|
INDEX `agent_invocations_deployRunId_createdAt_idx`(`deployRunId`, `createdAt`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `outbox_messages` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`deployRunId` VARCHAR(191) NULL,
|
||||||
|
`channel` VARCHAR(191) NOT NULL,
|
||||||
|
`template` VARCHAR(191) NOT NULL,
|
||||||
|
`payload` JSON NOT NULL,
|
||||||
|
`status` ENUM('PENDING', 'SENT', 'FAILED', 'DEAD') NOT NULL DEFAULT 'PENDING',
|
||||||
|
`attemptCount` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`nextAttemptAt` DATETIME(3) NULL,
|
||||||
|
`lastError` TEXT NULL,
|
||||||
|
`idempotencyKey` VARCHAR(191) NOT NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE INDEX `outbox_messages_idempotencyKey_key`(`idempotencyKey`),
|
||||||
|
INDEX `outbox_messages_status_nextAttemptAt_idx`(`status`, `nextAttemptAt`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `environments` ADD CONSTRAINT `environments_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `projects`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `deploy_jobs` ADD CONSTRAINT `deploy_jobs_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `projects`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `deploy_jobs` ADD CONSTRAINT `deploy_jobs_environmentId_fkey` FOREIGN KEY (`environmentId`) REFERENCES `environments`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `deploy_runs` ADD CONSTRAINT `deploy_runs_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `projects`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `deploy_runs` ADD CONSTRAINT `deploy_runs_environmentId_fkey` FOREIGN KEY (`environmentId`) REFERENCES `environments`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `deploy_runs` ADD CONSTRAINT `deploy_runs_jobId_fkey` FOREIGN KEY (`jobId`) REFERENCES `deploy_jobs`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `run_steps` ADD CONSTRAINT `run_steps_deployRunId_fkey` FOREIGN KEY (`deployRunId`) REFERENCES `deploy_runs`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `audit_logs` ADD CONSTRAINT `audit_logs_deployRunId_fkey` FOREIGN KEY (`deployRunId`) REFERENCES `deploy_runs`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `project_member_permissions` ADD CONSTRAINT `project_member_permissions_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `project_member_permissions` ADD CONSTRAINT `project_member_permissions_projectId_fkey` FOREIGN KEY (`projectId`) REFERENCES `projects`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `platform_messages` ADD CONSTRAINT `platform_messages_requesterId_fkey` FOREIGN KEY (`requesterId`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `platform_messages` ADD CONSTRAINT `platform_messages_handledById_fkey` FOREIGN KEY (`handledById`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `agent_invocations` ADD CONSTRAINT `agent_invocations_deployRunId_fkey` FOREIGN KEY (`deployRunId`) REFERENCES `deploy_runs`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `outbox_messages` ADD CONSTRAINT `outbox_messages_deployRunId_fkey` FOREIGN KEY (`deployRunId`) REFERENCES `deploy_runs`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -1,68 +1,3 @@
|
|||||||
CREATE TABLE `users` (
|
-- This migration was executed manually before Prisma migration history was
|
||||||
`id` VARCHAR(191) NOT NULL,
|
-- enabled in production. The current baseline migration already contains these
|
||||||
`account` VARCHAR(191) NOT NULL,
|
-- tables, so this directory is intentionally kept as a no-op history marker.
|
||||||
`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;
|
|
||||||
|
|||||||
@@ -9,4 +9,8 @@
|
|||||||
- 部署前先执行迁移检查,再启动 API 服务。
|
- 部署前先执行迁移检查,再启动 API 服务。
|
||||||
- 原生 SQL 只允许作为例外使用,必须放在 Repository 方法中并说明原因。
|
- 原生 SQL 只允许作为例外使用,必须放在 Repository 方法中并说明原因。
|
||||||
|
|
||||||
|
`20260611000000_baseline_current_schema` 是当前线上 schema 的基线迁移。生产库已经存在这些表时,Jenkins 部署脚本会先执行 `prisma migrate resolve --applied` 标记基线,再运行 `prisma migrate deploy`。
|
||||||
|
|
||||||
|
`20260611120000_auth_members_messages` 是生产手动执行过的历史迁移,现在保留为 no-op 历史标记;原始 SQL 留存在 `prisma/manual-migrations/20260611120000_auth_members_messages.sql`。
|
||||||
|
|
||||||
首阶段 schema 定义在 `prisma/schema.prisma`。没有明确数据库连接前,不要求本地自动执行迁移。
|
首阶段 schema 定义在 `prisma/schema.prisma`。没有明确数据库连接前,不要求本地自动执行迁移。
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system.
|
||||||
|
provider = "mysql"
|
||||||
Reference in New Issue
Block a user