CMS NestJS Modules (內容管理系統模組)
Overview
@rytass/cms-base-nestjs-module 系列提供完整的 NestJS CMS 解決方案,支援文章管理、分類系統、審核流程和多語言內容。
套件清單
套件 說明
@rytass/cms-base-nestjs-module
核心 CMS 模組(服務層、實體、DataLoader)
@rytass/cms-base-nestjs-graphql-module
GraphQL 整合層(Resolvers、Queries、Mutations)
Quick Start
安裝
核心模組
npm install @rytass/cms-base-nestjs-module
GraphQL 整合(可選)
npm install @rytass/cms-base-nestjs-graphql-module
基本設定
import { CMSBaseModule } from '@rytass/cms-base-nestjs-module';
@Module({ imports: [ TypeOrmModule.forRoot({ /* 資料庫配置 */ }), CMSBaseModule.forRoot({ multipleLanguageMode: true, enableDraftMode: true, signatureLevels: ['編輯', '主編'], }), ], }) export class AppModule {}
GraphQL 整合
import { CMSBaseGraphQLModule } from '@rytass/cms-base-nestjs-graphql-module';
@Module({ imports: [ GraphQLModule.forRoot({ /* Apollo 配置 */ }), CMSBaseGraphQLModule.forRoot({ multipleLanguageMode: true, enableDraftMode: true, signatureLevels: ['編輯', '主編'], }), ], }) export class AppModule {}
Core Concepts
文章生命週期 (Article Stages)
DRAFT → REVIEWING → VERIFIED → SCHEDULED/RELEASED ↓ DELETED
階段 說明 觸發操作
DRAFT
草稿 建立/退回
REVIEWING
審核中 submit()
VERIFIED
已核准 approve()
SCHEDULED
預約發布 release(futureDate)
RELEASED
已發布 release()
DELETED
已刪除 archive()
UNKNOWN
未知狀態 例外狀態
Symbol-Based 注入
import { RESOLVED_ARTICLE_REPO, ArticleBaseService, } from '@rytass/cms-base-nestjs-module';
@Injectable() export class ArticleResolver { constructor( @Inject(RESOLVED_ARTICLE_REPO) private readonly articleRepo: Repository<BaseArticleEntity>,
// ArticleBaseService 直接使用類別注入
private readonly articleService: ArticleBaseService,
) {} }
DataLoader 模式
防止 N+1 查詢問題:
import { ArticleDataLoader } from '@rytass/cms-base-nestjs-module';
@Injectable() export class ArticleResolver { constructor(private readonly articleDataLoader: ArticleDataLoader) {}
@ResolveField() async categories(@Parent() article: Article) { return this.articleDataLoader.categoriesLoader.load(article.id); } }
可用的 DataLoaders:
DataLoader 提供的 Loaders
ArticleDataLoader
stageLoader , categoriesLoader
ArticleVersionDataLoader
stageVersionsLoader , versionsLoader
ArticleSignatureDataLoader
versionSignaturesLoader
CategoryDataLoader
withParentsLoader
Common Patterns
自訂實體擴展
import { BaseArticleEntity } from '@rytass/cms-base-nestjs-module';
@Entity('articles') @ChildEntity() export class CustomArticle extends BaseArticleEntity { @Column({ nullable: true }) featuredImage?: string;
@Column({ type: 'jsonb', nullable: true }) seoMetadata?: Record<string, any>; }
// 模組配置 CMSBaseModule.forRoot({ articleEntity: CustomArticle, articleVersionEntity: CustomArticleVersion, // 可選 articleVersionContentEntity: CustomContent, // 可選 categoryEntity: CustomCategory, // 可選 })
文章版本管理
// 新增版本 await articleService.addVersion(articleId, { title: '更新標題', content: [...], categoryIds: ['cat-1'], userId: 'user-1', submitted: true, // 直接提交審核 });
// 刪除特定版本 await articleService.deleteVersion(articleId, 2);
// 封存文章(軟刪除) await articleService.archive(articleId);
多語言內容
// 建立多語言文章 await articleService.create({ multiLanguageContents: { 'zh-TW': { title: '標題', content: '內容' }, 'en-US': { title: 'Title', content: 'Content' }, }, categoryIds: ['cat-1'], });
審核流程
// 1. 提交審核 await articleService.submit(articleId, { userId: 'user-1' });
// 2. 核准 await articleService.approveVersion( { id: articleId, version: 1 }, { signatureLevel: '主編', signerId: 'user-1' }, );
// 3. 發布 await articleService.release(articleId, { releasedAt: new Date(), version: 1, // 可選,指定版本 userId: 'user-1', });
// 4. 退回(重新編輯) await articleService.putBack(articleId);
// 5. 拒絕審核 await articleService.rejectVersion( { id: articleId }, { signatureLevel: '主編', signerId: 'user-1', reason: '內容不完整' }, );
// 6. 撤回已發布文章 await articleService.withdraw(articleId, 1); // id, version
GraphQL 查詢範例
公開文章列表
query Articles($page: Int, $limit: Int) { articles(page: $page, limit: $limit) { items { articleId title categories { id, name } releasedBy { username } } meta { totalCount, hasNextPage } } }
後台文章(含所有階段)
query BackstageArticles($stage: ArticleStage) { backstageArticles(stage: $stage) { items { articleId stage signatures { approved, level } } } }
權限控制整合
注意: BaseResource 和 BaseAction 枚舉定義在 @rytass/cms-base-nestjs-graphql-module 的 src/constants/enum/ 目錄下,但目前未從 index.ts 導出。如需使用,請直接引用字串或在專案中自行定義。
import { AllowActions } from '@rytass/member-base-nestjs-module';
@Mutation() @AllowActions([['article', 'create']]) // 使用字串代替枚舉 async createArticle() { }
Module Options
選項 預設 說明
multipleLanguageMode
false 啟用多語言內容
allowMultipleParentCategories
false 分類允許多個父分類
allowCircularCategories
false 分類允許循環參照
fullTextSearchMode
false 啟用全文搜尋
enableDraftMode
false 啟用草稿模式
signatureLevels
[] 審核層級定義
autoReleaseWhenLatestSignatureApproved
false 最終核准後自動發布
articleEntity
BaseArticleEntity 自訂文章實體類別
articleVersionEntity
BaseArticleVersionEntity 自訂文章版本實體類別
articleVersionContentEntity
BaseArticleVersionContentEntity 自訂文章內容實體類別
categoryEntity
BaseCategoryEntity 自訂分類實體類別
categoryMultiLanguageNameEntity
BaseCategoryMultiLanguageNameEntity 自訂多語言分類名稱實體類別
signatureLevelEntity
BaseSignatureLevelEntity 自訂審核層級實體類別
Additional Types
Enums
// 文章階段 enum ArticleStage { DRAFT = 'DRAFT', REVIEWING = 'REVIEWING', VERIFIED = 'VERIFIED', SCHEDULED = 'SCHEDULED', RELEASED = 'RELEASED', DELETED = 'DELETED', UNKNOWN = 'UNKNOWN', }
// 文章排序 enum ArticleSorter { CREATED_AT_DESC = 'CREATED_AT_DESC', CREATED_AT_ASC = 'CREATED_AT_ASC', UPDATED_AT_DESC = 'UPDATED_AT_DESC', UPDATED_AT_ASC = 'UPDATED_AT_ASC', SUBMITTED_AT_DESC = 'SUBMITTED_AT_DESC', SUBMITTED_AT_ASC = 'SUBMITTED_AT_ASC', RELEASED_AT_DESC = 'RELEASED_AT_DESC', RELEASED_AT_ASC = 'RELEASED_AT_ASC', }
// 文章搜尋模式 enum ArticleSearchMode { FULL_TEXT = 'full_text', TITLE = 'title', TITLE_AND_TAG = 'title-and-tag', }
// 審核結果 enum ArticleSignatureResult { APPROVED = 'APPROVED', REJECTED = 'REJECTED', }
Error Classes
// 基礎錯誤 class MultipleLanguageModeIsDisabledError extends Error {}
// 文章相關錯誤 class ArticleNotFoundError extends Error {} class ArticleVersionNotFoundError extends Error {}
// 分類相關錯誤 class CategoryNotFoundError extends Error {} class CircularCategoryNotAllowedError extends Error {} class MultipleParentCategoryNotAllowedError extends Error {} class ParentCategoryNotFoundError extends Error {}
Symbol Tokens
Repository Tokens(可用於依賴注入):
-
RESOLVED_ARTICLE_REPO
-
RESOLVED_ARTICLE_VERSION_REPO
-
RESOLVED_ARTICLE_VERSION_CONTENT_REPO
-
RESOLVED_CATEGORY_REPO
-
RESOLVED_CATEGORY_MULTI_LANGUAGE_NAME_REPO
-
RESOLVED_SIGNATURE_LEVEL_REPO
Mode Tokens(模組配置狀態):
-
MULTIPLE_LANGUAGE_MODE
-
DRAFT_MODE
-
MULTIPLE_CATEGORY_PARENT_MODE
-
CIRCULAR_CATEGORY_MODE
-
FULL_TEXT_SEARCH_MODE
-
SIGNATURE_LEVELS
-
AUTO_RELEASE_AFTER_APPROVED
API Reference
詳細 API 文件請參閱 reference.md。
Troubleshooting
循環依賴錯誤
使用 Symbol Token 注入而非直接類別注入:
// 正確 @Inject(RESOLVED_ARTICLE_REPO) repo: Repository<Article>
// 避免 constructor(private readonly repo: ArticleRepository)
DataLoader 快取問題
ArticleDataLoader 提供的 loaders:
-
stageLoader
-
帶 LRU 快取
-
categoriesLoader
-
帶 LRU 快取
如需無快取版本,請在自訂 DataLoader 時設定:
// DataLoader 本身支援 cache: false 選項 const uncachedLoader = new DataLoader( (keys) => this.batchLoad(keys), { cache: false } );
多語言模式未啟用
確認模組配置 multipleLanguageMode: true ,否則只能存取第一個語言內容。