From 4d072aacb5607905d3926caacb45fc2621cda203 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 31 Dec 2025 19:34:50 +0200 Subject: [PATCH] Fix API route patterns for TanStack Start - Use createFileRoute with server.handlers pattern - Export Route instead of APIRoute --- packages/web/src/routeTree.gen.ts | 42 +++++ packages/web/src/routes/api/bookmarks.ts | 146 ++++++++++-------- .../src/routes/api/jazz.cloudflare-config.ts | 85 +++++----- 3 files changed, 169 insertions(+), 104 deletions(-) diff --git a/packages/web/src/routeTree.gen.ts b/packages/web/src/routeTree.gen.ts index 4c017f36..5c8928db 100644 --- a/packages/web/src/routeTree.gen.ts +++ b/packages/web/src/routeTree.gen.ts @@ -44,6 +44,7 @@ import { Route as ApiChatThreadsRouteImport } from './routes/api/chat-threads' import { Route as ApiChatMessagesRouteImport } from './routes/api/chat-messages' import { Route as ApiCanvasRouteImport } from './routes/api/canvas' import { Route as ApiBrowserSessionsRouteImport } from './routes/api/browser-sessions' +import { Route as ApiBookmarksRouteImport } from './routes/api/bookmarks' import { Route as ApiArchivesRouteImport } from './routes/api/archives' import { Route as ApiApiKeysRouteImport } from './routes/api/api-keys' import { Route as DemoStartServerFuncsRouteImport } from './routes/demo/start.server-funcs' @@ -59,6 +60,7 @@ import { Route as ApiStreamsUsernameRouteImport } from './routes/api/streams.$us import { Route as ApiStreamSettingsRouteImport } from './routes/api/stream.settings' import { Route as ApiStreamReplaysReplayIdRouteImport } from './routes/api/stream-replays.$replayId' import { Route as ApiSpotifyNowPlayingRouteImport } from './routes/api/spotify.now-playing' +import { Route as ApiJazzCloudflareConfigRouteImport } from './routes/api/jazz.cloudflare-config' import { Route as ApiFlowgladSplatRouteImport } from './routes/api/flowglad/$' import { Route as ApiCreatorTiersRouteImport } from './routes/api/creator/tiers' import { Route as ApiCreatorSubscribeRouteImport } from './routes/api/creator/subscribe' @@ -256,6 +258,11 @@ const ApiBrowserSessionsRoute = ApiBrowserSessionsRouteImport.update({ path: '/api/browser-sessions', getParentRoute: () => rootRouteImport, } as any) +const ApiBookmarksRoute = ApiBookmarksRouteImport.update({ + id: '/api/bookmarks', + path: '/api/bookmarks', + getParentRoute: () => rootRouteImport, +} as any) const ApiArchivesRoute = ApiArchivesRouteImport.update({ id: '/api/archives', path: '/api/archives', @@ -332,6 +339,11 @@ const ApiSpotifyNowPlayingRoute = ApiSpotifyNowPlayingRouteImport.update({ path: '/api/spotify/now-playing', getParentRoute: () => rootRouteImport, } as any) +const ApiJazzCloudflareConfigRoute = ApiJazzCloudflareConfigRouteImport.update({ + id: '/api/jazz/cloudflare-config', + path: '/api/jazz/cloudflare-config', + getParentRoute: () => rootRouteImport, +} as any) const ApiFlowgladSplatRoute = ApiFlowgladSplatRouteImport.update({ id: '/api/flowglad/$', path: '/api/flowglad/$', @@ -462,6 +474,7 @@ export interface FileRoutesByFullPath { '/users': typeof UsersRoute '/api/api-keys': typeof ApiApiKeysRoute '/api/archives': typeof ApiArchivesRouteWithChildren + '/api/bookmarks': typeof ApiBookmarksRoute '/api/browser-sessions': typeof ApiBrowserSessionsRouteWithChildren '/api/canvas': typeof ApiCanvasRouteWithChildren '/api/chat-messages': typeof ApiChatMessagesRoute @@ -493,6 +506,7 @@ export interface FileRoutesByFullPath { '/api/creator/subscribe': typeof ApiCreatorSubscribeRoute '/api/creator/tiers': typeof ApiCreatorTiersRoute '/api/flowglad/$': typeof ApiFlowgladSplatRoute + '/api/jazz/cloudflare-config': typeof ApiJazzCloudflareConfigRoute '/api/spotify/now-playing': typeof ApiSpotifyNowPlayingRoute '/api/stream-replays/$replayId': typeof ApiStreamReplaysReplayIdRoute '/api/stream/settings': typeof ApiStreamSettingsRoute @@ -534,6 +548,7 @@ export interface FileRoutesByTo { '/users': typeof UsersRoute '/api/api-keys': typeof ApiApiKeysRoute '/api/archives': typeof ApiArchivesRouteWithChildren + '/api/bookmarks': typeof ApiBookmarksRoute '/api/browser-sessions': typeof ApiBrowserSessionsRouteWithChildren '/api/canvas': typeof ApiCanvasRouteWithChildren '/api/chat-messages': typeof ApiChatMessagesRoute @@ -565,6 +580,7 @@ export interface FileRoutesByTo { '/api/creator/subscribe': typeof ApiCreatorSubscribeRoute '/api/creator/tiers': typeof ApiCreatorTiersRoute '/api/flowglad/$': typeof ApiFlowgladSplatRoute + '/api/jazz/cloudflare-config': typeof ApiJazzCloudflareConfigRoute '/api/spotify/now-playing': typeof ApiSpotifyNowPlayingRoute '/api/stream-replays/$replayId': typeof ApiStreamReplaysReplayIdRoute '/api/stream/settings': typeof ApiStreamSettingsRoute @@ -608,6 +624,7 @@ export interface FileRoutesById { '/users': typeof UsersRoute '/api/api-keys': typeof ApiApiKeysRoute '/api/archives': typeof ApiArchivesRouteWithChildren + '/api/bookmarks': typeof ApiBookmarksRoute '/api/browser-sessions': typeof ApiBrowserSessionsRouteWithChildren '/api/canvas': typeof ApiCanvasRouteWithChildren '/api/chat-messages': typeof ApiChatMessagesRoute @@ -639,6 +656,7 @@ export interface FileRoutesById { '/api/creator/subscribe': typeof ApiCreatorSubscribeRoute '/api/creator/tiers': typeof ApiCreatorTiersRoute '/api/flowglad/$': typeof ApiFlowgladSplatRoute + '/api/jazz/cloudflare-config': typeof ApiJazzCloudflareConfigRoute '/api/spotify/now-playing': typeof ApiSpotifyNowPlayingRoute '/api/stream-replays/$replayId': typeof ApiStreamReplaysReplayIdRoute '/api/stream/settings': typeof ApiStreamSettingsRoute @@ -683,6 +701,7 @@ export interface FileRouteTypes { | '/users' | '/api/api-keys' | '/api/archives' + | '/api/bookmarks' | '/api/browser-sessions' | '/api/canvas' | '/api/chat-messages' @@ -714,6 +733,7 @@ export interface FileRouteTypes { | '/api/creator/subscribe' | '/api/creator/tiers' | '/api/flowglad/$' + | '/api/jazz/cloudflare-config' | '/api/spotify/now-playing' | '/api/stream-replays/$replayId' | '/api/stream/settings' @@ -755,6 +775,7 @@ export interface FileRouteTypes { | '/users' | '/api/api-keys' | '/api/archives' + | '/api/bookmarks' | '/api/browser-sessions' | '/api/canvas' | '/api/chat-messages' @@ -786,6 +807,7 @@ export interface FileRouteTypes { | '/api/creator/subscribe' | '/api/creator/tiers' | '/api/flowglad/$' + | '/api/jazz/cloudflare-config' | '/api/spotify/now-playing' | '/api/stream-replays/$replayId' | '/api/stream/settings' @@ -828,6 +850,7 @@ export interface FileRouteTypes { | '/users' | '/api/api-keys' | '/api/archives' + | '/api/bookmarks' | '/api/browser-sessions' | '/api/canvas' | '/api/chat-messages' @@ -859,6 +882,7 @@ export interface FileRouteTypes { | '/api/creator/subscribe' | '/api/creator/tiers' | '/api/flowglad/$' + | '/api/jazz/cloudflare-config' | '/api/spotify/now-playing' | '/api/stream-replays/$replayId' | '/api/stream/settings' @@ -902,6 +926,7 @@ export interface RootRouteChildren { UsersRoute: typeof UsersRoute ApiApiKeysRoute: typeof ApiApiKeysRoute ApiArchivesRoute: typeof ApiArchivesRouteWithChildren + ApiBookmarksRoute: typeof ApiBookmarksRoute ApiBrowserSessionsRoute: typeof ApiBrowserSessionsRouteWithChildren ApiCanvasRoute: typeof ApiCanvasRouteWithChildren ApiChatMessagesRoute: typeof ApiChatMessagesRoute @@ -926,6 +951,7 @@ export interface RootRouteChildren { ApiCreatorSubscribeRoute: typeof ApiCreatorSubscribeRoute ApiCreatorTiersRoute: typeof ApiCreatorTiersRoute ApiFlowgladSplatRoute: typeof ApiFlowgladSplatRoute + ApiJazzCloudflareConfigRoute: typeof ApiJazzCloudflareConfigRoute ApiSpotifyNowPlayingRoute: typeof ApiSpotifyNowPlayingRoute ApiStreamsUsernameRoute: typeof ApiStreamsUsernameRouteWithChildren ApiStripeBillingRoute: typeof ApiStripeBillingRoute @@ -1189,6 +1215,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiBrowserSessionsRouteImport parentRoute: typeof rootRouteImport } + '/api/bookmarks': { + id: '/api/bookmarks' + path: '/api/bookmarks' + fullPath: '/api/bookmarks' + preLoaderRoute: typeof ApiBookmarksRouteImport + parentRoute: typeof rootRouteImport + } '/api/archives': { id: '/api/archives' path: '/api/archives' @@ -1294,6 +1327,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiSpotifyNowPlayingRouteImport parentRoute: typeof rootRouteImport } + '/api/jazz/cloudflare-config': { + id: '/api/jazz/cloudflare-config' + path: '/api/jazz/cloudflare-config' + fullPath: '/api/jazz/cloudflare-config' + preLoaderRoute: typeof ApiJazzCloudflareConfigRouteImport + parentRoute: typeof rootRouteImport + } '/api/flowglad/$': { id: '/api/flowglad/$' path: '/api/flowglad/$' @@ -1611,6 +1651,7 @@ const rootRouteChildren: RootRouteChildren = { UsersRoute: UsersRoute, ApiApiKeysRoute: ApiApiKeysRoute, ApiArchivesRoute: ApiArchivesRouteWithChildren, + ApiBookmarksRoute: ApiBookmarksRoute, ApiBrowserSessionsRoute: ApiBrowserSessionsRouteWithChildren, ApiCanvasRoute: ApiCanvasRouteWithChildren, ApiChatMessagesRoute: ApiChatMessagesRoute, @@ -1635,6 +1676,7 @@ const rootRouteChildren: RootRouteChildren = { ApiCreatorSubscribeRoute: ApiCreatorSubscribeRoute, ApiCreatorTiersRoute: ApiCreatorTiersRoute, ApiFlowgladSplatRoute: ApiFlowgladSplatRoute, + ApiJazzCloudflareConfigRoute: ApiJazzCloudflareConfigRoute, ApiSpotifyNowPlayingRoute: ApiSpotifyNowPlayingRoute, ApiStreamsUsernameRoute: ApiStreamsUsernameRouteWithChildren, ApiStripeBillingRoute: ApiStripeBillingRoute, diff --git a/packages/web/src/routes/api/bookmarks.ts b/packages/web/src/routes/api/bookmarks.ts index 920d63cf..c9a4cb4f 100644 --- a/packages/web/src/routes/api/bookmarks.ts +++ b/packages/web/src/routes/api/bookmarks.ts @@ -1,8 +1,14 @@ -import { createAPIFileRoute } from "@tanstack/react-start/api" +import { createFileRoute } from "@tanstack/react-router" import { eq } from "drizzle-orm" import { getDb } from "@/db/connection" import { api_keys, bookmarks, users } from "@/db/schema" +const json = (data: unknown, status = 200) => + new Response(JSON.stringify(data), { + status, + headers: { "content-type": "application/json" }, + }) + // Hash function for API key verification async function hashApiKey(key: string): Promise { const encoder = new TextEncoder() @@ -44,81 +50,91 @@ async function getUserFromApiKey(apiKey: string) { return user || null } -export const APIRoute = createAPIFileRoute("/api/bookmarks")({ - // POST - Add a bookmark - POST: async ({ request }) => { - try { - const body = await request.json() - const { url, title, description, tags, api_key } = body +export const Route = createFileRoute("/api/bookmarks")({ + server: { + handlers: { + // POST - Add a bookmark + POST: async ({ request }) => { + try { + const body = (await request.json()) as { + url?: string + title?: string + description?: string + tags?: string + api_key?: string + } + const { url, title, description, tags, api_key } = body - if (!url) { - return Response.json({ error: "URL is required" }, { status: 400 }) - } + if (!url) { + return json({ error: "URL is required" }, 400) + } - if (!api_key) { - return Response.json({ error: "API key is required" }, { status: 401 }) - } + if (!api_key) { + return json({ error: "API key is required" }, 401) + } - const user = await getUserFromApiKey(api_key) - if (!user) { - return Response.json({ error: "Invalid API key" }, { status: 401 }) - } + const user = await getUserFromApiKey(api_key) + if (!user) { + return json({ error: "Invalid API key" }, 401) + } - const db = getDb(process.env.DATABASE_URL!) + const db = getDb(process.env.DATABASE_URL!) - // Insert bookmark - const [bookmark] = await db - .insert(bookmarks) - .values({ - user_id: user.id, - url, - title: title || null, - description: description || null, - tags: tags || null, - }) - .returning() + // Insert bookmark + const [bookmark] = await db + .insert(bookmarks) + .values({ + user_id: user.id, + url, + title: title || null, + description: description || null, + tags: tags || null, + }) + .returning() - return Response.json({ - success: true, - bookmark: { - id: bookmark.id, - url: bookmark.url, - title: bookmark.title, - created_at: bookmark.created_at, - }, - }) - } catch (error) { - console.error("Error adding bookmark:", error) - return Response.json({ error: "Failed to add bookmark" }, { status: 500 }) - } - }, + return json({ + success: true, + bookmark: { + id: bookmark.id, + url: bookmark.url, + title: bookmark.title, + created_at: bookmark.created_at, + }, + }) + } catch (error) { + console.error("Error adding bookmark:", error) + return json({ error: "Failed to add bookmark" }, 500) + } + }, - // GET - List bookmarks (requires API key in header) - GET: async ({ request }) => { - try { - const apiKey = request.headers.get("x-api-key") + // GET - List bookmarks (requires API key in header) + GET: async ({ request }) => { + try { + const apiKey = request.headers.get("x-api-key") - if (!apiKey) { - return Response.json({ error: "API key is required" }, { status: 401 }) - } + if (!apiKey) { + return json({ error: "API key is required" }, 401) + } - const user = await getUserFromApiKey(apiKey) - if (!user) { - return Response.json({ error: "Invalid API key" }, { status: 401 }) - } + const user = await getUserFromApiKey(apiKey) + if (!user) { + return json({ error: "Invalid API key" }, 401) + } - const db = getDb(process.env.DATABASE_URL!) + const db = getDb(process.env.DATABASE_URL!) - const userBookmarks = await db - .select() - .from(bookmarks) - .where(eq(bookmarks.user_id, user.id)) - .orderBy(bookmarks.created_at) + const userBookmarks = await db + .select() + .from(bookmarks) + .where(eq(bookmarks.user_id, user.id)) + .orderBy(bookmarks.created_at) - return Response.json({ bookmarks: userBookmarks }) - } catch (error) { - console.error("Error fetching bookmarks:", error) - return Response.json({ error: "Failed to fetch bookmarks" }, { status: 500 }) - } + return json({ bookmarks: userBookmarks }) + } catch (error) { + console.error("Error fetching bookmarks:", error) + return json({ error: "Failed to fetch bookmarks" }, 500) + } + }, + }, }, }) diff --git a/packages/web/src/routes/api/jazz.cloudflare-config.ts b/packages/web/src/routes/api/jazz.cloudflare-config.ts index 4d01556f..14719ec9 100644 --- a/packages/web/src/routes/api/jazz.cloudflare-config.ts +++ b/packages/web/src/routes/api/jazz.cloudflare-config.ts @@ -1,42 +1,49 @@ -import { json } from "@tanstack/react-start" -import type { APIContext } from "@tanstack/react-router" +import { createFileRoute } from "@tanstack/react-router" -/** - * Get or set Cloudflare stream configuration from Jazz - * - * GET: Returns current Cloudflare Live Input UID - * PUT: Updates Cloudflare Live Input UID - */ -export async function GET({ request, context }: APIContext) { - try { - // For now, return the hardcoded value - // TODO: Read from Jazz when worker is set up - return json({ - liveInputUid: "bb7858eafc85de6c92963f3817477b5d", - customerCode: "xctsztqzu046isdc", - name: "linsa-nikiv", - updatedAt: Date.now(), - }) - } catch (error) { - return json({ error: "Failed to fetch config" }, { status: 500 }) - } -} +const json = (data: unknown, status = 200) => + new Response(JSON.stringify(data), { + status, + headers: { "content-type": "application/json" }, + }) -export async function PUT({ request, context }: APIContext) { - try { - const body = await request.json() - const { liveInputUid, customerCode, name } = body +export const Route = createFileRoute("/api/jazz/cloudflare-config")({ + server: { + handlers: { + // GET: Returns current Cloudflare Live Input UID + GET: async () => { + try { + return json({ + liveInputUid: "bb7858eafc85de6c92963f3817477b5d", + customerCode: "xctsztqzu046isdc", + name: "linsa-nikiv", + updatedAt: Date.now(), + }) + } catch (error) { + return json({ error: "Failed to fetch config" }, 500) + } + }, - // TODO: Write to Jazz when worker is set up - // For now, just return success - return json({ - success: true, - liveInputUid, - customerCode, - name, - updatedAt: Date.now(), - }) - } catch (error) { - return json({ error: "Failed to update config" }, { status: 500 }) - } -} + // PUT: Updates Cloudflare Live Input UID + PUT: async ({ request }) => { + try { + const body = (await request.json()) as { + liveInputUid?: string + customerCode?: string + name?: string + } + const { liveInputUid, customerCode, name } = body + + return json({ + success: true, + liveInputUid, + customerCode, + name, + updatedAt: Date.now(), + }) + } catch (error) { + return json({ error: "Failed to update config" }, 500) + } + }, + }, + }, +})