UNPKG

@bernierllc/content-type-blog-post

Version:

Blog post content type with rich TipTap editor, SEO metadata, database storage, and web publishing

534 lines (396 loc) 12.2 kB
# @bernierllc/content-type-blog-post Blog post content type with rich TipTap editor, SEO metadata, database storage, and web publishing capabilities for modern content management systems. ## Installation ```bash npm install @bernierllc/content-type-blog-post ``` ### Peer Dependencies This package requires PostgreSQL client: ```bash npm install pg ``` ## Usage ### Basic Setup ```typescript import { BlogPostContentType } from '@bernierllc/content-type-blog-post'; import { Pool } from 'pg'; // Initialize database client const pool = new Pool({ connectionString: process.env.DATABASE_URL }); // Create blog post content type const blogPost = new BlogPostContentType({ dbClient: pool, tableName: 'blog_posts', baseUrl: 'https://example.com' }); // Initialize database schema await blogPost.initializeDatabase(); ``` ### Creating a Blog Post ```typescript const result = await blogPost.create({ title: 'Getting Started with TypeScript', slug: 'getting-started-with-typescript', content: '<p>TypeScript is a powerful...</p>', excerpt: 'Learn the basics of TypeScript in this comprehensive guide.', seo: { metaTitle: 'Getting Started with TypeScript - Complete Guide', metaDescription: 'Learn TypeScript basics, advanced types, and best practices in this comprehensive guide.', keywords: ['typescript', 'javascript', 'programming'], ogImage: 'https://example.com/images/typescript-guide.jpg' }, author: { name: 'John Doe', email: 'john@example.com', avatar: 'https://example.com/avatars/john.jpg' }, tags: ['typescript', 'tutorial', 'beginners'], categories: ['Programming', 'Web Development'], status: 'draft' }); if (result.success) { console.log('Blog post created:', result.data); } else { console.error('Failed to create blog post:', result.error); } ``` ### Publishing a Blog Post ```typescript // Update status to published const publishResult = await blogPost.update('post-id-here', { status: 'published', publishedAt: new Date() }); // Get the publish URL const url = blogPost.getPublishUrl('getting-started-with-typescript'); // Returns: https://example.com/blog/getting-started-with-typescript ``` ### Retrieving Blog Posts ```typescript // Get a single blog post const post = await blogPost.read('post-id-here'); // List all published posts const publishedPosts = await blogPost.list({ status: 'published' }); // List posts by tag const taggedPosts = await blogPost.list({ tags: ['typescript'] }); // List posts by author const authorPosts = await blogPost.list({ author: { name: 'John Doe' } }); ``` ### Updating a Blog Post ```typescript const updateResult = await blogPost.update('post-id-here', { title: 'Updated Title', content: '<p>Updated content...</p>', seo: { metaTitle: 'Updated Meta Title', metaDescription: 'Updated meta description' } }); ``` ### Deleting a Blog Post ```typescript const deleteResult = await blogPost.delete('post-id-here'); if (deleteResult.success) { console.log('Blog post deleted successfully'); } ``` ## API Reference ### `BlogPostContentType` Main class for managing blog post content. #### Constructor ```typescript constructor(config?: { dbClient?: any; tableName?: string; baseUrl?: string; }) ``` **Parameters:** - `dbClient` - PostgreSQL client instance (from `pg` package) - `tableName` - Database table name (default: `'blog_posts'`) - `baseUrl` - Base URL for published posts (default: `''`) #### Methods ##### `initializeDatabase()` Initialize database schema with blog posts table. ```typescript async initializeDatabase(): Promise<BlogPostResult<void>> ``` ##### `create(metadata)` Create a new blog post. ```typescript async create(metadata: Partial<BlogPostMetadata>): Promise<BlogPostResult<BlogPostMetadata>> ``` ##### `read(id)` Retrieve a blog post by ID. ```typescript async read(id: string): Promise<BlogPostResult<BlogPostMetadata>> ``` ##### `update(id, updates)` Update an existing blog post. ```typescript async update(id: string, updates: Partial<BlogPostMetadata>): Promise<BlogPostResult<BlogPostMetadata>> ``` ##### `delete(id)` Delete a blog post by ID. ```typescript async delete(id: string): Promise<BlogPostResult<void>> ``` ##### `list(filters?)` List blog posts with optional filtering. ```typescript async list(filters?: { status?: BlogPostStatus; tags?: string[]; categories?: string[]; author?: { name?: string; email?: string }; }): Promise<BlogPostResult<BlogPostMetadata[]>> ``` ##### `getPublishUrl(slug)` Get the publish URL for a blog post. ```typescript getPublishUrl(slug: string): string ``` ##### `validate(metadata)` Validate blog post metadata against schema. ```typescript validate(metadata: unknown): BlogPostResult<BlogPostMetadata> ``` ### Types #### `BlogPostMetadata` ```typescript interface BlogPostMetadata { content: string; createdAt: Date; updatedAt: Date; title: string; slug: string; excerpt?: string; seo: BlogPostSEO; author: BlogPostAuthor; tags: string[]; categories: string[]; status: BlogPostStatus; publishedAt?: Date; scheduledFor?: Date; featuredImage?: string; readingTime?: number; wordCount: number; } ``` #### `BlogPostSEO` ```typescript interface BlogPostSEO { metaTitle: string; metaDescription: string; keywords: string[]; ogImage?: string; ogType: 'article'; canonicalUrl?: string; } ``` #### `BlogPostAuthor` ```typescript interface BlogPostAuthor { name: string; email?: string; avatar?: string; bio?: string; } ``` #### `BlogPostStatus` ```typescript type BlogPostStatus = 'draft' | 'published' | 'scheduled' | 'archived'; ``` ### Utility Functions #### `calculateWordCount(content)` Calculate word count from HTML content. ```typescript function calculateWordCount(content: string): number ``` #### `calculateReadingTime(wordCount, wordsPerMinute?)` Calculate reading time in minutes. ```typescript function calculateReadingTime(wordCount: number, wordsPerMinute?: number): number ``` #### `validateSEOCompleteness(seo)` Validate that SEO metadata is complete for publishing. ```typescript function validateSEOCompleteness(seo: BlogPostSEO): { isComplete: boolean; errors: string[]; } ``` #### `generateSlug(title)` Generate URL-safe slug from title. ```typescript function generateSlug(title: string): string ``` ## Configuration ### Database Schema The package automatically creates the following PostgreSQL schema: ```sql CREATE TABLE blog_posts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(200) NOT NULL, slug VARCHAR(200) NOT NULL UNIQUE, content TEXT NOT NULL, excerpt VARCHAR(300), -- SEO metadata meta_title VARCHAR(60), meta_description VARCHAR(160), keywords TEXT[], og_image TEXT, canonical_url TEXT, -- Author information author_name VARCHAR(100) NOT NULL, author_email VARCHAR(100), author_avatar TEXT, author_bio VARCHAR(500), -- Taxonomy tags TEXT[] DEFAULT '{}', categories TEXT[] DEFAULT '{}', -- Publishing workflow status VARCHAR(20) DEFAULT 'draft', published_at TIMESTAMPTZ, scheduled_for TIMESTAMPTZ, -- Additional metadata featured_image TEXT, reading_time INTEGER, word_count INTEGER DEFAULT 0, -- Timestamps created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_blog_posts_slug ON blog_posts(slug); CREATE INDEX idx_blog_posts_status ON blog_posts(status); CREATE INDEX idx_blog_posts_published_at ON blog_posts(published_at); CREATE INDEX idx_blog_posts_tags ON blog_posts USING GIN(tags); CREATE INDEX idx_blog_posts_categories ON blog_posts USING GIN(categories); ``` ### TipTap Editor Configuration The content type includes TipTap WYSIWYG editor with the following extensions: - **starter-kit** - Basic formatting (bold, italic, headings, lists, etc.) - **link** - Hyperlink support - **image** - Image embedding - **code-block-lowlight** - Syntax-highlighted code blocks - **table** - Table support with rows, cells, and headers ## Integration Status ### Logger Integration **Status**: Planned Logger integration will be added in a future release for: - Audit logging of blog post lifecycle events (create, update, publish, delete) - Database operation logging - Error tracking and debugging - Performance monitoring ### NeverHub Integration **Status**: Planned NeverHub integration will be added in a future release for: - Event publishing for blog post lifecycle (created, updated, published, deleted) - Service discovery for author lookup and media management - Real-time collaboration features - Webhook notifications for publishing events ### Graceful Degradation The package is designed to work standalone without any external integrations. Logger and NeverHub integrations will be optional enhancements that provide additional functionality when available. ## Examples ### Complete Blog Publishing Workflow ```typescript import { BlogPostContentType, generateSlug } from '@bernierllc/content-type-blog-post'; const blogPost = new BlogPostContentType({ dbClient, baseUrl: 'https://blog.example.com' }); // 1. Create draft const draft = await blogPost.create({ title: 'My First Blog Post', slug: generateSlug('My First Blog Post'), content: '<h1>Hello World</h1><p>This is my first post...</p>', author: { name: 'Jane Smith', email: 'jane@example.com' }, status: 'draft' }); // 2. Add SEO metadata await blogPost.update(draft.data.id, { seo: { metaTitle: 'My First Blog Post - Example Blog', metaDescription: 'An introduction to blogging on our new platform.', keywords: ['blogging', 'first post', 'introduction'] } }); // 3. Schedule for publishing await blogPost.update(draft.data.id, { status: 'scheduled', scheduledFor: new Date('2025-12-01T09:00:00Z') }); // 4. Publish immediately await blogPost.update(draft.data.id, { status: 'published', publishedAt: new Date() }); // 5. Get publish URL const url = blogPost.getPublishUrl(draft.data.slug); console.log(`Published at: ${url}`); ``` ### SEO Best Practices ```typescript import { validateSEOCompleteness } from '@bernierllc/content-type-blog-post'; // Validate SEO before publishing const seoValidation = validateSEOCompleteness({ metaTitle: 'Complete Guide to TypeScript', metaDescription: 'Learn TypeScript from basics to advanced concepts.', keywords: ['typescript', 'javascript'], ogImage: 'https://example.com/og-image.jpg' }); if (!seoValidation.isComplete) { console.error('SEO incomplete:', seoValidation.errors); // ['Meta title should be between 30-60 characters', ...] } ``` ### Reading Time Calculation ```typescript import { calculateWordCount, calculateReadingTime } from '@bernierllc/content-type-blog-post'; const content = '<p>Your blog post content here...</p>'; const wordCount = calculateWordCount(content); const readingTime = calculateReadingTime(wordCount); // default: 200 words/minute await blogPost.create({ title: 'Article Title', content, wordCount, readingTime, // ... other fields }); ``` ## Error Handling All methods return a `BlogPostResult` type for consistent error handling: ```typescript interface BlogPostResult<T> { success: boolean; data?: T; error?: string; } ``` Example usage: ```typescript const result = await blogPost.create({ /* metadata */ }); if (result.success) { console.log('Created:', result.data); } else { console.error('Failed:', result.error); } ``` ## Testing The package includes comprehensive test coverage (91%+ coverage): ```bash # Run tests npm test # Run tests with coverage npm run test:coverage # Run tests in watch mode npm run test:watch ``` ## License 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. ## See Also - [@bernierllc/content-type-registry](../content-type-registry) - Registry for managing multiple content types - [@bernierllc/content-type-text](../content-type-text) - Base text content type