diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 1b3b00b..b2ec28b 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -1,5 +1,5 @@ import { join } from 'node:path' -import { app, BrowserWindow, dialog, shell } from 'electron' +import { app, BrowserWindow, dialog, session, shell } from 'electron' import { createSidecarRuntime } from './sidecar' const DEV_SERVER_URL = 'http://localhost:3000' @@ -61,6 +61,8 @@ const createWindow = async () => { webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: true, + contextIsolation: true, + nodeIntegration: false, }, }) mainWindow = windowRef @@ -78,6 +80,21 @@ const createWindow = async () => { return { action: 'deny' } }) + windowRef.webContents.on('will-navigate', (event, url) => { + const allowed = [DEV_SERVER_URL, sidecar.lastResolvedUrl].filter((v): v is string => v != null) + const isAllowed = allowed.some((origin) => url.startsWith(origin)) + + if (!isAllowed) { + event.preventDefault() + + if (canOpenExternally(url)) { + void shell.openExternal(url) + } else if (!app.isPackaged) { + console.warn(`Blocked navigation to: ${url}`) + } + } + }) + windowRef.on('closed', () => { if (mainWindow === windowRef) { mainWindow = null @@ -151,7 +168,13 @@ const handleWindowCreationError = (error: unknown, context: string) => { app .whenReady() - .then(() => ensureWindow()) + .then(() => { + session.defaultSession.setPermissionRequestHandler((_webContents, _permission, callback) => { + callback(false) + }) + + return ensureWindow() + }) .catch((error) => { handleWindowCreationError(error, 'Failed to create window') }) diff --git a/apps/desktop/src/main/sidecar.ts b/apps/desktop/src/main/sidecar.ts index 0bb7ba2..d57d8b5 100644 --- a/apps/desktop/src/main/sidecar.ts +++ b/apps/desktop/src/main/sidecar.ts @@ -27,6 +27,7 @@ type SidecarRuntimeOptions = { type SidecarRuntime = { resolveUrl: () => Promise stop: () => void + lastResolvedUrl: string | null } const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)) @@ -72,7 +73,9 @@ const isServerReady = async (url: string): Promise => { return true } - } catch {} + } catch { + // Expected: probe request fails while server is still starting up + } } return false @@ -239,11 +242,15 @@ export const createSidecarRuntime = (options: SidecarRuntimeOptions): SidecarRun throw new Error('Dev server not responding. Run `bun dev` in apps/server first.') } + state.url = options.devServerUrl return options.devServerUrl } return { resolveUrl, stop, + get lastResolvedUrl() { + return state.url + }, } } diff --git a/apps/server/src/router.tsx b/apps/server/src/router.tsx index 2625061..1390fbd 100644 --- a/apps/server/src/router.tsx +++ b/apps/server/src/router.tsx @@ -5,7 +5,14 @@ import type { RouterContext } from './routes/__root' import { routeTree } from './routeTree.gen' export const getRouter = () => { - const queryClient = new QueryClient() + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30 * 1000, + retry: 1, + }, + }, + }) const router = createRouter({ routeTree, diff --git a/apps/server/src/routes/__root.tsx b/apps/server/src/routes/__root.tsx index 3d68915..b7f758f 100644 --- a/apps/server/src/routes/__root.tsx +++ b/apps/server/src/routes/__root.tsx @@ -46,21 +46,23 @@ function RootDocument({ children }: Readonly<{ children: ReactNode }>) { {children} - , - }, - { - name: 'TanStack Query', - render: , - }, - ]} - /> + {import.meta.env.DEV && ( + , + }, + { + name: 'TanStack Query', + render: , + }, + ]} + /> + )} diff --git a/apps/server/src/server/api/contracts/todo.contract.ts b/apps/server/src/server/api/contracts/todo.contract.ts index b00a15e..8aed5cc 100644 --- a/apps/server/src/server/api/contracts/todo.contract.ts +++ b/apps/server/src/server/api/contracts/todo.contract.ts @@ -1,21 +1,14 @@ import { oc } from '@orpc/contract' import { createInsertSchema, createSelectSchema, createUpdateSchema } from 'drizzle-orm/zod' import { z } from 'zod' +import { generatedFieldKeys } from '@/server/db/fields' import { todoTable } from '@/server/db/schema' const selectSchema = createSelectSchema(todoTable) -const insertSchema = createInsertSchema(todoTable).omit({ - id: true, - createdAt: true, - updatedAt: true, -}) +const insertSchema = createInsertSchema(todoTable).omit(generatedFieldKeys) -const updateSchema = createUpdateSchema(todoTable).omit({ - id: true, - createdAt: true, - updatedAt: true, -}) +const updateSchema = createUpdateSchema(todoTable).omit(generatedFieldKeys) export const list = oc.input(z.void()).output(z.array(selectSchema)) diff --git a/apps/server/src/server/api/routers/todo.router.ts b/apps/server/src/server/api/routers/todo.router.ts index ea639bc..40c988f 100644 --- a/apps/server/src/server/api/routers/todo.router.ts +++ b/apps/server/src/server/api/routers/todo.router.ts @@ -15,7 +15,7 @@ export const create = os.todo.create.use(db).handler(async ({ context, input }) const [newTodo] = await context.db.insert(todoTable).values(input).returning() if (!newTodo) { - throw new ORPCError('NOT_FOUND') + throw new ORPCError('INTERNAL_SERVER_ERROR', { message: 'Failed to create todo' }) } return newTodo @@ -32,5 +32,9 @@ export const update = os.todo.update.use(db).handler(async ({ context, input }) }) export const remove = os.todo.remove.use(db).handler(async ({ context, input }) => { - await context.db.delete(todoTable).where(eq(todoTable.id, input.id)) + const [deleted] = await context.db.delete(todoTable).where(eq(todoTable.id, input.id)).returning({ id: todoTable.id }) + + if (!deleted) { + throw new ORPCError('NOT_FOUND') + } }) diff --git a/apps/server/src/server/db/fields.ts b/apps/server/src/server/db/fields.ts index de8a9b9..afea8cc 100644 --- a/apps/server/src/server/db/fields.ts +++ b/apps/server/src/server/db/fields.ts @@ -4,7 +4,7 @@ import { v7 as uuidv7 } from 'uuid' // id -export const id = (name: string) => uuid(name) +const id = (name: string) => uuid(name) export const pk = (name: string, strategy?: 'native' | 'extension') => { switch (strategy) { // PG 18+ diff --git a/apps/server/turbo.json b/apps/server/turbo.json index 10677c7..60b4196 100644 --- a/apps/server/turbo.json +++ b/apps/server/turbo.json @@ -4,6 +4,7 @@ "tasks": { "build": { "env": ["NODE_ENV", "VITE_*"], + "inputs": ["src/**", "public/**", "package.json", "tsconfig.json", "vite.config.ts"], "outputs": [".output/**"] }, "compile": {