WIP: My personal website
0

Configure Feed

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

fixed some caching of books

+50 -36
+39 -28
src/lib/server/atproto/books.ts
··· 7 7 8 8 type BookEntry = { value: BookRecord; uri: AtUriString }; 9 9 10 + type BookHiveBookView = { 11 + status?: string; 12 + title: string; 13 + authors: string; 14 + hiveId: string; 15 + cover: string | null; 16 + finishedAt: string | null; 17 + createdAt: string | null; 18 + stars: number | null; 19 + }; 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 - * collection, so we list it once and cache the raw records. Cached under the 16 - * the default ttl 1 day 26 + * collection, so we list it once and cache the normalized records. Cached 27 + * under the default ttl 1 day. 17 28 */ 18 - async function listBooks(repo: RepoInfo): Promise<BookEntry[]> { 19 - const cached = cache.get('books') as BookEntry[] | null; 29 + async function listBooks(repo: RepoInfo): Promise<BookHiveBookView[]> { 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 - const books = [...records]; 39 + const books = records.map(({ value: v }) => ({ 40 + status: v.status, 41 + title: v.title, 42 + authors: v.authors.split('\t').join(', '), 43 + hiveId: v.hiveId, 44 + cover: v.cover ? blobUrl(repo.pds, repo.did, v.cover) : null, 45 + finishedAt: v.finishedAt ?? null, 46 + createdAt: v.createdAt ?? null, 47 + stars: v.stars ?? null 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 - const reading = (await listBooks(repo)).find((r) => r.value.status === READING); 58 + const reading = (await listBooks(repo)).find((b) => b.status === READING); 39 59 if (!reading) return null; 40 60 41 - const v = reading.value; 42 - const test = blobUrl(repo.pds, repo.did, v.cover); 43 - console.log(test); 44 61 return { 45 - title: v.title, 46 - bookUrl: `https://bookhive.buzz/books/${v.hiveId}`, 47 - authors: v.authors.split('\t').join(', '), 48 - cover: v.cover ? blobUrl(repo.pds, repo.did, v.cover) : null 62 + title: reading.title, 63 + bookUrl: `https://bookhive.buzz/books/${reading.hiveId}`, 64 + authors: reading.authors, 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 - const cached = cache.get('finishedBooks') as FinishedBook[] | null; 58 - if (cached) return cached; 59 - 60 74 const repo = getRepoInfo('Books history'); 61 75 if (!repo) return []; 62 76 63 77 try { 64 - const finished = (await listBooks(repo)) 65 - .filter((r) => r.value.status === FINISHED) 66 - .map(({ value }) => ({ 67 - title: value.title, 68 - authors: value.authors.split('\t').join(', '), 69 - bookUrl: `https://bookhive.buzz/books/${value.hiveId}`, 70 - cover: value.cover ? blobUrl(repo.pds, repo.did, value.cover) : null, 71 - finishedAt: value.finishedAt ?? value.createdAt ?? null, 72 - stars: value.stars ?? null 78 + return (await listBooks(repo)) 79 + .filter((b) => b.status === FINISHED) 80 + .map((b) => ({ 81 + title: b.title, 82 + authors: b.authors, 83 + bookUrl: `https://bookhive.buzz/books/${b.hiveId}`, 84 + cover: b.cover, 85 + finishedAt: b.finishedAt ?? b.createdAt ?? null, 86 + stars: b.stars ?? null 73 87 })) 74 88 .sort((a, b) => (b.finishedAt ?? '').localeCompare(a.finishedAt ?? '')); 75 - 76 - cache.set('finishedBooks', finished); 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
··· 1 1 import { env } from '$env/dynamic/private'; 2 - import { Client, getBlobCidString, type AtIdentifierString } from '@atproto/lex'; 2 + import { 3 + Client, 4 + getBlobCidString, 5 + isBlobRef, 6 + type AtIdentifierString, 7 + type BlobRef 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 - export function blobUrl( 34 - pds: string, 35 - did: string, 36 - blob: Parameters<typeof getBlobCidString>[0] 37 - ): string { 39 + export function blobUrl(pds: string, did: string, blob: BlobRef | undefined): string | null { 40 + if (!blob) return null; 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
··· 3 3 import type { PageProps } from './$types'; 4 4 5 5 let { data }: PageProps = $props(); 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 - <BookHistory {currentlyReading} {finishedBooks} /> 15 + <BookHistory currentlyReading={data.currentlyReading} finishedBooks={data.finishedBooks} />