// Withdraw the viewer's own gallery from a group's pool. // POST /xrpc/social.grain.unspecced.deletePoolGallery // // The counterpart to createPoolGallery, and the same shape in reverse: a // gallery in a pool is the author's own records — the gallery, its photos, the // items joining them — living in the group's space rather than the public // repo. Putting them there should not mean being unable to take them back. // // Author-scoped by construction, not by a check: every write names // `repo: viewer.did`, so this can only ever remove the viewer's own records. // Evicting somebody else's gallery is the group's act and wants its own route. // // The rkeys are read rather than derived. createPoolGallery happens to name a // photo `${rkey}-${index}`, but a gallery edited later need not keep that, and // deleting by a guessed name would silently leave records behind in a space // nothing indexes. import { defineProcedure, InvalidRequestError } from "$hatk"; import { listSpaceRecords, poolUri } from "../spaces/client.ts"; import { throwSpaceError } from "../spaces/errors.ts"; interface ItemValue { gallery?: string; item?: string; } export default defineProcedure("social.grain.unspecced.deletePoolGallery", async (ctx) => { const { ok, db, viewer, pds, input } = ctx; if (!viewer) throw new InvalidRequestError("Authentication required"); const { group, rkey } = input; const space = poolUri(group); const selfUri = (collection: string, key: string) => `at://${viewer.did}/${collection}/${key}`; const galleryUri = selfUri("social.grain.gallery", rkey); let items: { rkey: string; value: unknown }[]; try { items = await listSpaceRecords(ctx, viewer.did, space, viewer.did, "social.grain.gallery.item"); } catch (err) { return throwSpaceError(err, db, viewer.did); } // The items that join this gallery to its photos, and the photos they name. const mine = items.filter((i) => (i.value as ItemValue)?.gallery === galleryUri); const photoRkeys = new Set(); for (const i of mine) { const item = (i.value as ItemValue)?.item; if (typeof item === "string" && item.startsWith(`at://${viewer.did}/social.grain.photo/`)) { photoRkeys.add(item.split("/").pop()!); } } const del = (collection: string, key: string) => ({ $type: "com.atproto.space.applyWrites#delete", collection, rkey: key, }); const writes = [ del("social.grain.gallery", rkey), ...mine.map((i) => del("social.grain.gallery.item", i.rkey)), ...[...photoRkeys].map((k) => del("social.grain.photo", k)), ]; try { await pds("com.atproto.space.applyWrites", { method: "POST", body: { space, repo: viewer.did, writes }, }); } catch (err) { return throwSpaceError(err, db, viewer.did); } return ok({ deleted: writes.length }); });