feat: expand commercial ai drama platform

This commit is contained in:
xz
2026-08-31 21:09:48 +08:00
parent f06e583c9b
commit 8caf947b80
35 changed files with 10181 additions and 188 deletions
+319
View File
@@ -0,0 +1,319 @@
import { copyFile, mkdir, readdir, rm } from "node:fs/promises";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { dbAll, dbPath, dbRun, withTransaction } from "../server/db.mjs";
const projectRoot = resolve(import.meta.dirname, "..");
const storageRoot = resolve(projectRoot, "storage");
const backupsRoot = resolve(projectRoot, "data", "backups");
const dryRun = process.argv.includes("--dry-run");
const keep = {
userIds: ["u-owner", "u-producer", "u-writer", "u-art", "u-voice", "u-review", "u-local-worker"],
notificationIds: ["user-notification-seed-review", "user-notification-seed-policy"],
usageIds: ["usage-seed-001"],
auditIds: ["aud-001", "aud-002", "aud-003", "aud-004"],
modelCatalogIds: [
"catalog-qwen-image-2d",
"catalog-wan-i2v",
"catalog-qwen-parser",
"catalog-bge-m3",
"catalog-indextts",
"catalog-paraformer",
"catalog-northstar-image"
],
modelRouteIds: [
"route-script-ingest",
"route-script-materialize",
"route-image-keyframe",
"route-video-clip",
"route-voice-tts",
"route-voice-asr"
],
knowledgeDocumentIds: ["knowledge-rain-night"],
knowledgeContextPackIds: ["knowledge-pack-rain-night-opening"],
scriptIds: ["script-thunder-mouth-v1"],
shotIds: ["shot-01", "shot-02", "shot-03"],
assetIds: [
"asset-character-chen-yu",
"asset-character-tang-xia",
"asset-location-metro-canopy-rain",
"asset-prop-blue-umbrella",
"asset-prop-yellow-poncho",
"asset-prop-warning-cones",
"asset-prop-phone",
"asset-voice-chen-yu",
"asset-voice-tang-xia",
"asset-style-cel-shading",
"asset-lora-rain-local"
],
jobIds: ["job-img-001", "job-i2v-002", "job-tts-003", "job-edit-004", "job-asr-005"],
apiClientIds: ["client-local-runner"]
};
const summary = {
db: {},
files: []
};
class DryRunRollback extends Error {
constructor() {
super("dry-run rollback");
}
}
function stamp() {
return new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "").replace("T", "-");
}
function placeholders(values) {
return values.map(() => "?").join(", ");
}
function record(table, changes) {
if (!changes) return;
summary.db[table] = (summary.db[table] || 0) + changes;
}
function deleteWhere(table, where, params = []) {
const result = dbRun(`DELETE FROM ${table} WHERE ${where}`, params);
record(table, Number(result.changes || 0));
}
function deleteExceptIds(table, ids, column = "id") {
deleteWhere(table, `${column} NOT IN (${placeholders(ids)})`, ids);
}
function selectIds(sql, params = []) {
return dbAll(sql, params).map((row) => row.id);
}
async function backupDatabase() {
await mkdir(backupsRoot, { recursive: true });
dbRun("PRAGMA wal_checkpoint(FULL)");
const backupPath = resolve(backupsRoot, `platform-demo-noise-snapshot-${stamp()}.sqlite`);
if (!dryRun) await copyFile(dbPath, backupPath);
return backupPath;
}
async function removePath(path) {
if (!path || !existsSync(path)) return;
if (!dryRun) await rm(path, { recursive: true, force: true });
summary.files.push(path);
}
async function removeChildrenByPrefix(rootPath, prefix) {
if (!existsSync(rootPath)) return;
const entries = await readdir(rootPath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.name.startsWith(prefix)) continue;
await removePath(resolve(rootPath, entry.name));
}
}
async function walkAndRemove(rootPath, matcher) {
if (!existsSync(rootPath)) return;
const entries = await readdir(rootPath, { withFileTypes: true });
for (const entry of entries) {
const current = resolve(rootPath, entry.name);
if (entry.isDirectory()) {
await walkAndRemove(current, matcher);
continue;
}
if (matcher(entry.name, current)) await removePath(current);
}
}
function listRelativePathsForJobs(jobIds) {
if (!jobIds.length) return [];
return dbAll(
`SELECT output_path
FROM generation_jobs
WHERE id IN (${placeholders(jobIds)})`,
jobIds
)
.map((row) => String(row.output_path || "").trim())
.filter(Boolean);
}
const smokeDeviceIds = selectIds("SELECT id FROM auth_devices WHERE label LIKE '%Smoke Browser%' OR label LIKE '%smoke%'");
const tempUserIds = selectIds(
`SELECT id
FROM users
WHERE id NOT IN (${placeholders(keep.userIds)})
OR email LIKE 'mfa-%@local.test'
OR email LIKE '%smoke%@local.test'
OR display_name LIKE '%测试%'`,
keep.userIds
);
const tempReviewIds = selectIds(
`SELECT id
FROM reviews
WHERE shot_id NOT IN (${placeholders(keep.shotIds)})`,
keep.shotIds
);
const tempJobIds = selectIds(
`SELECT id
FROM generation_jobs
WHERE id NOT IN (${placeholders(keep.jobIds)})
OR kind IN ('单句 TTS 试听', '回归队列任务', '依赖测试关键帧', '依赖测试任务')
OR output_path LIKE 'voices/auditions/%'
OR output_path LIKE '%pending-output%'`,
keep.jobIds
);
const tempAssetIds = selectIds(
`SELECT id
FROM assets
WHERE id NOT IN (${placeholders(keep.assetIds)})
OR name LIKE 'content-regression%'
OR name LIKE '%smoke%'`,
keep.assetIds
);
const tempCompositionIds = selectIds(
`SELECT id
FROM media_compositions
WHERE source_json LIKE '%missing-a.mp4%'
OR source_json LIKE '%missing-b.mp4%'
OR version LIKE 'local-%'`
);
const tempDeliveryIds = selectIds(
`SELECT id
FROM deliveries
WHERE version LIKE 'smoke-%'
OR version LIKE 'v-regression%'
OR manifest_path LIKE '%smoke%'
OR manifest_path LIKE '%regression%'`
);
const tempKnowledgeIds = selectIds(
`SELECT id
FROM knowledge_documents
WHERE id NOT IN (${placeholders(keep.knowledgeDocumentIds)})
OR title LIKE '接口验收%'`,
keep.knowledgeDocumentIds
);
const tempKnowledgePackIds = selectIds(
`SELECT id
FROM knowledge_context_packs
WHERE id NOT IN (${placeholders(keep.knowledgeContextPackIds)})
OR name LIKE '接口验收%'
OR name LIKE '回归%'`,
keep.knowledgeContextPackIds
);
const tempScriptIds = selectIds(
`SELECT id
FROM script_documents
WHERE id NOT IN (${placeholders(keep.scriptIds)})
OR title LIKE '接口验收%'
OR title LIKE '自动化验收%'
OR title LIKE '回归%'`,
keep.scriptIds
);
const tempJobOutputPaths = listRelativePathsForJobs(tempJobIds);
const backupPath = await backupDatabase();
try {
withTransaction(() => {
deleteWhere("notification_deliveries", "1 = 1");
deleteExceptIds("user_notifications", keep.notificationIds);
deleteExceptIds("usage_events", keep.usageIds);
deleteExceptIds("audit_logs", keep.auditIds);
deleteWhere("invitations", "1 = 1");
deleteExceptIds("api_clients", keep.apiClientIds);
deleteWhere("oidc_login_states", "1 = 1");
deleteWhere("saml_login_states", "1 = 1");
deleteWhere("auth_sso_tickets", "1 = 1");
deleteWhere("auth_mfa_challenges", "1 = 1");
deleteWhere("auth_mfa_enrollment_challenges", "1 = 1");
if (smokeDeviceIds.length) {
deleteWhere("auth_sessions", `device_id IN (${placeholders(smokeDeviceIds)})`, smokeDeviceIds);
deleteWhere("auth_devices", `id IN (${placeholders(smokeDeviceIds)})`, smokeDeviceIds);
}
deleteWhere("auth_sessions", "revoked_at IS NOT NULL OR julianday(expires_at) < julianday('now')");
if (tempUserIds.length) {
deleteWhere("auth_sessions", `user_id IN (${placeholders(tempUserIds)})`, tempUserIds);
deleteWhere("user_mfa_methods", `user_id IN (${placeholders(tempUserIds)})`, tempUserIds);
deleteWhere("auth_devices", `user_id IN (${placeholders(tempUserIds)})`, tempUserIds);
deleteWhere("organization_members", `user_id IN (${placeholders(tempUserIds)})`, tempUserIds);
deleteWhere("workspace_members", `user_id IN (${placeholders(tempUserIds)})`, tempUserIds);
deleteWhere("project_members", `user_id IN (${placeholders(tempUserIds)})`, tempUserIds);
deleteWhere("user_credentials", `user_id IN (${placeholders(tempUserIds)})`, tempUserIds);
deleteWhere("users", `id IN (${placeholders(tempUserIds)})`, tempUserIds);
}
deleteWhere("auth_security_events", "1 = 1");
deleteWhere("auth_devices", "id NOT IN (SELECT DISTINCT device_id FROM auth_sessions WHERE device_id IS NOT NULL)");
deleteWhere("review_comments", "1 = 1");
if (tempReviewIds.length) deleteWhere("reviews", `id IN (${placeholders(tempReviewIds)})`, tempReviewIds);
deleteWhere("delivery_access_feedback", "1 = 1");
deleteWhere("delivery_access_events", "1 = 1");
deleteWhere("delivery_access_links", "1 = 1");
deleteWhere("delivery_batch_items", "1 = 1");
deleteWhere("delivery_batches", "1 = 1");
deleteWhere("delivery_releases", "1 = 1");
if (tempDeliveryIds.length) deleteWhere("deliveries", `id IN (${placeholders(tempDeliveryIds)})`, tempDeliveryIds);
else deleteWhere("deliveries", "version LIKE 'smoke-%' OR version LIKE 'v-regression%'");
deleteWhere("media_compositions", "1 = 1");
if (tempJobIds.length) {
deleteWhere("job_attempts", `job_id IN (${placeholders(tempJobIds)})`, tempJobIds);
deleteWhere(
"job_dependencies",
`job_id IN (${placeholders(tempJobIds)}) OR depends_on_job_id IN (${placeholders(tempJobIds)})`,
[...tempJobIds, ...tempJobIds]
);
deleteWhere("generation_jobs", `id IN (${placeholders(tempJobIds)})`, tempJobIds);
}
deleteWhere("voice_lines", `shot_id NOT IN (${placeholders(keep.shotIds)})`, keep.shotIds);
deleteWhere("asset_bindings", `shot_id NOT IN (${placeholders(keep.shotIds)}) OR asset_id NOT IN (${placeholders(keep.assetIds)})`, [...keep.shotIds, ...keep.assetIds]);
deleteWhere("shot_versions", `shot_id NOT IN (${placeholders(keep.shotIds)})`, keep.shotIds);
deleteWhere("shots", `id NOT IN (${placeholders(keep.shotIds)})`, keep.shotIds);
if (tempAssetIds.length) {
deleteWhere("asset_versions", `asset_id IN (${placeholders(tempAssetIds)})`, tempAssetIds);
deleteWhere("assets", `id IN (${placeholders(tempAssetIds)})`, tempAssetIds);
}
if (tempKnowledgePackIds.length) deleteWhere("knowledge_context_packs", `id IN (${placeholders(tempKnowledgePackIds)})`, tempKnowledgePackIds);
if (tempKnowledgeIds.length) {
deleteWhere("knowledge_governance_reviews", `document_id IN (${placeholders(tempKnowledgeIds)})`, tempKnowledgeIds);
deleteWhere("knowledge_document_versions", `document_id IN (${placeholders(tempKnowledgeIds)})`, tempKnowledgeIds);
deleteWhere("knowledge_chunks", `document_id IN (${placeholders(tempKnowledgeIds)})`, tempKnowledgeIds);
deleteWhere("knowledge_documents", `id IN (${placeholders(tempKnowledgeIds)})`, tempKnowledgeIds);
}
if (tempScriptIds.length) deleteWhere("script_documents", `id IN (${placeholders(tempScriptIds)})`, tempScriptIds);
deleteExceptIds("model_routing_policies", keep.modelRouteIds);
deleteExceptIds("model_catalog_entries", keep.modelCatalogIds);
if (dryRun) throw new DryRunRollback();
});
} catch (error) {
if (!(error instanceof DryRunRollback)) throw error;
}
await removeChildrenByPrefix(resolve(storageRoot, "releases", "thunder-mouth"), "smoke-");
await removeChildrenByPrefix(resolve(storageRoot, "deliveries", "thunder-mouth"), "smoke-");
await removeChildrenByPrefix(resolve(storageRoot, "compositions"), "");
await walkAndRemove(resolve(storageRoot, "assets"), (name) => name.startsWith("content-regression") || name === "smoke-reference.txt");
for (const relativePath of tempJobOutputPaths) {
await removePath(resolve(projectRoot, relativePath));
}
const report = {
dryRun,
backupPath,
removedTables: summary.db,
removedFiles: summary.files.length,
sampleFiles: summary.files.slice(0, 10)
};
console.log(JSON.stringify(report, null, 2));