118 lines
4.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Relink variants for existing Photo docs based on files in uploads/.
* For each Photo doc, derive baseName from its filename and fill in:
* - path -> uploads/<base>-main (or first available in group)
* - variants.medium / variants.thumb -> matching files if present
*
* Dry run by default; set APPLY=1 to save.
*/
const fs = require('fs');
const path = require('path');
const mongoose = require('mongoose');
const Photo = require('../models/photo');
const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/photogallery';
const APPLY = process.env.APPLY === '1';
const UPLOAD_DIR = path.join(__dirname, '..', 'uploads');
const VARIANT_KEYS = { '': 'main', '-md': 'medium', '-sm': 'thumb' };
function parseFile(file) {
const ext = path.extname(file);
if (ext.toLowerCase() !== '.webp') return null;
const base = path.basename(file, ext);
const match = base.match(/^(.*?)(-md|-sm)?$/);
if (!match) return null;
return { baseName: match[1], variantKey: VARIANT_KEYS[match[2] || ''], filename: file };
}
function scanUploads() {
const groups = {};
const files = fs.readdirSync(UPLOAD_DIR).filter(f => f.toLowerCase().endsWith('.webp'));
for (const file of files) {
const parsed = parseFile(file);
if (!parsed) continue;
const { baseName, variantKey, filename } = parsed;
if (!groups[baseName]) groups[baseName] = {};
groups[baseName][variantKey] = filename;
}
return groups;
}
async function main() {
await mongoose.connect(MONGO_URI);
console.log(`Connected to Mongo: ${MONGO_URI}`);
const groups = scanUploads();
let updated = 0;
const missingGroups = [];
const docs = await Photo.find({});
// Index docs by filename/path and baseName to improve matching
const byFilename = new Map();
const byPath = new Map();
const byBase = new Map(); // baseName -> array of docs
for (const doc of docs) {
const fname = doc.filename || path.basename(doc.path || '');
byFilename.set(fname, doc);
if (doc.path) {
const rel = doc.path.replace(/\\/g, '/');
byPath.set(rel.startsWith('uploads/') ? rel.slice(''.length) : rel, doc);
byPath.set(rel, doc);
}
const parsed = parseFile(fname);
if (parsed) {
const arr = byBase.get(parsed.baseName) || [];
arr.push(doc);
byBase.set(parsed.baseName, arr);
}
}
for (const [baseName, variants] of Object.entries(groups)) {
const mainFile = variants.main || Object.values(variants)[0];
const relMain = path.join('uploads', mainFile).replace(/\\/g, '/');
let doc =
byFilename.get(mainFile) ||
byPath.get(relMain) ||
(() => {
const arr = byBase.get(baseName) || [];
return arr.length === 1 ? arr[0] : null;
})();
if (!doc) {
missingGroups.push(baseName);
continue;
}
const newPath = relMain;
const newVariants = {
medium: variants.medium ? path.join('uploads', variants.medium).replace(/\\/g, '/') : undefined,
thumb: variants.thumb ? path.join('uploads', variants.thumb).replace(/\\/g, '/') : undefined,
};
const pathChanged = doc.path !== newPath;
const medChanged = (doc.variants?.medium || undefined) !== newVariants.medium;
const thumbChanged = (doc.variants?.thumb || undefined) !== newVariants.thumb;
if (pathChanged || medChanged || thumbChanged) {
if (APPLY) {
doc.path = newPath;
doc.variants = newVariants;
await doc.save();
}
updated++;
}
}
console.log(`Relinked variants for ${updated} photos.${APPLY ? '' : ' (dry run, no writes)'}`);
if (missingGroups.length) {
const sample = missingGroups.slice(0, 10).join(', ');
console.log(`Skipped ${missingGroups.length} upload groups with no matching Photo doc. Examples: ${sample}`);
}
await mongoose.disconnect();
}
main().catch(err => {
console.error(err);
process.exit(1);
});