From 41072da8c278083b56649ba12ecfa4d7472eb4d0 Mon Sep 17 00:00:00 2001 From: mrboxs Date: Sat, 1 Aug 2026 18:37:26 +0700 Subject: [PATCH 1/5] feat: implement main layout with header and theme switcher while migrating to tabler and reicon icon sets --- .vscode/settings.json | 9 ++++ apps/web/.vscode/settings.json | 11 ----- apps/web/package.json | 5 +- apps/web/src/components/header.tsx | 45 ++++++++++++++++++ apps/web/src/components/theme-switcher.tsx | 24 ++++++++++ apps/web/src/routeTree.gen.ts | 55 +++++++++++++++++----- apps/web/src/routes/_main.tsx | 18 +++++++ apps/web/src/routes/{ => _main}/index.tsx | 4 +- bun.lock | 7 +-- 9 files changed, 147 insertions(+), 31 deletions(-) delete mode 100644 apps/web/.vscode/settings.json create mode 100644 apps/web/src/components/header.tsx create mode 100644 apps/web/src/components/theme-switcher.tsx create mode 100644 apps/web/src/routes/_main.tsx rename apps/web/src/routes/{ => _main}/index.tsx (92%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 13938fd..f7f278f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,14 @@ "editor.formatOnSaveMode": "file", "editor.codeActionsOnSave": { "source.fixAll.oxc": "explicit" + }, + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true } } diff --git a/apps/web/.vscode/settings.json b/apps/web/.vscode/settings.json deleted file mode 100644 index a525351..0000000 --- a/apps/web/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "files.watcherExclude": { - "**/routeTree.gen.ts": true - }, - "search.exclude": { - "**/routeTree.gen.ts": true - }, - "files.readonlyInclude": { - "**/routeTree.gen.ts": true - } -} diff --git a/apps/web/package.json b/apps/web/package.json index bb808d7..7c6eae7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,6 +19,7 @@ "@orpc/client": "catalog:", "@orpc/server": "catalog:", "@orpc/tanstack-query": "catalog:", + "@tabler/icons-react": "catalog:", "@tanstack/react-devtools": "^0.10.8", "@tanstack/react-query": "catalog:", "@tanstack/react-query-devtools": "catalog:", @@ -28,11 +29,11 @@ "@tanstack/react-start": "^1.168.32", "@tanstack/router-plugin": "^1.168.23", "evlog": "catalog:", - "lucide-react": "catalog:", "next-themes": "^0.4.6", "nitro": "catalog:", "react": "catalog:", - "react-dom": "catalog:" + "react-dom": "catalog:", + "reicon-react": "^1.1.302" }, "devDependencies": { "@altstack/config": "workspace:*", diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx new file mode 100644 index 0000000..f6ae839 --- /dev/null +++ b/apps/web/src/components/header.tsx @@ -0,0 +1,45 @@ +import { IconMenu2 } from '@tabler/icons-react' +import { Link } from '@tanstack/react-router' +import { Layers2, Layers } from 'reicon-react' + +import { env } from '@altstack/env/web' + +import { Button } from '@altstack/ui/components/button' + +import { ThemeSwitcher } from '#/components/theme-switcher' + +export const Header = () => ( +
+
+
+ + + +
+ + +
+ +

{env.VITE_APP_NAME}

+ +
+ +
+ + + +
+
+
+) diff --git a/apps/web/src/components/theme-switcher.tsx b/apps/web/src/components/theme-switcher.tsx new file mode 100644 index 0000000..ce14763 --- /dev/null +++ b/apps/web/src/components/theme-switcher.tsx @@ -0,0 +1,24 @@ +import { IconMoon, IconSun } from '@tabler/icons-react' +import { useTheme } from 'next-themes' + +import { Button } from '@altstack/ui/components/button' + +export const ThemeSwitcher = () => { + const { resolvedTheme, setTheme } = useTheme() + + const switchTheme = () => { + setTheme(resolvedTheme === 'dark' ? 'light' : 'dark') + } + + return ( + + ) +} diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index 7f5514d..9206453 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -9,15 +9,20 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as IndexRouteImport } from './routes/index' +import { Route as MainRouteImport } from './routes/_main' +import { Route as MainIndexRouteImport } from './routes/_main/index' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth.$' import { Route as ApiRpcSplatRouteImport } from './routes/api/rpc.$' import { Route as ApiRpcReferenceSplatRouteImport } from './routes/api/rpc.reference.$' -const IndexRoute = IndexRouteImport.update({ +const MainRoute = MainRouteImport.update({ + id: '/_main', + getParentRoute: () => rootRouteImport, +} as any) +const MainIndexRoute = MainIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRouteImport, + getParentRoute: () => MainRoute, } as any) const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({ id: '/api/auth/$', @@ -36,20 +41,21 @@ const ApiRpcReferenceSplatRoute = ApiRpcReferenceSplatRouteImport.update({ } as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute + '/': typeof MainIndexRoute '/api/auth/$': typeof ApiAuthSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute '/api/rpc/reference/$': typeof ApiRpcReferenceSplatRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute + '/': typeof MainIndexRoute '/api/auth/$': typeof ApiAuthSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute '/api/rpc/reference/$': typeof ApiRpcReferenceSplatRoute } export interface FileRoutesById { __root__: typeof rootRouteImport - '/': typeof IndexRoute + '/_main': typeof MainRouteWithChildren + '/_main/': typeof MainIndexRoute '/api/auth/$': typeof ApiAuthSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute '/api/rpc/reference/$': typeof ApiRpcReferenceSplatRoute @@ -59,11 +65,17 @@ export interface FileRouteTypes { fullPaths: '/' | '/api/auth/$' | '/api/rpc/$' | '/api/rpc/reference/$' fileRoutesByTo: FileRoutesByTo to: '/' | '/api/auth/$' | '/api/rpc/$' | '/api/rpc/reference/$' - id: '__root__' | '/' | '/api/auth/$' | '/api/rpc/$' | '/api/rpc/reference/$' + id: + | '__root__' + | '/_main' + | '/_main/' + | '/api/auth/$' + | '/api/rpc/$' + | '/api/rpc/reference/$' fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute + MainRoute: typeof MainRouteWithChildren ApiAuthSplatRoute: typeof ApiAuthSplatRoute ApiRpcSplatRoute: typeof ApiRpcSplatRoute ApiRpcReferenceSplatRoute: typeof ApiRpcReferenceSplatRoute @@ -71,13 +83,20 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' + '/_main': { + id: '/_main' + path: '' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + preLoaderRoute: typeof MainRouteImport parentRoute: typeof rootRouteImport } + '/_main/': { + id: '/_main/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof MainIndexRouteImport + parentRoute: typeof MainRoute + } '/api/auth/$': { id: '/api/auth/$' path: '/api/auth/$' @@ -102,8 +121,18 @@ declare module '@tanstack/react-router' { } } +interface MainRouteChildren { + MainIndexRoute: typeof MainIndexRoute +} + +const MainRouteChildren: MainRouteChildren = { + MainIndexRoute: MainIndexRoute, +} + +const MainRouteWithChildren = MainRoute._addFileChildren(MainRouteChildren) + const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, + MainRoute: MainRouteWithChildren, ApiAuthSplatRoute: ApiAuthSplatRoute, ApiRpcSplatRoute: ApiRpcSplatRoute, ApiRpcReferenceSplatRoute: ApiRpcReferenceSplatRoute, diff --git a/apps/web/src/routes/_main.tsx b/apps/web/src/routes/_main.tsx new file mode 100644 index 0000000..2becaab --- /dev/null +++ b/apps/web/src/routes/_main.tsx @@ -0,0 +1,18 @@ +import { createFileRoute, Outlet } from '@tanstack/react-router' + +import { Header } from '#/components/header' + +export const Route = createFileRoute('/_main')({ + component: RouteComponent, +}) + +function RouteComponent() { + return ( + <> +
+
+ +
+ + ) +} diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/_main/index.tsx similarity index 92% rename from apps/web/src/routes/index.tsx rename to apps/web/src/routes/_main/index.tsx index 5fcc317..2b82bb0 100644 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/_main/index.tsx @@ -5,7 +5,7 @@ import { Button } from '@altstack/ui/components/button' import { orpc } from '#/utils/orpc.ts' -export const Route = createFileRoute('/')({ +export const Route = createFileRoute('/_main/')({ component: Home, loader: ({ context }) => context.queryClient.ensureQueryData(orpc.health.queryOptions()), @@ -15,7 +15,7 @@ function Home() { const healthQuery = useSuspenseQuery(orpc.health.queryOptions()) return ( -
+

Welcome to TanStack Start

diff --git a/bun.lock b/bun.lock index 3b3a454..d13d3bf 100644 --- a/bun.lock +++ b/bun.lock @@ -25,6 +25,7 @@ "@orpc/client": "catalog:", "@orpc/server": "catalog:", "@orpc/tanstack-query": "catalog:", + "@tabler/icons-react": "catalog:", "@tanstack/react-devtools": "^0.10.8", "@tanstack/react-query": "catalog:", "@tanstack/react-query-devtools": "catalog:", @@ -34,11 +35,11 @@ "@tanstack/react-start": "^1.168.32", "@tanstack/router-plugin": "^1.168.23", "evlog": "catalog:", - "lucide-react": "catalog:", "next-themes": "^0.4.6", "nitro": "catalog:", "react": "catalog:", "react-dom": "catalog:", + "reicon-react": "^1.1.302", }, "devDependencies": { "@altstack/config": "workspace:*", @@ -1461,8 +1462,6 @@ "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], - "lucide-react": ["lucide-react@1.25.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw=="], - "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -1655,6 +1654,8 @@ "recast": ["recast@0.23.12", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA=="], + "reicon-react": ["reicon-react@1.1.302", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-QbBVVe8GDajRbHuWprW2w5n6nDQq9qIM3778ohzbb5SPU+FQJ02mN86RojHfW9KGU1DQGSJei3aEKHwbalcMkw=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], From c0661513d1c2d5162a13e3eeec062e0b2f0b08aa Mon Sep 17 00:00:00 2001 From: mrboxs Date: Sat, 1 Aug 2026 22:10:40 +0700 Subject: [PATCH 2/5] feat: migrate from TanStack Start routes to a standalone Elysia server architecture --- .env.example | 9 ++- apps/server/.gitignore | 42 +++++++++++++ apps/server/README.md | 19 ++++++ apps/server/package.json | 26 ++++++++ apps/server/src/index.ts | 55 +++++++++++++++++ apps/server/tsconfig.json | 11 ++++ apps/server/vite.config.ts | 18 ++++++ apps/web/src/middlewares/auth-middleware.ts | 9 ++- apps/web/src/routeTree.gen.ts | 66 +-------------------- apps/web/src/routes/api/auth.$.ts | 12 ---- apps/web/src/routes/api/rpc.$.ts | 11 ---- apps/web/src/routes/api/rpc.reference.$.ts | 11 ---- apps/web/src/utils/orpc.ts | 2 +- bun.lock | 53 +++++++++++++++++ package.json | 1 + packages/api/package.json | 1 + packages/api/src/context.ts | 7 ++- packages/api/src/handler.ts | 17 +++--- packages/auth/src/client.ts | 2 +- packages/auth/src/server/config.ts | 4 +- packages/env/src/server.ts | 3 +- packages/env/src/vite-env.d.ts | 1 + packages/env/src/web.ts | 1 + 23 files changed, 262 insertions(+), 119 deletions(-) create mode 100644 apps/server/.gitignore create mode 100644 apps/server/README.md create mode 100644 apps/server/package.json create mode 100644 apps/server/src/index.ts create mode 100644 apps/server/tsconfig.json create mode 100644 apps/server/vite.config.ts delete mode 100644 apps/web/src/routes/api/auth.$.ts delete mode 100644 apps/web/src/routes/api/rpc.$.ts delete mode 100644 apps/web/src/routes/api/rpc.reference.$.ts diff --git a/.env.example b/.env.example index c576be6..94c0d03 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,12 @@ +POSTGRES_PASSWORD=password NODE_ENV=development +PORT=3009 DATABASE_URL=postgresql://postgres:password@localhost:5432/db_name -APP_NAME=altstack +APP_NAME=Altstack BETTER_AUTH_URL=http://localhost:3010 BETTER_AUTH_SECRET=your-better-auth-secret -BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3010,http://:3010,https://dokploy.example.com +CORS_ORIGINS=http://localhost:3010,http://:3010,https://dokploy.example.com -VITE_APP_NAME=altstack +VITE_APP_NAME=Altstack VITE_APP_URL=http://localhost:3010 +VITE_SERVER_URL=http://localhost:3009 diff --git a/apps/server/.gitignore b/apps/server/.gitignore new file mode 100644 index 0000000..87e5610 --- /dev/null +++ b/apps/server/.gitignore @@ -0,0 +1,42 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +**/*.trace +**/*.zip +**/*.tar.gz +**/*.tgz +**/*.log +package-lock.json +**/*.bun \ No newline at end of file diff --git a/apps/server/README.md b/apps/server/README.md new file mode 100644 index 0000000..bebf061 --- /dev/null +++ b/apps/server/README.md @@ -0,0 +1,19 @@ +# Elysia with Bun runtime + +## Getting Started + +To get started with this template, simply paste this command into your terminal: + +```bash +bun create elysia ./elysia-example +``` + +## Development + +To start the development server run: + +```bash +bun run dev +``` + +Open http://localhost:3000/ with your browser to see the result. diff --git a/apps/server/package.json b/apps/server/package.json new file mode 100644 index 0000000..8509910 --- /dev/null +++ b/apps/server/package.json @@ -0,0 +1,26 @@ +{ + "name": "server", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun run --watch src/index.ts" + }, + "dependencies": { + "@altstack/api": "workspace:*", + "@altstack/auth": "workspace:*", + "@altstack/db": "workspace:*", + "@altstack/env": "workspace:*", + "@elysiajs/cors": "^1.4.2", + "elysia": "catalog:", + "evlog": "catalog:" + }, + "devDependencies": { + "@altstack/config": "workspace:*", + "@types/bun": "catalog:", + "bumpp": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plus": "catalog:" + } +} diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts new file mode 100644 index 0000000..77367dd --- /dev/null +++ b/apps/server/src/index.ts @@ -0,0 +1,55 @@ +import { cors } from '@elysiajs/cors' +import { Elysia } from 'elysia' +import { initLogger } from 'evlog' +import { createAuthMiddleware } from 'evlog/better-auth' +import { evlog } from 'evlog/elysia' +import { createFsDrain } from 'evlog/fs' + +import { handleOpenApi, handleRPC } from '@altstack/api/handler' + +import { auth } from '@altstack/auth/server' + +import { env } from '@altstack/env/server' + +initLogger({ + env: { service: 'altstack-server' }, +}) + +const identifyUser = createAuthMiddleware(auth, { + exclude: ['/api/auth/**'], + maskEmail: true, +}) + +const app = new Elysia() + .use( + evlog({ + drain: env.NODE_ENV === 'production' ? undefined : createFsDrain(), + }) + ) + .derive(async ({ request, log }) => { + await identifyUser(log, request.headers, new URL(request.url).pathname) + return {} + }) + .use( + cors({ + origin: env.CORS_ORIGINS, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true, + }) + ) + .all('/api/auth/*', async (context) => { + const { request, status } = context + if (['POST', 'GET'].includes(request.method)) { + return auth.handler(request) + } + return status(405) + }) + .all('/api/rpc*', async (context) => await handleRPC(context)) + .all('/api/reference*', async (context) => await handleOpenApi(context)) + .get('/', () => 'Altstack server is running!') + .listen(env.PORT) + +console.debug( + `🦊 Altstack server is running at http://${app.server?.hostname}:${app.server?.port}` +) diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json new file mode 100644 index 0000000..ad607cf --- /dev/null +++ b/apps/server/tsconfig.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@altstack/config/tsconfig.base.json", + "compilerOptions": { + "declaration": true, + "types": ["bun", "vite/client"], + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/apps/server/vite.config.ts b/apps/server/vite.config.ts new file mode 100644 index 0000000..53be045 --- /dev/null +++ b/apps/server/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite-plus' + +export default defineConfig({ + resolve: { tsconfigPaths: true }, + pack: { + dts: { + tsgo: true, + }, + format: 'esm', + outDir: 'dist', + clean: true, + entry: ['src/**/*.ts'], + }, + test: { + environment: 'node', + passWithNoTests: true, + }, +}) diff --git a/apps/web/src/middlewares/auth-middleware.ts b/apps/web/src/middlewares/auth-middleware.ts index 0482ce1..c9d5a69 100644 --- a/apps/web/src/middlewares/auth-middleware.ts +++ b/apps/web/src/middlewares/auth-middleware.ts @@ -1,11 +1,14 @@ import { createMiddleware } from '@tanstack/react-start' -import { auth } from '@altstack/auth/server' +import { authClient } from '@altstack/auth/client' export const authMiddleware = createMiddleware().server( async ({ next, request }) => { - const session = await auth.api.getSession({ - headers: request.headers, + const session = await authClient.getSession({ + fetchOptions: { + headers: request.headers, + throw: true, + }, }) return next({ context: { auth: session } }) diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index 9206453..797b778 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -11,9 +11,6 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as MainRouteImport } from './routes/_main' import { Route as MainIndexRouteImport } from './routes/_main/index' -import { Route as ApiAuthSplatRouteImport } from './routes/api/auth.$' -import { Route as ApiRpcSplatRouteImport } from './routes/api/rpc.$' -import { Route as ApiRpcReferenceSplatRouteImport } from './routes/api/rpc.reference.$' const MainRoute = MainRouteImport.update({ id: '/_main', @@ -24,61 +21,28 @@ const MainIndexRoute = MainIndexRouteImport.update({ path: '/', getParentRoute: () => MainRoute, } as any) -const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({ - id: '/api/auth/$', - path: '/api/auth/$', - getParentRoute: () => rootRouteImport, -} as any) -const ApiRpcSplatRoute = ApiRpcSplatRouteImport.update({ - id: '/api/rpc/$', - path: '/api/rpc/$', - getParentRoute: () => rootRouteImport, -} as any) -const ApiRpcReferenceSplatRoute = ApiRpcReferenceSplatRouteImport.update({ - id: '/api/rpc/reference/$', - path: '/api/rpc/reference/$', - getParentRoute: () => rootRouteImport, -} as any) export interface FileRoutesByFullPath { '/': typeof MainIndexRoute - '/api/auth/$': typeof ApiAuthSplatRoute - '/api/rpc/$': typeof ApiRpcSplatRoute - '/api/rpc/reference/$': typeof ApiRpcReferenceSplatRoute } export interface FileRoutesByTo { '/': typeof MainIndexRoute - '/api/auth/$': typeof ApiAuthSplatRoute - '/api/rpc/$': typeof ApiRpcSplatRoute - '/api/rpc/reference/$': typeof ApiRpcReferenceSplatRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/_main': typeof MainRouteWithChildren '/_main/': typeof MainIndexRoute - '/api/auth/$': typeof ApiAuthSplatRoute - '/api/rpc/$': typeof ApiRpcSplatRoute - '/api/rpc/reference/$': typeof ApiRpcReferenceSplatRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/api/auth/$' | '/api/rpc/$' | '/api/rpc/reference/$' + fullPaths: '/' fileRoutesByTo: FileRoutesByTo - to: '/' | '/api/auth/$' | '/api/rpc/$' | '/api/rpc/reference/$' - id: - | '__root__' - | '/_main' - | '/_main/' - | '/api/auth/$' - | '/api/rpc/$' - | '/api/rpc/reference/$' + to: '/' + id: '__root__' | '/_main' | '/_main/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { MainRoute: typeof MainRouteWithChildren - ApiAuthSplatRoute: typeof ApiAuthSplatRoute - ApiRpcSplatRoute: typeof ApiRpcSplatRoute - ApiRpcReferenceSplatRoute: typeof ApiRpcReferenceSplatRoute } declare module '@tanstack/react-router' { @@ -97,27 +61,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MainIndexRouteImport parentRoute: typeof MainRoute } - '/api/auth/$': { - id: '/api/auth/$' - path: '/api/auth/$' - fullPath: '/api/auth/$' - preLoaderRoute: typeof ApiAuthSplatRouteImport - parentRoute: typeof rootRouteImport - } - '/api/rpc/$': { - id: '/api/rpc/$' - path: '/api/rpc/$' - fullPath: '/api/rpc/$' - preLoaderRoute: typeof ApiRpcSplatRouteImport - parentRoute: typeof rootRouteImport - } - '/api/rpc/reference/$': { - id: '/api/rpc/reference/$' - path: '/api/rpc/reference/$' - fullPath: '/api/rpc/reference/$' - preLoaderRoute: typeof ApiRpcReferenceSplatRouteImport - parentRoute: typeof rootRouteImport - } } } @@ -133,9 +76,6 @@ const MainRouteWithChildren = MainRoute._addFileChildren(MainRouteChildren) const rootRouteChildren: RootRouteChildren = { MainRoute: MainRouteWithChildren, - ApiAuthSplatRoute: ApiAuthSplatRoute, - ApiRpcSplatRoute: ApiRpcSplatRoute, - ApiRpcReferenceSplatRoute: ApiRpcReferenceSplatRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/apps/web/src/routes/api/auth.$.ts b/apps/web/src/routes/api/auth.$.ts deleted file mode 100644 index fc8a45f..0000000 --- a/apps/web/src/routes/api/auth.$.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' - -import { auth } from '@altstack/auth/server' - -export const Route = createFileRoute('/api/auth/$')({ - server: { - handlers: { - GET: ({ request }) => auth.handler(request), - POST: ({ request }) => auth.handler(request), - }, - }, -}) diff --git a/apps/web/src/routes/api/rpc.$.ts b/apps/web/src/routes/api/rpc.$.ts deleted file mode 100644 index d81e752..0000000 --- a/apps/web/src/routes/api/rpc.$.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' - -import { handleRPC } from '@altstack/api/handler' - -export const Route = createFileRoute('/api/rpc/$')({ - server: { - handlers: { - ANY: handleRPC, - }, - }, -}) diff --git a/apps/web/src/routes/api/rpc.reference.$.ts b/apps/web/src/routes/api/rpc.reference.$.ts deleted file mode 100644 index a3d463e..0000000 --- a/apps/web/src/routes/api/rpc.reference.$.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' - -import { handleOpenApi } from '@altstack/api/handler' - -export const Route = createFileRoute('/api/rpc/reference/$')({ - server: { - handlers: { - ANY: handleOpenApi, - }, - }, -}) diff --git a/apps/web/src/utils/orpc.ts b/apps/web/src/utils/orpc.ts index 5ef60f0..167d75b 100644 --- a/apps/web/src/utils/orpc.ts +++ b/apps/web/src/utils/orpc.ts @@ -7,7 +7,7 @@ import type { ORPCRouterClient } from '@altstack/api/routers' import { env } from '@altstack/env/web' const link = new RPCLink({ - url: `${env.VITE_APP_URL}/api/rpc`, + url: `${env.VITE_SERVER_URL}/api/rpc`, fetch(url, options) { return fetch(url, { ...options, diff --git a/bun.lock b/bun.lock index d13d3bf..aced62c 100644 --- a/bun.lock +++ b/bun.lock @@ -15,6 +15,27 @@ "vite-plus": "catalog:", }, }, + "apps/server": { + "name": "server", + "version": "0.0.0", + "dependencies": { + "@altstack/api": "workspace:*", + "@altstack/auth": "workspace:*", + "@altstack/db": "workspace:*", + "@altstack/env": "workspace:*", + "@elysiajs/cors": "^1.4.2", + "elysia": "catalog:", + "evlog": "catalog:", + }, + "devDependencies": { + "@altstack/config": "workspace:*", + "@types/bun": "catalog:", + "bumpp": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plus": "catalog:", + }, + }, "apps/web": { "name": "web", "dependencies": { @@ -74,6 +95,7 @@ "@orpc/server": "catalog:", "@orpc/zod": "catalog:", "drizzle-orm": "catalog:", + "elysia": "catalog:", "evlog": "catalog:", "zod": "catalog:", }, @@ -211,6 +233,7 @@ "dotenv": "^17.4.2", "drizzle-kit": "^1.0.0-rc.4", "drizzle-orm": "^1.0.0-rc.4", + "elysia": "^1.4.29", "evlog": "^2.22.3", "jsdom": "^28.1.0", "lucide-react": "^1.25.0", @@ -332,6 +355,8 @@ "@blazediff/core": ["@blazediff/core@1.9.1", "", {}, "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA=="], + "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="], + "@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="], "@csstools/color-helpers": ["@csstools/color-helpers@6.1.0", "", {}, "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg=="], @@ -352,6 +377,8 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.12.0", "", {}, "sha512-mlUE+rZ8CatQekLhnaiN91Iemdd+e2gFKooGlnRB3oPTL3VghLfX24dx7HrzMNeC1JrIB/0kpsfyty3f5HNfxQ=="], + "@elysiajs/cors": ["@elysiajs/cors@1.4.2", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-FTCcbH35brTLigF1W7BYySRZomgI/dBEMK9BgK9RP9Nez7zmpGh4koL/Yr1BFv8nYz7CfhRvcM8d/c+XnwMaVQ=="], + "@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], @@ -692,6 +719,8 @@ "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + "@sinclair/typebox": ["@sinclair/typebox@0.34.52", "", {}, "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw=="], + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], "@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.6", "", { "dependencies": { "@solid-primitives/utils": "^6.4.1" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-5I0YJcTVYIWoMmgBSROBZGcz+ymhew/pGTg2dHW74BUjFKsV8Li4bOZYl0YAGP4mHw5o4UBd9/BEesqBci3wxw=="], @@ -824,6 +853,10 @@ "@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="], + "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="], "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], @@ -1166,6 +1199,8 @@ "electron-to-chromium": ["electron-to-chromium@1.5.394", "", {}, "sha512-Wmt2Gm0o8JWBuGgmc4XZ0u9s1RaCRqhxP47phplmfg04+qypTUurpeJGP45A7Fhv7jdrrVH44PLlR9qXo37cVQ=="], + "elysia": ["elysia@1.4.29", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-GwMRGGwSdjfPt+w3LA0fqTuYJtS8uVRJicvoar98/HrO5qdFKDc9CwjIb6Kja+v39lkY+58hr2JvdR9jQzlUuA=="], + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -1228,6 +1263,8 @@ "evlog": ["evlog@2.22.3", "", { "peerDependencies": { "@nestjs/common": ">=11.1.28", "@nuxt/kit": "^4.4.2", "@orpc/server": ">=1.14.8", "@tanstack/start-client-core": "^1.170.14", "ai": ">=6.0.168 <8.0.0", "elysia": ">=1.4.29", "eve": ">=0.24.3", "express": ">=5.2.1", "fastify": ">=5.10.0", "h3": "^1.15.11", "hono": ">=4.12.30", "next": ">=16.2.10", "nitro": "^3.0.260311-beta", "nitropack": "^2.13.4", "ofetch": "^1.5.1", "react": ">=19.2.7", "react-router": ">=7.18.1", "vite": "^7.0.0 || ^8.0.0" }, "optionalPeers": ["@nestjs/common", "@nuxt/kit", "@orpc/server", "@tanstack/start-client-core", "ai", "elysia", "eve", "express", "fastify", "h3", "hono", "next", "nitro", "nitropack", "ofetch", "react", "react-router", "vite"] }, "sha512-xybbUKtV26ezgyagT0aZ6tigIjwQuc4mI0Fm52goospAdGAA4ApC+keC36Bsxt1Gm7vT8W429T3WIsqMc8nwMQ=="], + "exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="], + "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], @@ -1238,6 +1275,8 @@ "exsolve": ["exsolve@1.1.0", "", {}, "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw=="], + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], @@ -1258,6 +1297,8 @@ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "file-type": ["file-type@22.0.1", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.5", "token-types": "^6.1.2", "uint8array-extras": "^1.5.0" } }, "sha512-ww5Mhre0EE+jmBvOXTmXAbEMuZE7uX4a3+oRCQFNj8w++g3ev913N6tXQz0XTXbueQ5TWQfm6BdaViEHHn8bhA=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], @@ -1338,6 +1379,8 @@ "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -1472,6 +1515,8 @@ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], @@ -1696,6 +1741,8 @@ "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + "server": ["server@workspace:apps/server"], + "set-cookie-parser": ["set-cookie-parser@3.1.2", "", {}, "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw=="], "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], @@ -1754,6 +1801,8 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], @@ -1788,6 +1837,8 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], "tough-cookie": ["tough-cookie@6.0.2", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA=="], @@ -1814,6 +1865,8 @@ "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + "unconfig": ["unconfig@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.5.0" } }, "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA=="], "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], diff --git a/package.json b/package.json index 2297331..3fee9ea 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "dotenv": "^17.4.2", "drizzle-kit": "^1.0.0-rc.4", "drizzle-orm": "^1.0.0-rc.4", + "elysia": "^1.4.29", "evlog": "^2.22.3", "jsdom": "^28.1.0", "lucide-react": "^1.25.0", diff --git a/packages/api/package.json b/packages/api/package.json index e5361a5..b9aecb5 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -49,6 +49,7 @@ "@orpc/server": "catalog:", "@orpc/zod": "catalog:", "drizzle-orm": "catalog:", + "elysia": "catalog:", "evlog": "catalog:", "zod": "catalog:" }, diff --git a/packages/api/src/context.ts b/packages/api/src/context.ts index 1e24d9f..8899b45 100644 --- a/packages/api/src/context.ts +++ b/packages/api/src/context.ts @@ -1,3 +1,4 @@ +import type { Context as ElysiaContext } from 'elysia' import type { EvlogOrpcContext } from 'evlog/orpc' import type { auth } from '@altstack/auth/server' @@ -9,13 +10,15 @@ export type ORPCContext = { auth: Awaited> } & EvlogOrpcContext -export async function createORPCContext(opts: Request): Promise { +export async function createORPCContext( + opts: ElysiaContext +): Promise { const [{ auth }, { db }] = await Promise.all([ import('@altstack/auth/server'), import('@altstack/db'), ]) - const session = await auth.api.getSession({ headers: opts.headers }) + const session = await auth.api.getSession({ headers: opts.request.headers }) return { db, auth: session } as ORPCContext } diff --git a/packages/api/src/handler.ts b/packages/api/src/handler.ts index 993c31f..9befc64 100644 --- a/packages/api/src/handler.ts +++ b/packages/api/src/handler.ts @@ -3,6 +3,7 @@ import { OpenAPIHandler } from '@orpc/openapi/fetch' import { OpenAPIReferencePlugin } from '@orpc/openapi/plugins' import { RPCHandler } from '@orpc/server/fetch' import { ZodToJsonSchemaConverter } from '@orpc/zod/zod4' +import type { Context as ElysiaContext } from 'elysia' import { withEvlog } from 'evlog/orpc' import { createORPCContext } from '@altstack/api/context' @@ -22,7 +23,7 @@ export const openApiHandler = withEvlog( schemaConverters: [new ZodToJsonSchemaConverter()], specGenerateOptions: { info: { - title: 'Altstack RPC API Reference', + title: 'Altstack API', version: '0.0.0', description: 'API Reference for Altstack', }, @@ -46,10 +47,10 @@ export const openApiHandler = withEvlog( }) ) -export async function handleRPC({ request }: { request: Request }) { - const ctx = await createORPCContext(request) +export async function handleRPC(opts: ElysiaContext) { + const ctx = await createORPCContext(opts) - const { matched, response } = await rpcHandler.handle(request, { + const { matched, response } = await rpcHandler.handle(opts.request, { context: ctx, prefix: '/api/rpc', }) @@ -57,12 +58,12 @@ export async function handleRPC({ request }: { request: Request }) { return matched ? response : new Response('Not Found', { status: 404 }) } -export async function handleOpenApi({ request }: { request: Request }) { - const ctx = await createORPCContext(request) +export async function handleOpenApi(opts: ElysiaContext) { + const ctx = await createORPCContext(opts) - const { matched, response } = await openApiHandler.handle(request, { + const { matched, response } = await openApiHandler.handle(opts.request, { context: ctx, - prefix: '/api/rpc/reference', + prefix: '/api/reference', }) return matched ? response : new Response('Not Found', { status: 404 }) diff --git a/packages/auth/src/client.ts b/packages/auth/src/client.ts index 478b7a8..de12926 100644 --- a/packages/auth/src/client.ts +++ b/packages/auth/src/client.ts @@ -10,7 +10,7 @@ import type { auth } from '@altstack/auth/server' import { env } from '@altstack/env/web' export const authClient = createAuthClient({ - baseURL: env.VITE_APP_URL, + baseURL: env.VITE_SERVER_URL, plugins: [ inferAdditionalFields(), multiSessionClient(), diff --git a/packages/auth/src/server/config.ts b/packages/auth/src/server/config.ts index 0e74c2f..fbd093e 100644 --- a/packages/auth/src/server/config.ts +++ b/packages/auth/src/server/config.ts @@ -6,7 +6,6 @@ import { openAPI as openAPIPlugin, username as usernamePlugin, } from 'better-auth/plugins' -import { tanstackStartCookies } from 'better-auth/tanstack-start' import { db } from '@altstack/db' @@ -40,12 +39,11 @@ export function createAuthConfig() { multiSessionPlugin(), openAPIPlugin(), usernamePlugin(), - tanstackStartCookies(), ], secret: env.BETTER_AUTH_SECRET, session: { expiresIn: 60 * 60 * 24 * 3, }, - trustedOrigins: env.BETTER_AUTH_TRUSTED_ORIGINS, + trustedOrigins: env.CORS_ORIGINS, } satisfies BetterAuthOptions } diff --git a/packages/env/src/server.ts b/packages/env/src/server.ts index 7525964..7d654c7 100644 --- a/packages/env/src/server.ts +++ b/packages/env/src/server.ts @@ -10,11 +10,12 @@ dotenv.config({ path: resolve(__dirname, '../../../.env') }) export const env = createEnv({ server: { NODE_ENV: z.enum(['development', 'production', 'test']), + PORT: z.coerce.number(), DATABASE_URL: z.url(), APP_NAME: z.string(), BETTER_AUTH_URL: z.url(), BETTER_AUTH_SECRET: z.string().min(32), - BETTER_AUTH_TRUSTED_ORIGINS: z + CORS_ORIGINS: z .string() .transform((cors) => cors diff --git a/packages/env/src/vite-env.d.ts b/packages/env/src/vite-env.d.ts index 32fc410..0df239a 100644 --- a/packages/env/src/vite-env.d.ts +++ b/packages/env/src/vite-env.d.ts @@ -7,6 +7,7 @@ interface ImportMetaEnv { readonly VITE_APP_NAME: string readonly VITE_APP_URL: string + readonly VITE_SERVER_URL: string } interface ImportMeta { diff --git a/packages/env/src/web.ts b/packages/env/src/web.ts index 93a846d..b56d764 100644 --- a/packages/env/src/web.ts +++ b/packages/env/src/web.ts @@ -6,6 +6,7 @@ export const env = createEnv({ client: { VITE_APP_NAME: z.string(), VITE_APP_URL: z.url(), + VITE_SERVER_URL: z.url(), }, runtimeEnv: import.meta.env, emptyStringAsUndefined: true, From 3eb7aa168d8610731e66cec66d8f5a44289db334 Mon Sep 17 00:00:00 2001 From: mrboxs Date: Sat, 1 Aug 2026 22:24:42 +0700 Subject: [PATCH 3/5] fix: disable request body parsing for RPC and reference API endpoints --- apps/server/src/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 77367dd..6d66a63 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -45,8 +45,12 @@ const app = new Elysia() } return status(405) }) - .all('/api/rpc*', async (context) => await handleRPC(context)) - .all('/api/reference*', async (context) => await handleOpenApi(context)) + .all('/api/rpc*', async (context) => await handleRPC(context), { + parse: 'none', + }) + .all('/api/reference*', async (context) => await handleOpenApi(context), { + parse: 'none', + }) .get('/', () => 'Altstack server is running!') .listen(env.PORT) From 9982a7e120215e99bd52fcf9c5d1655fc58d740f Mon Sep 17 00:00:00 2001 From: mrboxs Date: Sun, 2 Aug 2026 11:32:58 +0700 Subject: [PATCH 4/5] feat: add dashboard app with auth middleware and evlog plugins, migrate theme-provider to ui package --- apps/dashboard/.cta.json | 17 ++ apps/dashboard/.gitignore | 13 ++ apps/dashboard/README.md | 192 ++++++++++++++++++ apps/dashboard/nitro.config.ts | 13 ++ apps/dashboard/package.json | 59 ++++++ apps/dashboard/public/favicon.ico | Bin 0 -> 3870 bytes apps/dashboard/public/logo192.png | Bin 0 -> 5347 bytes apps/dashboard/public/logo512.png | Bin 0 -> 9664 bytes apps/dashboard/public/manifest.json | 25 +++ apps/dashboard/public/robots.txt | 3 + apps/dashboard/server/plugins/evlog-drain.ts | 7 + apps/dashboard/server/plugins/evlog-enrich.ts | 13 ++ apps/dashboard/src/functions/get-auth-fn.ts | 7 + apps/dashboard/src/lib/query-client.ts | 49 +++++ .../src/middlewares/auth-middleware.ts | 16 ++ apps/dashboard/src/routeTree.gen.ts | 68 +++++++ apps/dashboard/src/router.tsx | 28 +++ apps/dashboard/src/routes/__root.tsx | 104 ++++++++++ apps/dashboard/src/routes/index.tsx | 25 +++ apps/dashboard/src/styles.css | 1 + apps/dashboard/src/utils/orpc.ts | 23 +++ apps/dashboard/tsconfig.json | 29 +++ apps/dashboard/tsr.config.json | 3 + apps/dashboard/vite.config.ts | 36 ++++ apps/dashboard/vitest.config.ts | 9 + apps/server/package.json | 4 +- apps/web/package.json | 18 +- apps/web/server/plugins/evlog-drain.ts | 7 + apps/web/server/plugins/evlog-enrich.ts | 13 ++ apps/web/src/components/theme-switcher.tsx | 2 +- apps/web/src/router.tsx | 4 +- apps/web/src/routes/__root.tsx | 7 +- apps/web/src/routes/_main/index.tsx | 2 +- apps/web/vite.config.ts | 4 +- bun.lock | 115 ++++++++--- package.json | 22 +- packages/ui/package.json | 1 + .../components/customs}/theme-provider.tsx | 1 + 38 files changed, 890 insertions(+), 50 deletions(-) create mode 100644 apps/dashboard/.cta.json create mode 100644 apps/dashboard/.gitignore create mode 100644 apps/dashboard/README.md create mode 100644 apps/dashboard/nitro.config.ts create mode 100644 apps/dashboard/package.json create mode 100644 apps/dashboard/public/favicon.ico create mode 100644 apps/dashboard/public/logo192.png create mode 100644 apps/dashboard/public/logo512.png create mode 100644 apps/dashboard/public/manifest.json create mode 100644 apps/dashboard/public/robots.txt create mode 100644 apps/dashboard/server/plugins/evlog-drain.ts create mode 100644 apps/dashboard/server/plugins/evlog-enrich.ts create mode 100644 apps/dashboard/src/functions/get-auth-fn.ts create mode 100644 apps/dashboard/src/lib/query-client.ts create mode 100644 apps/dashboard/src/middlewares/auth-middleware.ts create mode 100644 apps/dashboard/src/routeTree.gen.ts create mode 100644 apps/dashboard/src/router.tsx create mode 100644 apps/dashboard/src/routes/__root.tsx create mode 100644 apps/dashboard/src/routes/index.tsx create mode 100644 apps/dashboard/src/styles.css create mode 100644 apps/dashboard/src/utils/orpc.ts create mode 100644 apps/dashboard/tsconfig.json create mode 100644 apps/dashboard/tsr.config.json create mode 100644 apps/dashboard/vite.config.ts create mode 100644 apps/dashboard/vitest.config.ts create mode 100644 apps/web/server/plugins/evlog-drain.ts create mode 100644 apps/web/server/plugins/evlog-enrich.ts rename {apps/web/src/components => packages/ui/src/components/customs}/theme-provider.tsx (92%) diff --git a/apps/dashboard/.cta.json b/apps/dashboard/.cta.json new file mode 100644 index 0000000..a90cfed --- /dev/null +++ b/apps/dashboard/.cta.json @@ -0,0 +1,17 @@ +{ + "projectName": "dashboard", + "mode": "file-router", + "typescript": true, + "packageManager": "bun", + "includeExamples": false, + "tailwind": true, + "projectPreset": "default", + "addOnOptions": {}, + "git": false, + "install": false, + "intent": true, + "routerOnly": false, + "version": 1, + "framework": "react", + "chosenAddOns": ["nitro", "compiler"] +} diff --git a/apps/dashboard/.gitignore b/apps/dashboard/.gitignore new file mode 100644 index 0000000..8b25bb5 --- /dev/null +++ b/apps/dashboard/.gitignore @@ -0,0 +1,13 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +.env +.nitro +.tanstack +.wrangler +.output +.vinxi +__unconfig* +todos.json diff --git a/apps/dashboard/README.md b/apps/dashboard/README.md new file mode 100644 index 0000000..5bdbbda --- /dev/null +++ b/apps/dashboard/README.md @@ -0,0 +1,192 @@ +Welcome to your new TanStack Start app! + +# Getting Started + +To run this application: + +```bash +bun install +bun --bun run dev +``` + +# Building For Production + +To build this application for production: + +```bash +bun --bun run build +``` + +## Styling + +This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. + +### Removing Tailwind CSS + +If you prefer not to use Tailwind CSS: + +1. Remove the demo pages in `src/routes/demo/` +2. Replace the Tailwind import in `src/styles.css` with your own styles +3. Remove `tailwindcss()` from the plugins array in `vite.config.ts` +4. Remove `@tailwindcss/vite` and `tailwindcss` from `package.json` + +## Deploy with Nitro + +This project uses Nitro as a generic server adapter, so it can run on any Node-compatible host. + +```bash +npm run build +node dist/server/index.mjs +``` + +The build output is a self-contained Node server. To deploy, push the `dist/` directory to your host (Render, Fly.io, your own VPS, etc.) and run the server command above. + +For host-specific presets (Vercel, Netlify, Cloudflare, AWS Lambda, etc.) and tuning, see https://v3.nitro.build/deploy. + +## Routing + +This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`. + +### Adding A Route + +To add a new route to your application just add a new file in the `./src/routes` directory. + +TanStack will automatically generate the content of the route file for you. + +Now that you have two routes you can use a `Link` component to navigate between them. + +### Adding Links + +To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`. + +```tsx +import { Link } from '@tanstack/react-router' +``` + +Then anywhere in your JSX you can use it like so: + +```tsx +About +``` + +This will create a link that will navigate to the `/about` route. + +More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). + +### Using A Layout + +In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you render `{children}` in the `shellComponent`. + +Here is an example layout that includes a header: + +```tsx +import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { charSet: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { title: 'My App' }, + ], + }), + shellComponent: ({ children }) => ( + + + + + +

+ +
+ {children} + + + + ), +}) +``` + +More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). + +## Server Functions + +TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components. + +```tsx +import { createServerFn } from '@tanstack/react-start' + +const getServerTime = createServerFn({ + method: 'GET', +}).handler(async () => { + return new Date().toISOString() +}) + +// Use in a component +function MyComponent() { + const [time, setTime] = useState('') + + useEffect(() => { + getServerTime().then(setTime) + }, []) + + return
Server time: {time}
+} +``` + +## API Routes + +You can create API routes by using the `server` property in your route definitions: + +```tsx +import { createFileRoute } from '@tanstack/react-router' +import { json } from '@tanstack/react-start' + +export const Route = createFileRoute('/api/hello')({ + server: { + handlers: { + GET: () => json({ message: 'Hello, World!' }), + }, + }, +}) +``` + +## Data Fetching + +There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. + +For example: + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/people')({ + loader: async () => { + const response = await fetch('https://swapi.dev/api/people') + return response.json() + }, + component: PeopleComponent, +}) + +function PeopleComponent() { + const data = Route.useLoaderData() + return ( +
    + {data.results.map((person) => ( +
  • {person.name}
  • + ))} +
+ ) +} +``` + +Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). + +# Learn More + +You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). + +For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start). diff --git a/apps/dashboard/nitro.config.ts b/apps/dashboard/nitro.config.ts new file mode 100644 index 0000000..0f4b502 --- /dev/null +++ b/apps/dashboard/nitro.config.ts @@ -0,0 +1,13 @@ +import evlog from 'evlog/nitro/v3' +import { defineConfig } from 'nitro' + +export default defineConfig({ + experimental: { + asyncContext: true, + }, + modules: [ + evlog({ + env: { service: 'altstack-dashboard' }, + }), + ], +}) diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json new file mode 100644 index 0000000..92a6395 --- /dev/null +++ b/apps/dashboard/package.json @@ -0,0 +1,59 @@ +{ + "name": "dashboard", + "private": true, + "type": "module", + "imports": { + "#/*": "./src/*" + }, + "scripts": { + "dev": "vp dev", + "generate-routes": "tsr generate", + "preview": "vp preview", + "test": "vp test run" + }, + "dependencies": { + "@altstack/api": "workspace:*", + "@altstack/auth": "workspace:*", + "@altstack/env": "workspace:*", + "@altstack/ui": "workspace:*", + "@orpc/client": "catalog:", + "@orpc/server": "catalog:", + "@orpc/tanstack-query": "catalog:", + "@tabler/icons-react": "catalog:", + "@tanstack/react-devtools": "catalog:", + "@tanstack/react-query": "catalog:", + "@tanstack/react-query-devtools": "catalog:", + "@tanstack/react-router": "catalog:", + "@tanstack/react-router-devtools": "catalog:", + "@tanstack/react-router-ssr-query": "catalog:", + "@tanstack/react-start": "catalog:", + "better-auth": "catalog:", + "evlog": "catalog:", + "nitro": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "reicon-react": "catalog:" + }, + "devDependencies": { + "@altstack/config": "workspace:*", + "@rolldown/plugin-babel": "catalog:", + "@tailwindcss/vite": "catalog:", + "@tanstack/devtools-vite": "catalog:", + "@tanstack/router-cli": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@vitejs/plugin-react": "catalog:", + "babel-plugin-react-compiler": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plus": "catalog:" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "esbuild", + "lightningcss" + ] + } +} diff --git a/apps/dashboard/public/favicon.ico b/apps/dashboard/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/apps/dashboard/public/logo192.png b/apps/dashboard/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/apps/dashboard/public/manifest.json b/apps/dashboard/public/manifest.json new file mode 100644 index 0000000..4d9581b --- /dev/null +++ b/apps/dashboard/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "TanStack App", + "name": "Create TanStack App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/apps/dashboard/public/robots.txt b/apps/dashboard/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/apps/dashboard/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/apps/dashboard/server/plugins/evlog-drain.ts b/apps/dashboard/server/plugins/evlog-drain.ts new file mode 100644 index 0000000..8519e7c --- /dev/null +++ b/apps/dashboard/server/plugins/evlog-drain.ts @@ -0,0 +1,7 @@ +import { createFsDrain } from 'evlog/fs' +import { definePlugin } from 'nitro' + +export default definePlugin((nitroApp) => { + if (!import.meta.dev) return + nitroApp.hooks.hook('evlog:drain', createFsDrain()) +}) diff --git a/apps/dashboard/server/plugins/evlog-enrich.ts b/apps/dashboard/server/plugins/evlog-enrich.ts new file mode 100644 index 0000000..30b1671 --- /dev/null +++ b/apps/dashboard/server/plugins/evlog-enrich.ts @@ -0,0 +1,13 @@ +import { + createUserAgentEnricher, + createRequestSizeEnricher, +} from 'evlog/enrichers' +import { definePlugin } from 'nitro' + +export default definePlugin((nitroApp) => { + const enrichers = [createUserAgentEnricher(), createRequestSizeEnricher()] + + nitroApp.hooks.hook('evlog:enrich', (ctx) => { + for (const enricher of enrichers) enricher(ctx) + }) +}) diff --git a/apps/dashboard/src/functions/get-auth-fn.ts b/apps/dashboard/src/functions/get-auth-fn.ts new file mode 100644 index 0000000..ddf6317 --- /dev/null +++ b/apps/dashboard/src/functions/get-auth-fn.ts @@ -0,0 +1,7 @@ +import { createServerFn } from '@tanstack/react-start' + +import { authMiddleware } from '#/middlewares/auth-middleware.ts' + +export const getAuthFn = createServerFn({ method: 'GET' }) + .middleware([authMiddleware]) + .handler(({ context }) => context.auth) diff --git a/apps/dashboard/src/lib/query-client.ts b/apps/dashboard/src/lib/query-client.ts new file mode 100644 index 0000000..597a116 --- /dev/null +++ b/apps/dashboard/src/lib/query-client.ts @@ -0,0 +1,49 @@ +import { MutationCache, QueryCache, QueryClient } from '@tanstack/react-query' + +function reportQueryError(message: string, metadata: Record) { + if (typeof globalThis.reportError === 'function') { + globalThis.reportError({ message, metadata }) + return + } + + console.debug(message, metadata) +} + +export const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30 * 1000, // 30 seconds + gcTime: 5 * 60 * 1000, // 5 minutes + retry: 3, + }, + }, + + queryCache: new QueryCache({ + onError: (error, query) => { + reportQueryError('[Query Error]', { queryKey: query.queryKey, error }) + }, + }), + + mutationCache: new MutationCache({ + onError: (error, mutation) => { + reportQueryError('[Mutation Error]', { + mutationKey: (mutation as { options: { mutationKey?: unknown } }) + .options.mutationKey, + error, + }) + }, + }), + }) + +let clientQueryClientSingleton: QueryClient | undefined = undefined +export const getQueryClient = () => { + if (typeof window === 'undefined') { + // Server: always make a new query client + return createQueryClient() + } + // Browser: use singleton pattern to keep the same query client + clientQueryClientSingleton ??= createQueryClient() + + return clientQueryClientSingleton +} diff --git a/apps/dashboard/src/middlewares/auth-middleware.ts b/apps/dashboard/src/middlewares/auth-middleware.ts new file mode 100644 index 0000000..c9d5a69 --- /dev/null +++ b/apps/dashboard/src/middlewares/auth-middleware.ts @@ -0,0 +1,16 @@ +import { createMiddleware } from '@tanstack/react-start' + +import { authClient } from '@altstack/auth/client' + +export const authMiddleware = createMiddleware().server( + async ({ next, request }) => { + const session = await authClient.getSession({ + fetchOptions: { + headers: request.headers, + throw: true, + }, + }) + + return next({ context: { auth: session } }) + } +) diff --git a/apps/dashboard/src/routeTree.gen.ts b/apps/dashboard/src/routeTree.gen.ts new file mode 100644 index 0000000..dceedff --- /dev/null +++ b/apps/dashboard/src/routeTree.gen.ts @@ -0,0 +1,68 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/apps/dashboard/src/router.tsx b/apps/dashboard/src/router.tsx new file mode 100644 index 0000000..9d68982 --- /dev/null +++ b/apps/dashboard/src/router.tsx @@ -0,0 +1,28 @@ +import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' + +import { getQueryClient } from '#/lib/query-client' +import { routeTree } from '#/routeTree.gen' +import { orpc } from '#/utils/orpc' + +export function getRouter() { + const queryClient = getQueryClient() + + const router = createTanStackRouter({ + routeTree, + context: { orpc, queryClient }, + scrollRestoration: true, + defaultPreload: 'intent', + defaultPreloadStaleTime: 0, + }) + + setupRouterSsrQueryIntegration({ router, queryClient }) + + return router +} + +declare module '@tanstack/react-router' { + interface Register { + router: ReturnType + } +} diff --git a/apps/dashboard/src/routes/__root.tsx b/apps/dashboard/src/routes/__root.tsx new file mode 100644 index 0000000..d337799 --- /dev/null +++ b/apps/dashboard/src/routes/__root.tsx @@ -0,0 +1,104 @@ +import { TanStackDevtools } from '@tanstack/react-devtools' +import type { QueryClient } from '@tanstack/react-query' +import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools' +import { + HeadContent, + Outlet, + Scripts, + createRootRouteWithContext, +} from '@tanstack/react-router' +import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' +import { createMiddleware } from '@tanstack/react-start' +import { evlogErrorHandler } from 'evlog/nitro/v3' + +import { ThemeProvider } from '@altstack/ui/components/customs/theme-provider' + +import { getAuthFn } from '#/functions/get-auth-fn' +import appCss from '#/styles.css?url' +import type { orpc } from '#/utils/orpc' + +interface RouterAppContext { + orpc: typeof orpc + queryClient: QueryClient +} + +export const Route = createRootRouteWithContext()({ + server: { + middleware: [createMiddleware().server(evlogErrorHandler)], + }, + beforeLoad: async () => { + const auth = await getAuthFn() + return { auth } + }, + component: RootComponent, + head: () => { + return { + meta: [ + { + charSet: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + { + title: 'TanStack Start Starter', + }, + ], + links: [ + { + rel: 'stylesheet', + href: appCss, + }, + ], + } + }, +}) + +function RootDocument({ children }: { children: React.ReactNode }) { + return ( + + + + + + {children} + , + }, + { + name: 'Tanstack Query', + render: , + }, + ]} + /> + + + + ) +} + +function RootComponent() { + return ( + + + + + + ) +} diff --git a/apps/dashboard/src/routes/index.tsx b/apps/dashboard/src/routes/index.tsx new file mode 100644 index 0000000..ec15d95 --- /dev/null +++ b/apps/dashboard/src/routes/index.tsx @@ -0,0 +1,25 @@ +import { useSuspenseQuery } from '@tanstack/react-query' +import { createFileRoute } from '@tanstack/react-router' + +import { orpc } from '#/utils/orpc' + +export const Route = createFileRoute('/')({ + component: Home, + loader: ({ context }) => + context.queryClient.ensureQueryData(orpc.health.queryOptions()), +}) + +function Home() { + const healthQuery = useSuspenseQuery(orpc.health.queryOptions()) + + return ( +
+

Welcome to TanStack Start

+

+ Edit src/routes/index.tsx to get started. +

+ +
healthy: {healthQuery.data.message}
+
+ ) +} diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css new file mode 100644 index 0000000..883b3fe --- /dev/null +++ b/apps/dashboard/src/styles.css @@ -0,0 +1 @@ +@import '@altstack/ui/globals.css'; diff --git a/apps/dashboard/src/utils/orpc.ts b/apps/dashboard/src/utils/orpc.ts new file mode 100644 index 0000000..167d75b --- /dev/null +++ b/apps/dashboard/src/utils/orpc.ts @@ -0,0 +1,23 @@ +import { createORPCClient } from '@orpc/client' +import { RPCLink } from '@orpc/client/fetch' +import { createTanstackQueryUtils } from '@orpc/tanstack-query' + +import type { ORPCRouterClient } from '@altstack/api/routers' + +import { env } from '@altstack/env/web' + +const link = new RPCLink({ + url: `${env.VITE_SERVER_URL}/api/rpc`, + fetch(url, options) { + return fetch(url, { + ...options, + credentials: 'include', + }) + }, +}) + +const getORPCClient = (): ORPCRouterClient => createORPCClient(link) + +const client: ORPCRouterClient = getORPCClient() + +export const orpc = createTanstackQueryUtils(client) diff --git a/apps/dashboard/tsconfig.json b/apps/dashboard/tsconfig.json new file mode 100644 index 0000000..c38d246 --- /dev/null +++ b/apps/dashboard/tsconfig.json @@ -0,0 +1,29 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "target": "ES2022", + "jsx": "react-jsx", + "module": "ESNext", + "paths": { + "#/*": ["./src/*"], + "@/*": ["./src/*"], + "@altstack/ui/*": ["../../packages/ui/src/*"] + }, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + } +} diff --git a/apps/dashboard/tsr.config.json b/apps/dashboard/tsr.config.json new file mode 100644 index 0000000..a1e772c --- /dev/null +++ b/apps/dashboard/tsr.config.json @@ -0,0 +1,3 @@ +{ + "target": "react" +} diff --git a/apps/dashboard/vite.config.ts b/apps/dashboard/vite.config.ts new file mode 100644 index 0000000..729445c --- /dev/null +++ b/apps/dashboard/vite.config.ts @@ -0,0 +1,36 @@ +import babel from '@rolldown/plugin-babel' +import tailwindcss from '@tailwindcss/vite' +import { devtools } from '@tanstack/devtools-vite' +import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import viteReact, { reactCompilerPreset } from '@vitejs/plugin-react' +import { nitro } from 'nitro/vite' +import { resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { lazyPlugins, defineConfig } from 'vite-plus' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const config = defineConfig({ + envDir: resolve(__dirname, '../..'), + resolve: { tsconfigPaths: true }, + server: { port: 3011 }, + plugins: lazyPlugins(() => [ + devtools(), + nitro({ rollupConfig: { external: [/^@sentry\//] } }), + tailwindcss(), + tanstackStart(), + viteReact(), + babel({ presets: [reactCompilerPreset()] }), + ]), + run: { + tasks: { + build: { + command: 'vp build', + input: [{ auto: true }, '!.output/**'], + output: ['.output/**'], + }, + }, + }, +}) + +export default config diff --git a/apps/dashboard/vitest.config.ts b/apps/dashboard/vitest.config.ts new file mode 100644 index 0000000..b8f2372 --- /dev/null +++ b/apps/dashboard/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite-plus/test/config' + +export default defineConfig({ + resolve: { tsconfigPaths: true }, + test: { + environment: 'node', + passWithNoTests: true, + }, +}) diff --git a/apps/server/package.json b/apps/server/package.json index 8509910..1fb3725 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -4,7 +4,9 @@ "private": true, "type": "module", "scripts": { - "dev": "bun run --watch src/index.ts" + "build": "vp pack", + "dev": "bun run --watch src/index.ts", + "start": "bun run dist/index.mjs" }, "dependencies": { "@altstack/api": "workspace:*", diff --git a/apps/web/package.json b/apps/web/package.json index 7c6eae7..f679ad0 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,23 +20,24 @@ "@orpc/server": "catalog:", "@orpc/tanstack-query": "catalog:", "@tabler/icons-react": "catalog:", - "@tanstack/react-devtools": "^0.10.8", + "@tanstack/react-devtools": "catalog:", "@tanstack/react-query": "catalog:", "@tanstack/react-query-devtools": "catalog:", - "@tanstack/react-router": "^1.170.18", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-router-ssr-query": "^1.167.1", - "@tanstack/react-start": "^1.168.32", - "@tanstack/router-plugin": "^1.168.23", + "@tanstack/react-router": "catalog:", + "@tanstack/react-router-devtools": "catalog:", + "@tanstack/react-router-ssr-query": "catalog:", + "@tanstack/react-start": "catalog:", + "@tanstack/router-plugin": "catalog:", + "better-auth": "catalog:", "evlog": "catalog:", - "next-themes": "^0.4.6", "nitro": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "reicon-react": "^1.1.302" + "reicon-react": "catalog:" }, "devDependencies": { "@altstack/config": "workspace:*", + "@rolldown/plugin-babel": "catalog:", "@tailwindcss/typography": "catalog:", "@tailwindcss/vite": "catalog:", "@tanstack/devtools-vite": "catalog:", @@ -47,6 +48,7 @@ "@types/react": "catalog:", "@types/react-dom": "catalog:", "@vitejs/plugin-react": "catalog:", + "babel-plugin-react-compiler": "catalog:", "jsdom": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", diff --git a/apps/web/server/plugins/evlog-drain.ts b/apps/web/server/plugins/evlog-drain.ts new file mode 100644 index 0000000..8519e7c --- /dev/null +++ b/apps/web/server/plugins/evlog-drain.ts @@ -0,0 +1,7 @@ +import { createFsDrain } from 'evlog/fs' +import { definePlugin } from 'nitro' + +export default definePlugin((nitroApp) => { + if (!import.meta.dev) return + nitroApp.hooks.hook('evlog:drain', createFsDrain()) +}) diff --git a/apps/web/server/plugins/evlog-enrich.ts b/apps/web/server/plugins/evlog-enrich.ts new file mode 100644 index 0000000..30b1671 --- /dev/null +++ b/apps/web/server/plugins/evlog-enrich.ts @@ -0,0 +1,13 @@ +import { + createUserAgentEnricher, + createRequestSizeEnricher, +} from 'evlog/enrichers' +import { definePlugin } from 'nitro' + +export default definePlugin((nitroApp) => { + const enrichers = [createUserAgentEnricher(), createRequestSizeEnricher()] + + nitroApp.hooks.hook('evlog:enrich', (ctx) => { + for (const enricher of enrichers) enricher(ctx) + }) +}) diff --git a/apps/web/src/components/theme-switcher.tsx b/apps/web/src/components/theme-switcher.tsx index ce14763..8c1ab60 100644 --- a/apps/web/src/components/theme-switcher.tsx +++ b/apps/web/src/components/theme-switcher.tsx @@ -1,7 +1,7 @@ import { IconMoon, IconSun } from '@tabler/icons-react' -import { useTheme } from 'next-themes' import { Button } from '@altstack/ui/components/button' +import { useTheme } from '@altstack/ui/components/customs/theme-provider' export const ThemeSwitcher = () => { const { resolvedTheme, setTheme } = useTheme() diff --git a/apps/web/src/router.tsx b/apps/web/src/router.tsx index bbf6a63..9d68982 100644 --- a/apps/web/src/router.tsx +++ b/apps/web/src/router.tsx @@ -1,9 +1,9 @@ import { createRouter as createTanStackRouter } from '@tanstack/react-router' import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' -import { getQueryClient } from '#/lib/query-client.ts' +import { getQueryClient } from '#/lib/query-client' import { routeTree } from '#/routeTree.gen' -import { orpc } from '#/utils/orpc.ts' +import { orpc } from '#/utils/orpc' export function getRouter() { const queryClient = getQueryClient() diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index 304318d..2c8146d 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -11,10 +11,11 @@ import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' import { createMiddleware } from '@tanstack/react-start' import { evlogErrorHandler } from 'evlog/nitro/v3' -import { ThemeProvider } from '#/components/theme-provider.tsx' -import { getAuthFn } from '#/functions/get-auth-fn.ts' +import { ThemeProvider } from '@altstack/ui/components/customs/theme-provider' + +import { getAuthFn } from '#/functions/get-auth-fn' import appCss from '#/styles.css?url' -import type { orpc } from '#/utils/orpc.ts' +import type { orpc } from '#/utils/orpc' interface RouterAppContext { orpc: typeof orpc diff --git a/apps/web/src/routes/_main/index.tsx b/apps/web/src/routes/_main/index.tsx index 2b82bb0..70d10f4 100644 --- a/apps/web/src/routes/_main/index.tsx +++ b/apps/web/src/routes/_main/index.tsx @@ -3,7 +3,7 @@ import { createFileRoute } from '@tanstack/react-router' import { Button } from '@altstack/ui/components/button' -import { orpc } from '#/utils/orpc.ts' +import { orpc } from '#/utils/orpc' export const Route = createFileRoute('/_main/')({ component: Home, diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 35d9378..9dfd1be 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,7 +1,8 @@ +import babel from '@rolldown/plugin-babel' import tailwindcss from '@tailwindcss/vite' import { devtools } from '@tanstack/devtools-vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' -import viteReact from '@vitejs/plugin-react' +import viteReact, { reactCompilerPreset } from '@vitejs/plugin-react' import { nitro } from 'nitro/vite' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' @@ -19,6 +20,7 @@ const config = defineConfig({ tailwindcss(), tanstackStart(), viteReact(), + babel({ presets: [reactCompilerPreset()] }), ]), run: { tasks: { diff --git a/bun.lock b/bun.lock index aced62c..55300b7 100644 --- a/bun.lock +++ b/bun.lock @@ -15,6 +15,48 @@ "vite-plus": "catalog:", }, }, + "apps/dashboard": { + "name": "dashboard", + "dependencies": { + "@altstack/api": "workspace:*", + "@altstack/auth": "workspace:*", + "@altstack/env": "workspace:*", + "@altstack/ui": "workspace:*", + "@orpc/client": "catalog:", + "@orpc/server": "catalog:", + "@orpc/tanstack-query": "catalog:", + "@tabler/icons-react": "catalog:", + "@tanstack/react-devtools": "catalog:", + "@tanstack/react-query": "catalog:", + "@tanstack/react-query-devtools": "catalog:", + "@tanstack/react-router": "catalog:", + "@tanstack/react-router-devtools": "catalog:", + "@tanstack/react-router-ssr-query": "catalog:", + "@tanstack/react-start": "catalog:", + "better-auth": "catalog:", + "evlog": "catalog:", + "nitro": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "reicon-react": "catalog:", + }, + "devDependencies": { + "@altstack/config": "workspace:*", + "@rolldown/plugin-babel": "catalog:", + "@tailwindcss/vite": "catalog:", + "@tanstack/devtools-vite": "catalog:", + "@tanstack/router-cli": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@vitejs/plugin-react": "catalog:", + "babel-plugin-react-compiler": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plus": "catalog:", + }, + }, "apps/server": { "name": "server", "version": "0.0.0", @@ -47,23 +89,24 @@ "@orpc/server": "catalog:", "@orpc/tanstack-query": "catalog:", "@tabler/icons-react": "catalog:", - "@tanstack/react-devtools": "^0.10.8", + "@tanstack/react-devtools": "catalog:", "@tanstack/react-query": "catalog:", "@tanstack/react-query-devtools": "catalog:", - "@tanstack/react-router": "^1.170.18", - "@tanstack/react-router-devtools": "^1.167.0", - "@tanstack/react-router-ssr-query": "^1.167.1", - "@tanstack/react-start": "^1.168.32", - "@tanstack/router-plugin": "^1.168.23", + "@tanstack/react-router": "catalog:", + "@tanstack/react-router-devtools": "catalog:", + "@tanstack/react-router-ssr-query": "catalog:", + "@tanstack/react-start": "catalog:", + "@tanstack/router-plugin": "catalog:", + "better-auth": "catalog:", "evlog": "catalog:", - "next-themes": "^0.4.6", "nitro": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "reicon-react": "^1.1.302", + "reicon-react": "catalog:", }, "devDependencies": { "@altstack/config": "workspace:*", + "@rolldown/plugin-babel": "catalog:", "@tailwindcss/typography": "catalog:", "@tailwindcss/vite": "catalog:", "@tanstack/devtools-vite": "catalog:", @@ -74,6 +117,7 @@ "@types/react": "catalog:", "@types/react-dom": "catalog:", "@vitejs/plugin-react": "catalog:", + "babel-plugin-react-compiler": "catalog:", "jsdom": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", @@ -178,6 +222,7 @@ "@tabler/icons-react": "catalog:", "class-variance-authority": "catalog:", "clsx": "catalog:", + "next-themes": "catalog:", "react": "catalog:", "react-dom": "catalog:", "shadcn": "catalog:", @@ -211,21 +256,29 @@ "@orpc/server": "^1.14.8", "@orpc/tanstack-query": "^1.14.8", "@orpc/zod": "^1.14.8", + "@rolldown/plugin-babel": "^0.2.3", "@tabler/icons-react": "^3.45.0", "@tailwindcss/typography": "^0.5.20", "@tailwindcss/vite": "^4.3.3", - "@tanstack/devtools-vite": "^0.8.1", + "@tanstack/devtools-vite": "^0.8.3", + "@tanstack/react-devtools": "^0.10.9", "@tanstack/react-query": "^5.101.4", "@tanstack/react-query-devtools": "^5.101.4", + "@tanstack/react-router": "^1.170.18", + "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router-ssr-query": "^1.167.1", + "@tanstack/react-start": "^1.168.34", "@tanstack/router-cli": "^1.167.21", + "@tanstack/router-plugin": "^1.168.23", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", "@types/bun": "^1.3.14", - "@types/node": "^22.20.1", + "@types/node": "^26.1.2", "@types/pg": "^8.20.0", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.0.5", + "babel-plugin-react-compiler": "^1.0.0", "better-auth": "^1.6.25", "bumpp": "^12.0.0", "class-variance-authority": "^0.7.1", @@ -237,10 +290,12 @@ "evlog": "^2.22.3", "jsdom": "^28.1.0", "lucide-react": "^1.25.0", + "next-themes": "^0.4.6", "nitro": "^3.0.260610-beta", "pg": "^8.22.0", "react": "^19.2.8", "react-dom": "^19.2.8", + "reicon-react": "^1.1.302", "shadcn": "^4.13.1", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.3", @@ -715,6 +770,8 @@ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-WfFv6/qGufotqBSBzBYwgpCkJBk8Nj7697LL9vTz/XWc67e0r3oewu8iMRwQj3AUL45GVD7wVsPjCsAAtW66Wg=="], + "@rolldown/plugin-babel": ["@rolldown/plugin-babel@0.2.3", "", { "dependencies": { "picomatch": "^4.0.4" }, "peerDependencies": { "@babel/core": "^7.29.0 || ^8.0.0-rc.1", "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1", "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1", "rolldown": "^1.0.0-rc.5", "vite": "^8.0.0" }, "optionalPeers": ["@babel/plugin-transform-runtime", "@babel/runtime", "vite"] }, "sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], @@ -775,7 +832,9 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="], - "@tanstack/devtools": ["@tanstack/devtools@0.12.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/keyboard": "^1.3.3", "@solid-primitives/resize-observer": "^2.1.3", "@tanstack/devtools-client": "0.0.8", "@tanstack/devtools-event-bus": "0.4.2", "@tanstack/devtools-ui": "0.6.0", "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" }, "bin": { "intent": "./bin/intent.js" } }, "sha512-JdxTSeVdjJheycgz4c7qbldNKDCEDWlWr1l9dZBhd9sOmRBT5Z70ka9Eb8mb+FUnalcOIB62IDSR/iSxAIUD8Q=="], + "@tanstack/devtools": ["@tanstack/devtools@0.13.0", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/keyboard": "^1.3.3", "@solid-primitives/resize-observer": "^2.1.3", "@tanstack/devtools-client": "0.0.8", "@tanstack/devtools-event-bus": "0.4.2", "@tanstack/devtools-ui": "0.6.0", "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" }, "bin": { "intent": "./bin/intent.js" } }, "sha512-p/nOH9bS/OO/u3402zPjoGu+Mz6Fzi/iRqJuYghuuYRUY32kZt+C0/d+pP/bi6/2JTi1FdT6oEXI2lWlA5tXxw=="], + + "@tanstack/devtools-bundler-core": ["@tanstack/devtools-bundler-core@0.1.1", "", { "dependencies": { "@tanstack/devtools-client": "0.0.8", "@tanstack/devtools-event-bus": "0.4.2", "chalk": "^5.6.2", "launch-editor": "^2.11.1", "magic-string": "^0.30.0", "oxc-parser": "^0.120.0", "picomatch": "^4.0.3" } }, "sha512-2kowecGXNi/FAnwmJKW3WDZ6XuacHDcz4JsMmx43E21G6ZFmoQFuOJCVuv2bFkQZIR1M7+FVLQF5bdS5MQY62Q=="], "@tanstack/devtools-client": ["@tanstack/devtools-client@0.0.8", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.5.0" } }, "sha512-cG3iZkGWCwN330bLBKa8+9r4Of2AXNoz2zUqcsy/4XsD3105ghVBx78cGyvJj9fSclNomPxoqAnDGXXhg1WLvA=="], @@ -785,7 +844,7 @@ "@tanstack/devtools-ui": ["@tanstack/devtools-ui@0.6.0", "", { "dependencies": { "clsx": "^2.1.1", "dayjs": "^1.11.19", "goober": "^2.1.16", "solid-js": "^1.9.9" } }, "sha512-CVaM6rT6Nl5ijo83vJYFa2SjofvpuOl/uOvbYGhBrRgUhhelNHhx8zZX+hnZCHmIr0/lzM65hsocnZ72592Rvg=="], - "@tanstack/devtools-vite": ["@tanstack/devtools-vite@0.8.1", "", { "dependencies": { "@tanstack/devtools-client": "0.0.8", "@tanstack/devtools-event-bus": "0.4.2", "chalk": "^5.6.2", "launch-editor": "^2.11.1", "magic-string": "^0.30.0", "oxc-parser": "^0.120.0", "picomatch": "^4.0.3" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "bin": { "intent": "./bin/intent.js" } }, "sha512-oQxOo0fI0bwhHtw/psFlIR0OS/bsKrirBxwnw2vuhCM4bjt3k4EZZsW/lvZ1+Vpouhts7LSyvngnxvGXbQ1sUQ=="], + "@tanstack/devtools-vite": ["@tanstack/devtools-vite@0.8.3", "", { "dependencies": { "@tanstack/devtools-bundler-core": "0.1.1", "@tanstack/devtools-client": "0.0.8", "@tanstack/devtools-event-bus": "0.4.2", "chalk": "^5.6.2" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "bin": { "intent": "./bin/intent.js" } }, "sha512-MqqE4/rdQUG55Y8Zux1Jj1I2wIBHdqYgjAJzP1grMUtFqSZ2XIDB7BHEV9UW/vrbhK7ocl4yFgVaJWROv0zeDA=="], "@tanstack/eslint-plugin-query": ["@tanstack/eslint-plugin-query@5.101.3", "", { "dependencies": { "@typescript-eslint/utils": "^8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": "^5.4.0 || ^6.0.0" }, "optionalPeers": ["typescript"] }, "sha512-UNWAtm5hNCLeluX3tjFtVdhMkpN3zFJTCsvb05SGTR9knN052RUFZHOjeBPosRjMvirwSZc++6zFQuzv56yguQ=="], @@ -797,7 +856,7 @@ "@tanstack/query-devtools": ["@tanstack/query-devtools@5.101.4", "", {}, "sha512-z5IPHnDX3aUWeTWlRKLyooBQekaCAw4xRpZqPQ390RiWTDBcTynjpPT221BArw0u2+pnQMdGvPQI9YNNubBcmA=="], - "@tanstack/react-devtools": ["@tanstack/react-devtools@0.10.8", "", { "dependencies": { "@tanstack/devtools": "0.12.5" }, "peerDependencies": { "@types/react": ">=16.8", "@types/react-dom": ">=16.8", "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-YJV6YttQf9lhhPbPBLULgy1eScEvJUMsCS26mjg9hfBKgAJQA5sF9zvzorDzW9Ob6o/asoXikO81JnRUVuFX0Q=="], + "@tanstack/react-devtools": ["@tanstack/react-devtools@0.10.9", "", { "dependencies": { "@tanstack/devtools": "0.13.0" }, "peerDependencies": { "@types/react": ">=16.8", "@types/react-dom": ">=16.8", "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-lS6mtccEmUaodsWiRORGM/MGKT0jgzcy5v+eY6pzOPxEgzTHUDhca+WGxShFqKxmF4oneRxXjww1gkvMrWq6uw=="], "@tanstack/react-query": ["@tanstack/react-query@5.101.4", "", { "dependencies": { "@tanstack/query-core": "5.101.4" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA=="], @@ -809,11 +868,11 @@ "@tanstack/react-router-ssr-query": ["@tanstack/react-router-ssr-query@1.167.1", "", { "dependencies": { "@tanstack/router-ssr-query-core": "1.169.1" }, "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/react-query": ">=5.90.0", "@tanstack/react-router": ">=1.127.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-W9j5JPnBikyafvuUfykFfHIWod58OAbAAa5leNkXBcoDoocghMmu6w9uZOmUZvAWT7CSvgj5tBUtF7CM2OoHXQ=="], - "@tanstack/react-start": ["@tanstack/react-start@1.168.32", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/react-start-client": "1.168.16", "@tanstack/react-start-rsc": "0.1.31", "@tanstack/react-start-server": "1.167.22", "@tanstack/router-utils": "1.162.2", "@tanstack/start-client-core": "1.170.14", "@tanstack/start-plugin-core": "1.171.24", "@tanstack/start-server-core": "1.169.17", "pathe": "^2.0.3" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "@vitejs/plugin-rsc": "*", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core", "@vitejs/plugin-rsc", "vite"] }, "sha512-y1WXHo+jPfHxiuuN1m+br06IcriiBQnEWryBdbKdEOS5vw2PmnOj+Cgf1/YcGOqtSougScWFeE2rL1FXWvsLLg=="], + "@tanstack/react-start": ["@tanstack/react-start@1.168.34", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/react-start-client": "1.168.16", "@tanstack/react-start-rsc": "0.1.33", "@tanstack/react-start-server": "1.167.22", "@tanstack/router-utils": "1.162.2", "@tanstack/start-client-core": "1.170.14", "@tanstack/start-plugin-core": "1.171.25", "@tanstack/start-server-core": "1.169.17", "pathe": "^2.0.3" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "@vitejs/plugin-rsc": "*", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core", "@vitejs/plugin-rsc", "vite"] }, "sha512-W5MDbD4QlDZHtEXlqN6bJUz7SdsPv6tbNPCih1i72FZqgQlhaxN1BoeoB8H+nN8M4tXRkfJD7VW75FCdQyaQDw=="], "@tanstack/react-start-client": ["@tanstack/react-start-client@1.168.16", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/router-core": "1.171.15", "@tanstack/start-client-core": "1.170.14" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-1OfHgy0wpHwe2tlB3FxMeA+IMX6Il/QAMf+8UdXuimReIc2Lz3BkMLBL38k4GIxBguX9sI8EMLO5jlTZ4e1olw=="], - "@tanstack/react-start-rsc": ["@tanstack/react-start-rsc@0.1.31", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/router-core": "1.171.15", "@tanstack/router-utils": "1.162.2", "@tanstack/start-client-core": "1.170.14", "@tanstack/start-fn-stubs": "1.162.0", "@tanstack/start-plugin-core": "1.171.24", "@tanstack/start-server-core": "1.169.17", "@tanstack/start-storage-context": "1.167.17", "pathe": "^2.0.3" }, "peerDependencies": { "@rspack/core": ">=2.0.0-0", "@vitejs/plugin-rsc": ">=0.5.20", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "react-server-dom-rspack": ">=0.0.2" }, "optionalPeers": ["@rspack/core", "@vitejs/plugin-rsc", "react-server-dom-rspack"] }, "sha512-WxjkXYflq550vTNJpdPyMaPC+Vyh88L5wOL+SiDTjPMGne9ad7FZmoJxqfCFEv1e7HVKMH/mMoE8619TsTNVzQ=="], + "@tanstack/react-start-rsc": ["@tanstack/react-start-rsc@0.1.33", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/router-core": "1.171.15", "@tanstack/router-utils": "1.162.2", "@tanstack/start-client-core": "1.170.14", "@tanstack/start-fn-stubs": "1.162.0", "@tanstack/start-plugin-core": "1.171.25", "@tanstack/start-server-core": "1.169.17", "@tanstack/start-storage-context": "1.167.17", "pathe": "^2.0.3" }, "peerDependencies": { "@rspack/core": ">=2.0.0-0", "@vitejs/plugin-rsc": ">=0.5.30", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "react-server-dom-rspack": ">=0.0.2" }, "optionalPeers": ["@rspack/core", "@vitejs/plugin-rsc", "react-server-dom-rspack"] }, "sha512-G4e1xwi/InoQmIGgNQSozcWASw68/o3NpbTz+exosdMGRZzLBeUDbj0swEAajxHm6jDEOQ/reSeIcDkcEfhMfw=="], "@tanstack/react-start-server": ["@tanstack/react-start-server@1.167.22", "", { "dependencies": { "@tanstack/react-router": "1.170.18", "@tanstack/router-core": "1.171.15", "@tanstack/start-server-core": "1.169.17" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-eH2PeHuLfL3R5YzE9+y2FfcE4Ld1LNV2ZfrCNVPJMMJFt+9nXDaRHg9BsEmc+JkTAGzz3FKLyQEoWwpbG6Ehqg=="], @@ -837,7 +896,7 @@ "@tanstack/start-fn-stubs": ["@tanstack/start-fn-stubs@1.162.0", "", {}, "sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ=="], - "@tanstack/start-plugin-core": ["@tanstack/start-plugin-core@1.171.24", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.15", "@tanstack/router-generator": "1.167.21", "@tanstack/router-plugin": "1.168.23", "@tanstack/router-utils": "1.162.2", "@tanstack/start-server-core": "1.169.17", "exsolve": "^1.0.7", "lightningcss": "^1.32.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "seroval": "^1.5.4", "source-map": "^0.7.6", "srvx": "^0.11.9", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^4.4.3" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core", "vite"] }, "sha512-l/tm+T0ntXHeIzr9kJDTJ2IDNZC0yFazjkvbEVeZsDOrJ8F+HiZmY+tXYqI5/nDYkwxY0DVQr+kGsTRVb6y2Jw=="], + "@tanstack/start-plugin-core": ["@tanstack/start-plugin-core@1.171.25", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.171.15", "@tanstack/router-generator": "1.167.21", "@tanstack/router-plugin": "1.168.23", "@tanstack/router-utils": "1.162.2", "@tanstack/start-server-core": "1.169.17", "exsolve": "^1.0.7", "lightningcss": "^1.32.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "seroval": "^1.5.4", "source-map": "^0.7.6", "srvx": "^0.11.9", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^4.4.3" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core", "vite"] }, "sha512-YmMye36vohfxau/MaVpltjkpJlf+wfUBoZp3S6Ue53mpAnsHr6El3XQDmcp1wS4kicZmyX1SNcobJpVZc+2dOQ=="], "@tanstack/start-server-core": ["@tanstack/start-server-core@1.169.17", "", { "dependencies": { "@tanstack/history": "1.162.0", "@tanstack/router-core": "1.171.15", "@tanstack/start-client-core": "1.170.14", "@tanstack/start-storage-context": "1.167.17", "fetchdts": "^0.1.6", "h3-v2": "npm:h3@2.0.1-rc.20", "seroval": "^1.5.4" } }, "sha512-u0N+PHJhMHnzfnlXYI9F+A/qweDe3E2X0mfkORPGIEkNQgvS548RA9fjwvixR2en5b848CfpEqUzwFhm/tQ40Q=="], @@ -873,13 +932,13 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@22.20.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q=="], + "@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], "@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="], - "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/react-dom": ["@types/react-dom@19.2.4", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw=="], "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], @@ -937,7 +996,7 @@ "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.5", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA=="], "@vitest/browser": ["@vitest/browser@4.1.10", "", { "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", "tinyrainbow": "^3.1.0", "ws": "^8.19.0" }, "peerDependencies": { "vitest": "4.1.10" } }, "sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng=="], @@ -1055,6 +1114,8 @@ "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="], + "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.44", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-T3ghW+sl/ZJ8w1v/yQx3qvJ9040DWoLBz8JT/CILbAKcFyG9b2MRe75v6W5uXjv6uH1lumK2Kv46y2zSkcej0Q=="], @@ -1149,6 +1210,8 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "dashboard": ["dashboard@workspace:apps/dashboard"], + "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="], "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="], @@ -1873,7 +1936,7 @@ "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], @@ -2127,14 +2190,10 @@ "@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="], - "@types/pg/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="], "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "bun-types/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "conf/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], diff --git a/package.json b/package.json index 3fee9ea..56312c6 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "db:stop": "docker compose stop postgres", "db:studio": "vp run --filter @altstack/db db:studio", "db:watch": "docker compose up postgres", + "dev:dashboard": "vp run @altstack/dashboard#dev", + "dev:server": "vp run @altstack/server#dev", "dev:web": "vp run @altstack/web#dev", "prepare": "vp config", "ready": "vp check && vp run -r test && vp run -r build" @@ -54,21 +56,29 @@ "@orpc/server": "^1.14.8", "@orpc/tanstack-query": "^1.14.8", "@orpc/zod": "^1.14.8", + "@rolldown/plugin-babel": "^0.2.3", "@tabler/icons-react": "^3.45.0", "@tailwindcss/typography": "^0.5.20", "@tailwindcss/vite": "^4.3.3", + "@tanstack/devtools-vite": "^0.8.3", + "@tanstack/react-devtools": "^0.10.9", "@tanstack/react-query": "^5.101.4", "@tanstack/react-query-devtools": "^5.101.4", - "@tanstack/devtools-vite": "^0.8.1", + "@tanstack/react-router": "^1.170.18", + "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router-ssr-query": "^1.167.1", + "@tanstack/react-start": "^1.168.34", "@tanstack/router-cli": "^1.167.21", + "@tanstack/router-plugin": "^1.168.23", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", "@types/bun": "^1.3.14", - "@types/node": "^22.20.1", + "@types/node": "^26.1.2", "@types/pg": "^8.20.0", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.0.5", + "babel-plugin-react-compiler": "^1.0.0", "better-auth": "^1.6.25", "bumpp": "^12.0.0", "class-variance-authority": "^0.7.1", @@ -80,10 +90,12 @@ "evlog": "^2.22.3", "jsdom": "^28.1.0", "lucide-react": "^1.25.0", + "next-themes": "^0.4.6", "nitro": "^3.0.260610-beta", "pg": "^8.22.0", "react": "^19.2.8", "react-dom": "^19.2.8", + "reicon-react": "^1.1.302", "shadcn": "^4.13.1", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.3", diff --git a/packages/ui/package.json b/packages/ui/package.json index ed66281..dd520c5 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -23,6 +23,7 @@ "@tabler/icons-react": "catalog:", "class-variance-authority": "catalog:", "clsx": "catalog:", + "next-themes": "catalog:", "react": "catalog:", "react-dom": "catalog:", "shadcn": "catalog:", diff --git a/apps/web/src/components/theme-provider.tsx b/packages/ui/src/components/customs/theme-provider.tsx similarity index 92% rename from apps/web/src/components/theme-provider.tsx rename to packages/ui/src/components/customs/theme-provider.tsx index 7a0d639..81c0296 100644 --- a/apps/web/src/components/theme-provider.tsx +++ b/packages/ui/src/components/customs/theme-provider.tsx @@ -1,5 +1,6 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes' import type { ComponentProps, FC } from 'react' +import React from 'react' export const ThemeProvider: FC> = ({ children, From e4092a40b48dee8559b8347ccae1f0362c3b0799 Mon Sep 17 00:00:00 2001 From: mrboxs Date: Sun, 2 Aug 2026 13:32:21 +0700 Subject: [PATCH 5/5] chore: update project configuration, environment settings, and documentation for Altstack --- .env.example | 2 +- apps/dashboard/.gitignore | 3 +++ apps/dashboard/public/manifest.json | 4 ++-- apps/dashboard/public/robots.txt | 2 +- apps/server/README.md | 16 +++++++++------- package.json | 6 +++--- packages/env/src/server.ts | 2 +- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index 94c0d03..d5c933e 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ DATABASE_URL=postgresql://postgres:password@localhost:5432/db_name APP_NAME=Altstack BETTER_AUTH_URL=http://localhost:3010 BETTER_AUTH_SECRET=your-better-auth-secret -CORS_ORIGINS=http://localhost:3010,http://:3010,https://dokploy.example.com +CORS_ORIGINS=http://localhost:3010,http://localhost:3011,http://:3010,https://dokploy.example.com VITE_APP_NAME=Altstack VITE_APP_URL=http://localhost:3010 diff --git a/apps/dashboard/.gitignore b/apps/dashboard/.gitignore index 8b25bb5..cf7baa9 100644 --- a/apps/dashboard/.gitignore +++ b/apps/dashboard/.gitignore @@ -11,3 +11,6 @@ dist-ssr .vinxi __unconfig* todos.json +*.local +.env* +!.env.example \ No newline at end of file diff --git a/apps/dashboard/public/manifest.json b/apps/dashboard/public/manifest.json index 4d9581b..2919f21 100644 --- a/apps/dashboard/public/manifest.json +++ b/apps/dashboard/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "TanStack App", - "name": "Create TanStack App Sample", + "short_name": "Altstack", + "name": "Altstack", "icons": [ { "src": "favicon.ico", diff --git a/apps/dashboard/public/robots.txt b/apps/dashboard/public/robots.txt index e9e57dc..2d1aa03 100644 --- a/apps/dashboard/public/robots.txt +++ b/apps/dashboard/public/robots.txt @@ -1,3 +1,3 @@ # https://www.robotstxt.org/robotstxt.html User-agent: * -Disallow: +Disallow: / \ No newline at end of file diff --git a/apps/server/README.md b/apps/server/README.md index bebf061..df8599d 100644 --- a/apps/server/README.md +++ b/apps/server/README.md @@ -2,18 +2,20 @@ ## Getting Started -To get started with this template, simply paste this command into your terminal: +Install the workspace dependencies: ```bash -bun create elysia ./elysia-example +vp install ``` -## Development - -To start the development server run: +Start the server in development mode from the repository root: ```bash -bun run dev +npm run dev:server ``` -Open http://localhost:3000/ with your browser to see the result. +## Development + +The server runs with watch mode and restarts on file changes. + +Open http://localhost:3009/ with your browser to see the result. diff --git a/package.json b/package.json index 56312c6..ebe23c7 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,9 @@ "db:stop": "docker compose stop postgres", "db:studio": "vp run --filter @altstack/db db:studio", "db:watch": "docker compose up postgres", - "dev:dashboard": "vp run @altstack/dashboard#dev", - "dev:server": "vp run @altstack/server#dev", - "dev:web": "vp run @altstack/web#dev", + "dev:dashboard": "vp run dashboard#dev", + "dev:server": "vp run server#dev", + "dev:web": "vp run web#dev", "prepare": "vp config", "ready": "vp check && vp run -r test && vp run -r build" }, diff --git a/packages/env/src/server.ts b/packages/env/src/server.ts index 7d654c7..4325a54 100644 --- a/packages/env/src/server.ts +++ b/packages/env/src/server.ts @@ -10,7 +10,7 @@ dotenv.config({ path: resolve(__dirname, '../../../.env') }) export const env = createEnv({ server: { NODE_ENV: z.enum(['development', 'production', 'test']), - PORT: z.coerce.number(), + PORT: z.coerce.number().int().min(1).max(65535), DATABASE_URL: z.url(), APP_NAME: z.string(), BETTER_AUTH_URL: z.url(),