Updated the backend so the users list only returns the correct ones

This commit is contained in:
Tiago Ribeiro
2024-09-06 09:33:30 +01:00
parent 680f4cfa95
commit 55cc9765e2
5 changed files with 163 additions and 179 deletions

View File

@@ -207,64 +207,6 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
</div> </div>
); );
const StudentsList = () => {
const filter = (x: User) =>
x.type === "student" &&
(!!selectedUser
? groups
.filter((g) => g.admin === selectedUser.id)
.flatMap((g) => g.participants)
.includes(x.id) || false
: groups.flatMap((g) => g.participants).includes(x.id));
return (
<UserList
user={user}
filters={[filter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
);
};
const TeachersList = () => {
const filter = (x: User) =>
x.type === "teacher" &&
(!!selectedUser
? groups
.filter((g) => g.admin === selectedUser.id)
.flatMap((g) => g.participants)
.includes(x.id) || false
: groups.flatMap((g) => g.participants).includes(x.id));
return (
<UserList
user={user}
filters={[filter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Teachers ({total})</h2>
</div>
)}
/>
);
};
const GroupsList = () => { const GroupsList = () => {
const filter = (x: Group) => x.admin === user.id || x.participants.includes(user.id); const filter = (x: Group) => x.admin === user.id || x.participants.includes(user.id);
@@ -518,8 +460,40 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
)} )}
</> </>
</Modal> </Modal>
{router.asPath === "/#students" && <StudentsList />} {router.asPath === "/#students" && (
{router.asPath === "/#teachers" && <TeachersList />} <UserList
user={user}
filters={[(x) => x.type === "student"]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
)}
{router.asPath === "/#teachers" && (
<UserList
user={user}
filters={[(x) => x.type === "teacher"]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Teachers ({total})</h2>
</div>
)}
/>
)}
{router.asPath === "/#groups" && <GroupsList />} {router.asPath === "/#groups" && <GroupsList />}
{router.asPath === "/#assignments" && ( {router.asPath === "/#assignments" && (
<AssignmentsPage <AssignmentsPage

View File

@@ -349,8 +349,8 @@ export default function MasterCorporateDashboard({user}: Props) {
); );
}, [assignments, groups, users]); }, [assignments, groups, users]);
const studentFilter = (user: User) => user.type === "student" && corporateUserGroups.includes(user.id); const studentFilter = (user: User) => user.type === "student";
const teacherFilter = (user: User) => user.type === "teacher" && corporateUserGroups.includes(user.id); const teacherFilter = (user: User) => user.type === "teacher";
const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id); const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id);
const UserDisplay = (displayUser: User) => ( const UserDisplay = (displayUser: User) => (
@@ -365,74 +365,7 @@ export default function MasterCorporateDashboard({user}: Props) {
</div> </div>
); );
const StudentsList = () => { const corporateUserFilter = (x: User) => x.type === "corporate";
const filter = (x: User) =>
x.type === "student" && (!!selectedUser ? corporateUserGroups.includes(x.id) || false : corporateUserGroups.includes(x.id));
return (
<UserList
user={user}
filters={[filter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
);
};
const TeachersList = () => {
const filter = (x: User) =>
x.type === "teacher" && (!!selectedUser ? corporateUserGroups.includes(x.id) || false : corporateUserGroups.includes(x.id));
return (
<UserList
user={user}
filters={[filter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Teachers ({total})</h2>
</div>
)}
/>
);
};
const corporateUserFilter = (x: User) =>
x.type === "corporate" && (!!selectedUser ? masterCorporateUserGroups.includes(x.id) || false : masterCorporateUserGroups.includes(x.id));
const CorporateList = () => {
return (
<UserList
user={user}
filters={[corporateUserFilter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Corporates ({total})</h2>
</div>
)}
/>
);
};
const GroupsList = () => { const GroupsList = () => {
return ( return (
@@ -704,10 +637,58 @@ export default function MasterCorporateDashboard({user}: Props) {
)} )}
</> </>
</Modal> </Modal>
{router.asPath === "/#students" && <StudentsList />} {router.asPath === "/#students" && (
{router.asPath === "/#teachers" && <TeachersList />} <UserList
user={user}
filters={[(x) => x.type === "student"]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
)}
{router.asPath === "/#teachers" && (
<UserList
user={user}
filters={[(x) => x.type === "teacher"]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Teachers ({total})</h2>
</div>
)}
/>
)}
{router.asPath === "/#groups" && <GroupsList />} {router.asPath === "/#groups" && <GroupsList />}
{router.asPath === "/#corporate" && <CorporateList />} {router.asPath === "/#corporate" && (
<UserList
user={user}
filters={[(x) => x.type === "corporate"]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Corporate ({total})</h2>
</div>
)}
/>
)}
{router.asPath === "/#assignments" && ( {router.asPath === "/#assignments" && (
<AssignmentsPage <AssignmentsPage
assignments={assignments} assignments={assignments}

View File

@@ -51,6 +51,7 @@ import usePermissions from "@/hooks/usePermissions";
import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments";
import AssignmentsPage from "./views/AssignmentsPage"; import AssignmentsPage from "./views/AssignmentsPage";
import {useRouter} from "next/router"; import {useRouter} from "next/router";
import useFilterStore from "@/stores/listFilterStore";
interface Props { interface Props {
user: User; user: User;
@@ -67,6 +68,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
const {permissions} = usePermissions(user.id); const {permissions} = usePermissions(user.id);
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id}); const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id});
const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
const router = useRouter(); const router = useRouter();
const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]);
@@ -90,7 +92,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
setShowModal(!!selectedUser && router.asPath === "/#"); setShowModal(!!selectedUser && router.asPath === "/#");
}, [selectedUser, router.asPath]); }, [selectedUser, router.asPath]);
const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id); const studentFilter = (user: User) => user.type === "student";
const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id); const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id);
@@ -106,35 +108,6 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
</div> </div>
); );
const StudentsList = () => {
const filter = (x: User) =>
x.type === "student" &&
(!!selectedUser
? groups
.filter((g) => g.admin === selectedUser.id)
.flatMap((g) => g.participants)
.includes(x.id) || false
: groups.flatMap((g) => g.participants).includes(x.id));
return (
<UserList
user={user}
filters={[filter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
);
};
const GroupsList = () => { const GroupsList = () => {
const filter = (x: Group) => x.admin === user.id; const filter = (x: Group) => x.admin === user.id;
@@ -285,16 +258,68 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
if (shouldReload) reload(); if (shouldReload) reload();
}} }}
onViewStudents={ onViewStudents={
selectedUser.type === "corporate" || selectedUser.type === "teacher" ? () => router.push("/#students") : undefined selectedUser.type === "corporate" || selectedUser.type === "teacher"
? () => {
appendUserFilters({
id: "view-students",
filter: (x: User) => x.type === "student",
});
appendUserFilters({
id: "belongs-to-admin",
filter: (x: User) =>
groups
.filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id))
.flatMap((g) => g.participants)
.includes(x.id),
});
router.push("/list/users");
}
: undefined
}
onViewTeachers={
selectedUser.type === "corporate" || selectedUser.type === "student"
? () => {
appendUserFilters({
id: "view-teachers",
filter: (x: User) => x.type === "teacher",
});
appendUserFilters({
id: "belongs-to-admin",
filter: (x: User) =>
groups
.filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id))
.flatMap((g) => g.participants)
.includes(x.id),
});
router.push("/list/users");
}
: undefined
} }
onViewTeachers={selectedUser.type === "corporate" ? () => router.push("/#teachers") : undefined}
user={selectedUser} user={selectedUser}
/> />
</div> </div>
)} )}
</> </>
</Modal> </Modal>
{router.asPath === "/#students" && <StudentsList />} {router.asPath === "/#students" && (
<UserList
user={user}
filters={[(x) => x.type === "student"]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => router.push("/")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
)}
{router.asPath === "/#groups" && <GroupsList />} {router.asPath === "/#groups" && <GroupsList />}
{router.asPath === "/#assignments" && ( {router.asPath === "/#assignments" && (
<AssignmentsPage <AssignmentsPage

View File

@@ -80,19 +80,15 @@ export default function UserList({
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (user && users) { if (users) {
const filterUsers = ["corporate", "teacher", "mastercorporate"].includes(user.type) const filteredUsers = filters.reduce((d, f) => d.filter(f), users);
? users.filter((u) => groups.flatMap((g) => g.participants).includes(u.id))
: users;
const filteredUsers = filters.reduce((d, f) => d.filter(f), filterUsers);
const sortedUsers = await asyncSorter<User>(filteredUsers, sortFunction); const sortedUsers = await asyncSorter<User>(filteredUsers, sortFunction);
setDisplayUsers([...sortedUsers]); setDisplayUsers([...sortedUsers]);
} }
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [user, users, sorter, groups]); }, [users, sorter]);
const deleteAccount = (user: User) => { const deleteAccount = (user: User) => {
if (!confirm(`Are you sure you want to delete ${user.name}'s account?`)) return; if (!confirm(`Are you sure you want to delete ${user.name}'s account?`)) return;

View File

@@ -4,6 +4,8 @@ import {app} from "@/firebase";
import {getFirestore, collection, getDocs} from "firebase/firestore"; import {getFirestore, collection, getDocs} from "firebase/firestore";
import {withIronSessionApiRoute} from "iron-session/next"; import {withIronSessionApiRoute} from "iron-session/next";
import {sessionOptions} from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import {getGroupsForUser} from "@/utils/groups.be";
import {uniq} from "lodash";
const db = getFirestore(app); const db = getFirestore(app);
@@ -16,11 +18,17 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
} }
const snapshot = await getDocs(collection(db, "users")); const snapshot = await getDocs(collection(db, "users"));
const users = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
res.status(200).json( if (!req.session.user) return res.status(200).json(users);
snapshot.docs.map((doc) => ({ if (req.session.user.type === "admin" || req.session.user.type === "developer") return res.status(200).json(users);
id: doc.id,
...doc.data(), const adminGroups = await getGroupsForUser(req.session.user.id);
})), const groups = await Promise.all(adminGroups.flatMap((x) => x.participants).map(async (x) => await getGroupsForUser(x)));
); const participants = uniq([...adminGroups.flatMap((x) => x.participants), ...groups.flat().flatMap((x) => x.participants)]);
res.status(200).json(users.filter((x) => participants.includes(x.id)));
} }