Something went wrong. Try again.
FlockOff is a mass-block tool for the Atmosphere flockoff.app
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107import { requireSession } from '@/lib/session'import { restoreAgent } from '@/lib/oauth'import { requireListScopes } from '@/lib/scope-gate'import { getBlockEvent, updateBlockEvent } from '@/lib/lexicon'import { canStartJobForDid, MAX_ACTIVE_JOBS_PER_USER, startAppendJob, isEventJobRunning } from '@/lib/queue'import { generateId } from '@/lib/utils'
// POST /api/blocks/append// Blocks additional accounts and appends them to an existing block event record.// If the event has a listUri, newly blocked accounts are also added to that list.
export async function POST(request: Request) { let session: { did: string } try { session = await requireSession() } catch { return Response.json({ error: 'Not authenticated' }, { status: 401 }) }
let body: { eventRkey: string; selectedDids: string[] } try { body = await request.json() } catch { return Response.json({ error: 'Invalid request body' }, { status: 400 }) }
const { eventRkey, selectedDids } = body if (!eventRkey || !selectedDids?.length) { return Response.json({ error: 'eventRkey and selectedDids are required' }, { status: 400 }) }
try { const agent = await restoreAgent(session.did) const { record } = await getBlockEvent(agent, session.did, eventRkey)
if (record.pendingDids?.length) { return Response.json( { error: 'This event has pending accounts. Resume it before appending new accounts.' }, { status: 409 } ) }
// Safety: filter out any DIDs already processed to avoid duplicate blocks const alreadyProcessed = new Set([ ...(record.blockedDids ?? []), ...(record.failedDids ?? []), ...(record.pendingDids ?? []), ]) const didsToBlock = selectedDids.filter((did) => !alreadyProcessed.has(did))
if (didsToBlock.length === 0) { return Response.json({ error: 'All selected accounts are already processed' }, { status: 400 }) }
if (isEventJobRunning(eventRkey)) { return Response.json( { error: 'A block job is already running for this event. Wait for it to finish before starting another.' }, { status: 409 } ) }
if (!canStartJobForDid(session.did)) { return Response.json( { error: `Too many active block jobs. Wait for one to finish before starting another. Limit: ${MAX_ACTIVE_JOBS_PER_USER}.` }, { status: 429 } ) }
// The job writes listitem records when the event has a shared list — check // scopes before persisting anything so the job can't fail silently mid-run. // Checked after the job-state gates so those errors take precedence. if (record.listUri) { const scopeGate = await requireListScopes(session.did) if (scopeGate) return scopeGate }
await updateBlockEvent(agent, session.did, eventRkey, { ...record, pendingDids: [...(record.pendingDids ?? []), ...didsToBlock], stats: { total: (record.blockedDids ?? []).length + (record.failedDids ?? []).length + didsToBlock.length, completed: (record.blockedDids ?? []).length, failed: (record.failedDids ?? []).length, }, })
const jobId = generateId() await startAppendJob( jobId, session.did, eventRkey, didsToBlock, { blockedDids: record.blockedDids ?? [], blockedRkeys: record.blockedRkeys ?? [], failedDids: record.failedDids ?? [], }, record.listUri // pass through so the job also adds listitems if a list exists )
return Response.json({ jobId, eventRkey, total: didsToBlock.length }) } catch (err) { console.error('[api/blocks/append]', err) return Response.json({ error: 'Failed to start append job' }, { status: 500 }) }}