Skip to content

Commit

Permalink
feat(user): add permissions while creating role (#622)
Browse files Browse the repository at this point in the history
  • Loading branch information
dipendraupreti committed Mar 23, 2024
1 parent f8d5fb7 commit 5023a51
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 18 deletions.
20 changes: 20 additions & 0 deletions packages/user/src/customApiError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
interface CustomApiErrorType {
message: string;
name: string;
statusCode: number;
}

class CustomApiError extends Error {
public statusCode: number;

constructor({ message, name, statusCode }: CustomApiErrorType) {
super(message);
this.message = message;
this.name = name;
this.statusCode = statusCode;
}
}

export default CustomApiError;

export type { CustomApiErrorType };
8 changes: 8 additions & 0 deletions packages/user/src/model/roles/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ const plugin = async (
options: unknown,
done: () => void
) => {
fastify.delete(
ROUTE_ROLES,
{
preHandler: [fastify.verifySession()],
},
handlers.deleteRole
);

fastify.get(
ROUTE_ROLES,
{
Expand Down
8 changes: 5 additions & 3 deletions packages/user/src/model/roles/handlers/createRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import type { SessionRequest } from "supertokens-node/framework/fastify";
const createRole = async (request: SessionRequest, reply: FastifyReply) => {
const { body, log } = request;

const { role } = body as {
const { role, permissions } = body as {
role: string;
permissions: string[];
};

try {
const service = new RoleService();
await service.createRole(role);

return reply.send({ role });
const createResponse = await service.createRole(role, permissions);

return reply.send(createResponse);
} catch (error) {
log.error(error);
reply.status(500);
Expand Down
61 changes: 61 additions & 0 deletions packages/user/src/model/roles/handlers/deleteRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import CustomApiError from "../../../customApiError";
import RoleService from "../service";

import type { FastifyReply } from "fastify";
import type { SessionRequest } from "supertokens-node/framework/fastify";

const deleteRole = async (request: SessionRequest, reply: FastifyReply) => {
const { log, query } = request;

try {
let { role } = query as { role?: string };

if (role) {
try {
role = JSON.parse(role) as string;
} catch {
/* empty */
}

if (typeof role != "string") {
throw new CustomApiError({
name: "UNKNOWN_ROLE_ERROR",
message: `Invalid role`,
statusCode: 422,
});
}

const service = new RoleService();

const deleteResponse = await service.deleteRole(role);

return reply.send(deleteResponse);
}

throw new CustomApiError({
name: "UNKNOWN_ROLE_ERROR",
message: `Invalid role`,
statusCode: 422,
});
} catch (error) {
if (error instanceof CustomApiError) {
reply.status(error.statusCode);

return reply.send({
message: error.message,
name: error.name,
statusCode: error.statusCode,
});
}

log.error(error);
reply.status(500);

return reply.send({
status: "ERROR",
message: "Oops! Something went wrong",
});
}
};

export default deleteRole;
12 changes: 11 additions & 1 deletion packages/user/src/model/roles/handlers/getPermissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ const getPermissions = async (request: SessionRequest, reply: FastifyReply) => {
let permissions: string[] = [];

try {
const { role } = query as { role?: string };
let { role } = query as { role?: string };

if (role) {
try {
role = JSON.parse(role) as string;
} catch {
/* empty */
}

if (typeof role != "string") {
return reply.send({ permissions });
}

const service = new RoleService();

permissions = await service.getPermissionsForRole(role);
Expand Down
2 changes: 2 additions & 0 deletions packages/user/src/model/roles/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import createRole from "./createRole";
import deleteRole from "./deleteRole";
import getPermissions from "./getPermissions";
import getRoles from "./getRoles";
import updatePermissions from "./updatePermissions";

export default {
deleteRole,
createRole,
getRoles,
getPermissions,
Expand Down
15 changes: 13 additions & 2 deletions packages/user/src/model/roles/handlers/updatePermissions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CustomApiError from "../../../customApiError";
import RoleService from "../service";

import type { FastifyReply } from "fastify";
Expand All @@ -16,13 +17,23 @@ const updatePermissions = async (
};

const service = new RoleService();
const updatedPermissions = await service.updateRolePermissions(
const updatedPermissionsResponse = await service.updateRolePermissions(
role,
permissions
);

return reply.send({ permissions: updatedPermissions });
return reply.send(updatedPermissionsResponse);
} catch (error) {
if (error instanceof CustomApiError) {
reply.status(error.statusCode);

return reply.send({
message: error.message,
name: error.name,
statusCode: error.statusCode,
});
}

log.error(error);
reply.status(500);

Expand Down
61 changes: 57 additions & 4 deletions packages/user/src/model/roles/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,44 @@
import mercurius from "mercurius";

import RoleService from "./service";
import CustomApiError from "../../customApiError";

import type { MercuriusContext } from "mercurius";

const Mutation = {
createRole: async (
parent: unknown,
arguments_: {
role: string;
permissions: string[];
},
context: MercuriusContext
) => {
const { app } = context;

try {
const service = new RoleService();

const createResponse = await service.createRole(
arguments_.role,
arguments_.permissions
);

return createResponse;
} catch (error) {
app.log.error(error);

const mercuriusError = new mercurius.ErrorWithProps(
"Oops, Something went wrong"
);

mercuriusError.statusCode = 500;

return mercuriusError;
}
},

deleteRole: async (
parent: unknown,
arguments_: {
role: string;
Expand All @@ -16,10 +49,21 @@ const Mutation = {

try {
const service = new RoleService();
await service.createRole(arguments_.role);

return arguments_.role;
const { role } = arguments_;

const deleteResponse = await service.deleteRole(role);

return deleteResponse;
} catch (error) {
if (error instanceof CustomApiError) {
const mercuriusError = new mercurius.ErrorWithProps(error.name);

mercuriusError.statusCode = error.statusCode;

return mercuriusError;
}

app.log.error(error);

const mercuriusError = new mercurius.ErrorWithProps(
Expand All @@ -31,6 +75,7 @@ const Mutation = {
return mercuriusError;
}
},

updateRolePermissions: async (
parent: unknown,
arguments_: {
Expand All @@ -44,13 +89,21 @@ const Mutation = {

try {
const service = new RoleService();
const updatedPermissions = await service.updateRolePermissions(
const updatedPermissionsResponse = await service.updateRolePermissions(
role,
permissions
);

return updatedPermissions;
return updatedPermissionsResponse;
} catch (error) {
if (error instanceof CustomApiError) {
const mercuriusError = new mercurius.ErrorWithProps(error.name);

mercuriusError.statusCode = error.statusCode;

return mercuriusError;
}

app.log.error(error);

const mercuriusError = new mercurius.ErrorWithProps(
Expand Down
69 changes: 61 additions & 8 deletions packages/user/src/model/roles/service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
import UserRoles from "supertokens-node/recipe/userroles";

import CustomApiError from "../../customApiError";

class RoleService {
createRole = async (role: string) => {
await UserRoles.createNewRoleOrAddPermissions(role, []);
createRole = async (role: string, permissions?: string[]) => {
const createRoleResponse = await UserRoles.createNewRoleOrAddPermissions(
role,
permissions || []
);

return createRoleResponse;
};

deleteRole = async (
role: string
): Promise<{ status: "OK"; didRoleExist: boolean }> => {
const response = await UserRoles.getUsersThatHaveRole(role);

if (response.status === "UNKNOWN_ROLE_ERROR") {
throw new CustomApiError({
name: response.status,
message: `Invalid role`,
statusCode: 422,
});
}

if (response.users.length > 0) {
throw new CustomApiError({
name: "ROLE_IN_USE",
message:
"The role is currently assigned to one or more users and cannot be deleted",
statusCode: 422,
});
}

const deleteRoleResponse = await UserRoles.deleteRole(role);

return deleteRoleResponse;
};

getPermissionsForRole = async (role: string): Promise<string[]> => {
Expand All @@ -17,13 +51,23 @@ class RoleService {
return permissions;
};

getRoles = async (): Promise<string[]> => {
let roles: string[] = [];
getRoles = async (): Promise<{ role: string; permissions: string[] }[]> => {
let roles: { role: string; permissions: string[] }[] = [];

const response = await UserRoles.getAllRoles();

if (response.status === "OK") {
roles = response.roles;
// [DU 2024-MAR-20] This is N+1 problem
roles = await Promise.all(
response.roles.map(async (role) => {
const response = await UserRoles.getPermissionsForRole(role);

return {
role,
permissions: response.status === "OK" ? response.permissions : [],
};
})
);
}

return roles;
Expand All @@ -32,11 +76,15 @@ class RoleService {
updateRolePermissions = async (
role: string,
permissions: string[]
): Promise<string[]> => {
): Promise<{ status: "OK"; permissions: string[] }> => {
const response = await UserRoles.getPermissionsForRole(role);

if (response.status === "UNKNOWN_ROLE_ERROR") {
throw new Error("UNKNOWN_ROLE_ERROR");
throw new CustomApiError({
name: "UNKNOWN_ROLE_ERROR",
message: `Invalid role`,
statusCode: 422,
});
}

const rolePermissions = response.permissions;
Expand All @@ -52,7 +100,12 @@ class RoleService {
await UserRoles.removePermissionsFromRole(role, removedPermissions);
await UserRoles.createNewRoleOrAddPermissions(role, newPermissions);

return this.getPermissionsForRole(role);
const permissionsResponse = await this.getPermissionsForRole(role);

return {
status: "OK",
permissions: permissionsResponse,
};
};
}

Expand Down

0 comments on commit 5023a51

Please sign in to comment.