Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.55% covered (warning)
89.55%
60 / 67
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SettingsApiController
89.39% covered (warning)
89.39%
59 / 66
80.00% covered (warning)
80.00%
4 / 5
15.27
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
 list
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 get
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 update
78.12% covered (warning)
78.12%
25 / 32
0.00% covered (danger)
0.00%
0 / 1
4.17
 getSettingFromDb
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
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\Core\Settings\Presentation\Api;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Shared\Infrastructure\Http\ApiResponseTrait;
12use PDO;
13use Psr\Http\Message\ResponseFactoryInterface;
14use Psr\Http\Message\ResponseInterface;
15use Psr\Http\Message\ServerRequestInterface;
16use Throwable;
17
18/**
19 * REST API Controller for Global System Settings.
20 *
21 * Exposes /api/v1/settings and /api/v1/settings/{key} endpoints for dynamic parameter access.
22 *
23 * @package App\Core\Settings\Presentation\Api
24 */
25final readonly class SettingsApiController
26{
27    use ApiResponseTrait;
28
29    /**
30     * SettingsApiController constructor.
31     *
32     * @param ResponseFactoryInterface $responseFactory PSR-7 Response factory.
33     * @param PDO                      $pdo             PDO database connection instance.
34     * @param string                   $tablePrefix     Database table prefix.
35     */
36    public function __construct(
37        private ResponseFactoryInterface $responseFactory,
38        private PDO                      $pdo,
39        private string                   $tablePrefix = 'a_',
40        private ?PDO                     $clientPdo = null
41    ) {
42    }
43
44    /**
45     * Handles /api/v1/settings list request.
46     *
47     * @return ResponseInterface JSON API response containing all system settings.
48     */
49    public function list(): ResponseInterface
50    {
51        $tableName = $this->tablePrefix . 'core_settings_records';
52        $sql = sprintf('SELECT `setting_key`, `setting_value` FROM `%s`', $tableName);
53
54        $settings = [];
55        try {
56            $stmt = $this->pdo->query($sql);
57            if ($stmt !== false) {
58                while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
59                    $key = (string) $row['setting_key'];
60                    $settings[$key] = (string) $row['setting_value'];
61                }
62            }
63        } catch (Throwable) {
64            $settings = [];
65        }
66
67        return $this->buildJsonResponse($this->responseFactory, [
68            'status' => true,
69            'data'   => $settings,
70        ]);
71    }
72
73    /**
74     * Handles GET /api/v1/settings/{key} request.
75     *
76     * @param string $key Setting key name.
77     * @return ResponseInterface JSON API response with setting value.
78     */
79    public function get(string $key): ResponseInterface
80    {
81        $value = $this->getSettingFromDb($key);
82
83        if ($value === null) {
84            return $this->buildJsonResponse($this->responseFactory, [
85                'status'  => false,
86                'message' => sprintf("Setting '%s' not found.", $key),
87            ], 404);
88        }
89
90        return $this->buildJsonResponse($this->responseFactory, [
91            'status' => true,
92            'key'    => $key,
93            'value'  => $value,
94        ]);
95    }
96
97    /**
98     * Handles PUT /api/v1/settings/{key} request.
99     *
100     * @param ServerRequestInterface $request HTTP server request.
101     * @param string                 $key     Setting key name.
102     * @return ResponseInterface JSON API response.
103     */
104    public function update(ServerRequestInterface $request, string $key): ResponseInterface
105    {
106        $payload = $this->parseJsonBody($request);
107        $value = (string) ($payload['value'] ?? '');
108
109        $tableName = $this->tablePrefix . 'core_settings_records';
110        $sql = sprintf(
111            'UPDATE `%s` SET `setting_value` = :val, `updated_at` = :now WHERE `setting_key` = :key',
112            $tableName
113        );
114
115        try {
116            $stmt = $this->pdo->prepare($sql);
117            $stmt->execute([
118                ':val' => $value,
119                ':now' => date('Y-m-d H:i:s'),
120                ':key' => $key,
121            ]);
122
123            if ($this->clientPdo !== null) {
124                try {
125                    $stmtClient = $this->clientPdo->prepare($sql);
126                    $stmtClient->execute([
127                        ':val' => $value,
128                        ':now' => date('Y-m-d H:i:s'),
129                        ':key' => $key,
130                    ]);
131                } catch (Throwable) {
132                    // Non-blocking sync to client database
133                }
134            }
135
136            return $this->buildJsonResponse($this->responseFactory, [
137                'status'  => true,
138                'message' => 'Setting updated successfully.',
139                'key'     => $key,
140                'value'   => $value,
141            ]);
142        } catch (Throwable $e) {
143            return $this->buildJsonResponse($this->responseFactory, [
144                'status'  => false,
145                'message' => 'Failed to update setting: ' . $e->getMessage(),
146            ], 500);
147        }
148    }
149
150    /**
151     * Fetches setting value directly from database.
152     *
153     * @param string $key Setting key.
154     * @return string|null Setting string value or null if not found.
155     */
156    public function getSettingFromDb(string $key): ?string
157    {
158        $tableName = $this->tablePrefix . 'core_settings_records';
159        $sql = sprintf('SELECT `setting_value` FROM `%s` WHERE `setting_key` = :key LIMIT 1', $tableName);
160
161        try {
162            $stmt = $this->pdo->prepare($sql);
163            $stmt->execute([':key' => $key]);
164            $val = $stmt->fetchColumn();
165
166            return ($val !== false && $val !== null) ? (string) $val : null;
167        } catch (Throwable) {
168            return null;
169        }
170    }
171}