Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

delete associated subfs records when deleting sites

When a user deletes a site, now fetches the main record first to
extract any subfs references, then deletes all associated subfs
records along with the main record.

Prevents orphaned subfs records from accumulating in the PDS.

Changed files
+55 -1
src
routes
+55 -1
src/routes/site.ts
··· 4 4 import { Agent } from '@atproto/api' 5 5 import { deleteSite } from '../lib/db' 6 6 import { logger } from '../lib/logger' 7 + import { extractSubfsUris } from '../lib/wisp-utils' 7 8 8 9 export const siteRoutes = (client: NodeOAuthClient, cookieSecret: string) => 9 10 new Elysia({ ··· 31 32 // Create agent with OAuth session 32 33 const agent = new Agent((url, init) => auth.session.fetchHandler(url, init)) 33 34 34 - // Delete the record from AT Protocol 35 + // First, fetch the site record to find any subfs references 36 + let subfsUris: Array<{ uri: string; path: string }> = []; 37 + try { 38 + const existingRecord = await agent.com.atproto.repo.getRecord({ 39 + repo: auth.did, 40 + collection: 'place.wisp.fs', 41 + rkey: rkey 42 + }); 43 + 44 + if (existingRecord.data.value && typeof existingRecord.data.value === 'object' && 'root' in existingRecord.data.value) { 45 + const manifest = existingRecord.data.value as any; 46 + subfsUris = extractSubfsUris(manifest.root); 47 + 48 + if (subfsUris.length > 0) { 49 + console.log(`Found ${subfsUris.length} subfs records to delete`); 50 + logger.info(`[Site] Found ${subfsUris.length} subfs records associated with ${rkey}`); 51 + } 52 + } 53 + } catch (err) { 54 + // Record might not exist, continue with deletion 55 + console.log('Could not fetch site record for subfs cleanup, continuing...'); 56 + } 57 + 58 + // Delete the main record from AT Protocol 35 59 try { 36 60 await agent.com.atproto.repo.deleteRecord({ 37 61 repo: auth.did, ··· 42 66 } catch (err) { 43 67 logger.error(`[Site] Failed to delete site ${rkey} from PDS`, err) 44 68 throw new Error('Failed to delete site from AT Protocol') 69 + } 70 + 71 + // Delete associated subfs records 72 + if (subfsUris.length > 0) { 73 + console.log(`Deleting ${subfsUris.length} associated subfs records...`); 74 + 75 + await Promise.all( 76 + subfsUris.map(async ({ uri }) => { 77 + try { 78 + // Parse URI: at://did/collection/rkey 79 + const parts = uri.replace('at://', '').split('/'); 80 + const subRkey = parts[2]; 81 + 82 + await agent.com.atproto.repo.deleteRecord({ 83 + repo: auth.did, 84 + collection: 'place.wisp.subfs', 85 + rkey: subRkey 86 + }); 87 + 88 + console.log(` 🗑️ Deleted subfs: ${uri}`); 89 + logger.info(`[Site] Deleted subfs record: ${uri}`); 90 + } catch (err: any) { 91 + // Log but don't fail if subfs deletion fails 92 + console.warn(`Failed to delete subfs ${uri}:`, err?.message); 93 + logger.warn(`[Site] Failed to delete subfs ${uri}`, err); 94 + } 95 + }) 96 + ); 97 + 98 + logger.info(`[Site] Deleted ${subfsUris.length} subfs records for ${rkey}`); 45 99 } 46 100 47 101 // Delete from database