-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce ApiV1(Contacts/Contactgroups)Controller
- Loading branch information
1 parent
66522e8
commit 63a10ef
Showing
2 changed files
with
813 additions
and
0 deletions.
There are no files selected for viewing
361 changes: 361 additions & 0 deletions
361
application/controllers/ApiV1ContactgroupsController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,361 @@ | ||
<?php | ||
|
||
/* Icinga Notifications Web | (c) 2024 Icinga GmbH | GPLv2 */ | ||
|
||
namespace Icinga\Module\Notifications\Controllers; | ||
|
||
use Icinga\Exception\Http\HttpBadRequestException; | ||
use Icinga\Exception\Http\HttpException; | ||
use Icinga\Module\Notifications\Common\Database; | ||
use Icinga\Util\Environment; | ||
use Icinga\Util\Json; | ||
use ipl\Sql\Compat\FilterProcessor; | ||
use ipl\Sql\Select; | ||
use ipl\Stdlib\Filter; | ||
use ipl\Web\Compat\CompatController; | ||
use ipl\Web\Filter\QueryString; | ||
use ipl\Web\Url; | ||
|
||
class ApiV1ContactgroupsController extends CompatController | ||
{ | ||
private const ENDPOINT = 'notifications/api/v1/contactgroups'; | ||
|
||
/** | ||
* @return void | ||
*/ | ||
public function indexAction(): void | ||
{ | ||
$this->assertPermission('notifications/api/v1'); | ||
|
||
$request = $this->getRequest(); | ||
if (! $request->isApiRequest()) { | ||
$this->httpBadRequest('No API request'); | ||
} | ||
|
||
$method = $request->getMethod(); | ||
if (in_array($method, ['POST', 'PUT']) | ||
&& (! preg_match('/([^;]*);?/', $request->getHeader('Content-Type'), $matches) | ||
|| $matches[1] !== 'application/json' | ||
) | ||
) { | ||
$this->httpBadRequest('No JSON content'); | ||
} | ||
|
||
$results = []; | ||
$responseCode = 200; | ||
$db = Database::get(); | ||
$identifier = $request->getParam('identifier'); | ||
// TODO: Remove rawurldecode(). Only added to test, bcz phpstorm's http client encodes the params | ||
$queryString = rawurldecode(Url::fromRequest()->getQueryString()); | ||
$filter = FilterProcessor::assembleFilter( | ||
QueryString::fromString($queryString) | ||
->on( | ||
QueryString::ON_CONDITION, | ||
function (Filter\Condition $condition) { | ||
if ($condition->getColumn() === 'id') { | ||
$condition->setColumn('external_uuid'); | ||
} | ||
} | ||
)->parse() | ||
); | ||
|
||
switch ($method) { | ||
case 'GET': | ||
$stmt = (new Select()) | ||
->distinct() | ||
->from('contactgroup cg') | ||
->columns([ | ||
'contactgroup_id' => 'cg.id', | ||
'id' => 'cg.external_uuid', | ||
'name' | ||
]); | ||
|
||
if ($identifier !== null) { | ||
$stmt->where(['external_uuid = ?' => $identifier]); | ||
$result = $db->fetchOne($stmt); | ||
|
||
if ($result === false) { | ||
$this->httpNotFound('Contactgroup not found'); | ||
} | ||
|
||
$users = $this->fetchUserIdentifiers($result->contactgroup_id); | ||
if ($users) { | ||
$result->users = $users; | ||
} | ||
|
||
unset($result->contactgroup_id); | ||
$results[] = $result; | ||
|
||
break; | ||
} | ||
|
||
if ($filter !== null) { | ||
$stmt->where($filter); | ||
} | ||
|
||
$stmt->limit(500); | ||
$offset = 0; | ||
|
||
ob_end_clean(); | ||
Environment::raiseExecutionTime(); | ||
|
||
$this->getResponse() | ||
->setHeader('Content-Type', 'application/json') | ||
->setHeader('Cache-Control', 'no-store') | ||
->sendResponse(); | ||
|
||
echo '['; | ||
|
||
$res = $db->select($stmt->offset($offset)); | ||
do { | ||
foreach ($res as $i => $row) { | ||
$users = $this->fetchUserIdentifiers($row->contactgroup_id); | ||
if ($users) { | ||
$row->users = $users; | ||
} | ||
|
||
if ($i > 0 || $offset !== 0) { | ||
echo ",\n"; | ||
} | ||
|
||
unset($row->contactgroup_id); | ||
|
||
echo Json::sanitize($row); | ||
} | ||
|
||
$offset += 500; | ||
$res = $db->select($stmt->offset($offset)); | ||
} while ($res->rowCount()); | ||
|
||
echo ']'; | ||
|
||
exit; | ||
case 'POST': | ||
if ($filter !== null) { | ||
$this->httpBadRequest('Cannot filter on POST'); | ||
} | ||
|
||
$data = $request->getPost(); | ||
|
||
$this->assertValidData($data); | ||
|
||
$db->beginTransaction(); | ||
|
||
if ($identifier === null) { | ||
$identifier = $data['id']; | ||
|
||
if ($this->getContactgroupId($identifier) !== null) { | ||
throw new HttpException('422', 'Contactgroup already exists'); | ||
} | ||
|
||
$this->addContactgroup($data); | ||
} else { | ||
$contactgroupId = $this->getContactgroupId($identifier); | ||
if ($contactgroupId === null) { | ||
$this->httpNotFound('Contactgroup not found'); | ||
} | ||
|
||
if ($identifier === $data['id']) { | ||
throw new HttpException('422', 'Contactgroup already exists'); | ||
} | ||
|
||
$identifier = $data['id']; | ||
$this->removeContactgroup($contactgroupId); | ||
$this->addContactgroup($data); | ||
} | ||
|
||
$db->commitTransaction(); | ||
|
||
$this->getResponse()->setHeader('Location', self::ENDPOINT . '/' . $identifier); | ||
$responseCode = 201; | ||
|
||
break; | ||
case 'PUT': | ||
if ($identifier === null) { | ||
$this->httpBadRequest('Identifier is required'); | ||
} | ||
|
||
$data = $request->getPost(); | ||
|
||
$this->assertValidData($data); | ||
|
||
if ($identifier !== $data['id']) { | ||
$this->httpBadRequest('Identifier mismatch'); | ||
} | ||
|
||
$db->beginTransaction(); | ||
|
||
$contactgroupId = $this->getContactgroupId($identifier); | ||
if ($contactgroupId !== null) { | ||
$db->update('contactgroup', [ | ||
'name' => $data['name'], | ||
], ['id = ?' => $contactgroupId]); | ||
|
||
$db->delete('contactgroup_member', ['contactgroup_id = ?' => $identifier]); | ||
|
||
if (! empty($data['users'])) { | ||
$this->assertUsersExist($data['users']); | ||
|
||
foreach ($data['users'] as $userId) { | ||
$db->insert('contactgroup_member', [ | ||
'contactgroup_id' => $identifier, | ||
'contact_id' => $userId | ||
]); | ||
} | ||
} | ||
|
||
$responseCode = 204; | ||
} else { | ||
$this->addContactgroup($data); | ||
$responseCode = 201; | ||
} | ||
|
||
$db->commitTransaction(); | ||
|
||
break; | ||
case 'DELETE': | ||
if ($identifier === null) { | ||
$this->httpBadRequest('Identifier is required'); | ||
} | ||
|
||
$db->beginTransaction(); | ||
|
||
$contactgroupId = $this->getContactgroupId($identifier); | ||
if ($contactgroupId === null) { | ||
$this->httpNotFound('Contactgroup not found'); | ||
} | ||
|
||
$this->removeContactgroup($contactgroupId); | ||
|
||
$db->commitTransaction(); | ||
|
||
$responseCode = 204; | ||
|
||
break; | ||
default: | ||
$this->httpBadRequest('Invalid method'); | ||
} | ||
|
||
$this->getResponse() | ||
->setHttpResponseCode($responseCode) | ||
->json() | ||
->setSuccessData($results) | ||
->sendResponse(); | ||
} | ||
|
||
/** | ||
* Fetch the user(contact) identifiers of the contactgroup with the given id | ||
* | ||
* @param int $contactgroupId | ||
* | ||
* @return ?string[] | ||
*/ | ||
private function fetchUserIdentifiers(int $contactgroupId): ?array | ||
{ | ||
$users = Database::get()->fetchCol( | ||
(new Select()) | ||
->from('contactgroup_member cgm') | ||
->columns('co.external_uuid') | ||
->joinLeft('contact co', 'co.id = cgm.contact_id') | ||
->where(['cgm.contactgroup_id = ?' => $contactgroupId]) | ||
->groupBy('co.external_uuid') | ||
); | ||
|
||
return ! empty($users) ? $users : null; | ||
} | ||
|
||
/** | ||
* Assert that the given user IDs exist | ||
* | ||
* @param array $userIds | ||
* | ||
* @throws HttpException if a group does not exist | ||
*/ | ||
private function assertUsersExist(array $userIds): void | ||
{ | ||
$existingUserIds = Database::get()->fetchCol( | ||
(new Select()) | ||
->from('contact') | ||
->columns('id') | ||
->where(['id IN (?)' => $userIds]) | ||
); | ||
|
||
if (count($existingUserIds) !== count($userIds)) { | ||
throw new HttpException('404', 'Undefined user identifier given'); | ||
} | ||
} | ||
|
||
/** | ||
* Get the contactgroup id with the given identifier | ||
* | ||
* @param string $identifier | ||
* | ||
* @return ?int Returns null, if contact does not exist | ||
*/ | ||
private function getContactgroupId(string $identifier): ?int | ||
{ | ||
$contactgroup = Database::get()->fetchOne( | ||
(new Select()) | ||
->from('contactgroup') | ||
->columns('id') | ||
->where(['external_uuid = ?' => $identifier]) | ||
); | ||
|
||
return $contactgroup->id ?? null; | ||
} | ||
|
||
/** | ||
* Add a new contactgroup with the given data | ||
* | ||
* @param array $data | ||
* | ||
* @throws HttpException if a user does not exist | ||
*/ | ||
private function addContactgroup(array $data): void | ||
{ | ||
$db = Database::get(); | ||
$db->insert('contactgroup', [ | ||
'name' => $data['name'], | ||
'external_uuid' => $data['id'] | ||
]); | ||
|
||
$id = $db->lastInsertId(); | ||
|
||
if (! empty($data['users'])) { | ||
$this->assertUsersExist($data['users']); | ||
foreach ($data['users'] as $contactId) { | ||
$db->insert('contactgroup_member', [ | ||
'contactgroup_id' => $id, | ||
'contact_id' => $contactId | ||
]); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Remove the contactgroup with the given id | ||
* | ||
* @param int $id | ||
*/ | ||
private function removeContactgroup(int $id): void | ||
{ | ||
$db = Database::get(); | ||
|
||
$db->delete('contactgroup_member', ['contactgroup_id = ?' => $id]); | ||
$db->delete('contactgroup', ['id = ?' => $id]); | ||
} | ||
|
||
/** | ||
* Assert that the given data contains the required fields | ||
* | ||
* @param array $data | ||
* | ||
* @throws HttpBadRequestException | ||
*/ | ||
private function assertValidData(array $data): void | ||
{ | ||
if (! isset($data['id'], $data['name'])) { | ||
$this->httpBadRequest('missing required fields'); | ||
} | ||
} | ||
} |
Oops, something went wrong.