@appxdigital/appx-core 0.1.120 → 0.1.121-beta.10

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 (49) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/LICENSE +21 -21
  3. package/PRERELEASES.md +142 -0
  4. package/README.md +627 -533
  5. package/RELEASING.md +168 -0
  6. package/dist/common/config/permissionsConfigTypes.d.ts +8 -0
  7. package/dist/common/config/permissionsConfigTypes.d.ts.map +1 -1
  8. package/dist/common/config/permissionsConfigTypes.js +13 -1
  9. package/dist/common/decorators/permission.decorator.d.ts +14 -0
  10. package/dist/common/decorators/permission.decorator.d.ts.map +1 -1
  11. package/dist/common/decorators/permission.decorator.js +16 -1
  12. package/dist/common/guards/rbac.guard.d.ts.map +1 -1
  13. package/dist/common/guards/rbac.guard.js +8 -1
  14. package/dist/common/interceptors/prisma.interceptor.d.ts.map +1 -1
  15. package/dist/common/interceptors/prisma.interceptor.js +24 -8
  16. package/dist/common/setup/core-config.d.ts +66 -0
  17. package/dist/common/setup/core-config.d.ts.map +1 -0
  18. package/dist/common/setup/core-config.js +67 -0
  19. package/dist/common/setup/setup-core-security.d.ts +62 -0
  20. package/dist/common/setup/setup-core-security.d.ts.map +1 -0
  21. package/dist/common/setup/setup-core-security.js +40 -0
  22. package/dist/config/generate-all.js +4 -0
  23. package/dist/config/generate-controllers.js +19 -2
  24. package/dist/config/generate-dtos.d.ts +2 -0
  25. package/dist/config/generate-dtos.d.ts.map +1 -0
  26. package/dist/config/generate-dtos.js +285 -0
  27. package/dist/config/utils.d.ts +14 -0
  28. package/dist/config/utils.d.ts.map +1 -1
  29. package/dist/config/utils.js +35 -2
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/modules/auth/auth.controller.d.ts.map +1 -1
  34. package/dist/modules/auth/auth.controller.js +6 -2
  35. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  36. package/dist/modules/core/core.controller.d.ts.map +1 -1
  37. package/dist/modules/core/core.controller.js +12 -5
  38. package/dist/modules/user/user.service.d.ts.map +1 -1
  39. package/dist/modules/user/user.service.js +7 -3
  40. package/dist/prisma/prisma.service.d.ts +94 -2
  41. package/dist/prisma/prisma.service.d.ts.map +1 -1
  42. package/dist/prisma/prisma.service.js +338 -8
  43. package/docs/README.md +21 -0
  44. package/docs/data-access.md +108 -0
  45. package/docs/dtos.md +147 -0
  46. package/docs/graphql.md +72 -0
  47. package/docs/limitations.md +55 -0
  48. package/docs/permissions.md +155 -0
  49. package/package.json +112 -86
package/README.md CHANGED
@@ -1,533 +1,627 @@
1
- # AppX Core
2
-
3
- <p align="center">
4
- <img src="./public/appx-core-logo.png" alt="AppX Core Logo" width="220" />
5
- </p>
6
-
7
- **AppX Core** is a production-oriented backend foundation that accelerates the delivery of secure, data-access-controlled APIs with an integrated admin backoffice. It scaffolds a native **NestJS** application and extends it with opinionated building blocks for authentication, authorization (ABAC-first), CRUD acceleration, and administration through **AdminJS**.
8
-
9
- Built and maintained by **AppX** (appx-digital.com).
10
-
11
- ---
12
-
13
- ## Table of Contents
14
-
15
- - [Why AppX Core](#why-appx-core)
16
- - [Key Features](#key-features)
17
- - [Architecture Overview](#architecture-overview)
18
- - [Getting Started](#getting-started)
19
- - [Prerequisites](#prerequisites)
20
- - [1) Install the CLI](#1-install-the-cli)
21
- - [2) Create a New Project](#2-create-a-new-project)
22
- - [3) Configure Environment](#3-configure-environment)
23
- - [4) Define Data Models](#4-define-data-models)
24
- - [5) Generate Modules and CRUD](#5-generate-modules-and-crud)
25
- - [6) Run Migrations](#6-run-migrations)
26
- - [7) Run the App](#7-run-the-app)
27
- - [What you get out of the box](#what-you-get-out-of-the-box)
28
- - [Core Concepts](#core-concepts)
29
- - [Project Structure](#project-structure)
30
- - [Admin Backoffice](#admin-backoffice)
31
- - [Authentication](#authentication)
32
- - [Authorization Model](#authorization-model)
33
- - [Permissions](#permissions)
34
- - [Permissions Configuration Shape](#permissions-configuration-shape)
35
- - [Rules](#rules)
36
- - [Action Aliases and Fallbacks](#action-aliases-and-fallbacks)
37
- - [Custom Controller Actions](#custom-controller-actions)
38
- - [Temporarily Exposing Models](#temporarily-exposing-models)
39
- - [Generated CRUD Endpoints](#generated-crud-endpoints)
40
- - [Examples](#examples)
41
- - [Example 1: Admin-Only Operational Endpoint](#example-1-admin-only-operational-endpoint)
42
- - [Example 2: “My Profile” Endpoint (User Can Only See Self)](#example-2-my-profile-endpoint-user-can-only-see-self)
43
- - [Example 3: Registration Email Availability Check](#example-3-registration-email-availability-check)
44
- - [Limitations](#limitations)
45
- - [FAQ](#faq)
46
- - [Roadmap](#roadmap)
47
- - [Support](#support)
48
- - [Security](#security)
49
- - [Contributing](#contributing)
50
- - [License](#license)
51
-
52
- ---
53
-
54
- ## Why AppX Core
55
-
56
- Modern applications often fail not because teams cannot build endpoints, but because they ship access control too late, build inconsistent admin tooling, and recreate the same authentication and backoffice plumbing across projects.
57
-
58
- AppX Core puts **security and data-access control first**, while keeping development fast:
59
-
60
- - Start from a **native NestJS** structure (no “framework lock-in”).
61
- - Define data in **Prisma** once.
62
- - Generate a robust baseline for CRUD and service structure.
63
- - Enforce permissions where it matters: **at the data layer** (ABAC).
64
- - Operate and administrate through a standard **AdminJS** backoffice.
65
-
66
- ---
67
-
68
- ## Key Features
69
-
70
- - **NestJS-native scaffold** (standard structure, editable modules)
71
- - **Prisma ORM integration** with opinionated permissions enforcement
72
- - **ABAC-first permission filtering** (attribute-based access control)
73
- - **AdminJS backoffice** exposed at `/admin` with configurable access and models
74
- - **Authentication out-of-the-box**
75
- - Cookie/session auth
76
- - JWT auth (recommended)
77
- - **Fast CRUD acceleration**
78
- - Generated modules, controllers, and services per model
79
- - Standard REST CRUD endpoints
80
- - **Developer-friendly extension points**
81
- - Add custom endpoints with `@Permission(...)`
82
- - Use services for business logic, controllers as transport layer
83
-
84
- ---
85
-
86
- ## Architecture Overview
87
-
88
- AppX Core is designed as a **base layer** on top of NestJS:
89
-
90
- - Your application remains a NestJS project with conventional modules.
91
- - AppX Core provides:
92
- - base configuration and wiring
93
- - authentication modules
94
- - permissions system (ABAC)
95
- - generation pipeline based on Prisma schema
96
- - AdminJS integration
97
-
98
- ---
99
-
100
- ## Getting Started
101
-
102
- ### Prerequisites
103
-
104
- - **Node.js 20+**
105
- - A running database (**MySQL** or **PostgreSQL** tested/validated)
106
-
107
- ### 1) Install the CLI
108
-
109
- ```bash
110
- npm install -g @appxdigital/appx-core-cli
111
- ```
112
-
113
- ### 2) Create a New Project
114
-
115
- ```bash
116
- appx-core create
117
- cd <your-project>
118
- ```
119
-
120
- ### 3) Configure Environment
121
-
122
- Create a `.env` file at the project root:
123
-
124
- ```bash
125
- ## DataBase Configurations ##
126
- DB_HOST=127.0.0.1
127
- DB_PORT=3306
128
- DB_USER=root
129
- DB_PASSWORD=
130
- DB_NAME=generic
131
- DB_PROVIDER=mysql
132
- DB_URL="${DB_PROVIDER}://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
133
-
134
- ## Project configurations ##
135
- APP_PORT=3000
136
- USE_TRANSACTION=true
137
-
138
- ## Sessions ##
139
- SESSION_SECRET="change-me"
140
- SESSION_COOKIE_NAME="session_nestjs-project"
141
- SESSION_TTL=86400
142
-
143
- ## JWT ##
144
- JWT_EXPIRES_IN=10d
145
- JWT_REFRESH_EXPIRES_IN=1y
146
- JWT_SECRET="change-me"
147
- JWT_REFRESH_SECRET="change-me"
148
- ```
149
-
150
- ### 4) Define Data Models
151
-
152
- Edit:
153
-
154
- - `prisma/schema.prisma`
155
-
156
- Add your application models. You can add fields and relations freely. Avoid removing or changing types of the default models shipped with AppX Core.
157
-
158
- If you already have an existing database schema and want to import it:
159
-
160
- ```bash
161
- npx prisma db pull
162
- ```
163
-
164
- **Important:** `db pull` overrides `schema.prisma`. Ensure you preserve default AppX Core models/configuration and merge accordingly.
165
-
166
- ### 5) Generate Modules and CRUD
167
-
168
- After you edit `schema.prisma`, run:
169
-
170
- ```bash
171
- appx-core generate
172
- ```
173
-
174
- Run this **every time** you change the Prisma schema. This is the only required generation step (no need to run `prisma generate` separately).
175
-
176
- ### 6) Run Migrations
177
-
178
- ```bash
179
- npx prisma migrate dev
180
- ```
181
-
182
- You will be asked for a migration name.
183
-
184
- ### 7) Run the App
185
-
186
- ```bash
187
- npm run start:dev
188
- ```
189
-
190
- ### What you get out of the box
191
-
192
- - Admin backoffice at **`/admin`** (after you include models in `src/config/admin.config.ts`)
193
- - Auth endpoints with no prefix:
194
- - `POST /auth/register`
195
- - `POST /auth/login`
196
- - `POST /auth/logout`
197
- - `GET /me`
198
- - `POST /login/jwt` (recommended)
199
- - `POST /logout/jwt`
200
-
201
- ---
202
-
203
- ## Core Concepts
204
-
205
- ### Project Structure
206
-
207
- AppX Core generates a standard NestJS structure. You will typically see:
208
-
209
- - `src/main.ts` – app bootstrap and cross-cutting config (cookies, sessions, logs, CORS)
210
- - `src/app.module.ts` – high-level module wiring
211
-
212
- Most customization happens in:
213
-
214
- - `src/config/permissions.config.ts` – granular ABAC rules
215
- - `src/config/admin.config.ts` – AdminJS configuration and model registration
216
- - `prisma/schema.prisma` – your data model source of truth
217
-
218
- ### Admin Backoffice
219
-
220
- - AdminJS is exposed at **`/admin`**
221
- - Models must be included/configured in `admin.config.ts` to appear in the UI.
222
-
223
- ### Authentication
224
-
225
- Cookie/session:
226
-
227
- - `POST /auth/register`
228
- - `POST /auth/login`
229
- - `POST /auth/logout`
230
- - `GET /me`
231
-
232
- JWT (recommended):
233
-
234
- - `POST /login/jwt`
235
- - `POST /logout/jwt`
236
-
237
- ### Authorization Model
238
-
239
- AppX Core emphasizes enforcement at the data layer:
240
-
241
- - Permissions are designed to be applied automatically during database operations (ABAC), not only at the route layer.
242
- - Implement permissions using:
243
- - `permissions.config.ts`
244
- - `@Permission('actionName')` on endpoints
245
- - ABAC conditions aligned with Prisma `where` filters
246
-
247
- Unauthenticated requests are treated as a “guest context” (not a role).
248
-
249
- ---
250
-
251
- ## Permissions
252
-
253
- ### Permissions Configuration Shape
254
-
255
- Permissions live in `src/config/permissions.config.ts`:
256
-
257
- ```ts
258
- export const PermissionsConfig = {
259
- ModelName: {
260
- ADMIN: {
261
- findMany: 'ALL',
262
- create: 'ALL',
263
- updateMany: 'ALL',
264
- deleteMany: 'ALL',
265
- },
266
- USER: {
267
- findMany: { conditions: { id: PermissionPlaceholder.USER_ID } },
268
- updateMany: { conditions: { id: PermissionPlaceholder.USER_ID } },
269
- },
270
- },
271
- };
272
- ```
273
-
274
- ### Rules
275
-
276
- - `'ALL'` – unrestricted access
277
- - `{ conditions: { ... } }` – ABAC filtering using Prisma-like `where` syntax
278
-
279
- Example: allow deletion only if related refresh tokens are expired:
280
-
281
- ```ts
282
- deleteMany: {
283
- conditions: {
284
- refreshTokens: {
285
- every: { expiresAt: { lt: new Date() } },
286
- },
287
- },
288
- },
289
- ```
290
-
291
- ### Action Aliases and Fallbacks
292
-
293
- To avoid duplicated config, the permissions engine supports fallback behavior. In practice:
294
-
295
- - Define `findMany` when possible, as it generally covers read scenarios.
296
- - `count` may fall back to read permissions when not explicitly defined.
297
-
298
- ### Custom Controller Actions
299
-
300
- Add custom endpoints and protect them with `@Permission(...)`.
301
-
302
- ```ts
303
- @Permission('exportAuditReport')
304
- @Get('/audit/export')
305
- exportAuditReport() {
306
- return this.service.exportAuditReport();
307
- }
308
- ```
309
-
310
- ### Temporarily Exposing Models
311
-
312
- Some operations require privileged access (e.g., registration pre-checks). You can temporarily expose a model:
313
-
314
- - Endpoint-level (decorator’s second argument), or
315
- - Service-level scoped exposure
316
-
317
- Endpoint-level example:
318
-
319
- ```ts
320
- @Permission('checkEmailAvailability', ['user'])
321
- @Get('/auth/email-available')
322
- async isEmailAvailable(@Query('email') email: string) {
323
- return this.service.isEmailAvailable(email);
324
- }
325
- ```
326
-
327
- Service-level example:
328
-
329
- ```ts
330
- await this.prisma.withExposedModels(['user'], async () => {
331
- // privileged operations here
332
- });
333
- ```
334
-
335
- Use this sparingly and intentionally. Prefer ABAC whenever feasible.
336
-
337
- ---
338
-
339
- ## Generated CRUD Endpoints
340
-
341
- For each generated model/controller (subject to permissions):
342
-
343
- - `GET /[model_name]`
344
- - `GET /[model_name]/:id`
345
- - `POST /[model_name]`
346
- - `PUT /[model_name]/:id`
347
- - `DELETE /[model_name]/:id`
348
-
349
- To show a model in the admin backoffice, register it in `src/config/admin.config.ts`.
350
-
351
- ---
352
-
353
- ## Examples
354
-
355
- ### Example 1: Admin-Only Operational Endpoint
356
-
357
- Use case: expose a lightweight operational endpoint to verify that background jobs ran recently.
358
-
359
- Controller:
360
-
361
- ```ts
362
- @Permission('viewSystemHealth')
363
- @Get('/ops/health')
364
- getHealth() {
365
- return this.service.getHealthSummary();
366
- }
367
- ```
368
-
369
- Permissions (`permissions.config.ts`):
370
-
371
- ```ts
372
- export const PermissionsConfig = {
373
- System: {
374
- ADMIN: {
375
- viewSystemHealth: 'ALL',
376
- },
377
- USER: {
378
- // Not defined => denied
379
- },
380
- },
381
- };
382
- ```
383
-
384
- Service (example logic):
385
-
386
- ```ts
387
- async getHealthSummary() {
388
- return {
389
- status: 'ok',
390
- timestamp: new Date().toISOString(),
391
- };
392
- }
393
- ```
394
-
395
- ### Example 2: “My Profile” Endpoint (User Can Only See Self)
396
-
397
- Use case: ensure users can only access their own record, even if they try other IDs.
398
-
399
- Controller:
400
-
401
- ```ts
402
- @Permission('readMyProfile')
403
- @Get('/me/profile')
404
- getMyProfile() {
405
- return this.service.getMyProfile();
406
- }
407
- ```
408
-
409
- Permissions:
410
-
411
- ```ts
412
- export const PermissionsConfig = {
413
- User: {
414
- ADMIN: { readMyProfile: 'ALL' },
415
- USER: { readMyProfile: { conditions: { id: PermissionPlaceholder.USER_ID } } },
416
- },
417
- };
418
- ```
419
-
420
- Service (Prisma query remains straightforward; ABAC filtering is applied by the core):
421
-
422
- ```ts
423
- async getMyProfile() {
424
- return this.prisma.model.user.findFirst({
425
- where: { id: PermissionPlaceholder.USER_ID },
426
- });
427
- }
428
- ```
429
-
430
- ### Example 3: Registration Email Availability Check
431
-
432
- Use case: a public endpoint checks if an email is already used. This must access user data safely, even without a logged-in user.
433
-
434
- Controller:
435
-
436
- ```ts
437
- @Permission('checkEmailAvailability', ['user'])
438
- @Get('/auth/email-available')
439
- async isEmailAvailable(@Query('email') email: string) {
440
- return this.service.isEmailAvailable(email);
441
- }
442
- ```
443
-
444
- Service:
445
-
446
- ```ts
447
- async isEmailAvailable(email: string) {
448
- let exists = false;
449
-
450
- await this.prisma.withExposedModels(['user'], async () => {
451
- const user = await this.prisma.model.user.findFirst({ where: { email } });
452
- exists = !!user;
453
- });
454
-
455
- return { available: !exists };
456
- }
457
- ```
458
-
459
- This pattern should be used sparingly and intentionally.
460
-
461
- ---
462
-
463
- ## Limitations
464
-
465
- ### Prisma method restrictions (permission safety)
466
-
467
- Avoid these Prisma methods because they can bypass or complicate granular permission filtering:
468
-
469
- - `findUnique` → use `findFirst`
470
- - `delete` → use `deleteMany` with a single-item filter
471
- - `update` → use `updateMany` with a single-item filter
472
-
473
- Operational guidance:
474
-
475
- - Prefer `findMany`, `findFirst`, `updateMany`, and `deleteMany`.
476
- - When you intend a single-record operation, use a restrictive `where` clause.
477
-
478
- ### GraphQL status
479
-
480
- GraphQL is part of the direction, but is **not fully ready** in the current release. REST CRUD + AdminJS are the primary integration surfaces for now.
481
-
482
- ---
483
-
484
- ## FAQ
485
-
486
- **Are CRUD endpoints only for AdminJS?**
487
- No. They can be used for any API consumer. Permissions are enforced consistently.
488
-
489
- **Do I need to treat generated modules as read-only?**
490
- No. Generated NestJS modules are **editable** and intended to be extended.
491
-
492
- ---
493
-
494
- ## Roadmap
495
-
496
- Planned and under consideration:
497
-
498
- - Production-ready GraphQL
499
- - Object storage patterns (S3-friendly out-of-the-box support)
500
- - Forgot-password flow
501
- - Email templates + sending primitives
502
- - Push notifications primitives
503
- - WebSockets support
504
- - Stronger testing baseline (unit tests scaffolding and patterns)
505
- - “Agent-ready” development guidance (`agents.txt`, conventions for coding agents)
506
- - Optional AI-assisted project scaffolding
507
-
508
- ---
509
-
510
- ## Support
511
-
512
- Email: **core@appx-digital.com**
513
-
514
- ---
515
-
516
- ## Security
517
-
518
- Report vulnerabilities privately to **core@appx-digital.com**. Do not open public issues for sensitive reports.
519
-
520
- ---
521
-
522
- ## Contributing
523
-
524
- 1. Fork the repository.
525
- 2. Create a feature branch.
526
- 3. Keep changes focused; add tests where applicable.
527
- 4. Open a PR with clear motivation and impact.
528
-
529
- ---
530
-
531
- ## License
532
-
533
- MIT License. See [LICENSE](LICENSE).
1
+ # AppX Core
2
+
3
+ <p align="center">
4
+ <img src="./public/appx-core-logo.png" alt="AppX Core Logo" width="220" />
5
+ </p>
6
+
7
+ [![CI](https://github.com/appxdigital/appx-core/actions/workflows/ci.yml/badge.svg)](https://github.com/appxdigital/appx-core/actions/workflows/ci.yml)
8
+ [![Release](https://github.com/appxdigital/appx-core/actions/workflows/release.yml/badge.svg)](https://github.com/appxdigital/appx-core/actions/workflows/release.yml)
9
+ [![Release CLI](https://github.com/appxdigital/appx-core/actions/workflows/release-cli.yml/badge.svg)](https://github.com/appxdigital/appx-core/actions/workflows/release-cli.yml)
10
+
11
+ **AppX Core** is a production-oriented backend foundation that accelerates the delivery of secure, data-access-controlled APIs with an integrated admin backoffice. It scaffolds a native **NestJS** application and extends it with opinionated building blocks for authentication, authorization (ABAC-first), CRUD acceleration, and administration through **AdminJS**.
12
+
13
+ Built and maintained by **AppX** (appx-digital.com).
14
+
15
+ ---
16
+
17
+ ## Table of Contents
18
+
19
+ - [Why AppX Core](#why-appx-core)
20
+ - [Key Features](#key-features)
21
+ - [Architecture Overview](#architecture-overview)
22
+ - [Getting Started](#getting-started)
23
+ - [Prerequisites](#prerequisites)
24
+ - [1) Install the CLI](#1-install-the-cli)
25
+ - [2) Create a New Project](#2-create-a-new-project)
26
+ - [3) Configure Environment](#3-configure-environment)
27
+ - [4) Define Data Models](#4-define-data-models)
28
+ - [5) Generate Modules and CRUD](#5-generate-modules-and-crud)
29
+ - [6) Run Migrations](#6-run-migrations)
30
+ - [7) Run the App](#7-run-the-app)
31
+ - [What you get out of the box](#what-you-get-out-of-the-box)
32
+ - [Core Concepts](#core-concepts)
33
+ - [Project Structure](#project-structure)
34
+ - [Admin Backoffice](#admin-backoffice)
35
+ - [Authentication](#authentication)
36
+ - [Authorization Model](#authorization-model)
37
+ - [Permissions](#permissions)
38
+ - [Permissions Configuration Shape](#permissions-configuration-shape)
39
+ - [Rules](#rules)
40
+ - [Reading Related Models](#reading-related-models)
41
+ - [Action Aliases and Fallbacks](#action-aliases-and-fallbacks)
42
+ - [Custom Controller Actions](#custom-controller-actions)
43
+ - [Temporarily Exposing Models](#temporarily-exposing-models)
44
+ - [Generated CRUD Endpoints](#generated-crud-endpoints)
45
+ - [Examples](#examples)
46
+ - [Example 1: Admin-Only Operational Endpoint](#example-1-admin-only-operational-endpoint)
47
+ - [Example 2: “My Profile” Endpoint (User Can Only See Self)](#example-2-my-profile-endpoint-user-can-only-see-self)
48
+ - [Example 3: Registration Email Availability Check](#example-3-registration-email-availability-check)
49
+ - [Limitations](#limitations)
50
+ - [FAQ](#faq)
51
+ - [Roadmap](#roadmap)
52
+ - [Support](#support)
53
+ - [Security](#security)
54
+ - [Contributing](#contributing)
55
+ - [License](#license)
56
+
57
+ ---
58
+
59
+ ## Why AppX Core
60
+
61
+ Modern applications often fail not because teams cannot build endpoints, but because they ship access control too late, build inconsistent admin tooling, and recreate the same authentication and backoffice plumbing across projects.
62
+
63
+ AppX Core puts **security and data-access control first**, while keeping development fast:
64
+
65
+ - Start from a **native NestJS** structure (no “framework lock-in”).
66
+ - Define data in **Prisma** once.
67
+ - Generate a robust baseline for CRUD and service structure.
68
+ - Enforce permissions where it matters: **at the data layer** (ABAC).
69
+ - Operate and administrate through a standard **AdminJS** backoffice.
70
+
71
+ ---
72
+
73
+ ## Key Features
74
+
75
+ - **NestJS-native scaffold** (standard structure, editable modules)
76
+ - **Prisma ORM integration** with opinionated permissions enforcement
77
+ - **ABAC-first permission filtering** (attribute-based access control)
78
+ - **AdminJS backoffice** exposed at `/admin` with configurable access and models
79
+ - **Authentication out-of-the-box**
80
+ - Cookie/session auth
81
+ - JWT auth (recommended)
82
+ - **Fast CRUD acceleration**
83
+ - Generated modules, controllers, and services per model
84
+ - Standard REST CRUD endpoints
85
+ - **Developer-friendly extension points**
86
+ - Add custom endpoints with `@Permission(...)`
87
+ - Use services for business logic, controllers as transport layer
88
+
89
+ ---
90
+
91
+ ## Architecture Overview
92
+
93
+ AppX Core is designed as a **base layer** on top of NestJS:
94
+
95
+ - Your application remains a NestJS project with conventional modules.
96
+ - AppX Core provides:
97
+ - base configuration and wiring
98
+ - authentication modules
99
+ - permissions system (ABAC)
100
+ - generation pipeline based on Prisma schema
101
+ - AdminJS integration
102
+
103
+ ---
104
+
105
+ ## Getting Started
106
+
107
+ ### Prerequisites
108
+
109
+ - **Node.js 20+**
110
+ - A running database (**MySQL** or **PostgreSQL** tested/validated)
111
+
112
+ ### 1) Install the CLI
113
+
114
+ ```bash
115
+ npm install -g @appxdigital/appx-core-cli
116
+ ```
117
+
118
+ **Release channels.** AppX Core publishes both packages on two npm dist-tags:
119
+
120
+ - **`latest`** — stable releases. This is what the commands above install.
121
+ - **`beta`** — prereleases, for trying upcoming changes before they're promoted. Opt in explicitly with the `@beta` tag (a caret range such as `^0.1.x` never resolves to a prerelease on its own):
122
+
123
+ ```bash
124
+ npm install -g @appxdigital/appx-core-cli@beta # CLI prerelease
125
+ ```
126
+
127
+ `appx-core create` scaffolds a project that depends on the **stable** library. To test a library prerelease in that project, install it there:
128
+
129
+ ```bash
130
+ npm install @appxdigital/appx-core@beta # inside your project
131
+ ```
132
+
133
+ ### 2) Create a New Project
134
+
135
+ ```bash
136
+ appx-core create
137
+ cd <your-project>
138
+ ```
139
+
140
+ ### 3) Configure Environment
141
+
142
+ `appx-core create` **already writes a `.env`** at the project root, with secure
143
+ random secrets generated for you. You don't need to create it by hand — just
144
+ open it and set your database connection (and `CORS_ORIGIN` for production). The
145
+ generated file looks like this:
146
+
147
+ ```bash
148
+ ## Database ##
149
+ DB_HOST=127.0.0.1
150
+ DB_PORT=3306
151
+ DB_USER=root
152
+ DB_PASSWORD=
153
+ DB_NAME=generic
154
+ DB_PROVIDER=mysql
155
+ DB_URL="${DB_PROVIDER}://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
156
+
157
+ ## App ##
158
+ APP_PORT=3000
159
+ USE_TRANSACTION=true
160
+ CORS_ORIGIN=http://localhost:3000 # your front-end origin; set to your real domain in production
161
+
162
+ ## Sessions ##
163
+ SESSION_SECRET=<64-char random hex — generated for you>
164
+ SESSION_COOKIE_NAME=session_nestjs-project
165
+ SESSION_TTL=86400
166
+
167
+ ## JWT ##
168
+ JWT_EXPIRES_IN=10d
169
+ JWT_REFRESH_EXPIRES_IN=1y
170
+ JWT_SECRET=<random — generated for you>
171
+ JWT_REFRESH_SECRET=<random — generated for you>
172
+ ```
173
+
174
+ > **Keep the generated secrets — don't replace them with placeholders like `change-me`.**
175
+ > `SESSION_SECRET` must be **at least 32 characters** or the app throws at
176
+ > startup (`buildCoreSessionOptions` — there is no insecure fallback).
177
+ >
178
+ > **`NODE_ENV`** must be set in the runtime environment. The `start` scripts set
179
+ > it to `development`; set it explicitly in production. Env files resolve as
180
+ > `.env.${NODE_ENV}` first, then `.env` (via `coreEnvFilePath()`), so a plain
181
+ > `.env` works for development and you can add `.env.production` for prod.
182
+
183
+ Baseline HTTP hardening (a whitelisting `ValidationPipe`, CORS, and Helmet) is
184
+ applied for you by `setupCoreSecurity(app, …)` in `src/main.ts`; `helmet` is a
185
+ peer dependency the scaffold already includes.
186
+
187
+ ### 4) Define Data Models
188
+
189
+ Edit:
190
+
191
+ - `prisma/schema.prisma`
192
+
193
+ Add your application models. You can add fields and relations freely. Avoid removing or changing types of the default models shipped with AppX Core.
194
+
195
+ If you already have an existing database schema and want to import it:
196
+
197
+ ```bash
198
+ npx prisma db pull
199
+ ```
200
+
201
+ **Important:** `db pull` overrides `schema.prisma`. Ensure you preserve default AppX Core models/configuration and merge accordingly.
202
+
203
+ ### 5) Generate Modules and CRUD
204
+
205
+ After you edit `schema.prisma`, run:
206
+
207
+ ```bash
208
+ appx-core generate
209
+ ```
210
+
211
+ Run this **every time** you change the Prisma schema. This is the only required generation step (no need to run `prisma generate` separately).
212
+
213
+ ### 6) Run Migrations
214
+
215
+ ```bash
216
+ npx prisma migrate dev
217
+ ```
218
+
219
+ You will be asked for a migration name.
220
+
221
+ ### 7) Run the App
222
+
223
+ ```bash
224
+ npm run start:dev
225
+ ```
226
+
227
+ ### What you get out of the box
228
+
229
+ - Admin backoffice at **`/admin`** (after you include models in `src/config/admin.config.ts`)
230
+ - Auth endpoints with no prefix:
231
+ - `POST /auth/register`
232
+ - `POST /auth/login`
233
+ - `POST /auth/logout`
234
+ - `GET /me`
235
+ - `POST /login/jwt` (recommended)
236
+ - `POST /logout/jwt`
237
+
238
+ ---
239
+
240
+ ## Core Concepts
241
+
242
+ ### Project Structure
243
+
244
+ AppX Core generates a standard NestJS structure. You will typically see:
245
+
246
+ - `src/main.ts` – app bootstrap and cross-cutting config (cookies, sessions, logs, CORS)
247
+ - `src/app.module.ts` – high-level module wiring
248
+
249
+ Most customization happens in:
250
+
251
+ - `src/config/permissions.config.ts` – granular ABAC rules
252
+ - `src/config/admin.config.ts` – AdminJS configuration and model registration
253
+ - `prisma/schema.prisma` – your data model source of truth
254
+
255
+ ### Admin Backoffice
256
+
257
+ - AdminJS is exposed at **`/admin`**
258
+ - Models must be included/configured in `admin.config.ts` to appear in the UI.
259
+
260
+ ### Authentication
261
+
262
+ Cookie/session:
263
+
264
+ - `POST /auth/register`
265
+ - `POST /auth/login`
266
+ - `POST /auth/logout`
267
+ - `GET /me`
268
+
269
+ JWT (recommended):
270
+
271
+ - `POST /login/jwt`
272
+ - `POST /logout/jwt`
273
+
274
+ ### Authorization Model
275
+
276
+ AppX Core emphasizes enforcement at the data layer:
277
+
278
+ - Permissions are designed to be applied automatically during database operations (ABAC), not only at the route layer.
279
+ - Implement permissions using:
280
+ - `permissions.config.ts`
281
+ - `@Permission('actionName')` on endpoints
282
+ - ABAC conditions aligned with Prisma `where` filters
283
+
284
+ Unauthenticated requests are treated as a “guest context” (not a role).
285
+
286
+ ---
287
+
288
+ ## Permissions
289
+
290
+ ### Permissions Configuration Shape
291
+
292
+ Permissions live in `src/config/permissions.config.ts`:
293
+
294
+ ```ts
295
+ export const PermissionsConfig = {
296
+ ModelName: {
297
+ ADMIN: {
298
+ findMany: 'ALL',
299
+ create: 'ALL',
300
+ updateMany: 'ALL',
301
+ deleteMany: 'ALL',
302
+ },
303
+ USER: {
304
+ findMany: { conditions: { id: PermissionPlaceholder.USER_ID } },
305
+ updateMany: { conditions: { id: PermissionPlaceholder.USER_ID } },
306
+ },
307
+ },
308
+ };
309
+ ```
310
+
311
+ ### Rules
312
+
313
+ - `'ALL'` – unrestricted access
314
+ - `{ conditions: { ... } }` – ABAC filtering using Prisma-like `where` syntax
315
+
316
+ Example: allow deletion only if related refresh tokens are expired:
317
+
318
+ ```ts
319
+ deleteMany: {
320
+ conditions: {
321
+ refreshTokens: {
322
+ every: { expiresAt: { lt: new Date() } },
323
+ },
324
+ },
325
+ },
326
+ ```
327
+
328
+ ### Reading Related Models
329
+
330
+ When a query pulls in a relation (`include: { author: true }`, or the equivalent `select`), that related model's **own** read permissions are enforced too — a relation is never a way around ABAC. How this plays out depends on the relation's cardinality.
331
+
332
+ **To-one relations (`belongsTo`, 1:1)** are filtered with *inner-join* semantics: the related model's read `conditions` are applied to the **parent** row.
333
+
334
+ - If the related model has **no** read rule for the caller's role, including the relation raises a `403` (`Missing permissions on model X`). Grant a read rule on every model you expose through an `include`.
335
+ - If the related model has a **conditional** read rule and the specific related row does not satisfy it, the **whole parent row is dropped** from the result (`findFirst` returns `null`; `findMany` omits it) — the parent is *not* returned with the relation set to `null`. Put plainly: you cannot see a row whose related record you are not allowed to see.
336
+
337
+ **To-many relations (`hasMany`, many-to-many)** are filtered in place: the parent row is kept and the child list is narrowed to the rows the caller may read (which can be empty).
338
+
339
+ Example — `Comment.author` points at `User`, and a `User` is only self-readable:
340
+
341
+ ```ts
342
+ User: {
343
+ USER: { findMany: { conditions: { id: PermissionPlaceholder.USER_ID } } },
344
+ },
345
+ Comment: {
346
+ USER: { findMany: { conditions: { authorId: PermissionPlaceholder.USER_ID } } },
347
+ },
348
+ ```
349
+
350
+ `comment.findFirst({ where: { id }, include: { author: true } })` returns the comment only when the caller is also its author (a `User` row they are allowed to read); otherwise the comment itself is filtered out. This is intentional and fails closed.
351
+
352
+ > **Why not return the parent with the relation nulled?** Prisma does not allow a `where` filter on a to-one relation inside `include` / `select`, so the condition is applied to the parent instead. Masking the relation while keeping the parent would require an extra query per included relation; the framework favors the single-query, fail-closed behavior.
353
+
354
+ ### Action Aliases and Fallbacks
355
+
356
+ To avoid duplicated config, the permissions engine supports fallback behavior. In practice:
357
+
358
+ - Define `findMany` when possible, as it generally covers read scenarios.
359
+ - `count` may fall back to read permissions when not explicitly defined.
360
+
361
+ ### Custom Controller Actions
362
+
363
+ Add custom endpoints and protect them with `@Permission(...)`.
364
+
365
+ ```ts
366
+ @Permission('exportAuditReport')
367
+ @Get('/audit/export')
368
+ exportAuditReport() {
369
+ return this.service.exportAuditReport();
370
+ }
371
+ ```
372
+
373
+ ### Temporarily Exposing Models
374
+
375
+ Some operations require privileged access (e.g., registration pre-checks). You can temporarily expose a model:
376
+
377
+ - Endpoint-level (decorator’s second argument), or
378
+ - Service-level scoped exposure
379
+
380
+ Endpoint-level example:
381
+
382
+ ```ts
383
+ @Permission('checkEmailAvailability', ['user'])
384
+ @Get('/auth/email-available')
385
+ async isEmailAvailable(@Query('email') email: string) {
386
+ return this.service.isEmailAvailable(email);
387
+ }
388
+ ```
389
+
390
+ Service-level example:
391
+
392
+ ```ts
393
+ await this.prisma.withExposedModels(['user'], async () => {
394
+ // privileged operations here
395
+ });
396
+ ```
397
+
398
+ Use this sparingly and intentionally. Prefer ABAC whenever feasible.
399
+
400
+ ---
401
+
402
+ ## Generated CRUD Endpoints
403
+
404
+ For each generated model/controller (subject to permissions):
405
+
406
+ - `GET /[model_name]`
407
+ - `GET /[model_name]/:id`
408
+ - `POST /[model_name]`
409
+ - `PUT /[model_name]/:id`
410
+ - `DELETE /[model_name]/:id`
411
+
412
+ To show a model in the admin backoffice, register it in `src/config/admin.config.ts`.
413
+
414
+ ### Request validation (generated DTOs)
415
+
416
+ `appx-core generate` emits a `class-validator` DTO per model and action so `POST`/`PUT` bodies are validated and unknown fields are rejected (keep `setupCoreSecurity(app)` in `main.ts`, which enables `whitelist`). Two files are generated per action:
417
+
418
+ - **Base** — `src/generated/dto/<model>/<action>-<model>.generated.dto.ts`. Derived from your Prisma schema, **regenerated (overwritten) on every `generate`**, and gitignored. Do not edit it.
419
+ - **Subclass** — `src/modules/<model>/dto/<action>-<model>.dto.ts`. Generated **once** and yours to keep. Add custom validation here; it is never overwritten:
420
+
421
+ ```ts
422
+ import { IsEmail, Length } from 'class-validator';
423
+ import { CreateUserGeneratedDto } from '../../../generated/dto/user/create-user.generated.dto';
424
+
425
+ export class CreateUserDto extends CreateUserGeneratedDto {
426
+ @IsEmail()
427
+ email!: string; // tighten the base @IsString()
428
+
429
+ @Length(2, 60)
430
+ name?: string;
431
+ }
432
+ ```
433
+
434
+ **What's writable.** A field appears in the DTO unless it is the primary key (`@id`), a server-managed timestamp (`@default(now())` / `@updatedAt`), or annotated non-writable. To exclude a field from all generated CRUD writes, annotate it in `schema.prisma` with a doc-comment:
435
+
436
+ ```prisma
437
+ model User {
438
+ password String? /// @Role(none) // never read AND never CRUD-writable
439
+ role Role /// @NoWrite // not writable via CRUD (set it through a controlled flow)
440
+ }
441
+ ```
442
+
443
+ Use `/// @NoWrite` to exclude a field for **all** roles. For **per-role** control (e.g. only ADMIN may set `role`), use `restrictedFields` on the permission instead of `@NoWrite`.
444
+
445
+ ---
446
+
447
+ ## Examples
448
+
449
+ ### Example 1: Admin-Only Operational Endpoint
450
+
451
+ Use case: expose a lightweight operational endpoint to verify that background jobs ran recently.
452
+
453
+ Controller:
454
+
455
+ ```ts
456
+ @Permission('viewSystemHealth')
457
+ @Get('/ops/health')
458
+ getHealth() {
459
+ return this.service.getHealthSummary();
460
+ }
461
+ ```
462
+
463
+ Permissions (`permissions.config.ts`):
464
+
465
+ ```ts
466
+ export const PermissionsConfig = {
467
+ System: {
468
+ ADMIN: {
469
+ viewSystemHealth: 'ALL',
470
+ },
471
+ USER: {
472
+ // Not defined => denied
473
+ },
474
+ },
475
+ };
476
+ ```
477
+
478
+ Service (example logic):
479
+
480
+ ```ts
481
+ async getHealthSummary() {
482
+ return {
483
+ status: 'ok',
484
+ timestamp: new Date().toISOString(),
485
+ };
486
+ }
487
+ ```
488
+
489
+ ### Example 2: “My Profile” Endpoint (User Can Only See Self)
490
+
491
+ Use case: ensure users can only access their own record, even if they try other IDs.
492
+
493
+ Controller:
494
+
495
+ ```ts
496
+ @Permission('readMyProfile')
497
+ @Get('/me/profile')
498
+ getMyProfile() {
499
+ return this.service.getMyProfile();
500
+ }
501
+ ```
502
+
503
+ Permissions:
504
+
505
+ ```ts
506
+ export const PermissionsConfig = {
507
+ User: {
508
+ ADMIN: { readMyProfile: 'ALL' },
509
+ USER: { readMyProfile: { conditions: { id: PermissionPlaceholder.USER_ID } } },
510
+ },
511
+ };
512
+ ```
513
+
514
+ Service (Prisma query remains straightforward; ABAC filtering is applied by the core):
515
+
516
+ ```ts
517
+ async getMyProfile() {
518
+ return this.prisma.model.user.findFirst({
519
+ where: { id: PermissionPlaceholder.USER_ID },
520
+ });
521
+ }
522
+ ```
523
+
524
+ ### Example 3: Registration Email Availability Check
525
+
526
+ Use case: a public endpoint checks if an email is already used. This must access user data safely, even without a logged-in user.
527
+
528
+ Controller:
529
+
530
+ ```ts
531
+ @Permission('checkEmailAvailability', ['user'])
532
+ @Get('/auth/email-available')
533
+ async isEmailAvailable(@Query('email') email: string) {
534
+ return this.service.isEmailAvailable(email);
535
+ }
536
+ ```
537
+
538
+ Service:
539
+
540
+ ```ts
541
+ async isEmailAvailable(email: string) {
542
+ let exists = false;
543
+
544
+ await this.prisma.withExposedModels(['user'], async () => {
545
+ const user = await this.prisma.model.user.findFirst({ where: { email } });
546
+ exists = !!user;
547
+ });
548
+
549
+ return { available: !exists };
550
+ }
551
+ ```
552
+
553
+ This pattern should be used sparingly and intentionally.
554
+
555
+ ---
556
+
557
+ ## Limitations
558
+
559
+ ### Prisma method restrictions (permission safety)
560
+
561
+ Avoid these Prisma methods because they can bypass or complicate granular permission filtering:
562
+
563
+ - `findUnique` → use `findFirst`
564
+ - `delete` → use `deleteMany` with a single-item filter
565
+ - `update` → use `updateMany` with a single-item filter
566
+
567
+ Operational guidance:
568
+
569
+ - Prefer `findMany`, `findFirst`, `updateMany`, and `deleteMany`.
570
+ - When you intend a single-record operation, use a restrictive `where` clause.
571
+
572
+ ### GraphQL status
573
+
574
+ GraphQL is part of the direction, but is **not fully ready** in the current release. REST CRUD + AdminJS are the primary integration surfaces for now.
575
+
576
+ ---
577
+
578
+ ## FAQ
579
+
580
+ **Are CRUD endpoints only for AdminJS?**
581
+ No. They can be used for any API consumer. Permissions are enforced consistently.
582
+
583
+ **Do I need to treat generated modules as read-only?**
584
+ No. Generated NestJS modules are **editable** and intended to be extended.
585
+
586
+ ---
587
+
588
+ ## Roadmap
589
+
590
+ Planned and under consideration:
591
+
592
+ - Production-ready GraphQL
593
+ - Object storage patterns (S3-friendly out-of-the-box support)
594
+ - Forgot-password flow
595
+ - Email templates + sending primitives
596
+ - Push notifications primitives
597
+ - WebSockets support
598
+ - Stronger testing baseline (unit tests scaffolding and patterns)
599
+ - “Agent-ready” development guidance (`agents.txt`, conventions for coding agents)
600
+ - Optional AI-assisted project scaffolding
601
+
602
+ ---
603
+
604
+ ## Support
605
+
606
+ Email: **core@appx-digital.com**
607
+
608
+ ---
609
+
610
+ ## Security
611
+
612
+ Report vulnerabilities privately to **core@appx-digital.com**. Do not open public issues for sensitive reports.
613
+
614
+ ---
615
+
616
+ ## Contributing
617
+
618
+ 1. Fork the repository.
619
+ 2. Create a feature branch.
620
+ 3. Keep changes focused; add tests where applicable.
621
+ 4. Open a PR with clear motivation and impact.
622
+
623
+ ---
624
+
625
+ ## License
626
+
627
+ MIT License. See [LICENSE](LICENSE).