From b87c64c2eda5ce7e45cf4e3cba31a65bace9919c Mon Sep 17 00:00:00 2001 From: Keyvan Chamani Date: Thu, 31 Oct 2024 17:07:58 -0400 Subject: [PATCH] feat: k1ch/ introduce POST:/clients/{client_id}/permissions --- database/layer/admin-permission.js | 39 +++++++++++++++- .../src/api_endpoints/clients/permissions.js | 32 +++++++++++++ server/src/api_endpoints/clients/utils.js | 29 +++++++++++- server/the-usher-openapi-spec.yaml | 45 +++++++++++++++++++ 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 server/src/api_endpoints/clients/permissions.js diff --git a/database/layer/admin-permission.js b/database/layer/admin-permission.js index 7d15772..ef910b6 100644 --- a/database/layer/admin-permission.js +++ b/database/layer/admin-permission.js @@ -73,16 +73,53 @@ const getPermissionsByRoleKey = async (roleKey) => { return await usherDb('permissions') .join('rolepermissions', 'permissions.key', '=', 'rolepermissions.permissionkey') .where({ 'rolepermissions.rolekey': roleKey }) - .select('permissions.*'); + .select('permissions.*') + } catch (err) { + throw pgErrorHandler(err) + } +} + +/** + * Insert a new permission + * + * @param {Object} permissionObject - The data for the new permission + * @param {string} permissionObject.name - The name of permission + * @param {number} permissionObject.clientkey - A valid client key + * @param {string} permissionObject.description - A description of the permission + * @returns {Promise} - A promise that resolves to the inserted permission object + */ +const insertPermission = async (permissionObject) => { + try { + const [permission] = await usherDb('permissions').insert(permissionObject).returning('*') + return permission } catch (err) { throw pgErrorHandler(err); } } +/** + * Get permissions by name and clientKey + * + * @param {string} name - The name of the permission + * @param {number} clientKey - The client key + * @returns {Promise>} - A promise that resolves to an array of permissions + */ +const getPermissionsByNameClientKey = async (name, clientKey) => { + try { + const permissions = await usherDb('permissions') + .where({ name, clientkey: clientKey }) + return permissions + } catch (err) { + throw pgErrorHandler(err) + } +} + module.exports = { insertPermissionByClientId, updatePermissionByPermissionname, deletePermissionByPermissionname, getPermission, getPermissionsByRoleKey, + insertPermission, + getPermissionsByNameClientKey, } diff --git a/server/src/api_endpoints/clients/permissions.js b/server/src/api_endpoints/clients/permissions.js new file mode 100644 index 0000000..c330f08 --- /dev/null +++ b/server/src/api_endpoints/clients/permissions.js @@ -0,0 +1,32 @@ +const createError = require('http-errors') +const dbAdminPermission = require('database/layer/admin-permission') +const { checkClientExists, checkPermissionNameUniqueness } = require('./utils') + +/** + * HTTP Request handler + * Create a permission + * + * @param {Object} req - The request object + * @param {Object} res - The response object to send 201 statusCode and the cerated permission on success + * @param {Function} next - The next middleware function + * @returns {Promise} - A Promise that resolves to void when the permission is created + */ +const createPermission = async (req, res, next) => { + try { + const { client_id: clientId } = req.params + const client = await checkClientExists(clientId) + const payload = { + ...req.body, + clientkey: client.key, + } + await checkPermissionNameUniqueness(payload) + const permission = await dbAdminPermission.insertPermission(payload) + res.status(201).send(permission) + } catch ({ httpStatusCode = 500, message }) { + return next(createError(httpStatusCode, { message })) + } +} + +module.exports = { + createPermission, +} diff --git a/server/src/api_endpoints/clients/utils.js b/server/src/api_endpoints/clients/utils.js index 7ded761..d8b4f92 100644 --- a/server/src/api_endpoints/clients/utils.js +++ b/server/src/api_endpoints/clients/utils.js @@ -1,8 +1,9 @@ const dbAdminRole = require('database/layer/admin-client') +const dbAdminPermission = require('database/layer/admin-permission') const checkClientExists = async (clientId) => { try { - await dbAdminRole.getClient(clientId); + return await dbAdminRole.getClient(clientId); } catch { throw { httpStatusCode: 404, @@ -11,6 +12,32 @@ const checkClientExists = async (clientId) => { } } +/** + * Checks the uniqueness of a permission name for a given client key. + * + * This function queries the database to retrieve permissions by name and client key. + * If any permissions are found, it throws an error indicating the name is already taken. + * + * @async + * @function checkPermissionNameUniqueness + * @param {Object} params - The parameters for checking uniqueness. + * @param {string} params.name - The name of the permission to check. + * @param {string} params.clientkey - The client key associated with the permission. + * @throws {Object} Throws an error with HTTP status code 409 if the permission name is not unique. + * @throws {number} error.httpStatusCode - The HTTP status code indicating conflict (409). + * @throws {string} error.message - The error message indicating the permission name is taken. + */ +const checkPermissionNameUniqueness = async ({ name, clientkey: clientKey }) => { + const permissions = await dbAdminPermission.getPermissionsByNameClientKey(name, clientKey); + if (permissions?.length) { + throw { + httpStatusCode: 409, + message: 'The permission name is taken!' + }; + } +}; + module.exports = { checkClientExists, + checkPermissionNameUniqueness, } diff --git a/server/the-usher-openapi-spec.yaml b/server/the-usher-openapi-spec.yaml index 72eb7cd..ca478f5 100644 --- a/server/the-usher-openapi-spec.yaml +++ b/server/the-usher-openapi-spec.yaml @@ -1022,6 +1022,50 @@ paths: 404: $ref: '#/components/responses/NotFound' + /clients/{client_id}/permissions: + parameters: + - $ref: '#/components/parameters/clientIdPathParam' + post: + 'x-swagger-router-controller': 'clients/permissions' + summary: Create a new permission for the given client + operationId: createPermission + tags: + - Client Admin APIs + security: + - bearerAdminAuth: [] + - bearerClientAdminAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + $ref: '#/components/schemas/EntityNameDef' + description: + $ref: '#/components/schemas/EntityDescriptionDef' + required: + - name + additionalProperties: false + responses: + 201: + description: Returns the created permission + content: + application/json: + schema: + $ref: "#/components/schemas/PermissionObject" + 400: + $ref: '#/components/responses/BadRequest' + 404: + $ref: '#/components/responses/NotFound' + 409: + $ref: '#/components/responses/Conflict' + 500: + $ref: '#/components/responses/InternalError' + 503: + $ref: '#/components/responses/ServiceUnavailableError' + /sessions: delete: operationId: invalidateSession @@ -1269,6 +1313,7 @@ components: $ref: '#/components/schemas/EntityNameDef' description: $ref: '#/components/schemas/EntityDescriptionDef' + nullable: true created_at: type: string format: date-time