diff options
Diffstat (limited to 'web/lib')
| -rw-r--r-- | web/lib/api.ts | 87 | ||||
| -rw-r--r-- | web/lib/config.ts | 1 | ||||
| -rw-r--r-- | web/lib/services/links.ts | 40 | ||||
| -rw-r--r-- | web/lib/types/api/links.ts | 11 |
4 files changed, 135 insertions, 4 deletions
diff --git a/web/lib/api.ts b/web/lib/api.ts new file mode 100644 index 00000000..193d9bb7 --- /dev/null +++ b/web/lib/api.ts @@ -0,0 +1,87 @@ +"use client"; + +import { ZodTypeAny, z } from "zod"; +import { + ZNewBookmarkedLinkRequest, + zGetLinksResponseSchema, +} from "./types/api/links"; + +import serverConfig from "./config"; + +const BASE_URL = `${serverConfig.api_url}/api/v1`; + +export type FetchError = { + status?: number; + message?: string; +}; + +async function doRequest<Schema extends ZodTypeAny>( + _path: string, + respSchema: Schema, + _opts: RequestInit | undefined, +): Promise<[z.infer<typeof respSchema>, undefined] | [undefined, FetchError]>; + +async function doRequest<_Schema>( + _path: string, + _respSchema: undefined, + _opts: RequestInit | undefined, +): Promise<[undefined, undefined] | [undefined, FetchError]>; + +type InputSchema<T> = T extends ZodTypeAny ? T : undefined; + +async function doRequest<T>( + path: string, + respSchema?: InputSchema<T>, + opts?: RequestInit, +): Promise< + | (InputSchema<T> extends ZodTypeAny + ? [z.infer<InputSchema<T>>, undefined] + : [undefined, undefined]) + | [undefined, FetchError] +> { + try { + const res = await fetch(`${BASE_URL}${path}`, opts); + if (!res.ok) { + return [ + undefined, + { status: res.status, message: await res.text() }, + ] as const; + } + if (!respSchema) { + return [undefined, undefined] as const; + } + + let parsed = respSchema.safeParse(await res.json()); + if (!parsed.success) { + return [ + undefined, + { message: `Failed to parse response: ${parsed.error.toString()}` }, + ] as const; + } + + return [parsed.data, undefined] as const; + } catch (error: any) { + return [ + undefined, + { message: `Failed to execute fetch request: ${error}` }, + ] as const; + } +} + +export default class APIClient { + static async getLinks() { + return await doRequest(`/links`, zGetLinksResponseSchema, { + next: { tags: ["links"] }, + }); + } + + static async bookmarkLink(url: string) { + const body: ZNewBookmarkedLinkRequest = { + url, + }; + return await doRequest(`/links`, undefined, { + method: "POST", + body: JSON.stringify(body), + }); + } +} diff --git a/web/lib/config.ts b/web/lib/config.ts index 6b55aa2c..ec042b54 100644 --- a/web/lib/config.ts +++ b/web/lib/config.ts @@ -13,6 +13,7 @@ function buildAuthentikConfig() { } const serverConfig = { + api_url: process.env.API_URL || "http://localhost:3000", auth: { authentik: buildAuthentikConfig(), }, diff --git a/web/lib/services/links.ts b/web/lib/services/links.ts new file mode 100644 index 00000000..f3ff1757 --- /dev/null +++ b/web/lib/services/links.ts @@ -0,0 +1,40 @@ +import { LinkCrawlerQueue } from "@remember/shared/queues"; +import prisma from "@remember/db"; + +export async function bookmarkLink(url: string, userId: string) { + const link = await prisma.bookmarkedLink.create({ + data: { + url, + userId, + }, + }); + + // Enqueue crawling request + await LinkCrawlerQueue.add("crawl", { + linkId: link.id, + url: link.url, + }); + + return link; +} + +export async function getLinks(userId: string) { + return await prisma.bookmarkedLink.findMany({ + where: { + userId, + }, + select: { + id: true, + url: true, + createdAt: true, + details: { + select: { + title: true, + description: true, + imageUrl: true, + favicon: true, + }, + }, + }, + }); +} diff --git a/web/lib/types/api/links.ts b/web/lib/types/api/links.ts index 48214f9a..644589b4 100644 --- a/web/lib/types/api/links.ts +++ b/web/lib/types/api/links.ts @@ -7,10 +7,10 @@ export const zBookmarkedLinkSchema = z.object({ details: z .object({ - title: z.string(), - description: z.string().optional(), - imageUrl: z.string().url().optional(), - favicon: z.string().url().optional(), + title: z.string().nullish(), + description: z.string().nullish(), + imageUrl: z.string().url().nullish(), + favicon: z.string().url().nullish(), }) .nullish(), }); @@ -20,6 +20,9 @@ export type ZBookmarkedLink = z.infer<typeof zBookmarkedLinkSchema>; export const zNewBookmarkedLinkRequestSchema = zBookmarkedLinkSchema.pick({ url: true, }); +export type ZNewBookmarkedLinkRequest = z.infer< + typeof zNewBookmarkedLinkRequestSchema +>; // GET /v1/links export const zGetLinksResponseSchema = z.object({ |
