@studious-lms/server 1.4.5 → 1.4.7
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/lib/config/cors.d.ts.map +1 -1
- package/dist/lib/config/cors.js +8 -3
- package/dist/lib/config/cors.js.map +1 -1
- package/dist/models/access.d.ts +112 -0
- package/dist/models/access.d.ts.map +1 -0
- package/dist/models/access.js +49 -0
- package/dist/models/access.js.map +1 -0
- package/dist/models/announcement.d.ts +17 -2
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/announcement.js +21 -4
- package/dist/models/announcement.js.map +1 -1
- package/dist/models/assignment.d.ts +37 -37
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/assignment.js +15 -15
- package/dist/models/assignment.js.map +1 -1
- package/dist/models/attendance.d.ts +1 -1
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/attendance.js +6 -5
- package/dist/models/attendance.js.map +1 -1
- package/dist/models/auth.d.ts +5 -5
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +7 -11
- package/dist/models/auth.js.map +1 -1
- package/dist/models/class.d.ts +26 -26
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +7 -11
- package/dist/models/class.js.map +1 -1
- package/dist/models/comment.d.ts +233 -6
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/comment.js +133 -3
- package/dist/models/comment.js.map +1 -1
- package/dist/models/conversation.d.ts +7 -7
- package/dist/models/event.d.ts +52 -3
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/event.js +56 -6
- package/dist/models/event.js.map +1 -1
- package/dist/models/file.d.ts +178 -103
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/file.js +54 -14
- package/dist/models/file.js.map +1 -1
- package/dist/models/folder.d.ts +22 -22
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/folder.js +5 -4
- package/dist/models/folder.js.map +1 -1
- package/dist/models/labChat.d.ts +20 -20
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/labChat.js +8 -10
- package/dist/models/labChat.js.map +1 -1
- package/dist/models/marketing.d.ts +1 -1
- package/dist/models/message.d.ts +6 -6
- package/dist/models/newtonChat.d.ts +5 -5
- package/dist/models/school.d.ts +361 -0
- package/dist/models/school.d.ts.map +1 -0
- package/dist/models/school.js +314 -0
- package/dist/models/school.js.map +1 -0
- package/dist/models/section.d.ts +1 -1
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/section.js +5 -4
- package/dist/models/section.js.map +1 -1
- package/dist/models/user.d.ts +4 -4
- package/dist/models/worksheet.d.ts +67 -65
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +6 -14
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +4 -3
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +2 -2
- package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
- package/dist/routers/_app.d.ts +2462 -1104
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +4 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/announcement.d.ts +13 -5
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +25 -5
- package/dist/routers/announcement.js.map +1 -1
- package/dist/routers/assignment.d.ts +266 -36
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +221 -3
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/class.d.ts +21 -20
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +3 -2
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/comment.d.ts +40 -3
- package/dist/routers/comment.d.ts.map +1 -1
- package/dist/routers/comment.js +14 -5
- package/dist/routers/comment.js.map +1 -1
- package/dist/routers/conversation.d.ts +6 -6
- package/dist/routers/event.d.ts +21 -2
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +2 -2
- package/dist/routers/folder.d.ts +24 -24
- package/dist/routers/labChat.d.ts +7 -7
- package/dist/routers/school.d.ts +384 -0
- package/dist/routers/school.d.ts.map +1 -0
- package/dist/routers/school.js +79 -0
- package/dist/routers/school.js.map +1 -0
- package/dist/routers/search.d.ts +1 -1
- package/dist/routers/worksheet.d.ts +23 -21
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/services/announcement.d.ts.map +1 -1
- package/dist/services/announcement.js +10 -6
- package/dist/services/announcement.js.map +1 -1
- package/dist/services/assignment.d.ts +36 -36
- package/dist/services/class.d.ts +20 -19
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +22 -3
- package/dist/services/class.js.map +1 -1
- package/dist/services/comment.d.ts +31 -5
- package/dist/services/comment.d.ts.map +1 -1
- package/dist/services/comment.js +102 -19
- package/dist/services/comment.js.map +1 -1
- package/dist/services/conversation.d.ts +6 -6
- package/dist/services/event.d.ts +21 -2
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/event.js +11 -7
- package/dist/services/event.js.map +1 -1
- package/dist/services/file.d.ts +2 -2
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/file.js +17 -8
- package/dist/services/file.js.map +1 -1
- package/dist/services/folder.d.ts +24 -24
- package/dist/services/labChat.d.ts +7 -7
- package/dist/services/school.d.ts +299 -0
- package/dist/services/school.d.ts.map +1 -0
- package/dist/services/school.js +225 -0
- package/dist/services/school.js.map +1 -0
- package/dist/services/worksheet.d.ts +23 -21
- package/dist/services/worksheet.d.ts.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260502230500_assignment_submission_comments/migration.sql +60 -0
- package/prisma/migrations/20260504071500_school_memberships/migration.sql +81 -0
- package/prisma/schema.prisma +75 -2
- package/src/lib/config/cors.ts +8 -1
- package/src/models/access.ts +48 -0
- package/src/models/announcement.ts +19 -2
- package/src/models/assignment.ts +17 -13
- package/src/models/attendance.ts +4 -3
- package/src/models/auth.ts +8 -9
- package/src/models/class.ts +8 -9
- package/src/models/comment.ts +137 -1
- package/src/models/event.ts +54 -4
- package/src/models/file.ts +53 -12
- package/src/models/folder.ts +3 -2
- package/src/models/labChat.ts +9 -8
- package/src/models/school.ts +361 -0
- package/src/models/section.ts +3 -2
- package/src/models/worksheet.ts +7 -12
- package/src/pipelines/aiLabChat.ts +2 -1
- package/src/routers/_app.ts +2 -0
- package/src/routers/announcement.ts +30 -4
- package/src/routers/assignment.ts +243 -2
- package/src/routers/class.ts +1 -0
- package/src/routers/comment.ts +19 -2
- package/src/routers/school.ts +122 -0
- package/src/services/announcement.ts +12 -4
- package/src/services/class.ts +25 -2
- package/src/services/comment.ts +122 -18
- package/src/services/event.ts +23 -6
- package/src/services/file.ts +20 -6
- package/src/services/school.ts +308 -0
- package/tests/lib/cors.test.ts +13 -1
- package/tests/routers/assignment.test.ts +68 -2
- package/tests/routers/comment.test.ts +42 -1
- package/vercel.json +8 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* School model - school membership, invites, and admin lookups.
|
|
3
|
+
*/
|
|
4
|
+
import { SchoolMembershipRole } from "@prisma/client";
|
|
5
|
+
import { prisma } from "../lib/prisma.js";
|
|
6
|
+
|
|
7
|
+
const memberSelect = {
|
|
8
|
+
id: true,
|
|
9
|
+
role: true,
|
|
10
|
+
status: true,
|
|
11
|
+
createdAt: true,
|
|
12
|
+
updatedAt: true,
|
|
13
|
+
user: {
|
|
14
|
+
select: {
|
|
15
|
+
id: true,
|
|
16
|
+
username: true,
|
|
17
|
+
email: true,
|
|
18
|
+
profile: {
|
|
19
|
+
select: {
|
|
20
|
+
displayName: true,
|
|
21
|
+
profilePicture: true,
|
|
22
|
+
profilePictureThumbnail: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export function findSchoolMembership(schoolId: string, userId: string) {
|
|
30
|
+
return prisma.schoolMembership.findUnique({
|
|
31
|
+
where: { schoolId_userId: { schoolId, userId } },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function findActiveSchoolMembership(schoolId: string, userId: string) {
|
|
36
|
+
return prisma.schoolMembership.findFirst({
|
|
37
|
+
where: { schoolId, userId, status: "ACTIVE" },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function findActiveSchoolAdminMembership(schoolId: string, userId: string) {
|
|
42
|
+
return prisma.schoolMembership.findFirst({
|
|
43
|
+
where: { schoolId, userId, role: "ADMIN", status: "ACTIVE" },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function listUserSchoolMemberships(userId: string) {
|
|
48
|
+
return prisma.schoolMembership.findMany({
|
|
49
|
+
where: { userId, status: "ACTIVE" },
|
|
50
|
+
include: {
|
|
51
|
+
school: {
|
|
52
|
+
select: {
|
|
53
|
+
id: true,
|
|
54
|
+
name: true,
|
|
55
|
+
subdomain: true,
|
|
56
|
+
logoId: true,
|
|
57
|
+
createdAt: true,
|
|
58
|
+
updatedAt: true,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
orderBy: { createdAt: "asc" },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createSchoolWithAdmin(data: {
|
|
67
|
+
name: string;
|
|
68
|
+
subdomain?: string;
|
|
69
|
+
logoId?: string;
|
|
70
|
+
userId: string;
|
|
71
|
+
}) {
|
|
72
|
+
return prisma.school.create({
|
|
73
|
+
data: {
|
|
74
|
+
name: data.name,
|
|
75
|
+
subdomain: data.subdomain,
|
|
76
|
+
logoId: data.logoId,
|
|
77
|
+
memberships: {
|
|
78
|
+
create: {
|
|
79
|
+
userId: data.userId,
|
|
80
|
+
role: "ADMIN",
|
|
81
|
+
status: "ACTIVE",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
include: {
|
|
86
|
+
memberships: {
|
|
87
|
+
where: { userId: data.userId },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getSchoolById(schoolId: string) {
|
|
94
|
+
return prisma.school.findUnique({
|
|
95
|
+
where: { id: schoolId },
|
|
96
|
+
select: {
|
|
97
|
+
id: true,
|
|
98
|
+
name: true,
|
|
99
|
+
subdomain: true,
|
|
100
|
+
logoId: true,
|
|
101
|
+
createdAt: true,
|
|
102
|
+
updatedAt: true,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getSchoolOverview(schoolId: string) {
|
|
108
|
+
return prisma.school.findUnique({
|
|
109
|
+
where: { id: schoolId },
|
|
110
|
+
select: {
|
|
111
|
+
id: true,
|
|
112
|
+
name: true,
|
|
113
|
+
subdomain: true,
|
|
114
|
+
_count: {
|
|
115
|
+
select: {
|
|
116
|
+
classes: true,
|
|
117
|
+
memberships: { where: { status: "ACTIVE" } },
|
|
118
|
+
invites: { where: { status: "PENDING" } },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
memberships: {
|
|
122
|
+
where: { status: "ACTIVE" },
|
|
123
|
+
select: { role: true },
|
|
124
|
+
},
|
|
125
|
+
classes: {
|
|
126
|
+
orderBy: { name: "asc" },
|
|
127
|
+
take: 5,
|
|
128
|
+
select: {
|
|
129
|
+
id: true,
|
|
130
|
+
name: true,
|
|
131
|
+
subject: true,
|
|
132
|
+
section: true,
|
|
133
|
+
color: true,
|
|
134
|
+
_count: { select: { teachers: true, students: true } },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
invites: {
|
|
138
|
+
where: { status: "PENDING" },
|
|
139
|
+
orderBy: { createdAt: "desc" },
|
|
140
|
+
take: 5,
|
|
141
|
+
select: {
|
|
142
|
+
id: true,
|
|
143
|
+
email: true,
|
|
144
|
+
role: true,
|
|
145
|
+
expiresAt: true,
|
|
146
|
+
createdAt: true,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function listSchoolClasses(schoolId: string) {
|
|
154
|
+
return prisma.class.findMany({
|
|
155
|
+
where: { schoolId },
|
|
156
|
+
orderBy: [{ name: "asc" }, { section: "asc" }],
|
|
157
|
+
include: {
|
|
158
|
+
teachers: {
|
|
159
|
+
select: {
|
|
160
|
+
id: true,
|
|
161
|
+
username: true,
|
|
162
|
+
profile: {
|
|
163
|
+
select: {
|
|
164
|
+
displayName: true,
|
|
165
|
+
profilePicture: true,
|
|
166
|
+
profilePictureThumbnail: true,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
students: { select: { id: true } },
|
|
172
|
+
_count: { select: { teachers: true, students: true, assignments: true } },
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function listSchoolMembers(schoolId: string) {
|
|
178
|
+
return prisma.schoolMembership.findMany({
|
|
179
|
+
where: { schoolId },
|
|
180
|
+
select: memberSelect,
|
|
181
|
+
orderBy: [{ role: "asc" }, { createdAt: "asc" }],
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function countActiveAdmins(schoolId: string) {
|
|
186
|
+
return prisma.schoolMembership.count({
|
|
187
|
+
where: { schoolId, role: "ADMIN", status: "ACTIVE" },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function upsertSchoolMembership(data: {
|
|
192
|
+
schoolId: string;
|
|
193
|
+
userId: string;
|
|
194
|
+
role: SchoolMembershipRole;
|
|
195
|
+
}) {
|
|
196
|
+
return prisma.schoolMembership.upsert({
|
|
197
|
+
where: {
|
|
198
|
+
schoolId_userId: {
|
|
199
|
+
schoolId: data.schoolId,
|
|
200
|
+
userId: data.userId,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
update: {
|
|
204
|
+
role: data.role,
|
|
205
|
+
status: "ACTIVE",
|
|
206
|
+
},
|
|
207
|
+
create: {
|
|
208
|
+
schoolId: data.schoolId,
|
|
209
|
+
userId: data.userId,
|
|
210
|
+
role: data.role,
|
|
211
|
+
status: "ACTIVE",
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function updateSchoolMembershipRole(
|
|
217
|
+
schoolId: string,
|
|
218
|
+
userId: string,
|
|
219
|
+
role: SchoolMembershipRole
|
|
220
|
+
) {
|
|
221
|
+
return prisma.schoolMembership.update({
|
|
222
|
+
where: { schoolId_userId: { schoolId, userId } },
|
|
223
|
+
data: { role },
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function removeSchoolMembershipPreservingContent(schoolId: string, userId: string) {
|
|
228
|
+
return prisma.$transaction(async (tx) => {
|
|
229
|
+
const classes = await tx.class.findMany({
|
|
230
|
+
where: { schoolId },
|
|
231
|
+
select: { id: true },
|
|
232
|
+
});
|
|
233
|
+
const classIds = classes.map((cls) => ({ id: cls.id }));
|
|
234
|
+
|
|
235
|
+
for (const classWhere of classIds) {
|
|
236
|
+
await tx.class.update({
|
|
237
|
+
where: classWhere,
|
|
238
|
+
data: {
|
|
239
|
+
teachers: { disconnect: { id: userId } },
|
|
240
|
+
students: { disconnect: { id: userId } },
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return tx.schoolMembership.delete({
|
|
246
|
+
where: { schoolId_userId: { schoolId, userId } },
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function findUserByEmailForSchool(email: string) {
|
|
252
|
+
return prisma.user.findFirst({
|
|
253
|
+
where: { email },
|
|
254
|
+
select: { id: true, email: true, username: true },
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function findUserByIdForSchool(userId: string) {
|
|
259
|
+
return prisma.user.findUnique({
|
|
260
|
+
where: { id: userId },
|
|
261
|
+
select: { id: true, email: true, username: true },
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function createSchoolInvite(data: {
|
|
266
|
+
schoolId: string;
|
|
267
|
+
invitedById: string;
|
|
268
|
+
email: string;
|
|
269
|
+
role: SchoolMembershipRole;
|
|
270
|
+
token: string;
|
|
271
|
+
expiresAt: Date;
|
|
272
|
+
}) {
|
|
273
|
+
return prisma.schoolInvite.create({
|
|
274
|
+
data,
|
|
275
|
+
include: {
|
|
276
|
+
school: { select: { id: true, name: true } },
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function listSchoolInvites(schoolId: string) {
|
|
282
|
+
return prisma.schoolInvite.findMany({
|
|
283
|
+
where: { schoolId },
|
|
284
|
+
orderBy: { createdAt: "desc" },
|
|
285
|
+
select: {
|
|
286
|
+
id: true,
|
|
287
|
+
email: true,
|
|
288
|
+
role: true,
|
|
289
|
+
status: true,
|
|
290
|
+
expiresAt: true,
|
|
291
|
+
acceptedAt: true,
|
|
292
|
+
revokedAt: true,
|
|
293
|
+
createdAt: true,
|
|
294
|
+
invitedBy: {
|
|
295
|
+
select: {
|
|
296
|
+
id: true,
|
|
297
|
+
username: true,
|
|
298
|
+
profile: { select: { displayName: true } },
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function revokeSchoolInvite(inviteId: string, schoolId: string) {
|
|
306
|
+
return prisma.schoolInvite.update({
|
|
307
|
+
where: { id: inviteId, schoolId },
|
|
308
|
+
data: {
|
|
309
|
+
status: "REVOKED",
|
|
310
|
+
revokedAt: new Date(),
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function findPendingInviteByToken(token: string) {
|
|
316
|
+
return prisma.schoolInvite.findUnique({
|
|
317
|
+
where: { token },
|
|
318
|
+
include: { school: { select: { id: true, name: true } } },
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function acceptSchoolInvite(inviteId: string, userId: string) {
|
|
323
|
+
return prisma.$transaction(async (tx) => {
|
|
324
|
+
const invite = await tx.schoolInvite.findUnique({
|
|
325
|
+
where: { id: inviteId },
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
if (!invite) {
|
|
329
|
+
throw new Error("Invite not found");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const membership = await tx.schoolMembership.upsert({
|
|
333
|
+
where: {
|
|
334
|
+
schoolId_userId: {
|
|
335
|
+
schoolId: invite.schoolId,
|
|
336
|
+
userId,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
update: {
|
|
340
|
+
role: invite.role,
|
|
341
|
+
status: "ACTIVE",
|
|
342
|
+
},
|
|
343
|
+
create: {
|
|
344
|
+
schoolId: invite.schoolId,
|
|
345
|
+
userId,
|
|
346
|
+
role: invite.role,
|
|
347
|
+
status: "ACTIVE",
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
await tx.schoolInvite.update({
|
|
352
|
+
where: { id: inviteId },
|
|
353
|
+
data: {
|
|
354
|
+
status: "ACCEPTED",
|
|
355
|
+
acceptedAt: new Date(),
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return membership;
|
|
360
|
+
});
|
|
361
|
+
}
|
package/src/models/section.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Section model – class sections, ordering, assignment grouping.
|
|
3
3
|
*/
|
|
4
4
|
import { prisma } from "../lib/prisma.js";
|
|
5
|
+
import { classTeacherOrSchoolAdminWhere } from "./access.js";
|
|
5
6
|
|
|
6
7
|
/** @returns Section by ID. */
|
|
7
8
|
export function findSectionById(id: string) {
|
|
@@ -10,12 +11,12 @@ export function findSectionById(id: string) {
|
|
|
10
11
|
});
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
/** @returns Class if user is a teacher. */
|
|
14
|
+
/** @returns Class if user is a teacher or school admin. */
|
|
14
15
|
export function findClassWithTeacher(classId: string, teacherId: string) {
|
|
15
16
|
return prisma.class.findFirst({
|
|
16
17
|
where: {
|
|
17
18
|
id: classId,
|
|
18
|
-
|
|
19
|
+
...classTeacherOrSchoolAdminWhere(teacherId),
|
|
19
20
|
},
|
|
20
21
|
});
|
|
21
22
|
}
|
package/src/models/worksheet.ts
CHANGED
|
@@ -3,20 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { prisma } from "../lib/prisma.js";
|
|
5
5
|
import type { WorksheetQuestionType, GenerationStatus } from "@prisma/client";
|
|
6
|
+
import {
|
|
7
|
+
classMemberOrSchoolAdminWhere,
|
|
8
|
+
classTeacherOrSchoolAdminWhere,
|
|
9
|
+
} from "./access.js";
|
|
6
10
|
|
|
7
11
|
const worksheetClassMemberWhere = (userId: string) => ({
|
|
8
|
-
class:
|
|
9
|
-
OR: [
|
|
10
|
-
{ teachers: { some: { id: userId } } },
|
|
11
|
-
{ students: { some: { id: userId } } },
|
|
12
|
-
],
|
|
13
|
-
},
|
|
12
|
+
class: classMemberOrSchoolAdminWhere(userId),
|
|
14
13
|
});
|
|
15
14
|
|
|
16
15
|
const worksheetTeacherWhere = (userId: string) => ({
|
|
17
|
-
class:
|
|
18
|
-
teachers: { some: { id: userId } },
|
|
19
|
-
},
|
|
16
|
+
class: classTeacherOrSchoolAdminWhere(userId),
|
|
20
17
|
});
|
|
21
18
|
|
|
22
19
|
/** @returns Worksheet with questions and class. */
|
|
@@ -207,9 +204,7 @@ export function findSubmissionByIdForWorksheetAccess(
|
|
|
207
204
|
},
|
|
208
205
|
{
|
|
209
206
|
assignment: {
|
|
210
|
-
class:
|
|
211
|
-
teachers: { some: { id: userId } },
|
|
212
|
-
},
|
|
207
|
+
class: classTeacherOrSchoolAdminWhere(userId),
|
|
213
208
|
},
|
|
214
209
|
},
|
|
215
210
|
],
|
|
@@ -16,7 +16,8 @@ import { DocumentBlock } from "../lib/jsonStyles.js";
|
|
|
16
16
|
import { type LabChatResponse, labChatResponseSchema } from "./aiLabChatContract.js";
|
|
17
17
|
import { buildLabChatResponseMessages } from "./labChatPrompt.js";
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/** Cap model wait time; leave headroom after inference for PDF/GCS/DB within Vercel maxDuration (500s). */
|
|
20
|
+
const LAB_CHAT_RESPONSE_TIMEOUT_MS = 400_000;
|
|
20
21
|
|
|
21
22
|
const withTimeout = async <T>(
|
|
22
23
|
task: Promise<T>,
|
package/src/routers/_app.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { worksheetRouter } from "./worksheet.js";
|
|
|
22
22
|
import { commentRouter } from "./comment.js";
|
|
23
23
|
import { studentProgressRouter } from "./studentProgress.js";
|
|
24
24
|
import { searchRouter } from "./search.js";
|
|
25
|
+
import { schoolRouter } from "./school.js";
|
|
25
26
|
|
|
26
27
|
export const appRouter = createTRPCRouter({
|
|
27
28
|
class: classRouter,
|
|
@@ -45,6 +46,7 @@ export const appRouter = createTRPCRouter({
|
|
|
45
46
|
comment: commentRouter,
|
|
46
47
|
studentProgress: studentProgressRouter,
|
|
47
48
|
search: searchRouter,
|
|
49
|
+
school: schoolRouter,
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
// Export type router type definition
|
|
@@ -339,6 +339,24 @@ export const announcementRouter = createTRPCRouter({
|
|
|
339
339
|
class: {
|
|
340
340
|
include: {
|
|
341
341
|
teachers: true,
|
|
342
|
+
school: {
|
|
343
|
+
include: {
|
|
344
|
+
users: {
|
|
345
|
+
where: {
|
|
346
|
+
role: "ADMIN",
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
memberships: {
|
|
350
|
+
where: {
|
|
351
|
+
role: "ADMIN",
|
|
352
|
+
status: "ACTIVE",
|
|
353
|
+
},
|
|
354
|
+
select: {
|
|
355
|
+
userId: true,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
342
360
|
},
|
|
343
361
|
},
|
|
344
362
|
},
|
|
@@ -353,17 +371,25 @@ export const announcementRouter = createTRPCRouter({
|
|
|
353
371
|
});
|
|
354
372
|
}
|
|
355
373
|
|
|
356
|
-
// Only the author
|
|
374
|
+
// Only the author, a class teacher, or the class school admin can delete comments
|
|
357
375
|
const userId = ctx.user.id;
|
|
358
376
|
const isAuthor = comment.authorId === userId;
|
|
359
377
|
const isClassTeacher = comment.announcement!.class.teachers.some(
|
|
360
378
|
(teacher) => teacher.id === userId
|
|
361
379
|
);
|
|
380
|
+
const isSchoolAdmin = Boolean(
|
|
381
|
+
comment.announcement!.class.school?.memberships.some(
|
|
382
|
+
(admin) => admin.userId === userId
|
|
383
|
+
) ||
|
|
384
|
+
comment.announcement!.class.school?.users.some(
|
|
385
|
+
(admin) => admin.id === userId
|
|
386
|
+
)
|
|
387
|
+
);
|
|
362
388
|
|
|
363
|
-
if (!isAuthor && !isClassTeacher) {
|
|
389
|
+
if (!isAuthor && !isClassTeacher && !isSchoolAdmin) {
|
|
364
390
|
throw new TRPCError({
|
|
365
391
|
code: "FORBIDDEN",
|
|
366
|
-
message: "Only the comment author
|
|
392
|
+
message: "Only the comment author, class teachers, or school admins can delete comments",
|
|
367
393
|
});
|
|
368
394
|
}
|
|
369
395
|
|
|
@@ -696,4 +722,4 @@ export const announcementRouter = createTRPCRouter({
|
|
|
696
722
|
message: "Unexpected error",
|
|
697
723
|
});
|
|
698
724
|
}),
|
|
699
|
-
});
|
|
725
|
+
});
|