const fs = require('fs/promises') const path = require('path') const { ensureData, closePool, updateImageAssetSrc, replaceUploadSourceReferences, } = require('../src/db') const BACKEND_ROOT = path.join(__dirname, '..') const ASSETS_ROOT = path.join(BACKEND_ROOT, 'uploads', 'assets') const FLAT_ASSET_PATTERN = /^\/uploads\/assets\/[^/]+$/ function getShardedAssetSrc(src) { const filename = path.basename(src || '') const shardDirectory = filename.slice(0, 2) if (!filename || shardDirectory.length < 2) return '' return `/uploads/assets/${shardDirectory}/${filename}` } async function moveAssetFile(fromSrc, toSrc) { const fromPath = path.join(BACKEND_ROOT, fromSrc.replace(/^\//, '')) const toPath = path.join(BACKEND_ROOT, toSrc.replace(/^\//, '')) await fs.mkdir(path.dirname(toPath), { recursive: true }) try { await fs.rename(fromPath, toPath) return 'moved' } catch (error) { if (error?.code !== 'ENOENT') throw error } try { await fs.access(toPath) return 'already_moved' } catch (error) { if (error?.code === 'ENOENT') return 'missing' throw error } } async function main() { await ensureData() let dirEntries = [] try { dirEntries = await fs.readdir(ASSETS_ROOT, { withFileTypes: true }) } catch (error) { if (error?.code !== 'ENOENT') throw error } const flatAssets = dirEntries .filter((entry) => entry.isFile()) .map((entry) => ({ src: `/uploads/assets/${entry.name}` })) .filter((asset) => FLAT_ASSET_PATTERN.test(asset.src || '')) const summary = { scanned: flatAssets.length, migrated: 0, alreadyMoved: 0, skipped: 0, missingFiles: 0, failed: 0, updatedRows: 0, } for (const asset of flatAssets) { const nextSrc = getShardedAssetSrc(asset.src) if (!nextSrc) { summary.skipped += 1 continue } try { const moveStatus = await moveAssetFile(asset.src, nextSrc) if (moveStatus === 'missing') { summary.missingFiles += 1 continue } await updateImageAssetSrc({ fromSrc: asset.src, toSrc: nextSrc }) const replaced = await replaceUploadSourceReferences({ fromSrc: asset.src, toSrc: nextSrc }) summary.updatedRows += Number(replaced.updatedRows || 0) if (moveStatus === 'already_moved') summary.alreadyMoved += 1 else summary.migrated += 1 } catch (error) { summary.failed += 1 console.error('[migrate-flat-assets-to-sharded] failed:', asset.src, error?.message || error) } } console.log(JSON.stringify(summary, null, 2)) } main() .catch((error) => { console.error(error) process.exitCode = 1 }) .finally(async () => { await closePool() })