ENCOA-316 ENCOA-317:

Refactor components to remove Layout wrapper and pass it in the App component , implemented a skeleton feedback while loading page and improved API calls related to Dashboard/User Profile
This commit is contained in:
José Marques Lima
2025-01-25 19:38:29 +00:00
parent 4d788e13b4
commit 37216e2a5a
56 changed files with 4440 additions and 2979 deletions

View File

@@ -1,6 +1,5 @@
import { User } from "@/interfaces/user";
import { IncomingMessage, ServerResponse } from "http";
import { IronSession } from "iron-session";
import { NextApiRequest, NextApiResponse } from "next";
import { getUser } from "./users.be";

View File

@@ -27,13 +27,72 @@ export const getAssignment = async (id: string) => {
return await db.collection("assignments").findOne<Assignment>({ id });
};
export const getAssignmentsByAssignee = async (id: string, filter?: { [key in keyof Partial<Assignment>]: any }) => {
return await db
.collection("assignments")
.find<Assignment>({ assignees: id, ...(!filter ? {} : filter) })
export const getAssignmentsByAssignee = async (id: string, filter?: {}, projection?: {}, sort?: {}) => {
return await db.collection("assignments")
.aggregate([
{ $match: { assignees: id, ...(!filter ? {} : filter) } },
...(sort ? [{ $sort: sort }] : []),
...(projection ? [{ $project: projection }] : []),
])
.toArray();
};
export const getAssignmentsForStudent = async (id: string, currentDate: string) => {
return await db.collection("assignments")
.aggregate([{
$match: {
assignees: id, archived: { $ne: true },
endDate: { $gte: currentDate },
$or: [
{ autoStart: true, startDate: { $lt: currentDate } },
{ start: true },
],
}
},
{ $sort: { startDate: 1 } },
{
$project: {
id: 1,
name: 1,
startDate: 1,
endDate: 1,
exams: {
$sortArray: {
input: {
$filter: {
input: "$exams",
as: "exam",
cond: { $eq: ["$$exam.assignee", id] },
},
},
sortBy: { module: 1 },
},
},
hasResults: {
$cond: {
if: {
$gt: [
{
$size: {
$filter: {
input: "$results",
as: "result",
cond: { $eq: ["$$result.userId", id] },
},
},
},
0,
],
},
then: true,
else: false,
},
}
}
}
]).toArray()
};
export const getAssignmentsByAssignerBetweenDates = async (id: string, startDate: Date, endDate: Date) => {
return await db.collection("assignments").find<Assignment>({ assigner: id }).toArray();
};

View File

@@ -32,7 +32,7 @@ export const getEntitiesWithRoles = async (ids?: string[]): Promise<EntityWithRo
export const getEntities = async (ids?: string[], projection = {}) => {
return await db
.collection("entities")
.find<Entity>(ids ? { id: { $in: ids } } : {}, projection)
.find<Entity>(ids ? { id: { $in: ids } } : {}, { projection })
.toArray();
};

View File

@@ -1,13 +1,13 @@
import { CEFR_STEPS } from "@/resources/grading";
import { getUserCorporate } from "@/utils/groups.be";
import { User } from "@/interfaces/user";
import { Grading } from "@/interfaces";
import client from "@/lib/mongodb";
const db = client.db(process.env.MONGODB_DB);
export const getGradingSystemByEntity = async (id: string) =>
(await db.collection("grading").findOne<Grading>({ entity: id })) || { steps: CEFR_STEPS, entity: "" };
export const getGradingSystemByEntity = async (id: string, projection?: {}) =>
(await db.collection("grading").findOne<Grading>({ entity: id }, {
projection: projection,
})) || { steps: CEFR_STEPS, entity: "" };
export const getGradingSystemByEntities = async (ids: string[]) =>
await db.collection("grading").find<Grading>({ entity: { $in: ids } }).toArray();

View File

@@ -82,8 +82,8 @@ export const getGroups = async (): Promise<WithEntity<Group>[]> => {
.aggregate<WithEntity<Group>>(addEntityToGroupPipeline).toArray()
};
export const getParticipantGroups = async (id: string) => {
return await db.collection("groups").find<Group>({ participants: id }).toArray();
export const getParticipantGroups = async (id: string, projection?: {}) => {
return await db.collection("groups").find<Group>({ participants: id }, { projection: projection }).toArray();
};
export const getParticipantsGroups = async (ids: string[]) => {

View File

@@ -36,6 +36,43 @@ export function checkAccess(user: User, types: Type[], permissions?: PermissionT
return true;
}
export function groupAllowedEntitiesByPermissions(
user: User,
entities: EntityWithRoles[],
permissions: RolePermission[]
): { [key: string]: EntityWithRoles[] } {
if (["admin", "developer"].includes(user?.type)) {
return permissions.reduce((acc, permission) => {
acc[permission] = entities;
return acc;
}, {} as { [key: string]: EntityWithRoles[] });
}
const userEntityMap = new Map(user.entities.map(e => [e.id, e]));
const roleCache = new Map<string, Role | null>();
return entities.reduce((acc, entity) => {
const userEntity = userEntityMap.get(entity.id);
const role = userEntity
? roleCache.get(userEntity.role) ??
(() => {
const foundRole = entity.roles.find(r => r.id === userEntity.role) || null;
roleCache.set(userEntity.role, foundRole);
return foundRole;
})()
: null;
permissions.forEach(permission => {
if (!acc[permission]) acc[permission] = [];
if (role && role.permissions.includes(permission)) {
acc[permission].push(entity);
}
});
return acc;
}, {} as { [key: string]: EntityWithRoles[] });
}
export function findAllowedEntities(user: User, entities: EntityWithRoles[], permission: RolePermission) {
if (["admin", "developer"].includes(user?.type)) return entities
@@ -52,7 +89,6 @@ export function findAllowedEntitiesSomePermissions(user: User, entities: EntityW
export function doesEntityAllow(user: User, entity: EntityWithRoles, permission: RolePermission) {
if (isAdmin(user)) return true
const userEntity = findBy(user.entities, 'id', entity?.id)
if (!userEntity) return false

View File

@@ -1,12 +1,140 @@
import {Stat} from "@/interfaces/user";
import { Stat } from "@/interfaces/user";
import client from "@/lib/mongodb";
const db = client.db(process.env.MONGODB_DB);
export const getStatsByUser = async (id: string) => await db.collection("stats").find<Stat>({user: id}).toArray();
export const getStatsByUser = async (id: string) => await db.collection("stats").find<Stat>({ user: id }).toArray();
export const getStatsByUsers = async (ids: string[]) =>
await db
.collection("stats")
.find<Stat>({user: {$in: ids}})
.find<Stat>({ user: { $in: ids } })
.toArray();
export const getDetailedStatsByUser = async (id: string, query?: string) => {
let aggregateArray: any[] = [
{ $match: { user: id } },
{ $sort: { "date": 1 } },
]
switch (query) {
case "stats":
{
aggregateArray = aggregateArray.concat([{
$group: {
_id: "$session",
modules: { $addToSet: "$module" },
documents: { $push: "$$ROOT" },
totalCorrect: { $sum: "$score.correct" },
totalQuestions: { $sum: "$score.total" }
}
},
{
$project: {
_id: 0,
hasAllModules: {
$eq: [
{ $size: { $setIntersection: ["$modules", ["reading", "listening", "writing", "speaking"]] } },
4
]
},
uniqueModulesCount: { $size: "$modules" },
averageScore: {
$cond: [
{ $gt: ["$totalQuestions", 0] },
{ $multiply: [{ $divide: ["$totalCorrect", "$totalQuestions"] }, 100] },
0
]
},
documents: 1
}
},
{
$group: {
_id: null,
fullExams: { $sum: { $cond: ["$hasAllModules", 1, 0] } },
uniqueModules: { $sum: "$uniqueModulesCount" },
averageScore: {
$avg: "$averageScore"
},
allStats: { $push: "$documents" }
}
},
{
$project: {
_id: 0,
fullExams: 1,
uniqueModules: 1,
averageScore: 1,
allStats: {
$reduce: {
input: "$allStats",
initialValue: [],
in: { $concatArrays: ["$$value", "$$this"] }
}
}
}
}])
}
break;
case "byModule": {
aggregateArray = aggregateArray.concat([{
$facet: {
moduleCounts: [
{
$group: {
_id: {
module: "$module",
session: "$session"
}
}
},
{
$group: {
_id: "$_id.module",
count: {
$count: {}
}
}
},
{
$project: {
_id: 0,
module: "$_id",
count: "$count"
}
}
],
allDocuments: [
{
$project: {
_id: 0
}
}
]
}
},
{
$project: {
moduleCount: {
$arrayToObject: {
$map: {
input: "$moduleCounts",
as: "module",
in: {
k: "$$module.module",
v: "$$module.count"
}
}
}
},
allDocs: "$allDocuments"
}
}])
}
default:
}
return await db.collection("stats").aggregate(aggregateArray).toArray().then((result) => query ? result[0] : result
);
}

View File

@@ -6,7 +6,7 @@ import client from "@/lib/mongodb";
import { EntityWithRoles, WithEntities } from "@/interfaces/entity";
import { getEntity } from "./entities.be";
import { getRole } from "./roles.be";
import { findAllowedEntities } from "./permissions";
import { findAllowedEntities, groupAllowedEntitiesByPermissions } from "./permissions";
import { mapBy } from ".";
const db = client.db(process.env.MONGODB_DB);
@@ -38,7 +38,7 @@ export async function searchUsers(searchInput?: string, limit = 50, page = 0, so
}
}
const [{ users, totalUsers }] = await db
.collection("users").aggregate([
{
@@ -75,6 +75,39 @@ export async function countUsers(filter?: object) {
.countDocuments(filter || {})
}
export async function countUsersByTypes(types: Type[]) {
return await db
.collection("users")
.aggregate([
{
$match: {
type: { $in: types } // Filter only specified types
}
},
{
$group: {
_id: "$type",
count: { $sum: 1 } // Count documents in each group
}
},
{
$group: {
_id: null,
counts: {
$push: { k: "$_id", v: "$count" } // Convert to key-value pairs
}
}
},
{
$project: {
_id: 0,
result: { $arrayToObject: "$counts" } // Convert key-value pairs to an object
}
}
]).toArray().then(([{ result }]) => result);
}
export async function getUserWithEntity(id: string): Promise<WithEntities<User> | undefined> {
const user = await db.collection("users").findOne<User>({ id: id }, { projection: { _id: 0 } });
if (!user) return undefined;
@@ -91,8 +124,8 @@ export async function getUserWithEntity(id: string): Promise<WithEntities<User>
return { ...user, entities };
}
export async function getUser(id: string): Promise<User | undefined> {
const user = await db.collection("users").findOne<User>({ id: id }, { projection: { _id: 0 } });
export async function getUser(id: string, projection = {}): Promise<User | undefined> {
const user = await db.collection("users").findOne<User>({ id: id }, { projection: { _id: 0, ...projection } });
return !!user ? user : undefined;
}
@@ -120,7 +153,7 @@ export async function countEntityUsers(id: string, filter?: object) {
export async function getEntitiesUsers(ids: string[], filter?: object, limit?: number, projection = {}) {
return await db
.collection("users")
.find<User>({ "entities.id": { $in: ids }, ...(filter || {}) }, projection)
.find<User>({ "entities.id": { $in: ids }, ...(filter || {}) }, { projection })
.limit(limit || 0)
.toArray();
}
@@ -199,29 +232,45 @@ export async function getUserBalance(user: User) {
}
export const filterAllowedUsers = async (user: User, entities: EntityWithRoles[]) => {
const studentsAllowedEntities = findAllowedEntities(user, entities, 'view_students')
const teachersAllowedEntities = findAllowedEntities(user, entities, 'view_teachers')
const corporateAllowedEntities = findAllowedEntities(user, entities, 'view_corporates')
const masterCorporateAllowedEntities = findAllowedEntities(user, entities, 'view_mastercorporates')
const {
["view_students"]: allowedStudentEntities,
["view_teachers"]: allowedTeacherEntities,
["view_corporates"]: allowedCorporateEntities,
["view_mastercorporates"]: allowedMasterCorporateEntities,
} = groupAllowedEntitiesByPermissions(user, entities, [
"view_students",
"view_teachers",
'view_corporates',
'view_mastercorporates',
]);
const students = await getEntitiesUsers(mapBy(studentsAllowedEntities, 'id'), { type: "student" })
const teachers = await getEntitiesUsers(mapBy(teachersAllowedEntities, 'id'), { type: "teacher" })
const corporates = await getEntitiesUsers(mapBy(corporateAllowedEntities, 'id'), { type: "corporate" })
const masterCorporates = await getEntitiesUsers(mapBy(masterCorporateAllowedEntities, 'id'), { type: "mastercorporate" })
const students = await getEntitiesUsers(mapBy(allowedStudentEntities, 'id'), { type: "student" })
const teachers = await getEntitiesUsers(mapBy(allowedTeacherEntities, 'id'), { type: "teacher" })
const corporates = await getEntitiesUsers(mapBy(allowedCorporateEntities, 'id'), { type: "corporate" })
const masterCorporates = await getEntitiesUsers(mapBy(allowedMasterCorporateEntities, 'id'), { type: "mastercorporate" })
return [...students, ...teachers, ...corporates, ...masterCorporates]
}
export const countAllowedUsers = async (user: User, entities: EntityWithRoles[]) => {
const studentsAllowedEntities = findAllowedEntities(user, entities, 'view_students')
const teachersAllowedEntities = findAllowedEntities(user, entities, 'view_teachers')
const corporateAllowedEntities = findAllowedEntities(user, entities, 'view_corporates')
const masterCorporateAllowedEntities = findAllowedEntities(user, entities, 'view_mastercorporates')
const {
["view_students"]: allowedStudentEntities,
["view_teachers"]: allowedTeacherEntities,
["view_corporates"]: allowedCorporateEntities,
["view_mastercorporates"]: allowedMasterCorporateEntities,
} = groupAllowedEntitiesByPermissions(user, entities, [
"view_students",
"view_teachers",
'view_corporates',
'view_mastercorporates',
]);
const student = await countEntitiesUsers(mapBy(studentsAllowedEntities, 'id'), { type: "student" })
const teacher = await countEntitiesUsers(mapBy(teachersAllowedEntities, 'id'), { type: "teacher" })
const corporate = await countEntitiesUsers(mapBy(corporateAllowedEntities, 'id'), { type: "corporate" })
const mastercorporate = await countEntitiesUsers(mapBy(masterCorporateAllowedEntities, 'id'), { type: "mastercorporate" })
const student = await countEntitiesUsers(mapBy(allowedStudentEntities, 'id'), { type: "student" })
const teacher = await countEntitiesUsers(mapBy(allowedTeacherEntities, 'id'), { type: "teacher" })
const corporate = await countEntitiesUsers(mapBy(allowedCorporateEntities, 'id'), { type: "corporate" })
const mastercorporate = await countEntitiesUsers(mapBy(allowedMasterCorporateEntities, 'id'), { type: "mastercorporate" })
return { student, teacher, corporate, mastercorporate }
}