@bernierllc/content-management-suite
v0.4.2
Published
Comprehensive content management suite with editorial workflows, content types, and admin UI
Readme
/* Copyright (c) 2025 Bernier LLC
This file is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC. */
Content Management Suite
A comprehensive content management suite with editorial workflows, content types, and admin UI built with TypeScript and Tamagui.
Features
- 🎯 Editorial Workflows: Configurable content workflows with stages, transitions, and permissions
- 📝 Content Types: Pluggable content type system supporting text, images, audio, and video
- ⚡ Auto-save: Intelligent auto-save with backoff retry and debounce
- 🗑️ Soft Delete: Configurable soft delete with user visibility options
- 🎨 Modern UI: Tamagui-based components with responsive design
- 🔧 Configuration: File + database override + environment variable configuration
- 🔌 Plugin System: Extensible plugin architecture for custom functionality
- 🛡️ Security: JWT authentication, role-based permissions, and rate limiting
- 📊 Analytics: Built-in analytics and metrics collection
- 🌐 API: RESTful API with comprehensive endpoints
- 📱 Admin UI: NeverAdmin integration for workflow configuration
- 🔍 Search: Full-text search across all content types
- 📈 Performance: Caching, compression, and optimization
Installation
npm install @bernierllc/content-management-suiteQuick Start
import { createContentManagementSuite } from '@bernierllc/content-management-suite';
const suite = createContentManagementSuite({
config: {
server: {
port: 3000,
host: 'localhost'
}
}
});
await suite.start();
console.log('Content Management Suite started on http://localhost:3000');Configuration
The suite supports comprehensive configuration through multiple sources:
1. File Configuration
Create a content-management.config.json file:
{
"server": {
"port": 3000,
"host": "localhost",
"cors": {
"origin": "*",
"credentials": true
}
},
"database": {
"type": "sqlite",
"name": "content_management"
},
"content": {
"defaultWorkflow": {
"id": "standard",
"name": "Standard Workflow",
"stages": [
{
"id": "write",
"name": "Write",
"order": 1,
"isPublishStage": false,
"allowsScheduling": false,
"permissions": ["content.edit"]
},
{
"id": "publish",
"name": "Publish",
"order": 2,
"isPublishStage": true,
"allowsScheduling": true,
"permissions": ["content.publish"]
}
]
}
}
}2. Environment Variables
# Server
CONTENT_MANAGEMENT_PORT=3000
CONTENT_MANAGEMENT_HOST=localhost
# Database
CONTENT_MANAGEMENT_DATABASE_TYPE=postgresql
CONTENT_MANAGEMENT_DATABASE_URL=postgresql://user:password@localhost:5432/content_management
# Security
CONTENT_MANAGEMENT_JWT_SECRET=your-secret-key
CONTENT_MANAGEMENT_JWT_EXPIRES_IN=24h
# Integrations
CONTENT_MANAGEMENT_NEVERADMIN_ENABLED=true
CONTENT_MANAGEMENT_NEVERADMIN_URL=https://admin.myapp.com
CONTENT_MANAGEMENT_NEVERADMIN_API_KEY=your-api-key3. Database Override
Configuration can be overridden through the database for runtime changes:
await suite.updateConfig({
ui: {
theme: {
mode: 'dark'
}
}
});Content Types
The suite comes with four built-in content types:
Text Content
import { TextContentType } from '@bernierllc/content-management-suite';
// Register text content type
suite.registerContentType(TextContentType);
// Create text content
const textContent = {
title: 'My Blog Post',
slug: 'my-blog-post',
body: '<p>Hello world!</p>',
excerpt: 'A brief description',
tags: ['blog', 'example'],
categories: ['general'],
author: {
id: 'user-1',
name: 'John Doe',
email: '[email protected]'
}
};Image Content
import { ImageContentType } from '@bernierllc/content-management-suite';
// Register image content type
suite.registerContentType(ImageContentType);
// Create image content
const imageContent = {
title: 'Beautiful Landscape',
slug: 'beautiful-landscape',
description: 'A stunning mountain landscape',
altText: 'Mountain landscape with sunset',
imageUrl: 'https://example.com/images/landscape.jpg',
tags: ['landscape', 'nature'],
categories: ['photography'],
author: {
id: 'user-1',
name: 'John Doe',
email: '[email protected]'
}
};Audio Content
import { AudioContentType } from '@bernierllc/content-management-suite';
// Register audio content type
suite.registerContentType(AudioContentType);
// Create audio content
const audioContent = {
title: 'My Podcast Episode',
slug: 'my-podcast-episode',
description: 'A great podcast episode',
audioUrl: 'https://example.com/audio/episode.mp3',
duration: 1800, // 30 minutes
metadata: {
artist: 'John Doe',
album: 'Tech Talk Podcast',
year: 2023
},
tags: ['podcast', 'technology'],
categories: ['podcast'],
author: {
id: 'user-1',
name: 'John Doe',
email: '[email protected]'
}
};Video Content
import { VideoContentType } from '@bernierllc/content-management-suite';
// Register video content type
suite.registerContentType(VideoContentType);
// Create video content
const videoContent = {
title: 'My Video Tutorial',
slug: 'my-video-tutorial',
description: 'A comprehensive tutorial',
videoUrl: 'https://example.com/videos/tutorial.mp4',
duration: 1800, // 30 minutes
dimensions: {
width: 1920,
height: 1080
},
metadata: {
director: 'John Doe',
genre: 'Educational',
year: 2023
},
tags: ['tutorial', 'video'],
categories: ['tutorial'],
author: {
id: 'user-1',
name: 'John Doe',
email: '[email protected]'
}
};Editorial Workflows
Configure custom editorial workflows:
const customWorkflow = {
id: 'editorial',
name: 'Editorial Workflow',
description: 'Multi-stage editorial workflow',
stages: [
{
id: 'write',
name: 'Write',
order: 1,
isPublishStage: false,
allowsScheduling: false,
permissions: ['content.edit']
},
{
id: 'edit',
name: 'Edit',
order: 2,
isPublishStage: false,
allowsScheduling: false,
permissions: ['content.edit']
},
{
id: 'review',
name: 'Review',
order: 3,
isPublishStage: false,
allowsScheduling: false,
permissions: ['content.review']
},
{
id: 'publish',
name: 'Publish',
order: 4,
isPublishStage: true,
allowsScheduling: true,
permissions: ['content.publish']
}
],
transitions: [
{
id: 'write-to-edit',
from: 'write',
to: 'edit',
permissions: ['content.edit']
},
{
id: 'edit-to-review',
from: 'edit',
to: 'review',
permissions: ['content.review']
},
{
id: 'review-to-publish',
from: 'review',
to: 'publish',
permissions: ['content.publish']
}
]
};
await suite.workflowService.createWorkflow(customWorkflow);API Endpoints
Content Management
GET /api/content- List contentGET /api/content/:id- Get content by IDPOST /api/content- Create contentPUT /api/content/:id- Update contentDELETE /api/content/:id- Delete contentPOST /api/content/:id/publish- Publish contentPOST /api/content/:id/schedule- Schedule contentPOST /api/content/:id/unpublish- Unpublish content
Workflow Management
GET /api/workflows- List workflowsGET /api/workflows/:id- Get workflow by IDPOST /api/workflows- Create workflowPUT /api/workflows/:id- Update workflowDELETE /api/workflows/:id- Delete workflow
Content Type Management
GET /api/content-types- List content typesGET /api/content-types/:id- Get content type by IDPOST /api/content-types- Create content typePUT /api/content-types/:id- Update content typeDELETE /api/content-types/:id- Delete content type
Configuration Management
GET /api/config- Get configurationPUT /api/config- Update configuration
User Management
GET /api/users- List usersGET /api/users/:id- Get user by IDPOST /api/users- Create userPUT /api/users/:id- Update userDELETE /api/users/:id- Delete user
UI Components
Content Editor
import { ContentEditor } from '@bernierllc/content-management-suite';
<ContentEditor
content={content}
onChange={(content) => console.log('Content changed:', content)}
onSave={(content) => console.log('Save:', content)}
onPublish={(content) => console.log('Publish:', content)}
onSchedule={(content, date) => console.log('Schedule:', content, date)}
showToolbar={true}
showStatusBar={true}
showWordCount={true}
showCharacterCount={true}
placeholder="Start writing your content..."
maxCharacters={10000}
targetWordCount={1000}
/>Workflow Components
import {
WorkflowStepper,
StageActionButtons,
WorkflowTimeline,
WorkflowAdminConfig
} from '@bernierllc/content-management-suite';
// Workflow stepper
<WorkflowStepper
workflow={workflow}
currentStage={currentStage}
onStageChange={(stage) => console.log('Stage changed:', stage)}
/>
// Stage action buttons
<StageActionButtons
stage={currentStage}
content={content}
onAction={(action) => console.log('Action:', action)}
/>
// Workflow timeline
<WorkflowTimeline
workflow={workflow}
content={content}
onStageClick={(stage) => console.log('Stage clicked:', stage)}
/>
// Workflow admin configuration
<WorkflowAdminConfig
workflow={workflow}
onChange={(workflow) => console.log('Workflow changed:', workflow)}
onSave={(workflow) => console.log('Workflow saved:', workflow)}
/>Content List
import { ContentList } from '@bernierllc/content-management-suite';
<ContentList
content={contentList}
onContentSelect={(content) => console.log('Content selected:', content)}
onContentEdit={(content) => console.log('Edit content:', content)}
onContentDelete={(content) => console.log('Delete content:', content)}
onContentPublish={(content) => console.log('Publish content:', content)}
view="table" // table, list, grid, kanban
showSearch={true}
showFilters={true}
showSorting={true}
showPagination={true}
pageSize={20}
/>Plugin System
Create custom plugins:
const analyticsPlugin = {
name: 'analytics',
version: '1.0.0',
description: 'Analytics plugin for content management',
dependencies: ['@bernierllc/analytics'],
install: async (suite) => {
console.log('Installing analytics plugin');
// Initialize analytics
},
uninstall: async (suite) => {
console.log('Uninstalling analytics plugin');
// Cleanup analytics
},
enable: async (suite) => {
console.log('Enabling analytics plugin');
// Enable analytics tracking
},
disable: async (suite) => {
console.log('Disabling analytics plugin');
// Disable analytics tracking
}
};
suite.registerPlugin(analyticsPlugin);Middleware System
Add custom middleware:
suite.addMiddleware({
name: 'request-logger',
handler: (req, res, next) => {
console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`);
next();
},
order: 1
});Hook System
Add custom hooks:
suite.addHook({
name: 'content:created',
handler: async (content) => {
console.log('New content created:', content.id);
// Send notification, update analytics, etc.
},
priority: 1
});Security
JWT Authentication
const suite = createContentManagementSuite({
config: {
security: {
jwt: {
secret: 'your-super-secret-jwt-key',
expiresIn: '24h',
issuer: 'content-management-suite'
}
}
}
});Role-Based Permissions
const suite = createContentManagementSuite({
config: {
security: {
permissions: {
enabled: true,
defaultRole: 'user',
roles: [
{
name: 'admin',
permissions: ['*'],
description: 'Full administrative access'
},
{
name: 'editor',
permissions: ['content.edit', 'content.publish', 'content.schedule'],
description: 'Content editing and publishing'
},
{
name: 'author',
permissions: ['content.edit'],
description: 'Content creation and editing'
},
{
name: 'user',
permissions: ['content.view'],
description: 'Content viewing only'
}
]
}
}
}
});Performance
Caching
const suite = createContentManagementSuite({
config: {
performance: {
cache: {
enabled: true,
ttl: 300, // 5 minutes
maxSize: 1000
}
}
}
});Compression
const suite = createContentManagementSuite({
config: {
performance: {
compression: {
enabled: true,
level: 6
}
}
}
});Rate Limiting
const suite = createContentManagementSuite({
config: {
performance: {
rateLimit: {
enabled: true,
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100
}
}
}
});Logging
const suite = createContentManagementSuite({
config: {
logging: {
level: 'info',
format: 'json',
file: {
enabled: true,
path: './logs',
maxSize: '10MB',
maxFiles: 5
},
console: {
enabled: true,
colorize: true
}
}
}
});Integrations
NeverAdmin Integration
const suite = createContentManagementSuite({
config: {
integrations: {
neverAdmin: {
enabled: true,
url: 'https://admin.myapp.com',
apiKey: 'your-api-key',
syncInterval: 300000 // 5 minutes
}
}
}
});NeverHub Integration
const suite = createContentManagementSuite({
config: {
integrations: {
neverHub: {
enabled: true,
url: 'https://hub.myapp.com',
apiKey: 'your-api-key',
packageDiscovery: true
}
}
}
});Error Handling
suite.addHook({
name: 'error',
handler: async (error) => {
console.error('Suite error:', error);
// Send to error tracking service
}
});Graceful Shutdown
process.on('SIGINT', async () => {
console.log('Received SIGINT, shutting down gracefully...');
try {
await suite.stop();
console.log('Suite stopped successfully');
process.exit(0);
} catch (error) {
console.error('Error during shutdown:', error);
process.exit(1);
}
});Development vs Production
const isDevelopment = process.env.NODE_ENV === 'development';
const suite = createContentManagementSuite({
config: {
server: {
port: isDevelopment ? 3000 : 80,
host: isDevelopment ? 'localhost' : '0.0.0.0'
},
logging: {
level: isDevelopment ? 'debug' : 'info',
format: isDevelopment ? 'text' : 'json'
},
security: {
jwt: {
secret: isDevelopment ? 'dev-secret' : process.env.JWT_SECRET
}
}
}
});Testing
# Run unit tests
npm test
# Run Playwright tests
npm run test:playwright
# Run all tests
npm run test:allExamples
See the examples.ts file for comprehensive usage examples:
- Basic setup
- Advanced configuration
- Custom content types
- Plugin system
- Error handling
- API usage
- Graceful shutdown
- Environment-specific setup
License
Copyright (c) 2025 Bernier LLC. Licensed under limited-use license.
