alpha
Login
or
Join now
pds.dad
/
my-website
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
WIP: My personal website
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
fixed some caching of books
author
Bailey Townsend
date
2 weeks ago
(Jun 7, 2026, 1:52 PM -0500)
commit
d91c623c
d91c623c3c622f63bd32a5f2d14fcfac72060942
parent
96ba4178
96ba4178a2150f03ee067b45fe18b831a4a7109c
+50
-36
3 changed files
Expand all
Collapse all
Unified
Split
src
lib
server
atproto
books.ts
client.ts
routes
history
books
+page.svelte
+39
-28
src/lib/server/atproto/books.ts
Reviewed
···
7
7
8
8
type BookEntry = { value: BookRecord; uri: AtUriString };
9
9
10
10
+
type BookHiveBookView = {
11
11
+
status?: string;
12
12
+
title: string;
13
13
+
authors: string;
14
14
+
hiveId: string;
15
15
+
cover: string | null;
16
16
+
finishedAt: string | null;
17
17
+
createdAt: string | null;
18
18
+
stars: number | null;
19
19
+
};
20
20
+
10
21
const READING = 'buzz.bookhive.defs#reading';
11
22
const FINISHED = 'buzz.bookhive.defs#finished';
12
23
13
24
/**
14
25
* The currently-reading and finished views both come from the same bookhive
15
15
-
* collection, so we list it once and cache the raw records. Cached under the
16
16
-
* the default ttl 1 day
26
26
+
* collection, so we list it once and cache the normalized records. Cached
27
27
+
* under the default ttl 1 day.
17
28
*/
18
18
-
async function listBooks(repo: RepoInfo): Promise<BookEntry[]> {
19
19
-
const cached = cache.get('books') as BookEntry[] | null;
29
29
+
async function listBooks(repo: RepoInfo): Promise<BookHiveBookView[]> {
30
30
+
const cached = cache.get('books') as BookHiveBookView[] | null;
20
31
if (cached) return cached;
21
32
22
33
const client = createClient(repo.pds);
···
25
36
limit: 100
26
37
});
27
38
const records = res.records as ReadonlyArray<BookEntry>;
28
28
-
const books = [...records];
39
39
+
const books = records.map(({ value: v }) => ({
40
40
+
status: v.status,
41
41
+
title: v.title,
42
42
+
authors: v.authors.split('\t').join(', '),
43
43
+
hiveId: v.hiveId,
44
44
+
cover: v.cover ? blobUrl(repo.pds, repo.did, v.cover) : null,
45
45
+
finishedAt: v.finishedAt ?? null,
46
46
+
createdAt: v.createdAt ?? null,
47
47
+
stars: v.stars ?? null
48
48
+
}));
29
49
cache.set('books', books);
30
50
return books;
31
51
}
···
35
55
if (!repo) return null;
36
56
37
57
try {
38
38
-
const reading = (await listBooks(repo)).find((r) => r.value.status === READING);
58
58
+
const reading = (await listBooks(repo)).find((b) => b.status === READING);
39
59
if (!reading) return null;
40
60
41
41
-
const v = reading.value;
42
42
-
const test = blobUrl(repo.pds, repo.did, v.cover);
43
43
-
console.log(test);
44
61
return {
45
45
-
title: v.title,
46
46
-
bookUrl: `https://bookhive.buzz/books/${v.hiveId}`,
47
47
-
authors: v.authors.split('\t').join(', '),
48
48
-
cover: v.cover ? blobUrl(repo.pds, repo.did, v.cover) : null
62
62
+
title: reading.title,
63
63
+
bookUrl: `https://bookhive.buzz/books/${reading.hiveId}`,
64
64
+
authors: reading.authors,
65
65
+
cover: reading.cover
49
66
};
50
67
} catch (error) {
51
68
console.warn('Failed to fetch currently reading book:', error);
···
54
71
}
55
72
56
73
export async function fetchFinishedBooks(): Promise<FinishedBook[]> {
57
57
-
const cached = cache.get('finishedBooks') as FinishedBook[] | null;
58
58
-
if (cached) return cached;
59
59
-
60
74
const repo = getRepoInfo('Books history');
61
75
if (!repo) return [];
62
76
63
77
try {
64
64
-
const finished = (await listBooks(repo))
65
65
-
.filter((r) => r.value.status === FINISHED)
66
66
-
.map(({ value }) => ({
67
67
-
title: value.title,
68
68
-
authors: value.authors.split('\t').join(', '),
69
69
-
bookUrl: `https://bookhive.buzz/books/${value.hiveId}`,
70
70
-
cover: value.cover ? blobUrl(repo.pds, repo.did, value.cover) : null,
71
71
-
finishedAt: value.finishedAt ?? value.createdAt ?? null,
72
72
-
stars: value.stars ?? null
78
78
+
return (await listBooks(repo))
79
79
+
.filter((b) => b.status === FINISHED)
80
80
+
.map((b) => ({
81
81
+
title: b.title,
82
82
+
authors: b.authors,
83
83
+
bookUrl: `https://bookhive.buzz/books/${b.hiveId}`,
84
84
+
cover: b.cover,
85
85
+
finishedAt: b.finishedAt ?? b.createdAt ?? null,
86
86
+
stars: b.stars ?? null
73
87
}))
74
88
.sort((a, b) => (b.finishedAt ?? '').localeCompare(a.finishedAt ?? ''));
75
75
-
76
76
-
cache.set('finishedBooks', finished);
77
77
-
return finished;
78
89
} catch (error) {
79
90
console.warn('Failed to fetch finished books:', error);
80
91
return [];
+10
-6
src/lib/server/atproto/client.ts
Reviewed
···
1
1
import { env } from '$env/dynamic/private';
2
2
-
import { Client, getBlobCidString, type AtIdentifierString } from '@atproto/lex';
2
2
+
import {
3
3
+
Client,
4
4
+
getBlobCidString,
5
5
+
isBlobRef,
6
6
+
type AtIdentifierString,
7
7
+
type BlobRef
8
8
+
} from '@atproto/lex';
3
9
4
10
export type RepoInfo = { did: string; pds: string };
5
11
···
30
36
}
31
37
32
38
/** Builds a public getBlob URL for a blob ref stored on a record. */
33
33
-
export function blobUrl(
34
34
-
pds: string,
35
35
-
did: string,
36
36
-
blob: Parameters<typeof getBlobCidString>[0]
37
37
-
): string {
39
39
+
export function blobUrl(pds: string, did: string, blob: BlobRef | undefined): string | null {
40
40
+
if (!blob) return null;
41
41
+
if (!isBlobRef(blob)) return null;
38
42
const cidString = getBlobCidString(blob);
39
43
return `${pds}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${cidString}`;
40
44
}
+1
-2
src/routes/history/books/+page.svelte
Reviewed
···
3
3
import type { PageProps } from './$types';
4
4
5
5
let { data }: PageProps = $props();
6
6
-
const { currentlyReading, finishedBooks } = data;
7
6
</script>
8
7
9
8
<svelte:head>
···
13
12
<meta name="og:description" content="What I'm reading and books I've recently finished." />
14
13
</svelte:head>
15
14
16
16
-
<BookHistory {currentlyReading} {finishedBooks} />
15
15
+
<BookHistory currentlyReading={data.currentlyReading} finishedBooks={data.finishedBooks} />