feat(db): 0001 来源域——connection/snapshot/job/record + 快照不可变触发器
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// 聚合导出:Task 6 起按域追加 pgTable 定义(站点域 = 迁移文件 0000_pale_sumo)
|
||||
import { pgTable, uuid, text, integer, jsonb, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'
|
||||
// 聚合导出:Task 6 起按域追加 pgTable 定义(站点域 = 迁移文件 0000_pale_sumo;来源域 = 0001_*)
|
||||
import { pgTable, pgEnum, uuid, text, integer, bigint, jsonb, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'
|
||||
|
||||
export const sites = pgTable('sites', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
@@ -19,3 +19,72 @@ export const siteConfigRevisions = pgTable('site_config_revisions', {
|
||||
checksum: text('checksum').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (t) => [uniqueIndex('site_config_rev_uq').on(t.siteId, t.version)])
|
||||
|
||||
// ---- 来源域(spec §5.2;迁移文件 0001_*,末尾手写追加快照不可变触发器)----
|
||||
|
||||
export const sourceConnectionKind = pgEnum('source_connection_kind', ['wxr'])
|
||||
export const sourceSnapshotKind = pgEnum('source_snapshot_kind', ['wxr', 'sitemap', 'media_bundle', 'manual_url_list'])
|
||||
export const importJobStatus = pgEnum('import_job_status', [
|
||||
'queued', 'running', 'succeeded', 'succeeded_with_issues', 'failed', 'cancelled', 'stale',
|
||||
])
|
||||
|
||||
// Connection 只描述来源,不保存某次文件路径和 Hash
|
||||
export const sourceConnections = pgTable('source_connections', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
siteId: uuid('site_id').notNull().references(() => sites.id),
|
||||
kind: sourceConnectionKind('kind').notNull(),
|
||||
name: text('name').notNull(),
|
||||
configuration: jsonb('configuration').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
// Snapshot 创建后不可变(库内触发器强制,见 0001 迁移手写 SQL);替换文件必须创建新 Snapshot
|
||||
export const sourceSnapshots = pgTable('source_snapshots', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
siteId: uuid('site_id').notNull().references(() => sites.id),
|
||||
sourceConnectionId: uuid('source_connection_id').notNull().references(() => sourceConnections.id),
|
||||
kind: sourceSnapshotKind('kind').notNull(),
|
||||
storagePath: text('storage_path').notNull(),
|
||||
sha256: text('sha256').notNull(),
|
||||
byteSize: bigint('byte_size', { mode: 'number' }).notNull(),
|
||||
adapterVersion: text('adapter_version').notNull(),
|
||||
capturedAt: timestamp('captured_at', { withTimezone: true }).notNull(),
|
||||
metadata: jsonb('metadata').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (t) => [uniqueIndex('source_snapshots_uq').on(t.siteId, t.kind, t.sha256)])
|
||||
|
||||
export const importJobs = pgTable('import_jobs', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
siteId: uuid('site_id').notNull().references(() => sites.id),
|
||||
sourceSnapshotId: uuid('source_snapshot_id').notNull().references(() => sourceSnapshots.id),
|
||||
status: importJobStatus('status').notNull(),
|
||||
mappingVersion: text('mapping_version').notNull(),
|
||||
idempotencyKey: text('idempotency_key').notNull().unique(),
|
||||
attempt: integer('attempt').notNull().default(0),
|
||||
startedAt: timestamp('started_at', { withTimezone: true }),
|
||||
finishedAt: timestamp('finished_at', { withTimezone: true }),
|
||||
errorCode: text('error_code'),
|
||||
errorSummary: text('error_summary'),
|
||||
reportPath: text('report_path'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const sourceRecords = pgTable('source_records', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
siteId: uuid('site_id').notNull().references(() => sites.id),
|
||||
sourceConnectionId: uuid('source_connection_id').notNull().references(() => sourceConnections.id),
|
||||
sourceSnapshotId: uuid('source_snapshot_id').notNull().references(() => sourceSnapshots.id),
|
||||
importJobId: uuid('import_job_id').notNull().references(() => importJobs.id),
|
||||
sourceType: text('source_type').notNull(),
|
||||
sourceId: text('source_id').notNull(),
|
||||
locale: text('locale').notNull(),
|
||||
sourceUrl: text('source_url'),
|
||||
rawLocation: text('raw_location').notNull(),
|
||||
sourceChecksum: text('source_checksum'),
|
||||
normalizedChecksum: text('normalized_checksum'),
|
||||
parseStatus: text('parse_status').notNull(),
|
||||
// 暂无 FK:content_entries 于 Task 8(内容域)落地后再补引用
|
||||
contentEntryId: uuid('content_entry_id'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
// 跨 Snapshot 稳定映射键 = site_id + source_connection_id + source_type + source_id + locale(snapshot_id 进键以允许跨快照再导入)
|
||||
}, (t) => [uniqueIndex('source_records_identity_uq').on(t.siteId, t.sourceConnectionId, t.sourceType, t.sourceId, t.locale, t.sourceSnapshotId)])
|
||||
|
||||
Reference in New Issue
Block a user