Skip to content

Commit 1879b41

Browse files
Introduce ApiV1(Contacts/Contactgroups)Controller
1 parent 0b0269a commit 1879b41

File tree

2 files changed

+813
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)