Skip to content

Commit

Permalink
Merge pull request #35 from uwblueprint/S24/sayi/add-user-management-…
Browse files Browse the repository at this point in the history
…endpoints

S24/sayi/add user management endpoints
  • Loading branch information
sthuray authored Oct 21, 2024
2 parents ff1a9b7 + 2f421fe commit 9a43dbb
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 21 deletions.
24 changes: 20 additions & 4 deletions backend/typescript/middlewares/validators/userValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,32 @@ export const updateUserDtoValidator = async (
res: Response,
next: NextFunction,
) => {
if (!validatePrimitive(req.body.firstName, "string")) {
if (
req.body.firstName !== undefined &&
req.body.firstName !== null &&
!validatePrimitive(req.body.firstName, "string")
) {
return res.status(400).send(getApiValidationError("firstName", "string"));
}
if (!validatePrimitive(req.body.lastName, "string")) {
if (
req.body.lastName !== undefined &&
req.body.lastName !== null &&
!validatePrimitive(req.body.lastName, "string")
) {
return res.status(400).send(getApiValidationError("lastName", "string"));
}
if (!validatePrimitive(req.body.email, "string")) {
if (
req.body.email !== undefined &&
req.body.email !== null &&
!validatePrimitive(req.body.email, "string")
) {
return res.status(400).send(getApiValidationError("email", "string"));
}
if (!validatePrimitive(req.body.role, "string")) {
if (
req.body.role !== undefined &&
req.body.role !== null &&
!validatePrimitive(req.body.role, "string")
) {
return res.status(400).send(getApiValidationError("role", "string"));
}
if (
Expand Down
113 changes: 96 additions & 17 deletions backend/typescript/rest/userRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Router } from "express";

import { isAuthorizedByRole } from "../middlewares/auth";
import { getAccessToken, isAuthorizedByRole } from "../middlewares/auth";
import {
createUserDtoValidator,
updateUserDtoValidator,
Expand Down Expand Up @@ -62,6 +62,8 @@ userRouter.get("/", async (req, res) => {
res
.status(400)
.json({ error: "userId query parameter must be a string." });
} else if (Number.isNaN(Number(userId))) {
res.status(400).json({ error: "Invalid user ID" });
} else {
try {
const user = await userService.getUserById(userId);
Expand All @@ -87,7 +89,11 @@ userRouter.get("/", async (req, res) => {
const user = await userService.getUserByEmail(email);
res.status(200).json(user);
} catch (error: unknown) {
res.status(500).json({ error: getErrorMessage(error) });
if (error instanceof NotFoundError) {
res.status(404).send(getErrorMessage(error));
} else {
res.status(500).json({ error: getErrorMessage(error) });
}
}
}
}
Expand All @@ -101,7 +107,7 @@ userRouter.post("/", createUserDtoValidator, async (req, res) => {
lastName: req.body.lastName,
email: req.body.email,
role: req.body.role ?? Role.VOLUNTEER,
status: req.body.status ?? UserStatus.ACTIVE, // TODO: make this default to inactive once user registration flow is done
status: UserStatus.INVITED,
skillLevel: req.body.skillLevel ?? null,
canSeeAllLogs: req.body.canSeeAllLogs ?? null,
canAssignUsersToTasks: req.body.canSeeAllUsers ?? null,
Expand All @@ -119,26 +125,65 @@ userRouter.post("/", createUserDtoValidator, async (req, res) => {

/* Update the user with the specified userId */
userRouter.put("/:userId", updateUserDtoValidator, async (req, res) => {
const userId = Number(req.params.userId);
if (Number.isNaN(userId)) {
res.status(400).json({ error: "Invalid user ID" });
return;
}

const accessToken = getAccessToken(req);
if (!accessToken) {
res.status(404).json({ error: "Access token not found" });
return;
}

try {
const userId = Number(req.params.userId);
if (Number.isNaN(userId)) {
res.status(400).json({ error: "Invalid user ID" });
const isBehaviourist = await authService.isAuthorizedByRole(
accessToken,
new Set([Role.ANIMAL_BEHAVIOURIST]),
);
const behaviouristUpdatableSet = new Set(["skillLevel"]);
if (isBehaviourist) {
const deniedFieldSet = Object.keys(req.body).filter((field) => {
return !behaviouristUpdatableSet.has(field);
});
if (deniedFieldSet.length > 0) {
const deniedFieldsString = "Not authorized to update field(s): ".concat(
deniedFieldSet.join(", "),
);
res.status(403).json({ error: deniedFieldsString });
return;
}
}
} catch (error: unknown) {
if (error instanceof NotFoundError) {
res.status(400).json({ error: getErrorMessage(error) });
} else {
res.status(500).json({ error: getErrorMessage(error) });
}
}

try {
const user: UserDTO = await userService.getUserById(String(userId));
const updatedUser = await userService.updateUserById(userId, {
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
role: req.body.role,
status: req.body.status,
skillLevel: req.body.skillLevel ?? null,
canSeeAllLogs: req.body.canSeeAllLogs ?? null,
canAssignUsersToTasks: req.body.canSeeAllUsers ?? null,
phoneNumber: req.body.phoneNumber ?? null,
firstName: req.body.firstName ?? user.firstName,
lastName: req.body.lastName ?? user.lastName,
email: req.body.email ?? user.email,
role: req.body.role ?? user.role,
status: req.body.status ?? user.status,
skillLevel: req.body.skillLevel ?? user.skillLevel,
canSeeAllLogs: req.body.canSeeAllLogs ?? user.canSeeAllLogs,
canAssignUsersToTasks:
req.body.canAssignUsersToTasks ?? user.canAssignUsersToTasks,
phoneNumber: req.body.phoneNumber ?? user.phoneNumber,
});
res.status(200).json(updatedUser);
} catch (error: unknown) {
res.status(500).json({ error: getErrorMessage(error) });
if (error instanceof NotFoundError) {
res.status(400).json({ error: getErrorMessage(error) });
} else {
res.status(500).json({ error: getErrorMessage(error) });
}
}
});

Expand All @@ -151,6 +196,21 @@ userRouter.delete("/", async (req, res) => {
return;
}

const accessToken = getAccessToken(req);
if (!accessToken) {
res.status(404).json({ error: "Access token not found" });
return;
}

const isAdministrator = await authService.isAuthorizedByRole(
accessToken,
new Set([Role.ADMINISTRATOR]),
);
if (!isAdministrator) {
res.status(403).json({ error: "Not authorized to delete user" });
return;
}

if (userId) {
if (typeof userId !== "string") {
res
Expand All @@ -160,10 +220,22 @@ userRouter.delete("/", async (req, res) => {
res.status(400).json({ error: "Invalid user ID" });
} else {
try {
const user: UserDTO = await userService.getUserById(userId);
if (user.status === "Active") {
res.status(400).json({
error:
"user status must be 'Inactive' or 'Invited' before deletion.",
});
return;
}
await userService.deleteUserById(Number(userId));
res.status(204).send();
} catch (error: unknown) {
res.status(500).json({ error: getErrorMessage(error) });
if (error instanceof NotFoundError) {
res.status(400).json({ error: getErrorMessage(error) });
} else {
res.status(500).json({ error: getErrorMessage(error) });
}
}
}
return;
Expand All @@ -176,6 +248,13 @@ userRouter.delete("/", async (req, res) => {
.json({ error: "email query parameter must be a string." });
} else {
try {
const user: UserDTO = await userService.getUserByEmail(email);
if (user.status === "Active") {
res.status(400).json({
error: "user status must be 'Inactive' or 'Invited' for deletion.",
});
return;
}
await userService.deleteUserByEmail(email);
res.status(204).send();
} catch (error: unknown) {
Expand Down
2 changes: 2 additions & 0 deletions backend/typescript/services/implementations/userService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ class UserService implements IUserService {
{
first_name: user.firstName,
last_name: user.lastName,
email: user.email,
role: user.role,
status: user.status,
skill_level: user.skillLevel,
Expand Down Expand Up @@ -274,6 +275,7 @@ class UserService implements IUserService {
{
first_name: oldUser.first_name,
last_name: oldUser.last_name,
email: oldUser.email,
role: oldUser.role,
status: oldUser.status,
skill_level: oldUser.skill_level,
Expand Down

0 comments on commit 9a43dbb

Please sign in to comment.