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>
32 KiB
32 KiB
TASK: Content API Backend for AI Pipeline
Status: Ready for Implementation Priority: P1 Estimated Effort: 2-3 weeks Dependencies: Mod/Content (native CMS), external HTTP clients
Overview
Build Laravel API endpoints at host.uk.com to orchestrate the AI content pipeline. This serves as the bridge between external requests, AI services, and the native CMS (Mod/Content).
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ External Requests │
│ (Webhooks, API Calls, Scheduled Jobs) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ host.uk.com Laravel API │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ Content │ │ AI Gateway │ │ Mod/Content │ │ Mod/Social │ │
│ │ Briefs │ │ (Gemini/ │ │ Native CMS │ │ Scheduler │ │
│ │ Queue │ │ Claude) │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Database │ │ Gemini/ │ │ Mod/Content │ │ Mod/Social │
│ (Briefs, │ │ Claude │ │ ContentItem │ │ Posts & │
│ Queue) │ │ APIs │ │ Model │ │ Accounts │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
Database Schema
Migration 1: Content Briefs
// database/migrations/xxxx_create_content_briefs_table.php
Schema::create('content_briefs', function (Blueprint $table) {
$table->id();
$table->string('service'); // social, link, analytics, trust, notify
$table->string('content_type'); // help_article, blog_post, landing_page
$table->string('title');
$table->string('slug')->nullable();
$table->text('description')->nullable();
$table->json('keywords')->nullable();
$table->string('category')->nullable();
$table->string('difficulty')->nullable(); // beginner, intermediate, advanced
$table->integer('target_word_count')->default(1000);
$table->json('prompt_variables')->nullable(); // Additional context
$table->string('status')->default('pending'); // pending, queued, generating, review, published
$table->integer('priority')->default(50); // 1-100, higher = more urgent
$table->timestamp('scheduled_for')->nullable();
$table->timestamps();
$table->index(['service', 'status']);
$table->index(['status', 'priority']);
});
Migration 2: Content Queue
// database/migrations/xxxx_create_content_queue_table.php
Schema::create('content_queue', function (Blueprint $table) {
$table->id();
$table->foreignId('brief_id')->constrained('content_briefs')->onDelete('cascade');
$table->string('stage'); // draft, refine, review, publish
$table->text('gemini_output')->nullable();
$table->text('claude_output')->nullable();
$table->text('final_content')->nullable();
$table->json('metadata')->nullable(); // frontmatter, seo data
$table->integer('content_item_id')->nullable();
$table->string('content_status')->nullable();
$table->json('generation_log')->nullable(); // Track AI calls, costs
$table->timestamp('generated_at')->nullable();
$table->timestamp('refined_at')->nullable();
$table->timestamp('published_at')->nullable();
$table->timestamps();
$table->index('stage');
});
Migration 3: Social Queue
// database/migrations/xxxx_create_social_queue_table.php
Schema::create('social_queue', function (Blueprint $table) {
$table->id();
$table->foreignId('content_id')->nullable()->constrained('content_queue')->onDelete('set null');
$table->string('platform'); // twitter, linkedin, instagram, facebook
$table->text('content');
$table->string('media_url')->nullable();
$table->json('metadata')->nullable();
$table->timestamp('scheduled_for');
$table->string('status')->default('pending'); // pending, scheduled, published, failed
$table->string('social_post_id')->nullable();
$table->timestamps();
$table->index(['platform', 'status']);
$table->index('scheduled_for');
});
Migration 4: AI Usage Tracking
// database/migrations/xxxx_create_ai_usage_table.php
Schema::create('ai_usage', function (Blueprint $table) {
$table->id();
$table->string('provider'); // gemini, claude, openai
$table->string('model');
$table->string('purpose'); // draft, refine, social, image
$table->integer('input_tokens')->default(0);
$table->integer('output_tokens')->default(0);
$table->decimal('cost_estimate', 10, 6)->default(0);
$table->foreignId('brief_id')->nullable()->constrained('content_briefs')->onDelete('set null');
$table->json('metadata')->nullable();
$table->timestamps();
$table->index(['provider', 'created_at']);
});
API Endpoints
Content Briefs API
// routes/api.php
Route::prefix('content')->middleware(['auth:sanctum'])->group(function () {
// Briefs
Route::get('briefs', [ContentBriefController::class, 'index']);
Route::post('briefs', [ContentBriefController::class, 'store']);
Route::get('briefs/{brief}', [ContentBriefController::class, 'show']);
Route::put('briefs/{brief}', [ContentBriefController::class, 'update']);
Route::delete('briefs/{brief}', [ContentBriefController::class, 'destroy']);
Route::post('briefs/bulk', [ContentBriefController::class, 'bulkStore']);
Route::get('briefs/next', [ContentBriefController::class, 'next']); // For external callers
// Queue
Route::get('queue', [ContentQueueController::class, 'index']);
Route::get('queue/{item}', [ContentQueueController::class, 'show']);
Route::post('queue/{item}/generate', [ContentQueueController::class, 'generate']);
Route::post('queue/{item}/refine', [ContentQueueController::class, 'refine']);
Route::post('queue/{item}/publish', [ContentQueueController::class, 'publish']);
Route::post('queue/{item}/approve', [ContentQueueController::class, 'approve']);
Route::post('queue/{item}/reject', [ContentQueueController::class, 'reject']);
// Generation (for external callers)
Route::post('generate/draft', [GenerationController::class, 'draft']);
Route::post('generate/refine', [GenerationController::class, 'refine']);
Route::post('generate/social', [GenerationController::class, 'socialPosts']);
});
Content Integration API (Mod/Content)
Route::prefix('content-items')->middleware(['auth:sanctum'])->group(function () {
Route::get('sites', [ContentController::class, 'sites']);
Route::get('items/{site}', [ContentController::class, 'items']);
Route::post('items/{site}', [ContentController::class, 'createItem']);
Route::put('items/{site}/{id}', [ContentController::class, 'updateItem']);
Route::post('items/{site}/{id}/publish', [ContentController::class, 'publishItem']);
Route::delete('items/{site}/{id}', [ContentController::class, 'deleteItem']);
Route::get('categories/{site}', [ContentController::class, 'categories']);
Route::get('media/{site}', [ContentController::class, 'media']);
});
Social Scheduling API
Route::prefix('social')->middleware(['auth:sanctum'])->group(function () {
Route::get('queue', [SocialQueueController::class, 'index']);
Route::post('queue', [SocialQueueController::class, 'store']);
Route::post('queue/bulk', [SocialQueueController::class, 'bulkStore']);
Route::put('queue/{item}', [SocialQueueController::class, 'update']);
Route::delete('queue/{item}', [SocialQueueController::class, 'destroy']);
Route::post('queue/{item}/schedule', [SocialQueueController::class, 'schedule']);
});
Analytics/Stats API
Route::prefix('stats')->middleware(['auth:sanctum'])->group(function () {
Route::get('content', [StatsController::class, 'contentStats']);
Route::get('ai-usage', [StatsController::class, 'aiUsage']);
Route::get('pipeline', [StatsController::class, 'pipelineStatus']);
});
Services
AI Gateway Service
// app/Services/AIGatewayService.php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use App\Models\AIUsage;
class AIGatewayService
{
public function __construct(
protected string $geminiApiKey,
protected string $claudeApiKey
) {}
/**
* Generate content draft using Gemini
*/
public function generateDraft(string $prompt, array $options = []): array
{
$startTime = microtime(true);
$response = Http::withHeaders([
'Content-Type' => 'application/json',
])->post("https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key={$this->geminiApiKey}", [
'contents' => [
['parts' => [['text' => $prompt]]]
],
'generationConfig' => [
'temperature' => $options['temperature'] ?? 0.7,
'maxOutputTokens' => $options['max_tokens'] ?? 4096,
]
]);
$result = $response->json();
// Log usage
$this->logUsage('gemini', 'gemini-pro', 'draft', $prompt, $result, $options['brief_id'] ?? null);
return [
'success' => $response->successful(),
'content' => $result['candidates'][0]['content']['parts'][0]['text'] ?? null,
'raw' => $result,
'duration' => microtime(true) - $startTime,
];
}
/**
* Refine content using Claude
*/
public function refineContent(string $content, string $refinementPrompt, array $options = []): array
{
$startTime = microtime(true);
$response = Http::withHeaders([
'x-api-key' => $this->claudeApiKey,
'anthropic-version' => '2024-01-01',
'Content-Type' => 'application/json',
])->post('https://api.anthropic.com/v1/messages', [
'model' => $options['model'] ?? 'claude-3-opus-20240229',
'max_tokens' => $options['max_tokens'] ?? 4096,
'messages' => [
[
'role' => 'user',
'content' => $refinementPrompt . "\n\n---\n\n" . $content
]
]
]);
$result = $response->json();
// Log usage
$this->logUsage('claude', $options['model'] ?? 'claude-3-opus', 'refine', $refinementPrompt, $result, $options['brief_id'] ?? null);
return [
'success' => $response->successful(),
'content' => $result['content'][0]['text'] ?? null,
'raw' => $result,
'duration' => microtime(true) - $startTime,
];
}
/**
* Generate social media posts from content
*/
public function generateSocialPosts(string $content, string $title, string $url): array
{
$prompt = <<<PROMPT
Generate social media posts for this content:
Title: {$title}
URL: {$url}
Content summary:
{$content}
Generate posts for:
1. Twitter (280 chars max, include URL)
2. LinkedIn (300 words, professional tone)
3. Facebook (150 words, conversational)
Return as JSON: {"twitter": "...", "linkedin": "...", "facebook": "..."}
PROMPT;
return $this->generateDraft($prompt, ['temperature' => 0.8]);
}
protected function logUsage(string $provider, string $model, string $purpose, string $input, array $result, ?int $briefId): void
{
// Estimate tokens (rough calculation)
$inputTokens = (int) (strlen($input) / 4);
$outputTokens = isset($result['content'][0]['text'])
? (int) (strlen($result['content'][0]['text']) / 4)
: (isset($result['candidates'][0]['content']['parts'][0]['text'])
? (int) (strlen($result['candidates'][0]['content']['parts'][0]['text']) / 4)
: 0);
// Cost estimates (approximate)
$costPerInputToken = match($provider) {
'gemini' => 0.000001,
'claude' => 0.000015,
default => 0.00001,
};
$costPerOutputToken = match($provider) {
'gemini' => 0.000002,
'claude' => 0.000075,
default => 0.00003,
};
AIUsage::create([
'provider' => $provider,
'model' => $model,
'purpose' => $purpose,
'input_tokens' => $inputTokens,
'output_tokens' => $outputTokens,
'cost_estimate' => ($inputTokens * $costPerInputToken) + ($outputTokens * $costPerOutputToken),
'brief_id' => $briefId,
'metadata' => [
'usage' => $result['usage'] ?? null,
],
]);
}
}
Content Client Service (Mod/Content)
// app/Mod/Content/Services/ContentClientService.php
<?php
namespace Mod\Content\Services;
use Mod\Content\Models\ContentItem;
use Mod\Content\Models\ContentSite;
use Illuminate\Support\Facades\Cache;
class ContentClientService
{
protected array $sites = [
'social' => 'social.host.uk.com',
'link' => 'link.host.uk.com',
'analytics' => 'analytics.host.uk.com',
'trust' => 'trust.host.uk.com',
'notify' => 'notify.host.uk.com',
];
public function getItems(string $site, array $params = []): array
{
$cacheKey = "content_items_{$site}_" . md5(serialize($params));
return Cache::remember($cacheKey, 300, function () use ($site, $params) {
return ContentItem::query()
->where('site', $site)
->when($params['status'] ?? null, fn($q, $s) => $q->where('status', $s))
->limit($params['per_page'] ?? 20)
->get()
->toArray();
});
}
public function createItem(string $site, array $data): array
{
$item = ContentItem::create([
'site' => $site,
'title' => $data['title'],
'content' => $data['content'],
'excerpt' => $data['excerpt'] ?? '',
'status' => $data['status'] ?? 'draft',
'categories' => $data['categories'] ?? [],
'tags' => $data['tags'] ?? [],
'meta' => $data['meta'] ?? [],
]);
// Clear cache
Cache::forget("content_items_{$site}_*");
return [
'success' => true,
'item' => $item->toArray(),
'id' => $item->id,
];
}
public function updateItem(string $site, int $itemId, array $data): array
{
$item = ContentItem::where('site', $site)->findOrFail($itemId);
$item->update($data);
Cache::forget("content_items_{$site}_*");
return [
'success' => true,
'item' => $item->fresh()->toArray(),
];
}
public function publishItem(string $site, int $itemId): array
{
return $this->updateItem($site, $itemId, ['status' => 'published']);
}
public function getCategories(string $site): array
{
return Cache::remember("content_categories_{$site}", 3600, function () use ($site) {
return ContentItem::where('site', $site)
->distinct('category')
->pluck('category')
->toArray();
});
}
}
Social Scheduler Service (Mod/Social)
// app/Mod/Social/Services/SocialSchedulerService.php
<?php
namespace Mod\Social\Services;
use Mod\Social\Models\Post;
use Mod\Social\Models\Account;
class SocialSchedulerService
{
public function schedulePost(array $data): array
{
$post = Post::create([
'content' => $data['content'],
'media' => $data['media'] ?? [],
'accounts' => $data['accounts'], // Platform account IDs
'scheduled_at' => $data['scheduled_at'],
'status' => 'scheduled',
]);
return [
'success' => true,
'post' => $post->toArray(),
'id' => $post->id,
];
}
public function getAccounts(): array
{
return Account::all()->toArray();
}
public function getScheduledPosts(array $params = []): array
{
return Post::query()
->where('status', 'scheduled')
->when($params['account_id'] ?? null, fn($q, $id) => $q->where('account_id', $id))
->orderBy('scheduled_at')
->get()
->toArray();
}
}
Controllers
Content Brief Controller
// app/Http/Controllers/Api/ContentBriefController.php
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\ContentBrief;
use App\Http\Requests\ContentBriefRequest;
use Illuminate\Http\Request;
class ContentBriefController extends Controller
{
public function index(Request $request)
{
$briefs = ContentBrief::query()
->when($request->service, fn($q, $s) => $q->where('service', $s))
->when($request->status, fn($q, $s) => $q->where('status', $s))
->when($request->type, fn($q, $t) => $q->where('content_type', $t))
->orderBy('priority', 'desc')
->orderBy('created_at', 'asc')
->paginate($request->per_page ?? 20);
return response()->json($briefs);
}
public function store(ContentBriefRequest $request)
{
$brief = ContentBrief::create($request->validated());
return response()->json($brief, 201);
}
public function bulkStore(Request $request)
{
$request->validate([
'briefs' => 'required|array',
'briefs.*.service' => 'required|string',
'briefs.*.content_type' => 'required|string',
'briefs.*.title' => 'required|string',
]);
$briefs = collect($request->briefs)->map(function ($data) {
return ContentBrief::create($data);
});
return response()->json([
'created' => $briefs->count(),
'briefs' => $briefs,
], 201);
}
/**
* Get next brief to process (for external callers)
*/
public function next(Request $request)
{
$brief = ContentBrief::query()
->where('status', 'pending')
->when($request->service, fn($q, $s) => $q->where('service', $s))
->orderBy('priority', 'desc')
->orderBy('scheduled_for', 'asc')
->orderBy('created_at', 'asc')
->first();
if (!$brief) {
return response()->json(['message' => 'No briefs pending'], 404);
}
// Mark as queued
$brief->update(['status' => 'queued']);
return response()->json($brief);
}
public function show(ContentBrief $brief)
{
return response()->json($brief->load('queueItem'));
}
public function update(ContentBriefRequest $request, ContentBrief $brief)
{
$brief->update($request->validated());
return response()->json($brief);
}
public function destroy(ContentBrief $brief)
{
$brief->delete();
return response()->json(null, 204);
}
}
Generation Controller (for external callers)
// app/Http/Controllers/Api/GenerationController.php
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\AIGatewayService;
use Mod\Content\Services\ContentClientService;
use App\Models\ContentBrief;
use App\Models\ContentQueue;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class GenerationController extends Controller
{
public function __construct(
protected AIGatewayService $ai,
protected ContentClientService $content
) {}
/**
* Generate draft content (Gemini)
*/
public function draft(Request $request)
{
$request->validate([
'brief_id' => 'required|exists:content_briefs,id',
]);
$brief = ContentBrief::findOrFail($request->brief_id);
// Load prompt template
$promptTemplate = $this->getPromptTemplate($brief->content_type);
$prompt = $this->buildPrompt($promptTemplate, $brief);
// Generate with Gemini
$result = $this->ai->generateDraft($prompt, [
'brief_id' => $brief->id,
]);
if (!$result['success']) {
return response()->json(['error' => 'Generation failed', 'details' => $result], 500);
}
// Create or update queue item
$queueItem = ContentQueue::updateOrCreate(
['brief_id' => $brief->id],
[
'stage' => 'draft',
'gemini_output' => $result['content'],
'generated_at' => now(),
'generation_log' => [
'gemini' => [
'duration' => $result['duration'],
'timestamp' => now()->toIso8601String(),
]
],
]
);
$brief->update(['status' => 'generating']);
return response()->json([
'success' => true,
'queue_item' => $queueItem,
'content' => $result['content'],
]);
}
/**
* Refine content (Claude)
*/
public function refine(Request $request)
{
$request->validate([
'queue_id' => 'required|exists:content_queue,id',
]);
$queueItem = ContentQueue::with('brief')->findOrFail($request->queue_id);
if (!$queueItem->gemini_output) {
return response()->json(['error' => 'No draft to refine'], 400);
}
// Load refinement prompt
$refinementPrompt = $this->getRefinementPrompt($queueItem->brief->content_type);
// Refine with Claude
$result = $this->ai->refineContent(
$queueItem->gemini_output,
$refinementPrompt,
['brief_id' => $queueItem->brief_id]
);
if (!$result['success']) {
return response()->json(['error' => 'Refinement failed', 'details' => $result], 500);
}
// Update queue item
$queueItem->update([
'stage' => 'review',
'claude_output' => $result['content'],
'final_content' => $result['content'],
'refined_at' => now(),
'generation_log' => array_merge($queueItem->generation_log ?? [], [
'claude' => [
'duration' => $result['duration'],
'timestamp' => now()->toIso8601String(),
]
]),
]);
$queueItem->brief->update(['status' => 'review']);
return response()->json([
'success' => true,
'queue_item' => $queueItem->fresh(),
'content' => $result['content'],
]);
}
/**
* Generate social posts for content
*/
public function socialPosts(Request $request)
{
$request->validate([
'queue_id' => 'required|exists:content_queue,id',
'url' => 'required|url',
]);
$queueItem = ContentQueue::with('brief')->findOrFail($request->queue_id);
$result = $this->ai->generateSocialPosts(
substr($queueItem->final_content, 0, 2000), // Summary
$queueItem->brief->title,
$request->url
);
if (!$result['success']) {
return response()->json(['error' => 'Social generation failed'], 500);
}
$posts = json_decode($result['content'], true);
return response()->json([
'success' => true,
'posts' => $posts,
]);
}
protected function getPromptTemplate(string $type): string
{
$templates = [
'help_article' => Storage::disk('local')->get('prompts/help-article.txt'),
'blog_post' => Storage::disk('local')->get('prompts/blog-post.txt'),
'landing_page' => Storage::disk('local')->get('prompts/landing-page.txt'),
];
return $templates[$type] ?? $templates['help_article'];
}
protected function getRefinementPrompt(string $type): string
{
return Storage::disk('local')->get('prompts/refinement.txt');
}
protected function buildPrompt(string $template, ContentBrief $brief): string
{
$variables = array_merge([
'SERVICE_NAME' => $this->getServiceName($brief->service),
'SERVICE_URL' => $this->getServiceUrl($brief->service),
'TITLE' => $brief->title,
'DESCRIPTION' => $brief->description ?? '',
'KEYWORDS' => implode(', ', $brief->keywords ?? []),
'CATEGORY' => $brief->category ?? '',
'DIFFICULTY' => $brief->difficulty ?? 'beginner',
'WORD_COUNT' => $brief->target_word_count,
], $brief->prompt_variables ?? []);
foreach ($variables as $key => $value) {
$template = str_replace('{{' . $key . '}}', $value, $template);
}
return $template;
}
protected function getServiceName(string $service): string
{
return match($service) {
'social' => 'Host Social',
'link' => 'Host Link',
'analytics' => 'Host Analytics',
'trust' => 'Host Trust',
'notify' => 'Host Notify',
default => 'Host UK',
};
}
protected function getServiceUrl(string $service): string
{
return match($service) {
'social' => 'social.host.uk.com',
'link' => 'link.host.uk.com',
'analytics' => 'analytics.host.uk.com',
'trust' => 'trust.host.uk.com',
'notify' => 'notify.host.uk.com',
default => 'host.uk.com',
};
}
}
Configuration
Environment Variables
# AI Services
GEMINI_API_KEY=your_gemini_api_key
ANTHROPIC_API_KEY=your_claude_api_key
# External Webhook Secret (for validation)
CONTENT_WEBHOOK_SECRET=your_webhook_secret
Config File
// config/content.php
return [
'ai' => [
'gemini' => [
'api_key' => env('GEMINI_API_KEY'),
'model' => 'gemini-pro',
'max_tokens' => 4096,
],
'claude' => [
'api_key' => env('ANTHROPIC_API_KEY'),
'model' => 'claude-3-opus-20240229',
'max_tokens' => 4096,
],
],
'sites' => [
'social' => 'social.host.uk.com',
'link' => 'link.host.uk.com',
'analytics' => 'analytics.host.uk.com',
'trust' => 'trust.host.uk.com',
'notify' => 'notify.host.uk.com',
],
'webhook' => [
'secret' => env('CONTENT_WEBHOOK_SECRET'),
],
'defaults' => [
'word_count' => 1000,
'priority' => 50,
],
];
Implementation Checklist
Phase 1: Core Infrastructure (Week 1)
-
Database
- Create migrations
- Create models with relationships
- Create factories for testing
-
Services
- AIGatewayService (Gemini + Claude)
- ContentClientService (Mod/Content)
- SocialSchedulerService (Mod/Social)
-
Basic API
- ContentBrief CRUD
- ContentQueue management
- Authentication (Sanctum)
Phase 2: Generation Pipeline (Week 2)
-
Generation Controller
- Draft endpoint (Gemini)
- Refine endpoint (Claude)
- Social posts endpoint
-
Prompt Management
- Store prompt templates
- Variable substitution
- Service-specific prompts
-
Content Publishing (Mod/Content)
- Create draft items
- Update existing items
- Publish workflow
Phase 3: Integration (Week 3)
-
External Webhooks
- Webhook endpoints for external callers
- Secret validation
- Response formatting
-
Social Integration (Mod/Social)
- Schedule social posts
- Account management
- Post status tracking
-
Monitoring
- AI usage tracking
- Cost reporting
- Error logging
Phase 4: Polish
-
Admin Dashboard (optional)
- Brief management UI
- Queue status view
- Analytics dashboard
-
Testing
- Unit tests for services
- Feature tests for API
- Integration tests
-
Documentation
- API documentation
- External integration examples
- Deployment guide
External Integration Endpoints Summary
| Endpoint | Method | Purpose |
|---|---|---|
/api/content/briefs/next |
GET | Get next brief to process |
/api/content/generate/draft |
POST | Generate draft with Gemini |
/api/content/generate/refine |
POST | Refine with Claude |
/api/content/generate/social |
POST | Generate social posts |
/api/content/queue/{id}/publish |
POST | Publish to Mod/Content |
/api/social/queue |
POST | Schedule via Mod/Social |
File Structure
app/
├── Http/
│ ├── Controllers/
│ │ └── Api/
│ │ ├── ContentBriefController.php
│ │ ├── ContentQueueController.php
│ │ ├── GenerationController.php
│ │ ├── SocialQueueController.php
│ │ └── StatsController.php
│ └── Requests/
│ └── ContentBriefRequest.php
├── Models/
│ ├── ContentBrief.php
│ ├── ContentQueue.php
│ ├── SocialQueue.php
│ └── AIUsage.php
├── Mod/
│ ├── Content/
│ │ └── Services/
│ │ └── ContentClientService.php
│ └── Social/
│ └── Services/
│ └── SocialSchedulerService.php
└── Services/
└── AIGatewayService.php
database/
└── migrations/
├── xxxx_create_content_briefs_table.php
├── xxxx_create_content_queue_table.php
├── xxxx_create_social_queue_table.php
└── xxxx_create_ai_usage_table.php
storage/
└── app/
└── prompts/
├── help-article.txt
├── blog-post.txt
├── landing-page.txt
└── refinement.txt
config/
└── content.php