cms-modules
SKILL.md
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_REPORESOLVED_ARTICLE_VERSION_REPORESOLVED_ARTICLE_VERSION_CONTENT_REPORESOLVED_CATEGORY_REPORESOLVED_CATEGORY_MULTI_LANGUAGE_NAME_REPORESOLVED_SIGNATURE_LEVEL_REPO
Mode Tokens(模組配置狀態):
MULTIPLE_LANGUAGE_MODEDRAFT_MODEMULTIPLE_CATEGORY_PARENT_MODECIRCULAR_CATEGORY_MODEFULL_TEXT_SEARCH_MODESIGNATURE_LEVELSAUTO_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,否則只能存取第一個語言內容。
Weekly Installs
6
Repository
rytass/utilsGitHub Stars
6
First Seen
Feb 5, 2026
Security Audits
Installed on
amp6
gemini-cli6
replit6
github-copilot6
codex6
kimi-cli6