quadrats-module
SKILL.md
Quadrats CMS NestJS Module (Quadrats 富文本 CMS 模組)
Overview
@rytass/quadrats-nestjs 提供 Quadrats CMS 的 NestJS 整合模組,支援富文本內容管理、文章版本控制、多語言內容和圖片上傳。
Quick Start
安裝
npm install @rytass/quadrats-nestjs @quadrats/core
基本設定
import { QuadratsModule } from '@rytass/quadrats-nestjs';
@Module({
imports: [
QuadratsModule.forRoot({
accessKey: 'your-access-key',
secret: 'your-secret',
// host: 'https://api.quadrats.io', // 預設值
}),
],
})
export class AppModule {}
非同步設定
import { QuadratsModule } from '@rytass/quadrats-nestjs';
import { ConfigService } from '@nestjs/config';
@Module({
imports: [
QuadratsModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
accessKey: config.get('QUADRATS_ACCESS_KEY'),
secret: config.get('QUADRATS_SECRET'),
}),
}),
],
})
export class AppModule {}
Services
QuadratsArticleService
文章 CRUD 與版本管理:
import { QuadratsArticleService, Language } from '@rytass/quadrats-nestjs';
import { Paragraph } from '@quadrats/core';
@Injectable()
export class ContentService {
constructor(private readonly articleService: QuadratsArticleService) {}
// 建立文章
async createArticle() {
return this.articleService.create({
title: '文章標題',
categoryIds: ['category-id'],
tags: ['tag1', 'tag2'],
contents: [
Paragraph.create({ children: [{ text: '文章內容' }] }),
],
language: Language.ZH_TW,
releasedAt: new Date(),
});
}
// 多語言文章
async createMultiLangArticle() {
return this.articleService.create({
title: '多語言文章',
categoryIds: ['category-id'],
tags: ['multilang'],
languageContents: [
{
language: Language.ZH_TW,
elements: [Paragraph.create({ children: [{ text: '繁體中文內容' }] })],
},
{
language: Language.EN,
elements: [Paragraph.create({ children: [{ text: 'English content' }] })],
},
],
});
}
// 取得文章
async getArticle(id: string, versionId?: string) {
return this.articleService.get(id, versionId);
}
// 取得文章 ID 列表
async getArticleIds() {
return this.articleService.getIds({
limit: 10,
offset: 0,
categoryIds: ['category-id'],
tags: ['featured'],
});
}
// 新增版本
async addVersion(id: string) {
return this.articleService.addVersion({
id,
title: '更新標題',
categoryIds: ['category-id'],
tags: ['updated'],
contents: [
Paragraph.create({ children: [{ text: '新版本內容' }] }),
],
});
}
// 刪除文章
async removeArticle(id: string) {
return this.articleService.remove(id);
}
}
QuadratsArticleCategoryService
分類管理:
import { QuadratsArticleCategoryService } from '@rytass/quadrats-nestjs';
@Injectable()
export class CategoryManagementService {
constructor(private readonly categoryService: QuadratsArticleCategoryService) {}
// 取得所有分類
async getAllCategories() {
return this.categoryService.getAll();
}
// 取得單一分類
async getCategory(id: string) {
return this.categoryService.get(id);
}
// 建立分類
async createCategory(name: string, parentId?: string) {
return this.categoryService.create(name, parentId);
}
// 重新命名分類
async renameCategory(id: string, newName: string) {
return this.categoryService.rename(id, newName);
}
}
QuadratsArticleTagService
標籤查詢:
import { QuadratsArticleTagService } from '@rytass/quadrats-nestjs';
@Injectable()
export class TagService {
constructor(private readonly tagService: QuadratsArticleTagService) {}
// 取得所有標籤
async getAllTags() {
return this.tagService.getAll({
limit: 50,
offset: 0,
searchTerm: 'tech', // 可選搜尋詞
});
}
}
QuadratsArticleImageService
圖片上傳:
import { QuadratsArticleImageService, ImageDetailURL } from '@rytass/quadrats-nestjs';
import { Readable } from 'stream';
@Injectable()
export class ImageUploadService {
constructor(private readonly imageService: QuadratsArticleImageService) {}
// 上傳圖片 (urlMode = false 或省略,回傳詳細 URL 資訊)
async uploadImageWithDetails(buffer: Buffer): Promise<ImageDetailURL> {
return this.imageService.uploadImage(buffer, false);
// 回傳: ImageDetailURL { id, preload, thumbnails, public, full }
}
// 上傳圖片 (不帶 urlMode 時預設為 false,回傳詳細 URL 資訊)
async uploadImageDefault(buffer: Buffer): Promise<ImageDetailURL> {
return this.imageService.uploadImage(buffer);
// 回傳: ImageDetailURL { id, preload, thumbnails, public, full }
}
// 上傳圖片 (urlMode = true,回傳簡單 URL 字串)
async uploadImageUrl(buffer: Buffer): Promise<string> {
return this.imageService.uploadImage(buffer, true);
// 回傳: string (URL)
}
// 支援 Stream 上傳
async uploadFromStream(stream: Readable): Promise<ImageDetailURL> {
return this.imageService.uploadImage(stream);
}
}
### ImageDetailURL
```typescript
interface ImageDetailURL {
id: string; // 檔案 ID
preload: string; // 模糊預載 URL
thumbnails: string; // 小尺寸 URL
public: string; // 標準尺寸 URL
full: string; // 大尺寸 URL
}
Language Enum
支援的語言(含別名):
import { Language } from '@rytass/quadrats-nestjs';
enum Language {
DEFAULT = 'DEFAULT',
ZH = 'TRADITIONAL_CHINESE', // 繁體中文(別名)
ZH_TW = 'TRADITIONAL_CHINESE', // 繁體中文
ZH_CN = 'SIMPLIFIED_CHINESE', // 簡體中文
EN_US = 'ENGLISH_UNITED_STATES', // 美式英文(別名)
EN = 'ENGLISH_UNITED_STATES', // 美式英文
EN_GB = 'ENGLISH_UNITED_KINGDOM', // 英式英文
JA_JP = 'JAPANESE', // 日文
JP = 'JAPANESE', // 日文(別名)
KO_KR = 'KOREAN', // 韓文
KR = 'KOREAN', // 韓文(別名)
ES = 'SPANISH_SPAIN', // 西班牙文
PT = 'PORTUGUESE_PORTUGAL', // 葡萄牙文
DE = 'GERMANY_GERMAN', // 德文
IT = 'ITALIAN_ITALY', // 義大利文
FR = 'FRENCH_FRANCE', // 法文
}
提示:
ZH/ZH_TW、EN/EN_US、JP/JA_JP、KR/KO_KR為相同值的別名,可依習慣選用。
Data Types
QuadratsArticle
interface QuadratsArticle {
id: string;
versionId: string;
title: string;
categories: QuadratsArticleCategory[];
tags: string[];
releasedAt: Date | null;
contents: QuadratsArticleContentItem[];
}
QuadratsArticleCategory
interface QuadratsArticleCategory {
id: string;
name: string;
}
QuadratsArticleContentItem
import type { QuadratsElement } from '@quadrats/core';
interface QuadratsArticleContentItem {
language: string;
elements: QuadratsElement[];
}
Complete Example
import { Module, Injectable } from '@nestjs/common';
import {
QuadratsModule,
QuadratsArticleService,
QuadratsArticleCategoryService,
QuadratsArticleImageService,
Language,
} from '@rytass/quadrats-nestjs';
import { Paragraph, createEditor } from '@quadrats/core';
// Module 設定
@Module({
imports: [
QuadratsModule.forRoot({
accessKey: process.env.QUADRATS_ACCESS_KEY!,
secret: process.env.QUADRATS_SECRET!,
}),
],
providers: [BlogService],
})
export class BlogModule {}
// 部落格服務
@Injectable()
export class BlogService {
constructor(
private readonly articleService: QuadratsArticleService,
private readonly categoryService: QuadratsArticleCategoryService,
private readonly imageService: QuadratsArticleImageService,
) {}
// 發佈文章(含圖片)
async publishPost(data: {
title: string;
content: string;
categoryId: string;
tags: string[];
coverImage?: Buffer;
}) {
// 上傳封面圖(不帶 urlMode 或 urlMode=false 時回傳 ImageDetailURL)
let coverUrl: string | undefined;
if (data.coverImage) {
const imageResult = await this.imageService.uploadImage(data.coverImage);
coverUrl = imageResult.public;
}
// 建立文章內容
const elements = [
Paragraph.create({ children: [{ text: data.content }] }),
];
// 建立文章
return this.articleService.create({
title: data.title,
categoryIds: [data.categoryId],
tags: data.tags,
contents: elements,
language: Language.ZH_TW,
releasedAt: new Date(),
});
}
// 取得文章列表
async getPosts(categoryId?: string, page = 1, pageSize = 10) {
const ids = await this.articleService.getIds({
categoryIds: categoryId ? [categoryId] : undefined,
limit: pageSize,
offset: (page - 1) * pageSize,
});
return Promise.all(ids.map(id => this.articleService.get(id)));
}
// 更新文章(建立新版本)
async updatePost(id: string, updates: { title: string; content: string }) {
return this.articleService.addVersion({
id,
title: updates.title,
categoryIds: [],
tags: [],
contents: [
Paragraph.create({ children: [{ text: updates.content }] }),
],
});
}
// 分類管理
async setupCategories() {
const tech = await this.categoryService.create('技術');
const frontend = await this.categoryService.create('前端', tech.id);
const backend = await this.categoryService.create('後端', tech.id);
return { tech, frontend, backend };
}
}
Configuration Options
interface QuadratsModuleOptions {
accessKey: string; // Quadrats API Access Key
secret: string; // Quadrats API Secret
host?: string; // API Host (預設: https://api.quadrats.io)
}
interface QuadratsModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
name?: string;
useFactory: (...args: any[]) => Promise<QuadratsModuleOptions> | QuadratsModuleOptions;
inject?: (InjectionToken | OptionalFactoryDependency)[];
}
Dependencies
Required:
@quadrats/core^1.1.5 (富文本元素定義)
Peer Dependencies:
@nestjs/common^9.4.2
Troubleshooting
文章建立失敗
確保 contents 或 languageContents 其中之一有設定:
// 正確: 使用 contents
await articleService.create({
title: '標題',
categoryIds: [],
tags: [],
contents: [Paragraph.create({ children: [{ text: '內容' }] })],
});
// 正確: 使用 languageContents
await articleService.create({
title: '標題',
categoryIds: [],
tags: [],
languageContents: [
{ language: Language.ZH_TW, elements: [...] },
],
});
// 錯誤: 兩者都沒設定
await articleService.create({
title: '標題',
categoryIds: [],
tags: [],
// 會拋出錯誤: `contents` or `languageContents` should be set
});
圖片上傳失敗
確認圖片格式和大小符合要求,並使用正確的 Buffer 或 Stream:
import * as fs from 'fs';
// 從檔案讀取
const buffer = fs.readFileSync('image.jpg');
await imageService.uploadImage(buffer);
// 從 Stream 讀取
const stream = fs.createReadStream('image.jpg');
await imageService.uploadImage(stream);
Weekly Installs
7
Repository
rytass/utilsGitHub Stars
6
First Seen
Feb 5, 2026
Security Audits
Installed on
codex7
amp6
github-copilot6
replit6
kimi-cli6
gemini-cli6