teknolis / app /Http /Controllers /SocialAutomationController.php.bak-20260329-media-lab
OpenAI Codex
Fix storage symlink and adapt cron status for Spaces
1501522
Raw
History Blame Contribute Delete
13.5 kB
<?php
namespace App\Http\Controllers;
use App\Models\SocialAutomationSetting;
use App\Models\SocialPost;
use App\Services\SocialContentGeneratorService;
use App\Services\SocialAutomationHealthService;
use App\Services\SocialPublisherService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\View\View;
class SocialAutomationController extends Controller
{
public function index(Request $request): View
{
$user = $request->user();
$setting = $this->settingFor($user->id);
$credentials = $user->socialPlatformCredentials()
->get()
->keyBy('platform');
$posts = $user->socialPosts()
->latest()
->paginate(12)
->withQueryString();
$stats = [
'scheduled' => $user->socialPosts()->where('status', 'scheduled')->count(),
'posted' => $user->socialPosts()->where('status', 'posted')->count(),
'failed' => $user->socialPosts()->where('status', 'failed')->count(),
'need_approval' => $user->socialPosts()->where('requires_approval', true)->where('status', 'draft')->count(),
];
return view('social.index', [
'credentials' => $credentials,
'posts' => $posts,
'stats' => $stats,
'platforms' => $this->platforms(),
'setting' => $setting,
]);
}
public function credentialsIndex(Request $request, SocialAutomationHealthService $health): View
{
$user = $request->user();
$platforms = $this->platforms();
return view('social.credentials', [
'credentials' => $user->socialPlatformCredentials()
->get()
->keyBy('platform'),
'cronStatus' => $health->cronStatus(),
'platformProgress' => $health->platformProgress($user, $platforms),
'platforms' => $platforms,
]);
}
public function generate(Request $request, SocialContentGeneratorService $generator): RedirectResponse
{
$data = $request->validate([
'topic' => ['required', 'string', 'max:350'],
'tone' => ['nullable', 'string', 'max:100'],
'platform_hint' => ['nullable', 'string', 'max:100'],
]);
try {
$result = $generator->generate(
$data['topic'],
$data['platform_hint'] ?? 'facebook, instagram, tiktok, x',
$data['tone'] ?? 'friendly-professional'
);
} catch (\Throwable $e) {
return redirect()->route('social.index')
->with('social_error', $e->getMessage())
->withInput();
}
return redirect()->route('social.index')->with('social_generated', $result);
}
public function store(Request $request): RedirectResponse
{
$data = $request->validate([
'platforms' => ['required', 'array', 'min:1'],
'platforms.*' => ['required', 'in:facebook,instagram,tiktok,x'],
'title' => ['nullable', 'string', 'max:150'],
'caption' => ['required', 'string', 'max:2000'],
'hashtags' => ['nullable', 'string', 'max:500'],
'image_url' => ['nullable', 'url', 'max:2048'],
'video_url' => ['nullable', 'url', 'max:2048'],
'scheduled_at' => ['required', 'date'],
'status' => ['required', 'in:draft,scheduled'],
]);
$hashtags = collect(explode(',', (string) ($data['hashtags'] ?? '')))
->map(fn ($item) => trim($item))
->filter()
->map(fn ($item) => ltrim($item, '#'))
->values()
->all();
$setting = $this->settingFor($request->user()->id);
$scheduledAt = Carbon::parse($data['scheduled_at']);
if (in_array('instagram', $data['platforms'], true) && blank($data['image_url'] ?? null)) {
return redirect()->route('social.index')
->with('social_error', 'Instagram membutuhkan image_url publik sebelum post bisa dibuat.')
->withInput();
}
if (in_array('tiktok', $data['platforms'], true) && blank($data['video_url'] ?? null)) {
return redirect()->route('social.index')
->with('social_error', 'TikTok membutuhkan video_url publik sebelum post bisa dibuat.')
->withInput();
}
foreach ($data['platforms'] as $platform) {
$requiresApproval = ($data['status'] === 'scheduled') && ($setting->approval_mode === 'manual');
$status = $requiresApproval ? 'draft' : $data['status'];
$request->user()->socialPosts()->create([
'platform' => $platform,
'status' => $status,
'requires_approval' => $requiresApproval,
'title' => $data['title'] ?? null,
'caption' => $data['caption'],
'hashtags' => $hashtags,
'image_url' => $data['image_url'] ?? null,
'video_url' => $data['video_url'] ?? null,
'scheduled_at' => $scheduledAt,
'approved_at' => $requiresApproval ? null : now(),
]);
}
return redirect()->route('social.index')->with('social_success', 'Post berhasil ditambahkan ke antrian.');
}
public function saveCredential(Request $request): RedirectResponse
{
$data = $request->validate([
'platform' => ['required', 'in:facebook,instagram,tiktok,x'],
'account_label' => ['nullable', 'string', 'max:120'],
'account_id' => ['nullable', 'string', 'max:190'],
'access_token' => ['nullable', 'string', 'max:4000'],
'meta_client_id' => ['nullable', 'string', 'max:200'],
'meta_client_secret' => ['nullable', 'string', 'max:200'],
]);
$existing = $request->user()->socialPlatformCredentials()
->where('platform', $data['platform'])
->first();
$existingMeta = $existing?->meta ?? [];
$meta = [
'client_id' => filled($data['meta_client_id'] ?? null)
? $data['meta_client_id']
: data_get($existingMeta, 'client_id'),
'client_secret' => filled($data['meta_client_secret'] ?? null)
? $data['meta_client_secret']
: data_get($existingMeta, 'client_secret'),
];
$request->user()->socialPlatformCredentials()->updateOrCreate(
['platform' => $data['platform']],
[
'account_label' => $data['account_label'] ?? null,
'account_id' => $data['account_id'] ?? null,
'access_token' => filled($data['access_token'] ?? null)
? $data['access_token']
: ($existing?->access_token),
'meta' => $meta,
]
);
return redirect()
->to(route('social.credentials.index').'#platform-'.$data['platform'])
->with('social_success', 'Credential platform tersimpan.');
}
public function saveSettings(Request $request): RedirectResponse
{
$data = $request->validate([
'approval_mode' => ['required', 'in:manual,auto'],
'daily_limit' => ['required', 'integer', 'min:1', 'max:500'],
'facebook_daily_limit' => ['required', 'integer', 'min:0', 'max:500'],
'instagram_daily_limit' => ['required', 'integer', 'min:0', 'max:500'],
'tiktok_daily_limit' => ['required', 'integer', 'min:0', 'max:500'],
'x_daily_limit' => ['required', 'integer', 'min:0', 'max:500'],
'max_retries' => ['required', 'integer', 'min:0', 'max:10'],
'retry_delay_minutes' => ['required', 'integer', 'min:1', 'max:1440'],
]);
$platformDailyLimits = [
'facebook' => (int) $data['facebook_daily_limit'],
'instagram' => (int) $data['instagram_daily_limit'],
'tiktok' => (int) $data['tiktok_daily_limit'],
'x' => (int) $data['x_daily_limit'],
];
SocialAutomationSetting::query()->updateOrCreate(
['user_id' => $request->user()->id],
[
'approval_mode' => $data['approval_mode'],
'daily_limit' => (int) $data['daily_limit'],
'platform_daily_limits' => $platformDailyLimits,
'max_retries' => (int) $data['max_retries'],
'retry_delay_minutes' => (int) $data['retry_delay_minutes'],
]
);
return redirect()->route('social.index')->with('social_success', 'Pengaturan automation berhasil disimpan.');
}
public function approvePost(Request $request, SocialPost $post): RedirectResponse
{
abort_unless($post->user_id === $request->user()->id, 403);
if (! $post->requires_approval) {
return redirect()->route('social.index')->with('social_error', 'Post ini tidak membutuhkan approval.');
}
$post->update([
'requires_approval' => false,
'status' => 'scheduled',
'approved_at' => now(),
'error_message' => null,
]);
return redirect()->route('social.index')->with('social_success', 'Post sudah di-approve dan masuk antrian scheduled.');
}
public function publishNow(Request $request, SocialPost $post, SocialPublisherService $publisher): RedirectResponse
{
abort_unless($post->user_id === $request->user()->id, 403);
if ($post->status === 'posted') {
return redirect()->route('social.index')->with('social_error', 'Post ini sudah pernah dipublish.');
}
if ($post->requires_approval) {
return redirect()->route('social.index')->with('social_error', 'Post masih menunggu approval.');
}
[$allowed, $limitMessage] = $this->canPublishNow($request->user()->id, $post->platform);
if (! $allowed) {
return redirect()->route('social.index')->with('social_error', $limitMessage);
}
$credential = $request->user()->socialPlatformCredentials()
->where('platform', $post->platform)
->first();
if (! $credential) {
return redirect()->route('social.index')->with('social_error', 'Credential untuk platform '.$post->platform.' belum diisi.');
}
try {
$result = $publisher->publish($post, $credential);
$post->update([
'status' => 'posted',
'posted_at' => now(),
'approved_at' => $post->approved_at ?? now(),
'external_post_id' => $result['external_post_id'] ?? null,
'error_message' => null,
'meta' => $result['payload'] ?? null,
'next_retry_at' => null,
]);
return redirect()->route('social.index')->with('social_success', 'Post berhasil dipublish ke '.$post->platform.'.');
} catch (\Throwable $e) {
$post->update([
'status' => 'failed',
'attempt_count' => $post->attempt_count + 1,
'error_message' => $e->getMessage(),
'next_retry_at' => null,
]);
return redirect()->route('social.index')->with('social_error', $e->getMessage());
}
}
private function settingFor(int $userId): SocialAutomationSetting
{
return SocialAutomationSetting::query()->firstOrCreate(
['user_id' => $userId],
[
'approval_mode' => 'manual',
'daily_limit' => 20,
'platform_daily_limits' => [
'facebook' => 8,
'instagram' => 8,
'tiktok' => 4,
'x' => 12,
],
'max_retries' => 2,
'retry_delay_minutes' => 15,
]
);
}
private function canPublishNow(int $userId, string $platform): array
{
$setting = $this->settingFor($userId);
$today = now()->toDateString();
$postedToday = SocialPost::query()
->where('user_id', $userId)
->where('status', 'posted')
->whereDate('posted_at', $today)
->count();
if ($postedToday >= $setting->daily_limit) {
return [false, 'Batas harian total post sudah tercapai.'];
}
$platformLimits = $setting->platform_daily_limits ?? [];
$platformLimit = (int) ($platformLimits[$platform] ?? 0);
if ($platformLimit > 0) {
$postedPerPlatform = SocialPost::query()
->where('user_id', $userId)
->where('platform', $platform)
->where('status', 'posted')
->whereDate('posted_at', $today)
->count();
if ($postedPerPlatform >= $platformLimit) {
return [false, 'Batas harian platform '.$platform.' sudah tercapai.'];
}
}
return [true, null];
}
private function platforms(): array
{
return [
'facebook' => 'Facebook Page',
'instagram' => 'Instagram Business',
'tiktok' => 'TikTok',
'x' => 'X / Twitter',
];
}
}