| name | content-platforms |
| description | CMS, blogging platforms, and content management patterns |
| domain | domain-applications |
| version | 1.0.0 |
| tags | ["cms","blog","content","markdown","rich-text","media"] |
| triggers | {"keywords":{"primary":["cms","blog","content management","headless cms","rich text editor"],"secondary":["markdown","media library","versioning","publishing","seo","tiptap"]},"context_boost":["content","article","post","page","publish"],"context_penalty":["e-commerce","payment","game"],"priority":"medium"} |
Content Platforms
Overview
Building content management systems, blogging platforms, and rich media applications.
Content Models
Headless CMS Schema
interface ContentType {
id: string;
name: string;
slug: string;
fields: Field[];
settings: ContentTypeSettings;
}
interface Field {
id: string;
name: string;
type: FieldType;
required: boolean;
localized: boolean;
validation?: FieldValidation;
}
type FieldType =
| 'text'
| 'richText'
| 'number'
| 'boolean'
| 'date'
| 'media'
| 'reference'
| 'array'
| 'json';
const blogPostType: ContentType = {
id: 'blogPost',
name: 'Blog Post',
slug: 'blog-posts',
fields: [
{ id: 'title', name: 'Title', type: 'text', required: true, localized: true },
{ id: 'slug', name: 'Slug', type: 'text', required: true, localized: false },
{ id: 'content', name: 'Content', type: 'richText', required: true, localized: true },
{ id: 'excerpt', name: 'Excerpt', type: 'text', required: false, localized: true },
{ id: 'featuredImage', name: 'Featured Image', type: 'media', required: false, localized: false },
{ id: 'author', name: 'Author', type: 'reference', required: true, localized: false },
{ id: 'tags', name: 'Tags', type: 'array', required: false, localized: false },
{ id: 'publishedAt', name: 'Published At', type: 'date', required: false, localized: false },
{ id: 'seo', name: 'SEO', type: 'json', required: false, localized: true },
],
settings: {
previewable: true,
versionable: true,
publishable: true,
},
};
Rich Text Editor
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Image from '@tiptap/extension-image';
import Link from '@tiptap/extension-link';
import Placeholder from '@tiptap/extension-placeholder';
function RichTextEditor({
content,
onChange,
}: {
content: string;
onChange: (content: string) => void;
}) {
const editor = useEditor({
extensions: [
StarterKit,
Image.configure({ inline: true }),
Link.configure({ openOnClick: false }),
Placeholder.configure({ placeholder: 'Start writing...' }),
],
content,
onUpdate: ({ editor }) => {
onChange(editor.getHTML());
},
});
if (!editor) return null;
(
);
}
() {
(
);
}
Media Management
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import sharp from 'sharp';
const s3 = new S3Client({ region: process.env.AWS_REGION });
interface MediaAsset {
id: string;
filename: string;
mimeType: string;
size: number;
url: string;
thumbnailUrl?: string;
width?: number;
height?: number;
alt?: string;
}
async function uploadMedia(file: Express.Multer.File): Promise<MediaAsset> {
const id = crypto.randomUUID();
const extension = path.extname(file.originalname);
key = ;
processedBuffer = file.;
: | ;
: | ;
(file..()) {
image = (file.);
metadata = image.();
width = metadata.;
height = metadata.;
(width && width > ) {
processedBuffer = image
.(, , { : })
.();
}
thumbnail = image
.(, , { : })
.({ : })
.();
s3.( ({
: process..,
: ,
: thumbnail,
: ,
}));
}
s3.( ({
: process..,
: key,
: processedBuffer,
: file.,
}));
prisma..({
: {
id,
: file.,
: file.,
: processedBuffer.,
: ,
: file..()
?
: ,
width,
height,
},
});
}
() {
cacheKey = ;
cached = redis.(cacheKey);
(cached) {
.(cached, );
}
original = s3.( ({
: process..,
: key,
}));
image = ( original.?.());
(options. || options.) {
image = image.(options., options., {
: ,
: ,
});
}
(options.) {
image = image.(options., { : });
}
buffer = image.();
redis.(cacheKey, , buffer.());
buffer;
}
Content Versioning
interface ContentVersion {
id: string;
contentId: string;
version: number;
data: Record<string, any>;
createdBy: string;
createdAt: Date;
changeDescription?: string;
}
async function createVersion(
contentId: string,
data: Record<string, any>,
userId: string,
description?: string
) {
const current = await prisma.content.findUnique({
where: { id: contentId },
});
await prisma.contentVersion.create({
data: {
contentId,
version: current.version,
data: current.data,
createdBy: userId,
changeDescription: description,
},
});
return prisma..({
: { : contentId },
: {
data,
: { : },
},
});
}
() {
prisma..({
: { contentId },
: { : },
: {
: { : { : , : } },
},
});
}
() {
version = prisma..({
: { contentId, : versionNumber },
});
(!version) {
();
}
(contentId, version., userId, );
}
() {
diff = ();
(oldVersion., newVersion.);
}
Publishing Workflow
enum ContentStatus {
DRAFT = 'draft',
IN_REVIEW = 'in_review',
APPROVED = 'approved',
PUBLISHED = 'published',
ARCHIVED = 'archived',
}
const workflowTransitions: Record<ContentStatus, ContentStatus[]> = {
[ContentStatus.DRAFT]: [ContentStatus.IN_REVIEW],
[ContentStatus.IN_REVIEW]: [ContentStatus.DRAFT, ContentStatus.APPROVED],
[ContentStatus.APPROVED]: [ContentStatus.IN_REVIEW, ContentStatus.PUBLISHED],
[ContentStatus.PUBLISHED]: [ContentStatus.ARCHIVED],
[ContentStatus.ARCHIVED]: [ContentStatus.DRAFT],
};
async function transitionContent(
contentId: string,
newStatus: ContentStatus,
: ,
?:
) {
content = prisma..({ : { : contentId } });
allowedTransitions = workflowTransitions[content.];
(!allowedTransitions.(newStatus)) {
();
}
prisma..({
: {
contentId,
: content.,
: newStatus,
userId,
comment,
},
});
prisma..({
: { : contentId },
: {
: newStatus,
...(newStatus === . && { : () }),
},
});
}
() {
prisma..({
: { : contentId },
: {
: publishAt,
: .,
},
});
queue.(, { contentId }, {
: publishAt.() - .(),
});
}
SEO & Metadata
interface SEOMetadata {
title: string;
description: string;
keywords?: string[];
ogImage?: string;
ogType?: string;
canonical?: string;
noIndex?: boolean;
}
function generateSEOTags(meta: SEOMetadata, url: string) {
return {
title: meta.title,
meta: [
{ name: 'description', content: meta.description },
meta.keywords && { name: 'keywords', content: meta.keywords.join(', ') },
meta.noIndex && { name: 'robots', content: 'noindex, nofollow' },
{ property: 'og:title', content: meta.title },
{ property: 'og:description', content: meta.description },
{ property: 'og:type', : meta. || },
{ : , : url },
meta. && { : , : meta. },
{ : , : },
{ : , : meta. },
{ : , : meta. },
meta. && { : , : meta. },
].(),
: [
meta. && { : , : meta. },
].(),
};
}
Related Skills
- [[frontend]] - Content rendering
- [[database]] - Content storage
- [[cloud-platforms]] - Media hosting