Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Topic /k1ch/ Introduce API POST: clients/{client_id}/permissions #129

Merged
merged 5 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion database/layer/admin-permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object>} - 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<Array<Object>>} - 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,
}
60 changes: 59 additions & 1 deletion database/test/db-admin-permissions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ const adminPermissions = require('../layer/admin-permission')
const { usherDb } = require('../layer/knex')

describe('Admin permissions view', () => {
let validClientKey
let permissionTableColumns

before(async () => {
const { key: clientKey } = await usherDb('clients').select('key').first()
validClientKey = clientKey
permissionTableColumns = Object.keys(await usherDb('permissions').columnInfo())
})

describe('Test Get permission', () => {
let validPermissionKey

Expand All @@ -28,9 +37,58 @@ describe('Admin permissions view', () => {
.count('permissionkey as permission_count')
.groupBy('rolekey')
.orderBy('permission_count', 'desc')
.first();
.first()
const permissions = await adminPermissions.getPermissionsByRoleKey(rolekey)
assert.equal(permission_count, permissions.length)
})
})

describe('Insert Permission', () => {
it('Should insert a new permission successfully', async () => {
const permissionObject = {
name: 'Test Permission',
clientkey: validClientKey,
description: 'A test permission',
}
const insertedPermission = await adminPermissions.insertPermission(permissionObject)
assert.equal(insertedPermission.name, permissionObject.name)
assert.equal(insertedPermission.clientkey, permissionObject.clientkey)
assert.equal(insertedPermission.description, permissionObject.description)
assert.ok(permissionTableColumns.every((col) => col in insertedPermission))
await usherDb('permissions').where({ key: insertedPermission.key }).del()
})

it('Should throw an error when inserting a permission with invalid clientkey', async () => {
const invalidPermissionObject = {
name: 'Invalid Test Permission',
clientkey: null,
description: 'This should fail',
}
try {
await adminPermissions.insertPermission(invalidPermissionObject)
assert.fail('Expected an error but did not get one')
} catch (err) {
assert.ok(err instanceof Error)
}
})
})

describe('Get Permissions by Name and Client Key', () => {
it('Should return permissions for a given name and clientkey', async () => {
const permission = await usherDb('permissions').select('*').first()
const permissions = await adminPermissions.getPermissionsByNameClientKey(
permission.name,
permission.clientkey
)
assert.ok(permissions.length > 0)
assert.equal(permissions[0].name, permission.name)
assert.equal(permissions[0].clientkey, permission.clientkey)
assert.ok(permissionTableColumns.every((col) => col in permissions[0]))
})

it('Should return an empty array if no permissions match the criteria', async () => {
const permissions = await adminPermissions.getPermissionsByNameClientKey('Nonexistent Name', 99999)
assert.deepEqual(permissions, [])
})
})
})
62 changes: 32 additions & 30 deletions database/utils/pgErrorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,79 +6,81 @@ const { PgErrorCodes } = require('../constant/PgErrorCodes')
* @returns {message: text, httpStatusCode: number}
*/
const pgErrorHandler = (pgDbError) => {
const error = {}
let errorMessage;
let httpStatusCode;
switch (pgDbError.code) {
case PgErrorCodes.UniqueViolation:
error.message = 'The operation would result in duplicate resources!'
error.httpStatusCode = 409
errorMessage = 'The operation would result in duplicate resources!'
httpStatusCode = 409
break

case PgErrorCodes.CheckViolation:
error.message = 'The operation would violate a check constraint!'
error.httpStatusCode = 400
errorMessage = 'The operation would violate a check constraint!'
httpStatusCode = 400
break

case PgErrorCodes.NotNullViolation:
error.message = 'A required value is missing!'
error.httpStatusCode = 400
errorMessage = 'A required value is missing!'
httpStatusCode = 400
break

case PgErrorCodes.ForeignKeyViolation:
error.message = 'Referenced resource is invalid!'
error.httpStatusCode = 400
errorMessage = 'Referenced resource is invalid!'
httpStatusCode = 400
break

case PgErrorCodes.InvalidTextRepresentation:
error.message = 'The provided data format is invalid!'
error.httpStatusCode = 400
errorMessage = 'The provided data format is invalid!'
httpStatusCode = 400
break

case PgErrorCodes.UndefinedColumn:
error.message = 'Internal DB Error: Bad query - Specified column is invalid!'
error.httpStatusCode = 500
errorMessage = 'Internal DB Error: Bad query - Specified column is invalid!'
httpStatusCode = 500
break

case PgErrorCodes.SerializationFailure:
error.message = 'Internal DB Error: A transaction serialization error occurred!'
error.httpStatusCode = 500
errorMessage = 'Internal DB Error: A transaction serialization error occurred!'
httpStatusCode = 500
break

case PgErrorCodes.DeadlockDetected:
error.message = 'Internal DB Error: The operation was halted due to a potential deadlock!'
error.httpStatusCode = 500
errorMessage = 'Internal DB Error: The operation was halted due to a potential deadlock!'
httpStatusCode = 500
break

case PgErrorCodes.SyntaxError:
error.message = 'Internal DB Error: There is a syntax error in the provided SQL or data!'
error.httpStatusCode = 500
errorMessage = 'Internal DB Error: There is a syntax error in the provided SQL or data!'
httpStatusCode = 500
break

case PgErrorCodes.UndefinedTable:
error.message = 'Internal DB Error: The table or view you are trying to access does not exist!'
error.httpStatusCode = 500
errorMessage = 'Internal DB Error: The table or view you are trying to access does not exist!'
httpStatusCode = 500
break

case PgErrorCodes.DiskFull:
error.message = 'Internal DB Error: The operation failed due to insufficient disk space!'
error.httpStatusCode = 500
errorMessage = 'Internal DB Error: The operation failed due to insufficient disk space!'
httpStatusCode = 500
break

case PgErrorCodes.OutOfMemory:
error.message = 'Internal DB Error: The system ran out of memory!'
error.httpStatusCode = 500
errorMessage = 'Internal DB Error: The system ran out of memory!'
httpStatusCode = 500
break

case PgErrorCodes.TooManyConnections:
error.message = 'Internal DB Error: There are too many connections to the database!'
error.httpStatusCode = 500
errorMessage = 'Internal DB Error: There are too many connections to the database!'
httpStatusCode = 500
break

default:
error.message = `Unexpected DB Error - Code: ${pgDbError?.code}, Message: ${pgDbError?.message}, Error: ${JSON.stringify(pgDbError)}`
error.httpStatusCode = 503
errorMessage = `Unexpected DB Error - Code: ${pgDbError?.code}, Message: ${pgDbError?.message}, Error: ${JSON.stringify(pgDbError)}`
httpStatusCode = 503
break
}

const error = new Error(errorMessage)
error.httpStatusCode = httpStatusCode
return error
}

Expand Down
32 changes: 32 additions & 0 deletions server/src/api_endpoints/clients/permissions.js
Original file line number Diff line number Diff line change
@@ -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<void>} - 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)
k1ch marked this conversation as resolved.
Show resolved Hide resolved
} catch ({ httpStatusCode = 500, message }) {
return next(createError(httpStatusCode, { message }))
}
}

module.exports = {
createPermission,
}
35 changes: 30 additions & 5 deletions server/src/api_endpoints/clients/utils.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,41 @@
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,
message: 'Client does not exist!',
}
const error = new Error('Client does not exist!')
error.httpStatusCode = 404
throw error
}
}

/**
* 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) {
const error = new Error('The permission name is taken!')
error.httpStatusCode = 409
throw error
}
};

module.exports = {
checkClientExists,
checkPermissionNameUniqueness,
}
35 changes: 15 additions & 20 deletions server/src/api_endpoints/personas/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,18 @@ const dbAdminPersonaPermissions = require('database/layer/admin-personapermissio
const checkPersonaExists = async (personaKey) => {
const persona = await dbAdminPersona.getPersona(personaKey)
if (!persona) {
throw {
httpStatusCode: 404,
message: 'Persona does not exist!'
}
const error = new Error('Persona does not exist!')
error.httpStatusCode = 404
throw error
}
}

const checkPermissionExists = async (permissionKey) => {
const permission = await dbAdminPermission.getPermission(permissionKey)
if (!permission) {
throw {
httpStatusCode: 404,
message: 'Permission does not exist!'
}
const error = new Error('Permission does not exist!')
error.httpStatusCode = 404
throw error
}
}

Expand All @@ -35,10 +33,9 @@ const checkPermissionExists = async (permissionKey) => {
const checkPersonaRolesValidity = async (personaKey, roleKeys) => {
const validRoles = await dbAdminPersonaRoles.selectPersonaRolesInTheSameTenant(personaKey, roleKeys)
if (validRoles.length !== roleKeys.length) {
throw {
httpStatusCode: 400,
message: 'Make sure to provide valid role keys which are associated with clients in the same tenant!',
}
const error = new Error('Make sure to provide valid role keys which are associated with clients in the same tenant!')
error.httpStatusCode = 400
throw error
}
}

Expand All @@ -53,20 +50,18 @@ const checkPersonaRolesValidity = async (personaKey, roleKeys) => {
const checkPersonaPermissionsValidity = async (personaKey, permissionKeys) => {
const validPermissions = await dbAdminPersonaPermissions.selectPersonaPermissionsInTheSameTenant(personaKey, permissionKeys)
if (validPermissions.length !== permissionKeys.length) {
throw {
httpStatusCode: 400,
message: 'Make sure to provide valid permission keys which are associated with clients in the same tenant!',
}
const error = new Error('Make sure to provide valid permission keys which are associated with clients in the same tenant!')
error.httpStatusCode = 400
throw error
}
}

const checkRoleExists = async (roleKey) => {
const role = await dbAdminRole.getRole(roleKey)
if (!role) {
throw {
httpStatusCode: 404,
message: 'Role does not exist!'
}
const error = new Error('Role does not exist!')
error.httpStatusCode = 404
throw error
}
}

Expand Down
Loading