@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 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`. Once the scoped package is
177
- published, existing applications can switch with:
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.0.tgz`, including JavaScript and TypeScript declarations. In the new NestJS project, replace the npm installation step with:
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.0.tgz
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
- For the first release under the new name, run `npm run release:check` and
237
- `npm run release` from the repository root. Version `0.1.0` can be used because
238
- `@nestjs-cms/core` is a separate npm package from `nestjs-cms` (provided that
239
- version has not already been published under the new name).
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`, or `packages/admin` for `@nestjs-cms/admin`. The packages have
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 rights are not implemented. Setting `ADMIN_API_KEY` requires `X-API-Key` on CMS requests; production currently requires a key of at least 32 characters. This is an integration mechanism, not a full authentication system.
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, and resource permissions are not included. 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`.
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":"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"}
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: string;
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 = (0, resource_loader_1.loadResources)(options.resourcesPath, options.extension ?? ".js");
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;AAYvC,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,IAAA,+BAAa,EAC7B,OAAO,CAAC,aAAa,EACrB,OAAO,CAAC,SAAS,IAAI,KAAK,CAC3B,CAAC;QACF,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;AAxCY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,eAAM,EAAC,EAAE,CAAC;qCAEwB,oBAAU;GADhC,cAAc,CAwC1B"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs-cms/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Definition-driven NestJS CMS with S3 attachments",
5
5
  "engines": {
6
6
  "node": ">=22"