Skip to content

Commit

Permalink
#139 [FEAT]: Function to get managed users and classrooms and additio…
Browse files Browse the repository at this point in the history
…nal authorization fixes
  • Loading branch information
IosBonaldi committed Feb 15, 2025
1 parent 0855dc6 commit 3bd5634
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 54 deletions.
3 changes: 3 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ model User {
visibleApplicationAnswers Application[] @relation(name: "ApplicationAnswersViewersUsers") // applications that the user can see the answers
classrooms Classroom[] @relation(name: "ClassroomUsers") // classrooms that the user is in
createdClassrooms Classroom[] @relation(name: "ClassroomCreator") // classrooms that the user has created
createdUsers User[] @relation(name: "UserCreator") // users that the user has created
creatorId Int?
creator User? @relation(name: "UserCreator", fields: [creatorId], references: [id], onDelete: SetNull)
institutionId Int?
institution Institution? @relation(fields: [institutionId], references: [id], onDelete: SetNull)
}
Expand Down
2 changes: 1 addition & 1 deletion prisma/seeds/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5612,7 +5612,7 @@ async function main() {
},
];

const users: (Omit<User, 'createdAt' | 'updatedAt' | 'profileImageId'> & { classrooms: number[] })[] = [
const users: (Omit<User, 'createdAt' | 'updatedAt' | 'profileImageId' | 'creatorId'> & { classrooms: number[] })[] = [
{
id: 1,
name: 'Visitante',
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/applicationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ const getProtocolUserRoles = async (user: User, protocol: any, protocolId: numbe
protocol?.visibility === VisibilityMode.PUBLIC ||
(protocol?.visibility === VisibilityMode.AUTHENTICATED && user.role !== UserRole.GUEST) ||
protocol?.viewersUser?.some((viewer: any) => viewer.id === user.id) ||
protocol?.viewersClassroom?.some((classroom: any) => classroom.users.some((viewer: any) => viewer.id === user.id))
protocol?.viewersClassroom?.some((classroom: any) => classroom.users?.some((viewer: any) => viewer.id === user.id))
);
const answersViewer = !!(
protocol?.answersVisibility === VisibilityMode.PUBLIC ||
(protocol?.answersVisibility === VisibilityMode.AUTHENTICATED && user.role !== UserRole.GUEST) ||
protocol?.answersViewersUser?.some((viewer: any) => viewer.id === user.id) ||
protocol?.answersViewersClassroom?.some((classroom: any) => classroom.users.some((viewer: any) => viewer.id === user.id))
protocol?.answersViewersClassroom?.some((classroom: any) => classroom.users?.some((viewer: any) => viewer.id === user.id))
);

return { creator, manager, applier, viewer, answersViewer };
Expand Down
44 changes: 42 additions & 2 deletions src/controllers/classroomController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const getClassroomUserRoles = async (user: User, classroom: any, classroomId: nu
}));

const creator = classroom?.creatorId === user.id;
const member = classroom?.users.some((u: any) => u.id === user.id);
const member = classroom?.users?.some((u: any) => u.id === user.id);
const institutionMember = classroom?.institutionId === user.institutionId;

return { creator, member, institutionMember };
Expand Down Expand Up @@ -69,6 +69,8 @@ const getClassroomUserActions = async (user: User, classroom: any, classroomId:
const toSearch = user.role !== UserRole.USER && user.role !== UserRole.GUEST;
// Anyone can perform getMy operation on classrooms
const toGetMy = true;
// Anyone can perform getManaged operation on classrooms
const toGetManaged = true;

return { toUpdate, toDelete, toGet, toGetAll, toSearch, toGetMy };
};
Expand Down Expand Up @@ -112,6 +114,7 @@ const checkAuthorization = async (user: User, classroomId: number | undefined, i
throw new Error('This user is not authorized to perform this action');
break;
case 'getMy':
case 'getManaged':
// Anyone can perform getMy operation on classrooms (since the result is filtered according to the user)
break;
}
Expand Down Expand Up @@ -268,6 +271,38 @@ export const getMyClassrooms = async (req: Request, res: Response): Promise<void
}
};

export const getManagedClassrooms = async (req: Request, res: Response): Promise<void> => {
try {
// User from Passport-JWT
const user = req.user as User;
// Check if user is authorized to get his managed classrooms
await checkAuthorization(user, undefined, undefined, 'getManaged');
// Prisma operation
const classrooms = await prismaClient.classroom.findMany({
where: {
...(user.role !== UserRole.ADMIN && {
// Admins can manage all classrooms
OR: [
...(user.role === UserRole.COORDINATOR ? [{ institutionId: user.institutionId }] : []), // Coordinators can manage classrooms from their institutions and users they created
{ creatorId: user.id }, // Publishers and appliers can only manage classrooms they created
],
}),
},
select: fields,
});
// Embed user actions in the response
const processedClassrooms = await Promise.all(
classrooms.map(async (classroom) => {
return { ...classroom, actions: await getClassroomUserActions(user, classroom, undefined) };
})
);

res.status(200).json({ message: 'My managed classrooms found.', data: processedClassrooms });
} catch (error: any) {
res.status(400).json(errorFormatter(error));
}
};

export const searchClassroomByName = async (req: Request, res: Response): Promise<void> => {
try {
// User from passport-jwt
Expand All @@ -283,7 +318,12 @@ export const searchClassroomByName = async (req: Request, res: Response): Promis
const { term } = await searchUserSchema.validate(req.body);
// Prisma operation
const classrooms = await prismaClient.classroom.findMany({
where: { name: { startsWith: term } },
where: {
name: { startsWith: term },
...(curUser.role !== UserRole.ADMIN && {
OR: [{ institutionId: curUser.institutionId }, { institutionId: null }],
}),
},
select: publicFields,
});
// Embed user actions in the response
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/institutionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ const getInstitutionUserRoles = async (user: User, institution: any, institution
include: { users: { select: { id: true, role: true } } },
}));

const member = institution.users.some((u: any) => u.id === user.id);
const coordinator = institution.users.some((u: any) => u.id === user.id && u.role === UserRole.COORDINATOR);
const member = institution.users?.some((u: any) => u.id === user.id);
const coordinator = institution.users?.some((u: any) => u.id === user.id && u.role === UserRole.COORDINATOR);

return { member, coordinator };
};
Expand Down
38 changes: 21 additions & 17 deletions src/controllers/protocolController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ const getProtocolUserRoles = async (user: User, protocol: any, protocolId: numbe
protocol?.visibility === VisibilityMode.PUBLIC ||
(protocol?.visibility === VisibilityMode.AUTHENTICATED && user.role !== UserRole.GUEST) ||
protocol?.viewersUser?.some((viewer: any) => viewer.id === user.id) ||
protocol?.viewersClassroom?.some((classroom: any) => classroom.users.some((viewer: any) => viewer.id === user.id))
protocol?.viewersClassroom?.some((classroom: any) => classroom.users?.some((viewer: any) => viewer.id === user.id))
);
const answersViewer = !!(
protocol?.answersVisibility === VisibilityMode.PUBLIC ||
(protocol?.answersVisibility === VisibilityMode.AUTHENTICATED && user.role !== UserRole.GUEST) ||
protocol?.answersViewersUser?.some((viewer: any) => viewer.id === user.id) ||
protocol?.answersViewersClassroom?.some((classroom: any) => classroom.users.some((viewer: any) => viewer.id === user.id))
protocol?.answersViewersClassroom?.some((classroom: any) => classroom.users?.some((viewer: any) => viewer.id === user.id))
);

return { creator, manager, applier, viewer, answersViewer };
Expand Down Expand Up @@ -1370,8 +1370,8 @@ export const getProtocol = async (req: Request, res: Response): Promise<void> =>
user.role !== UserRole.USER &&
(user.role === UserRole.ADMIN ||
processedProtocol.creator.id === user.id ||
processedProtocol.managers.some((manager) => manager.id === user.id) ||
processedProtocol.appliers.some((applier) => applier.id === user.id) ||
processedProtocol.managers?.some((manager) => manager.id === user.id) ||
processedProtocol.appliers?.some((applier) => applier.id === user.id) ||
processedProtocol.applicability === VisibilityMode.PUBLIC)
? processedProtocol
: {
Expand Down Expand Up @@ -1418,7 +1418,7 @@ export const getProtocolWithAnswers = async (req: Request, res: Response): Promi
if (
user.role !== UserRole.ADMIN &&
(user.id !== protocol.creator.id ||
!protocol.managers.some((manager) => manager.id === user.id) ||
!protocol.managers?.some((manager) => manager.id === user.id) ||
user.id !== application.applier.id ||
user.institutionId !== application.applier.institutionId ||
user.institutionId !== protocol.creator.institutionId ||
Expand All @@ -1433,21 +1433,25 @@ export const getProtocolWithAnswers = async (req: Request, res: Response): Promi
for (const page of protocol.pages) {
for (const itemGroup of page.itemGroups) {
for (const item of itemGroup.items) {
item.itemAnswers = item.itemAnswers.filter((itemAnswer) =>
protocol.applications.some((application) =>
application.answers.some((answer) => answer.id === itemAnswer.group.applicationAnswer.id)
)
item.itemAnswers = item.itemAnswers.filter(
(itemAnswer) =>
protocol.applications?.some(
(application) => application.answers?.some((answer) => answer.id === itemAnswer.group.applicationAnswer.id)
)
);
item.tableAnswers = item.tableAnswers.filter((tableAnswer) =>
protocol.applications.some((application) =>
application.answers.some((answer) => answer.id === tableAnswer.group.applicationAnswer.id)
)
item.tableAnswers = item.tableAnswers.filter(
(tableAnswer) =>
protocol.applications?.some(
(application) => application.answers?.some((answer) => answer.id === tableAnswer.group.applicationAnswer.id)
)
);
for (const itemOption of item.itemOptions) {
itemOption.optionAnswers = itemOption.optionAnswers.filter((optionAnswer) =>
protocol.applications.some((application) =>
application.answers.some((answer) => answer.id === optionAnswer.group.applicationAnswer.id)
)
itemOption.optionAnswers = itemOption.optionAnswers.filter(
(optionAnswer) =>
protocol.applications?.some(
(application) =>
application.answers?.some((answer) => answer.id === optionAnswer.group.applicationAnswer.id)
)
);
}
}
Expand Down
Loading

0 comments on commit 3bd5634

Please sign in to comment.