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:
105
apps/backend/scripts/migration/update-hero-images.ts
Normal file
105
apps/backend/scripts/migration/update-hero-images.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env tsx
|
||||
/**
|
||||
* Update posts with heroImage by matching Webflow URLs to uploaded Media
|
||||
*/
|
||||
|
||||
import { config as dotenvConfig } from 'dotenv'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
const envPath = resolve(__dirname, '../../.env')
|
||||
|
||||
dotenvConfig({ path: envPath })
|
||||
|
||||
// Debug
|
||||
console.log('Loading .env from:', envPath)
|
||||
console.log('PAYLOAD_SECRET loaded:', !!process.env.PAYLOAD_SECRET)
|
||||
|
||||
import { getPayload } from 'payload'
|
||||
import config from '../../src/payload.config'
|
||||
import { parseWebflowCSV } from './csvParser'
|
||||
import { transformPosts } from './transformers'
|
||||
|
||||
async function main() {
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
console.log('🔍 Loading media files...')
|
||||
const media = await payload.find({ collection: 'media', limit: 100, depth: 0 })
|
||||
|
||||
// Create filename to ID mapping
|
||||
const filenameToId = new Map<string, string>()
|
||||
media.docs.forEach((m: any) => {
|
||||
filenameToId.set(m.filename, m.id)
|
||||
})
|
||||
|
||||
console.log(`📁 Found ${filenameToId.size} media files`)
|
||||
|
||||
console.log('📂 Loading CSV data...')
|
||||
const data = await parseWebflowCSV('/Users/pukpuk/Dev/website-enchun-mgr/恩群數位行銷 - 行銷放大鏡集.csv')
|
||||
|
||||
console.log('📝 Loading posts...')
|
||||
const posts = await payload.find({ collection: 'posts', limit: 100, depth: 0 })
|
||||
const postsBySlug = new Map<string, any>()
|
||||
posts.docs.forEach((post: any) => {
|
||||
postsBySlug.set(post.slug, post)
|
||||
})
|
||||
|
||||
console.log('\n🔗 Matching hero images...\n')
|
||||
|
||||
let matched = 0
|
||||
let updated = 0
|
||||
let notFound = 0
|
||||
|
||||
for (const webflowPost of data.posts) {
|
||||
const featuredImage = webflowPost.featuredImage
|
||||
if (!featuredImage) continue
|
||||
|
||||
// Extract filename from Webflow URL
|
||||
const urlParts = featuredImage.split('/')
|
||||
const webflowFilename = urlParts[urlParts.length - 1]
|
||||
|
||||
// Find matching media by comparing filename patterns
|
||||
let mediaId: string | null = null
|
||||
|
||||
for (const [filename, id] of filenameToId.entries()) {
|
||||
// Check if Webflow filename is contained in Payload filename or vice versa
|
||||
// They may have different prefixes but the hash should match
|
||||
if (filename.includes(webflowFilename.split('.')[0].substring(0, 15)) ||
|
||||
webflowFilename.includes(filename.split('.')[0].substring(0, 15))) {
|
||||
mediaId = id
|
||||
matched++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!mediaId) {
|
||||
notFound++
|
||||
console.log(`❌ No match: ${webflowPost.title?.substring(0, 40)}`)
|
||||
console.log(` URL: ${webflowFilename}`)
|
||||
continue
|
||||
}
|
||||
|
||||
// Find and update the post
|
||||
const transformed = transformPosts([webflowPost])[0]
|
||||
const post = postsBySlug.get(transformed.slug)
|
||||
|
||||
if (post && !post.heroImage) {
|
||||
await payload.update({
|
||||
collection: 'posts',
|
||||
id: post.id,
|
||||
data: { heroImage: mediaId },
|
||||
})
|
||||
updated++
|
||||
console.log(`✓ Updated: ${webflowPost.title?.substring(0, 40)}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== SUMMARY ===')
|
||||
console.log(`Matched: ${matched}`)
|
||||
console.log(`Updated: ${updated}`)
|
||||
console.log(`Not found: ${notFound}`)
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
Reference in New Issue
Block a user