105 lines
3.5 KiB
JavaScript
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);
|
|
});
|