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("social.grain.actor.profile", "did", [actor]), lookup("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 } : {}), }, } : {}), }); });