257 lines
8.7 KiB
PHP
257 lines
8.7 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Http\Controllers\Api;
|
||
|
|
|
||
|
|
use App\Http\Controllers\Controller;
|
||
|
|
use App\Jobs\PublishPostJob;
|
||
|
|
use App\Models\Post;
|
||
|
|
use App\Models\Site;
|
||
|
|
use App\Models\SocialAccount;
|
||
|
|
use Illuminate\Http\Request;
|
||
|
|
use Illuminate\Support\Facades\Validator;
|
||
|
|
|
||
|
|
class WebhookController extends Controller
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Create a new post via webhook.
|
||
|
|
*
|
||
|
|
* POST /api/webhooks/posts
|
||
|
|
*
|
||
|
|
* Required headers:
|
||
|
|
* - X-Muninn-Token: Your webhook token (from .env)
|
||
|
|
*
|
||
|
|
* Request body:
|
||
|
|
* {
|
||
|
|
* "site_name": "My Blog", // Site name to find the site
|
||
|
|
* "type": "blog", // Post type (required)
|
||
|
|
* "title": "Post Title", // Optional title
|
||
|
|
* "body": "Post content", // Required
|
||
|
|
* "target": "subreddit-name", // Optional target (e.g., subreddit)
|
||
|
|
* "image_url": "https://...", // Optional image URL
|
||
|
|
* "status": "draft", // Optional status (defaults to draft)
|
||
|
|
* "scheduled_at": "2026-07-28...", // Optional scheduled time
|
||
|
|
* "social_accounts": ["account1", "account2"] // Optional array of social account usernames
|
||
|
|
* }
|
||
|
|
*/
|
||
|
|
public function createPost(Request $request)
|
||
|
|
{
|
||
|
|
// Verify webhook token
|
||
|
|
$validToken = config('services.muninn.webhook_token');
|
||
|
|
$providedToken = $request->header('X-Muninn-Token');
|
||
|
|
|
||
|
|
if ($validToken && $providedToken !== $validToken) {
|
||
|
|
return response()->json([
|
||
|
|
'success' => false,
|
||
|
|
'error' => 'Invalid or missing webhook token',
|
||
|
|
], 401);
|
||
|
|
}
|
||
|
|
|
||
|
|
$validator = Validator::make($request->all(), [
|
||
|
|
'type' => ['required', 'string', 'in:' . implode(',', Post::TYPES)],
|
||
|
|
'body' => ['required', 'string'],
|
||
|
|
'title' => ['nullable', 'string', 'max:255'],
|
||
|
|
'target' => ['nullable', 'string', 'max:255'],
|
||
|
|
'image_url' => ['nullable', 'url', 'max:2048'],
|
||
|
|
'status' => ['nullable', 'string', 'in:' . implode(',', Post::STATUSES)],
|
||
|
|
'scheduled_at' => ['nullable', 'date'],
|
||
|
|
'site_name' => ['nullable', 'string'],
|
||
|
|
'social_accounts' => ['nullable', 'array'],
|
||
|
|
'social_accounts.*' => ['string'],
|
||
|
|
]);
|
||
|
|
|
||
|
|
if ($validator->fails()) {
|
||
|
|
return response()->json([
|
||
|
|
'success' => false,
|
||
|
|
'error' => 'Validation failed',
|
||
|
|
'errors' => $validator->errors(),
|
||
|
|
], 422);
|
||
|
|
}
|
||
|
|
|
||
|
|
$validated = $validator->validated();
|
||
|
|
|
||
|
|
// Find site by name if provided
|
||
|
|
$siteId = null;
|
||
|
|
if ($validated['site_name'] ?? null) {
|
||
|
|
$site = Site::where('name', $validated['site_name'])->first();
|
||
|
|
if ($site) {
|
||
|
|
$siteId = $site->id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create the post
|
||
|
|
$post = Post::create([
|
||
|
|
'site_id' => $siteId,
|
||
|
|
'type' => $validated['type'],
|
||
|
|
'title' => $validated['title'] ?? null,
|
||
|
|
'body' => $validated['body'],
|
||
|
|
'target' => $validated['target'] ?? null,
|
||
|
|
'image_url' => $validated['image_url'] ?? null,
|
||
|
|
'status' => $validated['status'] ?? 'draft',
|
||
|
|
'scheduled_at' => $validated['scheduled_at'] ?? null,
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Link social accounts by username if provided
|
||
|
|
if ($validated['social_accounts'] ?? null) {
|
||
|
|
foreach ($validated['social_accounts'] as $username) {
|
||
|
|
$account = SocialAccount::where('username', $username)->first();
|
||
|
|
if ($account) {
|
||
|
|
$post->socialAccounts()->attach($account->id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'post' => [
|
||
|
|
'id' => $post->id,
|
||
|
|
'type' => $post->type,
|
||
|
|
'status' => $post->status,
|
||
|
|
'created_at' => $post->created_at->toIso8601String(),
|
||
|
|
],
|
||
|
|
], 201);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Trigger publishing for a post via webhook.
|
||
|
|
*
|
||
|
|
* POST /api/webhooks/posts/{post}/publish
|
||
|
|
*
|
||
|
|
* Required headers:
|
||
|
|
* - X-Muninn-Token: Your webhook token
|
||
|
|
*/
|
||
|
|
public function publishPost(Request $request, Post $post)
|
||
|
|
{
|
||
|
|
// Verify webhook token
|
||
|
|
$validToken = config('services.muninn.webhook_token');
|
||
|
|
$providedToken = $request->header('X-Muninn-Token');
|
||
|
|
|
||
|
|
if ($validToken && $providedToken !== $validToken) {
|
||
|
|
return response()->json([
|
||
|
|
'success' => false,
|
||
|
|
'error' => 'Invalid or missing webhook token',
|
||
|
|
], 401);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if post can be published
|
||
|
|
$publisherManager = app(\App\Support\Publishing\PublisherManager::class);
|
||
|
|
|
||
|
|
if (! $publisherManager->resolve($post)) {
|
||
|
|
return response()->json([
|
||
|
|
'success' => false,
|
||
|
|
'error' => "No publisher available for post type '{$post->type}'",
|
||
|
|
], 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set status to publishing and dispatch job
|
||
|
|
$post->update(['status' => 'publishing', 'publish_error' => null]);
|
||
|
|
PublishPostJob::dispatch($post->id);
|
||
|
|
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'message' => 'Publish job dispatched',
|
||
|
|
'post_id' => $post->id,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get post status via webhook.
|
||
|
|
*
|
||
|
|
* GET /api/webhooks/posts/{post}/status
|
||
|
|
*
|
||
|
|
* Required headers:
|
||
|
|
* - X-Muninn-Token: Your webhook token
|
||
|
|
*/
|
||
|
|
public function getPostStatus(Request $request, Post $post)
|
||
|
|
{
|
||
|
|
// Verify webhook token
|
||
|
|
$validToken = config('services.muninn.webhook_token');
|
||
|
|
$providedToken = $request->header('X-Muninn-Token');
|
||
|
|
|
||
|
|
if ($validToken && $providedToken !== $validToken) {
|
||
|
|
return response()->json([
|
||
|
|
'success' => false,
|
||
|
|
'error' => 'Invalid or missing webhook token',
|
||
|
|
], 401);
|
||
|
|
}
|
||
|
|
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'post' => [
|
||
|
|
'id' => $post->id,
|
||
|
|
'type' => $post->type,
|
||
|
|
'status' => $post->status,
|
||
|
|
'title' => $post->title,
|
||
|
|
'published_url' => $post->published_url,
|
||
|
|
'publish_error' => $post->publish_error,
|
||
|
|
'created_at' => $post->created_at->toIso8601String(),
|
||
|
|
'updated_at' => $post->updated_at->toIso8601String(),
|
||
|
|
'scheduled_at' => $post->scheduled_at?->toIso8601String(),
|
||
|
|
],
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* List posts with optional filters via webhook.
|
||
|
|
*
|
||
|
|
* GET /api/webhooks/posts
|
||
|
|
*
|
||
|
|
* Query params:
|
||
|
|
* - type: Filter by post type
|
||
|
|
* - status: Filter by status
|
||
|
|
* - site_id: Filter by site
|
||
|
|
* - limit: Limit number of results (default 20)
|
||
|
|
*
|
||
|
|
* Required headers:
|
||
|
|
* - X-Muninn-Token: Your webhook token
|
||
|
|
*/
|
||
|
|
public function listPosts(Request $request)
|
||
|
|
{
|
||
|
|
// Verify webhook token
|
||
|
|
$validToken = config('services.muninn.webhook_token');
|
||
|
|
$providedToken = $request->header('X-Muninn-Token');
|
||
|
|
|
||
|
|
if ($validToken && $providedToken !== $validToken) {
|
||
|
|
return response()->json([
|
||
|
|
'success' => false,
|
||
|
|
'error' => 'Invalid or missing webhook token',
|
||
|
|
], 401);
|
||
|
|
}
|
||
|
|
|
||
|
|
$query = Post::with(['site', 'socialAccounts'])->orderByDesc('created_at');
|
||
|
|
|
||
|
|
if ($request->has('type')) {
|
||
|
|
$query->where('type', $request->input('type'));
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($request->has('status')) {
|
||
|
|
$query->where('status', $request->input('status'));
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($request->has('site_id')) {
|
||
|
|
$query->where('site_id', $request->input('site_id'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$limit = $request->input('limit', 20);
|
||
|
|
$posts = $query->take($limit)->get();
|
||
|
|
|
||
|
|
return response()->json([
|
||
|
|
'success' => true,
|
||
|
|
'count' => $posts->count(),
|
||
|
|
'posts' => $posts->map(fn ($post) => [
|
||
|
|
'id' => $post->id,
|
||
|
|
'type' => $post->type,
|
||
|
|
'status' => $post->status,
|
||
|
|
'title' => $post->title,
|
||
|
|
'body' => $post->body,
|
||
|
|
'published_url' => $post->published_url,
|
||
|
|
'publish_error' => $post->publish_error,
|
||
|
|
'site' => $post->site ? ['id' => $post->site->id, 'name' => $post->site->name] : null,
|
||
|
|
'social_accounts' => $post->socialAccounts->map(fn ($sa) => ['id' => $sa->id, 'username' => $sa->username, 'network' => $sa->network]),
|
||
|
|
'created_at' => $post->created_at->toIso8601String(),
|
||
|
|
'updated_at' => $post->updated_at->toIso8601String(),
|
||
|
|
'scheduled_at' => $post->scheduled_at?->toIso8601String(),
|
||
|
|
]),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|