quadrats-module
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);
More from rytass/utils
wms-module
|
24logistics-development
|
13logistics-adapters
|
12invoice-adapters
Taiwan e-invoice integration (台灣電子發票整合). Use when working with ECPay (綠界), EZPay (藍新), BankPro (金財通), or Amego (光貿) invoice services. Covers issuing invoices (開立發票), voiding (作廢), allowances (折讓), and querying invoice data.
8payment-adapters
Taiwan payment integration (台灣支付整合). Use when working with ECPay (綠界), NewebPay (藍新), HwaNan Bank (華南銀行), CTBC (中信), iCash Pay, or Happy Card payment services. Covers credit card (信用卡), virtual account (虛擬帳號), ATM, CVS payment (超商代碼/條碼), card binding (卡片綁定), installments (分期付款), recurring payments (訂閱付款), and NestJS integration.
7member-module
|
7