this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix fetch actor fetches from other repos

Hilke Ros a829aa1d 902318f1

+178 -52
+44 -19
app/api/artist/route.ts
··· 1 1 import { NextRequest, NextResponse } from "next/server"; 2 - import { Client } from "@atproto/lex"; 2 + import { Client, type AtIdentifierString } from "@atproto/lex"; 3 3 import { getSession } from "@/lib/auth/session"; 4 4 import { getOAuthClient } from "@/lib/auth/client"; 5 + import { fetchFirstRecordFromRepo } from "@/lib/atproto-actors"; 5 6 import * as ch from "@/src/lexicons/ch"; 6 7 7 8 export async function GET(request: NextRequest) { ··· 11 12 } 12 13 13 14 const didParam = request.nextUrl.searchParams.get("did"); 15 + const repoDid = (didParam || session.did) as AtIdentifierString; 14 16 15 17 try { 16 - const client = await getOAuthClient(); 17 - const oauthSession = await client.restore(session.did); 18 - const lexClient = new Client(oauthSession); 18 + if (didParam && repoDid !== session.did) { 19 + const externalRecord = await fetchFirstRecordFromRepo( 20 + ch.indiemusi.alpha.actor.artist, 21 + repoDid, 22 + ); 19 23 20 - const repo = (didParam || session.did) as any; 21 - const records = await lexClient.list(ch.indiemusi.alpha.actor.artist, { 22 - limit: 10, 23 - repo, 24 - }); 24 + if (externalRecord) { 25 + const value = externalRecord.value as Record<string, unknown>; 26 + const artist = { 27 + ...value, 28 + $type: typeof value?.$type === "string" ? value.$type : "ch.indiemusi.alpha.actor.artist", 29 + }; 30 + 31 + return NextResponse.json({ 32 + success: true, 33 + artist, 34 + uri: externalRecord.uri, 35 + did: repoDid, 36 + }); 37 + } 38 + } else { 39 + const client = await getOAuthClient(); 40 + const oauthSession = await client.restore(session.did); 41 + const lexClient = new Client(oauthSession); 25 42 26 - if (records.records.length > 0) { 27 - const record = records.records[0]; 28 - const artist = { 29 - ...(record.value || {}), 30 - $type: record.value?.$type || "ch.indiemusi.alpha.actor.artist", 31 - }; 32 - return NextResponse.json({ 33 - success: true, 34 - artist, 35 - uri: record.uri, 43 + const records = await lexClient.list(ch.indiemusi.alpha.actor.artist, { 44 + limit: 10, 45 + repo: repoDid, 36 46 }); 47 + 48 + if (records.records.length > 0) { 49 + const record = records.records[0]; 50 + const artist = { 51 + ...(record.value || {}), 52 + $type: record.value?.$type || "ch.indiemusi.alpha.actor.artist", 53 + }; 54 + return NextResponse.json({ 55 + success: true, 56 + artist, 57 + uri: record.uri, 58 + did: repoDid, 59 + }); 60 + } 37 61 } 38 62 39 63 return NextResponse.json({ 40 64 success: true, 41 65 artist: null, 42 66 uri: null, 67 + did: repoDid, 43 68 }); 44 69 } catch (error) { 45 70 console.error("Failed to fetch artist:", error);
+35 -16
app/api/master-owner/route.ts
··· 1 1 import { NextRequest, NextResponse } from "next/server"; 2 - import { Client } from "@atproto/lex"; 2 + import { Client, type AtIdentifierString } from "@atproto/lex"; 3 3 import { getSession } from "@/lib/auth/session"; 4 4 import { getOAuthClient } from "@/lib/auth/client"; 5 + import { fetchFirstRecordFromRepo } from "@/lib/atproto-actors"; 5 6 import * as ch from "@/src/lexicons/ch"; 6 7 7 8 export async function GET(request: NextRequest) { ··· 11 12 } 12 13 13 14 const didParam = request.nextUrl.searchParams.get("did"); 15 + const repoDid = (didParam || session.did) as AtIdentifierString; 14 16 15 17 try { 16 - const client = await getOAuthClient(); 17 - const oauthSession = await client.restore(session.did); 18 - const lexClient = new Client(oauthSession); 18 + if (didParam && repoDid !== session.did) { 19 + const externalRecord = await fetchFirstRecordFromRepo( 20 + ch.indiemusi.alpha.actor.masterOwner, 21 + repoDid, 22 + ); 23 + if (externalRecord) { 24 + return NextResponse.json({ 25 + success: true, 26 + masterOwner: externalRecord.value, 27 + uri: externalRecord.uri, 28 + did: repoDid, 29 + }); 30 + } 31 + } else { 32 + const client = await getOAuthClient(); 33 + const oauthSession = await client.restore(session.did); 34 + const lexClient = new Client(oauthSession); 19 35 20 - const records = await lexClient.list(ch.indiemusi.alpha.actor.masterOwner, { 21 - limit: 10, 22 - repo: (didParam || session.did) as any, 23 - }) 36 + const records = await lexClient.list(ch.indiemusi.alpha.actor.masterOwner, { 37 + limit: 10, 38 + repo: repoDid, 39 + }); 24 40 25 - if (records.records.length > 0) { 26 - const record = records.records[0]; 27 - console.log("Fetched master owner records:", record.value); 28 - return NextResponse.json({ 29 - success: true, 30 - masterOwner: record.value, 31 - uri: record.uri, 32 - }); 41 + if (records.records.length > 0) { 42 + const record = records.records[0]; 43 + console.log("Fetched master owner records:", record.value); 44 + return NextResponse.json({ 45 + success: true, 46 + masterOwner: record.value, 47 + uri: record.uri, 48 + did: repoDid, 49 + }); 50 + } 33 51 } 34 52 35 53 return NextResponse.json({ 36 54 success: true, 37 55 masterOwner: null, 56 + did: repoDid, 38 57 }); 39 58 } catch (error) { 40 59 console.error("Failed to fetch master owner:", error);
+32 -16
app/api/publishing-owner/route.ts
··· 2 2 import { Client, AtIdentifierString } from "@atproto/lex"; 3 3 import { getSession } from "@/lib/auth/session"; 4 4 import { getOAuthClient } from "@/lib/auth/client"; 5 + import { fetchFirstRecordFromRepo } from "@/lib/atproto-actors"; 5 6 import * as ch from "@/src/lexicons/ch"; 6 7 import { cleanIPI, isValidIPI, IPI_ERROR_MESSAGE } from "@/lib/validation"; 7 8 ··· 15 16 const did = request.nextUrl.searchParams.get("did"); 16 17 const repoDid = (did || session.did) as AtIdentifierString; 17 18 18 - const client = await getOAuthClient(); 19 - const oauthSession = await client.restore(session.did); 20 - const lexClient = new Client(oauthSession); 21 - 22 - const query = await lexClient.list(ch.indiemusi.alpha.actor.publishingOwner, { 23 - limit: 10, 24 - repo: repoDid, 25 - }) 19 + if (did && repoDid !== session.did) { 20 + const externalRecord = await fetchFirstRecordFromRepo( 21 + ch.indiemusi.alpha.actor.publishingOwner, 22 + repoDid, 23 + ); 24 + if (externalRecord) { 25 + return NextResponse.json({ 26 + success: true, 27 + publishingOwner: externalRecord.value, 28 + uri: externalRecord.uri, 29 + did: repoDid, 30 + }); 31 + } 32 + } else { 33 + const client = await getOAuthClient(); 34 + const oauthSession = await client.restore(session.did); 35 + const lexClient = new Client(oauthSession); 26 36 27 - if (query.records.length > 0) { 28 - const record = query.records[0]; 29 - console.log("Fetched publishing owner records:", record.value); 30 - return NextResponse.json({ 31 - success: true, 32 - publishingOwner: record.value, 33 - uri: record.uri, 34 - did: repoDid, 37 + const query = await lexClient.list(ch.indiemusi.alpha.actor.publishingOwner, { 38 + limit: 10, 39 + repo: repoDid, 35 40 }); 41 + 42 + if (query.records.length > 0) { 43 + const record = query.records[0]; 44 + console.log("Fetched publishing owner records:", record.value); 45 + return NextResponse.json({ 46 + success: true, 47 + publishingOwner: record.value, 48 + uri: record.uri, 49 + did: repoDid, 50 + }); 51 + } 36 52 } 37 53 38 54 return NextResponse.json({
+67 -1
lib/atproto-actors.ts
··· 5 5 avatar?: string; 6 6 } 7 7 8 + import { Client, type AtIdentifierString } from "@atproto/lex"; 9 + 8 10 const PUBLIC_ACTOR_API_BASE = "https://public.api.bsky.app/xrpc/app.bsky.actor"; 9 11 10 12 function getAtprotoHandleFromAlsoKnownAs(alsoKnownAs: unknown): string | undefined { ··· 19 21 return aka ? aka.slice(5) : undefined; 20 22 } 21 23 22 - function getDidDocumentUrl(did: string): string | null { 24 + export function getDidDocumentUrl(did: string): string | null { 23 25 if (did.startsWith("did:plc:")) { 24 26 return `https://plc.directory/${did}`; 25 27 } ··· 41 43 } 42 44 43 45 return null; 46 + } 47 + 48 + export async function resolvePdsEndpoint(did: string): Promise<string | null> { 49 + const didDocumentUrl = getDidDocumentUrl(did); 50 + if (!didDocumentUrl) { 51 + return null; 52 + } 53 + 54 + const res = await fetch(didDocumentUrl, { 55 + method: "GET", 56 + headers: { 57 + Accept: "application/json", 58 + }, 59 + cache: "no-store", 60 + }); 61 + 62 + if (!res.ok) { 63 + return null; 64 + } 65 + 66 + const didDocument = await res.json(); 67 + const services = Array.isArray(didDocument?.service) ? didDocument.service : []; 68 + const pds = services.find( 69 + (entry: { type?: unknown; serviceEndpoint?: unknown }) => 70 + entry?.type === "AtprotoPersonalDataServer" && 71 + typeof entry?.serviceEndpoint === "string", 72 + ); 73 + 74 + return typeof pds?.serviceEndpoint === "string" 75 + ? pds.serviceEndpoint.replace(/\/$/, "") 76 + : null; 77 + } 78 + 79 + export async function fetchFirstRecordFromRepo( 80 + collection: { 81 + $type: string; 82 + [key: string]: unknown; 83 + }, 84 + repoDid: string, 85 + ): Promise<{ uri: string; value: unknown } | null> { 86 + const pdsEndpoint = await resolvePdsEndpoint(repoDid); 87 + if (!pdsEndpoint) { 88 + return null; 89 + } 90 + 91 + try { 92 + const repoClient = new Client(pdsEndpoint); 93 + const query = await repoClient.list(collection as any, { 94 + limit: 10, 95 + repo: repoDid as AtIdentifierString, 96 + }); 97 + 98 + if (query.records.length === 0) { 99 + return null; 100 + } 101 + 102 + const record = query.records[0]; 103 + return { 104 + uri: record.uri, 105 + value: record.value, 106 + }; 107 + } catch { 108 + return null; 109 + } 44 110 } 45 111 46 112 async function resolveHandleFromDid(did: string): Promise<string | undefined> {