Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.34% covered (success)
94.34%
50 / 53
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
WorkflowApiController
94.23% covered (success)
94.23%
49 / 52
50.00% covered (danger)
50.00%
2 / 4
14.04
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 actionGetGraph
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 actionSaveGraph
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
5.06
 actionExecuteManual
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
5
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Automation\Presentation\Api;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Domain\Model\PermissionContext;
12use App\Modules\Automation\Application\Service\WorkflowDagEngine;
13use App\Modules\Automation\Domain\Repository\WorkflowRepositoryInterface;
14use App\Shared\Infrastructure\Http\ApiResponseTrait;
15use Nyholm\Psr7\Factory\Psr17Factory;
16use Psr\Http\Message\ResponseInterface;
17use Psr\Http\Message\ServerRequestInterface;
18
19/**
20 * Workflow Graph REST API Controller.
21 *
22 * Endpoints:
23 * - GET  /api/v1/workflows/{id}/graph          -> Returns raw graph and metadata
24 * - PUT  /api/v1/workflows/{id}/graph          -> Persists updated Drawflow canvas graph
25 * - POST /api/v1/workflows/{id}/execute-manual -> Executes manual workflow for a record
26 *
27 * @package App\Modules\Automation\Presentation\Api
28 */
29final readonly class WorkflowApiController
30{
31    use ApiResponseTrait;
32
33    private const string ERROR_WORKFLOW_NOT_FOUND = 'Workflow not found.';
34
35    /**
36     * WorkflowApiController constructor.
37     *
38     * @param WorkflowRepositoryInterface $workflowRepo Workflow repository.
39     * @param WorkflowDagEngine           $dagEngine    DAG execution engine.
40     * @param Psr17Factory                $psr17        PSR-17 response factory.
41     */
42    public function __construct(
43        private WorkflowRepositoryInterface $workflowRepo,
44        private WorkflowDagEngine           $dagEngine,
45        private Psr17Factory                $psr17
46    ) {
47    }
48
49    /**
50     * Returns graph JSON definition for a workflow.
51     *
52     * @param int $id Primary key.
53     * @return ResponseInterface JSON response.
54     */
55    public function actionGetGraph(int $id): ResponseInterface
56    {
57        $workflow = $this->workflowRepo->findById($id);
58        if ($workflow === null) {
59            return $this->jsonError($this->psr17, self::ERROR_WORKFLOW_NOT_FOUND, 404);
60        }
61
62        $graph = $workflow->graphData !== null ? json_decode($workflow->graphData, true) : [];
63
64        return $this->jsonSuccess($this->psr17, [
65            'id'             => $workflow->id,
66            'name'           => $workflow->name,
67            'target_module'  => $workflow->targetModule,
68            'trigger_type'   => $workflow->triggerType,
69            'graph_data'     => $graph,
70            'compiled_flow'  => $workflow->compiledFlow,
71            'total_runs'     => $workflow->totalRuns,
72            'last_run_at'    => $workflow->lastRunAt,
73        ]);
74    }
75
76    /**
77     * Saves updated graph JSON from Drawflow.
78     *
79     * @param ServerRequestInterface $request PSR-7 request.
80     * @param int                    $id      Workflow ID.
81     * @return ResponseInterface JSON response.
82     */
83    public function actionSaveGraph(ServerRequestInterface $request, int $id): ResponseInterface
84    {
85        $workflow = $this->workflowRepo->findById($id);
86        if ($workflow === null) {
87            return $this->jsonError($this->psr17, self::ERROR_WORKFLOW_NOT_FOUND, 404);
88        }
89
90        $body = (string) $request->getBody();
91        $payload = json_decode($body, true);
92        if (!is_array($payload) || !isset($payload['graph_data'])) {
93            return $this->jsonError($this->psr17, 'Missing graph_data payload.', 422);
94        }
95
96        $rawGraph = is_string($payload['graph_data'])
97            ? $payload['graph_data']
98            : json_encode($payload['graph_data']);
99
100        $this->workflowRepo->saveGraph($id, $rawGraph, $rawGraph);
101
102        return $this->jsonSuccess($this->psr17, [
103            'message' => 'Workflow graph persisted successfully.',
104            'id'      => $id,
105        ]);
106    }
107
108    /**
109     * Manually triggers execution of a workflow for a specific record.
110     *
111     * @param ServerRequestInterface $request PSR-7 request.
112     * @param int                    $id      Workflow ID.
113     * @param PermissionContext      $context Security context.
114     * @return ResponseInterface JSON response.
115     */
116    public function actionExecuteManual(
117        ServerRequestInterface $request,
118        int                    $id,
119        PermissionContext      $context
120    ): ResponseInterface {
121        $workflow = $this->workflowRepo->findById($id);
122        if ($workflow === null) {
123            return $this->jsonError($this->psr17, self::ERROR_WORKFLOW_NOT_FOUND, 404);
124        }
125
126        $body = (string) $request->getBody();
127        $payload = json_decode($body, true) ?? [];
128        $recordId = isset($payload['record_id']) ? (int) $payload['record_id'] : null;
129        $recordData = isset($payload['record_data']) && is_array($payload['record_data'])
130            ? $payload['record_data']
131            : [];
132
133        $this->dagEngine->executeAfterHook(
134            $workflow->targetModule,
135            'manual',
136            $recordId ?? 0,
137            $recordData,
138            null,
139            $context
140        );
141
142        return $this->jsonSuccess($this->psr17, [
143            'message'     => 'Manual workflow execution completed.',
144            'workflow_id' => $id,
145            'record_id'   => $recordId,
146        ]);
147    }
148}