a tool for shared writing and social publishing
1import { Agent, lexToJson } from "@atproto/api";
2import { ThreadViewPost } from "@atproto/api/dist/client/types/app/bsky/feed/defs";
3import { cookies } from "next/headers";
4import { NextRequest } from "next/server";
5import { createOauthClient } from "src/atproto-oauth";
6import { supabaseServerClient } from "supabase/serverClient";
7
8export const runtime = "nodejs";
9
10async function getAuthenticatedAgent(): Promise<Agent | null> {
11 try {
12 const cookieStore = await cookies();
13 const authToken =
14 cookieStore.get("auth_token")?.value ||
15 cookieStore.get("external_auth_token")?.value;
16
17 if (!authToken || authToken === "null") return null;
18
19 const { data } = await supabaseServerClient
20 .from("email_auth_tokens")
21 .select("identities(atp_did)")
22 .eq("id", authToken)
23 .eq("confirmed", true)
24 .single();
25
26 const did = data?.identities?.atp_did;
27 if (!did) return null;
28
29 const oauthClient = await createOauthClient();
30 const session = await oauthClient.restore(did);
31 return new Agent(session);
32 } catch (error) {
33 console.error("Failed to get authenticated agent:", error);
34 return null;
35 }
36}
37
38export async function GET(req: NextRequest) {
39 try {
40 const searchParams = req.nextUrl.searchParams;
41 const uri = searchParams.get("uri");
42 const depth = searchParams.get("depth");
43 const parentHeight = searchParams.get("parentHeight");
44
45 if (!uri) {
46 return Response.json(
47 { error: "uri parameter is required" },
48 { status: 400 },
49 );
50 }
51
52 // Try to use authenticated agent if user is logged in, otherwise fall back to public API
53 let agent = await getAuthenticatedAgent();
54 if (!agent) {
55 agent = new Agent({
56 service: "https://public.api.bsky.app",
57 });
58 }
59
60 const response = await agent.getPostThread({
61 uri,
62 depth: depth ? parseInt(depth, 10) : 6,
63 parentHeight: parentHeight ? parseInt(parentHeight, 10) : 80,
64 });
65
66 const thread = lexToJson(response.data.thread);
67
68 return Response.json(thread, {
69 headers: {
70 // Cache for 5 minutes on CDN, allow stale content for 1 hour while revalidating
71 "Cache-Control": "public, s-maxage=300, stale-while-revalidate=3600",
72 },
73 });
74 } catch (error) {
75 console.error("Error fetching Bluesky thread:", error);
76 return Response.json({ error: "Failed to fetch thread" }, { status: 500 });
77 }
78}