@nestjs-cms/core 0.1.1 → 0.1.3
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/README.md +850 -12
- package/dist/src/resource/resource.controller.js +1 -0
- package/dist/src/resource/resource.controller.js.map +1 -1
- package/dist/src/resource/resource.module.d.ts +4 -1
- package/dist/src/resource/resource.module.js +12 -1
- package/dist/src/resource/resource.module.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Add a resource-driven CMS backend to a NestJS application. Define each resource in one file; the module discovers it and provides validated CRUD APIs, filters, lifecycle hooks, and S3-backed attachments.
|
|
4
4
|
|
|
5
|
+
For a complete API and admin workspace with login, start with
|
|
6
|
+
[Build a CMS from scratch](#build-a-cms-from-scratch), or
|
|
7
|
+
[use the demo as a template](#build-your-own-cms-repository). For a backend-only
|
|
8
|
+
integration into a new NestJS application, follow the quick start below.
|
|
9
|
+
|
|
5
10
|
## Quick start
|
|
6
11
|
|
|
7
12
|
Requires Node.js 22+, npm, and a PostgreSQL database. The package supports NestJS 11 with the Express adapter.
|
|
@@ -171,10 +176,836 @@ configureCmsApp(app);
|
|
|
171
176
|
|
|
172
177
|
Set `CORS_ORIGINS=http://localhost:5173`, for example. Your host application still controls its listening port, bind address, and bootstrap process. If it already manages CORS and shutdown hooks, keep that configuration instead.
|
|
173
178
|
|
|
179
|
+
## Build a CMS from scratch
|
|
180
|
+
|
|
181
|
+
Use this route to create your own repository without cloning or renaming the
|
|
182
|
+
demo. You will build a NestJS API, a Next.js admin at `/admin`, PostgreSQL, and
|
|
183
|
+
MinIO, including administrator login, user and role management, and image uploads.
|
|
184
|
+
Docker runs the database and storage; Node.js runs the two applications.
|
|
185
|
+
|
|
186
|
+
`@nestjs-cms/auth` supplies users, roles, database-backed sessions, and the
|
|
187
|
+
login API. You configure the module once; no copied authentication files are
|
|
188
|
+
needed. This walkthrough uses the latest npm releases of all three CMS packages.
|
|
189
|
+
|
|
190
|
+
### 1. Create the repository and two apps
|
|
191
|
+
|
|
192
|
+
Install Node.js 22+, npm, Git, and Docker with Docker Compose. Choose your project
|
|
193
|
+
folder name in the first command; subsequent commands use workspace paths, so
|
|
194
|
+
there are no demo-specific package names to rename:
|
|
195
|
+
|
|
196
|
+
```sh
|
|
197
|
+
mkdir my-cms
|
|
198
|
+
cd my-cms
|
|
199
|
+
git init
|
|
200
|
+
mkdir apps
|
|
201
|
+
npx @nestjs/cli@11 new api --directory apps/api --package-manager npm --skip-git --skip-install
|
|
202
|
+
npx create-next-app@16 apps/admin --ts --app --src-dir --eslint --no-tailwind --use-npm --import-alias "@/*" --skip-install --disable-git
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
For any additional Next.js prompts, keep the defaults. These commands use NestJS
|
|
206
|
+
11 and Next.js 16; the CMS packages are installed from their latest npm releases
|
|
207
|
+
in the next step. See the [Nest CLI options](https://docs.nestjs.com/cli/usages)
|
|
208
|
+
and [Next.js CLI options](https://nextjs.org/docs/app/api-reference/cli/create-next-app)
|
|
209
|
+
for the scaffold commands.
|
|
210
|
+
|
|
211
|
+
Create `package.json` in the repository root:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"name": "my-cms",
|
|
216
|
+
"private": true,
|
|
217
|
+
"workspaces": ["apps/*"],
|
|
218
|
+
"scripts": {
|
|
219
|
+
"dev:api": "npm run start:dev --workspace=apps/api",
|
|
220
|
+
"dev:admin": "npm run dev --workspace=apps/admin -- --hostname 127.0.0.1 --port 3001",
|
|
221
|
+
"build": "npm run build --workspaces",
|
|
222
|
+
"typecheck:admin": "tsc --noEmit -p apps/admin/tsconfig.json",
|
|
223
|
+
"start:api": "npm run start:prod --workspace=apps/api",
|
|
224
|
+
"start:admin": "npm run start --workspace=apps/admin -- --hostname 127.0.0.1 --port 3001"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
You can change `name` to your own project name. Create a root `.gitignore`:
|
|
230
|
+
|
|
231
|
+
```gitignore
|
|
232
|
+
node_modules/
|
|
233
|
+
dist/
|
|
234
|
+
.next/
|
|
235
|
+
coverage/
|
|
236
|
+
*.tsbuildinfo
|
|
237
|
+
.env
|
|
238
|
+
.env.*
|
|
239
|
+
!.env.example
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Run all remaining shell commands from this repository root unless stated otherwise:
|
|
243
|
+
|
|
244
|
+
```sh
|
|
245
|
+
npm install
|
|
246
|
+
npm install @nestjs-cms/core@latest @nestjs-cms/auth@latest dotenv --workspace=apps/api
|
|
247
|
+
npm install @nestjs-cms/admin@latest @mui/material-nextjs@9 @emotion/cache --workspace=apps/admin
|
|
248
|
+
mkdir -p docker apps/api/src/resources apps/admin/src/sections
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Keep the single root `package-lock.json` in Git. The resulting layout will be:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
my-cms/
|
|
255
|
+
apps/api/src/ NestJS host, authentication, and resources
|
|
256
|
+
apps/admin/src/ Next.js shell and admin configuration
|
|
257
|
+
docker/ MinIO Dockerfile and bucket initializer
|
|
258
|
+
docker-compose.yml PostgreSQL and MinIO; no pgAdmin
|
|
259
|
+
.env Local settings, excluded from Git
|
|
260
|
+
package.json
|
|
261
|
+
package-lock.json
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 2. Create the shared environment file
|
|
265
|
+
|
|
266
|
+
Create `.env` in the repository root. Replace the example passwords with your
|
|
267
|
+
own values; the administrator password must have 12–128 characters:
|
|
268
|
+
|
|
269
|
+
```dotenv
|
|
270
|
+
NODE_ENV=development
|
|
271
|
+
PORT=3000
|
|
272
|
+
DB_TYPE=postgres
|
|
273
|
+
DB_HOST=127.0.0.1
|
|
274
|
+
DB_PORT=5432
|
|
275
|
+
DB_USERNAME=cms
|
|
276
|
+
DB_PASSWORD=change-this-local-db-password
|
|
277
|
+
DB_DATABASE=cms
|
|
278
|
+
DB_SYNCHRONIZE=true
|
|
279
|
+
|
|
280
|
+
S3_REGION=us-east-1
|
|
281
|
+
S3_BUCKET=cms-attachments
|
|
282
|
+
S3_ENDPOINT=http://127.0.0.1:9000
|
|
283
|
+
S3_FORCE_PATH_STYLE=true
|
|
284
|
+
S3_ACCESS_KEY_ID=minioadmin
|
|
285
|
+
S3_SECRET_ACCESS_KEY=change-this-local-storage-password
|
|
286
|
+
ATTACHMENT_MAX_BYTES=20971520
|
|
287
|
+
|
|
288
|
+
ADMIN_ORIGIN=http://localhost:3001
|
|
289
|
+
ADMIN_BOOTSTRAP_EMAIL=admin@example.com
|
|
290
|
+
ADMIN_BOOTSTRAP_PASSWORD=choose-a-unique-admin-password
|
|
291
|
+
ADMIN_API_KEY=
|
|
292
|
+
CORS_ORIGINS=
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Bootstrap creates the first administrator only when the users table is empty.
|
|
296
|
+
Remove both bootstrap values after the first successful startup; existing
|
|
297
|
+
accounts and passwords are never overwritten. `ADMIN_API_KEY` may be empty for
|
|
298
|
+
local development because session authentication still protects the API. No
|
|
299
|
+
secret is passed to the frontend.
|
|
300
|
+
|
|
301
|
+
### 3. Create Docker Compose and the storage files
|
|
302
|
+
|
|
303
|
+
Create `docker-compose.yml` in the repository root:
|
|
304
|
+
|
|
305
|
+
```yaml
|
|
306
|
+
services:
|
|
307
|
+
postgres:
|
|
308
|
+
image: postgres:17-alpine
|
|
309
|
+
environment:
|
|
310
|
+
POSTGRES_USER: ${DB_USERNAME:?Set DB_USERNAME in .env}
|
|
311
|
+
POSTGRES_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD in .env}
|
|
312
|
+
POSTGRES_DB: ${DB_DATABASE:?Set DB_DATABASE in .env}
|
|
313
|
+
ports:
|
|
314
|
+
- "127.0.0.1:${DB_PORT:-5432}:5432"
|
|
315
|
+
volumes:
|
|
316
|
+
- postgres-data:/var/lib/postgresql/data
|
|
317
|
+
healthcheck:
|
|
318
|
+
test: ["CMD-SHELL", 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"']
|
|
319
|
+
interval: 5s
|
|
320
|
+
timeout: 3s
|
|
321
|
+
retries: 30
|
|
322
|
+
|
|
323
|
+
minio:
|
|
324
|
+
build:
|
|
325
|
+
context: ./docker
|
|
326
|
+
dockerfile: minio.Dockerfile
|
|
327
|
+
command: server /data --console-address ':9001'
|
|
328
|
+
environment:
|
|
329
|
+
MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:?Set S3_ACCESS_KEY_ID in .env}
|
|
330
|
+
MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:?Set S3_SECRET_ACCESS_KEY in .env}
|
|
331
|
+
ports:
|
|
332
|
+
- "127.0.0.1:9000:9000"
|
|
333
|
+
- "127.0.0.1:9001:9001"
|
|
334
|
+
volumes:
|
|
335
|
+
- minio-data:/data
|
|
336
|
+
healthcheck:
|
|
337
|
+
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
338
|
+
interval: 5s
|
|
339
|
+
timeout: 3s
|
|
340
|
+
retries: 30
|
|
341
|
+
|
|
342
|
+
minio-init:
|
|
343
|
+
build:
|
|
344
|
+
context: ./docker
|
|
345
|
+
dockerfile: storage-init.Dockerfile
|
|
346
|
+
depends_on:
|
|
347
|
+
minio:
|
|
348
|
+
condition: service_healthy
|
|
349
|
+
environment:
|
|
350
|
+
S3_ENDPOINT: http://minio:9000
|
|
351
|
+
S3_REGION: ${S3_REGION:-us-east-1}
|
|
352
|
+
S3_BUCKET: ${S3_BUCKET:?Set S3_BUCKET in .env}
|
|
353
|
+
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:?Set S3_ACCESS_KEY_ID in .env}
|
|
354
|
+
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:?Set S3_SECRET_ACCESS_KEY in .env}
|
|
355
|
+
|
|
356
|
+
volumes:
|
|
357
|
+
postgres-data:
|
|
358
|
+
minio-data:
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Create `docker/minio.Dockerfile`. This builds a pinned MinIO source release:
|
|
362
|
+
|
|
363
|
+
```dockerfile
|
|
364
|
+
FROM golang:1.24.8-bookworm AS build
|
|
365
|
+
ENV CGO_ENABLED=0
|
|
366
|
+
RUN go install github.com/minio/minio@RELEASE.2025-10-15T17-29-55Z
|
|
367
|
+
|
|
368
|
+
FROM debian:bookworm-slim
|
|
369
|
+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/*
|
|
370
|
+
COPY --from=build /go/bin/minio /usr/local/bin/minio
|
|
371
|
+
EXPOSE 9000 9001
|
|
372
|
+
ENTRYPOINT ["minio"]
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Create `docker/storage-init.Dockerfile`:
|
|
376
|
+
|
|
377
|
+
```dockerfile
|
|
378
|
+
FROM node:22-alpine
|
|
379
|
+
WORKDIR /app
|
|
380
|
+
RUN npm init -y && npm install --save-exact @aws-sdk/client-s3@3.850.0
|
|
381
|
+
COPY init-s3.cjs ./init-s3.cjs
|
|
382
|
+
CMD ["node", "init-s3.cjs"]
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Create `docker/init-s3.cjs`:
|
|
386
|
+
|
|
387
|
+
```js
|
|
388
|
+
const { S3Client, CreateBucketCommand } = require("@aws-sdk/client-s3");
|
|
389
|
+
|
|
390
|
+
const client = new S3Client({
|
|
391
|
+
endpoint: process.env.S3_ENDPOINT,
|
|
392
|
+
region: process.env.S3_REGION,
|
|
393
|
+
forcePathStyle: true,
|
|
394
|
+
credentials: {
|
|
395
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
|
396
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
async function main() {
|
|
401
|
+
const Bucket = process.env.S3_BUCKET;
|
|
402
|
+
try {
|
|
403
|
+
await client.send(new CreateBucketCommand({ Bucket }));
|
|
404
|
+
} catch (error) {
|
|
405
|
+
if (error.name !== "BucketAlreadyOwnedByYou") throw error;
|
|
406
|
+
}
|
|
407
|
+
console.log(`Private bucket ready: ${Bucket}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
main()
|
|
411
|
+
.catch((error) => {
|
|
412
|
+
console.error(error);
|
|
413
|
+
process.exitCode = 1;
|
|
414
|
+
})
|
|
415
|
+
.finally(() => client.destroy());
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Start the services:
|
|
419
|
+
|
|
420
|
+
```sh
|
|
421
|
+
docker compose up -d --build
|
|
422
|
+
docker compose ps -a
|
|
423
|
+
docker compose logs minio-init
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
The first MinIO build can take several minutes. Continue when PostgreSQL and
|
|
427
|
+
MinIO are healthy and `minio-init` has exited with code 0. The bucket is private;
|
|
428
|
+
uploads and downloads will go through your authenticated API.
|
|
429
|
+
|
|
430
|
+
There is no pgAdmin service. You can inspect the database using
|
|
431
|
+
`docker compose exec postgres sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'`.
|
|
432
|
+
The MinIO console is at `http://localhost:9001`; sign in with your S3 credentials.
|
|
433
|
+
|
|
434
|
+
### 4. Configure the NestJS host and a resource
|
|
435
|
+
|
|
436
|
+
Create `apps/api/src/env.ts` so the API loads the root `.env` even though npm runs
|
|
437
|
+
its workspace commands from `apps/api`:
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
import { config } from "dotenv";
|
|
441
|
+
import { resolve } from "node:path";
|
|
442
|
+
|
|
443
|
+
config({ path: resolve(__dirname, "../../../.env") });
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Replace `apps/api/tsconfig.json` with the following. This explicitly uses CommonJS
|
|
447
|
+
and keeps compiled resources at `dist/resources`, where discovery expects them:
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
{
|
|
451
|
+
"compilerOptions": {
|
|
452
|
+
"module": "commonjs",
|
|
453
|
+
"moduleResolution": "node",
|
|
454
|
+
"target": "ES2023",
|
|
455
|
+
"rootDir": "./src",
|
|
456
|
+
"outDir": "./dist",
|
|
457
|
+
"declaration": true,
|
|
458
|
+
"sourceMap": true,
|
|
459
|
+
"removeComments": true,
|
|
460
|
+
"emitDecoratorMetadata": true,
|
|
461
|
+
"experimentalDecorators": true,
|
|
462
|
+
"esModuleInterop": true,
|
|
463
|
+
"strict": true,
|
|
464
|
+
"skipLibCheck": true,
|
|
465
|
+
"incremental": true
|
|
466
|
+
},
|
|
467
|
+
"include": ["src/**/*.ts"],
|
|
468
|
+
"exclude": ["node_modules", "dist", "test", "**/*.spec.ts"]
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Create `apps/api/src/resources/pages.ts`:
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
import { defineResource, z } from "@nestjs-cms/core";
|
|
476
|
+
|
|
477
|
+
export default defineResource({
|
|
478
|
+
name: "pages",
|
|
479
|
+
schema: z
|
|
480
|
+
.object({
|
|
481
|
+
title: z.string().trim().min(1).max(200),
|
|
482
|
+
status: z.enum(["draft", "published"]).default("draft"),
|
|
483
|
+
})
|
|
484
|
+
.strict(),
|
|
485
|
+
columns: {
|
|
486
|
+
title: { type: String, length: 200 },
|
|
487
|
+
status: { type: String, length: 20 },
|
|
488
|
+
},
|
|
489
|
+
filterable: ["title", "status"],
|
|
490
|
+
sortable: ["title", "createdAt"],
|
|
491
|
+
attachments: [
|
|
492
|
+
{ field: "cover", mimeTypes: ["image/jpeg", "image/png", "image/webp"] },
|
|
493
|
+
],
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### 5. Enable authentication, users, and roles
|
|
498
|
+
|
|
499
|
+
Replace `apps/api/src/app.module.ts`:
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
import "./env";
|
|
503
|
+
import { Module } from "@nestjs/common";
|
|
504
|
+
import { join } from "node:path";
|
|
505
|
+
import { CmsAuthModule } from "@nestjs-cms/auth";
|
|
506
|
+
|
|
507
|
+
@Module({
|
|
508
|
+
imports: [
|
|
509
|
+
CmsAuthModule.forRoot({
|
|
510
|
+
resourcesPath: join(__dirname, "resources"),
|
|
511
|
+
adminOrigin: process.env.ADMIN_ORIGIN ?? "http://localhost:3001",
|
|
512
|
+
bootstrapAdmin: process.env.ADMIN_BOOTSTRAP_EMAIL
|
|
513
|
+
? {
|
|
514
|
+
email: process.env.ADMIN_BOOTSTRAP_EMAIL,
|
|
515
|
+
password: process.env.ADMIN_BOOTSTRAP_PASSWORD ?? "",
|
|
516
|
+
}
|
|
517
|
+
: undefined,
|
|
518
|
+
}),
|
|
519
|
+
],
|
|
520
|
+
})
|
|
521
|
+
export class AppModule {}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
This registers the CMS and authentication together. Do not also import
|
|
525
|
+
`ResourceModule.forRoot`, create your own `users`/`roles` definitions, or copy
|
|
526
|
+
an auth guard. The default admin prefix is `/admin`. The package automatically
|
|
527
|
+
registers `pages:read`, `pages:write`, and the built-in `users:manage` permission.
|
|
528
|
+
|
|
529
|
+
Replace `apps/api/src/main.ts`:
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
import "./env";
|
|
533
|
+
import { NestFactory } from "@nestjs/core";
|
|
534
|
+
import { AppModule } from "./app.module";
|
|
535
|
+
|
|
536
|
+
async function bootstrap() {
|
|
537
|
+
const app = await NestFactory.create(AppModule);
|
|
538
|
+
app.enableShutdownHooks();
|
|
539
|
+
await app.listen(Number(process.env.PORT ?? 3000), "127.0.0.1");
|
|
540
|
+
}
|
|
541
|
+
void bootstrap();
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Do not add a global `/api` prefix: CMS routes and the auth controller already
|
|
545
|
+
include it. The generated `app.controller.ts` and `app.service.ts` are unused
|
|
546
|
+
and can be deleted along with their generated test. Only the registered auth
|
|
547
|
+
controller and CMS routes are needed here.
|
|
548
|
+
|
|
549
|
+
### 6. Configure the admin and its API proxy
|
|
550
|
+
|
|
551
|
+
Replace `apps/admin/next.config.ts`:
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
import type { NextConfig } from "next";
|
|
555
|
+
|
|
556
|
+
const nextConfig: NextConfig = {
|
|
557
|
+
basePath: "/admin",
|
|
558
|
+
async rewrites() {
|
|
559
|
+
return [
|
|
560
|
+
{
|
|
561
|
+
source: "/api/:path*",
|
|
562
|
+
destination: "http://127.0.0.1:3000/api/:path*",
|
|
563
|
+
},
|
|
564
|
+
];
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
export default nextConfig;
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Next.js adds `/admin` to the rewrite source, so the browser calls
|
|
572
|
+
`/admin/api/pages` and the API receives `/api/pages`. This also keeps the login
|
|
573
|
+
cookie on the admin origin. The frontend does not need to read the root `.env`.
|
|
574
|
+
|
|
575
|
+
Create `apps/admin/src/sections/pages.ts`:
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
import { defineSection } from "@nestjs-cms/admin";
|
|
579
|
+
|
|
580
|
+
export default defineSection({
|
|
581
|
+
key: "pages",
|
|
582
|
+
label: "Pages",
|
|
583
|
+
singular: "page",
|
|
584
|
+
description: "Manage page content and cover images.",
|
|
585
|
+
readPermission: "pages:read",
|
|
586
|
+
writePermission: "pages:write",
|
|
587
|
+
defaults: { title: "", status: "draft" },
|
|
588
|
+
listFields: [
|
|
589
|
+
{ field: "title", label: "Title" },
|
|
590
|
+
{ field: "status", label: "Status" },
|
|
591
|
+
{ field: "cover", label: "Cover", type: "attachment" },
|
|
592
|
+
],
|
|
593
|
+
searchFields: [{ field: "title", label: "Title", type: "text" }],
|
|
594
|
+
formFields: [
|
|
595
|
+
{
|
|
596
|
+
field: "title",
|
|
597
|
+
label: "Title",
|
|
598
|
+
type: "text",
|
|
599
|
+
required: true,
|
|
600
|
+
maxLength: 200,
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
field: "status",
|
|
604
|
+
label: "Status",
|
|
605
|
+
type: "select",
|
|
606
|
+
items: [
|
|
607
|
+
{ value: "draft", label: "Draft" },
|
|
608
|
+
{ value: "published", label: "Published" },
|
|
609
|
+
],
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
field: "cover",
|
|
613
|
+
label: "Cover",
|
|
614
|
+
type: "attachment",
|
|
615
|
+
accept: "image/jpeg,image/png,image/webp",
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
});
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Create `apps/admin/src/admin.config.ts`:
|
|
622
|
+
|
|
623
|
+
```ts
|
|
624
|
+
"use client";
|
|
625
|
+
|
|
626
|
+
import { defineAdmin, createAuthSections } from "@nestjs-cms/admin";
|
|
627
|
+
import pages from "./sections/pages";
|
|
628
|
+
|
|
629
|
+
export default defineAdmin({
|
|
630
|
+
title: "My CMS",
|
|
631
|
+
apiBaseUrl: "/admin/api",
|
|
632
|
+
defaultSection: "pages",
|
|
633
|
+
sections: [
|
|
634
|
+
pages,
|
|
635
|
+
...createAuthSections([
|
|
636
|
+
{ value: "pages:read", label: "View pages" },
|
|
637
|
+
{ value: "pages:write", label: "Edit pages" },
|
|
638
|
+
]),
|
|
639
|
+
],
|
|
640
|
+
});
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Replace `apps/admin/src/app/page.tsx`:
|
|
644
|
+
|
|
645
|
+
```tsx
|
|
646
|
+
"use client";
|
|
647
|
+
|
|
648
|
+
import { AdminPortal } from "@nestjs-cms/admin";
|
|
649
|
+
import config from "../admin.config";
|
|
650
|
+
|
|
651
|
+
export default function Page() {
|
|
652
|
+
return <AdminPortal config={config} />;
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
Replace `apps/admin/src/app/layout.tsx`:
|
|
657
|
+
|
|
658
|
+
```tsx
|
|
659
|
+
import type { ReactNode } from "react";
|
|
660
|
+
import { AppRouterCacheProvider } from "@mui/material-nextjs/v16-appRouter";
|
|
661
|
+
import "@nestjs-cms/admin/styles.css";
|
|
662
|
+
|
|
663
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
664
|
+
return (
|
|
665
|
+
<html lang="en">
|
|
666
|
+
<body>
|
|
667
|
+
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
|
|
668
|
+
{children}
|
|
669
|
+
</AppRouterCacheProvider>
|
|
670
|
+
</body>
|
|
671
|
+
</html>
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
The package supplies its theme and CSS. The generated Next.js starter stylesheet
|
|
677
|
+
and font imports are not needed. No Tailwind configuration is required.
|
|
678
|
+
|
|
679
|
+
### 7. Start, sign in, and create a page
|
|
680
|
+
|
|
681
|
+
With Docker services healthy, run these commands in two terminals from the root:
|
|
682
|
+
|
|
683
|
+
```sh
|
|
684
|
+
# Terminal 1
|
|
685
|
+
npm run dev:api
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
```sh
|
|
689
|
+
# Terminal 2
|
|
690
|
+
npm run dev:admin
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
Open **http://localhost:3001/admin**. Sign in with `ADMIN_BOOTSTRAP_EMAIL` and
|
|
694
|
+
`ADMIN_BOOTSTRAP_PASSWORD` from `.env`. Create a page, enter a title, choose a status, select
|
|
695
|
+
a cover image, and save. Reopen it to check that its content and image were saved.
|
|
696
|
+
Create an Editor role with both page permissions in **Roles**, then create a
|
|
697
|
+
user with that role and a password in **Users**. Sign in as that user to check
|
|
698
|
+
restricted access. Remove both bootstrap variables after the first successful
|
|
699
|
+
startup. Sessions and accounts persist across API restarts.
|
|
700
|
+
|
|
701
|
+
Use the `localhost` browser address shown above: `ADMIN_ORIGIN` must match it
|
|
702
|
+
exactly. The API is at `http://127.0.0.1:3000/api`, but direct unauthenticated
|
|
703
|
+
requests return 401. Browser requests, including images, go through
|
|
704
|
+
`http://localhost:3001/admin/api/*`, where the session cookie is available.
|
|
705
|
+
|
|
706
|
+
If login fails, check the credentials and API logs; after ten login attempts in
|
|
707
|
+
one minute, wait a minute before retrying. If uploads return 503, check
|
|
708
|
+
`docker compose logs minio-init` and the S3 values in `.env`. If the database port
|
|
709
|
+
is occupied, change `DB_PORT` and recreate the Compose services. If you change
|
|
710
|
+
`PORT`, update the Next.js rewrite destination too.
|
|
711
|
+
|
|
712
|
+
### 8. Build and extend the starter
|
|
713
|
+
|
|
714
|
+
Check and build both apps:
|
|
715
|
+
|
|
716
|
+
```sh
|
|
717
|
+
npm run typecheck:admin
|
|
718
|
+
npm run build
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
To smoke-test the compiled API locally, stop `dev:api` and run `npm run start:api`
|
|
722
|
+
with `NODE_ENV=development`; keep `dev:admin` running. Use `start:admin` for the
|
|
723
|
+
built frontend. A production deployment must use HTTPS and the settings below.
|
|
724
|
+
|
|
725
|
+
Add more backend files in `apps/api/src/resources` and corresponding frontend
|
|
726
|
+
sections in `apps/admin/src/sections`; import each section into `admin.config.ts`
|
|
727
|
+
and add it to `sections` in navigation order. Restart the API after adding a
|
|
728
|
+
resource. See [resource definitions](docs/resources.md) and
|
|
729
|
+
[admin configuration](packages/admin/README.md) for categories, grouped forms,
|
|
730
|
+
nested fields, and custom dashboards. New resources default to `<resource>:read` and `<resource>:write` permissions.
|
|
731
|
+
Add those permission choices to `createAuthSections`, and grant them to the
|
|
732
|
+
appropriate roles. Existing roles are never silently granted new permissions.
|
|
733
|
+
|
|
734
|
+
For deployment, set `NODE_ENV=production`, use HTTPS and a public HTTPS
|
|
735
|
+
`ADMIN_ORIGIN`, set `DB_SYNCHRONIZE=false`, and apply reviewed migrations for
|
|
736
|
+
all resource and authentication tables. The core currently also requires a
|
|
737
|
+
server-only `ADMIN_API_KEY` of at least 32 characters. Update the Next.js proxy
|
|
738
|
+
if the API runs on another host. See the
|
|
739
|
+
[auth package guide](packages/auth/README.md) for migrations, sessions, permission
|
|
740
|
+
mappings, and deployment details, and [operations](docs/operations.md) for storage.
|
|
741
|
+
|
|
742
|
+
Stop infrastructure with `docker compose down`. Volumes preserve the database and
|
|
743
|
+
attachments; `docker compose down -v` deletes both. Changing credentials in `.env`
|
|
744
|
+
does not change the credentials already initialized in a PostgreSQL volume.
|
|
745
|
+
|
|
746
|
+
## Build your own CMS repository
|
|
747
|
+
|
|
748
|
+
This walkthrough uses `kongyim/nestjs-cms-demo` as a starting template for your own
|
|
749
|
+
repository. It gives you a NestJS API, a Next.js admin with a dashboard, pages,
|
|
750
|
+
categories, users and roles, plus PostgreSQL and MinIO for local development.
|
|
751
|
+
All package installation commands use the latest releases from npm.
|
|
752
|
+
|
|
753
|
+
The two packages have different responsibilities: `@nestjs-cms/core` supplies
|
|
754
|
+
resource APIs and attachments; `@nestjs-cms/admin` supplies the React portal.
|
|
755
|
+
`@nestjs-cms/auth` supplies login, sessions, account bootstrap, built-in users and
|
|
756
|
+
roles, and server-side permissions through one module registration.
|
|
757
|
+
|
|
758
|
+
### 1. Create your workspace from the demo
|
|
759
|
+
|
|
760
|
+
Install Node.js 22+, npm, Git, and Docker with Docker Compose. Start Docker, then
|
|
761
|
+
run these commands in the directory where you keep your projects:
|
|
762
|
+
|
|
763
|
+
```sh
|
|
764
|
+
git clone https://github.com/kongyim/nestjs-cms-demo.git my-cms
|
|
765
|
+
cd my-cms
|
|
766
|
+
git remote rename origin upstream
|
|
767
|
+
npm install
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
This keeps the demo's history and names its remote `upstream`. To push to your
|
|
771
|
+
own repository later, create an empty Git repository with your hosting provider,
|
|
772
|
+
then run `git remote add origin <your-repository-url>`.
|
|
773
|
+
|
|
774
|
+
The workspace already includes the app configuration and root npm scripts:
|
|
775
|
+
|
|
776
|
+
```text
|
|
777
|
+
my-cms/
|
|
778
|
+
apps/
|
|
779
|
+
api/
|
|
780
|
+
src/resources/ Backend schemas for pages and categories
|
|
781
|
+
src/app.module.ts CMS and authentication registration
|
|
782
|
+
src/env.ts Loads the shared root .env
|
|
783
|
+
admin/
|
|
784
|
+
app/ Next.js App Router shell
|
|
785
|
+
src/admin.config.ts
|
|
786
|
+
src/sections/ Resource forms and dashboard registration
|
|
787
|
+
src/components/ Custom dashboard component
|
|
788
|
+
next.config.ts Admin base path and same-origin API proxy
|
|
789
|
+
docker/ MinIO build and private-bucket initialization
|
|
790
|
+
docker-compose.yml
|
|
791
|
+
package.json npm workspaces and commands for both apps
|
|
792
|
+
.env Create this in step 3; do not commit it
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
The folder name `my-cms` does not change the npm workspace names. The commands
|
|
796
|
+
below use the template's existing `@nestjs-cms-demo/api` and
|
|
797
|
+
`@nestjs-cms-demo/admin` names.
|
|
798
|
+
|
|
799
|
+
### 2. Install the latest CMS packages
|
|
800
|
+
|
|
801
|
+
From `my-cms`, update both workspaces:
|
|
802
|
+
|
|
803
|
+
```sh
|
|
804
|
+
npm install @nestjs-cms/core@latest @nestjs-cms/auth@latest --workspace=@nestjs-cms-demo/api
|
|
805
|
+
npm install @nestjs-cms/admin@latest --workspace=@nestjs-cms-demo/admin
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
If your copy of the template still uses the previous backend name `nestjs-cms`,
|
|
809
|
+
replace all imports from `'nestjs-cms'` with `'@nestjs-cms/core'` in
|
|
810
|
+
`apps/api/src` and `apps/api/test`, including type imports, then remove it:
|
|
811
|
+
|
|
812
|
+
```sh
|
|
813
|
+
npm uninstall nestjs-cms --workspace=@nestjs-cms-demo/api
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
For an older demo checkout with copied auth files, follow the migration steps in
|
|
817
|
+
[the auth package guide](packages/auth/README.md#migrating-the-demo). No local tarballs or library checkout are needed. Commit
|
|
818
|
+
the updated package files and root lockfile with your application.
|
|
819
|
+
|
|
820
|
+
### 3. Configure the database, storage, and first administrator
|
|
821
|
+
|
|
822
|
+
Create `.env` in `my-cms` (beside `docker-compose.yml`), using this complete local
|
|
823
|
+
development configuration. Replace the example passwords before starting:
|
|
824
|
+
|
|
825
|
+
```dotenv
|
|
826
|
+
NODE_ENV=development
|
|
827
|
+
PORT=3000
|
|
828
|
+
|
|
829
|
+
DB_TYPE=postgres
|
|
830
|
+
DB_HOST=127.0.0.1
|
|
831
|
+
DB_PORT=5432
|
|
832
|
+
DB_USERNAME=cms
|
|
833
|
+
DB_PASSWORD=change-this-local-db-password
|
|
834
|
+
DB_DATABASE=cms
|
|
835
|
+
DB_SYNCHRONIZE=true
|
|
836
|
+
|
|
837
|
+
S3_REGION=us-east-1
|
|
838
|
+
S3_BUCKET=cms-attachments
|
|
839
|
+
S3_ENDPOINT=http://127.0.0.1:9000
|
|
840
|
+
S3_FORCE_PATH_STYLE=true
|
|
841
|
+
S3_ACCESS_KEY_ID=minioadmin
|
|
842
|
+
S3_SECRET_ACCESS_KEY=change-this-local-storage-password
|
|
843
|
+
ATTACHMENT_MAX_BYTES=20971520
|
|
844
|
+
|
|
845
|
+
PGADMIN_DEFAULT_EMAIL=admin@example.com
|
|
846
|
+
PGADMIN_DEFAULT_PASSWORD=change-this-local-pgadmin-password
|
|
847
|
+
|
|
848
|
+
ADMIN_BASE_PATH=/admin
|
|
849
|
+
ADMIN_ORIGIN=http://localhost:3001
|
|
850
|
+
ADMIN_BOOTSTRAP_EMAIL=admin@example.com
|
|
851
|
+
ADMIN_BOOTSTRAP_PASSWORD=choose-a-unique-admin-password
|
|
852
|
+
ADMIN_API_KEY=
|
|
853
|
+
CORS_ORIGINS=
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
Use a 12–128 character administrator password. `ADMIN_API_KEY` can be empty for
|
|
857
|
+
this local setup: the demo still requires a valid user session and checks
|
|
858
|
+
permissions. Never expose a shared API key in frontend configuration.
|
|
859
|
+
|
|
860
|
+
The API and Next.js configuration load this root `.env`; Docker Compose reads
|
|
861
|
+
it too. `ADMIN_ORIGIN` is the browser origin without `/admin`. Keep browser URLs
|
|
862
|
+
on `localhost` consistently so origin checks and session cookies work together.
|
|
863
|
+
|
|
864
|
+
### 4. Start PostgreSQL and MinIO
|
|
865
|
+
|
|
866
|
+
From the workspace root:
|
|
867
|
+
|
|
868
|
+
```sh
|
|
869
|
+
docker compose up -d --build
|
|
870
|
+
docker compose ps -a
|
|
871
|
+
docker compose logs minio-init
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
The first build compiles MinIO and can take several minutes. Wait until
|
|
875
|
+
PostgreSQL and MinIO are healthy and `minio-init` exits with code 0. The initializer
|
|
876
|
+
creates the private bucket configured by `S3_BUCKET`.
|
|
877
|
+
|
|
878
|
+
The API runs on your host and connects to `127.0.0.1:5432` and
|
|
879
|
+
`http://127.0.0.1:9000`. If port 5432 is occupied, choose another `DB_PORT` in
|
|
880
|
+
`.env`; Compose and the API both use it. The other default ports are 3000, 3001,
|
|
881
|
+
8080, 9000, and 9001; stop conflicting services before continuing.
|
|
882
|
+
|
|
883
|
+
### 5. Start both apps and sign in
|
|
884
|
+
|
|
885
|
+
Run each command in a separate terminal, both from `my-cms`:
|
|
886
|
+
|
|
887
|
+
```sh
|
|
888
|
+
# Terminal 1
|
|
889
|
+
npm run dev:api
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
```sh
|
|
893
|
+
# Terminal 2
|
|
894
|
+
npm run dev:admin
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
Open **http://localhost:3001/admin** and sign in with your
|
|
898
|
+
`ADMIN_BOOTSTRAP_EMAIL` and `ADMIN_BOOTSTRAP_PASSWORD`. The first API startup
|
|
899
|
+
creates the tables and, only when the users table is empty, creates the
|
|
900
|
+
Administrator role and account. After a successful login, remove both bootstrap
|
|
901
|
+
variables from `.env` and restart the API. Changing those variables later does
|
|
902
|
+
not reset an existing account's password.
|
|
903
|
+
|
|
904
|
+
Try the complete workflow:
|
|
905
|
+
|
|
906
|
+
1. Create a category in **Categories**.
|
|
907
|
+
2. Create a page in **Pages**, select the category, and save it.
|
|
908
|
+
3. Edit the page, add an attachment, save, and reopen it to check persistence.
|
|
909
|
+
4. Create an Editor role with `pages:read` and `pages:write`, then create a user
|
|
910
|
+
with that role and a password. Sign in as that user to check restricted access.
|
|
911
|
+
|
|
912
|
+
| Service | Local URL | Login |
|
|
913
|
+
| ------------- | ----------------------------- | ----------------------------------------------------- |
|
|
914
|
+
| Admin portal | `http://localhost:3001/admin` | CMS user email and password |
|
|
915
|
+
| NestJS API | `http://localhost:3000/api` | Session-protected; use the admin proxy in the browser |
|
|
916
|
+
| MinIO console | `http://localhost:9001` | `S3_ACCESS_KEY_ID` / `S3_SECRET_ACCESS_KEY` |
|
|
917
|
+
| pgAdmin | `http://localhost:8080` | `PGADMIN_DEFAULT_EMAIL` / `PGADMIN_DEFAULT_PASSWORD` |
|
|
918
|
+
|
|
919
|
+
In pgAdmin, open the preconfigured **CMS PostgreSQL** connection and enter
|
|
920
|
+
`DB_PASSWORD`. Its internal database host is `postgres`, not `localhost`.
|
|
921
|
+
|
|
922
|
+
Browser requests to `/admin/api/*` are proxied by Next.js to the NestJS `/api/*`
|
|
923
|
+
routes. The demo's default session cookie uses `/admin/api`, so test authenticated
|
|
924
|
+
browser requests through the admin URL. No separate browser CORS setup is needed.
|
|
925
|
+
|
|
926
|
+
### 6. Customize your CMS
|
|
927
|
+
|
|
928
|
+
Start with these files:
|
|
929
|
+
|
|
930
|
+
| Change | File or directory |
|
|
931
|
+
| ------------------------------------------------ | ----------------------------------------------------- |
|
|
932
|
+
| Portal title, initial screen, navigation order | `apps/admin/src/admin.config.ts` |
|
|
933
|
+
| Dashboard content | `apps/admin/src/components/dashboard.tsx` |
|
|
934
|
+
| Backend fields, validation, filters, attachments | `apps/api/src/resources/` |
|
|
935
|
+
| List columns, search controls, form fields | `apps/admin/src/sections/` |
|
|
936
|
+
| Server-side resource permissions | `resourcePermissions` in `apps/api/src/app.module.ts` |
|
|
937
|
+
| Admin URL prefix and API destination | `apps/admin/next.config.ts` |
|
|
938
|
+
|
|
939
|
+
For example, change the `title` in `admin.config.ts` and reorder its `sections`
|
|
940
|
+
array to rearrange navigation. `defaultSection` selects the first screen without
|
|
941
|
+
changing that order. Use `defineCustomSection` for a custom React page such as the
|
|
942
|
+
dashboard; use `defineSection` for a resource list and editor.
|
|
943
|
+
|
|
944
|
+
To add a resource, create its backend definition in `apps/api/src/resources`
|
|
945
|
+
using [the resource guide](docs/resources.md), then create a matching frontend
|
|
946
|
+
section whose `key` is the resource name. Import the section in `admin.config.ts`
|
|
947
|
+
and add it to `sections`. Backend resources are discovered automatically on
|
|
948
|
+
restart; frontend sections are registered explicitly. Match form fields to the
|
|
949
|
+
backend schema, and update the module permission mapping and role permissions for the new
|
|
950
|
+
resource. Hiding navigation alone does not restrict API access.
|
|
951
|
+
|
|
952
|
+
The [admin package guide](packages/admin/README.md) covers grouped forms,
|
|
953
|
+
responsive field widths, nested JSON, attachment previews, password actions,
|
|
954
|
+
and custom pages.
|
|
955
|
+
|
|
956
|
+
To move the portal, set `ADMIN_BASE_PATH=/cms` (or `/tools/cms`) in `.env` and
|
|
957
|
+
restart both apps. Sign in again at the new path. Set `ADMIN_BASE_PATH=` for `/`.
|
|
958
|
+
Production builds must be rebuilt after a base-path change because Next.js embeds
|
|
959
|
+
the path in browser assets.
|
|
960
|
+
|
|
961
|
+
### 7. Check and build your repository
|
|
962
|
+
|
|
963
|
+
From the workspace root:
|
|
964
|
+
|
|
965
|
+
```sh
|
|
966
|
+
npm test -- --runInBand
|
|
967
|
+
npm run typecheck:admin
|
|
968
|
+
npm run build
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
The build compiles both apps. To run the built applications, stop the development
|
|
972
|
+
servers and use separate terminals:
|
|
973
|
+
|
|
974
|
+
```sh
|
|
975
|
+
# Terminal 1
|
|
976
|
+
npm run start:api
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
```sh
|
|
980
|
+
# Terminal 2
|
|
981
|
+
npm run start:admin
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
For deployment, use HTTPS, `NODE_ENV=production`, `DB_SYNCHRONIZE=false`, reviewed
|
|
985
|
+
database migrations, a server-only `ADMIN_API_KEY` of at least 32 characters, and
|
|
986
|
+
the public admin origin in `ADMIN_ORIGIN`. Migrations must include the host's
|
|
987
|
+
resource, credential, and session tables. If the API runs on another host, update
|
|
988
|
+
the rewrite destination in `apps/admin/next.config.ts` before building. See
|
|
989
|
+
[operations](docs/operations.md) for database and storage configuration.
|
|
990
|
+
|
|
991
|
+
To stop local infrastructure, run `docker compose down`; the volumes retain your
|
|
992
|
+
data. Adding `-v` deletes database and storage data. Changing database credentials
|
|
993
|
+
in `.env` does not update a PostgreSQL user already stored in a volume.
|
|
994
|
+
|
|
995
|
+
### Troubleshooting the workspace
|
|
996
|
+
|
|
997
|
+
| Symptom | Check |
|
|
998
|
+
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
999
|
+
| API cannot connect to PostgreSQL | Check `docker compose ps`, `DB_PORT`, and root `.env` credentials. |
|
|
1000
|
+
| Login fails on a fresh setup | Check API startup logs, bootstrap password length, and whether the users table already contains an account. |
|
|
1001
|
+
| Login works but requests fail or return 403 | Use `http://localhost:3001/admin`, check `ADMIN_ORIGIN`, and verify the user's role permissions. |
|
|
1002
|
+
| Attachments return 503 | Check MinIO health, `minio-init` logs, and the API's S3 endpoint, credentials, and bucket. |
|
|
1003
|
+
| A new resource or section is missing | Restart the API for resource discovery; import the frontend section and add it to `sections`. |
|
|
1004
|
+
|
|
174
1005
|
## Migrating from `nestjs-cms`
|
|
175
1006
|
|
|
176
|
-
The backend package is now named `@nestjs-cms/core`.
|
|
177
|
-
|
|
1007
|
+
The backend package is now named `@nestjs-cms/core`. Existing applications can
|
|
1008
|
+
switch with:
|
|
178
1009
|
|
|
179
1010
|
```sh
|
|
180
1011
|
npm uninstall nestjs-cms
|
|
@@ -194,10 +1025,10 @@ npm ci
|
|
|
194
1025
|
npm pack
|
|
195
1026
|
```
|
|
196
1027
|
|
|
197
|
-
This builds `nestjs-cms-core-0.1.
|
|
1028
|
+
This builds `nestjs-cms-core-0.1.3.tgz`, including JavaScript and TypeScript declarations. In the new NestJS project, replace the npm installation step with:
|
|
198
1029
|
|
|
199
1030
|
```sh
|
|
200
|
-
npm install /absolute/path/to/nestjs-cms-core-0.1.
|
|
1031
|
+
npm install /absolute/path/to/nestjs-cms-core-0.1.3.tgz
|
|
201
1032
|
```
|
|
202
1033
|
|
|
203
1034
|
The package excludes the demo application, demo resources, demo migrations, tests, and local secrets. Packing does not publish anything to a registry.
|
|
@@ -233,10 +1064,12 @@ output, or use a locally packed package as described above.
|
|
|
233
1064
|
|
|
234
1065
|
## Publishing updates to npm
|
|
235
1066
|
|
|
236
|
-
|
|
237
|
-
`
|
|
238
|
-
`@nestjs-cms/
|
|
239
|
-
|
|
1067
|
+
The authentication package requires the new core integration API. Publish
|
|
1068
|
+
`@nestjs-cms/core` 0.1.3 first, then `@nestjs-cms/auth` 0.1.0 and
|
|
1069
|
+
`@nestjs-cms/admin` 0.1.2. Their versions are already prepared in this checkout;
|
|
1070
|
+
run `release:check` and `release` in each package directory without bumping them
|
|
1071
|
+
again unless that version has already been published. Nothing is published by
|
|
1072
|
+
`release:check`.
|
|
240
1073
|
|
|
241
1074
|
After confirming `npm view @nestjs-cms/core version` returns the new release,
|
|
242
1075
|
the owner of the old package can mark it deprecated:
|
|
@@ -248,9 +1081,9 @@ npm deprecate 'nestjs-cms@*' 'Renamed to @nestjs-cms/core. Install @nestjs-cms/c
|
|
|
248
1081
|
This keeps existing versions available while directing users to the new package.
|
|
249
1082
|
For subsequent releases, follow the steps below.
|
|
250
1083
|
|
|
251
|
-
|
|
252
1084
|
Run the following commands in the package you changed: the repository root for
|
|
253
|
-
`@nestjs-cms/core`,
|
|
1085
|
+
`@nestjs-cms/core`, `packages/auth` for `@nestjs-cms/auth`, or
|
|
1086
|
+
`packages/admin` for `@nestjs-cms/admin`. The packages have
|
|
254
1087
|
independent versions; release only the packages you changed.
|
|
255
1088
|
|
|
256
1089
|
```sh
|
|
@@ -292,13 +1125,18 @@ publication rules.
|
|
|
292
1125
|
|
|
293
1126
|
## Documentation
|
|
294
1127
|
|
|
1128
|
+
- [Authentication, users, roles, and sessions](packages/auth/README.md)
|
|
295
1129
|
- [Resource definitions, hooks, and service methods](docs/resources.md)
|
|
296
1130
|
- [API contract and frontend examples](docs/api.md)
|
|
297
1131
|
- [Environment, databases, storage, and deployment](docs/operations.md)
|
|
298
1132
|
|
|
299
1133
|
`ResourceService` is exported for use in other application services. It provides CRUD and `createAttachment`, `findAttachment`, `downloadAttachment`, and `removeAttachment`. All browser attachment traffic goes through the backend API.
|
|
300
1134
|
|
|
301
|
-
User login, roles, and resource
|
|
1135
|
+
User login, roles, sessions, and resource permissions are provided by the optional
|
|
1136
|
+
[`@nestjs-cms/auth`](packages/auth/README.md) package. Core alone uses API-key
|
|
1137
|
+
authentication; production requires a server-only key of at least 32 characters.
|
|
1138
|
+
The auth package validates the session and permissions before supplying that key
|
|
1139
|
+
internally. It never exposes the key to the browser.
|
|
302
1140
|
|
|
303
1141
|
## Develop this library
|
|
304
1142
|
|
|
@@ -330,7 +1168,7 @@ Integration tests use the dedicated `cms_test` database and real MinIO services.
|
|
|
330
1168
|
|
|
331
1169
|
## Scope
|
|
332
1170
|
|
|
333
|
-
The package implements resource discovery, CRUD, filters, transactional hooks, and backend-managed attachments. Import/export, image resizing, encrypted/hashed fields, automatic relation expansion
|
|
1171
|
+
The package implements resource discovery, CRUD, filters, transactional hooks, and backend-managed attachments. Import/export, image resizing, encrypted/hashed fields, and automatic relation expansion are not included in core. Resource permissions are available through `@nestjs-cms/auth`. Multilingual and nested fields use JSON. See the [comparison with the original project](docs/resources.md#comparison-with-the-original-project) for differences from `polyu-souvenirs`.
|
|
334
1172
|
|
|
335
1173
|
## Reusable admin frontend
|
|
336
1174
|
|
|
@@ -157,6 +157,7 @@ __decorate([
|
|
|
157
157
|
], ResourceController.prototype, "remove", null);
|
|
158
158
|
exports.ResourceController = ResourceController = __decorate([
|
|
159
159
|
(0, common_1.Controller)("api"),
|
|
160
|
+
(0, common_1.SetMetadata)("nestjs-cms:resource-controller", true),
|
|
160
161
|
(0, common_1.UseGuards)(api_key_guard_1.ApiKeyGuard),
|
|
161
162
|
(0, common_1.UseFilters)(error_filter_1.ErrorFilter),
|
|
162
163
|
__metadata("design:paramtypes", [resource_service_1.ResourceService,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.controller.js","sourceRoot":"","sources":["../../../src/resource/resource.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"resource.controller.js","sourceRoot":"","sources":["../../../src/resource/resource.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { DynamicModule } from "@nestjs/common";
|
|
2
2
|
import { DataSource } from "typeorm";
|
|
3
|
+
import type { ResourceDefinition } from "./resource.types";
|
|
3
4
|
export interface ResourceModuleOptions {
|
|
4
5
|
/** Absolute path to the consumer's resource directory. */
|
|
5
|
-
resourcesPath
|
|
6
|
+
resourcesPath?: string;
|
|
7
|
+
/** Explicit definitions, combined with any discovered resources. */
|
|
8
|
+
resources?: ResourceDefinition[];
|
|
6
9
|
/** Use .ts only with a TypeScript runtime such as ts-node. Default: .js. */
|
|
7
10
|
extension?: ".ts" | ".js";
|
|
8
11
|
/** Optional dedicated CMS data source. This module closes it on shutdown. */
|
|
@@ -30,7 +30,18 @@ let ResourceModule = ResourceModule_1 = class ResourceModule {
|
|
|
30
30
|
}
|
|
31
31
|
static forRoot(options) {
|
|
32
32
|
// Scan once so the database and API use exactly the same definitions.
|
|
33
|
-
const resources =
|
|
33
|
+
const resources = [
|
|
34
|
+
...(options.resourcesPath
|
|
35
|
+
? (0, resource_loader_1.loadResources)(options.resourcesPath, options.extension ?? ".js")
|
|
36
|
+
: []),
|
|
37
|
+
...(options.resources ?? []),
|
|
38
|
+
];
|
|
39
|
+
const names = new Set();
|
|
40
|
+
for (const resource of resources) {
|
|
41
|
+
if (names.has(resource.name))
|
|
42
|
+
throw new Error(`Duplicate resource name: ${resource.name}`);
|
|
43
|
+
names.add(resource.name);
|
|
44
|
+
}
|
|
34
45
|
const registry = new resource_registry_1.ResourceRegistry(resources);
|
|
35
46
|
const source = options.dataSource ?? (0, create_data_source_1.createCmsDataSource)(resources);
|
|
36
47
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.module.js","sourceRoot":"","sources":["../../../src/resource/resource.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAuD;AACvD,qCAAqC;AACrC,uEAAqE;AACrE,uDAAkD;AAClD,+DAA2D;AAC3D,2DAAuD;AACvD,mEAA8D;AAC9D,yDAAqD;AACrD,sEAAkE;AAClE,sDAAkD;AAClD,oDAA+C;AAC/C,kDAA8C;
|
|
1
|
+
{"version":3,"file":"resource.module.js","sourceRoot":"","sources":["../../../src/resource/resource.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAuD;AACvD,qCAAqC;AACrC,uEAAqE;AACrE,uDAAkD;AAClD,+DAA2D;AAC3D,2DAAuD;AACvD,mEAA8D;AAC9D,yDAAqD;AACrD,sEAAkE;AAClE,sDAAkD;AAClD,oDAA+C;AAC/C,kDAA8C;AAevC,IAAM,cAAc,sBAApB,MAAM,cAAc;IACI;IAA7B,YAA6B,EAAc;QAAd,OAAE,GAAF,EAAE,CAAY;IAAG,CAAC;IAE/C,MAAM,CAAC,OAAO,CAAC,OAA8B;QAC3C,sEAAsE;QACtE,MAAM,SAAS,GAAG;YAChB,GAAG,CAAC,OAAO,CAAC,aAAa;gBACvB,CAAC,CAAC,IAAA,+BAAa,EAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;gBAClE,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;SAC7B,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC1B,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YAC/D,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,oCAAgB,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,IAAI,IAAA,wCAAmB,EAAC,SAAS,CAAC,CAAC;QACpE,OAAO;YACL,MAAM,EAAE,gBAAc;YACtB,WAAW,EAAE,CAAC,wCAAkB,CAAC;YACjC,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,oBAAU;oBACnB,UAAU,EAAE,GAAG,EAAE,CACf,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE;iBACtD;gBACD,EAAE,OAAO,EAAE,oCAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE;gBACjD,2CAAmB;gBACnB,kCAAe;gBACf,sCAAiB;gBACjB,sBAAS;gBACT,2BAAW;gBACX,0BAAW;aACZ;YACD,OAAO,EAAE;gBACP,kCAAe;gBACf,oCAAgB;gBAChB,sCAAiB;gBACjB,oBAAU;aACX;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,IAAI,CAAC,EAAE,CAAC,aAAa;YAAE,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC;CACF,CAAA;AAhDY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,eAAM,EAAC,EAAE,CAAC;qCAEwB,oBAAU;GADhC,cAAc,CAgD1B"}
|