2026-01-15 01:13:44 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
2026-01-17 17:44:37 +00:00
|
|
|
use App\Enums\Post\Status as PostStatus;
|
2026-01-17 15:36:49 +00:00
|
|
|
use App\Enums\PostPlatform\ContentType;
|
2026-01-18 16:10:01 +00:00
|
|
|
use App\Enums\SocialAccount\Platform;
|
2026-01-15 01:13:44 +00:00
|
|
|
use App\Http\Requests\StorePostRequest;
|
|
|
|
|
use App\Http\Requests\UpdatePostRequest;
|
2026-01-15 17:24:39 +00:00
|
|
|
use App\Jobs\PublishPost;
|
2026-01-15 01:13:44 +00:00
|
|
|
use App\Models\Post;
|
2026-01-18 16:10:01 +00:00
|
|
|
use App\Services\Social\PinterestPublisher;
|
2026-01-15 01:13:44 +00:00
|
|
|
use Carbon\Carbon;
|
|
|
|
|
use Illuminate\Http\RedirectResponse;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Inertia\Inertia;
|
|
|
|
|
use Inertia\Response;
|
|
|
|
|
|
|
|
|
|
class PostController extends Controller
|
|
|
|
|
{
|
2026-01-20 20:49:16 +00:00
|
|
|
public function index(Request $request, ?string $status = null): Response|RedirectResponse
|
2026-01-15 01:13:44 +00:00
|
|
|
{
|
2026-01-17 02:46:30 +00:00
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return redirect()->route('workspaces.create');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->authorize('view', $workspace);
|
|
|
|
|
|
2026-01-20 20:49:16 +00:00
|
|
|
$query = $workspace->posts()
|
2026-01-18 18:56:51 +00:00
|
|
|
->with(['postPlatforms' => function ($query) {
|
|
|
|
|
$query->where('enabled', true)->with('socialAccount');
|
2026-01-20 20:49:16 +00:00
|
|
|
}, 'user']);
|
|
|
|
|
|
|
|
|
|
// Apply status filter if provided
|
|
|
|
|
if ($status) {
|
|
|
|
|
$query = match ($status) {
|
|
|
|
|
'draft' => $query->draft(),
|
|
|
|
|
'scheduled' => $query->scheduled(),
|
|
|
|
|
'published' => $query->published(),
|
|
|
|
|
default => $query,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
|
|
|
return Inertia::render('posts/Index', [
|
|
|
|
|
'workspace' => $workspace,
|
2026-01-20 20:49:16 +00:00
|
|
|
'posts' => Inertia::scroll(fn () => $query->latest('scheduled_at')->paginate(15)),
|
|
|
|
|
'currentStatus' => $status,
|
2026-01-15 01:13:44 +00:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
public function calendar(Request $request): Response|RedirectResponse
|
2026-01-15 01:13:44 +00:00
|
|
|
{
|
2026-01-17 02:46:30 +00:00
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return redirect()->route('workspaces.create');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->authorize('view', $workspace);
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
$tz = $workspace->timezone;
|
2026-01-17 17:44:37 +00:00
|
|
|
$view = $request->input('view', 'week');
|
2026-01-15 01:13:44 +00:00
|
|
|
|
2026-01-17 17:44:37 +00:00
|
|
|
// Week view
|
2026-01-15 17:24:39 +00:00
|
|
|
$weekStart = $request->input('week')
|
|
|
|
|
? Carbon::parse($request->input('week'), $tz)->startOfWeek()
|
|
|
|
|
: Carbon::now($tz)->startOfWeek();
|
|
|
|
|
$weekEnd = $weekStart->copy()->endOfWeek();
|
|
|
|
|
|
2026-01-17 17:44:37 +00:00
|
|
|
// Month view
|
|
|
|
|
$monthDate = $request->input('month')
|
|
|
|
|
? Carbon::parse($request->input('month'), $tz)->startOfMonth()
|
|
|
|
|
: Carbon::now($tz)->startOfMonth();
|
|
|
|
|
$monthStart = $monthDate->copy()->startOfMonth()->startOfWeek();
|
|
|
|
|
$monthEnd = $monthDate->copy()->endOfMonth()->endOfWeek();
|
|
|
|
|
|
|
|
|
|
// Get posts for both views (we query the larger range to cover both)
|
|
|
|
|
$rangeStart = $view === 'month' ? $monthStart : $weekStart;
|
|
|
|
|
$rangeEnd = $view === 'month' ? $monthEnd : $weekEnd;
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
|
|
|
$posts = $workspace->posts()
|
2026-01-18 18:56:51 +00:00
|
|
|
->with(['postPlatforms' => function ($query) {
|
|
|
|
|
$query->where('enabled', true)->with('socialAccount');
|
|
|
|
|
}])
|
2026-01-17 17:44:37 +00:00
|
|
|
->whereBetween('scheduled_at', [$rangeStart->copy()->utc(), $rangeEnd->copy()->utc()])
|
2026-01-15 01:13:44 +00:00
|
|
|
->orderBy('scheduled_at')
|
|
|
|
|
->get()
|
2026-01-15 17:24:39 +00:00
|
|
|
->groupBy(fn ($post) => $post->scheduled_at?->setTimezone($tz)->format('Y-m-d'));
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
|
|
|
return Inertia::render('posts/Calendar', [
|
|
|
|
|
'workspace' => $workspace,
|
|
|
|
|
'posts' => $posts,
|
2026-01-15 17:24:39 +00:00
|
|
|
'currentWeekStart' => $weekStart->format('Y-m-d'),
|
2026-01-17 17:44:37 +00:00
|
|
|
'currentMonth' => $monthDate->format('Y-m-d'),
|
|
|
|
|
'view' => $view,
|
2026-01-15 01:13:44 +00:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
public function create(Request $request): RedirectResponse
|
2026-01-15 01:13:44 +00:00
|
|
|
{
|
2026-01-17 02:46:30 +00:00
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return redirect()->route('workspaces.create');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->authorize('view', $workspace);
|
|
|
|
|
|
|
|
|
|
$socialAccounts = $workspace->socialAccounts;
|
|
|
|
|
|
|
|
|
|
if ($socialAccounts->isEmpty()) {
|
2026-01-15 17:24:39 +00:00
|
|
|
session()->flash('flash.banner', 'Connect at least one social network before creating a post.');
|
|
|
|
|
session()->flash('flash.bannerStyle', 'danger');
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
return redirect()->route('accounts');
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
// Create a draft post - default to today if no date provided
|
|
|
|
|
$date = $request->input('date') ?: Carbon::now($workspace->timezone)->format('Y-m-d');
|
|
|
|
|
$scheduledAt = Carbon::parse($date, $workspace->timezone)
|
|
|
|
|
->setTime(9, 0)
|
|
|
|
|
->utc();
|
|
|
|
|
|
|
|
|
|
$post = $workspace->posts()->create([
|
|
|
|
|
'user_id' => $request->user()->id,
|
|
|
|
|
'status' => PostStatus::Draft,
|
2026-01-17 15:36:49 +00:00
|
|
|
'synced' => true,
|
2026-01-15 17:24:39 +00:00
|
|
|
'scheduled_at' => $scheduledAt,
|
2026-01-15 01:13:44 +00:00
|
|
|
]);
|
2026-01-15 17:24:39 +00:00
|
|
|
|
|
|
|
|
// Create post_platforms for each connected account
|
|
|
|
|
foreach ($socialAccounts as $account) {
|
|
|
|
|
$post->postPlatforms()->create([
|
|
|
|
|
'social_account_id' => $account->id,
|
|
|
|
|
'platform' => $account->platform->value,
|
|
|
|
|
'content' => '',
|
2026-01-17 15:36:49 +00:00
|
|
|
'content_type' => ContentType::defaultFor($account->platform),
|
2026-01-15 17:24:39 +00:00
|
|
|
'status' => 'pending',
|
2026-01-17 15:36:49 +00:00
|
|
|
'enabled' => true,
|
2026-01-15 17:24:39 +00:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
return redirect()->route('posts.edit', $post);
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
public function store(StorePostRequest $request): RedirectResponse
|
2026-01-15 01:13:44 +00:00
|
|
|
{
|
2026-01-17 02:46:30 +00:00
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return redirect()->route('workspaces.create');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->authorize('view', $workspace);
|
|
|
|
|
|
|
|
|
|
$post = $workspace->posts()->create([
|
|
|
|
|
'user_id' => $request->user()->id,
|
|
|
|
|
'status' => $request->input('status', PostStatus::Draft),
|
2026-01-17 15:36:49 +00:00
|
|
|
'synced' => $request->input('synced', true),
|
2026-01-15 01:13:44 +00:00
|
|
|
'scheduled_at' => $request->input('scheduled_at'),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
foreach ($request->input('platforms', []) as $platformData) {
|
|
|
|
|
$postPlatform = $post->postPlatforms()->create([
|
|
|
|
|
'social_account_id' => $platformData['social_account_id'],
|
|
|
|
|
'platform' => $platformData['platform'],
|
|
|
|
|
'content' => $platformData['content'],
|
2026-01-17 15:36:49 +00:00
|
|
|
'content_type' => $platformData['content_type'] ?? null,
|
2026-01-15 01:13:44 +00:00
|
|
|
'status' => 'pending',
|
2026-01-17 15:36:49 +00:00
|
|
|
'enabled' => $platformData['enabled'] ?? true,
|
2026-01-15 01:13:44 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (! empty($platformData['media_ids'])) {
|
|
|
|
|
$postPlatform->media()->whereIn('id', $platformData['media_ids'])->update([
|
|
|
|
|
'post_platform_id' => $postPlatform->id,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$route = $request->input('status') === PostStatus::Scheduled->value
|
2026-01-17 02:46:30 +00:00
|
|
|
? 'calendar'
|
|
|
|
|
: 'posts.index';
|
2026-01-15 01:13:44 +00:00
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
session()->flash('flash.banner', 'Post created successfully!');
|
|
|
|
|
session()->flash('flash.bannerStyle', 'success');
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
return redirect()->route($route);
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
public function show(Request $request, Post $post): Response|RedirectResponse
|
2026-01-15 01:13:44 +00:00
|
|
|
{
|
2026-01-17 02:46:30 +00:00
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return redirect()->route('workspaces.create');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->authorize('view', $workspace);
|
|
|
|
|
|
|
|
|
|
if ($post->workspace_id !== $workspace->id) {
|
|
|
|
|
abort(404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$post->load(['postPlatforms.socialAccount', 'postPlatforms.media', 'user']);
|
|
|
|
|
|
|
|
|
|
return Inertia::render('posts/Show', [
|
|
|
|
|
'workspace' => $workspace,
|
|
|
|
|
'post' => $post,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
public function edit(Request $request, Post $post): Response|RedirectResponse
|
2026-01-15 01:13:44 +00:00
|
|
|
{
|
2026-01-17 02:46:30 +00:00
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return redirect()->route('workspaces.create');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->authorize('view', $workspace);
|
|
|
|
|
|
|
|
|
|
if ($post->workspace_id !== $workspace->id) {
|
|
|
|
|
abort(404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($post->status === PostStatus::Published) {
|
2026-01-15 17:24:39 +00:00
|
|
|
session()->flash('flash.banner', 'Published posts cannot be edited.');
|
|
|
|
|
session()->flash('flash.bannerStyle', 'danger');
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
return redirect()->route('posts.show', $post);
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$post->load(['postPlatforms.socialAccount', 'postPlatforms.media']);
|
|
|
|
|
$socialAccounts = $workspace->socialAccounts;
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
$platformConfigs = $socialAccounts->mapWithKeys(function ($account) {
|
|
|
|
|
$platform = $account->platform;
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
$account->id => [
|
|
|
|
|
'maxContentLength' => $platform->maxContentLength(),
|
|
|
|
|
'maxImages' => $platform->maxImages(),
|
|
|
|
|
'allowedMediaTypes' => array_map(fn ($type) => $type->value, $platform->allowedMediaTypes()),
|
|
|
|
|
'supportsTextOnly' => $platform->supportsTextOnly(),
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-18 16:10:01 +00:00
|
|
|
// Fetch Pinterest boards if Pinterest account exists
|
|
|
|
|
$pinterestBoards = [];
|
|
|
|
|
$pinterestAccount = $socialAccounts->firstWhere('platform', Platform::Pinterest);
|
|
|
|
|
if ($pinterestAccount) {
|
|
|
|
|
try {
|
|
|
|
|
$pinterestBoards = app(PinterestPublisher::class)->getBoards($pinterestAccount);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
// Silently fail - boards will be empty
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
return Inertia::render('posts/Edit', [
|
|
|
|
|
'workspace' => $workspace,
|
|
|
|
|
'post' => $post,
|
|
|
|
|
'socialAccounts' => $socialAccounts,
|
2026-01-15 17:24:39 +00:00
|
|
|
'platformConfigs' => $platformConfigs,
|
2026-01-18 16:10:01 +00:00
|
|
|
'pinterestBoards' => $pinterestBoards,
|
2026-01-15 01:13:44 +00:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
public function update(UpdatePostRequest $request, Post $post): RedirectResponse
|
2026-01-15 01:13:44 +00:00
|
|
|
{
|
2026-01-17 02:46:30 +00:00
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return redirect()->route('workspaces.create');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->authorize('view', $workspace);
|
|
|
|
|
|
|
|
|
|
if ($post->workspace_id !== $workspace->id) {
|
|
|
|
|
abort(404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($post->status === PostStatus::Published) {
|
2026-01-15 17:24:39 +00:00
|
|
|
session()->flash('flash.banner', 'Published posts cannot be edited.');
|
|
|
|
|
session()->flash('flash.bannerStyle', 'danger');
|
|
|
|
|
|
|
|
|
|
return back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$scheduledAt = $post->scheduled_at;
|
|
|
|
|
if ($request->has('scheduled_at') && $request->input('scheduled_at')) {
|
|
|
|
|
$scheduledAt = Carbon::parse($request->input('scheduled_at'), $workspace->timezone)->utc();
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
$status = $request->input('status', $post->status);
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$post->update([
|
2026-01-15 17:24:39 +00:00
|
|
|
'status' => $status === 'publishing' ? PostStatus::Publishing : $status,
|
2026-01-17 15:36:49 +00:00
|
|
|
'synced' => $request->input('synced', $post->synced),
|
2026-01-15 17:24:39 +00:00
|
|
|
'scheduled_at' => $scheduledAt,
|
2026-01-15 01:13:44 +00:00
|
|
|
]);
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
// Get selected platform IDs
|
|
|
|
|
$selectedPlatformIds = collect($request->input('platforms', []))->pluck('id')->toArray();
|
|
|
|
|
|
|
|
|
|
// Update all platforms - disable those not selected, update content for selected ones
|
|
|
|
|
$post->postPlatforms()->update(['enabled' => false]);
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
foreach ($request->input('platforms', []) as $platformData) {
|
2026-01-18 16:10:01 +00:00
|
|
|
$updateData = [
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
'content' => $platformData['content'],
|
|
|
|
|
'content_type' => $platformData['content_type'] ?? null,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (isset($platformData['meta'])) {
|
|
|
|
|
$postPlatform = $post->postPlatforms()->where('id', $platformData['id'])->first();
|
|
|
|
|
$updateData['meta'] = array_merge($postPlatform->meta ?? [], $platformData['meta']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$post->postPlatforms()
|
|
|
|
|
->where('id', $platformData['id'])
|
2026-01-18 16:10:01 +00:00
|
|
|
->update($updateData);
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
// Dispatch publish job if publishing now
|
|
|
|
|
if ($status === 'publishing') {
|
|
|
|
|
PublishPost::dispatch($post);
|
|
|
|
|
|
|
|
|
|
session()->flash('flash.banner', 'Post is being published!');
|
|
|
|
|
session()->flash('flash.bannerStyle', 'success');
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
return redirect()->route('posts.show', $post);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-17 15:36:49 +00:00
|
|
|
// Redirect to show page for schedule action
|
|
|
|
|
if ($status === 'scheduled') {
|
|
|
|
|
session()->flash('flash.banner', 'Post scheduled successfully!');
|
|
|
|
|
session()->flash('flash.bannerStyle', 'success');
|
|
|
|
|
|
|
|
|
|
return redirect()->route('posts.show', $post);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stay on edit page for save action
|
|
|
|
|
session()->flash('flash.banner', 'Post saved!');
|
2026-01-15 17:24:39 +00:00
|
|
|
session()->flash('flash.bannerStyle', 'success');
|
|
|
|
|
|
2026-01-17 15:36:49 +00:00
|
|
|
return back();
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
public function destroy(Request $request, Post $post): RedirectResponse
|
2026-01-15 01:13:44 +00:00
|
|
|
{
|
2026-01-17 02:46:30 +00:00
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
|
|
|
|
return redirect()->route('workspaces.create');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->authorize('view', $workspace);
|
|
|
|
|
|
|
|
|
|
if ($post->workspace_id !== $workspace->id) {
|
|
|
|
|
abort(404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$post->delete();
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
session()->flash('flash.banner', 'Post deleted successfully!');
|
|
|
|
|
session()->flash('flash.bannerStyle', 'success');
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
return redirect()->route('calendar');
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
}
|