Skip to content

Commit c83e47b

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

File tree

2 files changed

+791
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)