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

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
73 lines · 3.0 KB Raw History
// Every gallery in a group's pool.
//   GET /xrpc/social.grain.unspecced.listPoolGalleries?group=did:...
//
// Nothing here comes from the index, because nothing in a space ever reaches a
// firehose to be indexed from. The read is `readPool`: the space names the
// repos that have written into it, each is read on the host that holds it, and
// all of it needs a credential the group's host issues to a member — so a
// viewer who is not one gets a 401 and no gallery at all, which is the whole
// feature.
//
// `cid` rides along per cover so the client can ask /space-blob for the
// bytes; they cannot come from the CDN.

import { defineQuery, InvalidRequestError } from "$hatk";
import type { GrainActorProfile } from "$hatk";
import { lookupHandles } from "../helpers/lookupHandles.ts";
import { readPool } from "../helpers/pool.ts";
import { poolUri } from "../spaces/client.ts";
import { throwSpaceError } from "../spaces/errors.ts";

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

  const group = params.group as string;
  const limit = Math.min(Number(params.limit) || 50, 100);

  try {
    const galleries = (await readPool(ctx, viewer.did, group)).slice(0, limit);

    // Whose gallery it is, in the words grain uses everywhere else. A DID
    // grain has never indexed keeps its row — the gallery is still readable,
    // and in a private pool an unindexed author is an ordinary case.
    const dids = [...new Set(galleries.map((g) => g.did))];
    const [handles, profiles] = await Promise.all([
      lookupHandles(db, dids),
      ctx.lookup<GrainActorProfile>("social.grain.actor.profile", "did", dids),
    ]);

    return ok({
      space: poolUri(group),
      galleries: galleries.map((g) => {
        const profile = profiles.get(g.did);
        const name = profile?.value.displayName;
        // Their Grain profile picture, which is a public record of their own:
        // being in a private pool says nothing about a person's face.
        const avatar = profile?.value.avatar
          ? ctx.blobUrl(g.did, profile.value.avatar, "avatar")
          : undefined;
        const cover = g.photos[0];
        return {
          space: g.space,
          did: g.did,
          rkey: g.rkey,
          title: g.title,
          ...(g.description ? { description: g.description } : {}),
          ...(g.createdAt ? { createdAt: g.createdAt } : {}),
          uri: g.uri,
          photoCount: g.photoCount,
          favCount: g.favCount,
          ...(g.viewerFav ? { viewerFav: g.viewerFav } : {}),
          commentCount: g.commentCount,
          ...(cover ? { cover } : {}),
          ...(handles.get(g.did) ? { handle: handles.get(g.did) } : {}),
          ...(name ? { displayName: name } : {}),
          ...(avatar ? { avatar } : {}),
        };
      }),
    });
  } catch (err) {
    return throwSpaceError(err, db, viewer.did);
  }
});