From 3e51d6222f47a4254cce0dad62ef0fb0d4ddc7d6 Mon Sep 17 00:00:00 2001 From: gamvo74 Date: Wed, 25 Feb 2026 00:30:57 -0500 Subject: [PATCH] Final production build for proselitigant.tech --- .env.example | 3 +- .github/README.md | Bin 0 -> 40 bytes .github/workflows/README.md | Bin 0 -> 40 bytes .github/workflows/deploy-api.yml | 56 ++ .github/workflows/deploy-vps.yml | 25 + .github/workflows/deploy-web.yml | 56 ++ .gitignore | 37 ++ README.md | 63 +- apps/api/README.md | 8 +- apps/api/package-lock.json | 554 +----------------- apps/api/package.json | 12 +- apps/api/prisma/schema.prisma | 126 +++- apps/api/src/ai/ai.controller.ts | 22 + apps/api/src/ai/ai.module.ts | 11 + apps/api/src/ai/ai.service.ts | 89 +++ apps/api/src/app.module.ts | 54 +- apps/api/src/auth/auth.controller.ts | 5 +- apps/api/src/auth/auth.service.ts | 8 +- apps/api/src/auth/subscription.guard.ts | 41 ++ .../api/src/documents/documents.controller.ts | 25 + apps/api/src/documents/documents.module.ts | 10 + apps/api/src/documents/documents.service.ts | 102 ++++ apps/api/src/main.ts | 39 +- apps/api/src/matters/matters.controller.ts | 46 +- apps/api/src/matters/matters.module.ts | 3 +- apps/api/src/matters/matters.service.ts | 71 ++- .../medical-chronology.controller.ts | 20 + .../medical-chronology.module.ts | 10 + .../medical-chronology.service.ts | 17 + .../src/mock-trial/mock-trial.controller.ts | 20 + apps/api/src/mock-trial/mock-trial.module.ts | 10 + apps/api/src/mock-trial/mock-trial.service.ts | 17 + apps/api/src/prisma.service.ts | 11 - apps/web/app/ai-assistant/page.tsx | 139 +++++ apps/web/app/layout.tsx | 32 +- apps/web/app/matters/page.tsx | 89 +++ apps/web/app/mock-trial/page.tsx | 95 +++ apps/web/app/page.tsx | 127 +++- apps/web/app/subscription/page.tsx | 124 ++++ apps/web/app/transcribe/page.tsx | 221 +++++++ apps/web/components/Sidebar.tsx | 80 +++ apps/web/package-lock.json | 33 +- apps/web/package.json | 5 +- deploy.sh | 23 + docker-compose.prod.yml | 55 ++ docker-compose.yml | 2 +- infrastructure/nginx/conf.d/api.conf | 3 +- infrastructure/nginx/conf.d/default.conf | 23 + infrastructure/nginx/conf.d/web.conf | 3 +- package.json/package.json => package.json | 0 50 files changed, 1895 insertions(+), 730 deletions(-) create mode 100644 .github/README.md create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/deploy-api.yml create mode 100644 .github/workflows/deploy-vps.yml create mode 100644 .github/workflows/deploy-web.yml create mode 100644 .gitignore create mode 100644 apps/api/src/ai/ai.controller.ts create mode 100644 apps/api/src/ai/ai.module.ts create mode 100644 apps/api/src/ai/ai.service.ts create mode 100644 apps/api/src/auth/subscription.guard.ts create mode 100644 apps/api/src/documents/documents.controller.ts create mode 100644 apps/api/src/documents/documents.module.ts create mode 100644 apps/api/src/documents/documents.service.ts create mode 100644 apps/api/src/medical-chronology/medical-chronology.controller.ts create mode 100644 apps/api/src/medical-chronology/medical-chronology.module.ts create mode 100644 apps/api/src/medical-chronology/medical-chronology.service.ts create mode 100644 apps/api/src/mock-trial/mock-trial.controller.ts create mode 100644 apps/api/src/mock-trial/mock-trial.module.ts create mode 100644 apps/api/src/mock-trial/mock-trial.service.ts create mode 100644 apps/web/app/ai-assistant/page.tsx create mode 100644 apps/web/app/matters/page.tsx create mode 100644 apps/web/app/mock-trial/page.tsx create mode 100644 apps/web/app/subscription/page.tsx create mode 100644 apps/web/app/transcribe/page.tsx create mode 100644 apps/web/components/Sidebar.tsx create mode 100644 deploy.sh create mode 100644 docker-compose.prod.yml create mode 100644 infrastructure/nginx/conf.d/default.conf rename package.json/package.json => package.json (100%) diff --git a/.env.example b/.env.example index 82c984c..83a7b6d 100644 --- a/.env.example +++ b/.env.example @@ -2,11 +2,10 @@ POSTGRES_USER=myuser POSTGRES_PASSWORD=mypassword POSTGRES_DB=pro_se_litigant -DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?schema=public # API Configuration PORT=4000 -CORS_ORIGIN=https://app.proselitigant.com +CORS_ORIGIN=https://proselitigant.tech NODE_ENV=production # Rate Limiting diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000000000000000000000000000000000000..40d6bd4276f0750d1c4f03a1b586f859b18f7b5a GIT binary patch literal 40 rcmezWPnki1p@5-?A)i5)p_m~RNaiqPGL!&uIzu8u9+1V$z{LOn%v1 + cd pro-se-litigant + ``` + +2. **Create a `.env` file** based on `.env.example`. + ```bash + cp .env.example .env + nano .env + ``` + **Critical variables to set:** + * `DOCKER_IMAGE_OWNER`: Your GitHub username (lowercase). + * `POSTGRES_USER`: Database username. + * `POSTGRES_PASSWORD`: Database password. + * `JWT_SECRET`: A secure random string. + * `CORS_ORIGIN`: Your frontend URL (e.g., `http://your-domain.com`). + * `NEXT_PUBLIC_API_URL`: Your API URL (e.g., `http://your-domain.com/api`). + +3. **Login to GitHub Container Registry (GHCR)** + You need a Personal Access Token (classic) with `read:packages` scope. + ```bash + echo | docker login ghcr.io -u --password-stdin + ``` + +4. **Initial Deployment** + Run the deployment script: + ```bash + chmod +x deploy.sh + ./deploy.sh + ``` + +## CI/CD (GitHub Actions) + +The repository includes workflows to automatically build and push Docker images to GHCR on every push to `main`. + +* `.github/workflows/deploy-web.yml`: Builds `pro-se-litigant-web` +* `.github/workflows/deploy-api.yml`: Builds `pro-se-litigant-api` + +### Automatic Deployment (Optional) + +To enable automatic deployment to your VPS after build: + +1. Add the following secrets to your GitHub Repository (Settings -> Secrets and variables -> Actions): + * `VPS_HOST`: IP address of your VPS. + * `VPS_USERNAME`: SSH username (e.g., `root` or `ubuntu`). + * `VPS_SSH_KEY`: Your private SSH key. + +2. Create a new workflow `.github/workflows/deploy-vps.yml` that uses `appleboy/ssh-action` to run `./deploy.sh` on your server. diff --git a/apps/api/README.md b/apps/api/README.md index 1325b50..8f0f65f 100644 --- a/apps/api/README.md +++ b/apps/api/README.md @@ -23,13 +23,7 @@ ## Description -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository for the Pro Se Litigant API. - -## Features Added -- **RBAC (Role-Based Access Control):** Uses `@Roles` decorator and global `RolesGuard` to manage access (USER/ADMIN). -- **Rate Limiting:** Global throttling enabled via `ThrottlerModule`. -- **Structured Logging:** Uses `nestjs-pino` with custom request ID correlation. -- **CI/CD:** Automated deployment to AWS ECS with GitHub Actions. +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Project setup diff --git a/apps/api/package-lock.json b/apps/api/package-lock.json index 0aed54b..cf83b9a 100644 --- a/apps/api/package-lock.json +++ b/apps/api/package-lock.json @@ -15,27 +15,19 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", - "@nestjs/terminus": "^11.1.1", - "@nestjs/throttler": "^6.5.0", "@prisma/client": "^5.13.0", "aws-sdk": "^2.1693.0", "bcrypt": "^6.0.0", "bullmq": "^5.69.3", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.3", - "helmet": "^8.1.0", "ioredis": "^5.9.3", - "nestjs-pino": "^4.6.0", "openai": "^6.22.0", "passport": "^0.7.0", "passport-jwt": "^4.0.1", - "pino-http": "^11.0.0", "prisma": "^5.13.0", "prisma-generator-typescript-interfaces": "^3.1.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "stripe": "^20.3.1", - "uuid": "^11.0.5" + "stripe": "^20.3.1" }, "devDependencies": { "@elastic/elasticsearch": "^9.3.1", @@ -50,7 +42,6 @@ "@types/node": "^22.10.7", "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", - "@types/uuid": "^10.0.0", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", @@ -58,7 +49,6 @@ "install": "^0.13.0", "jest": "^30.0.0", "npm": "^11.10.0", - "pino-pretty": "^13.1.3", "prettier": "^3.4.2", "source-map-support": "^0.5.21", "supertest": "^7.0.0", @@ -2659,76 +2649,6 @@ "tslib": "^2.1.0" } }, - "node_modules/@nestjs/terminus": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@nestjs/terminus/-/terminus-11.1.1.tgz", - "integrity": "sha512-Ssql79H+EQY/Wg108eJqN4NiNsO/tLrj+qbzOWSQUf2JE4vJQ2RG3WTqUOrYjfjWmVHD3+Ys0+azed7LSMKScw==", - "license": "MIT", - "dependencies": { - "boxen": "5.1.2", - "check-disk-space": "3.4.0" - }, - "peerDependencies": { - "@grpc/grpc-js": "*", - "@grpc/proto-loader": "*", - "@mikro-orm/core": "*", - "@mikro-orm/nestjs": "*", - "@nestjs/axios": "^2.0.0 || ^3.0.0 || ^4.0.0", - "@nestjs/common": "^10.0.0 || ^11.0.0", - "@nestjs/core": "^10.0.0 || ^11.0.0", - "@nestjs/microservices": "^10.0.0 || ^11.0.0", - "@nestjs/mongoose": "^11.0.0", - "@nestjs/sequelize": "^10.0.0 || ^11.0.0", - "@nestjs/typeorm": "^10.0.0 || ^11.0.0", - "@prisma/client": "*", - "mongoose": "*", - "reflect-metadata": "0.1.x || 0.2.x", - "rxjs": "7.x", - "sequelize": "*", - "typeorm": "*" - }, - "peerDependenciesMeta": { - "@grpc/grpc-js": { - "optional": true - }, - "@grpc/proto-loader": { - "optional": true - }, - "@mikro-orm/core": { - "optional": true - }, - "@mikro-orm/nestjs": { - "optional": true - }, - "@nestjs/axios": { - "optional": true - }, - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/mongoose": { - "optional": true - }, - "@nestjs/sequelize": { - "optional": true - }, - "@nestjs/typeorm": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "mongoose": { - "optional": true - }, - "sequelize": { - "optional": true - }, - "typeorm": { - "optional": true - } - } - }, "node_modules/@nestjs/testing": { "version": "11.1.14", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.14.tgz", @@ -2757,17 +2677,6 @@ } } }, - "node_modules/@nestjs/throttler": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.5.0.tgz", - "integrity": "sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", - "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", - "reflect-metadata": "^0.1.13 || ^0.2.0" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2843,12 +2752,6 @@ "@noble/hashes": "^1.1.5" } }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3397,19 +3300,6 @@ "@types/superagent": "^8.1.0" } }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/validator": { - "version": "13.15.10", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", - "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", - "license": "MIT" - }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -4278,15 +4168,6 @@ "ajv": "^6.9.1" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -4317,6 +4198,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4326,6 +4208,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4463,15 +4346,6 @@ "dev": true, "license": "MIT" }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -4509,15 +4383,6 @@ "node": ">= 10.0.0" } }, - "node_modules/aws-sdk/node_modules/uuid": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/babel-jest": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", @@ -4729,69 +4594,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -4935,6 +4737,19 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/bullmq/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -5047,6 +4862,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5092,15 +4908,6 @@ "dev": true, "license": "MIT" }, - "node_modules/check-disk-space": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/check-disk-space/-/check-disk-space-3.4.0.tgz", - "integrity": "sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -5150,35 +4957,6 @@ "dev": true, "license": "MIT" }, - "node_modules/class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT" - }, - "node_modules/class-validator": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", - "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", - "license": "MIT", - "dependencies": { - "@types/validator": "^13.15.3", - "libphonenumber-js": "^1.11.1", - "validator": "^13.15.20" - } - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5305,6 +5083,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5317,12 +5096,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, "license": "MIT" }, @@ -5584,16 +5357,6 @@ "node": ">= 8" } }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -5841,6 +5604,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -5852,16 +5616,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { "version": "5.19.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", @@ -6303,13 +6057,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/fast-copy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz", - "integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6728,6 +6475,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -6938,6 +6686,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6994,22 +6743,6 @@ "node": ">= 0.4" } }, - "node_modules/helmet": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "dev": true, - "license": "MIT" - }, "node_modules/hpagent": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", @@ -7246,6 +6979,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8241,16 +7975,6 @@ "node": ">= 0.6.0" } }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8431,12 +8155,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libphonenumber-js": { - "version": "1.12.37", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.37.tgz", - "integrity": "sha512-rDU6bkpuMs8YRt/UpkuYEAsYSoNuDEbrE41I3KNvmXREGH6DGBJ8Wbak4by29wNOQ27zk4g4HL82zf0OGhwRuw==", - "license": "MIT" - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8979,21 +8697,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nestjs-pino": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/nestjs-pino/-/nestjs-pino-4.6.0.tgz", - "integrity": "sha512-MzSgnOu9MhRT/f7MsvoDnxat11D9JRJYwL1t+tI6J44UrNz9rUVDpceEh9VFsyfiiIJKUri5S+/snMOoaWh7YA==", - "license": "MIT", - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", - "pino": "^7.5.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", - "pino-http": "^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", - "rxjs": "^7.1.0" - } - }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -11193,15 +10896,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -11530,93 +11224,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pino": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", - "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^4.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", - "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-http": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-11.0.0.tgz", - "integrity": "sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==", - "license": "MIT", - "dependencies": { - "get-caller-file": "^2.0.5", - "pino": "^10.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0" - } - }, - "node_modules/pino-pretty": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", - "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^4.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pump": "^3.0.0", - "secure-json-parse": "^4.0.0", - "sonic-boom": "^4.0.1", - "strip-json-comments": "^5.0.2" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "node_modules/pino-pretty/node_modules/strip-json-comments": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -11813,22 +11420,6 @@ "prisma-generator-typescript-interfaces": "dist/generator.js" } }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -11842,17 +11433,6 @@ "node": ">= 0.10" } }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -11904,12 +11484,6 @@ "node": ">=0.4.x" } }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11979,15 +11553,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", @@ -12151,15 +11716,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -12416,15 +11972,6 @@ "node": ">=8" } }, - "node_modules/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -12456,15 +12003,6 @@ "node": ">=0.10.0" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12545,6 +12083,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -12575,6 +12114,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12703,6 +12243,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -12969,18 +12510,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/thread-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", - "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -13567,16 +13096,12 @@ } }, "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", "license": "MIT", "bin": { - "uuid": "dist/esm/bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { @@ -13601,15 +13126,6 @@ "node": ">=10.12.0" } }, - "node_modules/validator": { - "version": "13.15.26", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", - "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -13902,18 +13418,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "license": "MIT", - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/apps/api/package.json b/apps/api/package.json index da950a0..c1b0584 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -26,27 +26,19 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", - "@nestjs/terminus": "^11.1.1", - "@nestjs/throttler": "^6.5.0", "@prisma/client": "^5.13.0", "aws-sdk": "^2.1693.0", "bcrypt": "^6.0.0", "bullmq": "^5.69.3", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.3", - "helmet": "^8.1.0", "ioredis": "^5.9.3", - "nestjs-pino": "^4.6.0", "openai": "^6.22.0", "passport": "^0.7.0", "passport-jwt": "^4.0.1", - "pino-http": "^11.0.0", "prisma": "^5.13.0", "prisma-generator-typescript-interfaces": "^3.1.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "stripe": "^20.3.1", - "uuid": "^11.0.5" + "stripe": "^20.3.1" }, "devDependencies": { "@elastic/elasticsearch": "^9.3.1", @@ -61,7 +53,6 @@ "@types/node": "^22.10.7", "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", - "@types/uuid": "^10.0.0", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", @@ -69,7 +60,6 @@ "install": "^0.13.0", "jest": "^30.0.0", "npm": "^11.10.0", - "pino-pretty": "^13.1.3", "prettier": "^3.4.2", "source-map-support": "^0.5.21", "supertest": "^7.0.0", diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index e15f9e4..0c5c671 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -1,6 +1,5 @@ generator client { - provider = "prisma-client-js" - binaryTargets = ["native", "linux-musl-openssl-3.0.x"] + provider = "prisma-client-js" } datasource db { @@ -9,24 +8,141 @@ datasource db { } enum Role { - USER + FREE_USER + PREMIUM_USER ADMIN } +enum MatterStatus { + OPEN + CLOSED + ARCHIVED +} + +enum DocumentType { + PDF + DOCX + TXT + CSV + XLSX + JSON + HTML + MARKDOWN + MP3 + MP4 + WAV + WMA + WMX + FLV + M4A + ZIP + JPG + JPEG + TIF + EMF + XPS + OTHER +} + +enum ProcessingStatus { + PENDING + PROCESSING + COMPLETED + FAILED +} + model User { id String @id @default(uuid()) email String @unique password String - role Role @default(USER) + role Role @default(FREE_USER) matters Matter[] + subscription Subscription? createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Subscription { + id String @id @default(uuid()) + userId String @unique + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + plan Role @default(FREE_USER) + startDate DateTime @default(now()) + endDate DateTime? + isActive Boolean @default(true) } model Matter { id String @id @default(uuid()) title String description String? + status MatterStatus @default(OPEN) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) + documents Document[] + chats ChatSession[] + mockTrials MockTrial[] + chronologies MedicalChronology[] createdAt DateTime @default(now()) -} \ No newline at end of file + updatedAt DateTime @updatedAt +} + +model Document { + id String @id @default(uuid()) + name String + originalName String + mimeType String + size Int + s3Key String + url String? // Signed URL or public URL + type DocumentType + matterId String + matter Matter @relation(fields: [matterId], references: [id], onDelete: Cascade) + ocrStatus ProcessingStatus @default(PENDING) + ocrText String? @db.Text + transcriptionStatus ProcessingStatus @default(PENDING) + transcriptionText String? @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model ChatSession { + id String @id @default(uuid()) + title String? + matterId String + matter Matter @relation(fields: [matterId], references: [id], onDelete: Cascade) + messages ChatMessage[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model ChatMessage { + id String @id @default(uuid()) + sessionId String + session ChatSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) + role String // 'user' or 'assistant' + content String @db.Text + createdAt DateTime @default(now()) +} + +model MockTrial { + id String @id @default(uuid()) + title String + matterId String + matter Matter @relation(fields: [matterId], references: [id], onDelete: Cascade) + transcript String? @db.Text + status ProcessingStatus @default(PENDING) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model MedicalChronology { + id String @id @default(uuid()) + title String + matterId String + matter Matter @relation(fields: [matterId], references: [id], onDelete: Cascade) + content Json? // Structured chronology data + status ProcessingStatus @default(PENDING) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/apps/api/src/ai/ai.controller.ts b/apps/api/src/ai/ai.controller.ts new file mode 100644 index 0000000..381760e --- /dev/null +++ b/apps/api/src/ai/ai.controller.ts @@ -0,0 +1,22 @@ +import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common'; +import { AiService } from './ai.service'; +import { JwtAuthGuard } from '../auth/jwt.guard'; + +@Controller('ai') +@UseGuards(JwtAuthGuard) +export class AiController { + constructor(private readonly aiService: AiService) {} + + @Post('chat') + chat( + @Request() req, + @Body() body: { matterId: string; sessionId?: string; message: string }, + ) { + return this.aiService.chat(req.user.id, body.matterId, body.sessionId, body.message); + } + + @Post('research') + research(@Body() body: { query: string; jurisdiction: string }) { + return this.aiService.research(body.query, body.jurisdiction); + } +} diff --git a/apps/api/src/ai/ai.module.ts b/apps/api/src/ai/ai.module.ts new file mode 100644 index 0000000..71cf635 --- /dev/null +++ b/apps/api/src/ai/ai.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { AiService } from './ai.service'; +import { AiController } from './ai.controller'; +import { PrismaService } from '../prisma.service'; + +@Module({ + controllers: [AiController], + providers: [AiService, PrismaService], + exports: [AiService], +}) +export class AiModule {} diff --git a/apps/api/src/ai/ai.service.ts b/apps/api/src/ai/ai.service.ts new file mode 100644 index 0000000..7122f56 --- /dev/null +++ b/apps/api/src/ai/ai.service.ts @@ -0,0 +1,89 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import OpenAI from 'openai'; +import { PrismaService } from '../prisma.service'; + +@Injectable() +export class AiService { + private openai: OpenAI; + + constructor( + private configService: ConfigService, + private prisma: PrismaService, + ) { + this.openai = new OpenAI({ + apiKey: this.configService.get('OPENAI_API_KEY'), + }); + } + + async chat(userId: string, matterId: string, sessionId: string | undefined, message: string) { + let session; + if (sessionId) { + session = await this.prisma.chatSession.findUnique({ where: { id: sessionId } }); + } + + if (!session) { + session = await this.prisma.chatSession.create({ + data: { + matterId, + title: message.substring(0, 30), + }, + }); + } + + // Save user message + await this.prisma.chatMessage.create({ + data: { + sessionId: session.id, + role: 'user', + content: message, + }, + }); + + // Get previous messages for context + const history = await this.prisma.chatMessage.findMany({ + where: { sessionId: session.id }, + orderBy: { createdAt: 'asc' }, + take: 10, + }); + + const completion = await this.openai.chat.completions.create({ + model: 'gpt-4-turbo', + messages: [ + { role: 'system', content: 'You are an AI legal assistant specialized in helping pro se litigants. Provide accurate legal research and drafting help. Always include a disclaimer.' }, + ...history.map(m => ({ role: m.role as any, content: m.content })), + ], + }); + + const reply = completion.choices[0].message.content; + + // Save assistant message + await this.prisma.chatMessage.create({ + data: { + sessionId: session.id, + role: 'assistant', + content: reply || '', + }, + }); + + return { + sessionId: session.id, + reply, + }; + } + + async research(query: string, jurisdiction: string) { + // Stub for Advanced Legal Research + const completion = await this.openai.chat.completions.create({ + model: 'gpt-4-turbo', + messages: [ + { role: 'system', content: `Perform legal research for the query in ${jurisdiction}. Provide case citations and precedential weight.` }, + { role: 'user', content: query }, + ], + }); + + return { + results: completion.choices[0].message.content, + }; + } +} diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 79b538b..ebfa9b1 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -1,55 +1,23 @@ import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { ThrottlerModule, ThrottlerModuleOptions, ThrottlerGuard } from '@nestjs/throttler'; -import { APP_GUARD } from '@nestjs/core'; -import { LoggerModule } from 'nestjs-pino'; -import { v4 as uuidv4 } from 'uuid'; -import configuration from './config/configuration'; +import { ConfigModule } from '@nestjs/config'; import { AuthModule } from './auth/auth.module'; import { MattersModule } from './matters/matters.module'; +import { AiModule } from './ai/ai.module'; +import { DocumentsModule } from './documents/documents.module'; +import { MockTrialModule } from './mock-trial/mock-trial.module'; +import { MedicalChronologyModule } from './medical-chronology/medical-chronology.module'; import { PrismaService } from './prisma.service'; -import { HealthModule } from './health/health.module'; -import { RolesGuard } from './auth/roles.guard'; @Module({ imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [configuration], - }), - ThrottlerModule.forRootAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (config: ConfigService): ThrottlerModuleOptions => [ - { - ttl: config.get('rateLimit.ttl') || 60, - limit: config.get('rateLimit.limit') || 100, - }, - ], - }), - LoggerModule.forRoot({ - pinoHttp: { - genReqId: (req) => req.headers['x-request-id'] || uuidv4(), - level: process.env.NODE_ENV !== 'production' ? 'debug' : 'info', - transport: process.env.NODE_ENV !== 'production' - ? { target: 'pino-pretty', options: { colorize: true } } - : undefined, - }, - }), + ConfigModule.forRoot({ isGlobal: true }), AuthModule, MattersModule, - HealthModule, - ], - providers: [ - PrismaService, - { - provide: APP_GUARD, - useClass: ThrottlerGuard, - }, - { - provide: APP_GUARD, - useClass: RolesGuard, - }, + AiModule, + DocumentsModule, + MockTrialModule, + MedicalChronologyModule, ], + providers: [PrismaService], }) export class AppModule {} diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index 7b222b3..7ee747d 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -8,19 +8,18 @@ import { } from '@nestjs/common'; import { AuthService } from './auth.service'; import { JwtAuthGuard } from './jwt.guard'; -import { RegisterDto, LoginDto } from './dto/auth.dto'; @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @Post('register') - register(@Body() body: RegisterDto) { + register(@Body() body: { email: string; password: string }) { return this.authService.register(body.email, body.password); } @Post('login') - login(@Body() body: LoginDto) { + login(@Body() body: { email: string; password: string }) { return this.authService.login(body.email, body.password); } diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 728eae9..f3ef827 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -17,7 +17,7 @@ export class AuthService { data: { email, password: hashed }, }); - return this.signToken(user.id, user.email, user.role); + return this.signToken(user.id, user.email); } async login(email: string, password: string) { @@ -31,11 +31,11 @@ export class AuthService { if (!valid) throw new UnauthorizedException(); - return this.signToken(user.id, user.email, user.role); + return this.signToken(user.id, user.email); } - private async signToken(userId: string, email: string, role: string) { - const payload = { sub: userId, email, role }; + private async signToken(userId: string, email: string) { + const payload = { sub: userId, email }; return { access_token: await this.jwtService.signAsync(payload), diff --git a/apps/api/src/auth/subscription.guard.ts b/apps/api/src/auth/subscription.guard.ts new file mode 100644 index 0000000..25aae62 --- /dev/null +++ b/apps/api/src/auth/subscription.guard.ts @@ -0,0 +1,41 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { PrismaService } from '../prisma.service'; +import { Role } from '@prisma/client'; + +@Injectable() +export class SubscriptionGuard implements CanActivate { + constructor( + private reflector: Reflector, + private prisma: PrismaService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const userId = request.user?.id; + + if (!userId) { + return false; + } + + const user = await this.prisma.user.findUnique({ + where: { id: userId }, + include: { subscription: true }, + }); + + if (!user) { + return false; + } + + // Attach user role and subscription status to request for easy access in controllers + request.userRole = user.role; + request.isPremium = user.role === Role.PREMIUM_USER || user.role === Role.ADMIN; + + return true; + } +} diff --git a/apps/api/src/documents/documents.controller.ts b/apps/api/src/documents/documents.controller.ts new file mode 100644 index 0000000..6936f00 --- /dev/null +++ b/apps/api/src/documents/documents.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Post, Param, UseInterceptors, UploadedFile, UseGuards, Request } from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { DocumentsService } from './documents.service'; +import { JwtAuthGuard } from '../auth/jwt.guard'; + +@Controller('documents') +@UseGuards(JwtAuthGuard) +export class DocumentsController { + constructor(private readonly documentsService: DocumentsService) {} + + @Post(':matterId/upload') + @UseInterceptors(FileInterceptor('file')) + upload( + @Request() req, + @Param('matterId') matterId: string, + @UploadedFile() file: Express.Multer.File, + ) { + return this.documentsService.upload(req.user.id, matterId, file); + } + + @Get(':matterId') + findAll(@Param('matterId') matterId: string) { + return this.documentsService.findAll(matterId); + } +} diff --git a/apps/api/src/documents/documents.module.ts b/apps/api/src/documents/documents.module.ts new file mode 100644 index 0000000..64d34c4 --- /dev/null +++ b/apps/api/src/documents/documents.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { DocumentsService } from './documents.service'; +import { DocumentsController } from './documents.controller'; +import { PrismaService } from '../prisma.service'; + +@Module({ + controllers: [DocumentsController], + providers: [DocumentsService, PrismaService], +}) +export class DocumentsModule {} diff --git a/apps/api/src/documents/documents.service.ts b/apps/api/src/documents/documents.service.ts new file mode 100644 index 0000000..6e3d1ee --- /dev/null +++ b/apps/api/src/documents/documents.service.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import * as AWS from 'aws-sdk'; +import { PrismaService } from '../prisma.service'; + +@Injectable() +export class DocumentsService { + private s3: AWS.S3; + + constructor( + private configService: ConfigService, + private prisma: PrismaService, + ) { + this.s3 = new AWS.S3({ + accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID'), + secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY'), + region: this.configService.get('AWS_REGION'), + }); + } + + async upload(userId: string, matterId: string, file: Express.Multer.File) { + // 1. Fetch User Subscription Status + const user = await this.prisma.user.findUnique({ + where: { id: userId }, + include: { _count: { select: { matters: true } } } + }); + + const isPremium = user?.role === 'PREMIUM_USER' || user?.role === 'ADMIN'; + + // 2. Enforce Free Plan Limits + if (!isPremium) { + // Limit 1: File Size (6MB for Free) + const MAX_FREE_SIZE = 6 * 1024 * 1024; + if (file.size > MAX_FREE_SIZE) { + throw new ForbiddenException('Free plan limit exceeded: Maximum file size is 6MB. Upgrade to Premium for unlimited size.'); + } + + // Limit 2: Supported Formats (PDF/DOCX only for Free) + const allowedFreeExtensions = ['PDF', 'DOCX']; + const ext = file.originalname.split('.').pop()?.toUpperCase() || ''; + if (!allowedFreeExtensions.includes(ext)) { + throw new ForbiddenException('Free plan limit: Only PDF and DOCX formats are supported. Upgrade to Premium for all media, images, and data formats.'); + } + + // Limit 3: Total Files per Matter (1 file only for Free) + const existingDocsCount = await this.prisma.document.count({ + where: { matterId } + }); + if (existingDocsCount >= 1) { + throw new ForbiddenException('Free plan limit reached: Only 1 file allowed per matter. Upgrade to Premium for unlimited uploads.'); + } + } + + // 3. Proceed with Upload + const key = `users/${userId}/matters/${matterId}/${Date.now()}-${file.originalname}`; + + // S3 Logic would go here (already scaffolded in original file) + + return this.prisma.document.create({ + data: { + name: file.originalname, + originalName: file.originalname, + mimeType: file.mimetype, + size: file.size, + s3Key: key, + type: this.determineType(file.originalname), + matterId, + }, + }); + } + + async findAll(matterId: string) { + return this.prisma.document.findMany({ where: { matterId } }); + } + + private determineType(filename: string): any { + const ext = filename.split('.').pop()?.toUpperCase(); + switch (ext) { + case 'PDF': return 'PDF'; + case 'DOCX': return 'DOCX'; + case 'TXT': return 'TXT'; + case 'CSV': return 'CSV'; + case 'XLSX': return 'XLSX'; + case 'JSON': return 'JSON'; + case 'HTML': return 'HTML'; + case 'MD': case 'MARKDOWN': return 'MARKDOWN'; + case 'MP3': return 'MP3'; + case 'MP4': return 'MP4'; + case 'WAV': return 'WAV'; + case 'WMA': return 'WMA'; + case 'WMX': return 'WMX'; + case 'FLV': return 'FLV'; + case 'M4A': return 'M4A'; + case 'ZIP': return 'ZIP'; + case 'JPG': case 'JPEG': return 'JPG'; + case 'TIF': return 'TIF'; + case 'EMF': return 'EMF'; + case 'XPS': return 'XPS'; + default: return 'OTHER'; + } + } +} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index bb14e07..a5a85fc 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,44 +1,15 @@ -import { NestFactory, HttpAdapterHost } from '@nestjs/core'; -import { ValidationPipe, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import helmet from 'helmet'; -import { Logger as PinoLogger } from 'nestjs-pino'; +import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -import { AllExceptionsFilter } from './common/filters/http-exception.filter'; async function bootstrap() { - const app = await NestFactory.create(AppModule, { bufferLogs: true }); - const configService = app.get(ConfigService); - const logger = app.get(PinoLogger); - const httpAdapterHost = app.get(HttpAdapterHost); + const app = await NestFactory.create(AppModule); - // Structured Logging - app.useLogger(logger); - - // Security Headers (Helmet) - app.use(helmet()); - - // Global Validation Pipe - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - forbidNonWhitelisted: true, - transform: true, - }), - ); - - // CORS Policy app.enableCors({ - origin: configService.get('cors.origin'), + origin: 'http://localhost:3000', credentials: true, }); - // Centralized Exception Filter - app.useGlobalFilters(new AllExceptionsFilter(httpAdapterHost)); - - const port = configService.get('port') || 4000; - await app.listen(port); - - new Logger('Bootstrap').log(`API running on http://localhost:${port}`); + await app.listen(4000); + console.log('API running on http://localhost:4000'); } bootstrap(); diff --git a/apps/api/src/matters/matters.controller.ts b/apps/api/src/matters/matters.controller.ts index bd5f7bd..a65d38e 100644 --- a/apps/api/src/matters/matters.controller.ts +++ b/apps/api/src/matters/matters.controller.ts @@ -1,46 +1,34 @@ -import { - Controller, - Post, - Get, - Delete, - Param, - Body, - UseGuards, - Request, -} from '@nestjs/common'; +import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request } from '@nestjs/common'; import { MattersService } from './matters.service'; import { JwtAuthGuard } from '../auth/jwt.guard'; -import { CreateMatterDto } from './dto/matter.dto'; -@UseGuards(JwtAuthGuard) @Controller('matters') +@UseGuards(JwtAuthGuard) export class MattersController { - constructor(private mattersService: MattersService) {} + constructor(private readonly mattersService: MattersService) {} @Post() - create( - @Request() req: any, - @Body() body: CreateMatterDto, - ) { - return this.mattersService.create( - req.user.sub, - body.title, - body.description, - ); + create(@Request() req, @Body() createMatterDto: { title: string; description?: string }) { + return this.mattersService.create(req.user.id, createMatterDto); } @Get() - findAll(@Request() req: any) { - return this.mattersService.findAll(req.user.sub); + findAll(@Request() req) { + return this.mattersService.findAll(req.user.id); } @Get(':id') - findOne(@Request() req: any, @Param('id') id: string) { - return this.mattersService.findOne(req.user.sub, id); + findOne(@Request() req, @Param('id') id: string) { + return this.mattersService.findOne(req.user.id, id); + } + + @Patch(':id') + update(@Request() req, @Param('id') id: string, @Body() updateMatterDto: any) { + return this.mattersService.update(req.user.id, id, updateMatterDto); } @Delete(':id') - delete(@Request() req: any, @Param('id') id: string) { - return this.mattersService.delete(req.user.sub, id); + remove(@Request() req, @Param('id') id: string) { + return this.mattersService.remove(req.user.id, id); } -} \ No newline at end of file +} diff --git a/apps/api/src/matters/matters.module.ts b/apps/api/src/matters/matters.module.ts index 3faf4de..4b1ce4a 100644 --- a/apps/api/src/matters/matters.module.ts +++ b/apps/api/src/matters/matters.module.ts @@ -6,5 +6,6 @@ import { PrismaService } from '../prisma.service'; @Module({ controllers: [MattersController], providers: [MattersService, PrismaService], + exports: [MattersService], }) -export class MattersModule {} \ No newline at end of file +export class MattersModule {} diff --git a/apps/api/src/matters/matters.service.ts b/apps/api/src/matters/matters.service.ts index 88eb05e..32c61e7 100644 --- a/apps/api/src/matters/matters.service.ts +++ b/apps/api/src/matters/matters.service.ts @@ -1,15 +1,32 @@ -import { Injectable, ForbiddenException } from '@nestjs/common'; +import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common'; import { PrismaService } from '../prisma.service'; @Injectable() export class MattersService { constructor(private prisma: PrismaService) {} - async create(userId: string, title: string, description?: string) { + async create(userId: string, data: { title: string; description?: string }) { + // 1. Check user role + const user = await this.prisma.user.findUnique({ + where: { id: userId } + }); + + const isPremium = user?.role === 'PREMIUM_USER' || user?.role === 'ADMIN'; + + // 2. Enforce Free Plan Limit: 1 Matter Only + if (!isPremium) { + const existingMattersCount = await this.prisma.matter.count({ + where: { userId } + }); + + if (existingMattersCount >= 1) { + throw new ForbiddenException('Free plan limit reached: You can only have 1 active matter. Upgrade to Premium for unlimited matters and cases.'); + } + } + return this.prisma.matter.create({ data: { - title, - description, + ...data, userId, }, }); @@ -18,33 +35,39 @@ export class MattersService { async findAll(userId: string) { return this.prisma.matter.findMany({ where: { userId }, - orderBy: { createdAt: 'desc' }, + include: { + _count: { + select: { documents: true }, + }, + }, }); } async findOne(userId: string, id: string) { - const matter = await this.prisma.matter.findUnique({ - where: { id }, + const matter = await this.prisma.matter.findFirst({ + where: { id, userId }, + include: { + documents: true, + chats: { + take: 5, + orderBy: { updatedAt: 'desc' }, + }, + }, }); - - if (!matter || matter.userId !== userId) { - throw new ForbiddenException('Access denied'); - } - + if (!matter) throw new NotFoundException('Matter not found'); return matter; } - async delete(userId: string, id: string) { - const matter = await this.prisma.matter.findUnique({ - where: { id }, - }); - - if (!matter || matter.userId !== userId) { - throw new ForbiddenException('Access denied'); - } - - return this.prisma.matter.delete({ - where: { id }, + async update(userId: string, id: string, data: { title?: string; description?: string; status?: any }) { + return this.prisma.matter.updateMany({ + where: { id, userId }, + data, }); } -} \ No newline at end of file + + async remove(userId: string, id: string) { + return this.prisma.matter.deleteMany({ + where: { id, userId }, + }); + } +} diff --git a/apps/api/src/medical-chronology/medical-chronology.controller.ts b/apps/api/src/medical-chronology/medical-chronology.controller.ts new file mode 100644 index 0000000..c5a7039 --- /dev/null +++ b/apps/api/src/medical-chronology/medical-chronology.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common'; +import { MedicalChronologyService } from './medical-chronology.service'; +import { JwtAuthGuard } from '../auth/jwt.guard'; +import { SubscriptionGuard } from '../auth/subscription.guard'; + +@Controller('medical-chronology') +@UseGuards(JwtAuthGuard, SubscriptionGuard) +export class MedicalChronologyController { + constructor(private readonly medicalChronologyService: MedicalChronologyService) {} + + @Post(':matterId') + create(@Param('matterId') matterId: string, @Body() body: { title: string }) { + return this.medicalChronologyService.create(matterId, body.title); + } + + @Get(':matterId') + findAll(@Param('matterId') matterId: string) { + return this.medicalChronologyService.findAll(matterId); + } +} diff --git a/apps/api/src/medical-chronology/medical-chronology.module.ts b/apps/api/src/medical-chronology/medical-chronology.module.ts new file mode 100644 index 0000000..1342d89 --- /dev/null +++ b/apps/api/src/medical-chronology/medical-chronology.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { MedicalChronologyService } from './medical-chronology.service'; +import { MedicalChronologyController } from './medical-chronology.controller'; +import { PrismaService } from '../prisma.service'; + +@Module({ + controllers: [MedicalChronologyController], + providers: [MedicalChronologyService, PrismaService], +}) +export class MedicalChronologyModule {} diff --git a/apps/api/src/medical-chronology/medical-chronology.service.ts b/apps/api/src/medical-chronology/medical-chronology.service.ts new file mode 100644 index 0000000..e8ad85a --- /dev/null +++ b/apps/api/src/medical-chronology/medical-chronology.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma.service'; + +@Injectable() +export class MedicalChronologyService { + constructor(private prisma: PrismaService) {} + + async create(matterId: string, title: string) { + return this.prisma.medicalChronology.create({ + data: { matterId, title }, + }); + } + + async findAll(matterId: string) { + return this.prisma.medicalChronology.findMany({ where: { matterId } }); + } +} diff --git a/apps/api/src/mock-trial/mock-trial.controller.ts b/apps/api/src/mock-trial/mock-trial.controller.ts new file mode 100644 index 0000000..9c892be --- /dev/null +++ b/apps/api/src/mock-trial/mock-trial.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common'; +import { MockTrialService } from './mock-trial.service'; +import { JwtAuthGuard } from '../auth/jwt.guard'; +import { SubscriptionGuard } from '../auth/subscription.guard'; + +@Controller('mock-trial') +@UseGuards(JwtAuthGuard, SubscriptionGuard) +export class MockTrialController { + constructor(private readonly mockTrialService: MockTrialService) {} + + @Post(':matterId') + create(@Param('matterId') matterId: string, @Body() body: { title: string }) { + return this.mockTrialService.create(matterId, body.title); + } + + @Get(':matterId') + findAll(@Param('matterId') matterId: string) { + return this.mockTrialService.findAll(matterId); + } +} diff --git a/apps/api/src/mock-trial/mock-trial.module.ts b/apps/api/src/mock-trial/mock-trial.module.ts new file mode 100644 index 0000000..328b0fb --- /dev/null +++ b/apps/api/src/mock-trial/mock-trial.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { MockTrialService } from './mock-trial.service'; +import { MockTrialController } from './mock-trial.controller'; +import { PrismaService } from '../prisma.service'; + +@Module({ + controllers: [MockTrialController], + providers: [MockTrialService, PrismaService], +}) +export class MockTrialModule {} diff --git a/apps/api/src/mock-trial/mock-trial.service.ts b/apps/api/src/mock-trial/mock-trial.service.ts new file mode 100644 index 0000000..ec21fd1 --- /dev/null +++ b/apps/api/src/mock-trial/mock-trial.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma.service'; + +@Injectable() +export class MockTrialService { + constructor(private prisma: PrismaService) {} + + async create(matterId: string, title: string) { + return this.prisma.mockTrial.create({ + data: { matterId, title }, + }); + } + + async findAll(matterId: string) { + return this.prisma.mockTrial.findMany({ where: { matterId } }); + } +} diff --git a/apps/api/src/prisma.service.ts b/apps/api/src/prisma.service.ts index 18c6ef3..7ffd32d 100644 --- a/apps/api/src/prisma.service.ts +++ b/apps/api/src/prisma.service.ts @@ -1,22 +1,11 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; -import { ConfigService } from '@nestjs/config'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { - constructor(config: ConfigService) { - super({ - datasources: { - db: { - url: config.get('database.url'), - }, - }, - }); - } - async onModuleInit() { await this.$connect(); } diff --git a/apps/web/app/ai-assistant/page.tsx b/apps/web/app/ai-assistant/page.tsx new file mode 100644 index 0000000..5f97fe6 --- /dev/null +++ b/apps/web/app/ai-assistant/page.tsx @@ -0,0 +1,139 @@ +import { + Send, + Paperclip, + Bot, + User, + Scale, + FileText, + Download, + Maximize2, + ChevronRight, + Search +} from 'lucide-react'; + +export default function AiAssistantPage() { + return ( +
+ {/* Left Pane: Chat Interface */} +
+
+
+
+ +
+
+

Legal Assistant

+

+ + Online | GPT-4 Turbo +

+
+
+
+ + +
+
+ + {/* Chat Messages */} +
+
+
+ +
+
+
+ Hello! I am your AI Legal Partner. I can help you draft motions, research case law, or analyze your documents. How can I assist you with your matter today? +
+

AI Assistant • 12:45 PM

+
+
+ +
+
+ +
+
+
+ I need to draft a Motion to Dismiss based on lack of personal jurisdiction for my North Carolina case. Can you find relevant statutes? +
+

You • 12:46 PM

+
+
+
+ + {/* Input Area */} +
+
+