feat(backend): update collections, config and migration tools

Update Payload CMS configuration, collections (Audit, Posts), and add migration scripts/reports.
This commit is contained in:
2026-02-11 11:50:23 +08:00
parent 8ca609a889
commit be7fc902fb
46 changed files with 5442 additions and 15 deletions

View File

@@ -0,0 +1,188 @@
/**
* Migration Reporter
* Story 1.3: Content Migration Script
*
* Generates migration reports in JSON and Markdown formats
*/
import type { MigrationReport, CollectionMigrationResult } from './types'
import { writeFile, mkdir } from 'fs/promises'
import { join } from 'path'
import { existsSync } from 'fs'
// ============================================================
// REPORT GENERATION
// ============================================================
/**
* Create a new empty report
*/
export function createReport(dryRun: boolean = false): MigrationReport {
return {
timestamp: new Date().toISOString(),
dryRun,
summary: {
total: 0,
created: 0,
skipped: 0,
failed: 0,
},
byCollection: {},
details: {},
}
}
/**
* Update report with collection results
*/
export function updateReport(
report: MigrationReport,
collectionResult: CollectionMigrationResult,
): MigrationReport {
const { collection, created, skipped, failed } = collectionResult
// Update byCollection stats
report.byCollection[collection] = { created, skipped, failed }
// Store details
report.details[collection] = collectionResult
// Update summary
report.summary.total += created + skipped + failed
report.summary.created += created
report.summary.skipped += skipped
report.summary.failed += failed
return report
}
/**
* Generate markdown report
*/
export function generateMarkdownReport(report: MigrationReport): string {
const lines: string[] = []
lines.push(`# Migration Report`)
lines.push(``)
lines.push(`**Generated:** ${new Date(report.timestamp).toLocaleString('zh-TW')}`)
lines.push(`**Mode:** ${report.dryRun ? '🧪 Dry Run (no changes made)' : '✅ Live Migration'}`)
lines.push(``)
lines.push(`---`)
lines.push(``)
// Summary section
lines.push(`## Summary`)
lines.push(``)
lines.push(`| Metric | Count |`)
lines.push(`|--------|-------|`)
lines.push(`| Total Items | ${report.summary.total} |`)
lines.push(`| ✅ Created | ${report.summary.created} |`)
lines.push(`| ⏭️ Skipped | ${report.summary.skipped} |`)
lines.push(`| ❌ Failed | ${report.summary.failed} |`)
lines.push(``)
// By collection section
lines.push(`## By Collection`)
lines.push(``)
for (const [collection, stats] of Object.entries(report.byCollection)) {
lines.push(`### ${collection.charAt(0).toUpperCase() + collection.slice(1)}`)
lines.push(``)
lines.push(`| Metric | Count |`)
lines.push(`|--------|-------|`)
lines.push(`| Created | ${stats.created} |`)
lines.push(`| Skipped | ${stats.skipped} |`)
lines.push(`| Failed | ${stats.failed} |`)
lines.push(``)
}
// Details section
if (report.details) {
lines.push(`## Details`)
lines.push(``)
for (const [collection, result] of Object.entries(report.details)) {
lines.push(`### ${collection.charAt(0).toUpperCase() + collection.slice(1)}`)
lines.push(``)
// Created items
if (result.results.some((r) => r.success)) {
lines.push(`#### ✅ Created (${result.results.filter((r) => r.success).length})`)
lines.push(``)
for (const item of result.results.filter((r) => r.success)) {
lines.push(`- \`${item.slug}\` (ID: ${item.id})`)
}
lines.push(``)
}
// Failed items
if (result.results.some((r) => !r.success)) {
lines.push(`#### ❌ Failed (${result.results.filter((r) => !r.success).length})`)
lines.push(``)
for (const item of result.results.filter((r) => !r.success)) {
lines.push(`- \`${item.slug}\`: ${item.error}`)
}
lines.push(``)
}
}
}
return lines.join('\n')
}
/**
* Save report to files
*/
export async function saveReport(
report: MigrationReport,
outputDir: string = './reports',
): Promise<void> {
// Ensure directory exists
if (!existsSync(outputDir)) {
await mkdir(outputDir, { recursive: true })
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0]
const baseName = `migration-${timestamp}`
// Save JSON report
const jsonPath = join(outputDir, `${baseName}.json`)
await writeFile(jsonPath, JSON.stringify(report, null, 2), 'utf-8')
// Save Markdown report
const mdPath = join(outputDir, `${baseName}.md`)
await writeFile(mdPath, generateMarkdownReport(report), 'utf-8')
console.log(`\n📄 Reports saved:`)
console.log(` - JSON: ${jsonPath}`)
console.log(` - Markdown: ${mdPath}`)
}
/**
* Print report summary to console
*/
export function printReportSummary(report: MigrationReport): void {
console.log(`\n${'='.repeat(60)}`)
console.log(`📊 MIGRATION REPORT`)
console.log(`${'='.repeat(60)}`)
console.log(``)
console.log(`Mode: ${report.dryRun ? '🧪 Dry Run' : '✅ Live'}`)
console.log(``)
console.log(`Summary:`)
console.log(` Total: ${report.summary.total}`)
console.log(` Created: ${report.summary.created}`)
console.log(` Skipped: ${report.summary.skipped} ⏭️`)
console.log(` Failed: ${report.summary.failed}`)
console.log(``)
if (report.byCollection) {
console.log(`By Collection:`)
for (const [collection, stats] of Object.entries(report.byCollection)) {
console.log(
` ${collection}: ${stats.created} created, ${stats.skipped} skipped, ${stats.failed} failed`,
)
}
}
console.log(`${'='.repeat(60)}`)
}