103 lines
2.7 KiB
JavaScript
103 lines
2.7 KiB
JavaScript
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()
|
|
})
|