diff --git a/README.md b/README.md index fa4ee24..10fcac8 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,18 @@ Get started at [potatonet.app](https://potatonet.app) ๐Ÿฅ” +- [ ] `//home`: bookmarks per user + - [x] fetch bookmarks + - [x] filter by query term + - [x] refresh bookmarks + - [ ] filter by tags +- [ ] atproto auth + - [x] login/logout + - [ ] bookmark CRUD +- [ ] explore + - [ ] query and paginate all bookmarks + - [ ] filter explore + - [ ] search bar `/search?q=` + > Special thanks to [pilcrowonpaper](https://pilcrowonpaper.com) for `@oslojs/encoding` library and the [encryption gist](https://gist.github.com/pilcrowonpaper/353318556029221c8e25f451b91e5f76) that the `encryption.ts` file is based on. diff --git a/bun.lockb b/bun.lockb index 4769cc72952b373ae63d62439f9aa443aa24157c..7ffaf89088491f1dca6bdc50e027201962d5232a 100755 GIT binary patch delta 17757 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 o1ZW + import type { LexiconCommunityBookmark } from "$lib/utils"; + + type BookmarkCardProps = { + isOwner: boolean; + bookmark: LexiconCommunityBookmark; + onTagClick: (tag: string) => void; + onTagDeleteClick?: (tag: string) => void; + }; + + let { isOwner, bookmark, onTagClick, onTagDeleteClick }: BookmarkCardProps = $props(); + + +
+ {bookmark.subject} + {#if bookmark.tags && bookmark.tags.length > 0} +
+ {#each bookmark.tags as tag} +
+ {#if isOwner} + + {/if} + +
+ {/each} +
+ {:else} +

No tags

+ {/if} +
diff --git a/src/lib/server/api.ts b/src/lib/server/api.ts new file mode 100644 index 0000000..7dc6acd --- /dev/null +++ b/src/lib/server/api.ts @@ -0,0 +1,52 @@ +import { dev } from "$app/environment"; +import { SLICES_NETWORK_ACCESS_TOKEN } from "$env/static/private"; +import type { LexiconCommunityBookmark, SliceItem, SliceList } from "$lib/utils"; + +const SLICES_NETWORK_SLICE_URI = "at://did:plc:gotnvwkr56ibs33l4hwgfoet/network.slices.slice/3m26tswgbi42i" + +const baseUrl = "https://api.slices.network/xrpc/"; + +type GetListProps = { + limit?: number; // default: 50, max: 100 + cursor?: string; + where?: { + [key: string]: { eq?: string, contains?: string, in?: string[] } + }; + sortBy?: { field: string, direction: "desc" | "asc" }[] +}; + +export class SlicesAPI { + + collection: string; + sliceUri: string; + + constructor({ collection, sliceUri }: { collection: string, sliceUri : string }) { + this.collection = collection; + this.sliceUri = sliceUri; + } + + async getRecord({ uri }: { uri: string }) { + const searchParams = new URLSearchParams({ slice: SLICES_NETWORK_SLICE_URI, uri }); + const response = await fetch(`${baseUrl}${this.collection}.getRecord?${searchParams.toString()}`); + return await response.json() as SliceItem; + } + + async getList(body: GetListProps) { + const response = await fetch(`${baseUrl}${this.collection}.getRecords`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": SLICES_NETWORK_ACCESS_TOKEN + }, + body: JSON.stringify({ ...body, slice: SLICES_NETWORK_SLICE_URI }) + }); + const data = await response.json() as SliceList; + console.log({ data }); + return data; + } +} + +export const LexiconBookmarkSlicesAPI = new SlicesAPI({ + collection: "community.lexicon.bookmarks.bookmark", + sliceUri: SLICES_NETWORK_SLICE_URI +}); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b978f95..69a0cc6 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,12 @@ +import { atclient } from "./atproto"; + // --- UTILITIES --- +export type CommonSliceFields = { + indexedAt: string; + cid: string; + uri: string; + collection: string; +} export type LexiconCommunityBookmark = { $type: "community.lexicon.bookmarks.bookmark"; @@ -13,6 +21,13 @@ export type LexiconCommunityLike = { createdAt: string; } +export type SliceItem = CommonSliceFields & { value: T }; + +export type SliceList = CommonSliceFields & { + cursor: string; + records: (CommonSliceFields & { did: string, value: T })[]; +} + export function parseAtUri(uri: string) { const regex = /at:\/\/(?did.*)\/(?.*)\/(?.*)/; const groups = regex.exec(uri)?.groups; diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 71db0f7..97b521c 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -2,5 +2,8 @@ import type { ServerLoadEvent } from "@sveltejs/kit"; export async function load({ locals }: ServerLoadEvent) { // have user available throughout the app via LayoutData - return { user: locals.user, authedAgent: locals.authedAgent }; + return !locals.user ? undefined : { user: { + did: locals.user.did, + handle: locals.user.handle + }}; } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 8745123..6807bff 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,53 +1,53 @@ - -
-
- +
+
+ -
- {#if user} - ๐Ÿก -
- -
- {:else} -
- - -
- {/if} -
-
+
+ {#if user} + ๐Ÿก +
+ +
+ {:else} +
+ + +
+ {/if} +
+
+ + {#key page.url.pathname} +
+ + {@render children()} + + {#snippet pending()} +

Page loading...

+ {/snippet} +
+
+ {/key} +
-
- - {@render children()} - - {#snippet pending()} -

Page loading...

- {/snippet} -
-
- -
diff --git a/src/routes/[handle]/home/+page.svelte b/src/routes/[handle]/home/+page.svelte index 731b4d4..6cc59a9 100644 --- a/src/routes/[handle]/home/+page.svelte +++ b/src/routes/[handle]/home/+page.svelte @@ -1,35 +1,67 @@ -{#if $bookmarksQuery.isLoading} +

Bookmarks by @{handle}

+ +{#if userBookmarksQuery.loading}

Loading...

-{:else if $bookmarksQuery.isError} +{:else if userBookmarksQuery.error}

Error

-{:else if $bookmarksQuery.isSuccess} - {@const bookmarks = $bookmarksQuery.data.records} - {#each bookmarks as { uri, cid, value: bookmark }} -

{bookmark.subject}

- {/each} +{:else} + {@const { cursor: returnedCursor, bookmarks } = userBookmarksQuery.current || { cursor: "", bookmarks: []}} + +
+ + + + +
+ + {#if isOwner} + + {/if} +
+
+ +
+ {#each bookmarks as bookmark} + {#if bookmark.subject.includes(query) && (bookmark.tags?.some(t => filterTags.length > 0 ? filterTags.includes(t) : true))} + + {/if} + {/each} +
{/if} diff --git a/src/routes/api/bookmarks/data.remote.ts b/src/routes/api/bookmarks/data.remote.ts new file mode 100644 index 0000000..a9282bf --- /dev/null +++ b/src/routes/api/bookmarks/data.remote.ts @@ -0,0 +1,25 @@ +import * as v from "valibot"; +import { getRequestEvent, query } from "$app/server" +import { LexiconBookmarkSlicesAPI } from "$lib/server/api" +import { Agent } from "@atproto/api"; + +const GetUserBookmarksValidator = v.object({ + handle: v.string(), + cursor: v.optional(v.string()) +}); + +export const getUserBookmarks = query(GetUserBookmarksValidator, async ({ handle, cursor }) => { + const { locals } = getRequestEvent(); + const agent = locals.authedAgent ?? new Agent({ service: "https://api.bsky.app" }); + const result = await agent.resolveHandle({ handle }); + if (!result.success) { throw Error() }; + + const data = await LexiconBookmarkSlicesAPI.getList({ + cursor, + where: { + did: { eq: result.data.did } + } + }); + + return { cursor: data.cursor, bookmarks: data.records.map((r) => r.value )}; +}); diff --git a/svelte.config.js b/svelte.config.js index 0b4933e..67d0e84 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -8,7 +8,10 @@ const config = { preprocess: vitePreprocess(), kit: { - adapter: adapter() + adapter: adapter(), + experimental: { + remoteFunctions: true + } }, compilerOptions: {