105 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
/**
* Cleanup helper:
* - Removes Photo docs whose main/variant paths are not WebP.
* - Deletes non-WebP files in uploads.
* - Optionally deletes orphaned WebP files (not referenced by any Photo) when DELETE_ORPHANS=1.
*
* Dry-run by default. Set APPLY=1 to make changes.
*/
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 DELETE_ORPHANS = process.env.DELETE_ORPHANS === '1';
const UPLOAD_DIR = path.join(__dirname, '..', 'uploads');
const isWebp = (p) => /\.webp$/i.test(p || '');
async function main() {
await mongoose.connect(MONGO_URI);
console.log(`Connected to Mongo: ${MONGO_URI}`);
// Find docs with non-webp main or variants
const nonWebpDocs = await Photo.find({
$or: [
{ path: { $not: /\.webp$/i } },
{ 'variants.medium': { $exists: true, $not: /\.webp$/i } },
{ 'variants.thumb': { $exists: true, $not: /\.webp$/i } },
]
}).select('_id path variants filename');
// Build referenced file set from remaining docs
const allDocs = await Photo.find().select('path variants');
const referenced = new Set();
for (const doc of allDocs) {
if (doc.path) referenced.add(doc.path);
if (doc.variants?.medium) referenced.add(doc.variants.medium);
if (doc.variants?.thumb) referenced.add(doc.variants.thumb);
}
// Scan uploads directory
const filesOnDisk = [];
const walk = (dir) => {
for (const entry of fs.readdirSync(dir)) {
const full = path.join(dir, entry);
const stat = fs.statSync(full);
if (stat.isDirectory()) walk(full);
else filesOnDisk.push(full);
}
};
walk(UPLOAD_DIR);
const nonWebpFiles = filesOnDisk.filter(f => !isWebp(f));
const orphans = filesOnDisk
.filter(f => isWebp(f))
.filter(f => !referenced.has(path.relative(path.join(__dirname, '..'), f).replace(/\\/g, '/')));
console.log(`Found ${nonWebpDocs.length} photo docs with non-WebP paths/variants.`);
console.log(`Found ${nonWebpFiles.length} non-WebP files on disk.`);
console.log(`Found ${orphans.length} orphaned WebP files${DELETE_ORPHANS ? ' (will delete if APPLY=1)' : ''}.`);
if (!APPLY) {
console.log('Dry run (set APPLY=1 to apply changes).');
await mongoose.disconnect();
return;
}
if (nonWebpDocs.length) {
const ids = nonWebpDocs.map(d => d._id);
await Photo.deleteMany({ _id: { $in: ids } });
console.log(`Deleted ${ids.length} photo docs with non-WebP paths/variants.`);
}
for (const file of nonWebpFiles) {
try {
fs.unlinkSync(file);
} catch (err) {
console.error('Failed to delete', file, err.message);
}
}
console.log(`Deleted ${nonWebpFiles.length} non-WebP files.`);
if (DELETE_ORPHANS && orphans.length) {
for (const file of orphans) {
try {
fs.unlinkSync(file);
} catch (err) {
console.error('Failed to delete orphan', file, err.message);
}
}
console.log(`Deleted ${orphans.length} orphaned WebP files.`);
}
await mongoose.disconnect();
console.log('Cleanup complete.');
}
main().catch(err => {
console.error(err);
process.exit(1);
});