Skip to content

Architecture

The core-content package provides headless CMS functionality for the Host UK platform. It handles content management, AI-powered generation, revision history, webhooks for external CMS integration, and search capabilities.

Package Overview

Namespace: Core\Mod\Content\Entry Point: Boot.php (Laravel Service Provider) Dependencies:

  • core-php (Foundation framework, events)
  • core-tenant (Workspaces, users, entitlements)
  • Optional: core-agentic (AI services for content generation)
  • Optional: core-mcp (MCP tool handlers)

Directory Structure

core-content/
├── Boot.php              # Service provider with event listeners
├── config.php            # Package configuration
├── Models/               # Eloquent models (10 models)
├── Services/             # Business logic services
├── Controllers/          # API and web controllers
│   └── Api/              # REST API controllers
├── Jobs/                 # Queue jobs
├── Mcp/                  # MCP tool handlers
│   └── Handlers/         # Individual MCP tools
├── Concerns/             # Traits
├── Console/              # Artisan commands
│   └── Commands/         # Command implementations
├── Enums/                # PHP enums
├── Migrations/           # Database migrations
├── Observers/            # Model observers
├── routes/               # Route definitions
├── View/                 # Livewire components and Blade views
│   ├── Modal/            # Livewire components
│   └── Blade/            # Blade templates
├── tests/                # Test suite
└── docs/                 # Documentation

Core Concepts

Content Items

The primary content model. Supports multiple content types and sources:

php
// Content types (where content originates)
enum ContentType: string {
    case NATIVE = 'native';       // Created in Host Hub editor
    case HOSTUK = 'hostuk';       // Alias for native (backwards compat)
    case SATELLITE = 'satellite'; // Per-service content
    case WORDPRESS = 'wordpress'; // Legacy synced content
}

Content items belong to workspaces and have:

  • Title, slug, excerpt, content (HTML/Markdown/JSON)
  • Status (draft, publish, future, private, pending)
  • Author and last editor tracking
  • Revision history
  • Taxonomy (categories, tags)
  • SEO metadata
  • Preview tokens for sharing unpublished content
  • CDN cache invalidation tracking

Content Briefs

Briefs drive AI-powered content generation. They define what content to create:

php
// Brief content types (what to generate)
enum BriefContentType: string {
    case HELP_ARTICLE = 'help_article';   // Documentation
    case BLOG_POST = 'blog_post';         // Blog articles
    case LANDING_PAGE = 'landing_page';   // Marketing pages
    case SOCIAL_POST = 'social_post';     // Social media
}

Brief workflow: pending -> queued -> generating -> review -> published

Revisions

Every content change creates an immutable revision snapshot. Revisions support:

  • Change type tracking (edit, autosave, restore, publish)
  • Word/character count tracking
  • Side-by-side diff comparison with LCS algorithm
  • Configurable retention policies (max count, max age)

Service Layer

AIGatewayService

Orchestrates two-stage AI content generation:

  1. Stage 1: Draft (Gemini) - Fast, cost-effective initial generation
  2. Stage 2: Refine (Claude) - Quality refinement and brand voice alignment
php
$gateway = app(AIGatewayService::class);

// Two-stage pipeline
$result = $gateway->generateAndRefine($brief);

// Or individual stages
$draft = $gateway->generateDraft($brief);
$refined = $gateway->refineDraft($brief, $draftContent);

// Direct Claude generation (skip Gemini)
$content = $gateway->generateDirect($brief);

ContentSearchService

Full-text search with multiple backend support:

php
// Backends (configured via CONTENT_SEARCH_BACKEND)
const BACKEND_DATABASE = 'database';        // LIKE queries with relevance
const BACKEND_SCOUT_DATABASE = 'scout_database';  // Laravel Scout
const BACKEND_MEILISEARCH = 'meilisearch';  // Laravel Scout + Meilisearch

Features:

  • Relevance scoring (title > slug > excerpt > content)
  • Filters: type, status, category, tag, date range, content_type
  • Autocomplete suggestions
  • Re-indexing support for Scout backends

WebhookRetryService

Handles failed webhook processing with exponential backoff:

Retry intervals: 1m, 5m, 15m, 1h, 4h
Max retries: 5 (configurable per webhook)

ContentRender

Public-facing content renderer with caching:

  • Homepage, blog listing, post, page rendering
  • Cache TTL: 1 hour production, 1 minute development
  • Cache key sanitisation for special characters

CdnPurgeService

CDN cache invalidation via Bunny CDN:

  • Triggered by ContentItemObserver on publish/update
  • URL-based and tag-based purging
  • Workspace-level cache clearing

Event-Driven Architecture

The package uses the event-driven module loading pattern from core-php:

php
class Boot extends ServiceProvider
{
    public static array $listens = [
        WebRoutesRegistering::class => 'onWebRoutes',
        ApiRoutesRegistering::class => 'onApiRoutes',
        ConsoleBooting::class => 'onConsole',
        McpToolsRegistering::class => 'onMcpTools',
    ];
}

Handlers register:

  • Web Routes: Public blog, help pages, content preview
  • API Routes: REST API for briefs, media, search, generation
  • Console: Artisan commands for scheduling, pruning
  • MCP Tools: AI agent content management tools

API Structure

Authenticated Endpoints (Session or API Key)

# Content Briefs
GET    /api/content/briefs           # List briefs
POST   /api/content/briefs           # Create brief
GET    /api/content/briefs/{id}      # Get brief
PUT    /api/content/briefs/{id}      # Update brief
DELETE /api/content/briefs/{id}      # Delete brief
POST   /api/content/briefs/bulk      # Bulk create
GET    /api/content/briefs/next      # Next ready for processing

# AI Generation (rate limited: 10/min)
POST   /api/content/generate/draft   # Generate draft (Gemini)
POST   /api/content/generate/refine  # Refine draft (Claude)
POST   /api/content/generate/full    # Full pipeline
POST   /api/content/generate/social  # Social posts from content

# Content Search (rate limited: 60/min)
GET    /api/content/search           # Full-text search
GET    /api/content/search/suggest   # Autocomplete
GET    /api/content/search/info      # Backend info
POST   /api/content/search/reindex   # Trigger re-index

# Revisions
GET    /api/content/items/{id}/revisions    # List revisions
GET    /api/content/revisions/{id}          # Get revision
POST   /api/content/revisions/{id}/restore  # Restore revision
GET    /api/content/revisions/{id}/compare/{other}  # Compare

# Preview
POST   /api/content/items/{id}/preview/generate  # Generate preview link
DELETE /api/content/items/{id}/preview/revoke    # Revoke preview link

Public Endpoints

# Webhooks (signature verified, no auth)
POST   /api/content/webhooks/{endpoint}  # Receive external webhooks

# Web Routes
GET    /blog                             # Blog listing
GET    /blog/{slug}                      # Blog post
GET    /help                             # Help centre
GET    /help/{slug}                      # Help article
GET    /content/preview/{id}             # Preview content

Rate Limiting

Defined in Boot::configureRateLimiting():

LimiterAuthenticatedUnauthenticated
content-generate10/min per user/workspace2/min per IP
content-briefs30/min per user5/min per IP
content-webhooks60/min per endpoint30/min per IP
content-searchConfigurable (default 60/min)20/min per IP

MCP Tools

Seven MCP tools for AI agent integration:

ToolDescription
content_listList content items with filters
content_readRead content by ID or slug
content_searchFull-text search
content_createCreate new content
content_updateUpdate existing content
content_deleteSoft delete content
content_taxonomiesList categories and tags

All tools:

  • Require workspace resolution
  • Check entitlements (content.mcp_access, content.items)
  • Log actions to MCP session
  • Return structured responses

Data Flow

Content Creation via MCP

Agent Request

ContentCreateHandler::handle()

resolveWorkspace() → Workspace model

checkEntitlement() → EntitlementService

ContentItem::create()

createRevision() → ContentRevision

recordUsage() → EntitlementService

Response with content ID

Webhook Processing

External CMS

POST /api/content/webhooks/{endpoint}

ContentWebhookController::receive()

Verify signature → ContentWebhookEndpoint::verifySignature()

Check type allowed → ContentWebhookEndpoint::isTypeAllowed()

Create ContentWebhookLog

Dispatch ProcessContentWebhook job

Job::handle()

Process based on event type (wordpress.*, cms.*, generic.*)

Create/Update/Delete ContentItem

Mark log completed

AI Generation Pipeline

ContentBrief

GenerateContentJob dispatched

Stage 1: AIGatewayService::generateDraft()

GeminiService::generate() → Draft content

Brief::markDraftComplete()

Stage 2: AIGatewayService::refineDraft()

ClaudeService::generate() → Refined content

Brief::markRefined()

AIUsage records created for each stage

Configuration

Key settings in config.php:

php
return [
    'generation' => [
        'default_timeout' => env('CONTENT_GENERATION_TIMEOUT', 300),
        'timeouts' => [
            'help_article' => 180,
            'blog_post' => 240,
            'landing_page' => 300,
            'social_post' => 60,
        ],
        'max_retries' => 3,
        'backoff' => [30, 60, 120],
    ],
    'revisions' => [
        'max_per_item' => env('CONTENT_MAX_REVISIONS', 50),
        'max_age_days' => 180,
        'preserve_published' => true,
    ],
    'cache' => [
        'ttl' => env('CONTENT_CACHE_TTL', 3600),
        'prefix' => 'content:render',
    ],
    'search' => [
        'backend' => env('CONTENT_SEARCH_BACKEND', 'database'),
        'min_query_length' => 2,
        'max_per_page' => 50,
        'default_per_page' => 20,
        'rate_limit' => 60,
    ],
];

Database Schema

Primary Tables

TablePurpose
content_itemsContent storage (posts, pages)
content_revisionsVersion history
content_taxonomiesCategories and tags
content_item_taxonomyPivot table
content_mediaMedia attachments
content_authorsAuthor profiles
content_briefsAI generation briefs
content_tasksScheduled content tasks
content_webhook_endpointsWebhook configurations
content_webhook_logsWebhook processing logs
ai_usageAI API usage tracking
promptsAI prompt templates
prompt_versionsPrompt version history

Key Indexes

  • content_items: Composite indexes on (workspace_id, slug, type), (workspace_id, status, type), (workspace_id, status, content_type)
  • content_revisions: Index on (content_item_id, revision_number)
  • content_webhook_logs: Index on (workspace_id, status), (status, created_at)

Extension Points

Adding New Content Types

  1. Add value to ContentType enum
  2. Update ContentType::isNative() if applicable
  3. Add any type-specific scopes to ContentItem

Adding New AI Generation Types

  1. Add value to BriefContentType enum
  2. Add timeout to config.php generation.timeouts
  3. Add prompt in AIGatewayService::getDraftSystemPrompt()

Adding New Webhook Event Types

  1. Add to ContentWebhookEndpoint::ALLOWED_TYPES
  2. Add handler in ProcessContentWebhook::processWordPress() or processCms()
  3. Add event type mapping in ContentWebhookController::normaliseEventType()

Adding New MCP Tools

  1. Create handler in Mcp/Handlers/ implementing McpToolHandler
  2. Define schema() with tool name, description, input schema
  3. Implement handle() with workspace resolution and entitlement checks
  4. Register in Boot::onMcpTools()

Released under the EUPL-1.2 License.