From c05b22c852e687660ebcc2e7dfef758bf6ed9b4a Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Wed, 17 Sep 2025 13:48:42 +0530 Subject: [PATCH 01/22] feat: initialize trpc backend --- apps/api/Dockerfile | 19 ++ apps/api/package.json | 28 +++ apps/api/src/index.ts | 17 ++ apps/api/src/trpc.ts | 14 ++ apps/api/tsconfig.json | 44 +++++ pnpm-lock.yaml | 384 ++++++++++++++++++++++++----------------- 6 files changed, 350 insertions(+), 156 deletions(-) create mode 100644 apps/api/Dockerfile create mode 100644 apps/api/package.json create mode 100644 apps/api/src/index.ts create mode 100644 apps/api/src/trpc.ts create mode 100644 apps/api/tsconfig.json diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile new file mode 100644 index 0000000..3249f40 --- /dev/null +++ b/apps/api/Dockerfile @@ -0,0 +1,19 @@ +FROM node:20 + +WORKDIR /app + +COPY package.json ./ + +RUN npm install -g pnpm + +RUN pnpm install + +COPY src ./src +COPY tsconfig.json ./ + + +RUN pnpm run build + +EXPOSE 4000 + +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..b1c650d --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,28 @@ +{ + "name": "api", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "tsx src/index.ts", + "build": "tsc" + }, + "keywords": [], + "author": "Ajeet Pratpa Singh", + "license": "ISC", + "packageManager": "pnpm@10.11.0", + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^24.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" + }, + "dependencies": { + "@trpc/server": "^11.5.1", + "express": "^4.21.2", + "tsx": "^4.20.3", + "zod": "^4.1.9" + } +} diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts new file mode 100644 index 0000000..d7029f7 --- /dev/null +++ b/apps/api/src/index.ts @@ -0,0 +1,17 @@ +import express from "express"; +import * as trpcExpress from '@trpc/server/adapters/express'; +import { appRouter } from './trpc.js'; + +const app = express(); + +app.use( + '/trpc', + trpcExpress.createExpressMiddleware({ + router: appRouter, + createContext: () => ({}), + }) +); + +app.listen(4000, () => { + console.log('tRPC server running on http://localhost:4000'); +}); diff --git a/apps/api/src/trpc.ts b/apps/api/src/trpc.ts new file mode 100644 index 0000000..9a903bf --- /dev/null +++ b/apps/api/src/trpc.ts @@ -0,0 +1,14 @@ +import { initTRPC } from '@trpc/server'; +import { z } from 'zod'; + +const t = initTRPC.create(); + +export const appRouter = t.router({ + hello: t.procedure + .input(z.object({ name: z.string().optional() })) + .query(({ input }) => { + return { message: `Hhhhello, ${input.name ?? 'world'}!` }; + }), +}); + +export type AppRouter = typeof appRouter; \ No newline at end of file diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..bdbad8c --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,44 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + "rootDir": "./src", + "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 708dbbb..d77376c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,35 @@ importers: version: 2.5.4 typescript: specifier: ^5.4.5 - version: 5.8.3 + version: 5.9.2 + + apps/api: + dependencies: + '@trpc/server': + specifier: ^11.5.1 + version: 11.5.1(typescript@5.9.2) + express: + specifier: ^4.21.2 + version: 4.21.2 + tsx: + specifier: ^4.20.3 + version: 4.20.3 + zod: + specifier: ^4.1.9 + version: 4.1.9 + devDependencies: + '@types/express': + specifier: ^4.17.21 + version: 4.17.23 + '@types/node': + specifier: ^24.5.1 + version: 24.5.1 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@24.5.1)(typescript@5.9.2) + typescript: + specifier: ^5.9.2 + version: 5.9.2 apps/backend: dependencies: @@ -71,7 +99,7 @@ importers: version: 4.20.3 typescript: specifier: ^5.3.3 - version: 5.8.3 + version: 5.9.2 apps/docs: dependencies: @@ -108,10 +136,10 @@ importers: version: 8.57.1 eslint-config-next: specifier: 14.2.6 - version: 14.2.6(eslint@8.57.1)(typescript@5.8.3) + version: 14.2.6(eslint@8.57.1)(typescript@5.9.2) typescript: specifier: ^5 - version: 5.8.3 + version: 5.9.2 apps/web: dependencies: @@ -183,7 +211,7 @@ importers: version: 2.6.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.8.3))) + version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.2))) zustand: specifier: ^5.0.1 version: 5.0.5(@types/react@18.3.23)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) @@ -205,7 +233,7 @@ importers: version: 8.57.1 eslint-config-next: specifier: 15.0.2 - version: 15.0.2(eslint@8.57.1)(typescript@5.8.3) + version: 15.0.2(eslint@8.57.1)(typescript@5.9.2) postcss: specifier: ^8 version: 8.5.5 @@ -214,22 +242,22 @@ importers: version: 3.5.3 tailwindcss: specifier: ^3.4.1 - version: 3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.8.3)) + version: 3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.2)) typescript: specifier: ^5 - version: 5.8.3 + version: 5.9.2 packages/eslint-config: devDependencies: '@typescript-eslint/eslint-plugin': specifier: ^7.1.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/parser': specifier: ^7.1.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.8.3) + version: 7.18.0(eslint@8.57.1)(typescript@5.9.2) '@vercel/style-guide': specifier: ^5.2.0 - version: 5.2.0(@next/eslint-plugin-next@14.2.6)(eslint@8.57.1)(prettier@3.5.3)(typescript@5.8.3) + version: 5.2.0(@next/eslint-plugin-next@14.2.6)(eslint@8.57.1)(prettier@3.5.3)(typescript@5.9.2) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.2(eslint@8.57.1) @@ -241,7 +269,7 @@ importers: version: 1.1.0 typescript: specifier: ^5.3.3 - version: 5.8.3 + version: 5.9.2 packages/shared: devDependencies: @@ -265,7 +293,7 @@ importers: version: link:../typescript-config '@turbo/gen': specifier: ^1.12.4 - version: 1.13.4(@types/node@20.19.0)(typescript@5.8.3) + version: 1.13.4(@types/node@20.19.0)(typescript@5.9.2) '@types/eslint': specifier: ^8.56.5 version: 8.56.12 @@ -283,7 +311,7 @@ importers: version: 8.57.1 typescript: specifier: ^5.3.3 - version: 5.8.3 + version: 5.9.2 packages: @@ -1188,6 +1216,11 @@ packages: '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@trpc/server@11.5.1': + resolution: {integrity: sha512-KIDzHRS5m8U1ncPwjgtOtPWK9lNO0kYL7b+lnvKXRqowSAQIEC/z6y7g/dkt4Aqv3DKI/STLydt2/afrP1QrxQ==} + peerDependencies: + typescript: '>=5.7.2' + '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -1262,6 +1295,9 @@ packages: '@types/node@20.19.0': resolution: {integrity: sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==} + '@types/node@24.5.1': + resolution: {integrity: sha512-/SQdmUP2xa+1rdx7VwB9yPq8PaKej8TD5cQ+XfKDPWWC+VDJU4rvVVagXqKUzhKjtFoNA8rXDJAkCxQPAe00+Q==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -4406,8 +4442,8 @@ packages: engines: {node: '>=4.2.0'} hasBin: true - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true @@ -4426,6 +4462,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.12.0: + resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==} + universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} @@ -4576,6 +4615,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@4.1.9: + resolution: {integrity: sha512-HI32jTq0AUAC125z30E8bQNz0RQ+9Uc+4J7V97gLYjZVKRjeydPgGt6dvQzFrav7MYOUGFqqOGiHpA/fdbd0cQ==} + zustand@5.0.5: resolution: {integrity: sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==} engines: {node: '>=12.20.0'} @@ -4989,7 +5031,7 @@ snapshots: dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.29 '@jridgewell/resolve-uri@3.1.2': {} @@ -5389,6 +5431,10 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} + '@trpc/server@11.5.1(typescript@5.9.2)': + dependencies: + typescript: 5.9.2 + '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} @@ -5397,7 +5443,7 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@turbo/gen@1.13.4(@types/node@20.19.0)(typescript@5.8.3)': + '@turbo/gen@1.13.4(@types/node@20.19.0)(typescript@5.9.2)': dependencies: '@turbo/workspaces': 1.13.4 chalk: 2.4.2 @@ -5407,7 +5453,7 @@ snapshots: minimatch: 9.0.5 node-plop: 0.26.3 proxy-agent: 6.5.0 - ts-node: 10.9.2(@types/node@20.19.0)(typescript@5.8.3) + ts-node: 10.9.2(@types/node@20.19.0)(typescript@5.9.2) update-check: 1.5.4 validate-npm-package-name: 5.0.1 transitivePeerDependencies: @@ -5502,6 +5548,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@24.5.1': + dependencies: + undici-types: 7.12.0 + '@types/normalize-package-data@2.4.4': {} '@types/parse-json@4.0.2': {} @@ -5540,13 +5590,13 @@ snapshots: '@types/tinycolor2@1.4.6': {} - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 @@ -5554,104 +5604,104 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@5.8.3) + ts-api-utils: 1.4.3(typescript@5.9.2) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/visitor-keys': 7.18.0 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.8.3) + ts-api-utils: 1.4.3(typescript@5.9.2) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/scope-manager': 8.34.0 - '@typescript-eslint/type-utils': 8.34.0(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/utils': 8.34.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.34.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 8.34.0(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.34.0 eslint: 8.57.1 graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.2) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 7.2.0 '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.9.2) '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 8.34.0 '@typescript-eslint/types': 8.34.0 - '@typescript-eslint/typescript-estree': 8.34.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.34.0(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.34.0 debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.34.0(typescript@5.8.3)': + '@typescript-eslint/project-service@8.34.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.34.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.34.0(typescript@5.9.2) '@typescript-eslint/types': 8.34.0 debug: 4.4.1(supports-color@5.5.0) - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -5680,42 +5730,42 @@ snapshots: '@typescript-eslint/types': 8.34.0 '@typescript-eslint/visitor-keys': 8.34.0 - '@typescript-eslint/tsconfig-utils@8.34.0(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.34.0(typescript@5.9.2)': dependencies: - typescript: 5.8.3 + typescript: 5.9.2 - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.2) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.2) debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.8.3) + ts-api-utils: 1.4.3(typescript@5.9.2) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.8.3) + ts-api-utils: 1.4.3(typescript@5.9.2) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.34.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.34.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.34.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.34.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.34.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.34.0(eslint@8.57.1)(typescript@5.9.2) debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -5729,7 +5779,7 @@ snapshots: '@typescript-eslint/types@8.34.0': {} - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -5737,13 +5787,13 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.2 - tsutils: 3.21.0(typescript@5.8.3) + tsutils: 3.21.0(typescript@5.9.2) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 @@ -5752,13 +5802,13 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@5.8.3) + ts-api-utils: 1.4.3(typescript@5.9.2) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 @@ -5767,13 +5817,13 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@5.8.3) + ts-api-utils: 1.4.3(typescript@5.9.2) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@7.2.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@7.2.0(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 @@ -5782,16 +5832,16 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@5.8.3) + ts-api-utils: 1.4.3(typescript@5.9.2) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.34.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.34.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/project-service': 8.34.0(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.34.0(typescript@5.8.3) + '@typescript-eslint/project-service': 8.34.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.34.0(typescript@5.9.2) '@typescript-eslint/types': 8.34.0 '@typescript-eslint/visitor-keys': 8.34.0 debug: 4.4.1(supports-color@5.5.0) @@ -5799,19 +5849,19 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@types/json-schema': 7.0.15 '@types/semver': 7.7.0 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.2) eslint: 8.57.1 eslint-scope: 5.1.1 semver: 7.7.2 @@ -5819,39 +5869,39 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@types/json-schema': 7.0.15 '@types/semver': 7.7.0 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.2) eslint: 8.57.1 semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) eslint: 8.57.1 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@8.34.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/utils@8.34.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@typescript-eslint/scope-manager': 8.34.0 '@typescript-eslint/types': 8.34.0 - '@typescript-eslint/typescript-estree': 8.34.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.34.0(typescript@5.9.2) eslint: 8.57.1 - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -5951,24 +6001,24 @@ snapshots: next: 15.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 - '@vercel/style-guide@5.2.0(@next/eslint-plugin-next@14.2.6)(eslint@8.57.1)(prettier@3.5.3)(typescript@5.8.3)': + '@vercel/style-guide@5.2.0(@next/eslint-plugin-next@14.2.6)(eslint@8.57.1)(prettier@3.5.3)(typescript@5.9.2)': dependencies: '@babel/core': 7.28.0 '@babel/eslint-parser': 7.28.0(@babel/core@7.28.0)(eslint@8.57.1) '@rushstack/eslint-patch': 1.11.0 - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.2) eslint-config-prettier: 9.1.2(eslint@8.57.1) eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1) + eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) - eslint-plugin-testing-library: 6.5.0(eslint@8.57.1)(typescript@5.8.3) + eslint-plugin-testing-library: 6.5.0(eslint@8.57.1)(typescript@5.9.2) eslint-plugin-tsdoc: 0.2.17 eslint-plugin-unicorn: 48.0.1(eslint@8.57.1) prettier-plugin-packagejson: 2.5.19(prettier@3.5.3) @@ -5976,7 +6026,7 @@ snapshots: '@next/eslint-plugin-next': 14.2.6 eslint: 8.57.1 prettier: 3.5.3 - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x @@ -6752,40 +6802,40 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-next@14.2.6(eslint@8.57.1)(typescript@5.8.3): + eslint-config-next@14.2.6(eslint@8.57.1)(typescript@5.9.2): dependencies: '@next/eslint-plugin-next': 14.2.6 '@rushstack/eslint-patch': 1.11.0 - '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x - supports-color - eslint-config-next@15.0.2(eslint@8.57.1)(typescript@5.8.3): + eslint-config-next@15.0.2(eslint@8.57.1)(typescript@5.9.2): dependencies: '@next/eslint-plugin-next': 15.0.2 '@rushstack/eslint-patch': 1.11.0 - '@typescript-eslint/eslint-plugin': 8.34.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.34.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x @@ -6803,7 +6853,7 @@ snapshots: eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1) eslint-import-resolver-node@0.3.9: dependencies: @@ -6813,7 +6863,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1(supports-color@5.5.0) @@ -6824,7 +6874,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.9.0 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -6839,7 +6889,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.9.0 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -6854,39 +6904,39 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.9.0 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -6896,7 +6946,7 @@ snapshots: eslint: 8.57.1 ignore: 5.3.2 - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -6907,7 +6957,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -6919,13 +6969,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -6936,7 +6986,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -6948,13 +6998,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -6965,7 +7015,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -6977,18 +7027,18 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 optionalDependencies: - '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) transitivePeerDependencies: - supports-color - typescript @@ -7014,11 +7064,11 @@ snapshots: eslint-plugin-only-warn@1.1.0: {} - eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1): + eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1): dependencies: eslint: 8.57.1 optionalDependencies: - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): dependencies: @@ -7054,9 +7104,9 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-testing-library@6.5.0(eslint@8.57.1)(typescript@5.8.3): + eslint-plugin-testing-library@6.5.0(eslint@8.57.1)(typescript@5.9.2): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 transitivePeerDependencies: - supports-color @@ -8417,13 +8467,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.5 - postcss-load-config@4.0.2(postcss@8.5.5)(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.8.3)): + postcss-load-config@4.0.2(postcss@8.5.5)(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.2)): dependencies: lilconfig: 3.1.3 yaml: 2.8.0 optionalDependencies: postcss: 8.5.5 - ts-node: 10.9.2(@types/node@20.19.0)(typescript@5.8.3) + ts-node: 10.9.2(@types/node@20.19.0)(typescript@5.9.2) postcss-nested@6.2.0(postcss@8.5.5): dependencies: @@ -9048,11 +9098,11 @@ snapshots: tailwind-merge@2.6.0: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.8.3))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.2))): dependencies: - tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.8.3)) + tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.2)) - tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.8.3)): + tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -9071,7 +9121,7 @@ snapshots: postcss: 8.5.5 postcss-import: 15.1.0(postcss@8.5.5) postcss-js: 4.0.1(postcss@8.5.5) - postcss-load-config: 4.0.2(postcss@8.5.5)(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.8.3)) + postcss-load-config: 4.0.2(postcss@8.5.5)(ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.2)) postcss-nested: 6.2.0(postcss@8.5.5) postcss-selector-parser: 6.1.2 resolve: 1.22.10 @@ -9120,17 +9170,17 @@ snapshots: touch@3.1.1: {} - ts-api-utils@1.4.3(typescript@5.8.3): + ts-api-utils@1.4.3(typescript@5.9.2): dependencies: - typescript: 5.8.3 + typescript: 5.9.2 - ts-api-utils@2.1.0(typescript@5.8.3): + ts-api-utils@2.1.0(typescript@5.9.2): dependencies: - typescript: 5.8.3 + typescript: 5.9.2 ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.19.0)(typescript@5.8.3): + ts-node@10.9.2(@types/node@20.19.0)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -9144,7 +9194,25 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.8.3 + typescript: 5.9.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + ts-node@10.9.2(@types/node@24.5.1)(typescript@5.9.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 24.5.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -9159,10 +9227,10 @@ snapshots: tslib@2.8.1: {} - tsutils@3.21.0(typescript@5.8.3): + tsutils@3.21.0(typescript@5.9.2): dependencies: tslib: 1.14.1 - typescript: 5.8.3 + typescript: 5.9.2 tsx@4.20.3: dependencies: @@ -9250,7 +9318,7 @@ snapshots: typescript@4.9.5: {} - typescript@5.8.3: {} + typescript@5.9.2: {} uglify-js@3.19.3: optional: true @@ -9266,6 +9334,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.12.0: {} + universal-user-agent@7.0.3: {} universalify@2.0.1: {} @@ -9443,6 +9513,8 @@ snapshots: yocto-queue@0.1.0: {} + zod@4.1.9: {} + zustand@5.0.5(@types/react@18.3.23)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)): optionalDependencies: '@types/react': 18.3.23 From 8349f5df2baeeed86bb1136eca5080a70f95ec2b Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Sun, 21 Sep 2025 20:08:42 +0530 Subject: [PATCH 02/22] db: connect to db --- apps/api/package.json | 6 ++-- apps/api/prisma/schema.prisma | 59 +++++++++++++++++++++++++++++++++++ apps/api/src/context.ts | 16 ++++++++++ apps/api/src/index.ts | 6 +++- apps/api/src/prisma.ts | 38 ++++++++++++++++++++++ pnpm-lock.yaml | 36 +++++++-------------- 6 files changed, 134 insertions(+), 27 deletions(-) create mode 100644 apps/api/prisma/schema.prisma create mode 100644 apps/api/src/context.ts create mode 100644 apps/api/src/prisma.ts diff --git a/apps/api/package.json b/apps/api/package.json index b1c650d..09f968a 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -16,13 +16,15 @@ "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^24.5.1", - "ts-node": "^10.9.2", + "prisma": "^5.22.0", + "tsx": "^4.20.3", "typescript": "^5.9.2" }, "dependencies": { + "@prisma/client": "^5.22.0", "@trpc/server": "^11.5.1", + "dotenv": "^16.5.0", "express": "^4.21.2", - "tsx": "^4.20.3", "zod": "^4.1.9" } } diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma new file mode 100644 index 0000000..5d19b26 --- /dev/null +++ b/apps/api/prisma/schema.prisma @@ -0,0 +1,59 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model QueryCount { + id Int @id @default(1) + total_queries BigInt +} + +model User { + id String @id @default(cuid()) + + email String @unique + + firstName String + + authMethod String + + createdAt DateTime @default(now()) + + lastLogin DateTime @updatedAt + + accounts Account[] +} + +model Account { + id String @id @default(cuid()) + + userId String // Foreign key to User + + type String // "oauth", "email", etc. + + provider String // "google", "github", etc. + + providerAccountId String // ID from the provider + + refresh_token String? + + access_token String? + + expires_at Int? + + token_type String? + + scope String? + + id_token String? + + session_state String? + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) +} diff --git a/apps/api/src/context.ts b/apps/api/src/context.ts new file mode 100644 index 0000000..de46125 --- /dev/null +++ b/apps/api/src/context.ts @@ -0,0 +1,16 @@ +import type { CreateExpressContextOptions } from '@trpc/server/adapters/express'; +import prisma from './prisma.js'; + +export async function createContext({ req, res }: CreateExpressContextOptions): Promise<{ + req: CreateExpressContextOptions['req']; + res: CreateExpressContextOptions['res']; + db: typeof prisma; +}> { + return { + req, + res, + db: prisma, + }; +} + +export type Context = Awaited>; diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index d7029f7..bdee4cd 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,14 +1,18 @@ import express from "express"; import * as trpcExpress from '@trpc/server/adapters/express'; import { appRouter } from './trpc.js'; +import { createContext } from "./context.js"; +import prismaModule from "./prisma.js"; const app = express(); +prismaModule.connectDB(); + app.use( '/trpc', trpcExpress.createExpressMiddleware({ router: appRouter, - createContext: () => ({}), + createContext, }) ); diff --git a/apps/api/src/prisma.ts b/apps/api/src/prisma.ts new file mode 100644 index 0000000..1a583b1 --- /dev/null +++ b/apps/api/src/prisma.ts @@ -0,0 +1,38 @@ +import { PrismaClient } from "@prisma/client"; +import dotenv from "dotenv"; + +dotenv.config(); + +const prisma = new PrismaClient({ + datasources: { + db: { + url: process.env.DATABASE_URL!, + }, + }, + log: ['error', 'warn'], +}); + +const withTimeout = async ( + operation: Promise, + timeoutMs: number = 5000 +): Promise => { + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Database operation timed out')), timeoutMs); + }); + + return Promise.race([operation, timeoutPromise]); +}; + +async function connectDB() { + try { + await prisma.$connect(); + console.log("✅ Database connected successfully"); + } catch (err) { + console.error("❌ Database connection failed:", err); + process.exit(1); + } +} + + +export { withTimeout }; +export default { prisma, connectDB }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d77376c..37ec3c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,15 +20,18 @@ importers: apps/api: dependencies: + '@prisma/client': + specifier: ^5.22.0 + version: 5.22.0(prisma@5.22.0) '@trpc/server': specifier: ^11.5.1 version: 11.5.1(typescript@5.9.2) + dotenv: + specifier: ^16.5.0 + version: 16.5.0 express: specifier: ^4.21.2 version: 4.21.2 - tsx: - specifier: ^4.20.3 - version: 4.20.3 zod: specifier: ^4.1.9 version: 4.1.9 @@ -39,9 +42,12 @@ importers: '@types/node': specifier: ^24.5.1 version: 24.5.1 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@24.5.1)(typescript@5.9.2) + prisma: + specifier: ^5.22.0 + version: 5.22.0 + tsx: + specifier: ^4.20.3 + version: 4.20.3 typescript: specifier: ^5.9.2 version: 5.9.2 @@ -9198,24 +9204,6 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - ts-node@10.9.2(@types/node@24.5.1)(typescript@5.9.2): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 24.5.1 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.9.2 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 From 6591c14101be92da1cac0657b22effc4853633d4 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Sun, 21 Sep 2025 20:51:09 +0530 Subject: [PATCH 03/22] chore: add query, user and projects routers --- apps/api/src/index.ts | 2 +- apps/api/src/routers/_app.ts | 16 ++++++++++++++++ apps/api/src/routers/projects.ts | 0 apps/api/src/routers/queries.ts | 7 +++++++ apps/api/src/routers/user.ts | 7 +++++++ apps/api/src/trpc.ts | 12 ++---------- 6 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 apps/api/src/routers/_app.ts create mode 100644 apps/api/src/routers/projects.ts create mode 100644 apps/api/src/routers/queries.ts create mode 100644 apps/api/src/routers/user.ts diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index bdee4cd..c250b92 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,6 +1,6 @@ import express from "express"; import * as trpcExpress from '@trpc/server/adapters/express'; -import { appRouter } from './trpc.js'; +import { appRouter } from '../src/routers/_app.js'; import { createContext } from "./context.js"; import prismaModule from "./prisma.js"; diff --git a/apps/api/src/routers/_app.ts b/apps/api/src/routers/_app.ts new file mode 100644 index 0000000..395b8d6 --- /dev/null +++ b/apps/api/src/routers/_app.ts @@ -0,0 +1,16 @@ +import { router, publicProcedure } from '../trpc.js'; +import { z } from 'zod'; + +const testRouter = router({ + test: publicProcedure + .input(z.object({ name: z.string().optional() })) + .query(({ input }) => { + return { message: `Hhhhello, ${input.name ?? 'world'}!` }; + }), +}) + +export const appRouter = router({ + hello: testRouter, +}); + +export type AppRouter = typeof appRouter; \ No newline at end of file diff --git a/apps/api/src/routers/projects.ts b/apps/api/src/routers/projects.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/api/src/routers/queries.ts b/apps/api/src/routers/queries.ts new file mode 100644 index 0000000..b4d121e --- /dev/null +++ b/apps/api/src/routers/queries.ts @@ -0,0 +1,7 @@ +import { router, publicProcedure } from '../trpc.js'; +import { z } from 'zod'; + +export const queryRouter = router({ + // get the total count of fetched queries + +}) \ No newline at end of file diff --git a/apps/api/src/routers/user.ts b/apps/api/src/routers/user.ts new file mode 100644 index 0000000..72fda9f --- /dev/null +++ b/apps/api/src/routers/user.ts @@ -0,0 +1,7 @@ +import { router, publicProcedure } from '../trpc.js'; +import { z } from 'zod'; + +export const userRouter = router({ + // get the total count of users + +}) \ No newline at end of file diff --git a/apps/api/src/trpc.ts b/apps/api/src/trpc.ts index 9a903bf..0db673e 100644 --- a/apps/api/src/trpc.ts +++ b/apps/api/src/trpc.ts @@ -1,14 +1,6 @@ import { initTRPC } from '@trpc/server'; -import { z } from 'zod'; const t = initTRPC.create(); -export const appRouter = t.router({ - hello: t.procedure - .input(z.object({ name: z.string().optional() })) - .query(({ input }) => { - return { message: `Hhhhello, ${input.name ?? 'world'}!` }; - }), -}); - -export type AppRouter = typeof appRouter; \ No newline at end of file +export const router = t.router; +export const publicProcedure = t.procedure; From 74dd3474576a80074f21045afeff946011126720 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Sun, 21 Sep 2025 21:36:55 +0530 Subject: [PATCH 04/22] feat: procedre to get the queries --- apps/api/src/routers/_app.ts | 3 +++ apps/api/src/routers/queries.ts | 10 +++++++++- apps/api/src/trpc.ts | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/api/src/routers/_app.ts b/apps/api/src/routers/_app.ts index 395b8d6..2294e6b 100644 --- a/apps/api/src/routers/_app.ts +++ b/apps/api/src/routers/_app.ts @@ -1,4 +1,6 @@ +import { query } from 'express'; import { router, publicProcedure } from '../trpc.js'; +import { queryRouter } from './queries.js'; import { z } from 'zod'; const testRouter = router({ @@ -11,6 +13,7 @@ const testRouter = router({ export const appRouter = router({ hello: testRouter, + query: queryRouter }); export type AppRouter = typeof appRouter; \ No newline at end of file diff --git a/apps/api/src/routers/queries.ts b/apps/api/src/routers/queries.ts index b4d121e..090541c 100644 --- a/apps/api/src/routers/queries.ts +++ b/apps/api/src/routers/queries.ts @@ -3,5 +3,13 @@ import { z } from 'zod'; export const queryRouter = router({ // get the total count of fetched queries - + count: publicProcedure + .query(async ({ ctx }) => { + const queryCount = await ctx.db.prisma.queryCount.findUnique({ + where: { id: 1 } + }); + return { + total_queries: queryCount?.total_queries.toString() || "0" + }; + }) }) \ No newline at end of file diff --git a/apps/api/src/trpc.ts b/apps/api/src/trpc.ts index 0db673e..c745c90 100644 --- a/apps/api/src/trpc.ts +++ b/apps/api/src/trpc.ts @@ -1,6 +1,7 @@ import { initTRPC } from '@trpc/server'; +import type { Context } from './context.js'; -const t = initTRPC.create(); +const t = initTRPC.context().create(); export const router = t.router; export const publicProcedure = t.procedure; From 5cf37e949bf558a5d8d7ad27f5e6fb8e2265758b Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Tue, 30 Sep 2025 16:48:46 +0530 Subject: [PATCH 05/22] feat: procedure fetch github projects --- apps/api/package.json | 1 + apps/api/src/routers/_app.ts | 21 +++--- apps/api/src/routers/projects.ts | 126 +++++++++++++++++++++++++++++++ apps/api/src/routers/queries.ts | 1 - apps/api/src/routers/user.ts | 9 ++- pnpm-lock.yaml | 3 + 6 files changed, 149 insertions(+), 12 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 09f968a..6bc75d4 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,6 +21,7 @@ "typescript": "^5.9.2" }, "dependencies": { + "@octokit/graphql": "^9.0.1", "@prisma/client": "^5.22.0", "@trpc/server": "^11.5.1", "dotenv": "^16.5.0", diff --git a/apps/api/src/routers/_app.ts b/apps/api/src/routers/_app.ts index 2294e6b..93aa884 100644 --- a/apps/api/src/routers/_app.ts +++ b/apps/api/src/routers/_app.ts @@ -1,19 +1,22 @@ -import { query } from 'express'; -import { router, publicProcedure } from '../trpc.js'; -import { queryRouter } from './queries.js'; -import { z } from 'zod'; +import { router, publicProcedure } from "../trpc.js"; +import { queryRouter } from "./queries.js"; +import { userRouter } from "./user.js"; +import { projectRouter } from "./projects.js"; +import { z } from "zod"; const testRouter = router({ - test: publicProcedure + test: publicProcedure .input(z.object({ name: z.string().optional() })) .query(({ input }) => { - return { message: `Hhhhello, ${input.name ?? 'world'}!` }; + return { message: `Hhhhello, ${input.name ?? "world"}!` }; }), -}) +}); export const appRouter = router({ hello: testRouter, - query: queryRouter + query: queryRouter, + user: userRouter, + project: projectRouter, }); -export type AppRouter = typeof appRouter; \ No newline at end of file +export type AppRouter = typeof appRouter; diff --git a/apps/api/src/routers/projects.ts b/apps/api/src/routers/projects.ts index e69de29..dd74b8a 100644 --- a/apps/api/src/routers/projects.ts +++ b/apps/api/src/routers/projects.ts @@ -0,0 +1,126 @@ +import { router, publicProcedure } from "../trpc.js"; +import dotenv from "dotenv"; +import { graphql } from "@octokit/graphql"; +import { z } from "zod"; +import type { + FilterProps, + RepositoryProps, + GraphQLResponseProps, + OptionsTypesProps, +} from "@opensox/shared"; + +dotenv.config(); + +const GH_PAT = process.env.GITHUB_PERSONAL_ACCESS_TOKEN; + +const graphqlWithAuth = graphql.defaults({ + headers: { + authorization: `token ${GH_PAT}`, + }, +}); + +const filterPropsSchema = z.object({ + language: z.string().optional(), + stars: z + .object({ + min: z.string().optional(), + max: z.string().optional(), + custom: z.string().optional(), + }) + .optional(), + forks: z + .object({ + min: z.string().optional(), + max: z.string().optional(), + }) + .optional(), + pushed: z.string().optional(), + created: z.string().optional(), +}); + +const optionsSchema = z.object({ + sort: z.literal("stars").optional(), + order: z.literal("desc").optional(), + per_page: z.number().optional(), + page: z.number().optional(), +}); + +const inputSchema = z.object({ + filters: filterPropsSchema.optional(), + options: optionsSchema.optional(), +}); + +export const projectRouter = router({ + getGithubProjects: publicProcedure + .input(inputSchema) + .query(async ({ input }): Promise => { + const queryParts: string[] = []; + const filters = input.filters || {}; + const options = input.options || {}; + + if (filters.language) { + queryParts.push(`language:${filters.language}`); + } + + if (filters.stars) { + queryParts.push(`stars:${filters.stars.min}..${filters.stars.max}`); + } + + if (filters.forks) { + queryParts.push(`forks:${filters.forks.min}..${filters.forks.max}`); + } + + if (filters.pushed) { + queryParts.push(`pushed:${filters.pushed}`); + } + + if (filters.created) { + queryParts.push(`created:${filters.created}`); + } + + // Default fields to filter contributor friendly repos + + queryParts.push(`is:organization`); + queryParts.push(`is:public`); + queryParts.push(`fork:true`); + + const searchQueryString = queryParts.join(" "); + + const response: GraphQLResponseProps = await graphqlWithAuth( + ` + query($searchQuery: String!, $first: Int!) { + search( + query: $searchQuery, + type: REPOSITORY, + first: $first + ) { + nodes { + ... on Repository { + id + name + description + url + owner { + avatarUrl + } + issues(states: OPEN) { + totalCount + } + primaryLanguage { + name + } + } + } + repositoryCount + } + } + `, + { + searchQuery: searchQueryString, + first: options.per_page || 100, + } + ); + + return response.search.nodes; + }), +}); diff --git a/apps/api/src/routers/queries.ts b/apps/api/src/routers/queries.ts index 090541c..52cac39 100644 --- a/apps/api/src/routers/queries.ts +++ b/apps/api/src/routers/queries.ts @@ -1,5 +1,4 @@ import { router, publicProcedure } from '../trpc.js'; -import { z } from 'zod'; export const queryRouter = router({ // get the total count of fetched queries diff --git a/apps/api/src/routers/user.ts b/apps/api/src/routers/user.ts index 72fda9f..6b46c6a 100644 --- a/apps/api/src/routers/user.ts +++ b/apps/api/src/routers/user.ts @@ -1,7 +1,12 @@ import { router, publicProcedure } from '../trpc.js'; -import { z } from 'zod'; export const userRouter = router({ // get the total count of users - + count: publicProcedure + .query(async ({ ctx }) => { + const userCount = await ctx.db.prisma.user.count(); + return { + total_users: userCount + }; + }) }) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37ec3c8..c9fa11a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: apps/api: dependencies: + '@octokit/graphql': + specifier: ^9.0.1 + version: 9.0.1 '@prisma/client': specifier: ^5.22.0 version: 5.22.0(prisma@5.22.0) From 2d9f45c988e93e4124b946431ee5183a196111dc Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Tue, 30 Sep 2025 17:31:24 +0530 Subject: [PATCH 06/22] feat: fuck ddos --- apps/api/package.json | 4 + apps/api/src/context.ts | 17 ++- apps/api/src/index.ts | 107 ++++++++++++++- apps/api/src/middleware/ipBlock.ts | 94 ++++++++++++++ apps/api/src/trpc.ts | 4 +- apps/api/test-ddos.js | 201 +++++++++++++++++++++++++++++ pnpm-lock.yaml | 12 ++ 7 files changed, 427 insertions(+), 12 deletions(-) create mode 100644 apps/api/src/middleware/ipBlock.ts create mode 100755 apps/api/test-ddos.js diff --git a/apps/api/package.json b/apps/api/package.json index 6bc75d4..9e641c5 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -14,6 +14,7 @@ "license": "ISC", "packageManager": "pnpm@10.11.0", "devDependencies": { + "@types/cors": "^2.8.19", "@types/express": "^4.17.21", "@types/node": "^24.5.1", "prisma": "^5.22.0", @@ -24,8 +25,11 @@ "@octokit/graphql": "^9.0.1", "@prisma/client": "^5.22.0", "@trpc/server": "^11.5.1", + "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^4.21.2", + "express-rate-limit": "^7.5.0", + "helmet": "^7.2.0", "zod": "^4.1.9" } } diff --git a/apps/api/src/context.ts b/apps/api/src/context.ts index de46125..74c0c23 100644 --- a/apps/api/src/context.ts +++ b/apps/api/src/context.ts @@ -1,15 +1,22 @@ -import type { CreateExpressContextOptions } from '@trpc/server/adapters/express'; -import prisma from './prisma.js'; +import type { CreateExpressContextOptions } from "@trpc/server/adapters/express"; +import prisma from "./prisma.js"; -export async function createContext({ req, res }: CreateExpressContextOptions): Promise<{ - req: CreateExpressContextOptions['req']; - res: CreateExpressContextOptions['res']; +export async function createContext({ + req, + res, +}: CreateExpressContextOptions): Promise<{ + req: CreateExpressContextOptions["req"]; + res: CreateExpressContextOptions["res"]; db: typeof prisma; + ip?: string; }> { + const ip = req.ip || req.socket.remoteAddress || "unknown"; + return { req, res, db: prisma, + ip, }; } diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index c250b92..e7cd3d3 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,21 +1,118 @@ +import dotenv from "dotenv"; import express from "express"; -import * as trpcExpress from '@trpc/server/adapters/express'; -import { appRouter } from '../src/routers/_app.js'; +import type { Request, Response } from "express"; +import * as trpcExpress from "@trpc/server/adapters/express"; +import { appRouter } from "../src/routers/_app.js"; import { createContext } from "./context.js"; import prismaModule from "./prisma.js"; +import cors from "cors"; +import type { CorsOptions as CorsOptionsType } from "cors"; +import rateLimit from "express-rate-limit"; +import helmet from "helmet"; +import ipBlocker from "./middleware/ipBlock.js"; + +dotenv.config(); const app = express(); +const PORT = process.env.PORT || 4000; +const CORS_ORIGINS = process.env.CORS_ORIGINS + ? process.env.CORS_ORIGINS.split(",") + : ["http://localhost:3000", "http://localhost:5000"]; + +// Security headers +app.use(helmet()); +app.use( + helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "'unsafe-inline'"], + styleSrc: ["'self'", "'unsafe-inline'"], + imgSrc: ["'self'", "data:", "https:"], + }, + }) +); + +// Apply IP blocking middleware first +app.use(ipBlocker.middleware); + +// Different rate limits for different endpoints +const authLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 5, + message: "Too many login attempts, please try again later", + standardHeaders: true, + legacyHeaders: false, +}); + +const apiLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 30, + message: "Too many requests from this IP", + standardHeaders: true, + legacyHeaders: false, +}); +// Request size limits +app.use(express.json({ limit: "10kb" })); +app.use(express.urlencoded({ limit: "10kb", extended: true })); + +// CORS configuration +const corsOptions: CorsOptionsType = { + origin: (origin, callback) => { + if (!origin || CORS_ORIGINS.includes(origin)) { + callback(null, origin); + } else { + callback(new Error("Not allowed by CORS")); + } + }, + methods: ["GET", "POST"], + allowedHeaders: ["Content-Type", "Authorization"], + credentials: true, + maxAge: 86400, // 24 hours +}; + +app.use(cors(corsOptions)); + +// Blocked IPs endpoint (admin endpoint) +app.get("/admin/blocked-ips", (req: Request, res: Response) => { + const blockedIPs = ipBlocker.getBlockedIPs(); + res.json({ + blockedIPs: blockedIPs.map((ip) => ({ + ...ip, + blockedUntil: new Date(ip.blockedUntil).toISOString(), + })), + }); +}); + +// Test endpoint +app.get("/test", apiLimiter, (req: Request, res: Response) => { + res.status(200).json({ status: "ok", message: "Test endpoint is working" }); +}); + +// Connect to database prismaModule.connectDB(); +// Apply rate limiting to tRPC endpoints +app.use("/trpc", apiLimiter); + +// tRPC middleware app.use( - '/trpc', + "/trpc", trpcExpress.createExpressMiddleware({ router: appRouter, createContext, }) ); -app.listen(4000, () => { - console.log('tRPC server running on http://localhost:4000'); +// Global error handling +app.use((err: Error, req: Request, res: Response, next: Function) => { + console.error(err.stack); + res.status(500).json({ + error: "Internal Server Error", + message: process.env.NODE_ENV === "development" ? err.message : undefined, + }); +}); + +app.listen(PORT, () => { + console.log(`tRPC server running on http://localhost:${PORT}`); }); diff --git a/apps/api/src/middleware/ipBlock.ts b/apps/api/src/middleware/ipBlock.ts new file mode 100644 index 0000000..5d3f8df --- /dev/null +++ b/apps/api/src/middleware/ipBlock.ts @@ -0,0 +1,94 @@ +import type { Request, Response, NextFunction } from "express"; + +interface BlockedIP { + ip: string; + blockedUntil: number; + reason: string; +} + +class IPBlocker { + private blockedIPs: Map = new Map(); + private readonly BLOCK_DURATION = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + private readonly MAX_REQUESTS = 70; // Maximum requests per window + private readonly WINDOW_MS = 60 * 1000; // 1 minute window + private requestCounts: Map = + new Map(); + + private isIPBlocked(ip: string): boolean { + const blockedIP = this.blockedIPs.get(ip); + if (!blockedIP) return false; + + if (Date.now() > blockedIP.blockedUntil) { + this.blockedIPs.delete(ip); + return false; + } + + return true; + } + + private trackRequest(ip: string): boolean { + const now = Date.now(); + const requestData = this.requestCounts.get(ip); + + if (!requestData || now > requestData.resetTime) { + this.requestCounts.set(ip, { count: 1, resetTime: now + this.WINDOW_MS }); + return true; + } + + requestData.count++; + if (requestData.count > this.MAX_REQUESTS) { + this.blockIP(ip, "Too many requests"); + return false; + } + + return true; + } + + public blockIP(ip: string, reason: string): void { + this.blockedIPs.set(ip, { + ip, + blockedUntil: Date.now() + this.BLOCK_DURATION, + reason, + }); + } + + public getBlockedIPs(): BlockedIP[] { + const now = Date.now(); + return Array.from(this.blockedIPs.values()).filter( + (blockedIP) => now <= blockedIP.blockedUntil + ); + } + + public middleware = ( + req: Request, + res: Response, + next: NextFunction + ): void => { + const ip = req.ip || req.socket.remoteAddress || "unknown"; + + if (this.isIPBlocked(ip)) { + const blockedIP = this.blockedIPs.get(ip); + res.status(403).json({ + error: "IP Blocked", + message: `Your IP has been blocked. Reason: ${blockedIP?.reason}`, + blockedUntil: new Date(blockedIP?.blockedUntil || 0).toISOString(), + }); + return; + } + + if (!this.trackRequest(ip)) { + res.status(429).json({ + error: "Too Many Requests", + message: "Your IP has been blocked due to suspicious activity", + }); + return; + } + + next(); + }; +} + +// Create a singleton instance +const ipBlocker = new IPBlocker(); + +export default ipBlocker; diff --git a/apps/api/src/trpc.ts b/apps/api/src/trpc.ts index c745c90..93e9901 100644 --- a/apps/api/src/trpc.ts +++ b/apps/api/src/trpc.ts @@ -1,5 +1,5 @@ -import { initTRPC } from '@trpc/server'; -import type { Context } from './context.js'; +import { initTRPC } from "@trpc/server"; +import type { Context } from "./context.js"; const t = initTRPC.context().create(); diff --git a/apps/api/test-ddos.js b/apps/api/test-ddos.js new file mode 100755 index 0000000..b0c4f1b --- /dev/null +++ b/apps/api/test-ddos.js @@ -0,0 +1,201 @@ +#!/usr/bin/env node + +/** + * DDOS Prevention Test Script (Node.js version) + * Tests rate limiting and IP blocking mechanisms + */ + +const API_URL = "http://localhost:4000"; + +// Color codes for terminal output +const colors = { + reset: "\x1b[0m", + blue: "\x1b[34m", + green: "\x1b[32m", + red: "\x1b[31m", + yellow: "\x1b[33m", +}; + +const log = { + info: (msg) => console.log(`${colors.blue}${msg}${colors.reset}`), + success: (msg) => console.log(`${colors.green}${msg}${colors.reset}`), + error: (msg) => console.log(`${colors.red}${msg}${colors.reset}`), + warning: (msg) => console.log(`${colors.yellow}${msg}${colors.reset}`), +}; + +async function makeRequest(url, showDetails = false) { + try { + const response = await fetch(url); + const data = await response.text(); + + if (showDetails && response.status !== 200) { + try { + const json = JSON.parse(data); + return { status: response.status, data: json }; + } catch { + return { status: response.status, data }; + } + } + + return { status: response.status, data }; + } catch (error) { + return { status: 0, error: error.message }; + } +} + +async function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function testServerRunning() { + log.warning("📡 Test 1: Checking if server is running..."); + const result = await makeRequest(`${API_URL}/test`); + + if (result.status === 0) { + log.error("❌ Server is not running on port 4000"); + log.error("Please start the server with: cd apps/api && pnpm dev"); + process.exit(1); + } + + log.success("✅ Server is running"); + console.log(""); +} + +async function testNormalRequests() { + log.warning("📊 Test 2: Sending 10 normal requests with delays..."); + let success = 0; + + for (let i = 1; i <= 10; i++) { + const result = await makeRequest(`${API_URL}/test`); + console.log(`Request ${i}: HTTP ${result.status}`); + + if (result.status === 200) success++; + await sleep(100); // 100ms delay between requests + } + + log.success(`✅ All ${success}/10 requests succeeded`); + console.log(""); +} + +async function testRateLimiting() { + log.warning("📊 Test 3: Testing Express rate limiter (40 rapid requests)..."); + const results = { success: 0, blocked: 0 }; + + const promises = Array.from({ length: 40 }, (_, i) => + makeRequest(`${API_URL}/test`).then((result) => { + if (result.status === 429 || result.status === 403) { + results.blocked++; + if (results.blocked === 1) { + log.error(`Request ${i + 1}: HTTP ${result.status} (RATE LIMITED)`); + console.log("Response:", result.data); + } + } else if (result.status === 200) { + results.success++; + } + + // Show progress every 10 requests + if ((i + 1) % 10 === 0) { + console.log(`Progress: ${i + 1}/40 (Success: ${results.success}, Blocked: ${results.blocked})`); + } + }) + ); + + await Promise.all(promises); + + console.log(""); + log.success(`✅ Success: ${results.success} | ❌ Blocked: ${results.blocked}`); + console.log(""); +} + +async function testIPBlocker() { + log.warning("📊 Test 4: Testing IP blocker (100 very rapid requests)..."); + const results = { success: 0, blocked: 0, ipBlocked: 0 }; + + for (let i = 1; i <= 100; i++) { + const result = await makeRequest(`${API_URL}/trpc/hello.test?input=%7B%22name%22%3A%22test%22%7D`, true); + + if (result.status === 403) { + results.ipBlocked++; + if (results.ipBlocked === 1) { + log.error(`\nIP BLOCKED at request ${i}!`); + console.log("Response:", result.data); + } + } else if (result.status === 429) { + results.blocked++; + } else if (result.status === 200) { + results.success++; + } + + // Show progress every 20 requests + if (i % 20 === 0) { + console.log(`Progress: ${i}/100 (Success: ${results.success}, Rate Limited: ${results.blocked}, IP Blocked: ${results.ipBlocked})`); + } + + // No delay - testing rapid requests + } + + console.log(""); + log.success(`✅ Success: ${results.success}`); + log.warning(`⚠️ Rate Limited (429): ${results.blocked}`); + log.error(`🚫 IP Blocked (403): ${results.ipBlocked}`); + console.log(""); +} + +async function checkBlockedIPs() { + log.warning("📊 Test 5: Checking blocked IPs..."); + const result = await makeRequest(`${API_URL}/admin/blocked-ips`); + + if (result.status === 200) { + try { + const data = JSON.parse(result.data); + console.log(JSON.stringify(data, null, 2)); + + if (data.blockedIPs && data.blockedIPs.length > 0) { + log.error(`\n🚫 ${data.blockedIPs.length} IP(s) currently blocked`); + data.blockedIPs.forEach((ip) => { + console.log(` IP: ${ip.ip}`); + console.log(` Reason: ${ip.reason}`); + console.log(` Blocked until: ${ip.blockedUntil}`); + }); + } else { + log.success("✅ No IPs currently blocked"); + } + } catch (e) { + console.log(result.data); + } + } + console.log(""); +} + +async function main() { + log.info("🧪 Testing DDOS Prevention Mechanisms"); + console.log("=========================================="); + console.log(""); + + await testServerRunning(); + await testNormalRequests(); + await testRateLimiting(); + + // Wait 1 second before IP blocker test + log.warning("⏳ Waiting 1 second before IP blocker test..."); + await sleep(1000); + console.log(""); + + await testIPBlocker(); + await checkBlockedIPs(); + + console.log("=========================================="); + log.info("📝 Expected Behavior:"); + console.log(" • Express rate limiter: blocks after 30 requests/15min"); + console.log(" • IP blocker: blocks after 70 requests/1min"); + console.log(" • Blocked IPs: remain blocked for 24 hours"); + console.log(""); + log.success(`🔍 View blocked IPs: ${API_URL}/admin/blocked-ips`); + console.log(""); +} + +main().catch((error) => { + log.error("Error running tests:"); + console.error(error); + process.exit(1); +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9fa11a..87d0251 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,16 +29,28 @@ importers: '@trpc/server': specifier: ^11.5.1 version: 11.5.1(typescript@5.9.2) + cors: + specifier: ^2.8.5 + version: 2.8.5 dotenv: specifier: ^16.5.0 version: 16.5.0 express: specifier: ^4.21.2 version: 4.21.2 + express-rate-limit: + specifier: ^7.5.0 + version: 7.5.0(express@4.21.2) + helmet: + specifier: ^7.2.0 + version: 7.2.0 zod: specifier: ^4.1.9 version: 4.1.9 devDependencies: + '@types/cors': + specifier: ^2.8.19 + version: 2.8.19 '@types/express': specifier: ^4.17.21 version: 4.17.23 From b4694f31be40a0c4336e09daaa391fab96ce67d1 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Wed, 1 Oct 2025 14:10:15 +0530 Subject: [PATCH 07/22] feat: migrate auth logic --- apps/api/package.json | 1 + apps/api/src/context.ts | 3 ++ apps/api/src/routers/_app.ts | 2 ++ apps/api/src/routers/auth.ts | 55 ++++++++++++++++++++++++++++++++++++ apps/api/src/trpc.ts | 32 ++++++++++++++++++++- apps/api/src/utils/auth.ts | 34 ++++++++++++++++++++++ pnpm-lock.yaml | 3 ++ 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 apps/api/src/routers/auth.ts create mode 100644 apps/api/src/utils/auth.ts diff --git a/apps/api/package.json b/apps/api/package.json index 9e641c5..0fa085c 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -30,6 +30,7 @@ "express": "^4.21.2", "express-rate-limit": "^7.5.0", "helmet": "^7.2.0", + "jsonwebtoken": "^9.0.2", "zod": "^4.1.9" } } diff --git a/apps/api/src/context.ts b/apps/api/src/context.ts index 74c0c23..2243459 100644 --- a/apps/api/src/context.ts +++ b/apps/api/src/context.ts @@ -1,5 +1,6 @@ import type { CreateExpressContextOptions } from "@trpc/server/adapters/express"; import prisma from "./prisma.js"; +import type { User } from "@prisma/client"; export async function createContext({ req, @@ -9,6 +10,7 @@ export async function createContext({ res: CreateExpressContextOptions["res"]; db: typeof prisma; ip?: string; + user?: User | null; }> { const ip = req.ip || req.socket.remoteAddress || "unknown"; @@ -17,6 +19,7 @@ export async function createContext({ res, db: prisma, ip, + user: null, }; } diff --git a/apps/api/src/routers/_app.ts b/apps/api/src/routers/_app.ts index 93aa884..f5ea9f3 100644 --- a/apps/api/src/routers/_app.ts +++ b/apps/api/src/routers/_app.ts @@ -2,6 +2,7 @@ import { router, publicProcedure } from "../trpc.js"; import { queryRouter } from "./queries.js"; import { userRouter } from "./user.js"; import { projectRouter } from "./projects.js"; +import { authRouter } from "./auth.js"; import { z } from "zod"; const testRouter = router({ @@ -17,6 +18,7 @@ export const appRouter = router({ query: queryRouter, user: userRouter, project: projectRouter, + auth: authRouter, }); export type AppRouter = typeof appRouter; diff --git a/apps/api/src/routers/auth.ts b/apps/api/src/routers/auth.ts new file mode 100644 index 0000000..b08c624 --- /dev/null +++ b/apps/api/src/routers/auth.ts @@ -0,0 +1,55 @@ +import { router, publicProcedure, protectedProcedure } from "../trpc.js"; +import { z } from "zod"; +import { generateToken } from "../utils/auth.js"; +import { TRPCError } from "@trpc/server"; + +const googleAuthSchema = z.object({ + email: z.string().email("Invalid email format"), + firstName: z.string().optional(), + authMethod: z.string().optional(), +}); + +export const authRouter = router({ + googleAuth: publicProcedure + .input(googleAuthSchema) + .mutation(async ({ input, ctx }) => { + try { + const { email, firstName, authMethod } = input; + + const user = await ctx.db.prisma.user.upsert({ + where: { email }, + update: { + lastLogin: new Date(), + }, + create: { + email, + firstName: firstName || "Opensox User", + authMethod: authMethod || "google", + lastLogin: new Date(), + }, + }); + + const token = generateToken(email); + + return { + user, + token, + }; + } catch (error) { + if (process.env.NODE_ENV !== "production") { + console.error("Google auth error:", error); + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Authentication failed", + }); + } + }), + getSession: protectedProcedure.query( + async ({ ctx }: { ctx: { user: any } }) => { + return { + user: ctx.user, + }; + } + ), +}); diff --git a/apps/api/src/trpc.ts b/apps/api/src/trpc.ts index 93e9901..61bc0c7 100644 --- a/apps/api/src/trpc.ts +++ b/apps/api/src/trpc.ts @@ -1,7 +1,37 @@ -import { initTRPC } from "@trpc/server"; +import { initTRPC, TRPCError } from "@trpc/server"; import type { Context } from "./context.js"; +import { verifyToken } from "./utils/auth.js"; const t = initTRPC.context().create(); +const isAuthed = t.middleware(async ({ ctx, next }) => { + const authHeader = ctx.req.headers.authorization; + + if (!authHeader || !authHeader.startsWith("Bearer ")) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Missing or invalid authorization header", + }); + } + + const token = authHeader.substring(7); + + try { + const user = await verifyToken(token); + return next({ + ctx: { + ...ctx, + user, + }, + }); + } catch (error) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Invalid or expired token", + }); + } +}); + export const router = t.router; export const publicProcedure = t.procedure; +export const protectedProcedure:any = t.procedure.use(isAuthed); diff --git a/apps/api/src/utils/auth.ts b/apps/api/src/utils/auth.ts new file mode 100644 index 0000000..0155685 --- /dev/null +++ b/apps/api/src/utils/auth.ts @@ -0,0 +1,34 @@ +import jwt from "jsonwebtoken"; +import prisma from "../prisma.js"; + +const JWT_SECRET = process.env.JWT_SECRET! as string; + +if (!process.env.JWT_SECRET) { + throw new Error("JWT_SECRET is not defined in the environment variables"); +} + +export const generateToken = (email: string): string => { + return jwt.sign({ email }, JWT_SECRET, { expiresIn: "48h" }); +}; + +export const verifyToken = async (token: string) => { + try { + const decoded = jwt.verify(token, JWT_SECRET); + + if (typeof decoded === "string") { + throw new Error("Invalid token payload"); + } + + const user = await prisma.prisma.user.findUnique({ + where: { email: decoded.email }, + }); + + if (!user) { + throw new Error("User not found"); + } + + return user; + } catch (error) { + throw new Error("Token verification failed"); + } +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87d0251..e9315e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: helmet: specifier: ^7.2.0 version: 7.2.0 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 zod: specifier: ^4.1.9 version: 4.1.9 From df3222d581fbc93b82d476b8287456e3405a9007 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Wed, 1 Oct 2025 17:20:47 +0530 Subject: [PATCH 08/22] feat: initalize trpc in frontend --- apps/web/package.json | 4 + apps/web/src/app/layout.tsx | 23 ++--- .../components/landing-sections/Brands.tsx | 89 +++++++++++++++---- apps/web/src/lib/trpc.ts | 4 + apps/web/src/providers/trpc-provider.tsx | 41 +++++++++ pnpm-lock.yaml | 82 ++++++++++++----- 6 files changed, 195 insertions(+), 48 deletions(-) create mode 100644 apps/web/src/lib/trpc.ts create mode 100644 apps/web/src/providers/trpc-provider.tsx diff --git a/apps/web/package.json b/apps/web/package.json index ae42688..6666c4d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,6 +16,10 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-radio-group": "^1.2.1", "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-query": "^5.90.2", + "@trpc/client": "^11.6.0", + "@trpc/react-query": "^11.6.0", + "@trpc/server": "^11.5.1", "@vercel/analytics": "^1.4.1", "@vercel/speed-insights": "^1.1.0", "class-variance-authority": "^0.7.0", diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index ab6f4c0..ced9511 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -9,6 +9,7 @@ import { getServerSession } from "next-auth/next"; import { authConfig } from "@/lib/auth/config"; import { SessionWrapper } from "./SessionWrapper"; import SupportDropdown from "@/components/landing-sections/SupportDropdown"; +import { TRPCProvider } from "@/providers/trpc-provider"; const dmReg = localFont({ src: "./fonts/DMMono-Regular.ttf", @@ -40,16 +41,18 @@ export default async function RootLayout({ - - - {children} - - + + + + {children} + + + diff --git a/apps/web/src/components/landing-sections/Brands.tsx b/apps/web/src/components/landing-sections/Brands.tsx index 41869b2..dd7c6bd 100644 --- a/apps/web/src/components/landing-sections/Brands.tsx +++ b/apps/web/src/components/landing-sections/Brands.tsx @@ -1,22 +1,75 @@ -import React from 'react' -import Header from '../ui/header' +"use client"; + +import React from "react"; +import Header from "../ui/header"; +import { trpc } from "@/lib/trpc"; const Brands = () => { - return ( -
-
-
-
- - -
-
- - -
-
+ const { data: queryData, isLoading: queryLoading } = + trpc.query.count.useQuery(); + + const { data: userData, isLoading: userLoading } = trpc.user.count.useQuery(); + + const formatNumber = (num: number | string) => { + const number = typeof num === "string" ? parseInt(num) : num; + return `${number.toLocaleString()}+`; + }; + + const queryCount = queryData?.total_queries + ? formatNumber(queryData.total_queries) + : "0+"; + + const userCount = userData?.total_users + ? formatNumber(userData.total_users) + : "0+"; + + return ( +
+
+
+
+ {queryLoading ? ( + + Loading... + + ) : ( + + {queryCount} + + )} + +
+
+ {userLoading ? ( + + Loading... + + ) : ( + + {userCount} + + )} +
- ) -} +
+
+ ); +}; -export default Brands \ No newline at end of file +export default Brands; diff --git a/apps/web/src/lib/trpc.ts b/apps/web/src/lib/trpc.ts new file mode 100644 index 0000000..a3b8713 --- /dev/null +++ b/apps/web/src/lib/trpc.ts @@ -0,0 +1,4 @@ +import { createTRPCReact } from "@trpc/react-query"; +import type { AppRouter } from "../../../api/src/routers/_app"; + +export const trpc = createTRPCReact(); diff --git a/apps/web/src/providers/trpc-provider.tsx b/apps/web/src/providers/trpc-provider.tsx new file mode 100644 index 0000000..39c9818 --- /dev/null +++ b/apps/web/src/providers/trpc-provider.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { httpBatchLink } from "@trpc/client"; +import { useState } from "react"; +import { trpc } from "@/lib/trpc"; + +export function TRPCProvider({ children }: { children: React.ReactNode }) { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, // 1 minute + }, + }, + }) + ); + + const [trpcClient] = useState(() => { + const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"; + const trpcUrl = baseUrl.endsWith("/trpc") ? baseUrl : `${baseUrl}/trpc`; + + return trpc.createClient({ + links: [ + httpBatchLink({ + url: trpcUrl, + async headers() { + return {}; + }, + }), + ], + }); + }); + + return ( + + {children} + + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9315e4..029feb4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -188,6 +188,18 @@ importers: '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.2.3(@types/react@18.3.23)(react@18.3.1) + '@tanstack/react-query': + specifier: ^5.90.2 + version: 5.90.2(react@18.3.1) + '@trpc/client': + specifier: ^11.6.0 + version: 11.6.0(@trpc/server@11.5.1(typescript@5.9.2))(typescript@5.9.2) + '@trpc/react-query': + specifier: ^11.6.0 + version: 11.6.0(@tanstack/react-query@5.90.2(react@18.3.1))(@trpc/client@11.6.0(@trpc/server@11.5.1(typescript@5.9.2))(typescript@5.9.2))(@trpc/server@11.5.1(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@trpc/server': + specifier: ^11.5.1 + version: 11.5.1(typescript@5.9.2) '@vercel/analytics': specifier: ^1.4.1 version: 1.5.0(next@15.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -1237,9 +1249,33 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@tanstack/query-core@5.90.2': + resolution: {integrity: sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==} + + '@tanstack/react-query@5.90.2': + resolution: {integrity: sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==} + peerDependencies: + react: ^18 || ^19 + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@trpc/client@11.6.0': + resolution: {integrity: sha512-DyWbYk2hd50BaVrXWVkaUnaSwgAF5g/lfBkXtkF1Aqlk6BtSzGUo3owPkgqQO2I5LwWy1+ra9TsSfBBvIZpTwg==} + peerDependencies: + '@trpc/server': 11.6.0 + typescript: '>=5.7.2' + + '@trpc/react-query@11.6.0': + resolution: {integrity: sha512-xljUCzROa23cC89SEd5fwbKiWrGus2NDwtg8zszPlsFvaByWW50Jx6y5sLPXhp/g1FBsEtCInNNhEEL0UCHwGw==} + peerDependencies: + '@tanstack/react-query': ^5.80.3 + '@trpc/client': 11.6.0 + '@trpc/server': 11.6.0 + react: '>=18.2.0' + react-dom: '>=18.2.0' + typescript: '>=5.7.2' + '@trpc/server@11.5.1': resolution: {integrity: sha512-KIDzHRS5m8U1ncPwjgtOtPWK9lNO0kYL7b+lnvKXRqowSAQIEC/z6y7g/dkt4Aqv3DKI/STLydt2/afrP1QrxQ==} peerDependencies: @@ -5453,8 +5489,29 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.8.1 + '@tanstack/query-core@5.90.2': {} + + '@tanstack/react-query@5.90.2(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.90.2 + react: 18.3.1 + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@trpc/client@11.6.0(@trpc/server@11.5.1(typescript@5.9.2))(typescript@5.9.2)': + dependencies: + '@trpc/server': 11.5.1(typescript@5.9.2) + typescript: 5.9.2 + + '@trpc/react-query@11.6.0(@tanstack/react-query@5.90.2(react@18.3.1))(@trpc/client@11.6.0(@trpc/server@11.5.1(typescript@5.9.2))(typescript@5.9.2))(@trpc/server@11.5.1(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + dependencies: + '@tanstack/react-query': 5.90.2(react@18.3.1) + '@trpc/client': 11.6.0(@trpc/server@11.5.1(typescript@5.9.2))(typescript@5.9.2) + '@trpc/server': 11.5.1(typescript@5.9.2) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + typescript: 5.9.2 + '@trpc/server@11.5.1(typescript@5.9.2)': dependencies: typescript: 5.9.2 @@ -6853,7 +6910,7 @@ snapshots: '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) @@ -6887,21 +6944,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1(supports-color@5.5.0) - eslint: 8.57.1 - get-tsconfig: 4.10.1 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.14 - unrs-resolver: 1.9.0 - optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -6928,7 +6970,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.9.0 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -6953,14 +6995,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.34.0(eslint@8.57.1)(typescript@5.9.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -7039,7 +7081,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From c8ea022b7dec37070f6899fc4db55321ac0c0428 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Wed, 1 Oct 2025 18:48:51 +0530 Subject: [PATCH 09/22] refactor: business logic --- apps/api/src/routers/auth.ts | 31 ++------ apps/api/src/routers/projects.ts | 90 ++--------------------- apps/api/src/routers/queries.ts | 19 ++--- apps/api/src/routers/user.ts | 17 ++--- apps/api/src/services/auth.service.ts | 47 ++++++++++++ apps/api/src/services/project.service.ts | 94 ++++++++++++++++++++++++ apps/api/src/services/query.service.ts | 16 ++++ apps/api/src/services/user.service.ts | 14 ++++ 8 files changed, 197 insertions(+), 131 deletions(-) create mode 100644 apps/api/src/services/auth.service.ts create mode 100644 apps/api/src/services/project.service.ts create mode 100644 apps/api/src/services/query.service.ts create mode 100644 apps/api/src/services/user.service.ts diff --git a/apps/api/src/routers/auth.ts b/apps/api/src/routers/auth.ts index b08c624..99ed39f 100644 --- a/apps/api/src/routers/auth.ts +++ b/apps/api/src/routers/auth.ts @@ -1,7 +1,7 @@ import { router, publicProcedure, protectedProcedure } from "../trpc.js"; import { z } from "zod"; -import { generateToken } from "../utils/auth.js"; import { TRPCError } from "@trpc/server"; +import { authService } from "../services/auth.service.js"; const googleAuthSchema = z.object({ email: z.string().email("Invalid email format"), @@ -14,27 +14,12 @@ export const authRouter = router({ .input(googleAuthSchema) .mutation(async ({ input, ctx }) => { try { - const { email, firstName, authMethod } = input; - - const user = await ctx.db.prisma.user.upsert({ - where: { email }, - update: { - lastLogin: new Date(), - }, - create: { - email, - firstName: firstName || "Opensox User", - authMethod: authMethod || "google", - lastLogin: new Date(), - }, - }); - - const token = generateToken(email); - - return { - user, - token, + const authInput = { + email: input.email, + firstName: input.firstName, + authMethod: input.authMethod, }; + return await authService.handleGoogleAuth(ctx.db.prisma, authInput); } catch (error) { if (process.env.NODE_ENV !== "production") { console.error("Google auth error:", error); @@ -47,9 +32,7 @@ export const authRouter = router({ }), getSession: protectedProcedure.query( async ({ ctx }: { ctx: { user: any } }) => { - return { - user: ctx.user, - }; + return authService.getSession(ctx.user); } ), }); diff --git a/apps/api/src/routers/projects.ts b/apps/api/src/routers/projects.ts index dd74b8a..ae908be 100644 --- a/apps/api/src/routers/projects.ts +++ b/apps/api/src/routers/projects.ts @@ -1,23 +1,7 @@ import { router, publicProcedure } from "../trpc.js"; -import dotenv from "dotenv"; -import { graphql } from "@octokit/graphql"; import { z } from "zod"; -import type { - FilterProps, - RepositoryProps, - GraphQLResponseProps, - OptionsTypesProps, -} from "@opensox/shared"; - -dotenv.config(); - -const GH_PAT = process.env.GITHUB_PERSONAL_ACCESS_TOKEN; - -const graphqlWithAuth = graphql.defaults({ - headers: { - authorization: `token ${GH_PAT}`, - }, -}); +import type { RepositoryProps } from "@opensox/shared"; +import { projectService } from "../services/project.service.js"; const filterPropsSchema = z.object({ language: z.string().optional(), @@ -54,73 +38,9 @@ export const projectRouter = router({ getGithubProjects: publicProcedure .input(inputSchema) .query(async ({ input }): Promise => { - const queryParts: string[] = []; - const filters = input.filters || {}; - const options = input.options || {}; - - if (filters.language) { - queryParts.push(`language:${filters.language}`); - } - - if (filters.stars) { - queryParts.push(`stars:${filters.stars.min}..${filters.stars.max}`); - } - - if (filters.forks) { - queryParts.push(`forks:${filters.forks.min}..${filters.forks.max}`); - } - - if (filters.pushed) { - queryParts.push(`pushed:${filters.pushed}`); - } - - if (filters.created) { - queryParts.push(`created:${filters.created}`); - } - - // Default fields to filter contributor friendly repos - - queryParts.push(`is:organization`); - queryParts.push(`is:public`); - queryParts.push(`fork:true`); - - const searchQueryString = queryParts.join(" "); - - const response: GraphQLResponseProps = await graphqlWithAuth( - ` - query($searchQuery: String!, $first: Int!) { - search( - query: $searchQuery, - type: REPOSITORY, - first: $first - ) { - nodes { - ... on Repository { - id - name - description - url - owner { - avatarUrl - } - issues(states: OPEN) { - totalCount - } - primaryLanguage { - name - } - } - } - repositoryCount - } - } - `, - { - searchQuery: searchQueryString, - first: options.per_page || 100, - } + return await projectService.fetchGithubProjects( + input.filters as any, + input.options as any ); - - return response.search.nodes; }), }); diff --git a/apps/api/src/routers/queries.ts b/apps/api/src/routers/queries.ts index 52cac39..14edd58 100644 --- a/apps/api/src/routers/queries.ts +++ b/apps/api/src/routers/queries.ts @@ -1,14 +1,9 @@ -import { router, publicProcedure } from '../trpc.js'; +import { router, publicProcedure } from "../trpc.js"; +import { queryService } from "../services/query.service.js"; export const queryRouter = router({ - // get the total count of fetched queries - count: publicProcedure - .query(async ({ ctx }) => { - const queryCount = await ctx.db.prisma.queryCount.findUnique({ - where: { id: 1 } - }); - return { - total_queries: queryCount?.total_queries.toString() || "0" - }; - }) -}) \ No newline at end of file + // get the total count of fetched queries + count: publicProcedure.query(async ({ ctx }) => { + return await queryService.getQueryCount(ctx.db.prisma); + }), +}); diff --git a/apps/api/src/routers/user.ts b/apps/api/src/routers/user.ts index 6b46c6a..ba0a404 100644 --- a/apps/api/src/routers/user.ts +++ b/apps/api/src/routers/user.ts @@ -1,12 +1,9 @@ -import { router, publicProcedure } from '../trpc.js'; +import { router, publicProcedure } from "../trpc.js"; +import { userService } from "../services/user.service.js"; export const userRouter = router({ - // get the total count of users - count: publicProcedure - .query(async ({ ctx }) => { - const userCount = await ctx.db.prisma.user.count(); - return { - total_users: userCount - }; - }) -}) \ No newline at end of file + // get the total count of users + count: publicProcedure.query(async ({ ctx }) => { + return await userService.getUserCount(ctx.db.prisma); + }), +}); diff --git a/apps/api/src/services/auth.service.ts b/apps/api/src/services/auth.service.ts new file mode 100644 index 0000000..1da9c06 --- /dev/null +++ b/apps/api/src/services/auth.service.ts @@ -0,0 +1,47 @@ +import { generateToken } from "../utils/auth.js"; +import { PrismaClient } from "@prisma/client"; + +interface GoogleAuthInput { + email: string; + firstName?: string | undefined; + authMethod?: string | undefined; +} + +export const authService = { + /** + * Handle Google authentication + * Creates or updates user and generates JWT token + */ + async handleGoogleAuth(prisma: PrismaClient, input: GoogleAuthInput) { + const { email, firstName, authMethod } = input; + + const user = await prisma.user.upsert({ + where: { email }, + update: { + lastLogin: new Date(), + }, + create: { + email, + firstName: firstName || "Opensox User", + authMethod: authMethod || "google", + lastLogin: new Date(), + }, + }); + + const token = generateToken(email); + + return { + user, + token, + }; + }, + + /** + * Get user session information + */ + getSession(user: any) { + return { + user, + }; + }, +}; diff --git a/apps/api/src/services/project.service.ts b/apps/api/src/services/project.service.ts new file mode 100644 index 0000000..7ae4d5e --- /dev/null +++ b/apps/api/src/services/project.service.ts @@ -0,0 +1,94 @@ +import { graphql } from "@octokit/graphql"; +import dotenv from "dotenv"; +import type { + FilterProps, + RepositoryProps, + GraphQLResponseProps, + OptionsTypesProps, +} from "@opensox/shared"; + +dotenv.config(); + +const GH_PAT = process.env.GITHUB_PERSONAL_ACCESS_TOKEN; + +const graphqlWithAuth = graphql.defaults({ + headers: { + authorization: `token ${GH_PAT}`, + }, +}); + +export const projectService = { + /** + * Fetch GitHub repositories based on filters and options + */ + async fetchGithubProjects( + filters: Partial = {}, + options: Partial = {} + ): Promise { + const queryParts: string[] = []; + + if (filters.language) { + queryParts.push(`language:${filters.language}`); + } + + if (filters.stars) { + queryParts.push(`stars:${filters.stars.min}..${filters.stars.max}`); + } + + if (filters.forks) { + queryParts.push(`forks:${filters.forks.min}..${filters.forks.max}`); + } + + if (filters.pushed) { + queryParts.push(`pushed:${filters.pushed}`); + } + + if (filters.created) { + queryParts.push(`created:${filters.created}`); + } + + // Default fields to filter contributor friendly repos + queryParts.push(`is:organization`); + queryParts.push(`is:public`); + queryParts.push(`fork:true`); + + const searchQueryString = queryParts.join(" "); + + const response: GraphQLResponseProps = await graphqlWithAuth( + ` + query($searchQuery: String!, $first: Int!) { + search( + query: $searchQuery, + type: REPOSITORY, + first: $first + ) { + nodes { + ... on Repository { + id + name + description + url + owner { + avatarUrl + } + issues(states: OPEN) { + totalCount + } + primaryLanguage { + name + } + } + } + repositoryCount + } + } + `, + { + searchQuery: searchQueryString, + first: options.per_page || 100, + } + ); + + return response.search.nodes; + }, +}; diff --git a/apps/api/src/services/query.service.ts b/apps/api/src/services/query.service.ts new file mode 100644 index 0000000..be9e22c --- /dev/null +++ b/apps/api/src/services/query.service.ts @@ -0,0 +1,16 @@ +import { PrismaClient } from "@prisma/client"; + +export const queryService = { + /** + * Get total count of queries + */ + async getQueryCount(prisma: PrismaClient) { + const queryCount = await prisma.queryCount.findUnique({ + where: { id: 1 }, + }); + + return { + total_queries: queryCount?.total_queries.toString() || "0", + }; + }, +}; diff --git a/apps/api/src/services/user.service.ts b/apps/api/src/services/user.service.ts new file mode 100644 index 0000000..fc5b731 --- /dev/null +++ b/apps/api/src/services/user.service.ts @@ -0,0 +1,14 @@ +import { PrismaClient } from "@prisma/client"; + +export const userService = { + /** + * Get total count of users + */ + async getUserCount(prisma: PrismaClient) { + const userCount = await prisma.user.count(); + + return { + total_users: userCount, + }; + }, +}; From 7101b7c0c64f75fdda41576fc36f5373673282d5 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Wed, 1 Oct 2025 18:56:51 +0530 Subject: [PATCH 10/22] feat: increment the query --- apps/api/src/routers/projects.ts | 4 +++- apps/api/src/services/query.service.ts | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/apps/api/src/routers/projects.ts b/apps/api/src/routers/projects.ts index ae908be..f82f767 100644 --- a/apps/api/src/routers/projects.ts +++ b/apps/api/src/routers/projects.ts @@ -2,6 +2,7 @@ import { router, publicProcedure } from "../trpc.js"; import { z } from "zod"; import type { RepositoryProps } from "@opensox/shared"; import { projectService } from "../services/project.service.js"; +import { queryService } from "../services/query.service.js"; const filterPropsSchema = z.object({ language: z.string().optional(), @@ -37,7 +38,8 @@ const inputSchema = z.object({ export const projectRouter = router({ getGithubProjects: publicProcedure .input(inputSchema) - .query(async ({ input }): Promise => { + .query(async ({ input, ctx }): Promise => { + await queryService.incrementQueryCount(ctx.db.prisma); return await projectService.fetchGithubProjects( input.filters as any, input.options as any diff --git a/apps/api/src/services/query.service.ts b/apps/api/src/services/query.service.ts index be9e22c..458e741 100644 --- a/apps/api/src/services/query.service.ts +++ b/apps/api/src/services/query.service.ts @@ -13,4 +13,25 @@ export const queryService = { total_queries: queryCount?.total_queries.toString() || "0", }; }, + + /** + * Increment the query count by 1 + */ + async incrementQueryCount(prisma: PrismaClient): Promise { + try { + const updatedCount = await prisma.queryCount.update({ + where: { id: 1 }, + data: { + total_queries: { + increment: 1, + }, + }, + }); + if (process.env.NODE_ENV === "development") { + console.log("Updated Query Count:", updatedCount); + } + } catch (error) { + console.error("Error updating query count:", error); + } + }, }; From 1285d8aa4b770304c8b09cb85ae5765f925e09fe Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Wed, 1 Oct 2025 19:03:23 +0530 Subject: [PATCH 11/22] feat: protect routes --- apps/api/src/routers/projects.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/src/routers/projects.ts b/apps/api/src/routers/projects.ts index f82f767..f3006d9 100644 --- a/apps/api/src/routers/projects.ts +++ b/apps/api/src/routers/projects.ts @@ -1,4 +1,4 @@ -import { router, publicProcedure } from "../trpc.js"; +import { router, protectedProcedure } from "../trpc.js"; import { z } from "zod"; import type { RepositoryProps } from "@opensox/shared"; import { projectService } from "../services/project.service.js"; @@ -36,9 +36,9 @@ const inputSchema = z.object({ }); export const projectRouter = router({ - getGithubProjects: publicProcedure + getGithubProjects: protectedProcedure .input(inputSchema) - .query(async ({ input, ctx }): Promise => { + .query(async ({ input, ctx }: any): Promise => { await queryService.incrementQueryCount(ctx.db.prisma); return await projectService.fetchGithubProjects( input.filters as any, From cceb43d2b014950b68e2f7d07ded658142646805 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Wed, 1 Oct 2025 19:23:38 +0530 Subject: [PATCH 12/22] feat: use trpc for auth --- apps/web/src/app/layout.tsx | 24 +++++++------- apps/web/src/lib/auth/config.ts | 42 +++++++++--------------- apps/web/src/lib/trpc-server.ts | 16 +++++++++ apps/web/src/providers/trpc-provider.tsx | 8 +++++ 4 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 apps/web/src/lib/trpc-server.ts diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index ced9511..bdba95f 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -41,18 +41,18 @@ export default async function RootLayout({ - - - - {children} - - - + + + + {children} + + + diff --git a/apps/web/src/lib/auth/config.ts b/apps/web/src/lib/auth/config.ts index f6ff2f8..b8763ad 100644 --- a/apps/web/src/lib/auth/config.ts +++ b/apps/web/src/lib/auth/config.ts @@ -1,5 +1,6 @@ import type { NextAuthOptions } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; +import { serverTrpc } from "../trpc-server"; export const authConfig: NextAuthOptions = { providers: [ @@ -11,20 +12,12 @@ export const authConfig: NextAuthOptions = { callbacks: { async signIn({ user, profile }) { try { - const response = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/api/auth/google`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - email: user.email, - firstName: profile?.name, - authMethod: "google", - }), - } - ); + await serverTrpc.auth.googleAuth.mutate({ + email: user.email!, + firstName: profile?.name, + authMethod: "google", + }); - if (!response.ok) throw new Error("Failed to save user"); return true; } catch (error) { console.error("Sign-in error:", error); @@ -42,21 +35,16 @@ export const authConfig: NextAuthOptions = { async jwt({ token, account, user }) { if (account && user) { - const response = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/api/auth/google`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - email: user.email, - firstName: user.name, - authMethod: "google", - }), - } - ); - if (response.ok) { - const data = await response.json(); + try { + const data = await serverTrpc.auth.googleAuth.mutate({ + email: user.email!, + firstName: user.name ?? undefined, + authMethod: "google", + }); + token.jwtToken = data.token; + } catch (error) { + console.error("JWT token error:", error); } } return token; diff --git a/apps/web/src/lib/trpc-server.ts b/apps/web/src/lib/trpc-server.ts new file mode 100644 index 0000000..66e774a --- /dev/null +++ b/apps/web/src/lib/trpc-server.ts @@ -0,0 +1,16 @@ +import { createTRPCProxyClient, httpBatchLink } from "@trpc/client"; +import type { AppRouter } from "../../../api/src/routers/_app"; + +/** + * Server-side tRPC client for use in NextAuth callbacks and server components + */ +export const serverTrpc = createTRPCProxyClient({ + links: [ + httpBatchLink({ + url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"}/trpc`, + headers() { + return {}; + }, + }), + ], +}); diff --git a/apps/web/src/providers/trpc-provider.tsx b/apps/web/src/providers/trpc-provider.tsx index 39c9818..12ff6b2 100644 --- a/apps/web/src/providers/trpc-provider.tsx +++ b/apps/web/src/providers/trpc-provider.tsx @@ -3,9 +3,11 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { httpBatchLink } from "@trpc/client"; import { useState } from "react"; +import { useSession } from "next-auth/react"; import { trpc } from "@/lib/trpc"; export function TRPCProvider({ children }: { children: React.ReactNode }) { + const session = useSession(); const [queryClient] = useState( () => new QueryClient({ @@ -26,6 +28,12 @@ export function TRPCProvider({ children }: { children: React.ReactNode }) { httpBatchLink({ url: trpcUrl, async headers() { + const token = (session.data as any)?.accessToken; + if (token) { + return { + authorization: `Bearer ${token}`, + }; + } return {}; }, }), From 8ca6bc4a7200844c3b9c879efc3442e8057e0594 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Wed, 1 Oct 2025 19:31:30 +0530 Subject: [PATCH 13/22] feat: use trpc call to fetch projects --- apps/web/src/hooks/useGetProjects.ts | 37 +++++++++------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/apps/web/src/hooks/useGetProjects.ts b/apps/web/src/hooks/useGetProjects.ts index 9f8f21a..70a37c4 100644 --- a/apps/web/src/hooks/useGetProjects.ts +++ b/apps/web/src/hooks/useGetProjects.ts @@ -1,37 +1,24 @@ import { useCallback } from "react"; import { FilterProps, RepositoryProps } from "@opensox/shared/types"; -import { CONFIG } from "@/utils/config"; -import { useSession } from "next-auth/react"; +import { trpc } from "@/lib/trpc"; export const useGetProjects = () => { - const { data: session } = useSession(); + const utils = trpc.useUtils(); const func = useCallback( async (filters: FilterProps): Promise => { - if (!session?.accessToken) { - throw new Error("No authentication token available"); - } - - const response = await fetch( - `${CONFIG.BASE_URL}/api/projects/get_projects`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.accessToken}`, - }, - body: JSON.stringify(filters), - } - ); - - if (!response.ok) { - throw new Error("Failed to fetch projects"); - } - - const data: RepositoryProps[] = await response.json(); + const data = await (utils.client.project.getGithubProjects as any).query({ + filters: filters as any, + options: { + sort: "stars" as const, + order: "desc" as const, + per_page: 30, + page: 1, + }, + }); return data; }, - [session?.accessToken] + [utils] ); return func; }; From ed67ee2c51c2734fcd82ffdaf2a0977c4e662626 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 00:54:31 +0530 Subject: [PATCH 14/22] dockerize: api --- .dockerignore | 60 +++++++++++++++++++++++++++++++ apps/api/Dockerfile | 68 ++++++++++++++++++++++++++++++++--- apps/api/package.json | 2 ++ apps/api/src/index.ts | 2 +- packages/shared/tsconfig.json | 14 +++++--- 5 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1f869dc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,60 @@ +# Dependencies +node_modules +**/node_modules + +# Build outputs +dist +**/dist +.next +**/out + +# Testing +coverage +**/.coverage + +# Environment files +.env +.env.* +!.env.example + +# Git +.git +.gitignore +**/.git + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Misc +README.md +**/README.md +LICENSE +*.md +!apps/api/src/**/*.md + +# Apps not needed for API +apps/web +apps/backend +apps/docs + +# Turbo +.turbo +**/.turbo +turbo.json + diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 3249f40..657f920 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -1,18 +1,78 @@ -FROM node:20 +FROM node:20 AS builder WORKDIR /app +# Install pnpm +RUN npm install -g pnpm + +# Copy workspace configuration files +COPY pnpm-workspace.yaml ./ COPY package.json ./ +COPY pnpm-lock.yaml* ./ + +# Copy package.json files for all workspaces (for better layer caching) +COPY apps/api/package.json ./apps/api/ +COPY packages/shared/package.json ./packages/shared/ + +# Install dependencies for entire workspace +RUN pnpm install + +# Copy shared package source (types directory contains the actual source files) +COPY packages/shared/types ./packages/shared/types +COPY packages/shared/src ./packages/shared/src +COPY packages/shared/tsconfig.json ./packages/shared/tsconfig.json + +# Copy API source and config +COPY apps/api/src ./apps/api/src +COPY apps/api/tsconfig.json ./apps/api/ +COPY apps/api/prisma ./apps/api/prisma + +# Build shared package first +RUN cd packages/shared && pnpm run build + +# Generate Prisma client +RUN cd apps/api && pnpm exec prisma generate +# Build API +RUN cd apps/api && pnpm run build + +# Production stage +FROM node:20-slim + +WORKDIR /app + +# Install OpenSSL and other dependencies required by Prisma +RUN apt-get update -y && \ + apt-get install -y openssl ca-certificates && \ + rm -rf /var/lib/apt/lists/* + +# Install pnpm RUN npm install -g pnpm +# Copy workspace configuration +COPY pnpm-workspace.yaml ./ +COPY package.json ./ +COPY pnpm-lock.yaml* ./ + +# Copy package.json files +COPY apps/api/package.json ./apps/api/ +COPY packages/shared/package.json ./packages/shared/ + +# Copy built artifacts from builder +COPY --from=builder /app/apps/api/dist ./apps/api/dist +COPY --from=builder /app/packages/shared/dist ./packages/shared/dist +COPY --from=builder /app/apps/api/prisma ./apps/api/prisma + +# Install all dependencies (needed for Prisma generation) RUN pnpm install -COPY src ./src -COPY tsconfig.json ./ +# Generate Prisma client in production stage +RUN cd apps/api && pnpm exec prisma generate +# Remove devDependencies after Prisma generation +RUN pnpm install --prod -RUN pnpm run build +WORKDIR /app/apps/api EXPOSE 4000 diff --git a/apps/api/package.json b/apps/api/package.json index 0fa085c..9bbf908 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -16,6 +16,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.7", "@types/node": "^24.5.1", "prisma": "^5.22.0", "tsx": "^4.20.3", @@ -23,6 +24,7 @@ }, "dependencies": { "@octokit/graphql": "^9.0.1", + "@opensox/shared": "workspace:*", "@prisma/client": "^5.22.0", "@trpc/server": "^11.5.1", "cors": "^2.8.5", diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index e7cd3d3..598af42 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -2,7 +2,7 @@ import dotenv from "dotenv"; import express from "express"; import type { Request, Response } from "express"; import * as trpcExpress from "@trpc/server/adapters/express"; -import { appRouter } from "../src/routers/_app.js"; +import { appRouter } from "./routers/_app.js"; import { createContext } from "./context.js"; import prismaModule from "./prisma.js"; import cors from "cors"; diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 1069fdf..902b957 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -1,20 +1,24 @@ { "compilerOptions": { "target": "es2020", - "module": "ESNext", + "module": "commonjs", + "moduleResolution": "node", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./dist", "declaration": true, - "sourceMap": true + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true }, "include": [ - "src/**/*" -, "types/index.ts" ], + "src/**/*", + "types/**/*" + ], "exclude": [ "node_modules", "dist" ] -} \ No newline at end of file +} From 2c0781074d2d784f750488e15fda10f87c5da68f Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 13:09:12 +0530 Subject: [PATCH 15/22] replace the dockerfile --- apps/api/Dockerfile => Dockerfile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/api/Dockerfile => Dockerfile (100%) diff --git a/apps/api/Dockerfile b/Dockerfile similarity index 100% rename from apps/api/Dockerfile rename to Dockerfile From 4ac3a67e2ba2a30f1119e7aa9c1dd12b76032a8b Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 13:19:41 +0530 Subject: [PATCH 16/22] chore: remove unused src --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 657f920..334561a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,6 @@ RUN pnpm install # Copy shared package source (types directory contains the actual source files) COPY packages/shared/types ./packages/shared/types -COPY packages/shared/src ./packages/shared/src COPY packages/shared/tsconfig.json ./packages/shared/tsconfig.json # Copy API source and config From 5a7a94f3b81e6abc3eefbc9fdfee4a2fb405981a Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 13:34:45 +0530 Subject: [PATCH 17/22] chore: fix build error --- Dockerfile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 334561a..a1b37fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,13 +27,15 @@ COPY apps/api/tsconfig.json ./apps/api/ COPY apps/api/prisma ./apps/api/prisma # Build shared package first -RUN cd packages/shared && pnpm run build +WORKDIR /app/packages/shared +RUN pnpm run build # Generate Prisma client -RUN cd apps/api && pnpm exec prisma generate +WORKDIR /app/apps/api +RUN pnpm exec prisma generate # Build API -RUN cd apps/api && pnpm run build +RUN pnpm run build # Production stage FROM node:20-slim @@ -66,7 +68,8 @@ COPY --from=builder /app/apps/api/prisma ./apps/api/prisma RUN pnpm install # Generate Prisma client in production stage -RUN cd apps/api && pnpm exec prisma generate +WORKDIR /app/apps/api +RUN pnpm exec prisma generate # Remove devDependencies after Prisma generation RUN pnpm install --prod From 2cde7b454c1d853d2cf6805e885218da4bc1f005 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 13:48:35 +0530 Subject: [PATCH 18/22] chore: fix build error --- railway.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/railway.toml b/railway.toml index 55b9f6f..ced4455 100644 --- a/railway.toml +++ b/railway.toml @@ -1,6 +1,3 @@ [build] -builder = "nixpacks" -buildCommand = "cd apps/backend && npm install && npm run build" - -[deploy] -startCommand = "cd apps/backend && npm run deploy && npm start" \ No newline at end of file +builder = "DOCKERFILE" +dockerfilePath = "Dockerfile" \ No newline at end of file From 761378a311a3348c1d2c7967a7b349596d484cf9 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 14:47:56 +0530 Subject: [PATCH 19/22] chore: fix pnpm build error --- pnpm-lock.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 029feb4..d890ba4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@octokit/graphql': specifier: ^9.0.1 version: 9.0.1 + '@opensox/shared': + specifier: workspace:* + version: link:../../packages/shared '@prisma/client': specifier: ^5.22.0 version: 5.22.0(prisma@5.22.0) @@ -57,6 +60,9 @@ importers: '@types/express': specifier: ^4.17.21 version: 4.17.23 + '@types/jsonwebtoken': + specifier: ^9.0.7 + version: 9.0.9 '@types/node': specifier: ^24.5.1 version: 24.5.1 @@ -6891,7 +6897,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -6955,7 +6961,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.9.0 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -7041,7 +7047,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 From 8ba3c7115eba0eabd1921af1e6acd30e300c33d9 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 14:54:10 +0530 Subject: [PATCH 20/22] fix: prisma client error --- apps/api/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/api/package.json b/apps/api/package.json index 9bbf908..75eb087 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -7,7 +7,8 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "tsx src/index.ts", - "build": "tsc" + "build": "prisma generate && tsc", + "postinstall": "prisma generate" }, "keywords": [], "author": "Ajeet Pratpa Singh", From a31c77d725950faa60b3d4b3c23cf7b27450a77a Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 15:00:45 +0530 Subject: [PATCH 21/22] fix: prisma client error --- apps/api/package.json | 3 +-- packages/shared/package.json | 3 ++- vercel.json | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 vercel.json diff --git a/apps/api/package.json b/apps/api/package.json index 75eb087..1aee8ee 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -7,8 +7,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "tsx src/index.ts", - "build": "prisma generate && tsc", - "postinstall": "prisma generate" + "build": "prisma generate && tsc" }, "keywords": [], "author": "Ajeet Pratpa Singh", diff --git a/packages/shared/package.json b/packages/shared/package.json index 0d1d399..ba33c66 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -5,7 +5,8 @@ "types": "dist/index.d.ts", "scripts": { "build": "tsc", - "clean": "rm -rf dist" + "clean": "rm -rf dist", + "prepublishOnly": "pnpm run build" }, "devDependencies": { "typescript": "^4.9.5" diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..b6c8dac --- /dev/null +++ b/vercel.json @@ -0,0 +1,6 @@ +{ + "buildCommand": "pnpm turbo build --filter=web...", + "installCommand": "pnpm install --frozen-lockfile=false", + "framework": "nextjs" +} + From 65ab676a9196b2263826964d87de355e520fd2c4 Mon Sep 17 00:00:00 2001 From: apsinghdev Date: Thu, 2 Oct 2025 15:10:37 +0530 Subject: [PATCH 22/22] fix: prisma client error --- apps/api/package.json | 3 ++- turbo.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 1aee8ee..4e0703f 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -7,7 +7,8 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "tsx src/index.ts", - "build": "prisma generate && tsc" + "build": "prisma generate && tsc", + "postinstall": "[ -f prisma/schema.prisma ] && prisma generate || true" }, "keywords": [], "author": "Ajeet Pratpa Singh", diff --git a/turbo.json b/turbo.json index 807e324..af76cf6 100644 --- a/turbo.json +++ b/turbo.json @@ -5,7 +5,7 @@ "build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", ".env*"], - "outputs": [".next/**", "!.next/cache/**"] + "outputs": [".next/**", "!.next/cache/**", "dist/**"] }, "lint": { "dependsOn": ["^lint"]