@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.
Files changed (151) hide show
  1. package/LICENSE +144 -0
  2. package/bin/index.mjs +2 -0
  3. package/package.json +36 -0
  4. package/src/copy.mjs +45 -0
  5. package/src/log.mjs +23 -0
  6. package/src/main.mjs +221 -0
  7. package/src/run.mjs +52 -0
  8. package/src/substitute.mjs +40 -0
  9. package/template/.env.example +36 -0
  10. package/template/.github/workflows/ci.yml +36 -0
  11. package/template/.husky/pre-commit +1 -0
  12. package/template/README.md +146 -0
  13. package/template/_editorconfig +8 -0
  14. package/template/_gitignore +7 -0
  15. package/template/_nvmrc +1 -0
  16. package/template/_package.json +107 -0
  17. package/template/_prettierignore +5 -0
  18. package/template/_prettierrc +6 -0
  19. package/template/docs/API_REFERENCE.md +123 -0
  20. package/template/docs/GETTING_STARTED.md +65 -0
  21. package/template/docs/MODULE_COMPLETION_CHECKLIST.md +40 -0
  22. package/template/docs/OAUTH.md +46 -0
  23. package/template/docs/SAMPLE_MODULE.md +23 -0
  24. package/template/docs/api.http +269 -0
  25. package/template/eslint.config.mjs +51 -0
  26. package/template/nest-cli.json +8 -0
  27. package/template/prisma/migrations/20260530000000_init/migration.sql +248 -0
  28. package/template/prisma/schema.prisma +299 -0
  29. package/template/prisma/seed.ts +44 -0
  30. package/template/scripts/db-create.mjs +126 -0
  31. package/template/scripts/gen-module.mjs +217 -0
  32. package/template/scripts/seed-test-user-org.ts +264 -0
  33. package/template/scripts/setup-local.mjs +224 -0
  34. package/template/scripts/test-db.mjs +69 -0
  35. package/template/src/app.module.ts +58 -0
  36. package/template/src/common/decorators/.gitkeep +1 -0
  37. package/template/src/common/dto/error-response.dto.ts +17 -0
  38. package/template/src/common/dto/membership-response.dto.ts +51 -0
  39. package/template/src/common/dto/mutation-response.dto.ts +11 -0
  40. package/template/src/common/dto/role-summary.dto.ts +18 -0
  41. package/template/src/common/dto/user-summary.dto.ts +23 -0
  42. package/template/src/common/enums/.gitkeep +1 -0
  43. package/template/src/common/filters/.gitkeep +1 -0
  44. package/template/src/common/filters/http-exception.filter.ts +78 -0
  45. package/template/src/common/guards/.gitkeep +1 -0
  46. package/template/src/common/interceptors/.gitkeep +1 -0
  47. package/template/src/common/pipes/.gitkeep +1 -0
  48. package/template/src/common/swagger/api-error-responses.ts +54 -0
  49. package/template/src/common/types/.gitkeep +1 -0
  50. package/template/src/config/app.config.ts +7 -0
  51. package/template/src/config/auth.config.ts +33 -0
  52. package/template/src/config/database.config.ts +6 -0
  53. package/template/src/config/env.validation.ts +131 -0
  54. package/template/src/config/index.ts +5 -0
  55. package/template/src/config/rbac.config.ts +6 -0
  56. package/template/src/database/prisma/prisma-transaction.ts +22 -0
  57. package/template/src/database/prisma/prisma.module.ts +9 -0
  58. package/template/src/database/prisma/prisma.service.ts +16 -0
  59. package/template/src/main.ts +42 -0
  60. package/template/src/modules/access-control/access-control.module.ts +24 -0
  61. package/template/src/modules/access-control/application/route-registry.validator.ts +289 -0
  62. package/template/src/modules/access-control/application/services/ability.factory.ts +28 -0
  63. package/template/src/modules/access-control/application/services/access-control.service.ts +478 -0
  64. package/template/src/modules/access-control/application/services/permission.guard.ts +77 -0
  65. package/template/src/modules/access-control/application/services/rbac-cache.service.ts +148 -0
  66. package/template/src/modules/access-control/dto/access-control-response.dto.ts +79 -0
  67. package/template/src/modules/access-control/dto/create-role.dto.ts +18 -0
  68. package/template/src/modules/access-control/dto/update-role-permissions.dto.ts +23 -0
  69. package/template/src/modules/access-control/dto/update-role.dto.ts +19 -0
  70. package/template/src/modules/access-control/presentation/access-control.controller.ts +157 -0
  71. package/template/src/modules/access-control/presentation/permissions.decorator.ts +8 -0
  72. package/template/src/modules/access-control/presentation/public.decorator.ts +7 -0
  73. package/template/src/modules/access-control/types/permission-key.ts +37 -0
  74. package/template/src/modules/access-control/types/rbac-context.ts +11 -0
  75. package/template/src/modules/access-control/types/route-permission-registry.ts +129 -0
  76. package/template/src/modules/audit/application/services/audit.service.ts +97 -0
  77. package/template/src/modules/audit/audit.module.ts +13 -0
  78. package/template/src/modules/audit/dto/audit-response.dto.ts +75 -0
  79. package/template/src/modules/audit/dto/list-audit-logs-query.dto.ts +75 -0
  80. package/template/src/modules/audit/presentation/audit.controller.ts +37 -0
  81. package/template/src/modules/auth/application/services/auth.service.ts +509 -0
  82. package/template/src/modules/auth/application/services/password.service.ts +15 -0
  83. package/template/src/modules/auth/application/services/token.service.ts +95 -0
  84. package/template/src/modules/auth/auth.module.ts +73 -0
  85. package/template/src/modules/auth/dto/auth-response.dto.ts +29 -0
  86. package/template/src/modules/auth/dto/login.dto.ts +15 -0
  87. package/template/src/modules/auth/dto/logout.dto.ts +3 -0
  88. package/template/src/modules/auth/dto/oauth-exchange.dto.ts +15 -0
  89. package/template/src/modules/auth/dto/refresh-token.dto.ts +14 -0
  90. package/template/src/modules/auth/dto/signup.dto.ts +27 -0
  91. package/template/src/modules/auth/infrastructure/passport/google-auth.guard.ts +27 -0
  92. package/template/src/modules/auth/infrastructure/passport/google.strategy.ts +56 -0
  93. package/template/src/modules/auth/infrastructure/passport/jwt-auth.guard.ts +5 -0
  94. package/template/src/modules/auth/infrastructure/passport/jwt.strategy.ts +43 -0
  95. package/template/src/modules/auth/presentation/auth.controller.ts +148 -0
  96. package/template/src/modules/auth/presentation/current-user.decorator.ts +11 -0
  97. package/template/src/modules/auth/presentation/google-oauth-exception.filter.ts +33 -0
  98. package/template/src/modules/auth/types/authenticated-user.ts +7 -0
  99. package/template/src/modules/auth/types/google-auth-profile.ts +6 -0
  100. package/template/src/modules/auth/types/jwt-payload.ts +5 -0
  101. package/template/src/modules/health/dto/health-response.dto.ts +9 -0
  102. package/template/src/modules/health/health.module.ts +7 -0
  103. package/template/src/modules/health/presentation/health.controller.ts +33 -0
  104. package/template/src/modules/invitations/application/services/invitations.service.ts +967 -0
  105. package/template/src/modules/invitations/dto/accept-invitation.dto.ts +24 -0
  106. package/template/src/modules/invitations/dto/create-invitation.dto.ts +100 -0
  107. package/template/src/modules/invitations/dto/invitation-response.dto.ts +108 -0
  108. package/template/src/modules/invitations/dto/invitation-token.dto.ts +15 -0
  109. package/template/src/modules/invitations/invitations.module.ts +12 -0
  110. package/template/src/modules/invitations/presentation/invitations.controller.ts +149 -0
  111. package/template/src/modules/memberships/application/services/memberships.service.ts +455 -0
  112. package/template/src/modules/memberships/dto/transfer-owner.dto.ts +11 -0
  113. package/template/src/modules/memberships/dto/update-billing-contact.dto.ts +8 -0
  114. package/template/src/modules/memberships/dto/update-membership-owner.dto.ts +8 -0
  115. package/template/src/modules/memberships/dto/update-membership-role.dto.ts +11 -0
  116. package/template/src/modules/memberships/dto/update-membership-status.dto.ts +9 -0
  117. package/template/src/modules/memberships/memberships.module.ts +12 -0
  118. package/template/src/modules/memberships/presentation/memberships.controller.ts +193 -0
  119. package/template/src/modules/organisations/application/services/organisations.service.ts +147 -0
  120. package/template/src/modules/organisations/dto/create-organisation.dto.ts +32 -0
  121. package/template/src/modules/organisations/dto/organisation-response.dto.ts +62 -0
  122. package/template/src/modules/organisations/infrastructure/repositories/organisations.repository.ts +24 -0
  123. package/template/src/modules/organisations/organisations.module.ts +12 -0
  124. package/template/src/modules/organisations/presentation/organisations.controller.ts +37 -0
  125. package/template/src/modules/organisations/types/default-organisation-data.ts +18 -0
  126. package/template/src/modules/platform-admin/.gitkeep +1 -0
  127. package/template/src/modules/request-context/application/services/request-context.service.ts +79 -0
  128. package/template/src/modules/request-context/presentation/org-scope.guard.ts +26 -0
  129. package/template/src/modules/request-context/presentation/request-context.interceptor.ts +26 -0
  130. package/template/src/modules/request-context/presentation/request-context.middleware.ts +31 -0
  131. package/template/src/modules/request-context/request-context.module.ts +25 -0
  132. package/template/src/modules/request-context/types/request-context.ts +29 -0
  133. package/template/src/modules/sample/application/services/sample.service.ts +67 -0
  134. package/template/src/modules/sample/dto/sample-echo.dto.ts +10 -0
  135. package/template/src/modules/sample/dto/sample-response.dto.ts +41 -0
  136. package/template/src/modules/sample/presentation/sample.controller.ts +63 -0
  137. package/template/src/modules/sample/sample.module.ts +11 -0
  138. package/template/src/modules/settings/application/services/settings.service.ts +139 -0
  139. package/template/src/modules/settings/dto/setting-response.dto.ts +27 -0
  140. package/template/src/modules/settings/dto/update-setting.dto.ts +16 -0
  141. package/template/src/modules/settings/presentation/settings.controller.ts +66 -0
  142. package/template/src/modules/settings/settings.module.ts +12 -0
  143. package/template/src/modules/settings/types/setting-definitions.ts +104 -0
  144. package/template/src/modules/users/.gitkeep +1 -0
  145. package/template/test/.gitkeep +1 -0
  146. package/template/test/jest-e2e.json +9 -0
  147. package/template/test/permission.guard.spec.ts +22 -0
  148. package/template/test/route-registry.validator.spec.ts +90 -0
  149. package/template/test/security.e2e-spec.ts +102 -0
  150. package/template/tsconfig.build.json +4 -0
  151. package/template/tsconfig.json +18 -0
@@ -0,0 +1,269 @@
1
+ @baseUrl = http://localhost:3000
2
+ @email = owner@example.com
3
+ @password = replace-with-printed-setup-password
4
+ @newUserEmail = new.owner@example.com
5
+ @newUserPassword = new-owner-password-123
6
+ @refreshToken = replace-with-refresh-token
7
+ @oauthCode = replace-with-oauth-exchange-code
8
+ @orgId = replace-with-org-id
9
+ @membershipId = replace-with-membership-id
10
+ @targetMembershipId = replace-with-target-membership-id
11
+ @roleId = replace-with-role-id
12
+ @newRoleId = replace-with-new-role-id
13
+ @invitationId = replace-with-invitation-id
14
+ @invitationToken = replace-with-invitation-token
15
+
16
+ ### Health
17
+ GET {{baseUrl}}/health
18
+
19
+ ### Auth: signup
20
+ # @name signup
21
+ POST {{baseUrl}}/auth/signup
22
+ Content-Type: application/json
23
+
24
+ {
25
+ "email": "{{newUserEmail}}",
26
+ "password": "{{newUserPassword}}",
27
+ "displayName": "New Owner"
28
+ }
29
+
30
+ ### Auth: login
31
+ # @name login
32
+ POST {{baseUrl}}/auth/login
33
+ Content-Type: application/json
34
+
35
+ {
36
+ "email": "{{email}}",
37
+ "password": "{{password}}"
38
+ }
39
+
40
+ ### Auth: refresh token
41
+ # @name refresh
42
+ POST {{baseUrl}}/auth/refresh
43
+ Content-Type: application/json
44
+
45
+ {
46
+ "refreshToken": "{{refreshToken}}"
47
+ }
48
+
49
+ ### Auth: Google OAuth start
50
+ GET {{baseUrl}}/auth/google
51
+
52
+ ### Auth: Google OAuth callback
53
+ GET {{baseUrl}}/auth/google/callback
54
+
55
+ ### Auth: exchange Google OAuth one-time code
56
+ POST {{baseUrl}}/auth/oauth/exchange
57
+ Content-Type: application/json
58
+
59
+ {
60
+ "code": "{{oauthCode}}"
61
+ }
62
+
63
+ ### Auth: current user
64
+ GET {{baseUrl}}/auth/me
65
+ Authorization: Bearer {{login.response.body.accessToken}}
66
+
67
+ ### Organisations: create organisation
68
+ # @name createOrg
69
+ POST {{baseUrl}}/organisations
70
+ Authorization: Bearer {{login.response.body.accessToken}}
71
+ Content-Type: application/json
72
+
73
+ {
74
+ "name": "Demo REST Client Org",
75
+ "slug": "demo-rest-client-org"
76
+ }
77
+
78
+ ### Memberships: current membership
79
+ GET {{baseUrl}}/organisations/{{orgId}}/memberships/me
80
+ Authorization: Bearer {{login.response.body.accessToken}}
81
+
82
+ ### Memberships: list
83
+ GET {{baseUrl}}/organisations/{{orgId}}/memberships
84
+ Authorization: Bearer {{login.response.body.accessToken}}
85
+
86
+ ### Memberships: update status
87
+ PATCH {{baseUrl}}/organisations/{{orgId}}/memberships/{{membershipId}}/status
88
+ Authorization: Bearer {{login.response.body.accessToken}}
89
+ Content-Type: application/json
90
+
91
+ {
92
+ "status": "SUSPENDED"
93
+ }
94
+
95
+ ### Memberships: assign role
96
+ PATCH {{baseUrl}}/organisations/{{orgId}}/memberships/{{membershipId}}/role
97
+ Authorization: Bearer {{login.response.body.accessToken}}
98
+ Content-Type: application/json
99
+
100
+ {
101
+ "roleId": "{{roleId}}"
102
+ }
103
+
104
+ ### Memberships: update owner flag
105
+ PATCH {{baseUrl}}/organisations/{{orgId}}/memberships/{{membershipId}}/owner
106
+ Authorization: Bearer {{login.response.body.accessToken}}
107
+ Content-Type: application/json
108
+
109
+ {
110
+ "isOwner": true
111
+ }
112
+
113
+ ### Memberships: update billing contact flag
114
+ PATCH {{baseUrl}}/organisations/{{orgId}}/memberships/{{membershipId}}/billing-contact
115
+ Authorization: Bearer {{login.response.body.accessToken}}
116
+ Content-Type: application/json
117
+
118
+ {
119
+ "isBillingContact": true
120
+ }
121
+
122
+ ### Memberships: transfer owner
123
+ POST {{baseUrl}}/organisations/{{orgId}}/memberships/transfer-owner
124
+ Authorization: Bearer {{login.response.body.accessToken}}
125
+ Content-Type: application/json
126
+
127
+ {
128
+ "toMembershipId": "{{targetMembershipId}}"
129
+ }
130
+
131
+ ### Invitations: create
132
+ # @name invite
133
+ POST {{baseUrl}}/organisations/{{orgId}}/invitations
134
+ Authorization: Bearer {{login.response.body.accessToken}}
135
+ Content-Type: application/json
136
+
137
+ {
138
+ "targetType": "EMAIL",
139
+ "targetValue": "teammate@example.com",
140
+ "roleId": "{{roleId}}"
141
+ }
142
+
143
+ ### Invitations: list
144
+ GET {{baseUrl}}/organisations/{{orgId}}/invitations
145
+ Authorization: Bearer {{login.response.body.accessToken}}
146
+
147
+ ### Invitations: revoke
148
+ POST {{baseUrl}}/organisations/{{orgId}}/invitations/{{invitationId}}/revoke
149
+ Authorization: Bearer {{login.response.body.accessToken}}
150
+
151
+ ### Invitations: resend
152
+ # @name resendInvitation
153
+ POST {{baseUrl}}/organisations/{{orgId}}/invitations/{{invitationId}}/resend
154
+ Authorization: Bearer {{login.response.body.accessToken}}
155
+
156
+ ### Invitations: accept
157
+ POST {{baseUrl}}/invitations/accept
158
+ Content-Type: application/json
159
+
160
+ {
161
+ "token": "{{invitationToken}}",
162
+ "password": "teammate-password-123",
163
+ "displayName": "Teammate"
164
+ }
165
+
166
+ ### Invitations: decline
167
+ POST {{baseUrl}}/invitations/decline
168
+ Content-Type: application/json
169
+
170
+ {
171
+ "token": "{{invitationToken}}"
172
+ }
173
+
174
+ ### Access control: list permissions
175
+ GET {{baseUrl}}/organisations/{{orgId}}/access-control/permissions
176
+ Authorization: Bearer {{login.response.body.accessToken}}
177
+
178
+ ### Access control: list route permissions
179
+ GET {{baseUrl}}/organisations/{{orgId}}/access-control/route-permissions
180
+ Authorization: Bearer {{login.response.body.accessToken}}
181
+
182
+ ### Access control: list roles
183
+ GET {{baseUrl}}/organisations/{{orgId}}/access-control/roles
184
+ Authorization: Bearer {{login.response.body.accessToken}}
185
+
186
+ ### Access control: create role
187
+ # @name createRole
188
+ POST {{baseUrl}}/organisations/{{orgId}}/access-control/roles
189
+ Authorization: Bearer {{login.response.body.accessToken}}
190
+ Content-Type: application/json
191
+
192
+ {
193
+ "name": "Support",
194
+ "description": "Can review support-facing records."
195
+ }
196
+
197
+ ### Access control: update role
198
+ PATCH {{baseUrl}}/organisations/{{orgId}}/access-control/roles/{{newRoleId}}
199
+ Authorization: Bearer {{login.response.body.accessToken}}
200
+ Content-Type: application/json
201
+
202
+ {
203
+ "name": "Support Lead",
204
+ "description": "Can manage support-facing records."
205
+ }
206
+
207
+ ### Access control: delete role
208
+ DELETE {{baseUrl}}/organisations/{{orgId}}/access-control/roles/{{newRoleId}}
209
+ Authorization: Bearer {{login.response.body.accessToken}}
210
+
211
+ ### Access control: list role permissions
212
+ GET {{baseUrl}}/organisations/{{orgId}}/access-control/roles/{{roleId}}/permissions
213
+ Authorization: Bearer {{login.response.body.accessToken}}
214
+
215
+ ### Access control: replace role permissions
216
+ PUT {{baseUrl}}/organisations/{{orgId}}/access-control/roles/{{roleId}}/permissions
217
+ Authorization: Bearer {{login.response.body.accessToken}}
218
+ Content-Type: application/json
219
+
220
+ {
221
+ "permissionKeys": ["memberships.read", "settings.read", "audit.read"]
222
+ }
223
+
224
+ ### Settings: list
225
+ GET {{baseUrl}}/organisations/{{orgId}}/settings
226
+ Authorization: Bearer {{login.response.body.accessToken}}
227
+
228
+ ### Settings: get one
229
+ GET {{baseUrl}}/organisations/{{orgId}}/settings/timezone
230
+ Authorization: Bearer {{login.response.body.accessToken}}
231
+
232
+ ### Settings: update one
233
+ PATCH {{baseUrl}}/organisations/{{orgId}}/settings
234
+ Authorization: Bearer {{login.response.body.accessToken}}
235
+ Content-Type: application/json
236
+
237
+ {
238
+ "key": "timezone",
239
+ "value": "UTC"
240
+ }
241
+
242
+ ### Audit: list
243
+ GET {{baseUrl}}/organisations/{{orgId}}/audit?limit=25
244
+ Authorization: Bearer {{login.response.body.accessToken}}
245
+
246
+ ### Audit: filtered list
247
+ GET {{baseUrl}}/organisations/{{orgId}}/audit?action=organisation.setting.update&targetType=OrganisationSetting&limit=10
248
+ Authorization: Bearer {{login.response.body.accessToken}}
249
+
250
+ ### Sample: status
251
+ GET {{baseUrl}}/organisations/{{orgId}}/sample/status
252
+ Authorization: Bearer {{login.response.body.accessToken}}
253
+
254
+ ### Sample: echo
255
+ POST {{baseUrl}}/organisations/{{orgId}}/sample/echo
256
+ Authorization: Bearer {{login.response.body.accessToken}}
257
+ Content-Type: application/json
258
+
259
+ {
260
+ "message": "Hello from REST Client"
261
+ }
262
+
263
+ ### Auth: logout
264
+ POST {{baseUrl}}/auth/logout
265
+ Content-Type: application/json
266
+
267
+ {
268
+ "refreshToken": "{{refreshToken}}"
269
+ }
@@ -0,0 +1,51 @@
1
+ import js from '@eslint/js';
2
+ import prettier from 'eslint-config-prettier';
3
+ import globals from 'globals';
4
+ import { dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import tseslint from 'typescript-eslint';
7
+
8
+ const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
9
+
10
+ export default tseslint.config(
11
+ js.configs.recommended,
12
+ ...tseslint.configs.recommendedTypeChecked,
13
+ prettier,
14
+ {
15
+ languageOptions: {
16
+ ecmaVersion: 2023,
17
+ globals: {
18
+ ...globals.node,
19
+ ...globals.jest,
20
+ },
21
+ parserOptions: {
22
+ projectService: {
23
+ allowDefaultProject: ['*.mjs', 'scripts/*.mjs'],
24
+ },
25
+ tsconfigRootDir,
26
+ },
27
+ },
28
+ rules: {
29
+ '@typescript-eslint/no-floating-promises': 'error',
30
+ '@typescript-eslint/no-misused-promises': 'error',
31
+ '@typescript-eslint/no-require-imports': 'off',
32
+ '@typescript-eslint/no-unnecessary-type-assertion': 'off',
33
+ '@typescript-eslint/no-unsafe-argument': 'off',
34
+ '@typescript-eslint/no-unsafe-assignment': 'off',
35
+ '@typescript-eslint/no-unsafe-call': 'off',
36
+ '@typescript-eslint/no-unsafe-member-access': 'off',
37
+ '@typescript-eslint/no-unsafe-return': 'off',
38
+ '@typescript-eslint/no-unused-vars': [
39
+ 'error',
40
+ {
41
+ argsIgnorePattern: '^_',
42
+ caughtErrorsIgnorePattern: '^_',
43
+ varsIgnorePattern: '^_',
44
+ },
45
+ ],
46
+ },
47
+ },
48
+ {
49
+ ignores: ['coverage/**', 'dist/**', 'node_modules/**'],
50
+ },
51
+ );
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true
7
+ }
8
+ }
@@ -0,0 +1,248 @@
1
+ -- CreateEnum
2
+ CREATE TYPE "OrganisationStatus" AS ENUM ('ACTIVE', 'SUSPENDED', 'DELETED');
3
+
4
+ -- CreateEnum
5
+ CREATE TYPE "MembershipStatus" AS ENUM ('ACTIVE', 'SUSPENDED', 'REVOKED');
6
+
7
+ -- CreateEnum
8
+ CREATE TYPE "AuthProvider" AS ENUM ('EMAIL_PASSWORD', 'GOOGLE', 'FACEBOOK', 'MOBILE_OTP', 'MAGIC_LINK');
9
+
10
+ -- CreateEnum
11
+ CREATE TYPE "InvitationTargetType" AS ENUM ('EMAIL', 'MOBILE');
12
+
13
+ -- CreateEnum
14
+ CREATE TYPE "InvitationStatus" AS ENUM ('PENDING', 'ACCEPTED', 'DECLINED', 'EXPIRED', 'REVOKED');
15
+
16
+ -- CreateTable
17
+ CREATE TABLE "User" (
18
+ "id" TEXT NOT NULL,
19
+ "email" TEXT,
20
+ "mobile" TEXT,
21
+ "passwordHash" TEXT,
22
+ "displayName" TEXT,
23
+ "isActive" BOOLEAN NOT NULL DEFAULT true,
24
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
25
+ "updatedAt" TIMESTAMP(3) NOT NULL,
26
+
27
+ CONSTRAINT "User_pkey" PRIMARY KEY ("id")
28
+ );
29
+
30
+ -- CreateTable
31
+ CREATE TABLE "AuthIdentity" (
32
+ "id" TEXT NOT NULL,
33
+ "userId" TEXT NOT NULL,
34
+ "provider" "AuthProvider" NOT NULL,
35
+ "providerUserId" TEXT NOT NULL,
36
+ "email" TEXT,
37
+ "mobile" TEXT,
38
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
39
+ "updatedAt" TIMESTAMP(3) NOT NULL,
40
+
41
+ CONSTRAINT "AuthIdentity_pkey" PRIMARY KEY ("id")
42
+ );
43
+
44
+ -- CreateTable
45
+ CREATE TABLE "RefreshToken" (
46
+ "id" TEXT NOT NULL,
47
+ "userId" TEXT NOT NULL,
48
+ "tokenHash" TEXT NOT NULL,
49
+ "expiresAt" TIMESTAMP(3) NOT NULL,
50
+ "revokedAt" TIMESTAMP(3),
51
+ "replacedByTokenId" TEXT,
52
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
53
+
54
+ CONSTRAINT "RefreshToken_pkey" PRIMARY KEY ("id")
55
+ );
56
+
57
+ -- CreateTable
58
+ CREATE TABLE "AuthExchangeCode" (
59
+ "id" TEXT NOT NULL,
60
+ "userId" TEXT NOT NULL,
61
+ "tokenHash" TEXT NOT NULL,
62
+ "expiresAt" TIMESTAMP(3) NOT NULL,
63
+ "usedAt" TIMESTAMP(3),
64
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
65
+
66
+ CONSTRAINT "AuthExchangeCode_pkey" PRIMARY KEY ("id")
67
+ );
68
+
69
+ -- CreateTable
70
+ CREATE TABLE "PasswordResetToken" (
71
+ "id" TEXT NOT NULL,
72
+ "userId" TEXT NOT NULL,
73
+ "tokenHash" TEXT NOT NULL,
74
+ "expiresAt" TIMESTAMP(3) NOT NULL,
75
+ "usedAt" TIMESTAMP(3),
76
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
77
+
78
+ CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id")
79
+ );
80
+
81
+ -- CreateTable
82
+ CREATE TABLE "Organisation" (
83
+ "id" TEXT NOT NULL,
84
+ "name" TEXT NOT NULL,
85
+ "slug" TEXT NOT NULL,
86
+ "status" "OrganisationStatus" NOT NULL DEFAULT 'ACTIVE',
87
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
88
+ "updatedAt" TIMESTAMP(3) NOT NULL,
89
+
90
+ CONSTRAINT "Organisation_pkey" PRIMARY KEY ("id")
91
+ );
92
+
93
+ -- CreateTable
94
+ CREATE TABLE "OrganisationSetting" (
95
+ "id" TEXT NOT NULL,
96
+ "orgId" TEXT NOT NULL,
97
+ "key" TEXT NOT NULL,
98
+ "value" JSONB NOT NULL,
99
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
100
+ "updatedAt" TIMESTAMP(3) NOT NULL,
101
+
102
+ CONSTRAINT "OrganisationSetting_pkey" PRIMARY KEY ("id")
103
+ );
104
+
105
+ -- CreateTable
106
+ CREATE TABLE "Membership" (
107
+ "id" TEXT NOT NULL,
108
+ "userId" TEXT NOT NULL,
109
+ "orgId" TEXT NOT NULL,
110
+ "roleId" TEXT NOT NULL,
111
+ "status" "MembershipStatus" NOT NULL DEFAULT 'ACTIVE',
112
+ "isOwner" BOOLEAN NOT NULL DEFAULT false,
113
+ "isBillingContact" BOOLEAN NOT NULL DEFAULT false,
114
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
115
+ "updatedAt" TIMESTAMP(3) NOT NULL,
116
+
117
+ CONSTRAINT "Membership_pkey" PRIMARY KEY ("id")
118
+ );
119
+
120
+ -- CreateTable
121
+ CREATE TABLE "Role" (
122
+ "id" TEXT NOT NULL,
123
+ "orgId" TEXT NOT NULL,
124
+ "name" TEXT NOT NULL,
125
+ "description" TEXT,
126
+ "isSystemSeeded" BOOLEAN NOT NULL DEFAULT false,
127
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
128
+ "updatedAt" TIMESTAMP(3) NOT NULL,
129
+
130
+ CONSTRAINT "Role_pkey" PRIMARY KEY ("id")
131
+ );
132
+
133
+ -- CreateTable
134
+ CREATE TABLE "Invitation" (
135
+ "id" TEXT NOT NULL,
136
+ "orgId" TEXT NOT NULL,
137
+ "targetType" "InvitationTargetType" NOT NULL,
138
+ "targetValue" TEXT NOT NULL,
139
+ "normalizedTargetValue" TEXT NOT NULL,
140
+ "roleId" TEXT NOT NULL,
141
+ "tokenHash" TEXT NOT NULL,
142
+ "status" "InvitationStatus" NOT NULL DEFAULT 'PENDING',
143
+ "expiresAt" TIMESTAMP(3) NOT NULL,
144
+ "invitedByUserId" TEXT NOT NULL,
145
+ "acceptedByUserId" TEXT,
146
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
147
+ "acceptedAt" TIMESTAMP(3),
148
+ "revokedAt" TIMESTAMP(3),
149
+
150
+ CONSTRAINT "Invitation_pkey" PRIMARY KEY ("id")
151
+ );
152
+
153
+ -- CreateTable
154
+ CREATE TABLE "Permission" (
155
+ "id" TEXT NOT NULL,
156
+ "key" TEXT NOT NULL,
157
+ "module" TEXT NOT NULL,
158
+ "action" TEXT NOT NULL,
159
+ "subject" TEXT NOT NULL,
160
+ "description" TEXT,
161
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
162
+
163
+ CONSTRAINT "Permission_pkey" PRIMARY KEY ("id")
164
+ );
165
+
166
+ -- CreateTable
167
+ CREATE TABLE "RolePermission" (
168
+ "roleId" TEXT NOT NULL,
169
+ "permissionId" TEXT NOT NULL,
170
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
171
+
172
+ CONSTRAINT "RolePermission_pkey" PRIMARY KEY ("roleId","permissionId")
173
+ );
174
+
175
+ -- CreateTable
176
+ CREATE TABLE "AuditLog" (
177
+ "id" TEXT NOT NULL,
178
+ "orgId" TEXT,
179
+ "actorUserId" TEXT,
180
+ "action" TEXT NOT NULL,
181
+ "targetType" TEXT NOT NULL,
182
+ "targetId" TEXT,
183
+ "metadata" JSONB,
184
+ "ipAddress" TEXT,
185
+ "userAgent" TEXT,
186
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
187
+
188
+ CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
189
+ );
190
+
191
+ -- CreateIndex
192
+ CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
193
+ CREATE UNIQUE INDEX "User_mobile_key" ON "User"("mobile");
194
+ CREATE INDEX "AuthIdentity_userId_idx" ON "AuthIdentity"("userId");
195
+ CREATE INDEX "AuthIdentity_provider_idx" ON "AuthIdentity"("provider");
196
+ CREATE UNIQUE INDEX "AuthIdentity_provider_providerUserId_key" ON "AuthIdentity"("provider", "providerUserId");
197
+ CREATE UNIQUE INDEX "RefreshToken_tokenHash_key" ON "RefreshToken"("tokenHash");
198
+ CREATE INDEX "RefreshToken_userId_idx" ON "RefreshToken"("userId");
199
+ CREATE INDEX "RefreshToken_expiresAt_idx" ON "RefreshToken"("expiresAt");
200
+ CREATE INDEX "RefreshToken_revokedAt_idx" ON "RefreshToken"("revokedAt");
201
+ CREATE UNIQUE INDEX "AuthExchangeCode_tokenHash_key" ON "AuthExchangeCode"("tokenHash");
202
+ CREATE INDEX "AuthExchangeCode_userId_idx" ON "AuthExchangeCode"("userId");
203
+ CREATE INDEX "AuthExchangeCode_expiresAt_idx" ON "AuthExchangeCode"("expiresAt");
204
+ CREATE INDEX "AuthExchangeCode_usedAt_idx" ON "AuthExchangeCode"("usedAt");
205
+ CREATE UNIQUE INDEX "PasswordResetToken_tokenHash_key" ON "PasswordResetToken"("tokenHash");
206
+ CREATE INDEX "PasswordResetToken_userId_idx" ON "PasswordResetToken"("userId");
207
+ CREATE INDEX "PasswordResetToken_expiresAt_idx" ON "PasswordResetToken"("expiresAt");
208
+ CREATE INDEX "PasswordResetToken_usedAt_idx" ON "PasswordResetToken"("usedAt");
209
+ CREATE UNIQUE INDEX "Organisation_slug_key" ON "Organisation"("slug");
210
+ CREATE INDEX "OrganisationSetting_orgId_idx" ON "OrganisationSetting"("orgId");
211
+ CREATE UNIQUE INDEX "OrganisationSetting_orgId_key_key" ON "OrganisationSetting"("orgId", "key");
212
+ CREATE INDEX "Membership_orgId_idx" ON "Membership"("orgId");
213
+ CREATE INDEX "Membership_userId_idx" ON "Membership"("userId");
214
+ CREATE INDEX "Membership_status_idx" ON "Membership"("status");
215
+ CREATE UNIQUE INDEX "Membership_userId_orgId_key" ON "Membership"("userId", "orgId");
216
+ CREATE INDEX "Role_orgId_idx" ON "Role"("orgId");
217
+ CREATE UNIQUE INDEX "Role_orgId_name_key" ON "Role"("orgId", "name");
218
+ CREATE UNIQUE INDEX "Role_id_orgId_key" ON "Role"("id", "orgId");
219
+ CREATE UNIQUE INDEX "Invitation_tokenHash_key" ON "Invitation"("tokenHash");
220
+ CREATE INDEX "Invitation_orgId_idx" ON "Invitation"("orgId");
221
+ CREATE INDEX "Invitation_targetType_normalizedTargetValue_idx" ON "Invitation"("targetType", "normalizedTargetValue");
222
+ CREATE INDEX "Invitation_status_idx" ON "Invitation"("status");
223
+ CREATE UNIQUE INDEX "Invitation_pending_target_unique" ON "Invitation"("orgId", "targetType", "normalizedTargetValue") WHERE "status" = 'PENDING';
224
+ CREATE UNIQUE INDEX "Permission_key_key" ON "Permission"("key");
225
+ CREATE INDEX "AuditLog_orgId_idx" ON "AuditLog"("orgId");
226
+ CREATE INDEX "AuditLog_orgId_createdAt_idx" ON "AuditLog"("orgId", "createdAt");
227
+ CREATE INDEX "AuditLog_actorUserId_idx" ON "AuditLog"("actorUserId");
228
+ CREATE INDEX "AuditLog_action_idx" ON "AuditLog"("action");
229
+ CREATE INDEX "AuditLog_targetType_targetId_idx" ON "AuditLog"("targetType", "targetId");
230
+ CREATE INDEX "AuditLog_createdAt_idx" ON "AuditLog"("createdAt");
231
+
232
+ -- AddForeignKey
233
+ ALTER TABLE "AuthIdentity" ADD CONSTRAINT "AuthIdentity_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
234
+ ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
235
+ ALTER TABLE "AuthExchangeCode" ADD CONSTRAINT "AuthExchangeCode_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
236
+ ALTER TABLE "PasswordResetToken" ADD CONSTRAINT "PasswordResetToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
237
+ ALTER TABLE "OrganisationSetting" ADD CONSTRAINT "OrganisationSetting_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organisation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
238
+ ALTER TABLE "Membership" ADD CONSTRAINT "Membership_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
239
+ ALTER TABLE "Membership" ADD CONSTRAINT "Membership_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organisation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
240
+ ALTER TABLE "Membership" ADD CONSTRAINT "Membership_roleId_orgId_fkey" FOREIGN KEY ("roleId", "orgId") REFERENCES "Role"("id", "orgId") ON DELETE RESTRICT ON UPDATE CASCADE;
241
+ ALTER TABLE "Role" ADD CONSTRAINT "Role_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organisation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
242
+ ALTER TABLE "Invitation" ADD CONSTRAINT "Invitation_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organisation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
243
+ ALTER TABLE "Invitation" ADD CONSTRAINT "Invitation_roleId_orgId_fkey" FOREIGN KEY ("roleId", "orgId") REFERENCES "Role"("id", "orgId") ON DELETE RESTRICT ON UPDATE CASCADE;
244
+ ALTER TABLE "Invitation" ADD CONSTRAINT "Invitation_invitedByUserId_fkey" FOREIGN KEY ("invitedByUserId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
245
+ ALTER TABLE "Invitation" ADD CONSTRAINT "Invitation_acceptedByUserId_fkey" FOREIGN KEY ("acceptedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
246
+ ALTER TABLE "RolePermission" ADD CONSTRAINT "RolePermission_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE CASCADE ON UPDATE CASCADE;
247
+ ALTER TABLE "RolePermission" ADD CONSTRAINT "RolePermission_permissionId_fkey" FOREIGN KEY ("permissionId") REFERENCES "Permission"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
248
+ ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_actorUserId_fkey" FOREIGN KEY ("actorUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;