~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
53 lines · 1.9 KB Raw History
// Share an existing private gallery with another account.
//   POST /xrpc/social.grain.unspecced.addSpaceMember
//
// The space's authority holds the member list and consults it when minting a
// credential, so adding someone here is what actually grants them the gallery.
// The invite row written alongside is only so they can find it without a link.

import { defineProcedure, InvalidRequestError } from "$hatk";
import { resolveActor } from "../helpers/resolveActor.ts";
import { addGalleryReader, parseSpaceUri } from "../spaces/client.ts";
import { throwSpaceError } from "../spaces/errors.ts";

export default defineProcedure("social.grain.unspecced.addSpaceMember", async (ctx) => {
  const { ok, db, viewer, pds, input } = ctx;
  if (!viewer) throw new InvalidRequestError("Authentication required");

  const { space, actor } = input;

  let authority: string;
  try {
    ({ authority } = parseSpaceUri(space));
  } catch {
    throw new InvalidRequestError(`Not a space uri: ${space}`);
  }
  if (authority !== viewer.did) {
    throw new InvalidRequestError("Only the gallery's author can add members", "NotAuthorized");
  }
  // A handle is what a person knows; the space stores a DID. Resolved through
  // the index first, then the viewer's PDS, which can resolve a handle grain
  // has never indexed.
  const did = await resolveActor(db, pds, actor);
  if (!did) {
    throw new InvalidRequestError(`Could not find ${actor}`, "ActorNotFound");
  }
  if (did === viewer.did) {
    throw new InvalidRequestError("You already have access to your own gallery");
  }

  try {
    await addGalleryReader(pds, space, did);
  } catch (err) {
    return throwSpaceError(err, db, viewer.did);
  }

  await db.run(
    `INSERT INTO _space_invites (space, member_did, author_did, created_at)
     VALUES ($1, $2, $3, $4)
     ON CONFLICT (space, member_did) DO NOTHING`,
    [space, did, viewer.did, new Date().toISOString()],
  );

  return ok({ did });
});