From af4d83190eba47deacc7f729f84255145bb09005 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Wed, 10 Jun 2026 18:10:13 -0300 Subject: [PATCH] Move automation reads and delete into actions --- .../Automation/CreateAutomation.php | 4 +- .../Automation/DeleteAutomation.php | 15 ++++ .../Automation/GetAutomationDetails.php | 27 ++++++ .../Automation/GetAutomationEditorData.php | 60 ++++++++++++++ .../Automation/Automation/ListAutomations.php | 20 +++++ .../Controllers/App/AutomationController.php | 62 ++++---------- .../Automations/StoreAutomationRequest.php | 4 +- .../Automation/Automation/ReadActionsTest.php | 83 +++++++++++++++++++ .../Feature/Automation/AutomationCrudTest.php | 8 +- 9 files changed, 230 insertions(+), 53 deletions(-) create mode 100644 app/Actions/Automation/Automation/DeleteAutomation.php create mode 100644 app/Actions/Automation/Automation/GetAutomationDetails.php create mode 100644 app/Actions/Automation/Automation/GetAutomationEditorData.php create mode 100644 app/Actions/Automation/Automation/ListAutomations.php create mode 100644 tests/Feature/Automation/Automation/ReadActionsTest.php diff --git a/app/Actions/Automation/Automation/CreateAutomation.php b/app/Actions/Automation/Automation/CreateAutomation.php index ce6d6040..b779bdfa 100644 --- a/app/Actions/Automation/Automation/CreateAutomation.php +++ b/app/Actions/Automation/Automation/CreateAutomation.php @@ -11,12 +11,12 @@ class CreateAutomation { - public function __invoke(Workspace $workspace, User $user, string $name): Automation + public function __invoke(Workspace $workspace, User $user, ?string $name = null): Automation { return Automation::create([ 'workspace_id' => $workspace->id, 'user_id' => $user->id, - 'name' => $name, + 'name' => $name ?: __('automations.default_name'), 'status' => Status::Draft, 'nodes' => [], 'connections' => [], diff --git a/app/Actions/Automation/Automation/DeleteAutomation.php b/app/Actions/Automation/Automation/DeleteAutomation.php new file mode 100644 index 00000000..1d4d4719 --- /dev/null +++ b/app/Actions/Automation/Automation/DeleteAutomation.php @@ -0,0 +1,15 @@ +delete(); + } +} diff --git a/app/Actions/Automation/Automation/GetAutomationDetails.php b/app/Actions/Automation/Automation/GetAutomationDetails.php new file mode 100644 index 00000000..af4424a2 --- /dev/null +++ b/app/Actions/Automation/Automation/GetAutomationDetails.php @@ -0,0 +1,27 @@ +, + * triggerItems: Collection, + * } + */ + public function __invoke(Automation $automation): array + { + return [ + 'runs' => $automation->runs()->excludingDryRuns()->latest()->take(50)->get(), + 'triggerItems' => $automation->triggerItems()->with('run')->latest()->take(50)->get(), + ]; + } +} diff --git a/app/Actions/Automation/Automation/GetAutomationEditorData.php b/app/Actions/Automation/Automation/GetAutomationEditorData.php new file mode 100644 index 00000000..157e4ee1 --- /dev/null +++ b/app/Actions/Automation/Automation/GetAutomationEditorData.php @@ -0,0 +1,60 @@ +, + * pinterestBoards: SupportCollection>, + * tiktokCreatorInfos: SupportCollection, + * } + */ + public function __invoke(Automation $automation): array + { + $socialAccounts = $automation->workspace->socialAccounts()->active()->get(); + + $pinterestBoards = $socialAccounts + ->where('platform', Platform::Pinterest) + ->mapWithKeys(fn ($account) => [ + $account->id => rescue( + fn () => $this->pinterestPublisher->getBoards($account), + [], + report: false, + ), + ]); + + $tiktokCreatorInfos = $socialAccounts + ->where('platform', Platform::TikTok) + ->mapWithKeys(fn ($account) => [ + $account->id => rescue( + fn () => $this->tikTokCreatorInfo->fetch($account), + null, + report: false, + ), + ]) + ->filter(); + + return [ + 'socialAccounts' => $socialAccounts, + 'pinterestBoards' => $pinterestBoards, + 'tiktokCreatorInfos' => $tiktokCreatorInfos, + ]; + } +} diff --git a/app/Actions/Automation/Automation/ListAutomations.php b/app/Actions/Automation/Automation/ListAutomations.php new file mode 100644 index 00000000..8b8fff3c --- /dev/null +++ b/app/Actions/Automation/Automation/ListAutomations.php @@ -0,0 +1,20 @@ +where('workspace_id', $workspace->id) + ->orderByDesc('created_at') + ->paginate($perPage ?? (int) config('app.pagination.default')); + } +} diff --git a/app/Http/Controllers/App/AutomationController.php b/app/Http/Controllers/App/AutomationController.php index b93e861f..d3bd414b 100644 --- a/app/Http/Controllers/App/AutomationController.php +++ b/app/Http/Controllers/App/AutomationController.php @@ -6,11 +6,14 @@ use App\Actions\Automation\Automation\ActivateAutomation; use App\Actions\Automation\Automation\CreateAutomation; +use App\Actions\Automation\Automation\DeleteAutomation; +use App\Actions\Automation\Automation\GetAutomationDetails; +use App\Actions\Automation\Automation\GetAutomationEditorData; +use App\Actions\Automation\Automation\ListAutomations; use App\Actions\Automation\Automation\PauseAutomation; use App\Actions\Automation\Automation\UpdateAutomation; use App\Actions\Automation\Run\RetryRunFromNode; use App\Actions\Automation\Run\TestAutomation; -use App\Enums\SocialAccount\Platform; use App\Http\Controllers\Controller; use App\Http\Requests\App\Automations\ActivateAutomationRequest; use App\Http\Requests\App\Automations\PauseAutomationRequest; @@ -26,8 +29,6 @@ use App\Http\Resources\AutomationTriggerItemResource; use App\Models\Automation; use App\Models\AutomationRun; -use App\Services\Social\PinterestPublisher; -use App\Services\Social\TikTokCreatorInfo; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Inertia\Inertia; @@ -35,13 +36,12 @@ class AutomationController extends Controller { - public function index(): Response + public function index(ListAutomations $list): Response { + $workspace = request()->user()->currentWorkspace; + $automations = Inertia::scroll(fn () => AutomationResource::collection( - Automation::query() - ->where('workspace_id', request()->user()->current_workspace_id) - ->orderByDesc('created_at') - ->paginate(config('app.pagination.default')) + $list($workspace) )); return Inertia::render('automations/Index', [ @@ -51,52 +51,24 @@ public function index(): Response public function store(StoreAutomationRequest $request, CreateAutomation $create): RedirectResponse { - $name = $request->validated('name'); - - if (! $name || $name === 'automations.default_name') { - $name = __('automations.default_name'); - } - $automation = $create( $request->user()->currentWorkspace, $request->user(), - $name, ); return redirect()->route('app.automations.edit', $automation->id); } - public function edit(Automation $automation): Response + public function edit(Automation $automation, GetAutomationEditorData $editorData): Response { $this->authorize('update', $automation); - $socialAccounts = $automation->workspace->socialAccounts()->active()->get(); + ['socialAccounts' => $socialAccounts, 'pinterestBoards' => $pinterestBoards, 'tiktokCreatorInfos' => $tiktokCreatorInfos] = $editorData($automation); $platformConfigs = $socialAccounts->mapWithKeys(fn ($account) => [ $account->id => new PlatformConfigResource($account), ]); - $pinterestBoards = $socialAccounts - ->where('platform', Platform::Pinterest) - ->mapWithKeys(fn ($account) => [ - $account->id => rescue( - fn () => app(PinterestPublisher::class)->getBoards($account), - [], - report: false, - ), - ]); - - $tiktokCreatorInfos = $socialAccounts - ->where('platform', Platform::TikTok) - ->mapWithKeys(fn ($account) => [ - $account->id => rescue( - fn () => app(TikTokCreatorInfo::class)->fetch($account), - null, - report: false, - ), - ]) - ->filter(); - return Inertia::render('automations/Form', [ 'automation' => AutomationResource::make($automation), 'socialAccounts' => SocialAccountResource::collection($socialAccounts), @@ -106,16 +78,16 @@ public function edit(Automation $automation): Response ]); } - public function show(Automation $automation): Response + public function show(Automation $automation, GetAutomationDetails $details): Response { $this->authorize('view', $automation); + ['runs' => $runs, 'triggerItems' => $triggerItems] = $details($automation); + return Inertia::render('automations/Show', [ 'automation' => AutomationResource::make($automation), - 'runs' => AutomationRunResource::collection($automation->runs()->excludingDryRuns()->latest()->take(50)->get()), - 'triggerItems' => AutomationTriggerItemResource::collection( - $automation->triggerItems()->with('run')->latest()->take(50)->get() - ), + 'runs' => AutomationRunResource::collection($runs), + 'triggerItems' => AutomationTriggerItemResource::collection($triggerItems), ]); } @@ -128,10 +100,10 @@ public function update(UpdateAutomationRequest $request, Automation $automation, return back(); } - public function destroy(Automation $automation): RedirectResponse + public function destroy(Automation $automation, DeleteAutomation $delete): RedirectResponse { $this->authorize('delete', $automation); - $automation->delete(); + $delete($automation); session()->flash('flash.banner', __('automations.flash.deleted')); session()->flash('flash.bannerStyle', 'success'); diff --git a/app/Http/Requests/App/Automations/StoreAutomationRequest.php b/app/Http/Requests/App/Automations/StoreAutomationRequest.php index df17d028..5a80a595 100644 --- a/app/Http/Requests/App/Automations/StoreAutomationRequest.php +++ b/app/Http/Requests/App/Automations/StoreAutomationRequest.php @@ -18,8 +18,6 @@ public function authorize(): bool */ public function rules(): array { - return [ - 'name' => ['required', 'string', 'max:120'], - ]; + return []; } } diff --git a/tests/Feature/Automation/Automation/ReadActionsTest.php b/tests/Feature/Automation/Automation/ReadActionsTest.php new file mode 100644 index 00000000..731739d8 --- /dev/null +++ b/tests/Feature/Automation/Automation/ReadActionsTest.php @@ -0,0 +1,83 @@ +create(); + $other = Workspace::factory()->create(); + + $older = Automation::factory()->for($workspace)->create(['created_at' => now()->subDay()]); + $newer = Automation::factory()->for($workspace)->create(['created_at' => now()]); + Automation::factory()->for($other)->create(); + + $result = app(ListAutomations::class)($workspace); + + expect($result->total())->toBe(2); + expect($result->items()[0]->id)->toBe($newer->id); + expect($result->items()[1]->id)->toBe($older->id); +}); + +it('deletes the automation', function () { + $automation = Automation::factory()->create(); + + app(DeleteAutomation::class)($automation); + + expect(Automation::find($automation->id))->toBeNull(); +}); + +it('returns non-dry runs and trigger items, newest first', function () { + $automation = Automation::factory()->create(); + $real = AutomationRun::factory()->for($automation)->create(); + AutomationRun::factory()->for($automation)->create(['is_dry_run' => true]); + $item = AutomationTriggerItem::factory()->for($automation)->create(); + + $result = app(GetAutomationDetails::class)($automation); + + expect($result['runs'])->toHaveCount(1); + expect($result['runs']->first()->id)->toBe($real->id); + expect($result['triggerItems'])->toHaveCount(1); + expect($result['triggerItems']->first()->id)->toBe($item->id); +}); + +it('returns only active social accounts for the automation workspace', function () { + $this->mock(PinterestPublisher::class); + $this->mock(TikTokCreatorInfo::class); + + $workspace = Workspace::factory()->create(); + $automation = Automation::factory()->for($workspace)->create(); + $active = SocialAccount::factory()->for($workspace)->create(['platform' => 'instagram', 'is_active' => true]); + SocialAccount::factory()->for($workspace)->create(['platform' => 'instagram', 'is_active' => false]); + + $result = app(GetAutomationEditorData::class)($automation); + + expect($result['socialAccounts'])->toHaveCount(1); + expect($result['socialAccounts']->first()->id)->toBe($active->id); + expect($result['pinterestBoards'])->toBeEmpty(); + expect($result['tiktokCreatorInfos'])->toBeEmpty(); +}); + +it('maps pinterest boards for pinterest accounts', function () { + $this->mock(PinterestPublisher::class, fn ($mock) => $mock->shouldReceive('getBoards')->andReturn([['id' => 'b1']])); + $this->mock(TikTokCreatorInfo::class); + + $workspace = Workspace::factory()->create(); + $automation = Automation::factory()->for($workspace)->create(); + $pinterest = SocialAccount::factory()->for($workspace)->create(['platform' => Platform::Pinterest->value, 'is_active' => true]); + + $result = app(GetAutomationEditorData::class)($automation); + + expect($result['pinterestBoards']->get($pinterest->id))->toBe([['id' => 'b1']]); +}); diff --git a/tests/Feature/Automation/AutomationCrudTest.php b/tests/Feature/Automation/AutomationCrudTest.php index 64fda829..8fbd2446 100644 --- a/tests/Feature/Automation/AutomationCrudTest.php +++ b/tests/Feature/Automation/AutomationCrudTest.php @@ -16,12 +16,14 @@ $this->user->refresh(); }); -it('creates an automation via POST', function () { +it('creates an automation with the default name (web sends no name)', function () { $response = $this->actingAs($this->user) - ->post(route('app.automations.store'), ['name' => 'My RSS auto']); + ->post(route('app.automations.store')); $response->assertRedirect(); - expect(Automation::where('workspace_id', $this->workspace->id)->count())->toBe(1); + + $automation = Automation::where('workspace_id', $this->workspace->id)->sole(); + expect($automation->name)->toBe(__('automations.default_name')); }); it('updates nodes and connections via PUT', function () {