@studious-lms/server 1.11.0 → 1.13.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/.env.example +3 -0
- package/dist/index.js +40 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/config/cors.d.ts.map +1 -1
- package/dist/lib/config/cors.js +7 -2
- package/dist/lib/config/cors.js.map +1 -1
- package/dist/lib/config/env.d.ts +8 -0
- package/dist/lib/config/env.d.ts.map +1 -1
- package/dist/lib/config/env.js +5 -2
- package/dist/lib/config/env.js.map +1 -1
- package/dist/lib/pusher.d.ts +2 -1
- package/dist/lib/pusher.d.ts.map +1 -1
- package/dist/lib/pusher.js +4 -3
- package/dist/lib/pusher.js.map +1 -1
- package/dist/models/agenda.d.ts +6 -0
- package/dist/models/agenda.d.ts.map +1 -1
- package/dist/models/announcement.d.ts +3 -0
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/assignment.d.ts +61 -0
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/auth.d.ts +6 -0
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/class.d.ts +28 -0
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +12 -3
- package/dist/models/class.js.map +1 -1
- package/dist/models/comment.d.ts +13 -0
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/comment.js +3 -2
- package/dist/models/comment.js.map +1 -1
- package/dist/models/conversation.d.ts +17 -0
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/conversation.js +13 -2
- package/dist/models/conversation.js.map +1 -1
- package/dist/models/event.d.ts +12 -0
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/file.d.ts +38 -0
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/folder.d.ts +6 -0
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +6 -0
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/newtonChat.d.ts +3 -0
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/school.d.ts +3 -0
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/section.d.ts +3 -0
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/worksheet.d.ts +62 -0
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +37 -7
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +59 -14
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/routers/_app.d.ts +4641 -2285
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +6 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/admin.d.ts +190 -0
- package/dist/routers/admin.d.ts.map +1 -0
- package/dist/routers/admin.js +50 -0
- package/dist/routers/admin.js.map +1 -0
- package/dist/routers/agenda.d.ts +6 -0
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/announcement.d.ts +6 -0
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +6 -5
- package/dist/routers/announcement.js.map +1 -1
- package/dist/routers/assignment.d.ts +58 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +4 -4
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/class.d.ts +54 -0
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +5 -2
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/comment.d.ts +26 -0
- package/dist/routers/comment.d.ts.map +1 -1
- package/dist/routers/comment.js +6 -3
- package/dist/routers/comment.js.map +1 -1
- package/dist/routers/discussion.d.ts +823 -0
- package/dist/routers/discussion.d.ts.map +1 -0
- package/dist/routers/discussion.js +151 -0
- package/dist/routers/discussion.js.map +1 -0
- package/dist/routers/event.d.ts +3 -0
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +4 -0
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/school.d.ts +6 -0
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/worksheet.d.ts +6 -0
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +4 -2
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/seedDatabase.js +1490 -1307
- package/dist/seedDatabase.js.map +1 -1
- package/dist/services/adminInstitution.d.ts +102 -0
- package/dist/services/adminInstitution.d.ts.map +1 -0
- package/dist/services/adminInstitution.js +117 -0
- package/dist/services/adminInstitution.js.map +1 -0
- package/dist/services/adminRunJob.d.ts +45 -0
- package/dist/services/adminRunJob.d.ts.map +1 -0
- package/dist/services/adminRunJob.js +41 -0
- package/dist/services/adminRunJob.js.map +1 -0
- package/dist/services/agenda.d.ts +6 -0
- package/dist/services/agenda.d.ts.map +1 -1
- package/dist/services/assignment.d.ts +50 -0
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +16 -9
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/class.d.ts +54 -0
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +20 -3
- package/dist/services/class.js.map +1 -1
- package/dist/services/comment.d.ts +20 -0
- package/dist/services/comment.d.ts.map +1 -1
- package/dist/services/comment.js +23 -2
- package/dist/services/comment.js.map +1 -1
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/conversation.js +9 -7
- package/dist/services/conversation.js.map +1 -1
- package/dist/services/discussion.d.ts +762 -0
- package/dist/services/discussion.d.ts.map +1 -0
- package/dist/services/discussion.js +625 -0
- package/dist/services/discussion.js.map +1 -0
- package/dist/services/event.d.ts +3 -0
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/file.d.ts +8 -0
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/school.d.ts +6 -0
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/studentProgress.d.ts.map +1 -1
- package/dist/services/studentProgress.js +27 -5
- package/dist/services/studentProgress.js.map +1 -1
- package/dist/services/studentProgressInferenceCache.d.ts +16 -0
- package/dist/services/studentProgressInferenceCache.d.ts.map +1 -0
- package/dist/services/studentProgressInferenceCache.js +43 -0
- package/dist/services/studentProgressInferenceCache.js.map +1 -0
- package/dist/services/worksheet.d.ts +6 -0
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +5 -5
- package/dist/services/worksheet.js.map +1 -1
- package/dist/trpc.d.ts +12 -0
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +16 -3
- package/dist/trpc.js.map +1 -1
- package/dist/utils/adminPanelAccess.d.ts +8 -0
- package/dist/utils/adminPanelAccess.d.ts.map +1 -0
- package/dist/utils/adminPanelAccess.js +44 -0
- package/dist/utils/adminPanelAccess.js.map +1 -0
- package/package.json +1 -1
- package/prisma/migrations/20260510183000_add_discussions/migration.sql +151 -0
- package/prisma/schema.prisma +98 -0
- package/src/index.ts +39 -2
- package/src/lib/config/cors.ts +5 -0
- package/src/lib/config/env.ts +3 -0
- package/src/lib/pusher.ts +2 -1
- package/src/models/class.ts +10 -1
- package/src/models/comment.ts +1 -0
- package/src/models/conversation.ts +12 -0
- package/src/models/worksheet.ts +39 -5
- package/src/pipelines/gradeWorksheet.ts +66 -15
- package/src/routers/_app.ts +4 -0
- package/src/routers/admin.ts +70 -0
- package/src/routers/announcement.ts +4 -3
- package/src/routers/assignment.ts +2 -2
- package/src/routers/class.ts +3 -0
- package/src/routers/comment.ts +5 -0
- package/src/routers/discussion.ts +205 -0
- package/src/routers/worksheet.ts +2 -0
- package/src/seedDatabase.ts +1713 -1361
- package/src/services/adminInstitution.ts +140 -0
- package/src/services/adminRunJob.ts +46 -0
- package/src/services/assignment.ts +19 -8
- package/src/services/class.ts +28 -1
- package/src/services/comment.ts +22 -0
- package/src/services/conversation.ts +9 -4
- package/src/services/discussion.ts +701 -0
- package/src/services/studentProgress.ts +80 -3
- package/src/services/studentProgressInferenceCache.ts +64 -0
- package/src/services/worksheet.ts +5 -2
- package/src/trpc.ts +15 -0
- package/src/utils/adminPanelAccess.ts +46 -0
- package/tests/integration/routers/conversation.test.ts +9 -7
- package/tests/integration/routers/discussion.test.ts +253 -0
- package/tests/unit/services/discussion.test.ts +356 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { Prisma } from '@prisma/client';
|
|
2
|
+
import { prisma } from '../lib/prisma.js';
|
|
3
|
+
|
|
4
|
+
export async function listSchoolsForAdmin(input: {
|
|
5
|
+
page: number;
|
|
6
|
+
pageSize: number;
|
|
7
|
+
query?: string;
|
|
8
|
+
}) {
|
|
9
|
+
const skip = input.page * input.pageSize;
|
|
10
|
+
const where: Prisma.SchoolWhereInput = {};
|
|
11
|
+
const q = input.query?.trim();
|
|
12
|
+
if (q) {
|
|
13
|
+
where.OR = [
|
|
14
|
+
{ name: { contains: q, mode: 'insensitive' } },
|
|
15
|
+
{ subdomain: { contains: q, mode: 'insensitive' } },
|
|
16
|
+
{ description: { contains: q, mode: 'insensitive' } },
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [items, total] = await Promise.all([
|
|
21
|
+
prisma.school.findMany({
|
|
22
|
+
where,
|
|
23
|
+
orderBy: { createdAt: 'desc' },
|
|
24
|
+
skip,
|
|
25
|
+
take: input.pageSize,
|
|
26
|
+
include: {
|
|
27
|
+
_count: {
|
|
28
|
+
select: {
|
|
29
|
+
memberships: true,
|
|
30
|
+
classes: true,
|
|
31
|
+
departments: true,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
prisma.school.count({ where }),
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
items,
|
|
41
|
+
total,
|
|
42
|
+
page: input.page,
|
|
43
|
+
pageSize: input.pageSize,
|
|
44
|
+
pageCount: Math.max(1, Math.ceil(total / input.pageSize)),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function listDepartmentsForAdmin(input: {
|
|
49
|
+
page: number;
|
|
50
|
+
pageSize: number;
|
|
51
|
+
schoolId?: string;
|
|
52
|
+
}) {
|
|
53
|
+
const skip = input.page * input.pageSize;
|
|
54
|
+
const where: Prisma.DepartmentWhereInput = {};
|
|
55
|
+
if (input.schoolId) where.schoolId = input.schoolId;
|
|
56
|
+
|
|
57
|
+
const [items, total] = await Promise.all([
|
|
58
|
+
prisma.department.findMany({
|
|
59
|
+
where,
|
|
60
|
+
orderBy: [{ schoolId: 'asc' }, { name: 'asc' }],
|
|
61
|
+
skip,
|
|
62
|
+
take: input.pageSize,
|
|
63
|
+
include: {
|
|
64
|
+
school: { select: { id: true, name: true, subdomain: true } },
|
|
65
|
+
_count: { select: { classes: true, members: true } },
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
prisma.department.count({ where }),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
items,
|
|
73
|
+
total,
|
|
74
|
+
page: input.page,
|
|
75
|
+
pageSize: input.pageSize,
|
|
76
|
+
pageCount: Math.max(1, Math.ceil(total / input.pageSize)),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function listSchoolDevelopmentProgramsForAdmin(input: {
|
|
81
|
+
page: number;
|
|
82
|
+
pageSize: number;
|
|
83
|
+
status?: string;
|
|
84
|
+
query?: string;
|
|
85
|
+
}) {
|
|
86
|
+
const skip = input.page * input.pageSize;
|
|
87
|
+
const where: Prisma.SchoolDevelopementProgramWhereInput = {};
|
|
88
|
+
if (input.status?.trim()) where.status = input.status.trim();
|
|
89
|
+
const q = input.query?.trim();
|
|
90
|
+
if (q) {
|
|
91
|
+
where.OR = [
|
|
92
|
+
{ name: { contains: q, mode: 'insensitive' } },
|
|
93
|
+
{ city: { contains: q, mode: 'insensitive' } },
|
|
94
|
+
{ country: { contains: q, mode: 'insensitive' } },
|
|
95
|
+
{ contactEmail: { contains: q, mode: 'insensitive' } },
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const [items, total] = await Promise.all([
|
|
100
|
+
prisma.schoolDevelopementProgram.findMany({
|
|
101
|
+
where,
|
|
102
|
+
orderBy: { submittedAt: 'desc' },
|
|
103
|
+
skip,
|
|
104
|
+
take: input.pageSize,
|
|
105
|
+
}),
|
|
106
|
+
prisma.schoolDevelopementProgram.count({ where }),
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
items,
|
|
111
|
+
total,
|
|
112
|
+
page: input.page,
|
|
113
|
+
pageSize: input.pageSize,
|
|
114
|
+
pageCount: Math.max(1, Math.ceil(total / input.pageSize)),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function listEarlyAccessRequestsForAdmin(input: {
|
|
119
|
+
page: number;
|
|
120
|
+
pageSize: number;
|
|
121
|
+
}) {
|
|
122
|
+
const skip = input.page * input.pageSize;
|
|
123
|
+
|
|
124
|
+
const [items, total] = await Promise.all([
|
|
125
|
+
prisma.earlyAccessRequest.findMany({
|
|
126
|
+
orderBy: { createdAt: 'desc' },
|
|
127
|
+
skip,
|
|
128
|
+
take: input.pageSize,
|
|
129
|
+
}),
|
|
130
|
+
prisma.earlyAccessRequest.count(),
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
items,
|
|
135
|
+
total,
|
|
136
|
+
page: input.page,
|
|
137
|
+
pageSize: input.pageSize,
|
|
138
|
+
pageCount: Math.max(1, Math.ceil(total / input.pageSize)),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Prisma, RunJobStatus, RunJobType } from '@prisma/client';
|
|
2
|
+
import { TRPCError } from '@trpc/server';
|
|
3
|
+
import { prisma } from '../lib/prisma.js';
|
|
4
|
+
|
|
5
|
+
export async function listRunJobsForAdmin(input: {
|
|
6
|
+
page: number;
|
|
7
|
+
pageSize: number;
|
|
8
|
+
status?: RunJobStatus;
|
|
9
|
+
type?: RunJobType;
|
|
10
|
+
}) {
|
|
11
|
+
const { page, pageSize, status, type } = input;
|
|
12
|
+
const skip = page * pageSize;
|
|
13
|
+
|
|
14
|
+
const where: Prisma.RunJobWhereInput = {};
|
|
15
|
+
if (status) where.status = status;
|
|
16
|
+
if (type) where.type = type;
|
|
17
|
+
|
|
18
|
+
const [items, total] = await Promise.all([
|
|
19
|
+
prisma.runJob.findMany({
|
|
20
|
+
where,
|
|
21
|
+
orderBy: { createdAt: 'desc' },
|
|
22
|
+
skip,
|
|
23
|
+
take: pageSize,
|
|
24
|
+
}),
|
|
25
|
+
prisma.runJob.count({ where }),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
items,
|
|
30
|
+
total,
|
|
31
|
+
page,
|
|
32
|
+
pageSize,
|
|
33
|
+
pageCount: Math.max(1, Math.ceil(total / pageSize)),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function getRunJobForAdmin(id: string) {
|
|
38
|
+
const job = await prisma.runJob.findUnique({ where: { id } });
|
|
39
|
+
if (!job) {
|
|
40
|
+
throw new TRPCError({
|
|
41
|
+
code: 'NOT_FOUND',
|
|
42
|
+
message: 'Run job not found',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return job;
|
|
46
|
+
}
|
|
@@ -26,6 +26,7 @@ import { deleteFile } from "../lib/googleCloudStorage.js";
|
|
|
26
26
|
import { sendToMultiple } from "./notification.js";
|
|
27
27
|
import { enqueueRunJob } from "../workers/queues.js";
|
|
28
28
|
import { logger } from "../utils/logger.js";
|
|
29
|
+
import { invalidateStudentProgressInferenceCache } from "./studentProgressInferenceCache.js";
|
|
29
30
|
|
|
30
31
|
export async function assignmentExists(id: string) {
|
|
31
32
|
const assignment = await findAssignmentById(id);
|
|
@@ -424,12 +425,13 @@ export async function updateAssignmentRecord(
|
|
|
424
425
|
});
|
|
425
426
|
}
|
|
426
427
|
|
|
427
|
-
const submissionData =
|
|
428
|
-
input.studentIds
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
428
|
+
const submissionData = input.studentIds
|
|
429
|
+
? (input.studentIds.length > 0
|
|
430
|
+
? input.studentIds.map((studentId) => ({ student: { connect: { id: studentId } } }))
|
|
431
|
+
: classData?.students.map((student) => ({
|
|
432
|
+
student: { connect: { id: student.id } },
|
|
433
|
+
})))
|
|
434
|
+
: undefined;
|
|
433
435
|
|
|
434
436
|
if (input.removedAttachments?.length) {
|
|
435
437
|
const filesToDelete = assignment.attachments.filter((f) =>
|
|
@@ -480,7 +482,7 @@ export async function updateAssignmentRecord(
|
|
|
480
482
|
connect: input.studentIds.map((id) => ({ id })),
|
|
481
483
|
},
|
|
482
484
|
}),
|
|
483
|
-
...(submissionData && {
|
|
485
|
+
...(input.studentIds && submissionData && {
|
|
484
486
|
submissions: {
|
|
485
487
|
deleteMany: {},
|
|
486
488
|
create: submissionData,
|
|
@@ -892,7 +894,7 @@ export async function updateSubmissionAsTeacherRecord(
|
|
|
892
894
|
);
|
|
893
895
|
}
|
|
894
896
|
|
|
895
|
-
|
|
897
|
+
const updated = await prisma.submission.update({
|
|
896
898
|
where: { id: submissionId },
|
|
897
899
|
data: {
|
|
898
900
|
...(gradeReceived !== undefined && { gradeReceived }),
|
|
@@ -906,6 +908,15 @@ export async function updateSubmissionAsTeacherRecord(
|
|
|
906
908
|
},
|
|
907
909
|
include: submissionTeacherInclude,
|
|
908
910
|
});
|
|
911
|
+
|
|
912
|
+
if (gradeReceived !== undefined) {
|
|
913
|
+
invalidateStudentProgressInferenceCache(
|
|
914
|
+
submission.assignment.classId,
|
|
915
|
+
submission.studentId,
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return updated;
|
|
909
920
|
}
|
|
910
921
|
|
|
911
922
|
export async function attachAssignmentToEventRecord(
|
package/src/services/class.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
createClassByImport,
|
|
27
27
|
} from "../models/class.js";
|
|
28
28
|
import { Class, Folder } from "@prisma/client";
|
|
29
|
+
import { invalidateStudentProgressInferenceCache } from "./studentProgressInferenceCache.js";
|
|
29
30
|
|
|
30
31
|
async function requireActiveSchoolMembers(schoolId: string, userIds: string[]) {
|
|
31
32
|
const uniqueUserIds = [...new Set(userIds)];
|
|
@@ -74,6 +75,9 @@ export async function getAllClasses(userId: string) {
|
|
|
74
75
|
schoolId: cls.schoolId ?? null,
|
|
75
76
|
school: cls.school ?? null,
|
|
76
77
|
department: cls.department ?? null,
|
|
78
|
+
discussionsEnabled: cls.discussionsEnabled,
|
|
79
|
+
studentsCanCreateDiscussions: cls.studentsCanCreateDiscussions,
|
|
80
|
+
studentsCanReply: cls.studentsCanReply,
|
|
77
81
|
})).filter((cls) => cls.members.some((member) => member.id === userId)),
|
|
78
82
|
studentInClass: studentClasses.map((cls) => ({
|
|
79
83
|
id: cls.id,
|
|
@@ -81,12 +85,24 @@ export async function getAllClasses(userId: string) {
|
|
|
81
85
|
section: cls.section,
|
|
82
86
|
subject: cls.subject,
|
|
83
87
|
dueToday: cls.assignments,
|
|
84
|
-
assignments: cls.assignments
|
|
88
|
+
assignments: cls.assignments.map((assignment) => ({
|
|
89
|
+
...assignment,
|
|
90
|
+
late: new Date(assignment.dueDate) < new Date(),
|
|
91
|
+
submitted: assignment.submissions.find(
|
|
92
|
+
(s) => s.studentId === userId,
|
|
93
|
+
)?.submitted ?? false,
|
|
94
|
+
returned: assignment.submissions.find(
|
|
95
|
+
(s) => s.studentId === userId,
|
|
96
|
+
)?.returned ?? false,
|
|
97
|
+
})),
|
|
85
98
|
members: [...cls.students, ...cls.teachers],
|
|
86
99
|
color: cls.color,
|
|
87
100
|
schoolId: cls.schoolId ?? null,
|
|
88
101
|
school: cls.school ?? null,
|
|
89
102
|
department: cls.department ?? null,
|
|
103
|
+
discussionsEnabled: cls.discussionsEnabled,
|
|
104
|
+
studentsCanCreateDiscussions: cls.studentsCanCreateDiscussions,
|
|
105
|
+
studentsCanReply: cls.studentsCanReply,
|
|
90
106
|
})),
|
|
91
107
|
};
|
|
92
108
|
}
|
|
@@ -171,6 +187,9 @@ export async function updateClass(
|
|
|
171
187
|
subject?: string;
|
|
172
188
|
color?: string;
|
|
173
189
|
departmentId?: string | null;
|
|
190
|
+
discussionsEnabled?: boolean;
|
|
191
|
+
studentsCanCreateDiscussions?: boolean;
|
|
192
|
+
studentsCanReply?: boolean;
|
|
174
193
|
}
|
|
175
194
|
) {
|
|
176
195
|
if (data.departmentId !== undefined) {
|
|
@@ -209,6 +228,9 @@ export async function updateClass(
|
|
|
209
228
|
subject: true,
|
|
210
229
|
color: true,
|
|
211
230
|
departmentId: true,
|
|
231
|
+
discussionsEnabled: true,
|
|
232
|
+
studentsCanCreateDiscussions: true,
|
|
233
|
+
studentsCanReply: true,
|
|
212
234
|
},
|
|
213
235
|
});
|
|
214
236
|
return { updatedClass };
|
|
@@ -498,10 +520,15 @@ export async function updateGrade(
|
|
|
498
520
|
title: true,
|
|
499
521
|
maxGrade: true,
|
|
500
522
|
weight: true,
|
|
523
|
+
classId: true,
|
|
501
524
|
},
|
|
502
525
|
},
|
|
503
526
|
},
|
|
504
527
|
});
|
|
528
|
+
invalidateStudentProgressInferenceCache(
|
|
529
|
+
updatedSubmission.assignment.classId,
|
|
530
|
+
updatedSubmission.studentId,
|
|
531
|
+
);
|
|
505
532
|
return updatedSubmission;
|
|
506
533
|
}
|
|
507
534
|
|
package/src/services/comment.ts
CHANGED
|
@@ -158,6 +158,28 @@ export async function deleteComment(userId: string, id: string) {
|
|
|
158
158
|
return { success: true };
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/** Pin or unpin a comment. Only teachers can pin comments. */
|
|
162
|
+
export async function pinComment(userId: string, commentId: string, isPinned: boolean) {
|
|
163
|
+
const comment = await requireCommentAccess(commentId, userId);
|
|
164
|
+
if (comment.parentCommentId) {
|
|
165
|
+
throw new TRPCError({
|
|
166
|
+
code: "BAD_REQUEST",
|
|
167
|
+
message: "Only top-level comments can be pinned",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (!isClassTeacher(comment, userId)) {
|
|
171
|
+
throw new TRPCError({
|
|
172
|
+
code: "FORBIDDEN",
|
|
173
|
+
message: "Only teachers can pin comments",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const updated = await prisma.comment.update({
|
|
177
|
+
where: { id: commentId },
|
|
178
|
+
data: { isPinned },
|
|
179
|
+
});
|
|
180
|
+
return updated;
|
|
181
|
+
}
|
|
182
|
+
|
|
161
183
|
/** Add or update a reaction (emoji) on a comment. */
|
|
162
184
|
export async function addReaction(
|
|
163
185
|
userId: string,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
findConversationByIdAndMember,
|
|
16
16
|
createConversationMember,
|
|
17
17
|
hideConversationMember,
|
|
18
|
+
unhideConversationMember,
|
|
18
19
|
deleteConversationMember,
|
|
19
20
|
} from "../models/conversation.js";
|
|
20
21
|
|
|
@@ -160,10 +161,14 @@ export async function createConversationRecord(
|
|
|
160
161
|
});
|
|
161
162
|
|
|
162
163
|
if (existingDM) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
const userMembership = existingDM.members.find(
|
|
165
|
+
(m) => m.userId === userId
|
|
166
|
+
);
|
|
167
|
+
if (userMembership) {
|
|
168
|
+
await unhideConversationMember(userId, existingDM.id);
|
|
169
|
+
}
|
|
170
|
+
const conversation = await findConversationByIdAndMember(existingDM.id, userId);
|
|
171
|
+
return conversation!;
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
|