diff --git a/web/src/routes/[handle]/+layout.ts b/web/src/routes/[handle]/+layout.ts index c6b58d25..003b3367 100644 --- a/web/src/routes/[handle]/+layout.ts +++ b/web/src/routes/[handle]/+layout.ts @@ -1,14 +1,38 @@ import { error, redirect } from "@sveltejs/kit"; import { createBobbinClient } from "$lib/api/client"; -import { resolveMiniDoc } from "$lib/api/identity"; +import type { MiniDoc } from "$lib/api/identity"; import { getProfile, type ProfileRecord } from "$lib/api/records"; -import { count } from "$lib/api/count"; -import { parallel, toHttpError, httpStatusFor } from "$lib/api/load"; +import { + enrich, + countOf, + viewerUriOf, + TYPE_COUNT, + TYPE_VIEWER, + type LinkDescriptor, + type LinkSource +} from "$lib/api/enrich"; +import { toHttpError, httpStatusFor } from "$lib/api/load"; import { ClientResponseError } from "$lib/api/client"; -import { getFollowRkey } from "$lib/api/graph"; +import { rkeyFromUri } from "$lib/api/uri"; import type { ProfileCounts } from "$lib/components/profile/types"; import type { LayoutLoad } from "./$types"; +const REPOS: LinkSource = "sh.tangled.repo:subject"; +const STRINGS: LinkSource = "sh.tangled.string:subject"; +const STARS: LinkSource = "sh.tangled.feed.star:.repo"; +const FOLLOWERS: LinkSource = "sh.tangled.graph.follow:subject"; +const FOLLOWING: LinkSource = "sh.tangled.graph.follow:.repo"; +const VOUCHES: LinkSource = "sh.tangled.graph.vouch:subject"; + +const COUNT_DESCRIPTORS: LinkDescriptor[] = [ + REPOS, + STRINGS, + STARS, + FOLLOWERS, + FOLLOWING, + VOUCHES +].map((source) => ({ source, type: TYPE_COUNT })); + export const load: LayoutLoad = async (event) => { const parent = await event.parent(); const identifier = decodeURIComponent(event.params.handle); @@ -19,9 +43,17 @@ export const load: LayoutLoad = async (event) => { } const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch }); - const doc = await resolveMiniDoc(ctx, identifier).catch((cause) => - toHttpError(cause, "Could not resolve user") - ); + const viewerDid = parent.auth?.did; + const resolved = await enrich(ctx, { + xrpc: "com.bad-example.identity.resolveMiniDoc", + params: { identifier }, + enrich: [ + ...COUNT_DESCRIPTORS, + ...(viewerDid ? [{ source: FOLLOWERS, type: TYPE_VIEWER }] : []) + ], + ...(viewerDid ? { viewer: viewerDid } : {}) + }).catch((cause) => toHttpError(cause, "Could not resolve user")); + const doc = resolved.output; // redirects dids and stale handles to the canonical handle url const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null; @@ -30,38 +62,25 @@ export const load: LayoutLoad = async (event) => { } const did = doc.did; - const viewerDid = parent.auth?.did; - - let profile: ProfileRecord | null = null; - try { - profile = (await getProfile(ctx, did)).value; - } catch (cause) { - if (!(cause instanceof ClientResponseError && httpStatusFor(cause) === 404)) { - toHttpError(cause, "Could not load profile"); - } - } - const raw = await parallel({ - repos: count(ctx, "sh.tangled.repo.countRepos", did), - strings: count(ctx, "sh.tangled.string.countStrings", did), - stars: count(ctx, "sh.tangled.feed.countStarsBy", did), - followers: count(ctx, "sh.tangled.graph.countFollows", did), - following: count(ctx, "sh.tangled.graph.countFollowsBy", did), - vouches: count(ctx, "sh.tangled.graph.countVouches", did), - viewerFollowRkey: - viewerDid && viewerDid !== did - ? getFollowRkey(ctx, viewerDid, did).catch(() => null) - : Promise.resolve(null) - }); + const profile: ProfileRecord | null = await getProfile(ctx, did) + .then((view) => view.value) + .catch((cause) => { + if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null; + return toHttpError(cause, "Could not load profile"); + }); const counts: ProfileCounts = { - repos: raw.repos.count, - strings: raw.strings.count, - stars: raw.stars.count, - followers: raw.followers.count, - following: raw.following.count, - vouches: raw.vouches.count + repos: countOf(resolved.data, did, REPOS), + strings: countOf(resolved.data, did, STRINGS), + stars: countOf(resolved.data, did, STARS), + followers: countOf(resolved.data, did, FOLLOWERS), + following: countOf(resolved.data, did, FOLLOWING), + vouches: countOf(resolved.data, did, VOUCHES) }; + const viewerFollowUri = + viewerDid && viewerDid !== did ? viewerUriOf(resolved.data, did, FOLLOWERS) : null; + const viewerFollowRkey = viewerFollowUri ? rkeyFromUri(viewerFollowUri) : viewerFollowUri; const notJoined = !profile && Object.values(counts).every((n) => n === 0); @@ -69,7 +88,7 @@ export const load: LayoutLoad = async (event) => { identity: { did, handle: doc.handle }, profile, counts, - viewerFollowRkey: raw.viewerFollowRkey, + viewerFollowRkey, notJoined }; };