import { existsSync, readFileSync } from 'node:fs' import postgres from 'postgres' import { createPostBodyThumbnailForImageUrl, extractPostImageUrlsFromContent, getExistingPostBodyPreviewThumbnailUrl, getExistingPostBodyThumbnailUrl, getExistingPostLightboxThumbnailUrl, getPostBodyThumbnailUrl } from '../server/utils/post-thumbnail-image.js' /** * 로컬 실행용 환경 파일을 process.env에 반영한다. * @returns {void} */ const loadEnvironmentFile = () => { const candidates = [ process.env.ENV_FILE, '.env.development', '.env' ].filter(Boolean) const envFile = candidates.find((filePath) => existsSync(filePath)) if (!envFile) { return } const lines = readFileSync(envFile, 'utf8').split(/\r?\n/) for (const line of lines) { const trimmedLine = line.trim() if (!trimmedLine || trimmedLine.startsWith('#')) { continue } const separatorIndex = trimmedLine.indexOf('=') if (separatorIndex === -1) { continue } const key = trimmedLine.slice(0, separatorIndex).trim() const rawValue = trimmedLine.slice(separatorIndex + 1).trim() if (!key || process.env[key] !== undefined) { continue } process.env[key] = rawValue.replace(/^['"]|['"]$/g, '') } } /** * 게시물 본문 목록을 조회한다. * @returns {Promise>} 게시물 본문 목록 */ const listPostContents = async () => { loadEnvironmentFile() if (!process.env.DATABASE_URL) { throw new Error('DATABASE_URL 환경 변수가 필요합니다.') } const sql = postgres(process.env.DATABASE_URL, { max: 1, idle_timeout: 5, connect_timeout: 10 }) try { return await sql` SELECT content FROM posts WHERE content IS NOT NULL ORDER BY COALESCE(published_at, updated_at) DESC ` } finally { await sql.end({ timeout: 5 }) } } const postContents = await listPostContents() const imageUrls = [...new Set(postContents .flatMap((post) => extractPostImageUrlsFromContent(post.content)) .filter((url) => getPostBodyThumbnailUrl(url)))] let createdCount = 0 let skippedCount = 0 let failedCount = 0 for (const imageUrl of imageUrls) { if ( getExistingPostBodyThumbnailUrl(imageUrl) && getExistingPostBodyPreviewThumbnailUrl(imageUrl) && getExistingPostLightboxThumbnailUrl(imageUrl) ) { skippedCount += 1 continue } const thumbnailUrl = await createPostBodyThumbnailForImageUrl(imageUrl) if (thumbnailUrl) { createdCount += 1 } else { failedCount += 1 } } process.stdout.write([ '게시물 본문 표시용 이미지 생성 완료:', `게시물 ${postContents.length}개`, `대상 ${imageUrls.length}개`, `생성 ${createdCount}개`, `기존 ${skippedCount}개`, `실패 ${failedCount}개` ].join(' ') + '\n')