@ftisindia/create-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +144 -0
- package/bin/index.mjs +2 -0
- package/package.json +36 -0
- package/src/copy.mjs +45 -0
- package/src/log.mjs +23 -0
- package/src/main.mjs +221 -0
- package/src/run.mjs +52 -0
- package/src/substitute.mjs +40 -0
- package/template/.env.example +36 -0
- package/template/.github/workflows/ci.yml +36 -0
- package/template/.husky/pre-commit +1 -0
- package/template/README.md +146 -0
- package/template/_editorconfig +8 -0
- package/template/_gitignore +7 -0
- package/template/_nvmrc +1 -0
- package/template/_package.json +107 -0
- package/template/_prettierignore +5 -0
- package/template/_prettierrc +6 -0
- package/template/docs/API_REFERENCE.md +123 -0
- package/template/docs/GETTING_STARTED.md +65 -0
- package/template/docs/MODULE_COMPLETION_CHECKLIST.md +40 -0
- package/template/docs/OAUTH.md +46 -0
- package/template/docs/SAMPLE_MODULE.md +23 -0
- package/template/docs/api.http +269 -0
- package/template/eslint.config.mjs +51 -0
- package/template/nest-cli.json +8 -0
- package/template/prisma/migrations/20260530000000_init/migration.sql +248 -0
- package/template/prisma/schema.prisma +299 -0
- package/template/prisma/seed.ts +44 -0
- package/template/scripts/db-create.mjs +126 -0
- package/template/scripts/gen-module.mjs +217 -0
- package/template/scripts/seed-test-user-org.ts +264 -0
- package/template/scripts/setup-local.mjs +224 -0
- package/template/scripts/test-db.mjs +69 -0
- package/template/src/app.module.ts +58 -0
- package/template/src/common/decorators/.gitkeep +1 -0
- package/template/src/common/dto/error-response.dto.ts +17 -0
- package/template/src/common/dto/membership-response.dto.ts +51 -0
- package/template/src/common/dto/mutation-response.dto.ts +11 -0
- package/template/src/common/dto/role-summary.dto.ts +18 -0
- package/template/src/common/dto/user-summary.dto.ts +23 -0
- package/template/src/common/enums/.gitkeep +1 -0
- package/template/src/common/filters/.gitkeep +1 -0
- package/template/src/common/filters/http-exception.filter.ts +78 -0
- package/template/src/common/guards/.gitkeep +1 -0
- package/template/src/common/interceptors/.gitkeep +1 -0
- package/template/src/common/pipes/.gitkeep +1 -0
- package/template/src/common/swagger/api-error-responses.ts +54 -0
- package/template/src/common/types/.gitkeep +1 -0
- package/template/src/config/app.config.ts +7 -0
- package/template/src/config/auth.config.ts +33 -0
- package/template/src/config/database.config.ts +6 -0
- package/template/src/config/env.validation.ts +131 -0
- package/template/src/config/index.ts +5 -0
- package/template/src/config/rbac.config.ts +6 -0
- package/template/src/database/prisma/prisma-transaction.ts +22 -0
- package/template/src/database/prisma/prisma.module.ts +9 -0
- package/template/src/database/prisma/prisma.service.ts +16 -0
- package/template/src/main.ts +42 -0
- package/template/src/modules/access-control/access-control.module.ts +24 -0
- package/template/src/modules/access-control/application/route-registry.validator.ts +289 -0
- package/template/src/modules/access-control/application/services/ability.factory.ts +28 -0
- package/template/src/modules/access-control/application/services/access-control.service.ts +478 -0
- package/template/src/modules/access-control/application/services/permission.guard.ts +77 -0
- package/template/src/modules/access-control/application/services/rbac-cache.service.ts +148 -0
- package/template/src/modules/access-control/dto/access-control-response.dto.ts +79 -0
- package/template/src/modules/access-control/dto/create-role.dto.ts +18 -0
- package/template/src/modules/access-control/dto/update-role-permissions.dto.ts +23 -0
- package/template/src/modules/access-control/dto/update-role.dto.ts +19 -0
- package/template/src/modules/access-control/presentation/access-control.controller.ts +157 -0
- package/template/src/modules/access-control/presentation/permissions.decorator.ts +8 -0
- package/template/src/modules/access-control/presentation/public.decorator.ts +7 -0
- package/template/src/modules/access-control/types/permission-key.ts +37 -0
- package/template/src/modules/access-control/types/rbac-context.ts +11 -0
- package/template/src/modules/access-control/types/route-permission-registry.ts +129 -0
- package/template/src/modules/audit/application/services/audit.service.ts +97 -0
- package/template/src/modules/audit/audit.module.ts +13 -0
- package/template/src/modules/audit/dto/audit-response.dto.ts +75 -0
- package/template/src/modules/audit/dto/list-audit-logs-query.dto.ts +75 -0
- package/template/src/modules/audit/presentation/audit.controller.ts +37 -0
- package/template/src/modules/auth/application/services/auth.service.ts +509 -0
- package/template/src/modules/auth/application/services/password.service.ts +15 -0
- package/template/src/modules/auth/application/services/token.service.ts +95 -0
- package/template/src/modules/auth/auth.module.ts +73 -0
- package/template/src/modules/auth/dto/auth-response.dto.ts +29 -0
- package/template/src/modules/auth/dto/login.dto.ts +15 -0
- package/template/src/modules/auth/dto/logout.dto.ts +3 -0
- package/template/src/modules/auth/dto/oauth-exchange.dto.ts +15 -0
- package/template/src/modules/auth/dto/refresh-token.dto.ts +14 -0
- package/template/src/modules/auth/dto/signup.dto.ts +27 -0
- package/template/src/modules/auth/infrastructure/passport/google-auth.guard.ts +27 -0
- package/template/src/modules/auth/infrastructure/passport/google.strategy.ts +56 -0
- package/template/src/modules/auth/infrastructure/passport/jwt-auth.guard.ts +5 -0
- package/template/src/modules/auth/infrastructure/passport/jwt.strategy.ts +43 -0
- package/template/src/modules/auth/presentation/auth.controller.ts +148 -0
- package/template/src/modules/auth/presentation/current-user.decorator.ts +11 -0
- package/template/src/modules/auth/presentation/google-oauth-exception.filter.ts +33 -0
- package/template/src/modules/auth/types/authenticated-user.ts +7 -0
- package/template/src/modules/auth/types/google-auth-profile.ts +6 -0
- package/template/src/modules/auth/types/jwt-payload.ts +5 -0
- package/template/src/modules/health/dto/health-response.dto.ts +9 -0
- package/template/src/modules/health/health.module.ts +7 -0
- package/template/src/modules/health/presentation/health.controller.ts +33 -0
- package/template/src/modules/invitations/application/services/invitations.service.ts +967 -0
- package/template/src/modules/invitations/dto/accept-invitation.dto.ts +24 -0
- package/template/src/modules/invitations/dto/create-invitation.dto.ts +100 -0
- package/template/src/modules/invitations/dto/invitation-response.dto.ts +108 -0
- package/template/src/modules/invitations/dto/invitation-token.dto.ts +15 -0
- package/template/src/modules/invitations/invitations.module.ts +12 -0
- package/template/src/modules/invitations/presentation/invitations.controller.ts +149 -0
- package/template/src/modules/memberships/application/services/memberships.service.ts +455 -0
- package/template/src/modules/memberships/dto/transfer-owner.dto.ts +11 -0
- package/template/src/modules/memberships/dto/update-billing-contact.dto.ts +8 -0
- package/template/src/modules/memberships/dto/update-membership-owner.dto.ts +8 -0
- package/template/src/modules/memberships/dto/update-membership-role.dto.ts +11 -0
- package/template/src/modules/memberships/dto/update-membership-status.dto.ts +9 -0
- package/template/src/modules/memberships/memberships.module.ts +12 -0
- package/template/src/modules/memberships/presentation/memberships.controller.ts +193 -0
- package/template/src/modules/organisations/application/services/organisations.service.ts +147 -0
- package/template/src/modules/organisations/dto/create-organisation.dto.ts +32 -0
- package/template/src/modules/organisations/dto/organisation-response.dto.ts +62 -0
- package/template/src/modules/organisations/infrastructure/repositories/organisations.repository.ts +24 -0
- package/template/src/modules/organisations/organisations.module.ts +12 -0
- package/template/src/modules/organisations/presentation/organisations.controller.ts +37 -0
- package/template/src/modules/organisations/types/default-organisation-data.ts +18 -0
- package/template/src/modules/platform-admin/.gitkeep +1 -0
- package/template/src/modules/request-context/application/services/request-context.service.ts +79 -0
- package/template/src/modules/request-context/presentation/org-scope.guard.ts +26 -0
- package/template/src/modules/request-context/presentation/request-context.interceptor.ts +26 -0
- package/template/src/modules/request-context/presentation/request-context.middleware.ts +31 -0
- package/template/src/modules/request-context/request-context.module.ts +25 -0
- package/template/src/modules/request-context/types/request-context.ts +29 -0
- package/template/src/modules/sample/application/services/sample.service.ts +67 -0
- package/template/src/modules/sample/dto/sample-echo.dto.ts +10 -0
- package/template/src/modules/sample/dto/sample-response.dto.ts +41 -0
- package/template/src/modules/sample/presentation/sample.controller.ts +63 -0
- package/template/src/modules/sample/sample.module.ts +11 -0
- package/template/src/modules/settings/application/services/settings.service.ts +139 -0
- package/template/src/modules/settings/dto/setting-response.dto.ts +27 -0
- package/template/src/modules/settings/dto/update-setting.dto.ts +16 -0
- package/template/src/modules/settings/presentation/settings.controller.ts +66 -0
- package/template/src/modules/settings/settings.module.ts +12 -0
- package/template/src/modules/settings/types/setting-definitions.ts +104 -0
- package/template/src/modules/users/.gitkeep +1 -0
- package/template/test/.gitkeep +1 -0
- package/template/test/jest-e2e.json +9 -0
- package/template/test/permission.guard.spec.ts +22 -0
- package/template/test/route-registry.validator.spec.ts +90 -0
- package/template/test/security.e2e-spec.ts +102 -0
- package/template/tsconfig.build.json +4 -0
- package/template/tsconfig.json +18 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
NestJS modular-monolith starter with email/password auth, Google OAuth handoff, organisations, memberships, invitations, per-organisation RBAC with CASL, request context, audit logs, typed settings, Swagger, and a complete sample module.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
Install dependencies if the scaffolder did not already do it:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run the guided local setup:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run setup:local
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Make sure PostgreSQL is running before setup starts. If your local PostgreSQL user,
|
|
20
|
+
password, host, or port differs from the default, update `.env` when the setup prompt
|
|
21
|
+
opens it for review.
|
|
22
|
+
|
|
23
|
+
The setup command checks and creates `.env` values, creates the `app` and `app_test` PostgreSQL databases if they are missing, generates Prisma, deploys migrations, seeds permissions, and prints demo login credentials. It is non-destructive: it does not drop, reset, truncate, or overwrite existing data.
|
|
24
|
+
|
|
25
|
+
Start the API:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run start:dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Open:
|
|
32
|
+
|
|
33
|
+
```txt
|
|
34
|
+
http://localhost:3000/health
|
|
35
|
+
http://localhost:3000/docs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use the printed demo email/password at `POST /auth/login`, then paste the returned access token into Swagger's bearer authorization.
|
|
39
|
+
|
|
40
|
+
For a fresher-friendly API walkthrough, response-shape notes, and Swagger usage
|
|
41
|
+
rules, read `docs/API_REFERENCE.md`.
|
|
42
|
+
|
|
43
|
+
## PostgreSQL
|
|
44
|
+
|
|
45
|
+
The default `.env` expects a local PostgreSQL server reachable at:
|
|
46
|
+
|
|
47
|
+
```txt
|
|
48
|
+
postgresql://postgres:postgres@localhost:5432/app
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Any reachable PostgreSQL works. Update `DATABASE_URL` and `TEST_DATABASE_URL` in `.env` if your local user, password, host, port, or database names differ. `npm run db:create` creates missing databases only; it never deletes existing databases.
|
|
52
|
+
|
|
53
|
+
## Daily Commands
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run setup:local # guided, idempotent local bootstrap
|
|
57
|
+
npm run start:dev # start in watch mode
|
|
58
|
+
npm run lint # ESLint
|
|
59
|
+
npm run format # Prettier write
|
|
60
|
+
npm run build # Nest build
|
|
61
|
+
npm test # unit tests
|
|
62
|
+
npm run test:e2e # e2e tests against TEST_DATABASE_URL
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`npm run db:reset` is the only destructive database command and is intentionally separate.
|
|
66
|
+
|
|
67
|
+
## Core Endpoints
|
|
68
|
+
|
|
69
|
+
```txt
|
|
70
|
+
GET /health
|
|
71
|
+
GET /docs
|
|
72
|
+
POST /auth/signup
|
|
73
|
+
POST /auth/login
|
|
74
|
+
POST /auth/refresh
|
|
75
|
+
POST /auth/oauth/exchange
|
|
76
|
+
POST /auth/logout
|
|
77
|
+
GET /auth/google
|
|
78
|
+
GET /auth/google/callback
|
|
79
|
+
GET /auth/me
|
|
80
|
+
POST /organisations
|
|
81
|
+
GET /organisations/:orgId/memberships/me
|
|
82
|
+
GET /organisations/:orgId/memberships
|
|
83
|
+
PATCH /organisations/:orgId/memberships/:membershipId/status
|
|
84
|
+
PATCH /organisations/:orgId/memberships/:membershipId/role
|
|
85
|
+
PATCH /organisations/:orgId/memberships/:membershipId/owner
|
|
86
|
+
PATCH /organisations/:orgId/memberships/:membershipId/billing-contact
|
|
87
|
+
POST /organisations/:orgId/memberships/transfer-owner
|
|
88
|
+
POST /organisations/:orgId/invitations
|
|
89
|
+
GET /organisations/:orgId/invitations
|
|
90
|
+
POST /organisations/:orgId/invitations/:invitationId/revoke
|
|
91
|
+
POST /organisations/:orgId/invitations/:invitationId/resend
|
|
92
|
+
POST /invitations/accept
|
|
93
|
+
POST /invitations/decline
|
|
94
|
+
GET /organisations/:orgId/access-control/permissions
|
|
95
|
+
GET /organisations/:orgId/access-control/route-permissions
|
|
96
|
+
GET /organisations/:orgId/access-control/roles
|
|
97
|
+
POST /organisations/:orgId/access-control/roles
|
|
98
|
+
PATCH /organisations/:orgId/access-control/roles/:roleId
|
|
99
|
+
DELETE /organisations/:orgId/access-control/roles/:roleId
|
|
100
|
+
GET /organisations/:orgId/access-control/roles/:roleId/permissions
|
|
101
|
+
PUT /organisations/:orgId/access-control/roles/:roleId/permissions
|
|
102
|
+
GET /organisations/:orgId/audit
|
|
103
|
+
GET /organisations/:orgId/settings
|
|
104
|
+
GET /organisations/:orgId/settings/:key
|
|
105
|
+
PATCH /organisations/:orgId/settings
|
|
106
|
+
GET /organisations/:orgId/sample/status
|
|
107
|
+
POST /organisations/:orgId/sample/echo
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Architecture Rules
|
|
111
|
+
|
|
112
|
+
- JWTs contain identity only. Organisation membership, roles, and permissions are read per request.
|
|
113
|
+
- Organisation-scoped routes use path scope: `/organisations/:orgId/...`.
|
|
114
|
+
- Protected organisation routes use `JwtAuthGuard`, `OrgScopeGuard`, `PermissionGuard`, and `@RequirePermissions(...)`.
|
|
115
|
+
- Every permission-gated route must be represented in `route-permission-registry.ts`; startup and tests fail on drift.
|
|
116
|
+
- Mutating business flows should write audit rows.
|
|
117
|
+
- Setup and CI commands are non-destructive. Use `npm run db:reset` only when you explicitly want to reset a local database.
|
|
118
|
+
|
|
119
|
+
## Adding Features
|
|
120
|
+
|
|
121
|
+
Use the sample module as the copy-paste reference:
|
|
122
|
+
|
|
123
|
+
```txt
|
|
124
|
+
src/modules/sample
|
|
125
|
+
docs/SAMPLE_MODULE.md
|
|
126
|
+
docs/MODULE_COMPLETION_CHECKLIST.md
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
You can also scaffold a starter module:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm run gen:module -- billing-reports
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The generator creates a guarded org-scoped controller, DTO, service with request-context and audit patterns, permission keys, route registry entries, and a starter spec. It prints the `AppModule` import you still need to add.
|
|
136
|
+
|
|
137
|
+
## REST Client
|
|
138
|
+
|
|
139
|
+
`docs/api.http` contains a REST Client flow for health, login, organisation setup, invitations, RBAC, settings, audit, and the sample endpoint.
|
|
140
|
+
|
|
141
|
+
## API Documentation
|
|
142
|
+
|
|
143
|
+
Swagger is mounted at `/docs` and includes tags, request schemas, response DTOs,
|
|
144
|
+
common error responses, bearer authorization, route parameters, and examples for
|
|
145
|
+
all implemented API routes. See `docs/API_REFERENCE.md` before adding or changing
|
|
146
|
+
controllers.
|
package/template/_nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "nest build",
|
|
8
|
+
"db:create": "node scripts/db-create.mjs",
|
|
9
|
+
"format": "prettier --write .",
|
|
10
|
+
"format:check": "prettier --check .",
|
|
11
|
+
"gen:module": "node scripts/gen-module.mjs",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"prepare": "husky || true",
|
|
14
|
+
"start": "nest start",
|
|
15
|
+
"start:dev": "nest start --watch",
|
|
16
|
+
"start:prod": "node dist/main.js",
|
|
17
|
+
"prisma:generate": "prisma generate",
|
|
18
|
+
"prisma:migrate": "prisma migrate dev --name init",
|
|
19
|
+
"prisma:deploy": "prisma migrate deploy",
|
|
20
|
+
"prisma:seed": "prisma db seed",
|
|
21
|
+
"seed:test-user-org": "tsx scripts/seed-test-user-org.ts",
|
|
22
|
+
"setup": "node scripts/setup-local.mjs",
|
|
23
|
+
"setup:local": "node scripts/setup-local.mjs",
|
|
24
|
+
"setup:ci": "node scripts/setup-local.mjs --yes",
|
|
25
|
+
"test": "jest",
|
|
26
|
+
"test:cov": "jest --coverage",
|
|
27
|
+
"test:e2e": "node scripts/test-db.mjs && jest --config test/jest-e2e.json --runInBand",
|
|
28
|
+
"test:watch": "jest --watch",
|
|
29
|
+
"db:reset": "prisma migrate reset"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@casl/ability": "6.8.1",
|
|
33
|
+
"@nestjs/common": "11.1.24",
|
|
34
|
+
"@nestjs/config": "4.0.4",
|
|
35
|
+
"@nestjs/core": "11.1.24",
|
|
36
|
+
"@nestjs/jwt": "11.0.2",
|
|
37
|
+
"@nestjs/passport": "11.0.5",
|
|
38
|
+
"@nestjs/platform-express": "11.1.24",
|
|
39
|
+
"@nestjs/swagger": "11.4.4",
|
|
40
|
+
"@nestjs/throttler": "6.5.0",
|
|
41
|
+
"@prisma/client": "5.22.0",
|
|
42
|
+
"bcryptjs": "3.0.3",
|
|
43
|
+
"class-transformer": "0.5.1",
|
|
44
|
+
"class-validator": "0.15.1",
|
|
45
|
+
"express-session": "1.19.0",
|
|
46
|
+
"passport": "0.7.0",
|
|
47
|
+
"passport-google-oauth20": "2.0.0",
|
|
48
|
+
"passport-jwt": "4.0.1",
|
|
49
|
+
"pg": "8.21.0",
|
|
50
|
+
"reflect-metadata": "0.2.2",
|
|
51
|
+
"rxjs": "7.8.2",
|
|
52
|
+
"zod": "4.4.3"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@eslint/js": "10.0.1",
|
|
56
|
+
"@nestjs/cli": "11.0.21",
|
|
57
|
+
"@nestjs/schematics": "11.1.0",
|
|
58
|
+
"@nestjs/testing": "11.1.24",
|
|
59
|
+
"@types/express-session": "1.19.0",
|
|
60
|
+
"@types/jest": "30.0.0",
|
|
61
|
+
"@types/node": "20.19.41",
|
|
62
|
+
"@types/passport": "1.0.17",
|
|
63
|
+
"@types/passport-google-oauth20": "2.0.17",
|
|
64
|
+
"@types/passport-jwt": "4.0.1",
|
|
65
|
+
"@types/pg": "8.20.0",
|
|
66
|
+
"@types/supertest": "7.2.0",
|
|
67
|
+
"eslint": "10.4.1",
|
|
68
|
+
"eslint-config-prettier": "10.1.8",
|
|
69
|
+
"globals": "17.6.0",
|
|
70
|
+
"husky": "9.1.7",
|
|
71
|
+
"jest": "30.4.2",
|
|
72
|
+
"lint-staged": "17.0.6",
|
|
73
|
+
"prettier": "3.8.3",
|
|
74
|
+
"prisma": "5.22.0",
|
|
75
|
+
"supertest": "7.2.2",
|
|
76
|
+
"ts-jest": "29.4.11",
|
|
77
|
+
"ts-node": "10.9.2",
|
|
78
|
+
"tsconfig-paths": "4.2.0",
|
|
79
|
+
"tsx": "4.22.3",
|
|
80
|
+
"typescript-eslint": "8.60.0",
|
|
81
|
+
"typescript": "5.9.3"
|
|
82
|
+
},
|
|
83
|
+
"lint-staged": {
|
|
84
|
+
"*.{js,mjs,ts,json,md,yml,yaml}": "prettier --write",
|
|
85
|
+
"*.{js,mjs,ts}": "eslint --fix"
|
|
86
|
+
},
|
|
87
|
+
"prisma": {
|
|
88
|
+
"seed": "tsx prisma/seed.ts"
|
|
89
|
+
},
|
|
90
|
+
"jest": {
|
|
91
|
+
"moduleFileExtensions": [
|
|
92
|
+
"js",
|
|
93
|
+
"json",
|
|
94
|
+
"ts"
|
|
95
|
+
],
|
|
96
|
+
"rootDir": ".",
|
|
97
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
98
|
+
"transform": {
|
|
99
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
100
|
+
},
|
|
101
|
+
"collectCoverageFrom": [
|
|
102
|
+
"src/**/*.(t|j)s"
|
|
103
|
+
],
|
|
104
|
+
"coverageDirectory": "coverage",
|
|
105
|
+
"testEnvironment": "node"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
The generated app exposes interactive Swagger documentation at:
|
|
4
|
+
|
|
5
|
+
```txt
|
|
6
|
+
http://localhost:3000/docs
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Start the API first:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm run start:dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Authentication In Swagger
|
|
16
|
+
|
|
17
|
+
1. Run `npm run setup:local` and copy the printed demo email and password.
|
|
18
|
+
2. Open `/docs`.
|
|
19
|
+
3. Run `POST /auth/login` with the demo credentials.
|
|
20
|
+
4. Copy `accessToken` from the response.
|
|
21
|
+
5. Click `Authorize` in Swagger.
|
|
22
|
+
6. Paste the token as the bearer token value.
|
|
23
|
+
|
|
24
|
+
After authorization, Swagger can call protected routes directly.
|
|
25
|
+
|
|
26
|
+
## Common Response Shape
|
|
27
|
+
|
|
28
|
+
Successful responses are documented per endpoint with typed response DTOs. Most
|
|
29
|
+
date values are ISO strings.
|
|
30
|
+
|
|
31
|
+
Errors use one common envelope:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"error": {
|
|
36
|
+
"code": "BAD_REQUEST",
|
|
37
|
+
"message": "Validation failed.",
|
|
38
|
+
"details": {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Typical status codes:
|
|
44
|
+
|
|
45
|
+
```txt
|
|
46
|
+
400 invalid body, route parameter, or query string
|
|
47
|
+
401 missing or invalid bearer token
|
|
48
|
+
403 authenticated user lacks org access or permission
|
|
49
|
+
404 resource not found
|
|
50
|
+
409 request conflicts with current state
|
|
51
|
+
410 invitation token expired or no longer usable
|
|
52
|
+
429 auth endpoint rate limit exceeded
|
|
53
|
+
503 health check dependency failure
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Organisation-Scoped Routes
|
|
57
|
+
|
|
58
|
+
Most business routes are scoped under:
|
|
59
|
+
|
|
60
|
+
```txt
|
|
61
|
+
/organisations/:orgId/...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
For those routes:
|
|
65
|
+
|
|
66
|
+
- `JwtAuthGuard` verifies the user identity.
|
|
67
|
+
- `OrgScopeGuard` loads the active membership for `orgId`.
|
|
68
|
+
- `PermissionGuard` checks the route permissions.
|
|
69
|
+
- `route-permission-registry.ts` is validated at startup and in tests, so
|
|
70
|
+
permission-gated routes must stay registered.
|
|
71
|
+
|
|
72
|
+
Use `POST /organisations` first if you need a fresh `orgId`.
|
|
73
|
+
|
|
74
|
+
## Endpoint Groups
|
|
75
|
+
|
|
76
|
+
| Group | Purpose |
|
|
77
|
+
| -------------- | ----------------------------------------------------------------------- |
|
|
78
|
+
| Health | Confirm API and database availability. |
|
|
79
|
+
| Auth | Signup, login, refresh, logout, Google OAuth handoff, current user. |
|
|
80
|
+
| Organisations | Create an organisation and seed owner role/settings. |
|
|
81
|
+
| Memberships | Read memberships and manage status, role, owner, billing contact. |
|
|
82
|
+
| Invitations | Invite, resend, revoke, accept, and decline organisation invitations. |
|
|
83
|
+
| Access control | List permissions, route-permission mapping, and manage roles. |
|
|
84
|
+
| Settings | Read and update typed organisation settings. |
|
|
85
|
+
| Audit | Search organisation audit events. |
|
|
86
|
+
| Sample | Copy-paste reference for request context, RBAC, DTOs, and audit writes. |
|
|
87
|
+
|
|
88
|
+
## REST Client File
|
|
89
|
+
|
|
90
|
+
`docs/api.http` contains a runnable flow for VS Code REST Client or compatible
|
|
91
|
+
HTTP clients. It mirrors the Swagger groups and is useful when a developer wants
|
|
92
|
+
repeatable API calls outside the browser.
|
|
93
|
+
|
|
94
|
+
Update the variables at the top after setup:
|
|
95
|
+
|
|
96
|
+
```http
|
|
97
|
+
@baseUrl = http://localhost:3000
|
|
98
|
+
@email = owner@example.com
|
|
99
|
+
@password = replace-with-printed-setup-password
|
|
100
|
+
@orgId = replace-with-org-id
|
|
101
|
+
@roleId = replace-with-role-id
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Keeping Swagger Complete
|
|
105
|
+
|
|
106
|
+
When adding a new endpoint, add these pieces in the same change:
|
|
107
|
+
|
|
108
|
+
- `@ApiTags` on the controller, or reuse the existing controller tag.
|
|
109
|
+
- `@ApiOperation` on every handler.
|
|
110
|
+
- `@ApiBearerAuth` for protected routes.
|
|
111
|
+
- `@ApiParam` and query DTO properties for route/query inputs.
|
|
112
|
+
- `@ApiProperty` or `@ApiPropertyOptional` on request DTO fields.
|
|
113
|
+
- A response DTO and `@ApiOkResponse` or `@ApiCreatedResponse`.
|
|
114
|
+
- `ApiErrorResponses` or `ApiProtectedErrorResponses`.
|
|
115
|
+
- A matching entry in `docs/api.http` for important flows.
|
|
116
|
+
|
|
117
|
+
Run these checks before handing the module to another developer:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run lint
|
|
121
|
+
npm run build
|
|
122
|
+
npm test
|
|
123
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
## One-Command Local Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm run setup:local
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The setup script:
|
|
10
|
+
|
|
11
|
+
- creates `.env` from `.env.example` if needed
|
|
12
|
+
- fills missing `DATABASE_URL`, `TEST_DATABASE_URL`, `JWT_SECRET`, and `TEST_USER_PASSWORD`
|
|
13
|
+
- creates the dev and test PostgreSQL databases if they do not exist
|
|
14
|
+
- generates Prisma Client
|
|
15
|
+
- deploys migrations
|
|
16
|
+
- seeds the permission catalogue
|
|
17
|
+
- seeds a demo owner, organisation, roles, settings, and membership
|
|
18
|
+
- prints the demo login credentials
|
|
19
|
+
|
|
20
|
+
It is non-destructive. It does not drop, reset, truncate, or overwrite existing application data.
|
|
21
|
+
|
|
22
|
+
## PostgreSQL
|
|
23
|
+
|
|
24
|
+
The default expects a local PostgreSQL server with user `postgres`, password `postgres`, and host `localhost:5432`.
|
|
25
|
+
|
|
26
|
+
```txt
|
|
27
|
+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app
|
|
28
|
+
TEST_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app_test
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If your database differs, edit `.env` and rerun `npm run setup:local`.
|
|
32
|
+
|
|
33
|
+
## Start The API
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm run start:dev
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Open:
|
|
40
|
+
|
|
41
|
+
```txt
|
|
42
|
+
http://localhost:3000/health
|
|
43
|
+
http://localhost:3000/docs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Login through Swagger with the printed demo credentials and authorize with the returned bearer token.
|
|
47
|
+
|
|
48
|
+
Read `docs/API_REFERENCE.md` for the full Swagger workflow, response shapes,
|
|
49
|
+
error envelope, endpoint groups, and the checklist for keeping future endpoints
|
|
50
|
+
documented.
|
|
51
|
+
|
|
52
|
+
## Useful Commands
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm run setup:local
|
|
56
|
+
npm run db:create
|
|
57
|
+
npm run lint
|
|
58
|
+
npm run format
|
|
59
|
+
npm run build
|
|
60
|
+
npm test
|
|
61
|
+
npm run test:e2e
|
|
62
|
+
npm run start:dev
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`npm run db:reset` is intentionally separate and destructive.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Module Completion Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist before considering a new organisation-scoped module complete.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- [ ] Module follows `application`, `dto`, `presentation`, `types`, and `infrastructure` folders where relevant.
|
|
8
|
+
- [ ] Controller route is path-scoped with `/organisations/:orgId/...` for organisation data.
|
|
9
|
+
- [ ] Controller uses `JwtAuthGuard`, `OrgScopeGuard`, and `PermissionGuard`.
|
|
10
|
+
- [ ] Every protected handler has `@RequirePermissions(...)`.
|
|
11
|
+
- [ ] Route-permission registry includes every protected route.
|
|
12
|
+
|
|
13
|
+
## Security
|
|
14
|
+
|
|
15
|
+
- [ ] Service calls `requestContext.assertOrgScope(orgId)` before reading or writing organisation data.
|
|
16
|
+
- [ ] Queries filter by `orgId` explicitly.
|
|
17
|
+
- [ ] Raw SQL, if any, filters by `orgId` explicitly.
|
|
18
|
+
- [ ] Cross-organisation IDs are rejected or return not found.
|
|
19
|
+
- [ ] Owner-only behavior is enforced in the service layer, not only through route metadata.
|
|
20
|
+
|
|
21
|
+
## Data and Transactions
|
|
22
|
+
|
|
23
|
+
- [ ] Writes that touch multiple rows use a transaction.
|
|
24
|
+
- [ ] Uniqueness and integrity rules are backed by database constraints where practical.
|
|
25
|
+
- [ ] Prisma known errors that users can trigger are mapped to clear HTTP errors.
|
|
26
|
+
- [ ] Cache invalidation, if needed, runs after the transaction commits.
|
|
27
|
+
|
|
28
|
+
## Audit and Settings
|
|
29
|
+
|
|
30
|
+
- [ ] Security-sensitive, billing, identity, access-control, and organisation-configuration writes create audit rows.
|
|
31
|
+
- [ ] Audit metadata includes previous and next values when useful.
|
|
32
|
+
- [ ] New setting keys are added to typed setting definitions and validated.
|
|
33
|
+
|
|
34
|
+
## Verification
|
|
35
|
+
|
|
36
|
+
- [ ] Build passes.
|
|
37
|
+
- [ ] Prisma generate passes.
|
|
38
|
+
- [ ] Happy-path API flow is verified.
|
|
39
|
+
- [ ] Missing-permission and cross-org access are verified.
|
|
40
|
+
- [ ] Seed/setup scripts remain idempotent.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# OAuth
|
|
2
|
+
|
|
3
|
+
## Google OAuth
|
|
4
|
+
|
|
5
|
+
Google OAuth is optional and disabled by default.
|
|
6
|
+
|
|
7
|
+
When enabled, the flow is:
|
|
8
|
+
|
|
9
|
+
```txt
|
|
10
|
+
GET /auth/google
|
|
11
|
+
GET /auth/google/callback
|
|
12
|
+
POST /auth/oauth/exchange
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The callback redirects to `AUTH_GOOGLE_SUCCESS_REDIRECT_URL` with a short-lived one-time `code`. The client exchanges that code with `POST /auth/oauth/exchange` to receive JWT access and refresh tokens.
|
|
16
|
+
|
|
17
|
+
Failures redirect to `AUTH_GOOGLE_ERROR_REDIRECT_URL?error=oauth_failed`.
|
|
18
|
+
|
|
19
|
+
## State And PKCE
|
|
20
|
+
|
|
21
|
+
Google OAuth uses state and PKCE. A short-lived `oauth.sid` session cookie is scoped only to `/auth/google` and `/auth/google/callback` so Passport can store and verify the OAuth state and PKCE code verifier during the handshake.
|
|
22
|
+
|
|
23
|
+
The rest of the app remains stateless JWT. There is no `passport.session()` and no persistent login session.
|
|
24
|
+
|
|
25
|
+
## Auth Endpoint Hardening
|
|
26
|
+
|
|
27
|
+
Login uses the same generic failure response and a dummy-hash path for missing users so
|
|
28
|
+
the password-check timing stays consistent. Signup returns a generic conflict response
|
|
29
|
+
for duplicate emails, but it is still weakly enumerable by design because successful
|
|
30
|
+
signup immediately returns tokens. Full non-enumerable signup requires a deferred
|
|
31
|
+
email-verification flow; rate-limit auth endpoints before exposing the app publicly.
|
|
32
|
+
|
|
33
|
+
Set these values when `AUTH_GOOGLE_ENABLED=true`:
|
|
34
|
+
|
|
35
|
+
```txt
|
|
36
|
+
AUTH_GOOGLE_CLIENT_ID=
|
|
37
|
+
AUTH_GOOGLE_CLIENT_SECRET=
|
|
38
|
+
AUTH_GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback
|
|
39
|
+
AUTH_GOOGLE_SUCCESS_REDIRECT_URL=http://localhost:3000/auth/google/success
|
|
40
|
+
AUTH_GOOGLE_ERROR_REDIRECT_URL=http://localhost:3000/auth/google/error
|
|
41
|
+
SESSION_SECRET=
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`SESSION_SECRET` must be at least 32 random characters.
|
|
45
|
+
|
|
46
|
+
The default `express-session` memory store is intended for development and single-instance deployments. In multi-instance production deployments, use sticky sessions or replace the store with a shared store such as Redis.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Sample Module
|
|
2
|
+
|
|
3
|
+
The generated app includes a small `SampleModule` under `src/modules/sample`.
|
|
4
|
+
|
|
5
|
+
It exists to demonstrate the expected shape of an organisation-scoped module:
|
|
6
|
+
|
|
7
|
+
- path-scoped routes under `/organisations/:orgId/sample`
|
|
8
|
+
- `JwtAuthGuard`, `OrgScopeGuard`, and `PermissionGuard`
|
|
9
|
+
- explicit permission metadata
|
|
10
|
+
- service-level `requestContext.assertOrgScope(orgId)`
|
|
11
|
+
- request-context reads in service responses
|
|
12
|
+
- DTO validation
|
|
13
|
+
- audit writes for mutating commands
|
|
14
|
+
- route-permission registry entries
|
|
15
|
+
|
|
16
|
+
## Routes
|
|
17
|
+
|
|
18
|
+
```txt
|
|
19
|
+
GET /organisations/:orgId/sample/status organisations.read
|
|
20
|
+
POST /organisations/:orgId/sample/echo organisations.update
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The module is intentionally simple and has no database table. Replace or delete it when your first real business module is ready.
|