Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228#!/usr/bin/env node
/** * Delete orphaned tape records from MongoDB and ATProto * For tapes that have no ZIP or MP4 files and cannot be recovered */
import { AtpAgent } from '@atproto/api';import { connect } from '../../../system/backend/database.mjs';import { userIDFromHandle } from '../../../system/backend/authorization.mjs';import { config } from 'dotenv';
config({ path: '../../../system/.env' });
const DRY_RUN = process.argv.includes('--dry-run');const VERBOSE = process.argv.includes('--verbose');
const handleArg = process.argv[2];if (!handleArg || !handleArg.startsWith('@') || handleArg.startsWith('--')) { console.error('Usage: node delete-orphaned-tapes.mjs @handle [--dry-run] [--verbose]'); console.error('Example: node delete-orphaned-tapes.mjs @jeffrey --dry-run'); process.exit(1);}
const handle = handleArg.replace('@', '');
async function fetchAllTapesFromPds(agent, did) { const allRecords = []; let cursor = undefined;
do { const response = await agent.com.atproto.repo.listRecords({ repo: did, collection: 'computer.aesthetic.tape', limit: 100, cursor, });
const { records = [], cursor: nextCursor } = response.data ?? response; allRecords.push(...records); cursor = nextCursor; } while (cursor);
return allRecords;}
async function main() { console.log(`\n${'โ'.repeat(80)}`); console.log(`๐๏ธ DELETE ORPHANED TAPES FOR @${handle}`); console.log(`${'โ'.repeat(80)}`); if (DRY_RUN) { console.log('โ ๏ธ DRY RUN MODE - No changes will be made'); } console.log('');
const database = await connect();
try { const userSub = await userIDFromHandle(handle, database); if (!userSub) { console.error(`โ Could not find user for handle @${handle}`); process.exit(1); }
const users = database.db.collection('users'); const tapes = database.db.collection('tapes'); const ovenBakes = database.db.collection('oven-bakes');
const userDoc = await users.findOne({ _id: userSub }); if (!userDoc?.atproto?.did || !userDoc?.atproto?.password) { console.error(`โ User @${handle} does not have ATProto credentials stored`); process.exit(1); }
console.log(`๐ Authenticating as ${userDoc.atproto.did}...`);
// Login to ATProto const pdsUrl = `https://${userDoc.atproto.pdsHost || 'jeffrey.at.aesthetic.computer'}`; const agent = new AtpAgent({ service: pdsUrl }); await agent.login({ identifier: userDoc.atproto.did, password: userDoc.atproto.password }); console.log('โ
Logged in to PDS\n');
// Fetch all ATProto records console.log('๐ฆ Fetching ATProto tape records...'); const atprotoRecords = await fetchAllTapesFromPds(agent, userDoc.atproto.did); console.log(`๐ Found ${atprotoRecords.length} ATProto records\n`);
// Fetch MongoDB tapes without MP4 or ZIP console.log('๐ผ Fetching orphaned MongoDB tapes...'); const orphanedTapes = await tapes.find({ user: userSub, $and: [ { $or: [ { mp4Url: { $exists: false } }, { mp4Url: null }, { mp4Url: '' } ] }, { $or: [ { zipUrl: { $exists: false } }, { zipUrl: null }, { zipUrl: '' } ] } ] }).sort({ when: 1 }).toArray();
console.log(`๐ Found ${orphanedTapes.length} orphaned tapes\n`);
if (orphanedTapes.length === 0) { console.log('โ
No orphaned tapes found!'); await database.disconnect(); process.exit(0); }
const results = { deletedMongo: 0, deletedAtproto: 0, deletedOvenBakes: 0, failed: 0 };
for (const tape of orphanedTapes) { console.log(`${'โ'.repeat(80)}`); console.log(`๐๏ธ Deleting tape: ${tape.code} (${tape.slug})`); console.log(` MongoDB _id: ${tape._id}`); try { // Find ATProto records for this slug const matchingRecords = atprotoRecords.filter(r => r.value?.slug === tape.slug); if (matchingRecords.length > 0) { console.log(` Found ${matchingRecords.length} ATProto record(s)`); for (const record of matchingRecords) { const rkey = record.uri.split('/').pop(); console.log(` ๐๏ธ Deleting ATProto record: ${rkey}`); if (!DRY_RUN) { try { await agent.com.atproto.repo.deleteRecord({ repo: agent.session.did, collection: 'computer.aesthetic.tape', rkey: rkey }); console.log(` โ
Deleted ATProto record ${rkey}`); results.deletedAtproto++; } catch (err) { console.log(` โ ๏ธ Failed to delete ATProto record: ${err.message}`); results.failed++; } } else { console.log(` [DRY RUN] Would delete ATProto record ${rkey}`); } } } else { console.log(` No ATProto records found for this slug`); } // Delete from oven-bakes const ovenBakeCount = await ovenBakes.countDocuments({ code: tape.code }); if (ovenBakeCount > 0) { console.log(` ๐๏ธ Deleting ${ovenBakeCount} oven-bake record(s)`); if (!DRY_RUN) { const ovenResult = await ovenBakes.deleteMany({ code: tape.code }); console.log(` โ
Deleted ${ovenResult.deletedCount} oven-bake record(s)`); results.deletedOvenBakes += ovenResult.deletedCount; } else { console.log(` [DRY RUN] Would delete ${ovenBakeCount} oven-bake record(s)`); } } // Delete from MongoDB tapes console.log(` ๐๏ธ Deleting MongoDB tape record`); if (!DRY_RUN) { await tapes.deleteOne({ _id: tape._id }); console.log(` โ
Deleted MongoDB tape record`); results.deletedMongo++; } else { console.log(` [DRY RUN] Would delete MongoDB tape record`); } } catch (error) { console.error(` โ Error: ${error.message}`); if (VERBOSE) { console.error(error); } results.failed++; } }
// Final summary console.log(`\n${'โ'.repeat(80)}`); console.log(`๐ CLEANUP COMPLETE`); console.log(`${'โ'.repeat(80)}`); console.log(`๐๏ธ MongoDB tapes deleted: ${results.deletedMongo}`); console.log(`๐๏ธ ATProto records deleted: ${results.deletedAtproto}`); console.log(`๐๏ธ Oven-bake records deleted: ${results.deletedOvenBakes}`); console.log(`โ Failed: ${results.failed}`); console.log(`${'โ'.repeat(80)}\n`);
if (DRY_RUN) { console.log('โน๏ธ This was a DRY RUN. Run without --dry-run to apply changes.\n'); }
await database.disconnect(); process.exit(results.failed > 0 ? 1 : 0);
} catch (error) { console.error('โ Fatal error:', error); if (VERBOSE) { console.error(error); } await database.disconnect(); process.exit(1); }}
main().catch(err => { console.error('๐ฅ Fatal error:', err); process.exit(1);});