Read-it-later social network
12
fork

Configure Feed

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

add tanstack query, hardcoded selfhosted.social agent for now, render bookmark subjects

zeudev 5e2cbc8f 1c8dca85

+125 -59
bun.lockb

This is a binary file and will not be displayed.

+8 -8
package.json
··· 14 14 }, 15 15 "devDependencies": { 16 16 "@sveltejs/adapter-netlify": "^5.2.3", 17 - "@sveltejs/kit": "^2.42.1", 18 - "@sveltejs/vite-plugin-svelte": "^6.2.0", 19 - "@tailwindcss/typography": "^0.5.16", 17 + "@sveltejs/kit": "^2.43.4", 18 + "@sveltejs/vite-plugin-svelte": "^6.2.1", 19 + "@tailwindcss/typography": "^0.5.19", 20 20 "autoprefixer": "^10.4.21", 21 21 "drizzle-kit": "^0.31.4", 22 - "svelte": "^5.39.2", 23 - "svelte-check": "^4.3.1", 22 + "svelte": "^5.39.6", 23 + "svelte-check": "^4.3.2", 24 24 "tailwindcss": "^4.1.13", 25 25 "typescript": "^5.9.2", 26 - "vite": "^7.1.6" 26 + "vite": "^7.1.7" 27 27 }, 28 28 "dependencies": { 29 - "@atproto/api": "^0.16.9", 29 + "@atproto/api": "^0.16.10", 30 30 "@atproto/oauth-client-node": "^0.3.8", 31 31 "@oslojs/encoding": "^1.1.0", 32 32 "@tailwindcss/vite": "^4.1.13", 33 - "@tanstack/svelte-query": "^5.89.0", 33 + "@tanstack/svelte-query": "^5.90.2", 34 34 "drizzle-orm": "^0.44.5", 35 35 "postgres": "^3.4.7" 36 36 }
+2 -2
src/app.d.ts
··· 1 1 // See https://svelte.dev/docs/kit/types#app.d.ts 2 2 3 - import type { Agent, AtpBaseClient } from "@atproto/api"; 3 + import type { Agent } from "@atproto/api"; 4 4 import type { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; 5 5 6 6 // for information about these interfaces ··· 10 10 11 11 // set on `hooks.server.ts`, available on server functions 12 12 interface Locals { 13 - agent: Agent | AtpBaseClient | undefined; 13 + authedAgent: Agent | undefined; 14 14 user: ProfileViewDetailed | undefined; 15 15 } 16 16
+4 -11
src/hooks.server.ts
··· 1 + import { Agent } from "@atproto/api"; 1 2 import { atclient } from "$lib/atproto"; 2 - import { AtpBaseClient, Agent } from "@atproto/api"; 3 3 4 4 import { decryptToString } from "$lib/server/encryption"; 5 5 import { decodeBase64, decodeBase64urlIgnorePadding } from "@oslojs/encoding"; ··· 25 25 const oauthSession = await atclient.restore(decrypted); 26 26 27 27 // set the authed agent 28 - const agent = new Agent(oauthSession); 29 - event.locals.agent = agent; 28 + const authedAgent = new Agent(oauthSession); 29 + event.locals.authedAgent = authedAgent; 30 30 31 31 // set the authed user with decrypted session DID 32 - const user = await agent.getProfile({ actor: decrypted }); 32 + const user = await authedAgent.getProfile({ actor: decrypted }); 33 33 event.locals.user = user.data; 34 - } 35 - else { 36 - // set public API agent 37 - const agent = new AtpBaseClient({ 38 - service: "https://slingshot.microcosm.blue" 39 - }); 40 - event.locals.agent = agent; 41 34 } 42 35 43 36 return resolve(event);
+24
src/lib/utils.ts
··· 1 + // --- UTILITIES --- 2 + 3 + export type LexiconCommunityBookmark = { 4 + $type: "community.lexicon.bookmarks.bookmark"; 5 + subject: string; 6 + createdAt: string; 7 + tags?: string[]; 8 + }; 9 + 10 + export type LexiconCommunityLike = { 11 + $type: "community.lexicon.interaction.like"; 12 + subject: string; 13 + createdAt: string; 14 + } 15 + 16 + export function parseAtUri(uri: string) { 17 + const regex = /at:\/\/(?<did>did.*)\/(?<lexi>.*)\/(?<rkey>.*)/; 18 + const groups = regex.exec(uri)?.groups; 19 + return { 20 + did: groups?.did, 21 + lexi: groups?.lexi, 22 + rkey: groups?.rkey 23 + } 24 + }
+1 -1
src/routes/+layout.server.ts
··· 2 2 3 3 export async function load({ locals }: ServerLoadEvent) { 4 4 // have user available throughout the app via LayoutData 5 - return { user: locals.user }; 5 + return { user: locals.user, authedAgent: locals.authedAgent }; 6 6 }
+45 -34
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import '../app.css'; 3 + import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query'; 4 + 3 5 let { data, children } = $props(); 4 6 const user = $derived(data.user); 7 + const queryClient = new QueryClient(); 5 8 </script> 6 9 7 - <div class="flex flex-col gap-8 w-screen h-full min-h-screen font-neco"> 8 - <header class="flex items-center w-full gap-4 px-8 py-4 justify-between"> 9 - <nav class="text-lg flex gap-4 items-center"> 10 - <a href="/" class="font-comico text-2xl hover:text-shadow-md">potatonet.app</a> 11 - <a href="https://tangled.sh/@zeu.dev/potatonet-app" class="hover:text-shadow-lg">🧶</a> 12 - <a href="https://bsky.app/profile/zeu.dev" class="hover:text-shadow-lg">🦋</a> 13 - </nav> 10 + <QueryClientProvider client={queryClient}> 11 + <div class="flex flex-col gap-8 w-screen h-full min-h-screen font-neco"> 12 + <header class="flex items-center w-full gap-4 px-8 py-4 justify-between"> 13 + <nav class="text-lg flex gap-4 items-center"> 14 + <a href="/" class="font-comico text-2xl hover:text-shadow-md">potatonet.app</a> 15 + <a href="https://tangled.sh/@zeu.dev/potatonet-app" class="hover:text-shadow-lg">🧶</a> 16 + <a href="https://bsky.app/profile/zeu.dev" class="hover:text-shadow-lg">🦋</a> 17 + </nav> 14 18 15 - <div class="flex gap-4 items-center"> 16 - {#if user} 17 - <a href={`/${user.handle}/home`} class="hover:text-shadow-lg">🏡</a> 18 - <form action="/?/logout" method="POST"> 19 - <button type="submit" class="hover:text-shadow-lg hover:cursor-pointer font-comico"> 20 - Logout 21 - </button> 22 - </form> 23 - {:else} 24 - <form action="/?/login" method="POST"> 25 - <input 26 - name="handle" 27 - type="text" 28 - placeholder="Handle (eg: zeu.dev)" 29 - class="border border-black border-dashed px-3 py-2 hover:shadow-lg focus:shadow-lg" 30 - /> 31 - <button type="submit" class="hover:text-shadow-lg hover:cursor-pointer font-comico"> 32 - Login 33 - </button> 34 - </form> 35 - {/if} 36 - </div> 37 - </header> 19 + <div class="flex gap-4 items-center text-lg"> 20 + {#if user} 21 + <a href={`/${user.handle}/home`} class="hover:text-shadow-lg">🏡</a> 22 + <form action="/?/logout" method="POST"> 23 + <button type="submit" class="hover:text-shadow-lg hover:cursor-pointer font-comico"> 24 + Logout 25 + </button> 26 + </form> 27 + {:else} 28 + <form action="/?/login" method="POST"> 29 + <input 30 + name="handle" 31 + type="text" 32 + placeholder="Handle (eg: zeu.dev)" 33 + class="border border-black border-dashed px-3 py-2 hover:shadow-lg focus:shadow-lg" 34 + /> 35 + <button type="submit" class="hover:text-shadow-lg hover:cursor-pointer font-comico"> 36 + Login 37 + </button> 38 + </form> 39 + {/if} 40 + </div> 41 + </header> 38 42 39 - <main class="flex flex-col gap-4 p-8"> 40 - {@render children()} 41 - </main> 42 - </div> 43 + <main class="flex flex-col gap-4 p-8"> 44 + <svelte:boundary> 45 + {@render children()} 46 + 47 + {#snippet pending()} 48 + <p>Page loading...</p> 49 + {/snippet} 50 + </svelte:boundary> 51 + </main> 52 + </div> 53 + </QueryClientProvider>
+2
src/routes/+page.svelte
··· 1 + <h1 class="text-3xl font-bold font-comico">explore</h1> 2 + <p>coming soon...</p>
+31 -1
src/routes/[handle]/home/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import { page } from "$app/state"; 3 + import { Agent } from "@atproto/api"; 4 + import { createQuery } from "@tanstack/svelte-query"; 5 + import type { LexiconCommunityBookmark } from "$lib/utils"; 3 6 4 - const handle = $derived(page.params.handle); 7 + const { handle } = page.params; 8 + const agent = new Agent({ service: "https://selfhosted.social" }); 9 + 10 + const bookmarksQuery = createQuery({ 11 + queryKey: ["bookmarks", handle], 12 + queryFn: async () => { 13 + if (!handle) { throw Error } 14 + const result = await agent.com.atproto.repo.listRecords({ 15 + repo: handle, 16 + collection: "community.lexicon.bookmarks.bookmark" 17 + }); 18 + if (!result.success) { throw Error } 19 + console.log({ result }); 20 + return result.data as unknown as { cursor: string, records: { uri: string, cid: string, value: LexiconCommunityBookmark }[] }; 21 + }, 22 + staleTime: 3000 23 + }); 5 24 </script> 25 + 26 + {#if $bookmarksQuery.isLoading} 27 + <p>Loading...</p> 28 + {:else if $bookmarksQuery.isError} 29 + <p>Error</p> 30 + {:else if $bookmarksQuery.isSuccess} 31 + {@const bookmarks = $bookmarksQuery.data.records} 32 + {#each bookmarks as { uri, cid, value: bookmark }} 33 + <p>{bookmark.subject}</p> 34 + {/each} 35 + {/if}
+1 -1
src/routes/oauth/callback/+server.ts
··· 30 30 error(500, { message: (err as Error).message }); 31 31 } 32 32 33 - redirect(301, "/farm"); 33 + redirect(301, `/`); 34 34 }
+7 -1
svelte.config.js
··· 9 9 10 10 kit: { 11 11 adapter: adapter() 12 - } 12 + }, 13 + 14 + compilerOptions: { 15 + experimental: { 16 + async: true 17 + } 18 + } 13 19 }; 14 20 15 21 export default config;