diff --git a/landing/src/knot/knot.ts b/landing/src/knot/knot.ts index bb1fbfc..897effc 100644 --- a/landing/src/knot/knot.ts +++ b/landing/src/knot/knot.ts @@ -17,39 +17,38 @@ function getAllRecords( owner: string, pds: string, collection: string, - validator: z.ZodType, - cursor?: string + validator: z.ZodType, + cursor?: string, ): Promise<{ uri: string; cid: string; value: T }[]> { return fetch( - `${pds}/xrpc/com.atproto.repo.listRecords?repo=${owner}&collection=${collection}&cursor=${cursor}` + `${pds}/xrpc/com.atproto.repo.listRecords?repo=${owner}&collection=${collection}&cursor=${cursor}`, ) .then((res) => res.json()) - - .then( - (res) => - // check that it matches the schema and assume that the schema matches T - listRecordsSchema(validator).safeParse(res).data as { - records: { - uri: string; - cid: string; - value: T; - }[]; - cursor?: string; - } - ) - .then(async (res) => [ - ...res.records, - ...(res.cursor - ? await getAllRecords(owner, pds, collection, validator, res.cursor) - : []), - ]); + .then((res) => listRecordsSchema(validator).safeParse(res).data) + .then(async (res) => + res + ? [ + ...res.records, + ...(res?.cursor + ? await getAllRecords( + owner, + pds, + collection, + validator, + res.cursor, + ) + : []), + ] + : [], + ); } export default function (): Response { // get upstream knot owner/status const owner = fetch(`${KNOT_HOST}/xrpc/sh.tangled.owner`); const status = owner.then( - async (res) => `${res.status} ${res.statusText} ${await res.clone().text()}` + async (res) => + `${res.status} ${res.statusText} ${await res.clone().text()}`, ); const ownerDid: Promise = owner @@ -60,81 +59,94 @@ export default function (): Response { const ownerPds = ownerDid .then((owner) => fetch( - `${SLINGSHOT_INSTANCE}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${owner}` - ) + `${SLINGSHOT_INSTANCE}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${owner}`, + ), ) .then((res) => res.json()) .then((x) => slingshotMiniDocSchema.safeParse(x).data?.pds); - const members = ownerPds - .then((pds) => - ownerDid.then(async (owner) => [ - owner, - ...(await getAllRecords<{ subject: string; domain: string }>( - owner, - pds, - "sh.tangled.knot.member", - z.object({ - subject: z.string(), - domain: z.string(), - }) - ).then((x) => - x - .filter((x) => x.value.domain === KNOT_NAME) - .map((x) => x.value.subject) - )), - ]) - ) - .then((dids) => - Promise.all( - dids.map((did) => - fetch( - `${SLINGSHOT_INSTANCE}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${did}` + const memberDids = Promise.all([ownerDid, ownerPds]).then( + async ([owner, pds]) => [ + owner, + ...(owner && pds + ? await getAllRecords( + owner, + pds, + "sh.tangled.knot.member", + // prettier-ignore + z.object({ subject: z.string(), domain: z.string(), }), ) - .then((res) => res.json()) - .then((res) => slingshotMiniDocSchema.safeParse(res).data) - ) - ) - ); + // ensure only members of the current knot are included + .then((member) => + member + .filter((record) => record.value.domain === KNOT_NAME) + .map((record) => record.value.subject), + ) + : []), + ], + ); - const membersRepr = members.then( - (x) => - " - " + - x - .map((x) => (x.handle !== "handle.invalid" ? x.handle : x.did)) - .join("\n - ") + const members = memberDids + .then((dids) => + dids.map((did) => + // prettier-ignore + fetch(`${SLINGSHOT_INSTANCE}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${did}`) + .then((res) => res.json()) + .then((res) => slingshotMiniDocSchema.safeParse(res).data), + ), + ) + .then((dids) => Promise.all(dids)) + .then((dids) => dids.filter((x) => x !== undefined)); + + const membersRepr = members.then((members) => + members + .map((x) => ` - ${x.handle !== "handle.invalid" ? x.handle : x.did}`) + .join("\n"), ); const repos = members.then((members) => Promise.all( members.map((member) => - getAllRecords>( + getAllRecords( member.did, member.pds, "sh.tangled.repo", - tangledRepoSchema + tangledRepoSchema, ).then((x) => x.map((x) => ({ ...x.value, user: member.handle !== "handle.invalid" ? member.handle : member.did, - })) - ) - ) - ).then((x) => x.flat().filter((x) => x.knot === KNOT_NAME)) + })), + ), + ), + ).then((x) => x.flat().filter((x) => x.knot === KNOT_NAME)), ); - const reposRepr = repos.then( - (x) => - " - " + - x - .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) - .map( - (x) => - `${x.user}/${x.name}${x.description ? `\n ${x.description}` : ""}${x.website ? `\n ${x.website}` : ""}` - ) - .join("\n - ") + - "\n" + // const reposRepr = repos.then( + // (x) => + // " - " + + // x + // .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) + // .map( + // (x) => + // `${x.user}/${x.name}${x.description ? `\n ${x.description}` : ""}${x.website ? `\n ${x.website}` : ""}`, + // ) + // .join("\n - ") + + // "\n", + // ); + const reposRepr = repos.then((repo) => + repo + .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) + .map((repo) => + [ + " - ", + `${repo.user}/${repo.name}`, + repo.description ? `\n ${repo.description}` : "", + repo.website ? `\n ${repo.website}` : "", + ].join(""), + ) + .join("\n\n"), ); const body = new ReadableStream({ @@ -157,6 +169,6 @@ export default function (): Response { "Access-Control-Allow-Origin": "*", Link: "; rel=stylesheet", }, - } + }, ); } diff --git a/landing/src/pds/pds.ts b/landing/src/pds/pds.ts index 1b81d79..b5fe518 100644 --- a/landing/src/pds/pds.ts +++ b/landing/src/pds/pds.ts @@ -17,21 +17,21 @@ async function renderUser(did: string): Promise { const handle = fetch( did.startsWith("did:plc") ? `${PLC_DIRECTORY}/${did}` - : `https://${did.replace("did:web:", "")}/.well-known/did.json` + : `https://${did.replace("did:web:", "")}/.well-known/did.json`, ) .then((res) => res.json()) .then((res) => didDocSchema.safeParse(res).data) .then((doc) => doc ? (doc.alsoKnownAs - .filter((x) => x.startsWith("at://"))[0] + .filter((x: string) => x.startsWith("at://"))[0] ?.replace("at://", "") ?? "handle.invalid") - : "handle.invalid" + : "handle.invalid", ) .catch(() => "handle.invalid"); const displayName: Promise = fetch( - `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.actor.profile&rkey=self` + `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.actor.profile&rkey=self`, ) .then((res) => res.json()) .then((data) => getRecordSchema(profileSelfSchema).safeParse(data).data) @@ -39,17 +39,19 @@ async function renderUser(did: string): Promise { .catch(() => undefined); const statusphere: Promise = fetch( - `${PDS}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=xyz.statusphere.status` + `${PDS}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=xyz.statusphere.status`, ) .then((res) => res.json()) .then((data) => listRecordsSchema(statusphereSchema).safeParse(data).data) .then((data) => - data && data.records.length > 0 ? data.records[0].value.status : undefined + data && data.records.length > 0 + ? data.records[0].value.status + : undefined, ) .catch(() => undefined); const nowPlaying = fetch( - `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=fm.teal.alpha.actor.status&rkey=self` + `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=fm.teal.alpha.actor.status&rkey=self`, ) .then((res) => res.json()) .then((data) => getRecordSchema(tealFmStatusSchema).safeParse(data).data) @@ -62,7 +64,7 @@ async function renderUser(did: string): Promise { (data.value.item.artists.length > 1 ? `${data.value.item.artists.length - 1} more artist${data.value.item.artists.length > 1 ? "s" : ""}` : "")) - : undefined + : undefined, ) .catch(() => undefined); @@ -84,22 +86,27 @@ async function renderUser(did: string): Promise { export default function (): Response { // get upstream pds status - const status = fetch(`${PDS}/xrpc/_health`).then( - async (res) => `${res.status} ${res.statusText} ${await res.text()}` - ); + const status = fetch(`${PDS}/xrpc/_health`) + .then(async (res) => `${res.status} ${res.statusText} ${await res.text()}`) + .catch((e) => + e instanceof Error + ? `ERR: ${e.name}\n\n${e.message}\n${e.cause}\n${e.stack}` + : `Err\n\n${e}`, + ); const users = fetch(`${PDS}/xrpc/com.atproto.sync.listRepos`) .then((res) => res.json()) + .catch(() => undefined) .then((data) => listReposSchema.safeParse(data).data) .then((data) => - data ? data.repos.filter((x) => x.active).map((x) => x.did) : undefined + data ? data.repos.filter((x) => x.active).map((x) => x.did) : undefined, ) .then(async (users) => users ? await Promise.all(users.map((did) => renderUser(did))).then((x) => - x.join("\n\n") + x.join("\n\n"), ) - : undefined + : undefined, ); const body = new ReadableStream({ @@ -120,6 +127,6 @@ export default function (): Response { "Access-Control-Allow-Origin": "*", Link: "; rel=stylesheet", }, - } + }, ); }