diff --git a/app/Http/Controllers/Auth/FacebookController.php b/app/Http/Controllers/Auth/FacebookController.php index 06af7f1c..9847afad 100644 --- a/app/Http/Controllers/Auth/FacebookController.php +++ b/app/Http/Controllers/Auth/FacebookController.php @@ -9,6 +9,7 @@ use App\Models\Workspace; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\View\View; @@ -193,9 +194,13 @@ public function selectPage(Request $request) return redirect()->route('app.accounts'); } + $pages = collect(data_get($oauthData, 'pages')) + ->map(fn ($page) => Arr::except($page, ['access_token'])) + ->toArray(); + return Inertia::render('accounts/FacebookPageSelect', [ 'workspace' => $workspace, - 'pages' => data_get($oauthData, 'pages'), + 'pages' => $pages, ]); } diff --git a/app/Http/Requests/App/Media/StoreChunkedMediaRequest.php b/app/Http/Requests/App/Media/StoreChunkedMediaRequest.php index 59d326ce..01ff2da6 100644 --- a/app/Http/Requests/App/Media/StoreChunkedMediaRequest.php +++ b/app/Http/Requests/App/Media/StoreChunkedMediaRequest.php @@ -40,7 +40,19 @@ public function rules(): array 'model' => ['required', 'string', Rule::in($this->allowedModels)], 'model_id' => ['required', 'string'], 'collection' => ['sometimes', 'string', 'max:255'], - 'file_name' => ['required', 'string', 'max:255'], + 'file_name' => [ + 'required', + 'string', + 'max:255', + 'regex:/\.(jpe?g|png|gif|webp|mp4)$/i', + ], + ]; + } + + public function messages(): array + { + return [ + 'file_name.regex' => 'File type not supported. Allowed: JPEG, PNG, GIF, WebP, MP4.', ]; } diff --git a/app/Http/Requests/App/Post/UpdatePostRequest.php b/app/Http/Requests/App/Post/UpdatePostRequest.php index 76f2cab0..22df881b 100644 --- a/app/Http/Requests/App/Post/UpdatePostRequest.php +++ b/app/Http/Requests/App/Post/UpdatePostRequest.php @@ -21,7 +21,15 @@ public function rules(): array return [ 'status' => ['required', 'string', Rule::in(array_column(Status::cases(), 'value'))], 'synced' => ['required', 'boolean'], - 'scheduled_at' => ['sometimes', 'nullable', 'date'], + 'scheduled_at' => [ + 'sometimes', + 'nullable', + 'date', + Rule::when( + in_array($this->input('status'), ['scheduled', 'publishing']), + ['after:now'] + ), + ], 'platforms' => ['required', 'array'], 'platforms.*.id' => ['required', 'uuid', Rule::exists('post_platforms', 'id')->where('post_id', $this->route('post')->id ?? $this->route('post'))], 'platforms.*.content' => ['nullable', 'string', 'max:63206'], diff --git a/app/Jobs/VerifyWorkspaceConnections.php b/app/Jobs/VerifyWorkspaceConnections.php index 64b4fb6e..6188c1de 100644 --- a/app/Jobs/VerifyWorkspaceConnections.php +++ b/app/Jobs/VerifyWorkspaceConnections.php @@ -72,8 +72,13 @@ private function verifyAccount(ConnectionVerifier $verifier, SocialAccount $acco ]); if ($account->status === Status::TokenExpired) { - // Second failure — escalate to Disconnected - $account->markAsDisconnected($e->getMessage()); + // Second failure — escalate to Disconnected (no individual notification, + // the batch notification from notifyOwner() handles it) + $account->update([ + 'status' => Status::Disconnected, + 'error_message' => $e->getMessage(), + 'disconnected_at' => now(), + ]); } else { // First failure — mark as TokenExpired (softer state) $account->markAsTokenExpired($e->getMessage()); diff --git a/tests/Feature/Controllers/ChunkedUploadTest.php b/tests/Feature/Controllers/ChunkedUploadTest.php index 561df38a..ddcc6a59 100644 --- a/tests/Feature/Controllers/ChunkedUploadTest.php +++ b/tests/Feature/Controllers/ChunkedUploadTest.php @@ -210,8 +210,9 @@ str_repeat('x', 1000), ); - // X-File-Name defaults to 'upload' so it should still work - $response->assertSuccessful(); + // X-File-Name defaults to 'upload' which has no extension, so it should fail validation + $response->assertUnprocessable(); + $response->assertJsonValidationErrors(['file_name']); }); test('chunked upload cleans up temp file after completion', function () {