bun.lockb
bun.lockb
This is a binary file and will not be displayed.
+8
-8
package.json
+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
+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
+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
+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
+1
-1
src/routes/+layout.server.ts
+45
-34
src/routes/+layout.svelte
+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
+2
src/routes/+page.svelte
+31
-1
src/routes/[handle]/home/+page.svelte
+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
+1
-1
src/routes/oauth/callback/+server.ts