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

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
156 lines · 5.4 KB Raw History
import { defineQuery, InvalidRequestError } from "$hatk";
// `GermnetworkDeclaration`, not `Declaration`: the generator disambiguates the
// name once a second `*.declaration` record exists, which fyi.opensocial's does.
import type { GrainActorProfile, GermnetworkDeclaration } from "$hatk";
import { resolveHandle } from "../helpers/resolveHandle.ts";

export default defineQuery("social.grain.unspecced.getActorProfile", async (ctx) => {
  const { ok, params, isTakendown, lookup, blobUrl, viewer: authViewer } = ctx;
  const viewer = authViewer?.did ?? params.viewer;

  const actor = await resolveHandle(ctx.db, params.actor);
  if (!actor) throw new InvalidRequestError("Actor not found");

  if (await isTakendown(actor)) {
    return ok({ did: actor, handle: actor, cid: "" });
  }

  const [
    profiles,
    germDeclarations,
    galleryCounts,
    followerCounts,
    followsCounts,
    viewerFollowingRows,
    followedByRows,
    viewerBlockingRows,
    blockedByRows,
    viewerMutedRows,
  ] = await Promise.all([
    lookup<GrainActorProfile>("social.grain.actor.profile", "did", [actor]),
    lookup<GermnetworkDeclaration>("com.germnetwork.declaration", "did", [actor]),
    // Only galleries with at least one item, matching the actor feed. Labeled
    // galleries still count: the feed hides them, but they are the author's.
    ctx.db
      .query(
        `SELECT COUNT(*) as count FROM "social.grain.gallery" g
         WHERE g.did = $1
           AND EXISTS (SELECT 1 FROM "social.grain.gallery.item" gi WHERE gi.gallery = g.uri)`,
        [actor],
      )
      .then((r: any) => {
        const m = new Map();
        m.set(actor, Number(r[0]?.count || 0));
        return m;
      }),
    ctx.db
      .query(
        `SELECT COUNT(DISTINCT did) as count FROM "social.grain.graph.follow" WHERE subject = $1`,
        [actor],
      )
      .then((r: any) => {
        const m = new Map();
        m.set(actor, Number(r[0]?.count || 0));
        return m;
      }),
    ctx.db
      .query(
        `SELECT COUNT(DISTINCT subject) as count FROM "social.grain.graph.follow" WHERE did = $1`,
        [actor],
      )
      .then((r: any) => {
        const m = new Map();
        m.set(actor, Number(r[0]?.count || 0));
        return m;
      }),
    ...(viewer && viewer !== actor
      ? [
          ctx.db.query(
            `SELECT uri FROM "social.grain.graph.follow" WHERE did = $1 AND subject = $2 LIMIT 1`,
            [viewer, actor],
          ) as Promise<{ uri: string }[]>,
          ctx.db.query(
            `SELECT uri FROM "social.grain.graph.follow" WHERE did = $1 AND subject = $2 LIMIT 1`,
            [actor, viewer],
          ) as Promise<{ uri: string }[]>,
          ctx.db.query(
            `SELECT uri FROM "social.grain.graph.block" WHERE did = $1 AND subject = $2 LIMIT 1`,
            [viewer, actor],
          ) as Promise<{ uri: string }[]>,
          ctx.db.query(
            `SELECT uri FROM "social.grain.graph.block" WHERE did = $1 AND subject = $2 LIMIT 1`,
            [actor, viewer],
          ) as Promise<{ uri: string }[]>,
          ctx.db.query(`SELECT 1 as v FROM _mutes WHERE did = $1 AND subject = $2 LIMIT 1`, [
            viewer,
            actor,
          ]) as Promise<{ v: number }[]>,
        ]
      : [
          Promise.resolve([]),
          Promise.resolve([]),
          Promise.resolve([]),
          Promise.resolve([]),
          Promise.resolve([]),
        ]),
  ]);
  const viewerFollowing = (viewerFollowingRows as { uri: string }[])[0]?.uri ?? null;
  const followedBy = (followedByRows as { uri: string }[])[0]?.uri ?? null;
  const viewerBlocking = (viewerBlockingRows as { uri: string }[])?.[0]?.uri ?? null;
  const blockedBy = !!(blockedByRows as { uri: string }[])?.[0]?.uri;
  const viewerMuted = !!(viewerMutedRows as { v: number }[])?.[0];

  const profile = profiles.get(actor);
  const germDecl = germDeclarations.get(actor);
  const messageMe = germDecl?.value.messageMe;
  const galleryCount = galleryCounts.get(actor) || 0;
  const followersCount = followerCounts.get(actor) || 0;
  const followsCount = followsCounts.get(actor) || 0;

  const repoRows = (await ctx.db.query("SELECT handle FROM _repos WHERE did = $1", [actor])) as {
    handle: string;
  }[];
  const repoHandle = repoRows[0]?.handle;

  if (!profile) {
    return ok({
      cid: "",
      did: actor,
      handle: repoHandle || actor,
      galleryCount,
      followersCount,
      followsCount,
      messageMe,
    });
  }

  return ok({
    cid: profile.cid,
    did: profile.did,
    handle: profile.handle ?? repoHandle ?? profile.did,
    displayName: profile.value.displayName,
    description: profile.value.description,
    avatar: blobUrl(profile.did, profile.value.avatar, "avatar"),
    galleryCount,
    followersCount,
    followsCount,
    createdAt: profile.value.createdAt,
    messageMe,
    ...(viewer &&
    viewer !== actor &&
    (viewerFollowing || followedBy || viewerBlocking || blockedBy || viewerMuted)
      ? {
          viewer: {
            // Hide follow relationships when either party blocks the other
            ...(!viewerBlocking && !blockedBy && viewerFollowing
              ? { following: viewerFollowing }
              : {}),
            ...(!viewerBlocking && !blockedBy && followedBy ? { followedBy: followedBy } : {}),
            ...(viewerBlocking ? { blocking: viewerBlocking } : {}),
            ...(blockedBy ? { blockedBy: true } : {}),
            ...(viewerMuted ? { muted: true } : {}),
          },
        }
      : {}),
  });
});