@studious-lms/server 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/models/school.d.ts +175 -0
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +75 -2
- package/dist/models/school.js.map +1 -1
- package/dist/routers/_app.d.ts +228 -0
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/school.d.ts +114 -0
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +32 -3
- package/dist/routers/school.js.map +1 -1
- package/dist/services/school.d.ts +94 -0
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +116 -4
- package/dist/services/school.js.map +1 -1
- package/package.json +1 -1
- package/src/models/school.ts +89 -0
- package/src/routers/school.ts +45 -0
- package/src/services/school.ts +178 -1
package/src/services/school.ts
CHANGED
|
@@ -23,17 +23,24 @@ import {
|
|
|
23
23
|
countActiveAdmins,
|
|
24
24
|
createSchoolInvite,
|
|
25
25
|
createSchoolWithAdmin,
|
|
26
|
+
deleteSchoolClassRecord,
|
|
26
27
|
findActiveSchoolAdminMembership,
|
|
27
28
|
findPendingInviteByToken,
|
|
29
|
+
findSchoolClass,
|
|
28
30
|
findSchoolMembership,
|
|
29
31
|
getSchoolById,
|
|
32
|
+
getSchoolMemberDetail,
|
|
30
33
|
getSchoolOverview,
|
|
34
|
+
listActiveSchoolMemberships,
|
|
35
|
+
listMemberClasses,
|
|
36
|
+
listRecentSchoolMembers,
|
|
31
37
|
listSchoolClasses,
|
|
32
38
|
listSchoolInvites,
|
|
33
39
|
listSchoolMembers,
|
|
34
40
|
listUserSchoolMemberships,
|
|
35
41
|
removeSchoolMembershipPreservingContent,
|
|
36
42
|
revokeSchoolInvite,
|
|
43
|
+
updateSchoolClassMembers,
|
|
37
44
|
updateSchoolMembershipRole,
|
|
38
45
|
updateSchoolRecord,
|
|
39
46
|
upsertSchoolMembership,
|
|
@@ -133,7 +140,10 @@ export async function updateSchoolDetails(
|
|
|
133
140
|
|
|
134
141
|
export async function getAdminOverview(userId: string, schoolId: string) {
|
|
135
142
|
await requireSchoolAdmin(schoolId, userId);
|
|
136
|
-
const school = await
|
|
143
|
+
const [school, recentMembers] = await Promise.all([
|
|
144
|
+
getSchoolOverview(schoolId),
|
|
145
|
+
listRecentSchoolMembers(schoolId),
|
|
146
|
+
]);
|
|
137
147
|
if (!school) {
|
|
138
148
|
throw new TRPCError({ code: "NOT_FOUND", message: "School not found" });
|
|
139
149
|
}
|
|
@@ -159,6 +169,7 @@ export async function getAdminOverview(userId: string, schoolId: string) {
|
|
|
159
169
|
},
|
|
160
170
|
recentClasses: school.classes,
|
|
161
171
|
recentInvites: school.invites,
|
|
172
|
+
recentMembers,
|
|
162
173
|
},
|
|
163
174
|
};
|
|
164
175
|
}
|
|
@@ -265,6 +276,172 @@ export async function updateMemberRole(
|
|
|
265
276
|
return { membership };
|
|
266
277
|
}
|
|
267
278
|
|
|
279
|
+
export async function getMemberDetail(
|
|
280
|
+
userId: string,
|
|
281
|
+
input: { schoolId: string; memberUserId: string }
|
|
282
|
+
) {
|
|
283
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
284
|
+
const membership = await getSchoolMemberDetail(
|
|
285
|
+
input.schoolId,
|
|
286
|
+
input.memberUserId
|
|
287
|
+
);
|
|
288
|
+
if (!membership) {
|
|
289
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Member not found" });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const [teachingClasses, enrolledClasses] = await listMemberClasses(
|
|
293
|
+
input.schoolId,
|
|
294
|
+
input.memberUserId
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const toClassSummary = (cls: (typeof teachingClasses)[number]) => ({
|
|
298
|
+
id: cls.id,
|
|
299
|
+
name: cls.name,
|
|
300
|
+
section: cls.section,
|
|
301
|
+
subject: cls.subject,
|
|
302
|
+
color: cls.color,
|
|
303
|
+
teacherCount: cls._count.teachers,
|
|
304
|
+
studentCount: cls._count.students,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
membership,
|
|
309
|
+
teachingClasses: teachingClasses.map(toClassSummary),
|
|
310
|
+
enrolledClasses: enrolledClasses.map(toClassSummary),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export async function bulkUpdateMemberRoles(
|
|
315
|
+
userId: string,
|
|
316
|
+
input: {
|
|
317
|
+
schoolId: string;
|
|
318
|
+
memberUserIds: string[];
|
|
319
|
+
role: SchoolMembershipRole;
|
|
320
|
+
}
|
|
321
|
+
) {
|
|
322
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
323
|
+
const memberships = await listActiveSchoolMemberships(
|
|
324
|
+
input.schoolId,
|
|
325
|
+
input.memberUserIds
|
|
326
|
+
);
|
|
327
|
+
if (memberships.length === 0) {
|
|
328
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "No members found" });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (input.role !== "ADMIN") {
|
|
332
|
+
const demotedAdmins = memberships.filter((m) => m.role === "ADMIN").length;
|
|
333
|
+
if (demotedAdmins > 0) {
|
|
334
|
+
const adminCount = await countActiveAdmins(input.schoolId);
|
|
335
|
+
if (adminCount - demotedAdmins < 1) {
|
|
336
|
+
throw new TRPCError({
|
|
337
|
+
code: "BAD_REQUEST",
|
|
338
|
+
message: "A school must have at least one active admin",
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
await prisma.schoolMembership.updateMany({
|
|
345
|
+
where: {
|
|
346
|
+
schoolId: input.schoolId,
|
|
347
|
+
userId: { in: memberships.map((m) => m.userId) },
|
|
348
|
+
status: "ACTIVE",
|
|
349
|
+
},
|
|
350
|
+
data: { role: input.role },
|
|
351
|
+
});
|
|
352
|
+
return { updatedCount: memberships.length };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export async function bulkRemoveMembers(
|
|
356
|
+
userId: string,
|
|
357
|
+
input: { schoolId: string; memberUserIds: string[] }
|
|
358
|
+
) {
|
|
359
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
360
|
+
const targetIds = input.memberUserIds.filter((id) => id !== userId);
|
|
361
|
+
const memberships = await listActiveSchoolMemberships(
|
|
362
|
+
input.schoolId,
|
|
363
|
+
targetIds
|
|
364
|
+
);
|
|
365
|
+
if (memberships.length === 0) {
|
|
366
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "No members found" });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const removedAdmins = memberships.filter((m) => m.role === "ADMIN").length;
|
|
370
|
+
if (removedAdmins > 0) {
|
|
371
|
+
const adminCount = await countActiveAdmins(input.schoolId);
|
|
372
|
+
if (adminCount - removedAdmins < 1) {
|
|
373
|
+
throw new TRPCError({
|
|
374
|
+
code: "BAD_REQUEST",
|
|
375
|
+
message: "A school must have at least one active admin",
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for (const membership of memberships) {
|
|
381
|
+
await removeSchoolMembershipPreservingContent(
|
|
382
|
+
input.schoolId,
|
|
383
|
+
membership.userId
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return { removedCount: memberships.length };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export async function deleteSchoolClass(
|
|
390
|
+
userId: string,
|
|
391
|
+
input: { schoolId: string; classId: string }
|
|
392
|
+
) {
|
|
393
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
394
|
+
const cls = await findSchoolClass(input.schoolId, input.classId);
|
|
395
|
+
if (!cls) {
|
|
396
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Class not found" });
|
|
397
|
+
}
|
|
398
|
+
await deleteSchoolClassRecord(input.schoolId, input.classId);
|
|
399
|
+
return { deletedClass: { id: input.classId } };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function updateClassMembers(
|
|
403
|
+
userId: string,
|
|
404
|
+
input: {
|
|
405
|
+
schoolId: string;
|
|
406
|
+
classId: string;
|
|
407
|
+
addTeacherIds?: string[];
|
|
408
|
+
removeTeacherIds?: string[];
|
|
409
|
+
addStudentIds?: string[];
|
|
410
|
+
removeStudentIds?: string[];
|
|
411
|
+
}
|
|
412
|
+
) {
|
|
413
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
414
|
+
const cls = await findSchoolClass(input.schoolId, input.classId);
|
|
415
|
+
if (!cls) {
|
|
416
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Class not found" });
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const addTeacherIds = input.addTeacherIds ?? [];
|
|
420
|
+
const addStudentIds = input.addStudentIds ?? [];
|
|
421
|
+
const addedIds = [...addTeacherIds, ...addStudentIds];
|
|
422
|
+
if (addedIds.length > 0) {
|
|
423
|
+
const memberships = await listActiveSchoolMemberships(
|
|
424
|
+
input.schoolId,
|
|
425
|
+
addedIds
|
|
426
|
+
);
|
|
427
|
+
const activeIds = new Set(memberships.map((m) => m.userId));
|
|
428
|
+
if (addedIds.some((id) => !activeIds.has(id))) {
|
|
429
|
+
throw new TRPCError({
|
|
430
|
+
code: "BAD_REQUEST",
|
|
431
|
+
message: "All added users must be active members of this school",
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const updated = await updateSchoolClassMembers(input.classId, {
|
|
437
|
+
addTeacherIds,
|
|
438
|
+
removeTeacherIds: input.removeTeacherIds ?? [],
|
|
439
|
+
addStudentIds,
|
|
440
|
+
removeStudentIds: input.removeStudentIds ?? [],
|
|
441
|
+
});
|
|
442
|
+
return { class: updated };
|
|
443
|
+
}
|
|
444
|
+
|
|
268
445
|
export async function removeMember(
|
|
269
446
|
userId: string,
|
|
270
447
|
input: { schoolId: string; memberUserId: string }
|