Skip to content

Commit 8ede2ff

Browse files
Introduce ApiV1(Contacts/Contactgroups)Controller
1 parent bbfe1be commit 8ede2ff

File tree

2 files changed

+948
-0
lines changed

2 files changed

+948
-0
lines changed
Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
<?php
2+
3+
/* Icinga Notifications Web | (c) 2024 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Notifications\Controllers;
6+
7+
use Icinga\Exception\Http\HttpBadRequestException;
8+
use Icinga\Exception\Http\HttpException;
9+
use Icinga\Exception\Http\HttpNotFoundException;
10+
use Icinga\Module\Notifications\Common\Database;
11+
use Icinga\Util\Environment;
12+
use Icinga\Util\Json;
13+
use ipl\Sql\Compat\FilterProcessor;
14+
use ipl\Sql\Select;
15+
use ipl\Stdlib\Filter;
16+
use ipl\Web\Compat\CompatController;
17+
use ipl\Web\Filter\QueryString;
18+
use ipl\Web\Url;
19+
use Ramsey\Uuid\Uuid;
20+
21+
class ApiV1ContactgroupsController extends CompatController
22+
{
23+
private const ENDPOINT = 'notifications/api/v1/contactgroups';
24+
25+
public function indexAction(): void
26+
{
27+
$this->assertPermission('notifications/api/v1');
28+
29+
$request = $this->getRequest();
30+
if (! $request->isApiRequest()) {
31+
$this->httpBadRequest('No API request');
32+
}
33+
34+
$method = $request->getMethod();
35+
if (in_array(
36+
$method, ['POST', 'PUT'])
37+
&& (! preg_match('/([^;]*);?/', $request->getHeader('Content-Type'), $matches)
38+
|| $matches[1] !== 'application/json'
39+
)
40+
) {
41+
$this->httpBadRequest('No JSON content');
42+
}
43+
44+
$results = [];
45+
$responseCode = 200;
46+
$db = Database::get();
47+
$identifier = $request->getParam('identifier');
48+
49+
if ($identifier && ! Uuid::isValid($identifier)) {
50+
$this->httpBadRequest('The given identifier is not a valid UUID');
51+
}
52+
53+
$filter = FilterProcessor::assembleFilter(
54+
QueryString::fromString(Url::fromRequest()->getQueryString())
55+
->on(
56+
QueryString::ON_CONDITION,
57+
function (Filter\Condition $condition) {
58+
$column = $condition->getColumn();
59+
if (! in_array($column, ['id', 'name'])) {
60+
$this->httpBadRequest(sprintf(
61+
'Invalid filter column %s given, only id and name are allowed',
62+
$column
63+
));
64+
}
65+
66+
if ($column === 'id') {
67+
if (! Uuid::isValid($condition->getValue())) {
68+
$this->httpBadRequest('The given filter id is not a valid UUID');
69+
}
70+
71+
$condition->setColumn('external_uuid');
72+
}
73+
}
74+
)->parse()
75+
);
76+
77+
switch ($method) {
78+
case 'GET':
79+
$stmt = (new Select())
80+
->distinct()
81+
->from('contactgroup cg')
82+
->columns([
83+
'contactgroup_id' => 'cg.id',
84+
'id' => 'cg.external_uuid',
85+
'name'
86+
]);
87+
88+
if ($identifier !== null) {
89+
$stmt->where(['external_uuid = ?' => $identifier]);
90+
$result = $db->fetchOne($stmt);
91+
92+
if ($result === false) {
93+
$this->httpNotFound('Contactgroup not found');
94+
}
95+
96+
$users = $this->fetchUserIdentifiers($result->contactgroup_id);
97+
if ($users) {
98+
$result->users = $users;
99+
}
100+
101+
unset($result->contactgroup_id);
102+
$results[] = $result;
103+
104+
break;
105+
}
106+
107+
if ($filter !== null) {
108+
$stmt->where($filter);
109+
}
110+
111+
$stmt->limit(500);
112+
$offset = 0;
113+
114+
ob_end_clean();
115+
Environment::raiseExecutionTime();
116+
117+
$this->getResponse()
118+
->setHeader('Content-Type', 'application/json')
119+
->setHeader('Cache-Control', 'no-store')
120+
->sendResponse();
121+
122+
echo '[';
123+
124+
$res = $db->select($stmt->offset($offset));
125+
do {
126+
foreach ($res as $i => $row) {
127+
$users = $this->fetchUserIdentifiers($row->contactgroup_id);
128+
if ($users) {
129+
$row->users = $users;
130+
}
131+
132+
if ($i > 0 || $offset !== 0) {
133+
echo ",\n";
134+
}
135+
136+
unset($row->contactgroup_id);
137+
138+
echo Json::sanitize($row);
139+
}
140+
141+
$offset += 500;
142+
$res = $db->select($stmt->offset($offset));
143+
} while ($res->rowCount());
144+
145+
echo ']';
146+
147+
exit;
148+
case 'POST':
149+
if ($filter !== null) {
150+
$this->httpBadRequest('Cannot filter on POST');
151+
}
152+
153+
$data = $request->getPost();
154+
155+
$this->assertValidData($data);
156+
157+
$db->beginTransaction();
158+
159+
if ($identifier === null) {
160+
if ($this->getContactgroupId($data['id']) !== null) {
161+
throw new HttpException(422, 'Contactgroup already exists');
162+
}
163+
164+
$this->addContactgroup($data);
165+
} else {
166+
$contactgroupId = $this->getContactgroupId($identifier);
167+
if ($contactgroupId === null) {
168+
$this->httpNotFound('Contactgroup not found');
169+
}
170+
171+
if ($identifier === $data['id'] || $this->getContactgroupId($data['id']) !== null) {
172+
throw new HttpException(422, 'Contact already exists');
173+
}
174+
175+
$this->removeContactgroup($contactgroupId);
176+
$this->addContactgroup($data);
177+
}
178+
179+
$db->commitTransaction();
180+
181+
$this->getResponse()->setHeader('Location', self::ENDPOINT . '/' . $data['id']);
182+
$responseCode = 201;
183+
184+
break;
185+
case 'PUT':
186+
if ($identifier === null) {
187+
$this->httpBadRequest('Identifier is required');
188+
}
189+
190+
$data = $request->getPost();
191+
192+
$this->assertValidData($data);
193+
194+
if ($identifier !== $data['id']) {
195+
$this->httpBadRequest('Identifier mismatch');
196+
}
197+
198+
$db->beginTransaction();
199+
200+
$contactgroupId = $this->getContactgroupId($identifier);
201+
if ($contactgroupId !== null) {
202+
$db->update('contactgroup', ['name' => $data['name']], ['id = ?' => $contactgroupId]);
203+
204+
$db->delete('contactgroup_member', ['contactgroup_id = ?' => $contactgroupId]);
205+
206+
if (! empty($data['users'])) {
207+
$this->addUsers($contactgroupId, $data['users']);
208+
}
209+
210+
$responseCode = 204;
211+
} else {
212+
$this->addContactgroup($data);
213+
$responseCode = 201;
214+
}
215+
216+
$db->commitTransaction();
217+
218+
break;
219+
case 'DELETE':
220+
if ($identifier === null) {
221+
$this->httpBadRequest('Identifier is required');
222+
}
223+
224+
$db->beginTransaction();
225+
226+
$contactgroupId = $this->getContactgroupId($identifier);
227+
if ($contactgroupId === null) {
228+
$this->httpNotFound('Contactgroup not found');
229+
}
230+
231+
$this->removeContactgroup($contactgroupId);
232+
233+
$db->commitTransaction();
234+
235+
$responseCode = 204;
236+
237+
break;
238+
default:
239+
$this->httpBadRequest('Invalid method');
240+
}
241+
242+
$this->getResponse()
243+
->setHttpResponseCode($responseCode)
244+
->json()
245+
->setSuccessData($results)
246+
->sendResponse();
247+
}
248+
249+
/**
250+
* Fetch the user(contact) identifiers of the contactgroup with the given id
251+
*
252+
* @param int $contactgroupId
253+
*
254+
* @return ?string[]
255+
*/
256+
private function fetchUserIdentifiers(int $contactgroupId): ?array
257+
{
258+
$users = Database::get()->fetchCol(
259+
(new Select())
260+
->from('contactgroup_member cgm')
261+
->columns('co.external_uuid')
262+
->joinLeft('contact co', 'co.id = cgm.contact_id')
263+
->where(['cgm.contactgroup_id = ?' => $contactgroupId])
264+
->groupBy('co.external_uuid')
265+
);
266+
267+
return ! empty($users) ? $users : null;
268+
}
269+
270+
/**
271+
* Assert that the given user IDs exist
272+
*
273+
* @param string $identifier
274+
*
275+
* @return int
276+
*
277+
* @throws HttpNotFoundException if the user with the given identifier does not exist
278+
*/
279+
private function getUserId(string $identifier): int
280+
{
281+
$user = Database::get()->fetchOne(
282+
(new Select())
283+
->from('contact')
284+
->columns('id')
285+
->where(['external_uuid = ?' => $identifier])
286+
);
287+
288+
if ($user === false) {
289+
throw new HttpNotFoundException(sprintf('User with identifier %s not found', $identifier));
290+
}
291+
292+
return $user->id;
293+
}
294+
295+
/**
296+
* Get the contactgroup id with the given identifier
297+
*
298+
* @param string $identifier
299+
*
300+
* @return ?int Returns null, if contact does not exist
301+
*/
302+
private function getContactgroupId(string $identifier): ?int
303+
{
304+
$contactgroup = Database::get()->fetchOne(
305+
(new Select())
306+
->from('contactgroup')
307+
->columns('id')
308+
->where(['external_uuid = ?' => $identifier])
309+
);
310+
311+
return $contactgroup->id ?? null;
312+
}
313+
314+
/**
315+
* Add a new contactgroup with the given data
316+
*
317+
* @param array<string, mixed> $data
318+
*/
319+
private function addContactgroup(array $data): void
320+
{
321+
Database::get()->insert('contactgroup', [
322+
'name' => $data['name'],
323+
'external_uuid' => $data['id']
324+
]);
325+
326+
$id = Database::get()->lastInsertId();
327+
328+
if (! empty($data['users'])) {
329+
$this->addUsers($id, $data['users']);
330+
}
331+
}
332+
333+
/**
334+
* Add the given users as contactgroup_member with the given id
335+
*
336+
* @param int $contactgroupId
337+
* @param string[] $users
338+
*
339+
* @return void
340+
*/
341+
private function addUsers(int $contactgroupId, array $users): void
342+
{
343+
foreach ($users as $identifier) {
344+
$contactId = $this->getUserId($identifier);
345+
346+
Database::get()->insert('contactgroup_member', [
347+
'contactgroup_id' => $contactgroupId,
348+
'contact_id' => $contactId
349+
]);
350+
}
351+
}
352+
353+
/**
354+
* Remove the contactgroup with the given id
355+
*
356+
* @param int $id
357+
*/
358+
private function removeContactgroup(int $id): void
359+
{
360+
Database::get()->delete('contactgroup_member', ['contactgroup_id = ?' => $id]);
361+
Database::get()->delete('contactgroup', ['id = ?' => $id]);
362+
}
363+
364+
/**
365+
* Assert that the given data contains the required fields
366+
*
367+
* @param array<string, mixed> $data
368+
*
369+
* @throws HttpBadRequestException
370+
*/
371+
private function assertValidData(array $data): void
372+
{
373+
if (! isset($data['id'], $data['name'])) {
374+
$this->httpBadRequest('The request body must contain the fields id and name');
375+
}
376+
377+
if (! Uuid::isValid($data['id'])) {
378+
$this->httpBadRequest('Given id in request body is not a valid UUID');
379+
}
380+
381+
if (! empty($data['users'])) {
382+
foreach ($data['users'] as $user) {
383+
if (! Uuid::isValid($user)) {
384+
$this->httpBadRequest('User identifiers in request body must be valid UUIDs');
385+
}
386+
}
387+
}
388+
}
389+
}

0 commit comments

Comments
 (0)