chore: gate brand knowledge schema migration
This commit is contained in:
parent
12f2baf3bc
commit
f4c2fbf023
@ -93,7 +93,8 @@ RUN test -s "/app/public/brand/header-logo.png" && \
|
||||
test -f "/app/prisma/migrations/20260831_add_private_task_detail/migration.sql" && \
|
||||
test -f "/app/prisma/migrations/20260902_add_employee_roster_history/migration.sql" && \
|
||||
test -f "/app/prisma/migrations/20260903_add_work_review_brand/migration.sql" && \
|
||||
test -f "/app/prisma/migrations/20260908_add_brand_knowledge/migration.sql"
|
||||
test -f "/app/prisma/migrations/20260908_add_brand_knowledge/migration.sql" && \
|
||||
test -f "/app/prisma/migrations/20260909_expand_brand_knowledge_upload/migration.sql"
|
||||
|
||||
# 这两个动态路由负责兜底访问本地上传文件;如果它们在构建上下文里丢失,
|
||||
# 线上音频/视频就会直接 404。这里提前失败,避免再把缺路由的镜像发出去。
|
||||
@ -220,6 +221,7 @@ COPY --from=builder --chown=root:root /app/prisma/migrations/20260831_add_privat
|
||||
COPY --from=builder --chown=root:root /app/prisma/migrations/20260902_add_employee_roster_history ./prisma/migrations/20260902_add_employee_roster_history
|
||||
COPY --from=builder --chown=root:root /app/prisma/migrations/20260903_add_work_review_brand ./prisma/migrations/20260903_add_work_review_brand
|
||||
COPY --from=builder --chown=root:root /app/prisma/migrations/20260908_add_brand_knowledge ./prisma/migrations/20260908_add_brand_knowledge
|
||||
COPY --from=builder --chown=root:root /app/prisma/migrations/20260909_expand_brand_knowledge_upload ./prisma/migrations/20260909_expand_brand_knowledge_upload
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/scripts/public-traffic-rollup-from-nginx.py ./scripts/public-traffic-rollup-from-nginx.py
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/scripts/collapse-performance-survey-template-versions.mjs ./scripts/collapse-performance-survey-template-versions.mjs
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
- `20260902_add_employee_roster_history`
|
||||
- `20260903_add_work_review_brand`
|
||||
- `20260908_add_brand_knowledge`
|
||||
- `20260909_expand_brand_knowledge_upload`
|
||||
|
||||
它们新增可空租约字段/索引、修改未来 Logo 默认值、精确把 `system_settings` 品牌两列中的退役 `s3.1080p.hk` 值改为本地品牌图,并把 `materials_uploadId_key` 重建为“`uploadId` 非空且素材未软删除”范围内的唯一 btree 索引;同时新增人员组织树、素材托管字段、素材不完整空壳标记、账号—人员绑定表、钉钉流程快照字段,以及任务详情图片变更审批单和通知 outbox。审批迁移只新增表、约束与索引,不改写现有素材和资产行。`20260829_add_recoverable_api_key_secret` 只新增 `api_keys.secret_ciphertext` / `secret_nonce` / `secret_auth_tag` 三个可空加密列、`api_key_reveal_audits` 审计表和 `api_key_id+created_at`、`viewer_user_id+created_at` 两个非唯一 btree 索引,不回填旧 key。`20260831_add_private_task_detail` 只新增 `materials.task_detail_demand_type`、`materials.task_detail_private default false`、`material_permission_pack_items.can_access_private default false`,并加上 `materials_private_optimization_check`,确保私密任务详情只能落在“任务-详情 + 优化”语义下。`20260902_add_employee_roster_history` 新增员工完整花名册的时间快照表及查询索引,团队产能按每周结束前最近一份快照统计在职人数;不会用当前花名册回填没有快照的历史周。新增 `form_fields` 后会将同步游标重置一次,让下一轮同步完整补齐明细字段。索引迁移会先检测未删除素材中的重复 `uploadId`;人员绑定迁移会先检测一个钉钉身份对应多个账号的冲突。任一预检失败都会直接停止,不会猜测性合并、删除素材或留下一半回填。迁移没有自动 down;执行失败时停止发布,恢复依赖执行前的数据库备份。
|
||||
|
||||
|
||||
@ -1513,6 +1513,7 @@ const expectedMigrationNames = new Set([
|
||||
'20260902_add_employee_roster_history',
|
||||
'20260903_add_work_review_brand',
|
||||
'20260908_add_brand_knowledge',
|
||||
'20260909_expand_brand_knowledge_upload',
|
||||
]);
|
||||
if (migrations.length !== expectedMigrationNames.size
|
||||
|| migrations.some((item) => !expectedMigrationNames.has(item && item.name)
|
||||
|
||||
@ -29,6 +29,7 @@ const TARGET_MIGRATIONS = Object.freeze([
|
||||
'20260902_add_employee_roster_history',
|
||||
'20260903_add_work_review_brand',
|
||||
'20260908_add_brand_knowledge',
|
||||
'20260909_expand_brand_knowledge_upload',
|
||||
])
|
||||
const VERIFIED_BACKUP_GENERATOR = 'scripts/create-verified-production-backup.mjs'
|
||||
const VERIFIED_BACKUP_MAINTENANCE_CONFIRMATION = 'NO_WRITES_DURING_SNAPSHOT'
|
||||
@ -314,7 +315,7 @@ async function queryEvidence(prisma, localMigrations) {
|
||||
OR (table_name = 'ding_workflow_sync_states' AND column_name IN ('id', 'status', 'source_cursor_raw', 'last_completed_at', 'lease_owner', 'lease_expires_at', 'current_ongoing_count'))
|
||||
OR (table_name = 'task_detail_change_requests' AND column_name IN ('id', 'material_id', 'requester_user_id', 'manager_dingtalk_user_id', 'status', 'operations', 'base_material_updated_at', 'expires_at'))
|
||||
OR (table_name = 'task_detail_change_notifications' AND column_name IN ('id', 'request_id', 'event_type', 'business_key', 'status', 'next_retry_at'))
|
||||
OR (table_name = 'brand_knowledge' AND column_name IN ('id', 'title', 'knowledge_type', 'brand', 'file_name', 'file_path', 'mime_type', 'file_size', 'uploaded_by', 'created_at', 'updated_at')))
|
||||
OR (table_name = 'brand_knowledge' AND column_name IN ('id', 'title', 'knowledge_type', 'brand', 'product_category_name', 'file_name', 'file_path', 'mime_type', 'file_size', 'preview_image_name', 'preview_image_path', 'preview_image_mime_type', 'preview_image_size', 'uploaded_by', 'created_at', 'updated_at')))
|
||||
ORDER BY table_name, column_name`,
|
||||
)
|
||||
const indexes = await prisma.$queryRawUnsafe(
|
||||
@ -434,10 +435,15 @@ export function assertTargetMigrationEvidence(localMigrations, evidence) {
|
||||
requireColumn('brand_knowledge', 'title', 'text', 'NO')
|
||||
requireColumn('brand_knowledge', 'knowledge_type', 'text', 'NO')
|
||||
requireColumn('brand_knowledge', 'brand', 'text', 'NO')
|
||||
requireColumn('brand_knowledge', 'product_category_name', 'text', 'NO')
|
||||
requireColumn('brand_knowledge', 'file_name', 'text', 'NO')
|
||||
requireColumn('brand_knowledge', 'file_path', 'text', 'NO')
|
||||
requireColumn('brand_knowledge', 'mime_type', 'text', 'NO')
|
||||
requireColumn('brand_knowledge', 'file_size', 'bigint', 'NO')
|
||||
requireColumn('brand_knowledge', 'preview_image_name', 'text', 'YES')
|
||||
requireColumn('brand_knowledge', 'preview_image_path', 'text', 'YES')
|
||||
requireColumn('brand_knowledge', 'preview_image_mime_type', 'text', 'YES')
|
||||
requireColumn('brand_knowledge', 'preview_image_size', 'bigint', 'YES')
|
||||
requireColumn('brand_knowledge', 'uploaded_by', 'text', 'NO')
|
||||
requireColumn('brand_knowledge', 'created_at', 'timestamp without time zone', 'NO')
|
||||
requireColumn('brand_knowledge', 'updated_at', 'timestamp without time zone', 'NO')
|
||||
@ -558,6 +564,11 @@ export function assertTargetMigrationEvidence(localMigrations, evidence) {
|
||||
'materials',
|
||||
['is_incomplete_shell'],
|
||||
)
|
||||
assertExactBtreeIndex(
|
||||
indexByName.get('brand_knowledge_product_category_name_idx'),
|
||||
'brand_knowledge',
|
||||
['product_category_name'],
|
||||
)
|
||||
assertExactBtreeIndex(
|
||||
indexByName.get('ding_workflow_snapshots_ongoing_updated_idx'),
|
||||
'ding_workflow_snapshots',
|
||||
|
||||
@ -345,6 +345,7 @@ test('target evidence rejects nullable logo columns and structurally wrong index
|
||||
['20260902_add_employee_roster_history', { checksum: 'r' }],
|
||||
['20260903_add_work_review_brand', { checksum: 's' }],
|
||||
['20260908_add_brand_knowledge', { checksum: 't' }],
|
||||
['20260909_expand_brand_knowledge_upload', { checksum: 'u' }],
|
||||
])
|
||||
const evidence = {
|
||||
migrationRows: [
|
||||
@ -368,6 +369,7 @@ test('target evidence rejects nullable logo columns and structurally wrong index
|
||||
{ migration_name: '20260902_add_employee_roster_history', checksum: 'r', finished_at: 'now', rolled_back_at: null },
|
||||
{ migration_name: '20260903_add_work_review_brand', checksum: 's', finished_at: 'now', rolled_back_at: null },
|
||||
{ migration_name: '20260908_add_brand_knowledge', checksum: 't', finished_at: 'now', rolled_back_at: null },
|
||||
{ migration_name: '20260909_expand_brand_knowledge_upload', checksum: 'u', finished_at: 'now', rolled_back_at: null },
|
||||
],
|
||||
columns: [
|
||||
{ table_name: 'materials', column_name: 'transcode_lease_token', data_type: 'text', is_nullable: 'YES' },
|
||||
@ -455,10 +457,15 @@ test('target evidence rejects nullable logo columns and structurally wrong index
|
||||
{ table_name: 'brand_knowledge', column_name: 'title', data_type: 'text', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'knowledge_type', data_type: 'text', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'brand', data_type: 'text', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'product_category_name', data_type: 'text', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'file_name', data_type: 'text', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'file_path', data_type: 'text', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'mime_type', data_type: 'text', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'file_size', data_type: 'bigint', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'preview_image_name', data_type: 'text', is_nullable: 'YES' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'preview_image_path', data_type: 'text', is_nullable: 'YES' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'preview_image_mime_type', data_type: 'text', is_nullable: 'YES' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'preview_image_size', data_type: 'bigint', is_nullable: 'YES' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'uploaded_by', data_type: 'text', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'created_at', data_type: 'timestamp without time zone', is_nullable: 'NO' },
|
||||
{ table_name: 'brand_knowledge', column_name: 'updated_at', data_type: 'timestamp without time zone', is_nullable: 'NO' },
|
||||
@ -492,6 +499,7 @@ test('target evidence rejects nullable logo columns and structurally wrong index
|
||||
{ indexname: 'task_detail_change_notifications_status_retry_idx', indexdef: 'CREATE INDEX task_detail_change_notifications_status_retry_idx ON public.task_detail_change_notifications USING btree (status, next_retry_at)' },
|
||||
{ indexname: 'api_key_reveal_audits_api_key_id_created_at_idx', indexdef: 'CREATE INDEX api_key_reveal_audits_api_key_id_created_at_idx ON public.api_key_reveal_audits USING btree (api_key_id, created_at)' },
|
||||
{ indexname: 'api_key_reveal_audits_viewer_user_id_created_at_idx', indexdef: 'CREATE INDEX api_key_reveal_audits_viewer_user_id_created_at_idx ON public.api_key_reveal_audits USING btree (viewer_user_id, created_at)' },
|
||||
{ indexname: 'brand_knowledge_product_category_name_idx', indexdef: 'CREATE INDEX brand_knowledge_product_category_name_idx ON public.brand_knowledge USING btree (product_category_name)' },
|
||||
],
|
||||
constraints: [{
|
||||
tablename: 'materials',
|
||||
@ -709,6 +717,7 @@ test('container supply chain and migrator image are immutable while SOP keeps co
|
||||
'20260902_add_employee_roster_history',
|
||||
'20260903_add_work_review_brand',
|
||||
'20260908_add_brand_knowledge',
|
||||
'20260909_expand_brand_knowledge_upload',
|
||||
]) {
|
||||
assert.match(dockerfile, new RegExp(migrationName))
|
||||
assert.match(migratorSource, new RegExp(`'${migrationName}'`))
|
||||
@ -726,6 +735,8 @@ test('container supply chain and migrator image are immutable while SOP keeps co
|
||||
assert.ok(sop.includes('`20260903_add_work_review_brand`'))
|
||||
assert.match(deploymentSource, /'20260908_add_brand_knowledge'/)
|
||||
assert.ok(sop.includes('`20260908_add_brand_knowledge`'))
|
||||
assert.match(deploymentSource, /'20260909_expand_brand_knowledge_upload'/)
|
||||
assert.ok(sop.includes('`20260909_expand_brand_knowledge_upload`'))
|
||||
assert.match(sop, /materials_private_optimization_check/)
|
||||
assert.match(sop, /task_detail_private/)
|
||||
assert.match(sop, /can_access_private/)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user