Merged in feature/ExamGenRework (pull request #128)
ENCOA-283 or ENCOA-282, I don't know someone deleted the issue Approved-by: Tiago Ribeiro
This commit is contained in:
@@ -195,6 +195,13 @@ const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ sta
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
{state.alreadyInClass && state.alreadyInClass.length > 0 && (
|
||||||
|
<li>
|
||||||
|
<div className="text-gray-700">
|
||||||
|
<span className="font-medium">{state.alreadyInClass.length}</span> users that are already assigned to the classroom
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
{errorCount > 0 && (
|
{errorCount > 0 && (
|
||||||
<li>
|
<li>
|
||||||
<div className="text-gray-700">
|
<div className="text-gray-700">
|
||||||
@@ -221,7 +228,7 @@ const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ sta
|
|||||||
</>
|
</>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal isOpen={showNotFoundModal} onClose={() => setShowNotFoundModal(false)}>
|
<Modal isOpen={showNotFoundModal} onClose={() => setShowNotFoundModal(false)} maxWidth="max-w-[85%]">
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2 mb-6">
|
<div className="flex items-center gap-2 mb-6">
|
||||||
<FaTimesCircle className="w-5 h-5 text-red-500" />
|
<FaTimesCircle className="w-5 h-5 text-red-500" />
|
||||||
@@ -231,7 +238,7 @@ const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ sta
|
|||||||
</>
|
</>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal isOpen={showOtherEntityModal} onClose={() => setShowOtherEntityModal(false)}>
|
<Modal isOpen={showOtherEntityModal} onClose={() => setShowOtherEntityModal(false)} maxWidth="max-w-[85%]">
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2 mb-6">
|
<div className="flex items-center gap-2 mb-6">
|
||||||
<FaExclamationCircle className="w-5 h-5 text-yellow-500" />
|
<FaExclamationCircle className="w-5 h-5 text-yellow-500" />
|
||||||
@@ -241,7 +248,7 @@ const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ sta
|
|||||||
</>
|
</>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal isOpen={showAlreadyInClassModal} onClose={() => setShowAlreadyInClassModal(false)}>
|
<Modal isOpen={showAlreadyInClassModal} onClose={() => setShowAlreadyInClassModal(false)} maxWidth="max-w-[85%]">
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2 mb-6">
|
<div className="flex items-center gap-2 mb-6">
|
||||||
<FaUsers className="w-5 h-5 text-blue-500" />
|
<FaUsers className="w-5 h-5 text-blue-500" />
|
||||||
|
|||||||
@@ -161,8 +161,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
readXlsxFile(
|
readXlsxFile(
|
||||||
file.content, { schema, ignoreEmptyRows: false })
|
file.content, { schema, ignoreEmptyRows: false })
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setClassroomTransferState((prev) => ({...prev, parsedExcel: data}))
|
setClassroomTransferState((prev) => ({ ...prev, parsedExcel: data }))
|
||||||
console.log(data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -236,7 +235,6 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
} as UserImport;
|
} as UserImport;
|
||||||
})
|
})
|
||||||
.filter((item): item is UserImport => item !== undefined);
|
.filter((item): item is UserImport => item !== undefined);
|
||||||
console.log(infos);
|
|
||||||
|
|
||||||
// On import reset state except excel parsing
|
// On import reset state except excel parsing
|
||||||
setClassroomTransferState((prev) => ({
|
setClassroomTransferState((prev) => ({
|
||||||
@@ -260,26 +258,26 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
const emails = classroomTransferState.imports.map((i) => i.email);
|
const emails = classroomTransferState.imports.map((i) => i.email);
|
||||||
const crossRefUsers = async () => {
|
const crossRefUsers = async () => {
|
||||||
try {
|
try {
|
||||||
console.log(user.entities);
|
|
||||||
const { data: nonExistantUsers } = await axios.post("/api/users/controller?op=dontExist", { emails });
|
const { data: nonExistantUsers } = await axios.post("/api/users/controller?op=dontExist", { emails });
|
||||||
const { data: nonEntityUsers } = await axios.post("/api/users/controller?op=entityCheck", { entities: user.entities, emails });
|
const { data: nonEntityUsers } = await axios.post("/api/users/controller?op=entityCheck", { entities: user.entities, emails });
|
||||||
const { data: alreadyPlaced } = await axios.post("/api/users/controller?op=crossRefClassrooms", {
|
const { data: alreadyPlaced } = await axios.post("/api/users/controller?op=crossRefClassrooms", {
|
||||||
sets: classroomTransferState.imports.map((info) => ({
|
sets: classroomTransferState.imports.map((info) => ({
|
||||||
email: info.email,
|
email: info.email,
|
||||||
classroom: info.groupName
|
classroom: info.groupName
|
||||||
}))
|
})),
|
||||||
|
entity
|
||||||
});
|
});
|
||||||
|
|
||||||
const excludeEmails = new Set([
|
const excludeEmails = new Set([
|
||||||
...nonExistantUsers,
|
...nonExistantUsers,
|
||||||
...nonEntityUsers,
|
...nonEntityUsers.map((o: any) => o.email),
|
||||||
...alreadyPlaced
|
...alreadyPlaced
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const filteredImports = classroomTransferState.imports.filter(i => !excludeEmails.has(i.email));
|
const filteredImports = classroomTransferState.imports.filter(i => !excludeEmails.has(i.email));
|
||||||
|
|
||||||
const nonExistantEmails = new Set(nonExistantUsers);
|
const nonExistantEmails = new Set(nonExistantUsers);
|
||||||
const nonEntityEmails = new Set(nonEntityUsers);
|
const nonEntityEmails = new Set(nonEntityUsers.map((o: any) => o.email));
|
||||||
const alreadyPlacedEmails = new Set(alreadyPlaced);
|
const alreadyPlacedEmails = new Set(alreadyPlaced);
|
||||||
|
|
||||||
setClassroomTransferState((prev) => ({
|
setClassroomTransferState((prev) => ({
|
||||||
@@ -287,7 +285,14 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
stage: 2,
|
stage: 2,
|
||||||
imports: filteredImports,
|
imports: filteredImports,
|
||||||
notFoundUsers: classroomTransferState.imports.filter(i => nonExistantEmails.has(i.email)),
|
notFoundUsers: classroomTransferState.imports.filter(i => nonExistantEmails.has(i.email)),
|
||||||
otherEntityUsers: classroomTransferState.imports.filter(i => nonEntityEmails.has(i.email)),
|
otherEntityUsers: classroomTransferState.imports.filter(i => nonEntityEmails.has(i.email))
|
||||||
|
.map(user => {
|
||||||
|
const nonEntityUser = nonEntityUsers.find((o: any) => o.email === user.email);
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
entityLabels: nonEntityUser?.names || []
|
||||||
|
};
|
||||||
|
}),
|
||||||
alreadyInClass: classroomTransferState.imports.filter(i => alreadyPlacedEmails.has(i.email)),
|
alreadyInClass: classroomTransferState.imports.filter(i => alreadyPlacedEmails.has(i.email)),
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -299,14 +304,14 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
crossRefUsers();
|
crossRefUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [classroomTransferState.imports, user.entities, classroomTransferState.stage])
|
}, [classroomTransferState.imports, user.entities, classroomTransferState.stage, entity])
|
||||||
|
|
||||||
// Stage 3 - Classroom Filter
|
// Stage 3 - Classroom Filter
|
||||||
// - See if there are classrooms with same name but different admin
|
// - See if there are classrooms with same name but different admin
|
||||||
// - Find which new classrooms need to be created
|
// - Find which new classrooms need to be created
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const crossRefClassrooms = async () => {
|
const crossRefClassrooms = async () => {
|
||||||
const classrooms = new Set(classroomTransferState.imports.map((i) => i.groupName));
|
const classrooms = Array.from(new Set(classroomTransferState.imports.map((i) => i.groupName)));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: notOwnedClassroomsSameName } = await axios.post("/api/groups/controller?op=crossRefOwnership", {
|
const { data: notOwnedClassroomsSameName } = await axios.post("/api/groups/controller?op=crossRefOwnership", {
|
||||||
@@ -362,7 +367,42 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const groupedUsers = classroomTransferState.imports.reduce((acc, user) => {
|
|
||||||
|
const getIds = async () => {
|
||||||
|
try {
|
||||||
|
const { data: emailIdMap } = await axios.post("/api/users/controller?op=getIds", {
|
||||||
|
emails: classroomTransferState.imports.map((u) => u.email)
|
||||||
|
});
|
||||||
|
|
||||||
|
return classroomTransferState.imports.map(importUser => {
|
||||||
|
const matchingUser = emailIdMap.find((mapping: any) => mapping.email === importUser.email);
|
||||||
|
return {
|
||||||
|
...importUser,
|
||||||
|
id: matchingUser?.id || undefined
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Something went wrong, please try again later!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const imports = await getIds();
|
||||||
|
|
||||||
|
if (!imports) return;
|
||||||
|
|
||||||
|
|
||||||
|
await axios.post("/api/groups/controller?op=deletePriorEntitiesGroups", {
|
||||||
|
ids: imports.map((u) => u.id),
|
||||||
|
entity
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios.post("/api/users/controller?op=assignToEntity", {
|
||||||
|
ids: imports.map((u) => u.id),
|
||||||
|
entity
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const groupedUsers = imports.reduce((acc, user) => {
|
||||||
if (!acc[user.groupName]) {
|
if (!acc[user.groupName]) {
|
||||||
acc[user.groupName] = [];
|
acc[user.groupName] = [];
|
||||||
}
|
}
|
||||||
@@ -385,11 +425,15 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
return axios.post('/api/groups', groupData);
|
return axios.post('/api/groups', groupData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const existingGroupUsers = Object.fromEntries(
|
const existingGroupUsers = Object.fromEntries(
|
||||||
Object.entries(groupedUsers)
|
Object.entries(groupedUsers)
|
||||||
.filter(([groupName]) => !classroomTransferState.classroomsToCreate.includes(groupName))
|
.filter(([groupName]) => !classroomTransferState.classroomsToCreate.includes(groupName))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let updatePromises: Promise<any>[] = [];
|
||||||
|
|
||||||
|
if (Object.keys(existingGroupUsers).length > 0) {
|
||||||
const { groups: groupNameToId, users: userEmailToId } = await axios.post('/api/groups?op=getIds', {
|
const { groups: groupNameToId, users: userEmailToId } = await axios.post('/api/groups?op=getIds', {
|
||||||
names: Object.keys(existingGroupUsers),
|
names: Object.keys(existingGroupUsers),
|
||||||
userEmails: Object.values(existingGroupUsers).flat().map(user => user.email)
|
userEmails: Object.values(existingGroupUsers).flat().map(user => user.email)
|
||||||
@@ -400,12 +444,13 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, any[]>);
|
}, {} as Record<string, any[]>);
|
||||||
|
|
||||||
const updatePromises = Object.entries(existingGroupsWithIds).map(([groupId, users]) => {
|
updatePromises = Object.entries(existingGroupsWithIds).map(([groupId, users]) => {
|
||||||
const userIds = users.map(user => userEmailToId[user.email]);
|
const userIds = users.map(user => userEmailToId[user.email]);
|
||||||
return axios.patch(`/api/groups/${groupId}`, {
|
return axios.patch(`/api/groups/${groupId}`, {
|
||||||
participants: userIds
|
participants: userIds
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
...createGroupPromises,
|
...createGroupPromises,
|
||||||
|
|||||||
@@ -51,12 +51,28 @@ const columns = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const institutionsColumn = columnHelper.accessor('entityLabels', {
|
||||||
|
cell: (info): string => {
|
||||||
|
const value = info.getValue();
|
||||||
|
if (!value || value.length === 0) {
|
||||||
|
return 'None';
|
||||||
|
}
|
||||||
|
return value.join(', ');
|
||||||
|
},
|
||||||
|
header: () => 'Institutions',
|
||||||
|
}) as unknown as typeof columns[0];
|
||||||
|
|
||||||
const UserTable: React.FC<{ users: UserImport[] }> = ({ users }) => {
|
const UserTable: React.FC<{ users: UserImport[] }> = ({ users }) => {
|
||||||
const [globalFilter, setGlobalFilter] = useState('');
|
const [globalFilter, setGlobalFilter] = useState('');
|
||||||
|
|
||||||
|
const tableColumns = [...columns];
|
||||||
|
if (users.some(user => 'entityLabels' in user)) {
|
||||||
|
tableColumns.push(institutionsColumn);
|
||||||
|
}
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: users,
|
data: users,
|
||||||
columns,
|
columns: tableColumns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ export interface UserImport {
|
|||||||
passport_id: string;
|
passport_id: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
};
|
};
|
||||||
|
entityLabels?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
case 'getIds':
|
case 'getIds':
|
||||||
res.status(200).json(await getIds(req.body));
|
res.status(200).json(await getIds(req.body));
|
||||||
break;
|
break;
|
||||||
|
case 'deletePriorEntitiesGroups':
|
||||||
|
await deletePriorEntitiesGroups(req.body);
|
||||||
|
res.status(200).json({ ok: true });
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
res.status(400).json({ error: 'Invalid operation!' })
|
res.status(400).json({ error: 'Invalid operation!' })
|
||||||
}
|
}
|
||||||
@@ -34,18 +38,42 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function crossRefOwnership(body: any): Promise<string[]> {
|
async function crossRefOwnership(body: any): Promise<string[]> {
|
||||||
const { userId, classrooms } = body;
|
const { userId, classrooms, entity } = body;
|
||||||
|
|
||||||
// First find which classrooms from input exist
|
|
||||||
const existingClassrooms = await db.collection('groups')
|
const existingClassrooms = await db.collection('groups')
|
||||||
.find({ name: { $in: classrooms } })
|
.find({
|
||||||
|
name: { $in: classrooms },
|
||||||
|
admin: { $ne: userId }
|
||||||
|
})
|
||||||
.project({ name: 1, admin: 1, _id: 0 })
|
.project({ name: 1, admin: 1, _id: 0 })
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
// From those existing classrooms, return the ones where user is NOT the admin
|
if (existingClassrooms.length === 0) {
|
||||||
return existingClassrooms
|
return [];
|
||||||
.filter(classroom => classroom.admin !== userId)
|
}
|
||||||
.map(classroom => classroom.name);
|
|
||||||
|
const adminUsers = await db.collection('users')
|
||||||
|
.find({
|
||||||
|
id: { $in: existingClassrooms.map(classroom => classroom.admin) }
|
||||||
|
})
|
||||||
|
.project({ id: 1, entities: 1, _id: 0 })
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const adminEntitiesMap = new Map(
|
||||||
|
adminUsers.map(admin => [
|
||||||
|
admin.id,
|
||||||
|
admin.entities?.map((e: any) => e.id) || []
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
return Array.from(new Set(
|
||||||
|
existingClassrooms
|
||||||
|
.filter(classroom => {
|
||||||
|
const adminEntities = adminEntitiesMap.get(classroom.admin) || [];
|
||||||
|
return adminEntities.includes(entity);
|
||||||
|
})
|
||||||
|
.map(classroom => classroom.name)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getIds(body: any): Promise<Record<string, string>> {
|
async function getIds(body: any): Promise<Record<string, string>> {
|
||||||
@@ -72,3 +100,31 @@ async function getIds(body: any): Promise<Record<string, string>> {
|
|||||||
}, {} as Record<string, string>)
|
}, {} as Record<string, string>)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function deletePriorEntitiesGroups(body: any) {
|
||||||
|
const { ids, entity } = body;
|
||||||
|
|
||||||
|
if (!Array.isArray(ids) || ids.length === 0 || !entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await db.collection('users')
|
||||||
|
.find({ id: { $in: ids } })
|
||||||
|
.project({ id: 1, entities: 1, _id: 0 })
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
// if the user doesn't have the target entity mark them for all groups deletion
|
||||||
|
const toDeleteUserIds = users
|
||||||
|
.filter(user => !user.entities?.some((e: any) => e.id === entity))
|
||||||
|
.map(user => user.id);
|
||||||
|
|
||||||
|
if (toDeleteUserIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.collection('groups').updateMany(
|
||||||
|
{ participants: { $in: toDeleteUserIds } },
|
||||||
|
{ $pull: { participants: { $in: toDeleteUserIds } } } as any
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
res.status(200).json(await entityCheck(req.body));
|
res.status(200).json(await entityCheck(req.body));
|
||||||
break;
|
break;
|
||||||
case 'crossRefClassrooms':
|
case 'crossRefClassrooms':
|
||||||
res.status(200).json(await crossRefClassrooms(req.body.sets));
|
res.status(200).json(await crossRefClassrooms(req.body));
|
||||||
|
break;
|
||||||
|
case 'getIds':
|
||||||
|
res.status(200).json(await getIds(req.body.emails));
|
||||||
|
break;
|
||||||
|
case 'assignToEntity':
|
||||||
|
await assignToEntity(req.body);
|
||||||
|
res.status(200).json({"ok": true});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
res.status(400).json({ error: 'Invalid operation!' })
|
res.status(400).json({ error: 'Invalid operation!' })
|
||||||
@@ -68,7 +75,10 @@ async function dontExist(emails: string[]): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function entityCheck(body: Record<string, any>): Promise<string[]> {
|
async function entityCheck(body: Record<string, any>): Promise<Array<{
|
||||||
|
email: string;
|
||||||
|
names?: string[];
|
||||||
|
}>> {
|
||||||
const { entities, emails } = body;
|
const { entities, emails } = body;
|
||||||
|
|
||||||
const pipeline = [
|
const pipeline = [
|
||||||
@@ -99,20 +109,65 @@ async function entityCheck(body: Record<string, any>): Promise<string[]> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Project only the email field
|
// Unwind the entities array (if it exists)
|
||||||
|
{
|
||||||
|
$unwind: {
|
||||||
|
path: "$entities",
|
||||||
|
preserveNullAndEmptyArrays: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Lookup entity details from entities collection
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: 'entities',
|
||||||
|
localField: 'entities.id',
|
||||||
|
foreignField: 'id',
|
||||||
|
as: 'entityDetails'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Unwind the entityDetails array
|
||||||
|
{
|
||||||
|
$unwind: {
|
||||||
|
path: "$entityDetails",
|
||||||
|
preserveNullAndEmptyArrays: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Group by email to collect all entity names in an array
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: "$email",
|
||||||
|
email: { $first: "$email" },
|
||||||
|
name: {
|
||||||
|
$push: {
|
||||||
|
$cond: [
|
||||||
|
{ $ifNull: ["$entityDetails.label", false] },
|
||||||
|
"$entityDetails.label",
|
||||||
|
"$$REMOVE"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Final projection to clean up
|
||||||
{
|
{
|
||||||
$project: {
|
$project: {
|
||||||
_id: 0,
|
_id: 0,
|
||||||
email: 1
|
email: 1,
|
||||||
|
name: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = await db.collection('users').aggregate(pipeline).toArray();
|
const results = await db.collection('users').aggregate(pipeline).toArray();
|
||||||
return results.map((result: any) => result.email);
|
return results.map(result => ({
|
||||||
|
email: result.email as string,
|
||||||
|
names: result.name as string[] | undefined
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function crossRefClassrooms(sets: { email: string, classroom: string }[]) {
|
async function crossRefClassrooms(body: any) {
|
||||||
|
const { sets, entity } = body as { sets: { email: string, classroom: string }[], entity: string };
|
||||||
|
|
||||||
const pipeline = [
|
const pipeline = [
|
||||||
// Match users with the provided emails
|
// Match users with the provided emails
|
||||||
{
|
{
|
||||||
@@ -132,7 +187,6 @@ async function crossRefClassrooms(sets: { email: string, classroom: string }[])
|
|||||||
$and: [
|
$and: [
|
||||||
{ $in: ['$$userId', '$participants'] },
|
{ $in: ['$$userId', '$participants'] },
|
||||||
{
|
{
|
||||||
// Match the classroom that corresponds to this user's email
|
|
||||||
$let: {
|
$let: {
|
||||||
vars: {
|
vars: {
|
||||||
matchingSet: {
|
matchingSet: {
|
||||||
@@ -153,6 +207,38 @@ async function crossRefClassrooms(sets: { email: string, classroom: string }[])
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Lookup admin's entities
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: 'users',
|
||||||
|
let: { adminId: '$admin' },
|
||||||
|
pipeline: [
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
$expr: { $eq: ['$id', '$$adminId'] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
as: 'adminInfo'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Filter where admin has the target entity
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
$expr: {
|
||||||
|
$in: [
|
||||||
|
entity,
|
||||||
|
{
|
||||||
|
$map: {
|
||||||
|
input: { $arrayElemAt: ['$adminInfo.entities', 0] },
|
||||||
|
as: 'entityObj',
|
||||||
|
in: '$$entityObj.id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
as: 'matchingGroups'
|
as: 'matchingGroups'
|
||||||
@@ -176,3 +262,50 @@ async function crossRefClassrooms(sets: { email: string, classroom: string }[])
|
|||||||
const results = await db.collection('users').aggregate(pipeline).toArray();
|
const results = await db.collection('users').aggregate(pipeline).toArray();
|
||||||
return results.map((result: any) => result.email);
|
return results.map((result: any) => result.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getIds(emails: string[]): Promise<Array<{ email: string; id: string }>> {
|
||||||
|
const users = await db.collection('users')
|
||||||
|
.find({ email: { $in: emails } })
|
||||||
|
.project({ email: 1, id: 1, _id: 0 })
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
return users.map(user => ({
|
||||||
|
email: user.email,
|
||||||
|
id: user.id
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function assignToEntity(body: any) {
|
||||||
|
const { ids, entity } = body;
|
||||||
|
|
||||||
|
if (!Array.isArray(ids) || ids.length === 0 || !entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await db.collection('users')
|
||||||
|
.find({ id: { $in: ids } })
|
||||||
|
.project({ id: 1, entities: 1 })
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const toUpdateUsers = users.filter((u) => u.entities[0].id !== entity);
|
||||||
|
|
||||||
|
if (toUpdateUsers.length > 0) {
|
||||||
|
const writes = users.map(user => ({
|
||||||
|
updateOne: {
|
||||||
|
filter: { id: user.id },
|
||||||
|
update: {
|
||||||
|
$set: {
|
||||||
|
entities: [{
|
||||||
|
id: entity,
|
||||||
|
role: user.entities?.[0]?.role
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
db.collection('users').bulkWrite(writes);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user