补齐数据库迁移链路并收紧初始化默认值
This commit is contained in:
parent
25316809db
commit
1aeadcc425
143
prisma/migrations/20251026015433_add_material_type/migration.sql
Normal file
143
prisma/migrations/20251026015433_add_material_type/migration.sql
Normal file
@ -0,0 +1,143 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"role" "Role" NOT NULL DEFAULT 'USER',
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT false,
|
||||
"canDownload" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "materials" (
|
||||
"id" TEXT NOT NULL,
|
||||
"previewUrl" TEXT NOT NULL,
|
||||
"productModel" TEXT NOT NULL,
|
||||
"categoryName" TEXT NOT NULL,
|
||||
"sceneName" TEXT NOT NULL,
|
||||
"materialType" TEXT NOT NULL DEFAULT '3DMAX',
|
||||
"renderer" TEXT NOT NULL DEFAULT 'CR',
|
||||
"authorName" TEXT NOT NULL,
|
||||
"zipFileName" TEXT NOT NULL,
|
||||
"zipFilePath" TEXT NOT NULL,
|
||||
"zipFileSize" BIGINT NOT NULL DEFAULT 0,
|
||||
"downloadCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"likeCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "materials_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "material_likes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"materialId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "material_likes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "categories" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "categories_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "scenes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "scenes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "system_settings" (
|
||||
"id" TEXT NOT NULL,
|
||||
"headerBgColor" TEXT NOT NULL DEFAULT '#1f2937',
|
||||
"logoUrl" TEXT NOT NULL DEFAULT 'http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png',
|
||||
"siteName" TEXT NOT NULL DEFAULT 'FLNXI素材库',
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "system_settings_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "users_username_key" ON "users"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "materials_userId_idx" ON "materials"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "materials_createdAt_idx" ON "materials"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "materials_categoryName_idx" ON "materials"("categoryName");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "materials_sceneName_idx" ON "materials"("sceneName");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "materials_materialType_idx" ON "materials"("materialType");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "materials_downloadCount_idx" ON "materials"("downloadCount");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "materials_likeCount_idx" ON "materials"("likeCount");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "material_likes_materialId_idx" ON "material_likes"("materialId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "material_likes_userId_idx" ON "material_likes"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "material_likes_materialId_userId_key" ON "material_likes"("materialId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "categories_name_key" ON "categories"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "scenes_name_key" ON "scenes"("name");
|
||||
|
||||
-- AddForeignKey(Postgres 不支持 IF NOT EXISTS,因此使用 DO 块捕获重复异常)
|
||||
DO $$
|
||||
BEGIN
|
||||
ALTER TABLE "materials" ADD CONSTRAINT "materials_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
ALTER TABLE "material_likes" ADD CONSTRAINT "material_likes_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "materials"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
ALTER TABLE "material_likes" ADD CONSTRAINT "material_likes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
@ -0,0 +1,50 @@
|
||||
-- 基线迁移:为上传模板体系创建基础表,便于后续增量迁移在 shadow 数据库中正常回放
|
||||
-- 使用 IF NOT EXISTS 保证在已有线上库上执行时是幂等、安全的
|
||||
|
||||
-- UploadTemplate:上传模板主表
|
||||
CREATE TABLE IF NOT EXISTS "UploadTemplate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'draft',
|
||||
"materialType" TEXT NOT NULL DEFAULT '其他',
|
||||
"materialKind" TEXT NOT NULL DEFAULT 'IMAGE',
|
||||
"version" INTEGER NOT NULL DEFAULT 1,
|
||||
"fieldGroups" TEXT,
|
||||
"displayConfig" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "UploadTemplate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- UploadField:模板字段表
|
||||
CREATE TABLE IF NOT EXISTS "UploadField" (
|
||||
"id" TEXT NOT NULL,
|
||||
"templateId" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"required" BOOLEAN NOT NULL DEFAULT false,
|
||||
"orderIndex" INTEGER NOT NULL DEFAULT 0,
|
||||
"layoutWidth" TEXT,
|
||||
"validations" TEXT,
|
||||
"optionsSource" TEXT,
|
||||
"optionsStatic" TEXT,
|
||||
"allowedExtensions" TEXT,
|
||||
"visibleOnHomeFilter" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isSearchable" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isReadOnly" BOOLEAN NOT NULL DEFAULT false,
|
||||
"addToCategory" BOOLEAN NOT NULL DEFAULT false,
|
||||
"conditions" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "UploadField_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- 关联关系:字段隶属于模板
|
||||
ALTER TABLE "UploadField"
|
||||
ADD CONSTRAINT "UploadField_templateId_fkey"
|
||||
FOREIGN KEY ("templateId") REFERENCES "UploadTemplate"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -0,0 +1,53 @@
|
||||
-- 确保上传模板/字段基础表存在(若之前迁移缺失时可兜底创建)
|
||||
CREATE TABLE IF NOT EXISTS "UploadTemplate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'draft',
|
||||
"materialType" TEXT NOT NULL DEFAULT '其他',
|
||||
"materialKind" TEXT NOT NULL DEFAULT 'IMAGE',
|
||||
"version" INTEGER NOT NULL DEFAULT 1,
|
||||
"fieldGroups" TEXT,
|
||||
"displayConfig" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "UploadTemplate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "UploadField" (
|
||||
"id" TEXT NOT NULL,
|
||||
"templateId" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"required" BOOLEAN NOT NULL DEFAULT false,
|
||||
"orderIndex" INTEGER NOT NULL DEFAULT 0,
|
||||
"layoutWidth" TEXT,
|
||||
"validations" TEXT,
|
||||
"optionsSource" TEXT,
|
||||
"optionsStatic" TEXT,
|
||||
"allowedExtensions" TEXT,
|
||||
"visibleOnHomeFilter" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isSearchable" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isReadOnly" BOOLEAN NOT NULL DEFAULT false,
|
||||
"addToCategory" BOOLEAN NOT NULL DEFAULT false,
|
||||
"conditions" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "UploadField_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
ALTER TABLE "UploadField"
|
||||
ADD CONSTRAINT "UploadField_templateId_fkey"
|
||||
FOREIGN KEY ("templateId") REFERENCES "UploadTemplate"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
-- AlterTable(幂等,确保列存在)
|
||||
ALTER TABLE "UploadField" ADD COLUMN IF NOT EXISTS "addToCategory" BOOLEAN NOT NULL DEFAULT false;
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "hiddenBuiltinTemplates" TEXT;
|
||||
@ -0,0 +1,69 @@
|
||||
-- 将 hiddenBuiltinTemplates 字段重命名为 enabledBuiltinTemplates
|
||||
-- 逻辑反转:之前存储隐藏的列表,现在存储启用的列表
|
||||
-- 默认全部启用(空数组或null表示全部启用)
|
||||
|
||||
-- 所有预制模板key
|
||||
DO $$
|
||||
DECLARE
|
||||
hidden_list TEXT;
|
||||
enabled_list TEXT;
|
||||
all_keys TEXT[] := ARRAY['3dmax-material', 'psd-material', 'ai-material', 'cad-material', 'c4d-material', 'video-material', 'texture-material', 'model-material', 'standard-accessory-material', 'other-material'];
|
||||
hidden_array TEXT[];
|
||||
enabled_array TEXT[];
|
||||
key_item TEXT;
|
||||
BEGIN
|
||||
-- 检查字段是否存在
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'system_settings'
|
||||
AND column_name = 'hiddenBuiltinTemplates'
|
||||
) THEN
|
||||
-- 获取当前的 hiddenBuiltinTemplates 值
|
||||
SELECT "hiddenBuiltinTemplates" INTO hidden_list FROM "system_settings" LIMIT 1;
|
||||
|
||||
-- 如果 hiddenBuiltinTemplates 存在且有值,转换为启用列表
|
||||
IF hidden_list IS NOT NULL AND hidden_list != '' THEN
|
||||
BEGIN
|
||||
-- 解析隐藏列表
|
||||
SELECT ARRAY(SELECT json_array_elements_text(hidden_list::json)) INTO hidden_array;
|
||||
|
||||
-- 将隐藏的列表转换为启用的列表(取反:所有key减去隐藏的key)
|
||||
enabled_array := ARRAY(
|
||||
SELECT unnest(all_keys)
|
||||
EXCEPT
|
||||
SELECT unnest(hidden_array)
|
||||
);
|
||||
|
||||
-- 转换为JSON数组
|
||||
enabled_list := COALESCE(
|
||||
(SELECT json_agg(value) FROM unnest(enabled_array) AS value),
|
||||
'[]'::json
|
||||
)::text;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
-- 解析失败,设置为空数组(表示全部启用)
|
||||
enabled_list := '[]';
|
||||
END;
|
||||
ELSE
|
||||
-- 如果没有隐藏列表,表示全部启用,设置为空数组
|
||||
enabled_list := '[]';
|
||||
END IF;
|
||||
|
||||
-- 重命名字段
|
||||
ALTER TABLE "system_settings"
|
||||
RENAME COLUMN "hiddenBuiltinTemplates" TO "enabledBuiltinTemplates";
|
||||
|
||||
-- 更新值
|
||||
UPDATE "system_settings"
|
||||
SET "enabledBuiltinTemplates" = enabled_list;
|
||||
ELSE
|
||||
-- 如果字段不存在,直接创建新字段
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'system_settings'
|
||||
AND column_name = 'enabledBuiltinTemplates'
|
||||
) THEN
|
||||
ALTER TABLE "system_settings"
|
||||
ADD COLUMN "enabledBuiltinTemplates" TEXT DEFAULT '[]';
|
||||
END IF;
|
||||
END IF;
|
||||
END $$;
|
||||
@ -0,0 +1,7 @@
|
||||
-- 添加 materialType 字段到上传模板,默认值为“其他”
|
||||
ALTER TABLE "UploadTemplate"
|
||||
ADD COLUMN IF NOT EXISTS "materialType" TEXT NOT NULL DEFAULT '其他';
|
||||
|
||||
-- 为已有数据回填 materialType(保持默认值)
|
||||
UPDATE "UploadTemplate"
|
||||
SET "materialType" = COALESCE(NULLIF("materialType", ''), '其他');
|
||||
@ -0,0 +1,3 @@
|
||||
-- 为 system_settings 表新增分类字段管理配置列
|
||||
ALTER TABLE "system_settings"
|
||||
ADD COLUMN IF NOT EXISTS "categoryManagedFields" TEXT;
|
||||
@ -0,0 +1,27 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "brandName" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "seriesName" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "skuCode" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "materialTexture" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "renderTime" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "imageResolution" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "colorMode" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "displayImages" TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "platformName" TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "previewUrls" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "metadata" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "templateId" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "materials_platformName_idx" ON "materials"("platformName");
|
||||
|
||||
-- AddForeignKey
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'materials_templateId_fkey'
|
||||
) THEN
|
||||
ALTER TABLE "materials" ADD CONSTRAINT "materials_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "UploadTemplate"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
@ -0,0 +1,38 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "FieldDefinition" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL DEFAULT 'select',
|
||||
"description" TEXT,
|
||||
"source" TEXT NOT NULL DEFAULT 'custom',
|
||||
"sourceTemplateIds" TEXT,
|
||||
"isStandard" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isFilterEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"filterDisplayName" TEXT,
|
||||
"filterOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FieldDefinition_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FieldOption" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fieldId" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"orderIndex" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FieldOption_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "FieldOption_fieldId_fkey" FOREIGN KEY ("fieldId") REFERENCES "FieldDefinition"("id") ON DELETE CASCADE ON UPDATE NO ACTION
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FieldDefinition_key_key" ON "FieldDefinition"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FieldOption_fieldId_value_key" ON "FieldOption"("fieldId", "value");
|
||||
@ -0,0 +1,9 @@
|
||||
-- 为素材表增加视频相关字段,用于后续视频预览 / 转码能力
|
||||
|
||||
ALTER TABLE "materials"
|
||||
ADD COLUMN "videoSourceUrl" TEXT,
|
||||
ADD COLUMN "videoPreview540Url" TEXT,
|
||||
ADD COLUMN "videoPreviewClipUrl" TEXT,
|
||||
ADD COLUMN "videoCoverUrl" TEXT,
|
||||
ADD COLUMN "videoDuration" INTEGER,
|
||||
ADD COLUMN "videoProcessingStatus" TEXT;
|
||||
@ -0,0 +1,11 @@
|
||||
-- 为上传模板增加 materialKind 字段,用于区分图片/视频等素材大类
|
||||
ALTER TABLE "UploadTemplate"
|
||||
ADD COLUMN IF NOT EXISTS "materialKind" TEXT NOT NULL DEFAULT 'IMAGE';
|
||||
|
||||
-- 依据 materialType 初步归类已有模板
|
||||
UPDATE "UploadTemplate"
|
||||
SET "materialKind" = CASE
|
||||
WHEN "materialType" = '视频' THEN 'VIDEO'
|
||||
WHEN "materialType" = '其他' THEN 'OTHER'
|
||||
ELSE 'IMAGE'
|
||||
END;
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "materialKind" TEXT NOT NULL DEFAULT 'IMAGE';
|
||||
@ -0,0 +1,18 @@
|
||||
-- Add ownership fields to materials for clean user/team attribution
|
||||
-- ownerType: 素材归属类型(USER / TEAM),默认 TEAM
|
||||
-- ownerId: 归属主体ID(团队空间ID或用户ID),默认 default-team
|
||||
-- createdByUserId: 最初上传者用户ID
|
||||
-- lastTransferredAt: 最近一次所有权移交时间
|
||||
|
||||
ALTER TABLE "materials"
|
||||
ADD COLUMN IF NOT EXISTS "ownerType" TEXT NOT NULL DEFAULT 'TEAM',
|
||||
ADD COLUMN IF NOT EXISTS "ownerId" TEXT NOT NULL DEFAULT 'default-team',
|
||||
ADD COLUMN IF NOT EXISTS "createdByUserId" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "lastTransferredAt" TIMESTAMPTZ;
|
||||
|
||||
-- 索引:加速基于归属的查询
|
||||
CREATE INDEX IF NOT EXISTS "materials_ownerType_ownerId_idx"
|
||||
ON "materials" ("ownerType", "ownerId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "materials_createdByUserId_idx"
|
||||
ON "materials" ("createdByUserId");
|
||||
@ -0,0 +1,291 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `logoUrl` on the `system_settings` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[dingtalkUserId]` on the table `users` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[phone]` on the table `users` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "FieldDefinition" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "FieldOption" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "UploadField" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "UploadTemplate" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "categories" ADD COLUMN "options" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "materials" ADD COLUMN "deletedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "isPremium" BOOLEAN NOT NULL DEFAULT false,
|
||||
ALTER COLUMN "zipFileName" SET DEFAULT '',
|
||||
ALTER COLUMN "zipFilePath" SET DEFAULT '',
|
||||
ALTER COLUMN "lastTransferredAt" SET DATA TYPE TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "scenes" ADD COLUMN "options" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "system_settings" DROP COLUMN "logoUrl",
|
||||
ADD COLUMN "enableAiImageEdit" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "enableRegistration" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "enableTunnelService" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "enableUserActivation" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "headerLogoUrl" TEXT NOT NULL DEFAULT 'http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png',
|
||||
ADD COLUMN "loginLogoUrl" TEXT NOT NULL DEFAULT 'http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png',
|
||||
ADD COLUMN "premiumRatio" DOUBLE PRECISION NOT NULL DEFAULT 10.0,
|
||||
ADD COLUMN "s3AccessKey" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "s3Bucket" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "s3Endpoint" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "s3PublicUrl" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "s3Region" TEXT NOT NULL DEFAULT 'us-east-1',
|
||||
ADD COLUMN "s3SecretKey" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "avatar" TEXT,
|
||||
ADD COLUMN "department" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN "dingtalkUserId" TEXT,
|
||||
ADD COLUMN "isSuperAdmin" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "loginType" TEXT NOT NULL DEFAULT 'normal',
|
||||
ADD COLUMN "managedDepartment" TEXT,
|
||||
ADD COLUMN "parentId" TEXT,
|
||||
ADD COLUMN "phone" TEXT,
|
||||
ADD COLUMN "realName" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FilterItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fieldKey" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"group" TEXT,
|
||||
"orderIndex" INTEGER NOT NULL DEFAULT 0,
|
||||
"multiSelect" BOOLEAN NOT NULL DEFAULT false,
|
||||
"showOnHome" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "FilterItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "material_favorites" (
|
||||
"id" TEXT NOT NULL,
|
||||
"materialId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "material_favorites_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "material_download_logs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"materialId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"downloadedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "material_download_logs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "material_logs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"materialId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"changes" JSONB,
|
||||
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "material_logs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "platforms" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"options" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "platforms_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "departments" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "departments_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "danmakus" (
|
||||
"id" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "danmakus_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "invitations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"usedBy" TEXT,
|
||||
"usedAt" TIMESTAMP(3),
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"maxUses" INTEGER NOT NULL DEFAULT 1,
|
||||
"currentUses" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "invitations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_favorites_materialId_idx" ON "material_favorites"("materialId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_favorites_userId_idx" ON "material_favorites"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "material_favorites_materialId_userId_key" ON "material_favorites"("materialId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_download_logs_materialId_idx" ON "material_download_logs"("materialId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_download_logs_userId_idx" ON "material_download_logs"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_download_logs_downloadedAt_idx" ON "material_download_logs"("downloadedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_download_logs_materialId_userId_downloadedAt_idx" ON "material_download_logs"("materialId", "userId", "downloadedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_logs_materialId_idx" ON "material_logs"("materialId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_logs_userId_idx" ON "material_logs"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "material_logs_timestamp_idx" ON "material_logs"("timestamp");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "platforms_name_key" ON "platforms"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "departments_name_key" ON "departments"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "danmakus_createdAt_idx" ON "danmakus"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "invitations_code_key" ON "invitations"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "invitations_code_idx" ON "invitations"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "invitations_createdBy_idx" ON "invitations"("createdBy");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FieldDefinition_source_idx" ON "FieldDefinition"("source");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FieldDefinition_isStandard_idx" ON "FieldDefinition"("isStandard");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FieldDefinition_isFilterEnabled_idx" ON "FieldDefinition"("isFilterEnabled");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FieldDefinition_filterOrder_idx" ON "FieldDefinition"("filterOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FieldDefinition_isFilterEnabled_filterOrder_idx" ON "FieldDefinition"("isFilterEnabled", "filterOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FieldOption_fieldId_idx" ON "FieldOption"("fieldId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FieldOption_fieldId_orderIndex_idx" ON "FieldOption"("fieldId", "orderIndex");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadField_templateId_idx" ON "UploadField"("templateId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadField_key_idx" ON "UploadField"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadField_type_idx" ON "UploadField"("type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadField_visibleOnHomeFilter_idx" ON "UploadField"("visibleOnHomeFilter");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadField_isSearchable_idx" ON "UploadField"("isSearchable");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadField_addToCategory_idx" ON "UploadField"("addToCategory");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadField_orderIndex_idx" ON "UploadField"("orderIndex");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadField_templateId_orderIndex_idx" ON "UploadField"("templateId", "orderIndex");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadTemplate_status_idx" ON "UploadTemplate"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadTemplate_materialType_idx" ON "UploadTemplate"("materialType");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadTemplate_materialKind_idx" ON "UploadTemplate"("materialKind");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadTemplate_createdAt_idx" ON "UploadTemplate"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UploadTemplate_updatedAt_idx" ON "UploadTemplate"("updatedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "materials_deletedAt_idx" ON "materials"("deletedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_dingtalkUserId_key" ON "users"("dingtalkUserId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_phone_key" ON "users"("phone");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "material_favorites" ADD CONSTRAINT "material_favorites_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "materials"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "material_favorites" ADD CONSTRAINT "material_favorites_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "material_download_logs" ADD CONSTRAINT "material_download_logs_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "materials"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "material_download_logs" ADD CONSTRAINT "material_download_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "material_logs" ADD CONSTRAINT "material_logs_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "materials"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "material_logs" ADD CONSTRAINT "material_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -0,0 +1,13 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "system_configs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "system_configs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "system_configs_key_key" ON "system_configs"("key");
|
||||
@ -0,0 +1,5 @@
|
||||
-- 添加音频素材相关字段
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "audioDuration" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "audioCategory" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "audioScene" TEXT;
|
||||
ALTER TABLE "materials" ADD COLUMN IF NOT EXISTS "audioUrl" TEXT;
|
||||
@ -0,0 +1,14 @@
|
||||
-- 默认 Logo 不再指向旧对象存储,避免新安装继承旧环境地址
|
||||
ALTER TABLE "system_settings"
|
||||
ALTER COLUMN "headerLogoUrl" SET DEFAULT '/logo/logo2.png';
|
||||
|
||||
ALTER TABLE "system_settings"
|
||||
ALTER COLUMN "loginLogoUrl" SET DEFAULT '/logo/logo2.png';
|
||||
|
||||
UPDATE "system_settings"
|
||||
SET "headerLogoUrl" = '/logo/logo2.png'
|
||||
WHERE "headerLogoUrl" LIKE 'http://s3.1080p.hk/%';
|
||||
|
||||
UPDATE "system_settings"
|
||||
SET "loginLogoUrl" = '/logo/logo2.png'
|
||||
WHERE "loginLogoUrl" LIKE 'http://s3.1080p.hk/%';
|
||||
@ -0,0 +1,14 @@
|
||||
-- 默认 Logo 不再指向不存在的公共静态资源,避免新安装和旧数据继承死路径
|
||||
ALTER TABLE "system_settings"
|
||||
ALTER COLUMN "headerLogoUrl" SET DEFAULT '';
|
||||
|
||||
ALTER TABLE "system_settings"
|
||||
ALTER COLUMN "loginLogoUrl" SET DEFAULT '';
|
||||
|
||||
UPDATE "system_settings"
|
||||
SET "headerLogoUrl" = ''
|
||||
WHERE "headerLogoUrl" IN ('/logo/logo1.png', '/logo/logo2.png', '/logo/logo1', '/logo/logo2');
|
||||
|
||||
UPDATE "system_settings"
|
||||
SET "loginLogoUrl" = ''
|
||||
WHERE "loginLogoUrl" IN ('/logo/logo1.png', '/logo/logo2.png', '/logo/logo1', '/logo/logo2');
|
||||
@ -79,6 +79,7 @@ model MaterialShareToken {
|
||||
@@index([revokedAt])
|
||||
@@map("material_share_tokens")
|
||||
}
|
||||
|
||||
model UploadTemplate {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
@ -372,8 +373,8 @@ model SystemSettings {
|
||||
premiumRatio Float @default(10.0)
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
headerLogoUrl String @default("http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png")
|
||||
loginLogoUrl String @default("http://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png")
|
||||
headerLogoUrl String @default("")
|
||||
loginLogoUrl String @default("")
|
||||
s3AccessKey String @default("")
|
||||
s3Bucket String @default("")
|
||||
s3Endpoint String @default("")
|
||||
|
||||
159
prisma/seed.ts
159
prisma/seed.ts
@ -17,113 +17,13 @@
|
||||
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import {
|
||||
BUILTIN_TEMPLATE_KEYS,
|
||||
createBuiltinTemplate,
|
||||
} from '../lib/upload-template-presets'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
// ============================================================================
|
||||
// 内置模板配置(与 lib/system-integrity.ts 保持一致)
|
||||
// ============================================================================
|
||||
|
||||
const BUILTIN_TEMPLATE_KEYS = [
|
||||
'3dmax-material',
|
||||
'psd-material',
|
||||
'audio-material',
|
||||
'video-material',
|
||||
'model-material',
|
||||
'other-material',
|
||||
] as const
|
||||
|
||||
type BuiltinTemplateKey = (typeof BUILTIN_TEMPLATE_KEYS)[number]
|
||||
|
||||
const BUILTIN_TEMPLATE_CONFIG: Record<
|
||||
BuiltinTemplateKey,
|
||||
{
|
||||
name: string
|
||||
description: string
|
||||
materialType: string
|
||||
materialKind: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'
|
||||
fields: Array<{
|
||||
key: string
|
||||
label: string
|
||||
type: string
|
||||
required: boolean
|
||||
layoutWidth: string
|
||||
visibleOnHomeFilter: boolean
|
||||
isSearchable: boolean
|
||||
addToCategory?: boolean
|
||||
}>
|
||||
}
|
||||
> = {
|
||||
'3dmax-material': {
|
||||
name: '3DMAX 素材模板',
|
||||
description: '适用于 3D 模型类素材',
|
||||
materialType: '3DMAX',
|
||||
materialKind: 'IMAGE',
|
||||
fields: [
|
||||
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
|
||||
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'renderer', label: '渲染器', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true, addToCategory: true },
|
||||
],
|
||||
},
|
||||
'psd-material': {
|
||||
name: 'PSD 素材模板',
|
||||
description: '针对平面设计 PSD 素材',
|
||||
materialType: 'PSD',
|
||||
materialKind: 'IMAGE',
|
||||
fields: [
|
||||
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
|
||||
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
],
|
||||
},
|
||||
'audio-material': {
|
||||
name: '音频素材模板',
|
||||
description: '适用于音频文件素材,支持 MP3、WAV、FLAC、AAC 等格式',
|
||||
materialType: '音频',
|
||||
materialKind: 'AUDIO',
|
||||
fields: [
|
||||
{ key: 'productModel', label: '音频名称', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
|
||||
{ key: 'audioCategory', label: '音频分类', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'audioScene', label: '使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'authorName', label: '作者', type: 'text', required: false, layoutWidth: 'half', visibleOnHomeFilter: false, isSearchable: true },
|
||||
],
|
||||
},
|
||||
'video-material': {
|
||||
name: '视频素材模板',
|
||||
description: '适用于视频文件素材',
|
||||
materialType: '视频',
|
||||
materialKind: 'VIDEO',
|
||||
fields: [
|
||||
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
|
||||
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
],
|
||||
},
|
||||
'model-material': {
|
||||
name: '模特素材模板',
|
||||
description: '用于模特/人像素材',
|
||||
materialType: '模特',
|
||||
materialKind: 'IMAGE',
|
||||
fields: [
|
||||
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
|
||||
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
],
|
||||
},
|
||||
'other-material': {
|
||||
name: '其他素材模板',
|
||||
description: '适用于其他类型的素材',
|
||||
materialType: '其他',
|
||||
materialKind: 'OTHER',
|
||||
fields: [
|
||||
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
|
||||
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 种子函数
|
||||
// ============================================================================
|
||||
@ -134,8 +34,8 @@ async function seedBuiltinTemplates() {
|
||||
let skipped = 0
|
||||
|
||||
for (const key of BUILTIN_TEMPLATE_KEYS) {
|
||||
const templateId = `builtin-${key}`
|
||||
const config = BUILTIN_TEMPLATE_CONFIG[key]
|
||||
const template = createBuiltinTemplate(key)
|
||||
const templateId = template.id
|
||||
|
||||
// 检查是否已存在
|
||||
const existing = await prisma.uploadTemplate.findUnique({
|
||||
@ -153,17 +53,17 @@ async function seedBuiltinTemplates() {
|
||||
await tx.uploadTemplate.create({
|
||||
data: {
|
||||
id: templateId,
|
||||
name: config.name,
|
||||
description: config.description,
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
status: 'active',
|
||||
materialType: config.materialType,
|
||||
materialKind: config.materialKind,
|
||||
materialType: template.materialType,
|
||||
materialKind: template.materialKind,
|
||||
version: 1,
|
||||
},
|
||||
})
|
||||
|
||||
for (let i = 0; i < config.fields.length; i++) {
|
||||
const field = config.fields[i]
|
||||
for (let i = 0; i < template.fields.length; i++) {
|
||||
const field = template.fields[i]
|
||||
await tx.uploadField.create({
|
||||
data: {
|
||||
templateId,
|
||||
@ -191,8 +91,8 @@ async function seedBuiltinTemplates() {
|
||||
async function seedDefaultAdmin() {
|
||||
console.log('正在检查默认管理员...')
|
||||
|
||||
const adminEmail = process.env.INIT_ADMIN_EMAIL || 'admin@flnxi.com'
|
||||
const adminPassword = process.env.INIT_ADMIN_PASSWORD || '88888888'
|
||||
const adminEmail = process.env.INIT_ADMIN_EMAIL || 'admin@local.flnxi'
|
||||
const configuredPassword = process.env.INIT_ADMIN_PASSWORD?.trim()
|
||||
|
||||
// 检查是否已有管理员
|
||||
const existingAdmin = await prisma.user.findFirst({
|
||||
@ -204,8 +104,14 @@ async function seedDefaultAdmin() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!configuredPassword) {
|
||||
console.log(' 跳过: 未设置 INIT_ADMIN_PASSWORD,不在 seed 日志中生成或输出管理员密码')
|
||||
console.log(' 请通过系统激活/初始化流程生成一次性管理员密码,或设置 INIT_ADMIN_PASSWORD 后重新执行 seed')
|
||||
return
|
||||
}
|
||||
|
||||
// 创建默认管理员
|
||||
const hashedPassword = await bcrypt.hash(adminPassword, 10)
|
||||
const hashedPassword = await bcrypt.hash(configuredPassword, 10)
|
||||
const admin = await prisma.user.create({
|
||||
data: {
|
||||
email: adminEmail,
|
||||
@ -220,6 +126,29 @@ async function seedDefaultAdmin() {
|
||||
})
|
||||
|
||||
console.log(` 创建: 管理员 ${admin.email}`)
|
||||
|
||||
await prisma.systemConfig.upsert({
|
||||
where: { key: 'initial_admin_state' },
|
||||
update: {
|
||||
value: JSON.stringify({
|
||||
userId: admin.id,
|
||||
email: admin.email,
|
||||
mustChangePassword: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
}),
|
||||
},
|
||||
create: {
|
||||
key: 'initial_admin_state',
|
||||
value: JSON.stringify({
|
||||
userId: admin.id,
|
||||
email: admin.email,
|
||||
mustChangePassword: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
console.log(' 初始管理员密码来源: 环境变量 INIT_ADMIN_PASSWORD(已写入哈希,不在日志中输出明文)')
|
||||
}
|
||||
|
||||
async function seedDefaultData() {
|
||||
|
||||
@ -9,74 +9,14 @@
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import {
|
||||
BUILTIN_TEMPLATE_KEYS,
|
||||
BUILTIN_TEMPLATE_NAMES,
|
||||
createBuiltinTemplate,
|
||||
} from '../lib/upload-template-presets'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
// 内置模板配置(直接定义,避免导入问题)
|
||||
const BUILTIN_TEMPLATE_KEYS = [
|
||||
'3dmax-material',
|
||||
'psd-material',
|
||||
'audio-material',
|
||||
'video-material',
|
||||
'model-material',
|
||||
'other-material',
|
||||
] as const
|
||||
|
||||
const BUILTIN_TEMPLATE_NAMES: Record<string, string> = {
|
||||
'3dmax-material': '3DMAX 素材模板',
|
||||
'psd-material': 'PSD 素材模板',
|
||||
'audio-material': '音频素材模板',
|
||||
'video-material': '视频素材模板',
|
||||
'model-material': '模特素材模板',
|
||||
'other-material': '其他素材模板',
|
||||
}
|
||||
|
||||
const BUILTIN_TEMPLATE_DESCRIPTIONS: Record<string, string> = {
|
||||
'3dmax-material': '适用于 3D 模型类素材',
|
||||
'psd-material': '针对平面设计 PSD 素材',
|
||||
'audio-material': '适用于音频文件素材,支持 MP3、WAV、FLAC、AAC 等格式',
|
||||
'video-material': '适用于视频文件素材',
|
||||
'model-material': '用于模特/人像素材',
|
||||
'other-material': '适用于其他类型的素材',
|
||||
}
|
||||
|
||||
const BUILTIN_TEMPLATE_TYPES: Record<string, string> = {
|
||||
'3dmax-material': '3DMAX',
|
||||
'psd-material': 'PSD',
|
||||
'audio-material': '音频',
|
||||
'video-material': '视频',
|
||||
'model-material': '模特',
|
||||
'other-material': '其他',
|
||||
}
|
||||
|
||||
const BUILTIN_TEMPLATE_KINDS: Record<string, string> = {
|
||||
'3dmax-material': 'IMAGE',
|
||||
'psd-material': 'IMAGE',
|
||||
'audio-material': 'AUDIO',
|
||||
'video-material': 'VIDEO',
|
||||
'model-material': 'IMAGE',
|
||||
'other-material': 'OTHER',
|
||||
}
|
||||
|
||||
const BUILTIN_TEMPLATE_FIELDS: Record<string, string[]> = {
|
||||
'3dmax-material': ['productModel', 'categoryName', 'sceneName', 'renderer'],
|
||||
'psd-material': ['productModel', 'categoryName', 'sceneName'],
|
||||
'audio-material': ['productModel', 'audioCategory', 'audioScene', 'authorName'],
|
||||
'video-material': ['productModel', 'categoryName', 'sceneName'],
|
||||
'model-material': ['productModel', 'categoryName', 'sceneName'],
|
||||
'other-material': ['productModel', 'categoryName', 'sceneName'],
|
||||
}
|
||||
|
||||
const STANDARD_FIELDS: Record<string, any> = {
|
||||
productModel: { key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
|
||||
categoryName: { key: 'categoryName', label: '使用店铺', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
sceneName: { key: 'sceneName', label: '场景类别', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
renderer: { key: 'renderer', label: '渲染器', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true, addToCategory: true },
|
||||
audioCategory: { key: 'audioCategory', label: '音频分类', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
audioScene: { key: 'audioScene', label: '使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
authorName: { key: 'authorName', label: '作者', type: 'text', required: false, layoutWidth: 'half', visibleOnHomeFilter: false, isSearchable: true },
|
||||
}
|
||||
|
||||
async function fixMissingTemplates() {
|
||||
console.log('开始修复缺失的模板记录...\n')
|
||||
|
||||
@ -112,24 +52,21 @@ async function fixMissingTemplates() {
|
||||
console.log('\n步骤 2: 创建缺失的内置模板...')
|
||||
|
||||
for (const key of missingKeys) {
|
||||
const templateId = `builtin-${key}`
|
||||
const name = BUILTIN_TEMPLATE_NAMES[key]
|
||||
const description = BUILTIN_TEMPLATE_DESCRIPTIONS[key]
|
||||
const materialType = BUILTIN_TEMPLATE_TYPES[key]
|
||||
const materialKind = BUILTIN_TEMPLATE_KINDS[key]
|
||||
const fieldKeys = BUILTIN_TEMPLATE_FIELDS[key] || []
|
||||
const presetTemplate = createBuiltinTemplate(key)
|
||||
const templateId = presetTemplate.id
|
||||
const name = presetTemplate.name
|
||||
|
||||
console.log(`\n创建模板: ${name} (${templateId})`)
|
||||
|
||||
// 创建模板记录
|
||||
const template = await prisma.uploadTemplate.create({
|
||||
const createdTemplate = await prisma.uploadTemplate.create({
|
||||
data: {
|
||||
id: templateId,
|
||||
name,
|
||||
description,
|
||||
description: presetTemplate.description,
|
||||
status: 'active',
|
||||
materialType,
|
||||
materialKind,
|
||||
materialType: presetTemplate.materialType,
|
||||
materialKind: presetTemplate.materialKind,
|
||||
version: 1,
|
||||
},
|
||||
})
|
||||
@ -137,50 +74,30 @@ async function fixMissingTemplates() {
|
||||
console.log(` ✓ 模板记录已创建`)
|
||||
|
||||
// 创建字段记录
|
||||
const requireRenderer = key === '3dmax-material'
|
||||
const fieldOverrides: Record<string, Partial<{ required: boolean }>> = {
|
||||
productModel: { required: true },
|
||||
categoryName: { required: true },
|
||||
sceneName: { required: true },
|
||||
}
|
||||
|
||||
if (requireRenderer) {
|
||||
fieldOverrides.renderer = { required: true }
|
||||
}
|
||||
|
||||
for (let i = 0; i < fieldKeys.length; i++) {
|
||||
const fieldKey = fieldKeys[i]
|
||||
const fieldConfig = STANDARD_FIELDS[fieldKey]
|
||||
|
||||
if (!fieldConfig) {
|
||||
console.warn(` ⚠ 字段配置不存在: ${fieldKey}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const override = fieldOverrides[fieldKey] || {}
|
||||
|
||||
for (let i = 0; i < presetTemplate.fields.length; i++) {
|
||||
const field = presetTemplate.fields[i]
|
||||
await prisma.uploadField.create({
|
||||
data: {
|
||||
templateId: template.id,
|
||||
key: fieldConfig.key,
|
||||
label: fieldConfig.label,
|
||||
type: fieldConfig.type,
|
||||
required: override.required ?? fieldConfig.required,
|
||||
templateId: createdTemplate.id,
|
||||
key: field.key,
|
||||
label: field.label,
|
||||
type: field.type,
|
||||
required: field.required,
|
||||
orderIndex: i,
|
||||
layoutWidth: fieldConfig.layoutWidth,
|
||||
layoutWidth: field.layoutWidth,
|
||||
validations: null,
|
||||
optionsSource: null,
|
||||
optionsStatic: fieldConfig.optionsStatic || null,
|
||||
optionsStatic: field.optionsStatic || null,
|
||||
allowedExtensions: null,
|
||||
visibleOnHomeFilter: fieldConfig.visibleOnHomeFilter,
|
||||
isSearchable: fieldConfig.isSearchable,
|
||||
addToCategory: fieldConfig.addToCategory || false,
|
||||
visibleOnHomeFilter: field.visibleOnHomeFilter,
|
||||
isSearchable: field.isSearchable,
|
||||
addToCategory: field.addToCategory || false,
|
||||
conditions: null,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
console.log(` ✓ ${fieldKeys.length} 个字段已创建`)
|
||||
console.log(` ✓ ${presetTemplate.fields.length} 个字段已创建`)
|
||||
}
|
||||
|
||||
// 3. 验证修复结果
|
||||
|
||||
@ -20,7 +20,7 @@ const AUDIO_TEMPLATE_ID = 'builtin-audio-material'
|
||||
const AUDIO_FIELDS = [
|
||||
{ key: 'productModel', label: '音频名称', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true, addToCategory: false },
|
||||
{ key: 'audioCategory', label: '音频分类', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'audioScene', label: '使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'audioScene', label: '音频使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
|
||||
{ key: 'authorName', label: '作者', type: 'text', required: false, layoutWidth: 'half', visibleOnHomeFilter: false, isSearchable: true, addToCategory: false },
|
||||
]
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user