Sifa professional network API (Fastify, AT Protocol, Jetstream) sifa.id/
at main 84 lines 2.6 kB view raw
1import { Agent } from '@atproto/api'; 2import type { OAuthSession } from '@atproto/oauth-client'; 3import { eq } from 'drizzle-orm'; 4import { buildApplyWritesOp, writeToUserPds } from './pds-writer.js'; 5import type { ApplyWritesOp } from './pds-writer.js'; 6import type { Database } from '../db/index.js'; 7import { profiles, externalAccountVerifications } from '../db/schema/index.js'; 8 9const SIFA_COLLECTIONS = [ 10 'id.sifa.profile.self', 11 'id.sifa.profile.position', 12 'id.sifa.profile.education', 13 'id.sifa.profile.skill', 14 'id.sifa.profile.certification', 15 'id.sifa.profile.project', 16 'id.sifa.profile.volunteering', 17 'id.sifa.profile.publication', 18 'id.sifa.profile.course', 19 'id.sifa.profile.honor', 20 'id.sifa.profile.language', 21 'id.sifa.profile.externalAccount', 22] as const; 23 24export { SIFA_COLLECTIONS }; 25 26export async function buildPdsDeleteOps( 27 agent: Agent, 28 did: string, 29 collections: readonly string[], 30): Promise<ApplyWritesOp[]> { 31 const ops: ApplyWritesOp[] = []; 32 for (const collection of collections) { 33 let cursor: string | undefined; 34 do { 35 const existing = await agent.com.atproto.repo.listRecords({ 36 repo: did, 37 collection, 38 limit: 100, 39 cursor, 40 }); 41 for (const rec of existing.data.records) { 42 const rkey = rec.uri.split('/').pop() ?? ''; 43 if (rkey) ops.push(buildApplyWritesOp('delete', collection, rkey)); 44 } 45 cursor = existing.data.cursor; 46 } while (cursor); 47 } 48 return ops; 49} 50 51export async function wipeSifaData( 52 session: OAuthSession, 53 did: string, 54 db: Database, 55): Promise<void> { 56 const agent = new Agent(session); 57 const ops = await buildPdsDeleteOps(agent, did, SIFA_COLLECTIONS); 58 59 // applyWrites has a 200-op limit per call 60 const BATCH_SIZE = 200; 61 for (let i = 0; i < ops.length; i += BATCH_SIZE) { 62 const batch = ops.slice(i, i + BATCH_SIZE); 63 await writeToUserPds(session, did, batch); 64 } 65 66 // Update instead of delete: preserve createdAt (join date), langs, headlineOverride, 67 // aboutOverride, handle, displayName, avatarUrl, and pdsHost -- only null profile content. 68 await db 69 .update(profiles) 70 .set({ 71 headline: null, 72 about: null, 73 industry: null, 74 locationCountry: null, 75 locationRegion: null, 76 locationCity: null, 77 countryCode: null, 78 openTo: null, 79 preferredWorkplace: null, 80 updatedAt: new Date(), 81 }) 82 .where(eq(profiles.did, did)); 83 await db.delete(externalAccountVerifications).where(eq(externalAccountVerifications.did, did)); 84}