refactor: rename namespace Core\Content to Core\Mod\Content

Aligns content module namespace with the standard module structure
convention (Core\Mod\{Name}) for consistency across the monorepo.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-27 16:24:53 +00:00
parent f990dc1bd3
commit 6ede1b1a20
68 changed files with 2077 additions and 187 deletions

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content;
namespace Core\Mod\Content;
use Core\Events\ApiRoutesRegistering;
use Core\Events\ConsoleBooting;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Concerns;
namespace Core\Mod\Content\Concerns;
/**
* Trait for making ContentItem searchable with Laravel Scout.

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Console\Commands;
namespace Core\Mod\Content\Console\Commands;
use Mod\Agentic\Services\ContentService;
use Illuminate\Console\Command;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Console\Commands;
namespace Core\Mod\Content\Console\Commands;
use Mod\Agentic\Services\ContentService;
use Illuminate\Console\Command;

View file

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Core\Content\Console\Commands;
namespace Core\Mod\Content\Console\Commands;
use Core\Content\Enums\ContentType;
use Core\Content\Models\ContentAuthor;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentMedia;
use Core\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Enums\ContentType;
use Core\Mod\Content\Models\ContentAuthor;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentMedia;
use Core\Mod\Content\Models\ContentTaxonomy;
use Core\Mod\Tenant\Models\Workspace;
use Carbon\Carbon;
use Illuminate\Console\Command;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Console\Commands;
namespace Core\Mod\Content\Console\Commands;
use Mod\Agentic\Services\ContentService;
use Illuminate\Console\Command;

View file

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Core\Content\Console\Commands;
namespace Core\Mod\Content\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Core\Content\Services\WebhookRetryService;
use Core\Mod\Content\Services\WebhookRetryService;
/**
* ProcessPendingWebhooks

View file

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Core\Content\Console\Commands;
namespace Core\Mod\Content\Console\Commands;
use Illuminate\Console\Command;
use Core\Content\Models\ContentRevision;
use Core\Mod\Content\Models\ContentRevision;
/**
* Prune old content revisions based on retention policy.

View file

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Core\Content\Console\Commands;
namespace Core\Mod\Content\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
/**
* PublishScheduledContent

View file

@ -2,15 +2,15 @@
declare(strict_types=1);
namespace Core\Content\Controllers\Api;
namespace Core\Mod\Content\Controllers\Api;
use Core\Front\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Core\Mod\Api\Concerns\HasApiResponses;
use Core\Mod\Api\Concerns\ResolvesWorkspace;
use Core\Content\Models\ContentBrief;
use Core\Content\Resources\ContentBriefResource;
use Core\Mod\Content\Models\ContentBrief;
use Core\Mod\Content\Resources\ContentBriefResource;
/**
* Content Brief API Controller

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Controllers\Api;
namespace Core\Mod\Content\Controllers\Api;
use Core\Front\Controller;
use Core\Mod\Api\Concerns\HasApiResponses;
@ -11,7 +11,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Core\Content\Models\ContentMedia;
use Core\Mod\Content\Models\ContentMedia;
/**
* Content Media API Controller

View file

@ -2,16 +2,16 @@
declare(strict_types=1);
namespace Core\Content\Controllers\Api;
namespace Core\Mod\Content\Controllers\Api;
use Core\Front\Controller;
use Core\Mod\Api\Concerns\HasApiResponses;
use Core\Mod\Api\Concerns\ResolvesWorkspace;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentRevision;
use Core\Content\Resources\ContentRevisionResource;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentRevision;
use Core\Mod\Content\Resources\ContentRevisionResource;
/**
* Content Revision API Controller

View file

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace Core\Content\Controllers\Api;
namespace Core\Mod\Content\Controllers\Api;
use Core\Front\Controller;
use Core\Mod\Api\Concerns\HasApiResponses;
use Core\Mod\Api\Concerns\ResolvesWorkspace;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Core\Content\Services\ContentSearchService;
use Core\Mod\Content\Services\ContentSearchService;
/**
* Content Search API Controller

View file

@ -2,15 +2,15 @@
declare(strict_types=1);
namespace Core\Content\Controllers\Api;
namespace Core\Mod\Content\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Log;
use Core\Content\Jobs\ProcessContentWebhook;
use Core\Content\Models\ContentWebhookEndpoint;
use Core\Content\Models\ContentWebhookLog;
use Core\Mod\Content\Jobs\ProcessContentWebhook;
use Core\Mod\Content\Models\ContentWebhookEndpoint;
use Core\Mod\Content\Models\ContentWebhookLog;
/**
* Controller for receiving external content webhooks.

View file

@ -2,18 +2,18 @@
declare(strict_types=1);
namespace Core\Content\Controllers\Api;
namespace Core\Mod\Content\Controllers\Api;
use Core\Front\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Core\Mod\Api\Concerns\HasApiResponses;
use Core\Mod\Api\Concerns\ResolvesWorkspace;
use Core\Content\Jobs\GenerateContentJob;
use Core\Content\Models\AIUsage;
use Core\Content\Models\ContentBrief;
use Core\Content\Resources\ContentBriefResource;
use Core\Content\Services\AIGatewayService;
use Core\Mod\Content\Jobs\GenerateContentJob;
use Core\Mod\Content\Models\AIUsage;
use Core\Mod\Content\Models\ContentBrief;
use Core\Mod\Content\Resources\ContentBriefResource;
use Core\Mod\Content\Services\AIGatewayService;
/**
* Content Generation API Controller

View file

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Core\Content\Controllers;
namespace Core\Mod\Content\Controllers;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
/**
* ContentPreviewController - Preview draft content before publishing.

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Enums;
namespace Core\Mod\Content\Enums;
/**
* Content type for ContentBrief.

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Enums;
namespace Core\Mod\Content\Enums;
/**
* Content source type for ContentItem.

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Jobs;
namespace Core\Mod\Content\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -10,8 +10,8 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Core\Content\Models\ContentBrief;
use Core\Content\Services\AIGatewayService;
use Core\Mod\Content\Models\ContentBrief;
use Core\Mod\Content\Services\AIGatewayService;
/**
* GenerateContentJob

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Jobs;
namespace Core\Mod\Content\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -10,12 +10,12 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Core\Content\Enums\ContentType;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentMedia;
use Core\Content\Models\ContentTaxonomy;
use Core\Content\Models\ContentWebhookEndpoint;
use Core\Content\Models\ContentWebhookLog;
use Core\Mod\Content\Enums\ContentType;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentMedia;
use Core\Mod\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Models\ContentWebhookEndpoint;
use Core\Mod\Content\Models\ContentWebhookLog;
/**
* Process incoming content webhooks.

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Mcp\Handlers;
namespace Core\Mod\Content\Mcp\Handlers;
use Carbon\Carbon;
use Core\Front\Mcp\Contracts\McpToolHandler;
@ -11,10 +11,10 @@ use Core\Mod\Tenant\Models\Workspace;
use Core\Mod\Tenant\Services\EntitlementService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Core\Content\Enums\ContentType;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentRevision;
use Core\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Enums\ContentType;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentRevision;
use Core\Mod\Content\Models\ContentTaxonomy;
/**
* MCP tool handler for creating content items.

View file

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace Core\Content\Mcp\Handlers;
namespace Core\Mod\Content\Mcp\Handlers;
use Core\Front\Mcp\Contracts\McpToolHandler;
use Core\Front\Mcp\McpContext;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Support\Facades\Auth;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentRevision;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentRevision;
/**
* MCP tool handler for deleting content items.

View file

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Core\Content\Mcp\Handlers;
namespace Core\Mod\Content\Mcp\Handlers;
use Core\Front\Mcp\Contracts\McpToolHandler;
use Core\Front\Mcp\McpContext;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Support\Str;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
/**
* MCP tool handler for listing content items.

View file

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace Core\Content\Mcp\Handlers;
namespace Core\Mod\Content\Mcp\Handlers;
use Core\Front\Mcp\Contracts\McpToolHandler;
use Core\Front\Mcp\McpContext;
use Core\Mod\Tenant\Models\Workspace;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
/**
* MCP tool handler for reading content items.

View file

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Core\Content\Mcp\Handlers;
namespace Core\Mod\Content\Mcp\Handlers;
use Core\Front\Mcp\Contracts\McpToolHandler;
use Core\Front\Mcp\McpContext;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Support\Str;
use Core\Content\Services\ContentSearchService;
use Core\Mod\Content\Services\ContentSearchService;
/**
* MCP tool handler for searching content.

View file

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace Core\Content\Mcp\Handlers;
namespace Core\Mod\Content\Mcp\Handlers;
use Core\Front\Mcp\Contracts\McpToolHandler;
use Core\Front\Mcp\McpContext;
use Core\Mod\Tenant\Models\Workspace;
use Core\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Models\ContentTaxonomy;
/**
* MCP tool handler for listing content taxonomies.

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Mcp\Handlers;
namespace Core\Mod\Content\Mcp\Handlers;
use Carbon\Carbon;
use Core\Front\Mcp\Contracts\McpToolHandler;
@ -10,9 +10,9 @@ use Core\Front\Mcp\McpContext;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentRevision;
use Core\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentRevision;
use Core\Mod\Content\Models\ContentTaxonomy;
/**
* MCP tool handler for updating content items.

View file

@ -2,9 +2,9 @@
declare(strict_types=1);
namespace Core\Content\Middleware;
namespace Core\Mod\Content\Middleware;
use Core\Content\Services\ContentRender;
use Core\Mod\Content\Services\ContentRender;
use Core\Mod\Tenant\Models\Workspace;
use Closure;
use Illuminate\Http\Request;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -20,9 +20,9 @@ class AIUsage extends Model
{
use HasFactory;
protected static function newFactory(): \Core\Content\Database\Factories\AIUsageFactory
protected static function newFactory(): \Core\Mod\Content\Database\Factories\AIUsageFactory
{
return \Core\Content\Database\Factories\AIUsageFactory::new();
return \Core\Mod\Content\Database\Factories\AIUsageFactory::new();
}
protected $table = 'ai_usage';

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -14,9 +14,9 @@ class ContentAuthor extends Model
{
use HasFactory;
protected static function newFactory(): \Core\Content\Database\Factories\ContentAuthorFactory
protected static function newFactory(): \Core\Mod\Content\Database\Factories\ContentAuthorFactory
{
return \Core\Content\Database\Factories\ContentAuthorFactory::new();
return \Core\Mod\Content\Database\Factories\ContentAuthorFactory::new();
}
protected $fillable = [

View file

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Core\Content\Enums\BriefContentType;
use Core\Mod\Content\Enums\BriefContentType;
use Core\Mod\Tenant\Models\Workspace;
/**
@ -23,9 +23,9 @@ class ContentBrief extends Model
{
use HasFactory;
protected static function newFactory(): \Core\Content\Database\Factories\ContentBriefFactory
protected static function newFactory(): \Core\Mod\Content\Database\Factories\ContentBriefFactory
{
return \Core\Content\Database\Factories\ContentBriefFactory::new();
return \Core\Mod\Content\Database\Factories\ContentBriefFactory::new();
}
public const STATUS_PENDING = 'pending';

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Core\Mod\Tenant\Models\User;
use Core\Mod\Tenant\Models\Workspace;
@ -14,17 +14,17 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Core\Content\Enums\ContentType;
use Core\Content\Observers\ContentItemObserver;
use Core\Mod\Content\Enums\ContentType;
use Core\Mod\Content\Observers\ContentItemObserver;
#[ObservedBy([ContentItemObserver::class])]
class ContentItem extends Model
{
use HasFactory, HasSeoMetadata, SoftDeletes;
protected static function newFactory(): \Core\Content\Database\Factories\ContentItemFactory
protected static function newFactory(): \Core\Mod\Content\Database\Factories\ContentItemFactory
{
return \Core\Content\Database\Factories\ContentItemFactory::new();
return \Core\Mod\Content\Database\Factories\ContentItemFactory::new();
}
protected $fillable = [

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -13,9 +13,9 @@ class ContentMedia extends Model
{
use HasFactory;
protected static function newFactory(): \Core\Content\Database\Factories\ContentMediaFactory
protected static function newFactory(): \Core\Mod\Content\Database\Factories\ContentMediaFactory
{
return \Core\Content\Database\Factories\ContentMediaFactory::new();
return \Core\Mod\Content\Database\Factories\ContentMediaFactory::new();
}
protected $table = 'content_media';

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Core\Mod\Tenant\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Mod\Agentic\Models\Prompt;
use Core\Mod\Tenant\Models\Workspace;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -14,9 +14,9 @@ class ContentTaxonomy extends Model
{
use HasFactory;
protected static function newFactory(): \Core\Content\Database\Factories\ContentTaxonomyFactory
protected static function newFactory(): \Core\Mod\Content\Database\Factories\ContentTaxonomyFactory
{
return \Core\Content\Database\Factories\ContentTaxonomyFactory::new();
return \Core\Mod\Content\Database\Factories\ContentTaxonomyFactory::new();
}
protected $fillable = [

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -35,9 +35,9 @@ class ContentWebhookEndpoint extends Model
{
use HasFactory;
protected static function newFactory(): \Core\Content\Database\Factories\ContentWebhookEndpointFactory
protected static function newFactory(): \Core\Mod\Content\Database\Factories\ContentWebhookEndpointFactory
{
return \Core\Content\Database\Factories\ContentWebhookEndpointFactory::new();
return \Core\Mod\Content\Database\Factories\ContentWebhookEndpointFactory::new();
}
protected $table = 'content_webhook_endpoints';

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Models;
namespace Core\Mod\Content\Models;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -13,9 +13,9 @@ class ContentWebhookLog extends Model
{
use HasFactory;
protected static function newFactory(): \Core\Content\Database\Factories\ContentWebhookLogFactory
protected static function newFactory(): \Core\Mod\Content\Database\Factories\ContentWebhookLogFactory
{
return \Core\Content\Database\Factories\ContentWebhookLogFactory::new();
return \Core\Mod\Content\Database\Factories\ContentWebhookLogFactory::new();
}
protected $fillable = [

View file

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Core\Content\Observers;
namespace Core\Mod\Content\Observers;
use Illuminate\Support\Facades\Log;
use Core\Content\Models\ContentItem;
use Core\Content\Services\CdnPurgeService;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Services\CdnPurgeService;
/**
* Content Item Observer - handles CDN cache purging on content changes.

View file

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Core\Content\Services;
namespace Core\Mod\Content\Services;
use Mod\Agentic\Services\AgenticResponse;
use Mod\Agentic\Services\ClaudeService;
use Mod\Agentic\Services\GeminiService;
use Core\Content\Models\AIUsage;
use Core\Content\Models\ContentBrief;
use Core\Mod\Content\Models\AIUsage;
use Core\Mod\Content\Models\ContentBrief;
use RuntimeException;
/**

View file

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Core\Content\Services;
namespace Core\Mod\Content\Services;
use Illuminate\Support\Facades\Log;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
use Plug\Cdn\CdnManager;
use Plug\Response;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Services;
namespace Core\Mod\Content\Services;
use DOMDocument;
use DOMElement;

View file

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace Core\Content\Services;
namespace Core\Mod\Content\Services;
use Core\Front\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Tenant\Models\Workspace;
/**

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Core\Content\Services;
namespace Core\Mod\Content\Services;
use Carbon\Carbon;
use Core\Mod\Tenant\Models\Workspace;
@ -10,7 +10,7 @@ use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
/**
* Content Search Service

View file

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Core\Content\Services;
namespace Core\Mod\Content\Services;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
use Core\Content\Models\ContentWebhookLog;
use Core\Mod\Content\Models\ContentWebhookLog;
/**
* WebhookRetryService

View file

@ -2,16 +2,16 @@
declare(strict_types=1);
namespace Core\Content\View\Modal\Admin;
namespace Core\Mod\Content\View\Modal\Admin;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentTaxonomy;
use Core\Content\Services\ContentSearchService;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Services\ContentSearchService;
/**
* Content Search Livewire Component

View file

@ -2,15 +2,15 @@
declare(strict_types=1);
namespace Core\Content\View\Modal\Admin;
namespace Core\Mod\Content\View\Modal\Admin;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Core\Content\Models\ContentWebhookEndpoint;
use Core\Content\Models\ContentWebhookLog;
use Core\Mod\Content\Models\ContentWebhookEndpoint;
use Core\Mod\Content\Models\ContentWebhookLog;
/**
* Livewire component for managing content webhook endpoints.

View file

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Core\Content\View\Modal\Web;
namespace Core\Mod\Content\View\Modal\Web;
use Livewire\Component;
use Core\Content\Services\ContentRender;
use Core\Mod\Content\Services\ContentRender;
use Core\Mod\Tenant\Models\Workspace;
use Core\Mod\Tenant\Services\WorkspaceService;

View file

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Core\Content\View\Modal\Web;
namespace Core\Mod\Content\View\Modal\Web;
use Livewire\Component;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Tenant\Models\Workspace;
use Core\Mod\Tenant\Services\WorkspaceService;

View file

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Core\Content\View\Modal\Web;
namespace Core\Mod\Content\View\Modal\Web;
use Livewire\Component;
use Core\Content\Services\ContentRender;
use Core\Mod\Content\Services\ContentRender;
use Core\Mod\Tenant\Models\Workspace;
use Core\Mod\Tenant\Services\WorkspaceService;

View file

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Core\Content\View\Modal\Web;
namespace Core\Mod\Content\View\Modal\Web;
use Livewire\Component;
use Core\Content\Services\ContentRender;
use Core\Mod\Content\Services\ContentRender;
use Core\Mod\Tenant\Models\Workspace;
use Core\Mod\Tenant\Services\WorkspaceService;

View file

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Core\Content\View\Modal\Web;
namespace Core\Mod\Content\View\Modal\Web;
use Core\Mod\Tenant\Models\Workspace;
use Livewire\Component;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentItem;
/**
* Preview - Render draft/unpublished content with preview token.

View file

@ -0,0 +1,677 @@
# TASK-004: Native CMS and WordPress Removal
**Status:** ✅ complete (verified)
**Created:** 2026-01-02
**Last Updated:** 2026-01-02 16:30 by Claude Opus 4.5 (Implementation Agent)
**Assignee:** Claude Opus 4.5 (Implementation Agent)
**Verifier:** Claude Opus 4.5 (Verification Agent)
---
## Critical Context (READ FIRST)
**WordPress served its purpose. Now we own the content layer.**
### The Current State
WordPress (hestia.host.uk.com) has been the content backend for satellite sites:
- Blog posts and help articles synced via REST API
- ContentItem model stores local copies with sync metadata
- SatelliteService uses "local-first" pattern (check DB, fallback to WP API)
- Webhook integration for real-time sync
This worked for bootstrapping, but creates:
- **Operational overhead** — two systems to maintain
- **Sync complexity** — race conditions, stale content, webhook failures
- **Stack mismatch** — PHP ↔ WordPress when we could be pure Laravel
- **Future friction** — MCP integration wants native content, not WP bridges
### The Vision
A fully native content management system where:
- ContentItem is the **source of truth** (not a sync cache)
- Content Editor at `/hub/content-editor/{workspace}/new/{contentType}` is the primary authoring tool
- AI assistance via MCP for content generation, SEO, translation
- Satellite sites serve directly from Laravel (no WordPress dependency)
- WordPress can re-integrate later as *one option among many* (headless CMS, not *the* CMS)
### Why Now
1. Content Editor already exists and works well
2. SatelliteService already has local-first logic
3. MCP Portal (mcp.host.uk.com) needs native content APIs
4. Phase 42 content generation uses native workflows
5. WordPress hosting is an unnecessary cost
---
## Objective
Remove WordPress as a required dependency for satellite site content. Make the native Content Editor the primary authoring tool, with ContentItem as the source of truth. Prepare the content layer for MCP integration (AI-assisted content creation, semantic search, agent access).
**"Done" looks like:**
- Satellite sites (blog, help) serve content entirely from Laravel database
- Content Editor is the only place to create/edit content
- No WordPress API calls in normal operation
- MCP tools can read/write content natively
- WordPress integration exists as an optional import/export feature
---
## Acceptance Criteria
### Phase 1: Remove WordPress Dependency ✅ VERIFIED
- [x] AC1: SatelliteService never calls WordPressService in normal operation
- [x] AC2: ContentItem has `content_type = 'native'` (new value, distinct from 'wordpress')
- [x] AC3: Blog pages (`/blog`, `/blog/{slug}`) render from native content only
- [x] AC4: Help pages (`/help`, `/help/{slug}`) render from native content only
- [x] AC5: WordPress sync code is moved to `app/Legacy/` (not deleted, for future import)
- [x] AC6: `WORDPRESS_URL` env var is optional, not required for app boot
### Phase 2: Content Editor Enhancements
- [x] AC7: Content Editor supports rich text with Flux Editor (not just textarea)
- [x] AC8: Content Editor has media upload (not WordPress media library)
- [x] AC9: Content Editor has category/tag management
- [x] AC10: Content Editor has SEO fields (meta title, description, OG image)
- [x] AC11: Content Editor has scheduling (publish_at datetime)
- [x] AC12: Content Editor has revision history
### Phase 3: MCP Integration ✅ COMPLETE
- [x] AC13: MCP tool `content_list` returns content items for a workspace
- [x] AC14: MCP tool `content_read` returns full content by ID or slug
- [x] AC15: MCP tool `content_create` creates new content (respects entitlements)
- [x] AC16: MCP tool `content_update` updates existing content
- [x] AC17: MCP resource `content://workspace/slug` provides content for context
- [x] AC18: AI content generation uses MCP tools (not direct OpenAI/Claude calls)
### Phase 4: Optional WordPress Import ✅ COMPLETE
- [x] AC19: Artisan command `content:import-wordpress` imports from WP REST API
- [x] AC20: Import preserves original WordPress IDs in `wp_id` field
- [x] AC21: Import handles media, categories, tags, authors
- [x] AC22: Import is idempotent (re-running updates, doesn't duplicate)
---
## Implementation Checklist
### Phase 1: WordPress Removal
- [x] Create new enum value `ContentType::NATIVE` in `app/Enums/ContentType.php`
- [x] Update `ContentItem` model to default to `content_type = 'native'`
- [x] Refactor `SatelliteService` to remove WordPress fallback:
- [x] `getHomepage()` — native only
- [x] `getPosts()` — native only
- [x] `getPost()` — native only
- [x] `getPage()` — native only
- [x] Move WordPress integration files to `app/Legacy/`:
- [x] `app/Services/WordPressService.php``app/Legacy/WordPress/WordPressService.php`
- [x] `app/Services/ContentSyncService.php``app/Legacy/WordPress/ContentSyncService.php`
- [x] `app/Http/Controllers/Api/ContentWebhookController.php``app/Legacy/WordPress/`
- [x] `app/Jobs/ProcessContentWebhook.php``app/Legacy/WordPress/`
- [x] Update `config/services.php` to make WordPress optional
- [x] Make WordPress routes conditional in `routes/api.php` (enabled via `CONTENT_WORDPRESS_ENABLED`)
- [x] Update satellite views to not expect WordPress-specific fields (added native filter option)
- [x] Add feature flag `CONTENT_SOURCE=native` (default) vs `CONTENT_SOURCE=wordpress`
- [x] Write migration to update existing `content_type = 'wordpress'``'native'`
### Phase 2: Content Editor
- [x] Implement Flux Editor integration in `ContentEditor.php`
- [x] Create media upload in Content Editor (WithFileUploads trait, Livewire)
- [x] Add media library modal to Content Editor (featured image selection)
- [x] Add taxonomy management UI in Content Editor sidebar (categories/tags)
- [x] Add SEO fields component in Content Editor sidebar (meta title, description, keywords, preview)
- [x] Implement `publish_at` scheduling with datetime picker in sidebar
- [x] Create `ContentRevision` model for version history
- [x] Add revision history panel with restore functionality
- [x] Add autosave functionality (60-second interval)
- [x] Add Ctrl+S keyboard shortcut for save
### Phase 3: MCP Tools
- [x] Create `app/Mcp/Tools/ContentTools.php`:
- [x] `content_list` — paginated, filterable by type/status/search
- [x] `content_read` — by ID or slug, returns markdown
- [x] `content_create` — with entitlement check and taxonomy support
- [x] `content_update` — with revision creation
- [x] `content_delete` — soft delete
- [x] `taxonomies` — list categories and tags
- [x] Create `app/Mcp/Resources/ContentResource.php`:
- [x] URI format: `content://{workspace}/{slug}`
- [x] Returns markdown with YAML frontmatter for AI context
- [x] Includes categories, tags, SEO meta, publish_at
- [x] Register tools in MCP server (`app/Mcp/Servers/HostHub.php`)
- [x] Add entitlement features: `content.mcp_access`, `content.items`, `content.ai_generation`
- [x] Write MCP tool tests (48 tests passing)
### Phase 4: WordPress Import ✅ COMPLETE
- [x] Create `app/Console/Commands/ContentImportWordPress.php`
- [x] Implement batch import with progress bar
- [x] Map WordPress fields to ContentItem fields
- [x] Download and store media files locally
- [x] Create authors from WordPress users
- [x] Handle categories and tags
- [x] Add `--dry-run` flag for preview
- [x] Add `--since` flag for incremental imports
- [x] Add `--limit` flag for controlling import count
- [x] Add `--skip-media` flag to skip file downloads
- [x] Add `--types` flag to select what to import
- [x] Support JWT and Basic Auth for private content
- [x] Handle HTML entities and smart quotes
- [x] Extract SEO metadata from Yoast/RankMath
### Testing
- [x] Feature test: Satellite blog renders native content
- [x] Feature test: Content Editor creates native content
- [x] Feature test: Content Editor updates native content
- [x] Feature test: Media upload works
- [x] Feature test: MCP tools require authentication
- [x] Feature test: MCP tools respect entitlements
- [x] Unit test: ContentItem has correct relationships
- [x] Unit test: SatelliteService returns native content
- [x] Integration test: WordPress import processes all content types (21 tests)
---
## Migration Strategy
### Zero Downtime Approach
1. **Phase 1a: Add native support alongside WordPress**
- Keep WordPress sync running
- Add `content_type = 'native'` capability
- Content Editor creates native content
- SatelliteService prefers native, falls back to WordPress
2. **Phase 1b: Migrate existing content**
- Run one-time migration to copy `wordpress``native`
- Verify all satellite pages render correctly
- Monitor for 1 week
3. **Phase 1c: Disable WordPress sync**
- Remove webhook registration
- Set `CONTENT_SOURCE=native`
- Keep WordPress running (read-only backup)
4. **Phase 1d: Remove WordPress**
- Move sync code to `app/Legacy/`
- Remove WordPress infrastructure
- Update documentation
### Rollback Plan
If issues arise:
1. Set `CONTENT_SOURCE=wordpress`
2. SatelliteService reverts to WordPress fallback
3. Re-enable webhook sync
4. Content continues serving from WordPress
---
## Data Model Changes
### ContentItem Updates
```php
// New enum value
enum ContentType: string
{
case WORDPRESS = 'wordpress'; // Legacy synced content
case NATIVE = 'native'; // Native Host UK content
case HOSTUK = 'hostuk'; // Keep for backwards compat, alias to native
case SATELLITE = 'satellite'; // Per-satellite site content
}
// New fields (if not present)
Schema::table('content_items', function (Blueprint $table) {
$table->timestamp('publish_at')->nullable()->after('status');
$table->unsignedBigInteger('revision_of')->nullable()->after('id');
$table->foreign('revision_of')->references('id')->on('content_items');
});
```
### New Table: content_revisions
```php
Schema::create('content_revisions', function (Blueprint $table) {
$table->id();
$table->foreignId('content_item_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->nullable()->constrained();
$table->string('title');
$table->longText('content_json');
$table->longText('content_html');
$table->text('change_summary')->nullable();
$table->timestamps();
});
```
---
## MCP Tool Specifications
### content_list
```json
{
"name": "content_list",
"description": "List content items for a workspace. Use to find articles, blog posts, or help pages.",
"parameters": {
"workspace": "Workspace slug (main, bio, social, etc.)",
"type": "Filter by type: post, page, or all",
"status": "Filter by status: draft, published, scheduled",
"limit": "Max items to return (default 20)",
"search": "Search title and content"
},
"use_when": [
"Need to find existing content",
"Want to see what's published on a satellite site",
"Looking for content to update or reference"
]
}
```
### content_read
```json
{
"name": "content_read",
"description": "Read full content of an article or page. Returns markdown for easy processing.",
"parameters": {
"workspace": "Workspace slug",
"identifier": "Content slug or ID"
},
"returns": "Full content as markdown with frontmatter (title, author, date, categories)"
}
```
### content_create
```json
{
"name": "content_create",
"description": "Create new content. Requires workspace write permission and ai.credits entitlement.",
"parameters": {
"workspace": "Target workspace",
"type": "post or page",
"title": "Content title",
"content": "Markdown content",
"status": "draft (default), published, or scheduled",
"publish_at": "ISO datetime for scheduled publishing",
"categories": "Array of category slugs",
"tags": "Array of tag strings"
}
}
```
---
## Dependencies
- Flux Editor component (already in Flux Pro)
- S3 or local storage for media
- MCP server infrastructure (existing)
- Entitlement system (existing)
---
## Risks and Mitigations
| Risk | Impact | Mitigation |
|------|--------|------------|
| Content loss during migration | High | Backup before migration, keep WordPress running until verified |
| SEO impact from URL changes | Medium | Keep same URL structure, 301 redirects if needed |
| Editor bugs causing data loss | High | Implement autosave, revision history from day 1 |
| MCP tools expose sensitive content | Medium | Workspace permission checks, entitlement gating |
---
## Verification Results
### Phase 1 Verification: 2026-01-02 by Claude Opus 4.5 (Verification Agent)
| Criterion | Status | Evidence |
|-----------|--------|----------|
| AC1: SatelliteService never calls WordPressService | ✅ PASS | `grep WordPressService app/Services/SatelliteService.php` returns no matches. File uses only `ContentItem` queries with `->native()` scope. Lines 38-44, 76-80, 104-111, 128-134 all use `ContentItem::forWorkspace()->native()`. |
| AC2: ContentItem has `content_type = 'native'` (new default) | ✅ PASS | `ContentType::NATIVE` enum exists at `app/Enums/ContentType.php:16`. Model's `booted()` method at lines 444-452 sets default via `ContentType::default()` which returns `NATIVE`. |
| AC3: Blog pages render from native content only | ✅ PASS | `app/Livewire/Satellite/Blog.php` uses `SatelliteService::getPosts()` which queries `ContentItem::native()`. No WordPressService import. |
| AC4: Help pages render from native content only | ✅ PASS | `app/Livewire/Satellite/Help.php` queries `ContentItem::forWorkspace()->native()->pages()` directly at lines 38-44. No WordPressService import. |
| AC5: WordPress sync code moved to `app/Legacy/` | ✅ PASS | Files exist at `app/Legacy/WordPress/`: `WordPressService.php`, `ContentSyncService.php`, `ContentWebhookController.php`, `ProcessContentWebhook.php`. Original locations (`app/Services/WordPressService.php`, `app/Services/ContentSyncService.php`) return "No such file or directory". |
| AC6: `WORDPRESS_URL` env var is optional | ✅ PASS | `config/services.php:48-54` shows `content.source` defaults to `native` and `content.wordpress_enabled` defaults to `false`. WordPress config at lines 67-74 only read if enabled. `routes/api.php:41` gates WordPress routes behind `config('services.content.wordpress_enabled')`. |
**Additional Verification:**
- Migration `2026_01_02_140456_update_wordpress_content_to_native.php` exists and converts `wordpress`/`hostuk` to `native`
- Tests: `./vendor/bin/pest --filter="Satellite|Content"` — 67 passed (2 unrelated SocialProof failures)
**Verdict:** ✅ PASS — Phase 1 acceptance criteria AC1-AC6 all met. Ready for Phase 2 implementation.
---
### Phase 2 Verification: 2026-01-02 by Claude Opus 4.5 (Verification Agent)
| Criterion | Status | Evidence |
|-----------|--------|----------|
| AC7: Content Editor supports rich text with Flux Editor | ✅ PASS | `<flux:editor` at `content-editor.blade.php:150`. Uses Flux Pro's rich text editor component with `wire:model="content"`. |
| AC8: Content Editor has media upload | ✅ PASS | `ContentEditor.php:355-380` implements `uploadFeaturedImage()` method with `WithFileUploads` trait. Uses Laravel's `store()` to save to `content-media` disk. Creates `ContentMedia` record. Featured image selection also supports media library modal. |
| AC9: Content Editor has category/tag management | ✅ PASS | `ContentEditor.php:273-329` implements `addTag()`, `removeTag()`, `toggleCategory()` methods. Properties `$selectedCategories` and `$selectedTags` track selections. View has category checkboxes and tag input with add/remove UI in Settings tab. |
| AC10: Content Editor has SEO fields | ✅ PASS | `ContentEditor.php:53-56` declares `$seoTitle`, `$seoDescription`, `$seoKeywords`, `$ogImage`. Lines 447-451 save to `seo_meta` JSON field. View has SEO tab with character counters (70 for title, 160 for description) and search preview. |
| AC11: Content Editor has scheduling | ✅ PASS | `ContentEditor.php:49-50` declares `$publishAt` and `$isScheduled`. Lines 257-265 toggle scheduling. Method `schedule()` at lines 521-533 saves with status='future'. Migration adds `publish_at` column to content_items. View has scheduling toggle and datetime picker. |
| AC12: Content Editor has revision history | ✅ PASS | `ContentRevision` model at `app/Models/ContentRevision.php`. `ContentEditor.php:389-440` implements `loadRevisions()` and `restoreRevision()`. Save method creates revision via `ContentRevision::createFromContentItem()`. Migration creates `content_revisions` table. View has History tab with revision list and restore buttons. |
**Additional Verification:**
- Migrations exist: `2026_01_02_141713_add_scheduling_fields_to_content_items_table.php`, `2026_01_02_141714_create_content_revisions_table.php`
- Tests: `./vendor/bin/pest --filter="Content"` — 56 Content tests passed (2 unrelated SocialProof failures)
- Autosave: `ContentEditor.php:507` calls `save(ContentRevision::CHANGE_AUTOSAVE)` every 60 seconds
- Keyboard shortcut: Ctrl+S save supported via view JavaScript
**Verdict:** ✅ PASS — Phase 2 acceptance criteria AC7-AC12 all met. Ready for Phase 3 implementation.
---
### Phase 3 Verification: 2026-01-02 by Claude Opus 4.5 (Verification Agent)
| Criterion | Status | Evidence |
|-----------|--------|----------|
| AC13: MCP tool `content_list` returns content items for a workspace | ✅ PASS | `ContentTools.php:98-161` implements `listContent()` with pagination, filtering by type/status/search, returns items with id, slug, title, type, status, categories, tags. Test: `ContentToolsTest.php` "lists content items for a workspace" passes. |
| AC14: MCP tool `content_read` returns full content by ID or slug | ✅ PASS | `ContentTools.php:166-238` implements `readContent()` resolving by ID or slug, returns JSON with full content or markdown format with YAML frontmatter. Test: "reads content by slug", "reads content by ID", "returns content as markdown" all pass. |
| AC15: MCP tool `content_create` creates new content (respects entitlements) | ✅ PASS | `ContentTools.php:243-346` implements `createContent()` with entitlement check at line 246 (`checkEntitlement`), calls `EntitlementService::can()` for `content.mcp_access` and `content.items`. Records usage at line 329. Test: "creates a new content item" passes with entitlement setup in beforeEach. |
| AC16: MCP tool `content_update` updates existing content | ✅ PASS | `ContentTools.php:351-454` implements `updateContent()` with revision creation at line 438 via `createRevision()`. Handles title, content, status, slug, categories, tags updates. Test: "updates existing content", "creates revision on update" pass. |
| AC17: MCP resource `content://workspace/slug` provides content for context | ✅ PASS | `ContentResource.php:19-133` implements URI format `content://{workspace}/{slug}`, returns markdown with YAML frontmatter including title, slug, type, status, author, categories, tags, publish_at, seo meta. `list()` method at line 140-169 returns all published native content as resources. Tests: 14 ContentResourceTest tests pass. |
| AC18: AI content generation uses MCP tools (not direct OpenAI/Claude calls) | ✅ PASS | MCP tools for content creation are fully functional via `ContentTools::createContent()`. AI agents access content via MCP protocol using `content_create` action. Entitlement feature `content.ai_generation` exists in FeatureSeeder.php:246. HostHub MCP server (line 86) registers ContentTools. 48 MCP content tests pass demonstrating AI agent capability. |
**Additional Verification:**
- MCP Server: `HostHub.php:86` includes `ContentTools::class` in tools array, `ContentResource::class` at line 92 in resources
- Entitlement Features: `FeatureSeeder.php:228-253` defines `content.mcp_access`, `content.items`, `content.ai_generation`
- Tests: `./vendor/bin/pest tests/Feature/Mcp/ContentToolsTest.php tests/Feature/Mcp/ContentResourceTest.php` — 48 passed (117 assertions)
- Migrations: `make_wp_id_nullable_on_content_items_table.php`, `make_wp_id_nullable_on_content_taxonomies_table.php` allow native content without WordPress IDs
**Verdict:** ✅ PASS — Phase 3 acceptance criteria AC13-AC18 all met. Ready for Phase 4 implementation.
---
### Phase 1 Implementation Notes (2026-01-02)
**Completed by:** Claude Opus 4.5 (Implementation Agent)
Phase 1 implementation is complete. Summary of changes:
1. **ContentType enum** (`app/Enums/ContentType.php`): Added `NATIVE` value as the new default content type.
2. **ContentItem model** (`app/Models/ContentItem.php`): Now defaults to `content_type = 'native'` and has `native()` scope for filtering.
3. **SatelliteService** (`app/Services/SatelliteService.php`): Completely rewritten to use native ContentItem queries. No WordPress fallback - returns content from database directly.
4. **Legacy namespace** (`app/Legacy/WordPress/`): Moved WordPress integration files:
- `WordPressService.php`
- `ContentSyncService.php`
- `ContentWebhookController.php`
- `ProcessContentWebhook.php`
5. **Feature flags** (`config/services.php`):
- `CONTENT_SOURCE=native` (default) - controls SatelliteService behaviour
- `CONTENT_WORDPRESS_ENABLED=false` (default) - gates WordPress webhook routes
6. **Routes** (`routes/api.php`): WordPress webhook endpoints now conditional on `CONTENT_WORDPRESS_ENABLED`.
7. **Livewire components updated**:
- `ContentEditor.php` - uses ContentType enum
- `Satellite/Blog.php`, `Post.php`, `Help.php`, `HelpArticle.php` - use SatelliteService
- `Workspace/Home.php` - uses SatelliteService
- `Admin/Content.php`, `ContentManager.php`, `Databases.php` - use Legacy namespace
8. **Migration** (`2026_01_02_140456_update_wordpress_content_to_native.php`): Updates existing `wordpress` and `hostuk` content types to `native`.
9. **Tests**: All 71 content-related tests pass. Updated `SatelliteRoutesTest` to expect home view (not waitlist) when workspace has no content.
**Ready for verification:** Phase 1 acceptance criteria AC1-AC6 should be tested.
---
### Phase 2 Implementation Notes (2026-01-02)
**Completed by:** Claude Opus 4.5 (Implementation Agent)
Phase 2 implementation is complete. Summary of changes:
1. **Migrations created:**
- `2026_01_02_141713_add_scheduling_fields_to_content_items_table.php`: Adds `publish_at`, `revision_count`, `last_edited_by` fields
- `2026_01_02_141714_create_content_revisions_table.php`: Creates revision history table
2. **ContentRevision model** (`app/Models/ContentRevision.php`): New model for version history with:
- Change type constants (edit, autosave, restore, publish, unpublish, schedule)
- `createFromContentItem()` static method for creating snapshots
- `restoreToContentItem()` method for restoring previous versions
- Word/character count tracking
- Diff summary generation
3. **ContentItem model updates** (`app/Models/ContentItem.php`):
- New `lastEditedBy()` relationship
- New `revisions()` HasMany relationship
- New `scopeScheduled()` and `scopeReadyToPublish()` scopes
- New `isScheduled()`, `createRevision()`, `latestRevision()` methods
4. **ContentEditor Livewire component** (`app/Livewire/Admin/ContentEditor.php`): Complete rewrite with:
- `WithFileUploads` trait for media upload
- Scheduling support with `isScheduled` toggle and `publishAt` datetime
- SEO fields (meta title, description, keywords) with character counts
- Category management (checkbox toggles)
- Tag management (add/remove with UI)
- Featured image upload and media library selection
- Revision history with restore capability
- Autosave functionality (60-second interval)
- Revision creation on save with change type tracking
5. **ContentEditor view** (`resources/views/admin/livewire/content-editor.blade.php`): Complete rewrite with:
- Two-column layout: main editor + sidebar
- Sidebar with 4 tabbed panels: Settings, SEO, Media, History
- Settings panel: scheduling toggle, datetime picker, categories, tags
- SEO panel: meta fields with character counters and search preview
- Media panel: featured image upload with drag-drop and library selection
- History panel: revision list with timestamps and restore buttons
- Ctrl+S keyboard shortcut for save
6. **Tests:** All 39 ContentManager tests pass including ContentItem model tests.
**Ready for verification:** Phase 2 acceptance criteria AC7-AC12 should be tested.
---
### Phase 3 Implementation Notes (2026-01-02)
**Completed by:** Claude Opus 4.5 (Implementation Agent)
Phase 3 implementation is complete. Summary of changes:
1. **ContentTools MCP Tool** (`app/Mcp/Tools/ContentTools.php`): Complete MCP tool for content management with:
- `list` action: Paginated, filterable by workspace, type, status, and search term
- `read` action: Returns content by ID or slug as markdown with frontmatter
- `create` action: Creates new content with entitlement check, taxonomy support, and automatic slug generation
- `update` action: Updates existing content with revision history tracking
- `delete` action: Soft deletes content
- `taxonomies` action: Lists categories and tags for a workspace
- Entitlement checking via `EntitlementService::can()`
- Automatic taxonomy creation when names don't exist
- Markdown output with YAML frontmatter for AI context
2. **ContentResource MCP Resource** (`app/Mcp/Resources/ContentResource.php`): MCP resource for content:// URIs:
- URI format: `content://{workspace}/{slug}` or `content://{workspace}/{id}`
- Returns markdown with YAML frontmatter including title, slug, type, status, author, categories, tags, SEO meta
- Workspace resolution by slug or ID
- Content resolution by slug or ID
- `list()` method returns all published native content as available resources
3. **MCP Server Registration** (`app/Mcp/Servers/HostHub.php`):
- Added `ContentTools` to tools array
- Added `ContentResource` to resources array
- Updated instructions with content tool documentation
4. **Entitlement Features** (`database/seeders/FeatureSeeder.php`): Added content features:
- `content.mcp_access`: Boolean feature for MCP access
- `content.items`: Limit feature for number of content items
- `content.ai_generation`: Boolean feature for AI content generation
5. **Database Migrations**: Created migrations to support native content:
- `2026_01_02_150351_make_wp_id_nullable_on_content_items_table.php`: Makes wp_id and sync_status nullable
- `2026_01_02_150640_make_wp_id_nullable_on_content_taxonomies_table.php`: Makes taxonomy wp_id nullable
6. **Factory Updates** (`database/factories/ContentItemFactory.php`): Added `native()` state for creating native content without WordPress fields.
7. **Tests**: Created comprehensive test suites:
- `tests/Feature/Mcp/ContentToolsTest.php`: 34 tests covering all actions and error handling
- `tests/Feature/Mcp/ContentResourceTest.php`: 14 tests for resource handling and listing
- All 48 tests pass
**Files Created/Modified:**
- `app/Mcp/Tools/ContentTools.php` (new)
- `app/Mcp/Resources/ContentResource.php` (new)
- `app/Mcp/Servers/HostHub.php` (modified)
- `database/seeders/FeatureSeeder.php` (modified)
- `database/factories/ContentItemFactory.php` (modified)
- `database/migrations/2026_01_02_150351_make_wp_id_nullable_on_content_items_table.php` (new)
- `database/migrations/2026_01_02_150640_make_wp_id_nullable_on_content_taxonomies_table.php` (new)
- `tests/Feature/Mcp/ContentToolsTest.php` (new)
- `tests/Feature/Mcp/ContentResourceTest.php` (new)
**Ready for verification:** Phase 3 acceptance criteria AC13-AC18 should be tested.
---
### Phase 4 Implementation Notes (2026-01-02)
**Completed by:** Claude Opus 4.5 (Implementation Agent)
Phase 4 implementation is complete. Summary of changes:
1. **ContentImportWordPress Command** (`app/Console/Commands/ContentImportWordPress.php`): Complete WordPress import artisan command with:
- Batch import with progress bars for each content type
- Support for posts, pages, categories, tags, authors, and media
- JWT and Basic Auth support for private/draft content
- `--dry-run` flag for preview mode (no database changes)
- `--since` flag for incremental imports (ISO 8601 date filter)
- `--limit` flag for controlling maximum items per type
- `--skip-media` flag to skip file downloads (creates records only)
- `--types` flag to select specific content types (default: posts,pages)
- `--workspace` flag to target specific workspace
- HTML entity decoding with smart quote normalization
- SEO metadata extraction from Yoast and RankMath plugins
- Idempotent operation (updates existing records, doesn't duplicate)
2. **Import Flow:**
- Validates WordPress site accessibility via REST API
- Resolves target workspace by ID or slug
- Imports in dependency order: authors → categories → tags → media → posts → pages
- Maps WordPress IDs to local IDs for relationship linking
- Creates ContentMedia records with optional file download to storage disk
- Syncs taxonomies with posts via pivot table
3. **Key Features:**
- `ContentType::WORDPRESS` used for imported content (distinct from `NATIVE`)
- `wp_id` and `wp_guid` preserved for reference
- `sync_status = 'synced'` and `synced_at` timestamp recorded
- Progress bars show import progress with created/updated/skipped counts
- Scheduled posts imported with `status = 'future'` and `publish_at` set
4. **Tests** (`tests/Feature/Console/ContentImportWordPressTest.php`): 21 comprehensive tests covering:
- Validation (site accessibility, workspace existence, date format)
- Author imports and updates
- Category and tag imports
- Post and page imports with full field mapping
- WordPress ID preservation
- HTML entity handling and smart quote normalization
- Category/tag linking on posts
- Media imports with file download
- Skip media flag (records without downloads)
- Dry run mode
- Incremental imports with --since
- Idempotency (re-running updates, doesn't duplicate)
- Limit option
- SEO metadata extraction
- Scheduled post handling
**Files Created:**
- `app/Console/Commands/ContentImportWordPress.php`
- `tests/Feature/Console/ContentImportWordPressTest.php`
**Usage Examples:**
```bash
# Basic import (posts and pages)
php artisan content:import-wordpress https://example.com --workspace=main
# Full import with all content types
php artisan content:import-wordpress https://example.com --workspace=main --types=authors,categories,tags,media,posts,pages
# Dry run preview
php artisan content:import-wordpress https://example.com --workspace=main --dry-run
# Incremental import (only content modified since date)
php artisan content:import-wordpress https://example.com --workspace=main --since=2024-01-01
# Authenticated import for drafts
php artisan content:import-wordpress https://example.com --workspace=main --username=admin --password=secret
# Limited import
php artisan content:import-wordpress https://example.com --workspace=main --limit=50
# Skip media downloads
php artisan content:import-wordpress https://example.com --workspace=main --skip-media
```
**Ready for verification:** Phase 4 acceptance criteria AC19-AC22 should be tested.
---
### Phase 4 Verification: 2026-01-02 by Claude Opus 4.5 (Verification Agent)
| Criterion | Status | Evidence |
|-----------|--------|----------|
| AC19: Artisan command `content:import-wordpress` imports from WP REST API | ✅ PASS | `ContentImportWordPress.php` is 925 lines implementing full WP REST API import. Signature at line 27-36 shows `content:import-wordpress {url}` with options. `validateWordPressSite()` at line 143 calls `/wp-json`, `importContentType()` at line 655 fetches from `/wp-json/wp/v2/{posts\|pages}`. Test: "validates WordPress site and shows site name" passes. |
| AC20: Import preserves original WordPress IDs in `wp_id` field | ✅ PASS | `importContentItem()` at line 758 sets `'wp_id' => $wpId` and `'wp_guid' => $item['guid']['rendered']`. Test: "preserves original WordPress IDs" at line 313 verifies `$post->wp_id->toBe(12345)` and `$post->wp_guid->toBe('https://example.com/?p=12345')`. |
| AC21: Import handles media, categories, tags, authors | ✅ PASS | `importAuthors()` line 231, `importCategories()` line 326, `importTags()` line 381, `importMedia()` line 480. Each has dedicated import method with progress bars. Tests verify all types: "imports WordPress users as authors", "imports WordPress categories", "imports WordPress tags", "imports WordPress media items" all pass. |
| AC22: Import is idempotent (re-running updates, doesn't duplicate) | ✅ PASS | Each import method checks for existing records first (e.g., `ContentItem::forWorkspace()->where('wp_id', $wpId)->first()` at line 729). If exists, updates instead of creates (line 791-793). Test: "idempotency → updates existing content instead of duplicating" verifies `ContentItem::count()->toBe(1)` after re-import and fields are updated. |
**Additional Verification:**
- Tests: `./vendor/bin/pest tests/Feature/Console/ContentImportWordPressTest.php` — 21 passed (90 assertions)
- Dry run mode: `--dry-run` flag prevents database changes (test at line 522)
- Incremental import: `--since` flag filters by modification date (test at line 569)
- Limit option: `--limit` flag caps items per type (test at line 647)
- SEO extraction: Yoast/RankMath metadata extracted (test at line 717)
- Scheduled posts: Future status and publish_at preserved (test at line 762)
- HTML entities: Smart quotes normalised to ASCII (test at line 348)
**Verdict:** ✅ PASS — Phase 4 acceptance criteria AC19-AC22 all met. TASK-004 is complete.
---
## Notes
### Why Keep WordPress Import
Even though we're removing WordPress as a dependency, keeping import capability:
1. Allows migration from other WordPress sites
2. Useful for clients moving from WP to Host Hub
3. Reference for future CMS integrations (Ghost, Strapi, etc.)
### MCP as the Future
The content MCP tools are strategic:
- Agents can create content without UI
- Enables automated content pipelines
- Positions Host UK as "AI-stabilised hosting" leader
- Content becomes programmable infrastructure
### Content Type Clarification
- `wordpress` — Legacy, synced from WordPress (to be migrated)
- `native` — Created in Host Hub (new default)
- `hostuk` — Alias for native (backwards compat)
- `satellite` — Per-service content (e.g., BioHost-specific help)
---
*This task transforms content from a synced dependency to owned infrastructure.*

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,130 @@
# Content Module Review
**Updated:** 2026-01-21 - All recommended improvements implemented
## Overview
The Content module manages the content system for Host UK, providing:
1. **Native Content Management** - ContentItem, ContentAuthor, ContentMedia, ContentTaxonomy, ContentRevision models for storing and managing content natively
2. **AI Content Generation** - Two-stage pipeline using Gemini (draft) + Claude (refinement) via ContentBrief and AIGatewayService
3. **WordPress Import** - One-time import from WordPress sites via REST API (ContentImportWordPress command)
4. **Public Rendering** - Satellite pages for blog/help content via Livewire components
5. **Content Processing** - HTML cleaning and JSON block parsing for headless rendering
The module is transitioning away from WordPress sync (deprecated) to a fully native content system with AI-assisted content generation.
## Production Readiness Score: 92/100 (was 85/100 - All recommended improvements implemented 2026-01-21)
## Critical Issues (Must Fix)
- [x] **Missing database migrations for core tables** - FIXED: Created `2026_01_14_000001_create_content_core_tables.php` migration documenting all 7 content tables with `Schema::hasTable()` guards.
- [x] **ContentBrief model missing factory** - FIXED: Created `ContentBriefFactory.php` with states for status, content types, and priorities.
- [x] **AIUsage model missing factory** - FIXED: Created `AIUsageFactory.php` with states for providers, purposes, and usage amounts.
- [x] **Content rendering XSS vulnerability** - FIXED: All blade templates now use `{{ }}` for titles. Added `getSanitisedContent()` method using HTMLPurifier for body content.
- [x] **Waitlist stored in cache only** - FIXED: `ContentRender` now uses `WaitlistEntry` model from Tenant module for persistent database storage.
## Recommended Improvements
- [x] **Add rate limiting to API endpoints** - FIXED: Rate limiting added to `/api/v1/content/generate/*` endpoints.
- [x] **Add validation for content_type in ContentBrief** - FIXED: BriefContentType enum added for type safety.
- [x] **ContentProcessingService error handling** - FIXED: DOM parsing errors now logged for debugging.
- [x] **Cache key collision potential** - FIXED: Cache key sanitisation added to ContentRender to handle special characters in slugs.
- [x] **Add indexes to ContentBrief table** - FIXED: Index added for `scheduled_for` column.
- [x] **Job timeout configuration** - FIXED: `GenerateContentJob` timeout now configurable for different content types.
- [x] **AIGatewayService constructor signature** - FIXED: Refactored to read config() fresh in getGemini()/getClaude() methods. Constructor now only stores optional overrides. Added resetServices() method for runtime config changes. Verified implementation shows clean separation of override vs config-based configuration.
- [x] **Help component queries pages not help articles** - FIXED: Help.php now uses `helpArticles` scope for proper content filtering.
- [x] **ContentRevision lacks pruning mechanism** - FIXED: Revision pruning command added with configurable retention policy.
- [x] **Deprecated commands still registered** - VERIFIED: ContentSync and ContentPublish already have proper deprecation warnings via trigger_error(E_USER_DEPRECATED) and console output.
## Missing Features (Future)
- [x] **Content scheduling system** - FIXED: Created `PublishScheduledContent` command (`content:publish-scheduled`) that uses `readyToPublish()` scope. Supports `--dry-run` and `--limit` options. Publishes 'future' status items whose `publish_at` has passed. Scheduled to run every minute via routes/console.php.
- [ ] **Media upload endpoint** - No API endpoint for uploading media. Only WordPress import creates ContentMedia records.
- [ ] **Content search** - No full-text search capability. Consider adding a search endpoint with elasticsearch/meilisearch integration.
- [ ] **Revision comparison/diff view** - `ContentRevision::getDiffSummary()` exists but returns boolean changed flags, not actual diff content.
- [ ] **Webhook handler** - ContentWebhookLog model exists but no webhook endpoint or processing job to receive/handle webhooks.
- [ ] **CDN cache purge integration** - `getCdnUrlsForPurgeAttribute()` generates purge URLs but no integration to actually call Bunny CDN purge API.
- [ ] **Content versioning API** - No API endpoints for listing/restoring revisions.
- [ ] **MCP tools** - `onMcpTools()` is stubbed out with commented examples. MCP integration incomplete.
- [ ] **Bulk operations** - No bulk publish, bulk delete, or bulk status change operations.
- [ ] **Content preview** - No preview endpoint for draft content before publishing.
## Test Coverage Assessment
**Current Coverage: Good for models, sparse for services**
Existing tests:
- `ContentRenderTest.php` - Tests ContentRender service, waitlist, workspace resolution (7 tests)
- `FactoriesTest.php` - Tests all model factories and computed properties (31 tests)
- `ContentManagerTest.php` - Tests model relationships, scopes, and queries (29 tests)
Missing test coverage:
- [ ] No tests for `AIGatewayService` - critical service, needs mocked tests
- [ ] No tests for `ContentProcessingService` - HTML parsing/cleaning needs test cases
- [ ] No tests for API controllers (`ContentBriefController`, `GenerationController`)
- [ ] No tests for `GenerateContentJob` queue job
- [ ] No tests for `ContentImportWordPress` command
- [ ] No integration tests for the full AI generation pipeline
- [ ] No tests for `ContentBrief` model (has no factory)
- [ ] No tests for `ContentRevision::createFromContentItem()` or `restoreToContentItem()`
## Security Concerns
1. **XSS in blade templates** - FIXED: Raw HTML output now uses sanitised content methods.
2. **No CSRF on subscribe endpoint** - The `WorkspaceRouter` handles POST /subscribe but the route bypasses middleware that would normally apply CSRF. Verify CSRF is checked.
3. **API authentication relies on middleware** - Routes use `auth` and `api.auth` middleware. Verify these are properly configured and cannot be bypassed.
4. **WordPress import stores arbitrary content** - `ContentImportWordPress` imports HTML content as-is. Could contain malicious scripts. Should sanitise on import.
5. **No authorisation checks on ContentBrief** - Controllers check workspace access but no policy/gate for fine-grained permissions (e.g., only editors can approve, only admins can delete).
6. **AI API keys in memory** - `AIGatewayService` stores API keys as instance properties. Not a major concern but consider using Laravel's encrypted config if storing in database.
## Notes
1. **Architecture clarity** - Good separation of concerns: Models handle data, Services handle business logic, Controllers handle HTTP, Jobs handle async. Clean module structure.
2. **WordPress transition** - The module is mid-transition from WordPress to native content. Some WordPress-specific code remains (ContentType::WORDPRESS, wp_id fields, import command). Deprecation notices are good.
3. **AI pipeline design** - The two-stage Gemini->Claude pipeline is well-designed for cost optimisation. Good use of usage tracking.
4. **ContentType enum** - Well-implemented enum with utility methods (label, color, icon, isNative). Good pattern.
5. **Content processing** - `ContentProcessingService` is comprehensive with proper DOM handling. Could be extracted to a shared package.
6. **Livewire components** - Follow consistent pattern. Consider extracting common workspace resolution logic to a trait.
7. **Hardcoded paths** - `ContentValidate` and `ContentGenerate` commands reference `base_path('doc/phase42/drafts')`. Should be configurable.
8. **Dependencies on other modules** - Module depends on:
- `Mod\Tenant` (Workspace, User)
- `Mod\Agentic` (ContentService, Prompt, ClaudeService, GeminiService, AgenticResponse)
- `Mod\Api` (HasApiResponses, ResolvesWorkspace)
- `Core` (Controller, events, HasSeoMetadata)
9. **Model pricing outdated** - `AIUsage::$pricing` will need regular updates as AI providers change pricing.

View file

@ -0,0 +1,71 @@
# Core-Content - January 2026
## Features Implemented
### Native CMS (TASK-004)
Complete content management system replacing WordPress dependency.
**Features:**
- Post/page content types
- Rich text editor
- Media management
- Categories and tags
- SEO metadata
- Scheduling
**Models:**
- `ContentItem` - posts, pages
- `ContentCategory`
- `ContentTag`
- `ContentMedia`
---
### Bulk Operations
Multi-select content management.
**Features:**
- Bulk publish/unpublish
- Bulk delete
- ContentManager UI
---
### CDN Cache Purge
Automatic CDN invalidation on publish.
**Files:**
- `Services/CdnPurgeService.php`
- `Observers/ContentItemObserver.php`
---
### Content Preview
Preview unpublished content with time-limited tokens.
**Files:**
- Preview endpoint
- Preview UI in editor
---
### Versioning API
Content revision history.
**Files:**
- `Controllers/ContentRevisionController.php`
- List, show, restore, compare endpoints
---
### Media Upload API
Full REST API for content media.
**Files:**
- `Controllers/ContentMediaController.php`

View file

@ -14,18 +14,18 @@
},
"autoload": {
"psr-4": {
"Core\\Content\\": ""
"Core\\Mod\\Content\\": ""
}
},
"autoload-dev": {
"psr-4": {
"Core\\Content\\Tests\\": "Tests/"
"Core\\Mod\\Content\\Tests\\": "Tests/"
}
},
"extra": {
"laravel": {
"providers": [
"Core\\Content\\Boot"
"Core\\Mod\\Content\\Boot"
]
}
},

View file

@ -10,13 +10,13 @@ declare(strict_types=1);
*/
use Illuminate\Support\Facades\Route;
use Core\Content\Controllers\Api\ContentBriefController;
use Core\Content\Controllers\Api\ContentMediaController;
use Core\Content\Controllers\Api\ContentRevisionController;
use Core\Content\Controllers\Api\ContentSearchController;
use Core\Content\Controllers\Api\ContentWebhookController;
use Core\Content\Controllers\Api\GenerationController;
use Core\Content\Controllers\ContentPreviewController;
use Core\Mod\Content\Controllers\Api\ContentBriefController;
use Core\Mod\Content\Controllers\Api\ContentMediaController;
use Core\Mod\Content\Controllers\Api\ContentRevisionController;
use Core\Mod\Content\Controllers\Api\ContentSearchController;
use Core\Mod\Content\Controllers\Api\ContentWebhookController;
use Core\Mod\Content\Controllers\Api\GenerationController;
use Core\Mod\Content\Controllers\ContentPreviewController;
/*
|--------------------------------------------------------------------------

View file

@ -3,11 +3,11 @@
declare(strict_types=1);
use Illuminate\Support\Facades\Route;
use Core\Content\View\Modal\Web\Blog;
use Core\Content\View\Modal\Web\Help;
use Core\Content\View\Modal\Web\HelpArticle;
use Core\Content\View\Modal\Web\Post;
use Core\Content\View\Modal\Web\Preview;
use Core\Mod\Content\View\Modal\Web\Blog;
use Core\Mod\Content\View\Modal\Web\Help;
use Core\Mod\Content\View\Modal\Web\HelpArticle;
use Core\Mod\Content\View\Modal\Web\Post;
use Core\Mod\Content\View\Modal\Web\Preview;
/*
|--------------------------------------------------------------------------

View file

@ -1,10 +1,10 @@
<?php
use Core\Content\Models\ContentAuthor;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentMedia;
use Core\Content\Models\ContentTaxonomy;
use Core\Content\Models\ContentWebhookLog;
use Core\Mod\Content\Models\ContentAuthor;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentMedia;
use Core\Mod\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Models\ContentWebhookLog;
use Core\Mod\Tenant\Models\User;
use Core\Mod\Tenant\Models\Workspace;

View file

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace Core\Content\Tests\Feature;
namespace Core\Mod\Content\Tests\Feature;
use Core\Mod\Tenant\Models\User;
use Core\Mod\Tenant\Models\Workspace;
use Core\Content\Enums\ContentType;
use Core\Content\Models\ContentItem;
use Core\Mod\Content\Enums\ContentType;
use Core\Mod\Content\Models\ContentItem;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

View file

@ -1,6 +1,6 @@
<?php
use Core\Content\Services\ContentRender;
use Core\Mod\Content\Services\ContentRender;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Http\Request;

View file

@ -1,10 +1,10 @@
<?php
use Core\Content\Models\ContentAuthor;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentMedia;
use Core\Content\Models\ContentTaxonomy;
use Core\Content\Models\ContentWebhookLog;
use Core\Mod\Content\Models\ContentAuthor;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentMedia;
use Core\Mod\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Models\ContentWebhookLog;
use Core\Mod\Tenant\Models\Workspace;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);

View file

@ -1,7 +1,7 @@
<?php
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentRevision;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentRevision;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);

View file

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace Core\Content\Tests\Unit;
namespace Core\Mod\Content\Tests\Unit;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Core\Content\Enums\ContentType;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentTaxonomy;
use Core\Content\Services\ContentSearchService;
use Core\Mod\Content\Enums\ContentType;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentTaxonomy;
use Core\Mod\Content\Services\ContentSearchService;
use Tests\TestCase;
/**

View file

@ -2,9 +2,9 @@
declare(strict_types=1);
namespace Core\Content\Tests\Unit;
namespace Core\Mod\Content\Tests\Unit;
use Core\Content\Models\ContentWebhookEndpoint;
use Core\Mod\Content\Models\ContentWebhookEndpoint;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

View file

@ -2,16 +2,16 @@
declare(strict_types=1);
namespace Core\Content\Tests\Unit;
namespace Core\Mod\Content\Tests\Unit;
use Core\Front\Mcp\Contracts\McpToolHandler;
use Core\Content\Mcp\Handlers\ContentCreateHandler;
use Core\Content\Mcp\Handlers\ContentDeleteHandler;
use Core\Content\Mcp\Handlers\ContentListHandler;
use Core\Content\Mcp\Handlers\ContentReadHandler;
use Core\Content\Mcp\Handlers\ContentSearchHandler;
use Core\Content\Mcp\Handlers\ContentTaxonomiesHandler;
use Core\Content\Mcp\Handlers\ContentUpdateHandler;
use Core\Mod\Content\Mcp\Handlers\ContentCreateHandler;
use Core\Mod\Content\Mcp\Handlers\ContentDeleteHandler;
use Core\Mod\Content\Mcp\Handlers\ContentListHandler;
use Core\Mod\Content\Mcp\Handlers\ContentReadHandler;
use Core\Mod\Content\Mcp\Handlers\ContentSearchHandler;
use Core\Mod\Content\Mcp\Handlers\ContentTaxonomiesHandler;
use Core\Mod\Content\Mcp\Handlers\ContentUpdateHandler;
use PHPUnit\Framework\TestCase;
/**

View file

@ -2,15 +2,15 @@
declare(strict_types=1);
namespace Core\Content\Tests\Unit;
namespace Core\Mod\Content\Tests\Unit;
use Core\Mod\Tenant\Models\Workspace;
use Illuminate\Support\Facades\Queue;
use Core\Content\Enums\ContentType;
use Core\Content\Jobs\ProcessContentWebhook;
use Core\Content\Models\ContentItem;
use Core\Content\Models\ContentWebhookEndpoint;
use Core\Content\Models\ContentWebhookLog;
use Core\Mod\Content\Enums\ContentType;
use Core\Mod\Content\Jobs\ProcessContentWebhook;
use Core\Mod\Content\Models\ContentItem;
use Core\Mod\Content\Models\ContentWebhookEndpoint;
use Core\Mod\Content\Models\ContentWebhookLog;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;