From 71ecd8c48c580764a805bd3d1369643034e354ea Mon Sep 17 00:00:00 2001 From: Subramanya Chakravarthy Date: Fri, 8 Aug 2025 20:14:20 +0530 Subject: [PATCH 1/3] feat(docs): add AI Coding Agent instructions for universal starter server --- .github/copilot-instructions.md | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..338dc70 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,47 @@ +# AI Coding Agent innstructions – Universal Starter (Server) + +## 📌 Tech Stack +- **Framework**: [Elysia](https://elysiajs.com/) +- **Database ORM**: [Prisma](https://www.prisma.io/) +- **RPC Layer**: [oRPC](https://orpc.dev/) – lightweight, type-safe RPC between server and client +- **Authentication**: [better-auth](https://better-auth.vercel.app/) + +--- + +## 1. General Rules +- Always **use TypeScript** with strict typing. +- Keep imports **absolute** from `src/` using path aliases. +- Always return **typed responses** from RPC handlers and API routes. + +--- + +## 2. Elysia Guidelines +- Group related routes in a single Elysia plugin file inside `src/routes/`. +- When adding middleware: + - Use `app.use()` for global middleware. + - Use plugin-scoped middleware for route-specific logic. + +## 3. Prisma Guidelines +- Never write raw SQL unless necessary; use Prisma Client for all DB operations. +- Use select or include to fetch only required fields — avoid findMany() without filters unless needed. + +Example: + +```typescript +import { prisma } from '@/src/lib/db'; + +export const userService = { + create: (data: { name: string; email: string }) => + prisma.user.create({ data }), + + findByEmail: (email: string) => + prisma.user.findUnique({ + where: { email }, + select: { id: true, name: true, email: true } + }), +}; +``` + +## 4. oRPC Guidelines +- Use typed inputs/outputs for every RPC method. +- Group related RPCs into a single file and export from index.ts \ No newline at end of file From 40a6d74e0400e4b5b3a064d80a6c13293da24d00 Mon Sep 17 00:00:00 2001 From: Subramanya Chakravarthy Date: Mon, 11 Aug 2025 15:57:58 +0530 Subject: [PATCH 2/3] feat: integrate ORPC with Better Auth and Prisma - Added ORPC dependencies to package.json. - Implemented ORPC handler in src/orpc/index.ts with CORS and OpenAPI plugins. - Created authentication middleware for ORPC to manage user sessions and roles. - Developed database middleware to provide Prisma instance to ORPC context. - Defined user-related procedures, including a 'me' endpoint to fetch user details. - Established appRouter to organize ORPC procedures. - Updated tsconfig.json for improved module resolution and TypeScript features. --- bun.lock | 43 ++++- package.json | 3 + src/index.ts | 18 ++ src/orpc/index.ts | 31 ++++ src/orpc/middleware/auth.middleware.ts | 62 +++++++ src/orpc/middleware/db.middleware.ts | 18 ++ src/orpc/middleware/index.ts | 8 + src/orpc/procedures/user/me.p.ts | 27 +++ src/orpc/routers.ts | 7 + tsconfig.json | 217 ++++++++++++++----------- 10 files changed, 335 insertions(+), 99 deletions(-) create mode 100644 src/orpc/index.ts create mode 100644 src/orpc/middleware/auth.middleware.ts create mode 100644 src/orpc/middleware/db.middleware.ts create mode 100644 src/orpc/middleware/index.ts create mode 100644 src/orpc/procedures/user/me.p.ts create mode 100644 src/orpc/routers.ts diff --git a/bun.lock b/bun.lock index 41dab79..ab92559 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,9 @@ "@better-auth/expo": "^1.3.4", "@elysiajs/cors": "^1.3.3", "@elysiajs/swagger": "^1.3.1", + "@orpc/openapi": "^1.7.10", + "@orpc/server": "^1.7.10", + "@orpc/zod": "^1.7.10", "@plunk/node": "^3.0.3", "@prisma/client": "^6.11.1", "@prisma/extension-accelerate": "^2.0.1", @@ -63,6 +66,32 @@ "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@orpc/client": ["@orpc/client@1.7.10", "", { "dependencies": { "@orpc/shared": "1.7.10", "@orpc/standard-server": "1.7.10", "@orpc/standard-server-fetch": "1.7.10", "@orpc/standard-server-peer": "1.7.10" } }, "sha512-49F0pmmihZhsZqtrKw416H6bTWHHKQYG684JY9wG/igzUKqpjETY6zcWybzphm3Oy1Kpk/BgRLxUsjc+40jmCA=="], + + "@orpc/contract": ["@orpc/contract@1.7.10", "", { "dependencies": { "@orpc/client": "1.7.10", "@orpc/shared": "1.7.10", "@standard-schema/spec": "^1.0.0", "openapi-types": "^12.1.3" } }, "sha512-PZV6ieUsFEgbBRlA8ffpDRyRrfPyzNMhqAbSWZqC8a3Yea5qo2CTVfyMX37EeZQIRiGIlpLesw1zGOuG5BXsdA=="], + + "@orpc/json-schema": ["@orpc/json-schema@1.7.10", "", { "dependencies": { "@orpc/contract": "1.7.10", "@orpc/openapi": "1.7.10", "@orpc/server": "1.7.10", "@orpc/shared": "1.7.10", "json-schema-typed": "^8.0.1" } }, "sha512-BcKWfSOyNCgabRp7duXl7WRMVCBLV2IpsMfVx7JRIn/MA+PYxQQwRzIulG02A0DFEnjy9QYciuURxgRJnSUkDg=="], + + "@orpc/openapi": ["@orpc/openapi@1.7.10", "", { "dependencies": { "@orpc/client": "1.7.10", "@orpc/contract": "1.7.10", "@orpc/openapi-client": "1.7.10", "@orpc/server": "1.7.10", "@orpc/shared": "1.7.10", "@orpc/standard-server": "1.7.10", "json-schema-typed": "^8.0.1", "rou3": "^0.7.3" } }, "sha512-rf2kk5epcUe+YiejTv/MsaSb9OuU0IXSQtN6xQd0nbTy+8hJtDN5KxjXGMtUsqFph0T3KeiwFTaTzPVrHPSfCg=="], + + "@orpc/openapi-client": ["@orpc/openapi-client@1.7.10", "", { "dependencies": { "@orpc/client": "1.7.10", "@orpc/contract": "1.7.10", "@orpc/shared": "1.7.10", "@orpc/standard-server": "1.7.10" } }, "sha512-vv9C9GQ77QWg/LUzHKu0XO9n6AffpklrwJxzG2CpeV6aEAEOURbR/xG+OhsUAd6wQ2sGv8hekTLjDyoOAJ3lKQ=="], + + "@orpc/server": ["@orpc/server@1.7.10", "", { "dependencies": { "@orpc/client": "1.7.10", "@orpc/contract": "1.7.10", "@orpc/shared": "1.7.10", "@orpc/standard-server": "1.7.10", "@orpc/standard-server-aws-lambda": "1.7.10", "@orpc/standard-server-fetch": "1.7.10", "@orpc/standard-server-node": "1.7.10", "@orpc/standard-server-peer": "1.7.10", "cookie": "^1.0.2" }, "peerDependencies": { "crossws": ">=0.3.4", "ws": ">=8.18.1" }, "optionalPeers": ["crossws", "ws"] }, "sha512-RWXWfIH8DE/BcI6ROk+2IUngcmvM03+gOywdAhH1le9x5wHugfMC3St5TPisVCGmO6n62oRsSmK2qEIIGktaog=="], + + "@orpc/shared": ["@orpc/shared@1.7.10", "", { "dependencies": { "radash": "^12.1.1", "type-fest": "^4.39.1" } }, "sha512-xjB7vQETQGU+rnVyYgnUyaH4Yf8FgWiUMQr7StoWVkbyBdIGTJFxRe9GUdrlxlSVg4DhI78rFM0eRZlW9Hs3zQ=="], + + "@orpc/standard-server": ["@orpc/standard-server@1.7.10", "", { "dependencies": { "@orpc/shared": "1.7.10" } }, "sha512-imcIfPsCsNwZ2ma0hTs8WQcQq1g4C25U8P2SmGXghXWoQZopeLQDExa50yJeRFZ0jrui3cPbJXGQCPd5xGiGHA=="], + + "@orpc/standard-server-aws-lambda": ["@orpc/standard-server-aws-lambda@1.7.10", "", { "dependencies": { "@orpc/shared": "1.7.10", "@orpc/standard-server": "1.7.10", "@orpc/standard-server-fetch": "1.7.10", "@orpc/standard-server-node": "1.7.10" } }, "sha512-Kaf6YNFXw8G50667J4adRHM2b+c78C46wxUyTjDLEc3A/ysuTxFuJHJDlx9L8/GnyxxDz/9IKx1eHG5J3y3DEw=="], + + "@orpc/standard-server-fetch": ["@orpc/standard-server-fetch@1.7.10", "", { "dependencies": { "@orpc/shared": "1.7.10", "@orpc/standard-server": "1.7.10" } }, "sha512-/PgSNhyet1zGy3DPEoUIoidj/XnlRrCstF7j895YyBkLc9+Q07jGynKL+HPDJTeTnXxz+I90ZSou5Wy1xpoqcw=="], + + "@orpc/standard-server-node": ["@orpc/standard-server-node@1.7.10", "", { "dependencies": { "@orpc/shared": "1.7.10", "@orpc/standard-server": "1.7.10", "@orpc/standard-server-fetch": "1.7.10" } }, "sha512-Sl6VMOwRxHMaW5g+WXuVekFbH1qjUWOkB9DHNLk9peB6ylRCZdiTM9gl+B1XKMdyfkHd3LVROSwnaXhe4PHHvw=="], + + "@orpc/standard-server-peer": ["@orpc/standard-server-peer@1.7.10", "", { "dependencies": { "@orpc/shared": "1.7.10", "@orpc/standard-server": "1.7.10" } }, "sha512-H3S6IjCrKU+U0xffcXRmR3jqt74v8/aAR+W2tT3IfZ+dKQp7E6HQvYgp5pZcMSs9xxnvkfOtStcOf45U+ZPe1Q=="], + + "@orpc/zod": ["@orpc/zod@1.7.10", "", { "dependencies": { "@orpc/json-schema": "1.7.10", "@orpc/openapi": "1.7.10", "@orpc/shared": "1.7.10", "escape-string-regexp": "^5.0.0", "wildcard-match": "^5.1.3" }, "peerDependencies": { "@orpc/contract": "1.7.10", "@orpc/server": "1.7.10", "zod": ">=3.25.0" } }, "sha512-HBgJ30e8ASTMSNwptyvTFg6c5CYPoLg/C5y6jPuWnSpKWxhcP0wcVnKCnVXlTSk1OtTtUJcWzlpEBNtXYx8Vvw=="], + "@peculiar/asn1-android": ["@peculiar/asn1-android@2.3.16", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.15", "asn1js": "^3.0.5", "tslib": "^2.8.1" } }, "sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw=="], "@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.3.15", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.15", "@peculiar/asn1-x509": "^2.3.15", "asn1js": "^3.0.5", "tslib": "^2.8.1" } }, "sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA=="], @@ -107,6 +136,8 @@ "@sinclair/typebox": ["@sinclair/typebox@0.34.37", "", {}, "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], @@ -149,6 +180,8 @@ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "exact-mirror": ["exact-mirror@0.1.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw=="], "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], @@ -171,6 +204,8 @@ "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], + "json-schema-typed": ["json-schema-typed@8.0.1", "", {}, "sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg=="], + "kysely": ["kysely@0.28.2", "", {}, "sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A=="], "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], @@ -199,6 +234,8 @@ "pvutils": ["pvutils@1.1.3", "", {}, "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ=="], + "radash": ["radash@12.1.1", "", {}, "sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA=="], + "react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="], "react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="], @@ -207,7 +244,7 @@ "resend": ["resend@4.8.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-R8eBOFQDO6dzRTDmaMEdpqrkmgSjPpVXt4nGfWsZdYOet0kqra0xgbvTES6HmCriZEXbmGk3e0DiGIaLFTFSHA=="], - "rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="], + "rou3": ["rou3@0.7.3", "", {}, "sha512-KKenF/hB2iIhS1ohj226LT+/8uKCBpSMqeS4V1UPN9vad99uLoyIhrULRRB1skaB40LQHcBlSsAi3sT8MaoDDQ=="], "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], @@ -231,6 +268,8 @@ "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], + "wildcard-match": ["wildcard-match@5.1.4", "", {}, "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g=="], + "zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="], "zod": ["zod@4.0.14", "", {}, "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw=="], @@ -239,6 +278,8 @@ "@tokenizer/inflate/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "better-call/rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="], + "@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], "@scalar/themes/@scalar/types/zod": ["zod@3.25.71", "", {}, "sha512-BsBc/NPk7h8WsUWYWYL+BajcJPY8YhjelaWu2NMLuzgraKAz4Lb4/6K11g9jpuDetjMiqhZ6YaexFLOC0Ogi3Q=="], diff --git a/package.json b/package.json index 520b871..faf6e69 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "@better-auth/expo": "^1.3.4", "@elysiajs/cors": "^1.3.3", "@elysiajs/swagger": "^1.3.1", + "@orpc/openapi": "^1.7.10", + "@orpc/server": "^1.7.10", + "@orpc/zod": "^1.7.10", "@plunk/node": "^3.0.3", "@prisma/client": "^6.11.1", "@prisma/extension-accelerate": "^2.0.1", diff --git a/src/index.ts b/src/index.ts index 5016f6a..b4e8588 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { Elysia } from "elysia"; import { auth } from "@/src/lib/auth"; import { isOriginAllowed } from "./cors"; import { OpenAPI } from "./lib/open-api"; +import { orpcHandler } from "./orpc"; const app = new Elysia() .use( @@ -55,6 +56,23 @@ const app = new Elysia() }, }, ) + // Orpc + .all( + "/orpc/*", + async ({ request }: { request: Request }) => { + const { response } = await orpcHandler.handle(request, { + prefix: "/orpc", + context: { + headers: request.headers, + }, + }); + + return response ?? new Response("Not Found", { status: 404 }); + }, + { + parse: "none", + }, + ) .listen(3000); console.log( diff --git a/src/orpc/index.ts b/src/orpc/index.ts new file mode 100644 index 0000000..26ee4a5 --- /dev/null +++ b/src/orpc/index.ts @@ -0,0 +1,31 @@ +import { OpenAPIHandler } from "@orpc/openapi/fetch"; +import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins"; +import { CORSPlugin } from "@orpc/server/plugins"; +import { ZodToJsonSchemaConverter } from "@orpc/zod"; +import { appRouter } from "./routers"; + +export const orpcHandler = new OpenAPIHandler(appRouter, { + plugins: [ + new CORSPlugin({ + origin: (origin, options) => origin, + allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"], + }), + new OpenAPIReferencePlugin({ + schemaConverters: [new ZodToJsonSchemaConverter()], + specGenerateOptions: { + info: { + title: "ORPC Playground", + version: "1.0.0", + }, + components: { + securitySchemes: { + bearerAuth: { + type: "http", + scheme: "bearer", + }, + }, + }, + }, + }), + ], +}); diff --git a/src/orpc/middleware/auth.middleware.ts b/src/orpc/middleware/auth.middleware.ts new file mode 100644 index 0000000..36e3809 --- /dev/null +++ b/src/orpc/middleware/auth.middleware.ts @@ -0,0 +1,62 @@ +import { ORPCError, os } from "@orpc/server"; +import { auth } from "@/src/lib/auth"; + +type BAUser = typeof auth.$Infer.Session.user | null; +type BASession = typeof auth.$Infer.Session.session | null; + +export type InputCtx = { + // initial context must provide request headers + headers: Headers | Record; +}; + +export type AuthCtx = InputCtx & { + // injected by middleware + user: BAUser; + session: BASession; +}; + +export const withBetterAuth = os + .$context() + .middleware(async ({ context, next }) => { + const headers = + context.headers instanceof Headers + ? context.headers + : new Headers(context.headers); + + console.log("withBetterAuth context headers", headers); + + // Server-side Better Auth API (no client in middlewares) + const sessionData = await auth.api.getSession({ headers }); // server API call + console.log("withBetterAuth sessionData", sessionData); + + return next({ + context: { + // preserve existing context and inject auth data + ...context, + session: sessionData?.session || null, + user: sessionData?.user || null, + }, + }); + }); + +export const requireAuth = os + .$context() + .middleware(async ({ context, next }) => { + console.log({ context }); + if (!context.session || !context.user) { + throw new ORPCError("UNAUTHORIZED", { message: "Unauthenticated" }); + } + return next({ context }); + }); + +export const requireRole = (roles: string[]) => + os.$context().middleware(async ({ context, next }) => { + const rolesValue = (context.user as unknown as { roles?: unknown })?.roles; + const userRoles: string[] = Array.isArray(rolesValue) + ? (rolesValue as string[]) + : []; + if (!roles.some((r) => userRoles.includes(r))) { + throw new ORPCError("FORBIDDEN", { message: "Forbidden" }); + } + return next({ context }); + }); diff --git a/src/orpc/middleware/db.middleware.ts b/src/orpc/middleware/db.middleware.ts new file mode 100644 index 0000000..e99c014 --- /dev/null +++ b/src/orpc/middleware/db.middleware.ts @@ -0,0 +1,18 @@ +import { os } from "@orpc/server"; +import { prisma } from "@/src/lib/db"; + +export const dbProviderMiddleware = os + .$context<{ + db?: typeof prisma; + }>() + .middleware(async ({ context, next }) => { + const db = context.db ?? prisma; + + return next({ + context: { + // preserve existing context while injecting db + ...context, + db, + }, + }); + }); diff --git a/src/orpc/middleware/index.ts b/src/orpc/middleware/index.ts new file mode 100644 index 0000000..6fd012d --- /dev/null +++ b/src/orpc/middleware/index.ts @@ -0,0 +1,8 @@ +import { os } from "@orpc/server"; +import { withBetterAuth } from "./auth.middleware"; +import { dbProviderMiddleware } from "./db.middleware"; + +export const baseMiddleware = os + .$context<{ headers: Headers | Record }>() + .use(dbProviderMiddleware) + .use(withBetterAuth); diff --git a/src/orpc/procedures/user/me.p.ts b/src/orpc/procedures/user/me.p.ts new file mode 100644 index 0000000..598de8b --- /dev/null +++ b/src/orpc/procedures/user/me.p.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; +import { baseMiddleware } from "../../middleware"; +import { requireAuth } from "../../middleware/auth.middleware"; + +export const meProcedure = baseMiddleware + .use(requireAuth) + .route({ method: "GET", path: "/user/me" }) + .output( + z.object({ + id: z.string(), + email: z.string(), + name: z.string().nullable(), + }), + ) + .handler(({ context }) => { + console.log(context); + const user = context.user; + if (!user) { + // Should be unreachable because of requireAuth, but keeps types narrow + throw new Error("Unauthenticated"); + } + return { + id: user.id, + email: user.email, + name: user.name ?? null, + }; + }); diff --git a/src/orpc/routers.ts b/src/orpc/routers.ts new file mode 100644 index 0000000..52fa8c3 --- /dev/null +++ b/src/orpc/routers.ts @@ -0,0 +1,7 @@ +import { meProcedure } from "./procedures/user/me.p"; + +export const appRouter = { + user: { + me: meProcedure, + }, +}; diff --git a/tsconfig.json b/tsconfig.json index 583ec68..e4de421 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,108 +1,129 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - "paths": { - "@/*": [ - "./*" - ] - }, + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + "paths": { + "@/*": ["./*"] + }, - /* Language and Environment */ - "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + // Environment setup & latest features + "lib": ["ESNext"], + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, - /* Modules */ - "module": "ES2022", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + // Bundler mode + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + // Best practices + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + /* Language and Environment */ + "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Modules */ + "module": "Preserve" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + "types": [ + "bun-types" + ] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } } From aa57bea25424649a1f6d3548be6f7544470554e1 Mon Sep 17 00:00:00 2001 From: Subramanya Chakravarthy Date: Mon, 11 Aug 2025 16:35:33 +0530 Subject: [PATCH 3/3] feat: update email service and auth middleware by removing console logs; add zod dependency --- .github/copilot-instructions.md | 2 +- bun.lock | 7 ++++++- package.json | 3 ++- src/lib/auth.ts | 1 - src/lib/email/email-service.ts | 1 - src/lib/email/providers/resend.ts | 2 -- src/orpc/index.ts | 10 +++++----- src/orpc/middleware/auth.middleware.ts | 4 ---- src/orpc/procedures/user/me.p.ts | 6 +++--- tsconfig.json | 5 +++-- 10 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 338dc70..cdf914c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,4 +1,4 @@ -# AI Coding Agent innstructions – Universal Starter (Server) +# AI Coding Agent instructions – Universal Starter (Server) ## 📌 Tech Stack - **Framework**: [Elysia](https://elysiajs.com/) diff --git a/bun.lock b/bun.lock index ab92559..135492c 100644 --- a/bun.lock +++ b/bun.lock @@ -18,6 +18,7 @@ "elysia-rate-limit": "^4.4.0", "nodemailer": "^7.0.5", "resend": "^4.8.0", + "zod": "^4.0.17", }, "devDependencies": { "@biomejs/biome": "2.0.6", @@ -272,12 +273,16 @@ "zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="], - "zod": ["zod@4.0.14", "", {}, "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw=="], + "zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], + + "@better-auth/expo/zod": ["zod@4.0.14", "", {}, "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw=="], "@scalar/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="], "@tokenizer/inflate/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "better-auth/zod": ["zod@4.0.14", "", {}, "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw=="], + "better-call/rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="], "@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], diff --git a/package.json b/package.json index faf6e69..0a118a5 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "elysia": "latest", "elysia-rate-limit": "^4.4.0", "nodemailer": "^7.0.5", - "resend": "^4.8.0" + "resend": "^4.8.0", + "zod": "^4.0.17" }, "devDependencies": { "@biomejs/biome": "2.0.6", diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 18fa393..c34e2a2 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -29,7 +29,6 @@ export const auth = betterAuth({ }, emailVerification: { sendVerificationEmail: async ({ user, url, token }, request) => { - console.log("Sending verification email to:", user.email); await emailService.sendEmailVerification(user.email, url); }, sendOnSignUp: true, diff --git a/src/lib/email/email-service.ts b/src/lib/email/email-service.ts index 8d6e28b..97636e0 100644 --- a/src/lib/email/email-service.ts +++ b/src/lib/email/email-service.ts @@ -89,7 +89,6 @@ class EmailService { try { // Validate required fields this.validateOptions(options); - console.log("Sending email with options:", options); return await this.provider.sendEmail(options); } catch (error) { console.error("Error sending email:", error); diff --git a/src/lib/email/providers/resend.ts b/src/lib/email/providers/resend.ts index 00e7a57..ce335ed 100644 --- a/src/lib/email/providers/resend.ts +++ b/src/lib/email/providers/resend.ts @@ -30,8 +30,6 @@ export class ResendProvider implements EmailProvider { })), }); - console.log("Email sent successfully:", result.data?.id); - return { success: true, messageId: result.data?.id, diff --git a/src/orpc/index.ts b/src/orpc/index.ts index 26ee4a5..dc145ab 100644 --- a/src/orpc/index.ts +++ b/src/orpc/index.ts @@ -1,15 +1,15 @@ import { OpenAPIHandler } from "@orpc/openapi/fetch"; import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins"; -import { CORSPlugin } from "@orpc/server/plugins"; +// import { CORSPlugin } from "@orpc/server/plugins"; import { ZodToJsonSchemaConverter } from "@orpc/zod"; import { appRouter } from "./routers"; export const orpcHandler = new OpenAPIHandler(appRouter, { plugins: [ - new CORSPlugin({ - origin: (origin, options) => origin, - allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"], - }), + // new CORSPlugin({ + // origin: (origin, options) => origin, + // allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"], + // }), new OpenAPIReferencePlugin({ schemaConverters: [new ZodToJsonSchemaConverter()], specGenerateOptions: { diff --git a/src/orpc/middleware/auth.middleware.ts b/src/orpc/middleware/auth.middleware.ts index 36e3809..76841ac 100644 --- a/src/orpc/middleware/auth.middleware.ts +++ b/src/orpc/middleware/auth.middleware.ts @@ -23,11 +23,8 @@ export const withBetterAuth = os ? context.headers : new Headers(context.headers); - console.log("withBetterAuth context headers", headers); - // Server-side Better Auth API (no client in middlewares) const sessionData = await auth.api.getSession({ headers }); // server API call - console.log("withBetterAuth sessionData", sessionData); return next({ context: { @@ -42,7 +39,6 @@ export const withBetterAuth = os export const requireAuth = os .$context() .middleware(async ({ context, next }) => { - console.log({ context }); if (!context.session || !context.user) { throw new ORPCError("UNAUTHORIZED", { message: "Unauthenticated" }); } diff --git a/src/orpc/procedures/user/me.p.ts b/src/orpc/procedures/user/me.p.ts index 598de8b..38bed51 100644 --- a/src/orpc/procedures/user/me.p.ts +++ b/src/orpc/procedures/user/me.p.ts @@ -1,3 +1,4 @@ +import { ORPCError } from "@orpc/server"; import { z } from "zod"; import { baseMiddleware } from "../../middleware"; import { requireAuth } from "../../middleware/auth.middleware"; @@ -13,11 +14,10 @@ export const meProcedure = baseMiddleware }), ) .handler(({ context }) => { - console.log(context); - const user = context.user; + const { user } = context; if (!user) { // Should be unreachable because of requireAuth, but keeps types narrow - throw new Error("Unauthenticated"); + throw new ORPCError("UNAUTHORIZED", { message: "Unauthenticated" }); } return { id: user.id, diff --git a/tsconfig.json b/tsconfig.json index e4de421..c5eb964 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,8 @@ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@src/*": ["./src/*"] }, // Environment setup & latest features @@ -52,7 +53,7 @@ "module": "Preserve" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ "moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "baseUrl": "." /* Specify the base directory to resolve non-relative module names. */, // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */