279 lines
8.8 KiB
TypeScript
279 lines
8.8 KiB
TypeScript
import { CorporateUser, Type, User } from "@/interfaces/user";
|
||
import { getGroupsForUser, getParticipantGroups, getUserGroups, getUsersGroups } from "./groups.be";
|
||
import { uniq } from "lodash";
|
||
import { getUserCodes } from "./codes.be";
|
||
import client from "@/lib/mongodb";
|
||
import { EntityWithRoles, WithEntities } from "@/interfaces/entity";
|
||
import { getEntity } from "./entities.be";
|
||
import { getRole } from "./roles.be";
|
||
import { groupAllowedEntitiesByPermissions } from "./permissions";
|
||
import { mapBy } from ".";
|
||
|
||
const db = client.db(process.env.MONGODB_DB);
|
||
|
||
export async function getUsers(filter?: object, limit = 0, sort = {}, projection = {}) {
|
||
return await db
|
||
.collection("users")
|
||
.find<User>(filter || {}, { projection: { _id: 0, ...projection } })
|
||
.limit(limit)
|
||
.sort(sort)
|
||
.toArray();
|
||
}
|
||
|
||
export async function searchUsers(searchInput?: string, limit = 50, page = 0, sort: object = { "name": 1 }, projection = {}, filter?: object) {
|
||
const compoundFilter = {
|
||
"compound": {
|
||
"should": [...(searchInput != "" ? [{
|
||
"autocomplete": {
|
||
"query": searchInput,
|
||
"path": "name",
|
||
}
|
||
}, {
|
||
"autocomplete": {
|
||
"query": searchInput,
|
||
"path": "email",
|
||
}
|
||
}] : [{ exists: { path: "name" } }])],
|
||
"minimumShouldMatch": 1,
|
||
|
||
}
|
||
}
|
||
|
||
const [{ users, totalUsers }] = await db
|
||
.collection("users").aggregate([
|
||
{
|
||
"$search": {
|
||
"index": "UsersByNameEmail",
|
||
...compoundFilter
|
||
}
|
||
},
|
||
...(filter ? [{
|
||
"$match": Object.entries(filter).reduce((accm, [key, value]) => {
|
||
if (value.length === 0) return accm
|
||
accm[key] = Array.isArray(value) ? { $in: value } : value
|
||
return accm;
|
||
}, {} as any)
|
||
}] : []),
|
||
{
|
||
$facet: {
|
||
totalUsers: [{ $count: "count" }],
|
||
users: [
|
||
{ $sort: sort },
|
||
{ $skip: limit * page },
|
||
{ $limit: limit },
|
||
{ "$project": { _id: 0, label: { $concat: ["$name", " - ", "$email"] }, value: "$id", ...projection } }
|
||
]
|
||
}
|
||
}
|
||
]).toArray();
|
||
return { users, total: totalUsers[0]?.count || 0 };
|
||
}
|
||
|
||
export async function countUsers(filter?: object) {
|
||
return await db
|
||
.collection("users")
|
||
.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;
|
||
|
||
const entities = await Promise.all(
|
||
user.entities.map(async (e) => {
|
||
const entity = await getEntity(e.id);
|
||
const role = await getRole(e.role);
|
||
|
||
return { entity, role };
|
||
}),
|
||
);
|
||
|
||
return { ...user, entities };
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
export async function getSpecificUsers(ids: string[], projection = {}) {
|
||
if (ids.length === 0) return [];
|
||
|
||
return await db
|
||
.collection("users")
|
||
.find<User>({ id: { $in: ids } }, { projection: { _id: 0, ...projection } })
|
||
.toArray();
|
||
}
|
||
|
||
export async function getEntityUsers(id: string, limit?: number, filter?: object, projection = {}) {
|
||
return await db
|
||
.collection("users")
|
||
.find<User>({ "entities.id": id, ...(filter || {}) }, { projection: { _id: 0, ...projection } })
|
||
.limit(limit || 0)
|
||
.toArray();
|
||
}
|
||
|
||
export async function countEntityUsers(id: string, filter?: object) {
|
||
return await db.collection("users").countDocuments({ "entities.id": id, ...(filter || {}) });
|
||
}
|
||
|
||
export async function getEntitiesUsers(ids: string[], filter?: object, limit?: number, projection = {}) {
|
||
return await db
|
||
.collection("users")
|
||
.find<User>({ "entities.id": { $in: ids }, ...(filter || {}) }, { projection })
|
||
.limit(limit || 0)
|
||
.toArray();
|
||
}
|
||
|
||
export async function countEntitiesUsers(ids: string[], filter?: object) {
|
||
return await db.collection("users").countDocuments({ "entities.id": { $in: ids }, ...(filter || {}) });
|
||
}
|
||
|
||
export async function getLinkedUsers(
|
||
userID?: string,
|
||
userType?: Type,
|
||
type?: Type,
|
||
page?: number,
|
||
size?: number,
|
||
sort?: string,
|
||
direction?: "asc" | "desc",
|
||
) {
|
||
const filters = {
|
||
...(!!type ? { type } : {}),
|
||
};
|
||
|
||
if (!userID || userType === "admin" || userType === "developer") {
|
||
const users = await db
|
||
.collection("users")
|
||
.find<User>(filters)
|
||
.sort(sort ? { [sort]: direction === "desc" ? -1 : 1 } : {})
|
||
.skip(page && size ? page * size : 0)
|
||
.limit(size || 0)
|
||
.toArray();
|
||
|
||
const total = await db.collection("users").countDocuments(filters);
|
||
return { users, total };
|
||
}
|
||
|
||
const adminGroups = await getUserGroups(userID);
|
||
const groups = await getUsersGroups(adminGroups.flatMap((x) => x.participants));
|
||
const belongingGroups = await getParticipantGroups(userID);
|
||
|
||
const participants = uniq([
|
||
...adminGroups.flatMap((x) => x.participants),
|
||
...(userType === "mastercorporate" ? groups.flat().flatMap((x) => x.participants) : []),
|
||
...(userType === "teacher" ? belongingGroups.flatMap((x) => x.participants) : []),
|
||
]);
|
||
|
||
// ⨯ [FirebaseError: Invalid Query. A non-empty array is required for 'in' filters.] {
|
||
if (participants.length === 0) return { users: [], total: 0 };
|
||
|
||
const users = await db
|
||
.collection("users")
|
||
.find<User>({ ...filters, id: { $in: participants } })
|
||
.skip(page && size ? page * size : 0)
|
||
.limit(size || 0)
|
||
.toArray();
|
||
const total = await db.collection("users").countDocuments({ ...filters, id: { $in: participants } });
|
||
|
||
return { users, total };
|
||
}
|
||
|
||
export async function getUserBalance(user: User) {
|
||
const codes = await getUserCodes(user.id);
|
||
if (user.type !== "corporate" && user.type !== "mastercorporate") return codes.length;
|
||
|
||
const groups = await getGroupsForUser(user.id);
|
||
const participants = uniq(groups.flatMap((x) => x.participants));
|
||
|
||
if (user.type === "corporate") return participants.length + codes.filter((x) => !participants.includes(x.userId || "")).length;
|
||
|
||
const participantUsers = await Promise.all(participants.map(getUser));
|
||
const corporateUsers = participantUsers.filter((x) => x?.type === "corporate") as CorporateUser[];
|
||
|
||
return (
|
||
corporateUsers.reduce((acc, curr) => acc + 0, 0) +
|
||
corporateUsers.length +
|
||
codes.filter((x) => !participants.includes(x.userId || "") && !corporateUsers.map((u) => u.id).includes(x.userId || "")).length
|
||
);
|
||
}
|
||
|
||
export const filterAllowedUsers = async (user: User, entities: EntityWithRoles[], projection = {}) => {
|
||
|
||
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, teachers, corporates, masterCorporates] = await Promise.all([
|
||
getEntitiesUsers(mapBy(allowedStudentEntities, 'id'), { type: "student" }, 0, projection),
|
||
getEntitiesUsers(mapBy(allowedTeacherEntities, 'id'), { type: "teacher" }, 0, projection),
|
||
getEntitiesUsers(mapBy(allowedCorporateEntities, 'id'), { type: "corporate" }, 0, projection),
|
||
getEntitiesUsers(mapBy(allowedMasterCorporateEntities, 'id'), { type: "mastercorporate" }, 0, projection),
|
||
])
|
||
|
||
return [...students, ...teachers, ...corporates, ...masterCorporates]
|
||
}
|
||
|
||
export const countAllowedUsers = async (user: User, entities: EntityWithRoles[]) => {
|
||
const {
|
||
["view_students"]: allowedStudentEntities,
|
||
["view_teachers"]: allowedTeacherEntities,
|
||
["view_corporates"]: allowedCorporateEntities,
|
||
["view_mastercorporates"]: allowedMasterCorporateEntities,
|
||
} = groupAllowedEntitiesByPermissions(user, entities, [
|
||
"view_students",
|
||
"view_teachers",
|
||
'view_corporates',
|
||
'view_mastercorporates',
|
||
]);
|
||
console.log(mapBy(allowedStudentEntities, 'id'))
|
||
const [student, teacher, corporate, mastercorporate] = await Promise.all([
|
||
countEntitiesUsers(mapBy(allowedStudentEntities, 'id'), { type: "student" }),
|
||
countEntitiesUsers(mapBy(allowedTeacherEntities, 'id'), { type: "teacher" }),
|
||
countEntitiesUsers(mapBy(allowedCorporateEntities, 'id'), { type: "corporate" }),
|
||
countEntitiesUsers(mapBy(allowedMasterCorporateEntities, 'id'), { type: "mastercorporate" }),
|
||
])
|
||
console.log(student)
|
||
return { student, teacher, corporate, mastercorporate }
|
||
}
|