tangled
alpha
login
or
join now
modamo.xyz
/
bambu
1
fork
atom
this repo has no description
1
fork
atom
overview
issues
pulls
pipelines
Display book covers
modamo-gh
1 month ago
e109fea9
53e36fd7
+68
-11
4 changed files
expand all
collapse all
unified
split
app
api
blob
route.ts
library
page.tsx
lib
auth
client.ts
next.config.ts
+37
app/api/blob/route.ts
···
1
1
+
import { getOAuthClient } from "@/lib/auth/client";
2
2
+
import { getSession } from "@/lib/auth/session";
3
3
+
import { Agent } from "@atproto/api";
4
4
+
import { NextRequest, NextResponse } from "next/server";
5
5
+
6
6
+
export async function GET(request: NextRequest) {
7
7
+
const { searchParams } = new URL(request.url);
8
8
+
const cid = searchParams.get("cid");
9
9
+
const did = searchParams.get("did");
10
10
+
11
11
+
if (!cid || !did) {
12
12
+
return new NextResponse("Missing cid or did", { status: 400 });
13
13
+
}
14
14
+
15
15
+
const client = await getOAuthClient();
16
16
+
const session = await client.restore(did);
17
17
+
18
18
+
if (!session) {
19
19
+
return new NextResponse("Unauthorized", { status: 401 });
20
20
+
}
21
21
+
22
22
+
try {
23
23
+
const agent = new Agent(session);
24
24
+
const response = await agent.com.atproto.sync.getBlob({ cid, did });
25
25
+
26
26
+
return new NextResponse(response.data, {
27
27
+
headers: {
28
28
+
"Cache-Control": "public, max-age=31536000, immutable",
29
29
+
"Content-Type": response.headers["content-type"] || "image/jpeg"
30
30
+
}
31
31
+
});
32
32
+
} catch (error) {
33
33
+
console.error("Error fetching blob:", error);
34
34
+
35
35
+
return new NextResponse("Failed to fetch blob", { status: 500 });
36
36
+
}
37
37
+
}
+24
-6
app/library/page.tsx
···
25
25
repo: session.did
26
26
});
27
27
28
28
-
books = booksResponse.data.records;
28
28
+
books = booksResponse.data.records.filter(
29
29
+
(book) =>
30
30
+
book.value.status === "buzz.bookhive.defs#reading" ||
31
31
+
book.value.status === "buzz.bookhive.defs#wantToRead"
32
32
+
);
29
33
} catch (error) {
30
34
console.error("Error fetching profile:", error);
31
35
···
39
43
<h1 className="text-amber-100 text-4xl">bambü</h1>
40
44
<div className="h-16 relative w-16">
41
45
<Image
42
42
-
alt="User avatar"
46
46
+
alt={`Profile picture for ${profile?.handle}`}
43
47
className="object-cover rounded-full"
44
48
fill
45
49
priority
···
47
51
/>
48
52
</div>
49
53
</header>
50
50
-
<div className="bg-emerald-900 rounded-2xl row-span-9">
51
51
-
{books?.map((book, index) => (
52
52
-
<p key={index}>{JSON.stringify(book)}</p>
53
53
-
))}
54
54
+
<div className="auto-rows-min bg-emerald-900 gap-4 grid grid-cols-5 overflow-y-scroll p-4 rounded-2xl row-span-9">
55
55
+
{books?.map((book) => {
56
56
+
return (
57
57
+
<div
58
58
+
className="aspect-2/3 overflow-hidden relative rounded-2xl"
59
59
+
key={book.value.hiveId}
60
60
+
>
61
61
+
<Image
62
62
+
alt={`Book cover for ${book.value.title} by ${book.value.authors}`}
63
63
+
className="object-cover"
64
64
+
fill
65
65
+
src={`/api/blob?cid=${book.value.cover?.ref?.toString()}&did=${
66
66
+
session.did
67
67
+
}`}
68
68
+
/>
69
69
+
</div>
70
70
+
);
71
71
+
})}
54
72
</div>
55
73
</main>
56
74
);
+6
-5
lib/auth/client.ts
···
9
9
import { getDB } from "../db";
10
10
11
11
declare global {
12
12
-
var _oauthClient: NodeOAuthClient | undefined
12
12
+
var _oauthClient: NodeOAuthClient | undefined;
13
13
}
14
14
15
15
const PRIVATE_KEY = process.env.PRIVATE_KEY;
16
16
const PUBLIC_URL = process.env.PUBLIC_URL;
17
17
18
18
-
export const SCOPE = "atproto";
18
18
+
export const SCOPE =
19
19
+
"atproto rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app#bsky_appview";
19
20
20
21
const getClientMetadata = () => {
21
22
if (PUBLIC_URL) {
···
49
50
};
50
51
51
52
export const getOAuthClient = async () => {
52
52
-
if(globalThis._oauthClient){
53
53
-
return globalThis._oauthClient
53
53
+
if (globalThis._oauthClient) {
54
54
+
return globalThis._oauthClient;
54
55
}
55
56
56
57
globalThis._oauthClient = new NodeOAuthClient({
···
104
105
.select("value")
105
106
.where("key", "=", key)
106
107
.executeTakeFirst();
107
107
-
108
108
+
108
109
return row ? JSON.parse(row.value) : undefined;
109
110
},
110
111
set: async (key: string, value: NodeSavedState) => {
+1
next.config.ts
···
2
2
3
3
const nextConfig: NextConfig = {
4
4
images: {
5
5
+
localPatterns: [{ pathname: "/api/blob" }],
5
6
remotePatterns: [new URL("https://cdn.bsky.app/img/**")]
6
7
}
7
8
};