#!/usr/bin/env node import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createDbPool } from '../db.mjs'; import { defaultPlazaCoverUrl } from '../plaza-posts.mjs'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); const shouldWrite = process.argv.includes('--write'); function loadEnvFile(filePath) { if (!fs.existsSync(filePath)) return; for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eq = trimmed.indexOf('='); if (eq < 0) continue; const key = trimmed.slice(0, eq).trim(); const value = trimmed.slice(eq + 1).trim(); if (!process.env[key]) process.env[key] = value; } } function absolutize(url) { const value = String(url ?? '').trim(); if (!value) return ''; if (/^https?:\/\//i.test(value)) return value; return new URL(value, publicBaseUrl).toString(); } loadEnvFile(path.join(root, '../../.env.local')); loadEnvFile(path.join(root, '.env')); const { resolvePublicBaseUrl } = await import('../user-publish.mjs'); const publicBaseUrl = resolvePublicBaseUrl(); const pool = createDbPool(); const [rows] = await pool.query( `SELECT pp.id, pp.publication_id, pp.title, pp.cover_url, pp.status, pr.public_url, pr.status AS publication_status FROM plaza_posts pp JOIN h5_publish_records pr ON pr.id = pp.publication_id WHERE pr.public_url IS NOT NULL ORDER BY pp.published_at DESC, pp.id DESC`, ); const updates = rows .map((row) => { const nextPublicUrl = absolutize(row.public_url); const nextCoverUrl = row.cover_url ? absolutize(row.cover_url) : absolutize(defaultPlazaCoverUrl(nextPublicUrl)); return { id: row.id, publicationId: row.publication_id, title: row.title, fromPublicUrl: String(row.public_url ?? ''), toPublicUrl: nextPublicUrl, fromCoverUrl: String(row.cover_url ?? ''), toCoverUrl: nextCoverUrl, status: row.status, publicationStatus: row.publication_status, }; }) .filter((row) => row.toPublicUrl && (row.fromPublicUrl !== row.toPublicUrl || row.fromCoverUrl !== row.toCoverUrl)); console.log( `${shouldWrite ? '写入模式' : 'Dry-run'}:共扫描 ${rows.length} 条帖子,命中 ${updates.length} 条待补 public_url/cover_url。`, ); console.log(`Public base: ${publicBaseUrl}`); for (const row of updates.slice(0, 20)) { console.log(`- ${row.id} | ${row.title}`); console.log(` public_url: ${row.fromPublicUrl || '(empty)'} -> ${row.toPublicUrl}`); console.log(` cover_url : ${row.fromCoverUrl || '(empty)'} -> ${row.toCoverUrl}`); } if (updates.length > 20) { console.log(`... 其余 ${updates.length - 20} 条省略`); } if (shouldWrite && updates.length > 0) { const now = Date.now(); for (const row of updates) { await pool.query( `UPDATE h5_publish_records pr JOIN plaza_posts pp ON pp.publication_id = pr.id SET pr.public_url = ?, pp.cover_url = ?, pr.updated_at = ?, pp.updated_at = ? WHERE pr.id = ?`, [row.toPublicUrl, row.toCoverUrl, now, now, row.publicationId], ); } console.log(`已更新 ${updates.length} 条帖子公开链接与封面。`); } else if (!shouldWrite) { console.log('未写入数据库;如需正式回填,请执行: node scripts/backfill-plaza-public-urls.mjs --write'); } await pool.end();