@studious-lms/server 1.7.0 → 1.8.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/access.d.ts +21 -66
- package/dist/models/access.d.ts.map +1 -1
- package/dist/models/access.js +10 -26
- package/dist/models/access.js.map +1 -1
- package/dist/models/agenda.d.ts +2 -0
- package/dist/models/agenda.d.ts.map +1 -1
- package/dist/models/announcement.d.ts +2 -1
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/announcement.js +2 -3
- package/dist/models/announcement.js.map +1 -1
- package/dist/models/assignment.d.ts +7 -0
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/auth.d.ts +2 -4
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +2 -3
- package/dist/models/auth.js.map +1 -1
- package/dist/models/class.d.ts +32 -4
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +15 -2
- package/dist/models/class.js.map +1 -1
- package/dist/models/cloudStorage.d.ts +140 -0
- package/dist/models/cloudStorage.d.ts.map +1 -0
- package/dist/models/cloudStorage.js +175 -0
- package/dist/models/cloudStorage.js.map +1 -0
- package/dist/models/comment.d.ts +0 -24
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/comment.js +2 -6
- package/dist/models/comment.js.map +1 -1
- package/dist/models/conversation.d.ts +22 -22
- package/dist/models/department.d.ts +154 -0
- package/dist/models/department.d.ts.map +1 -0
- package/dist/models/department.js +135 -0
- package/dist/models/department.js.map +1 -0
- package/dist/models/event.d.ts +7 -1
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/event.js +2 -5
- package/dist/models/event.js.map +1 -1
- package/dist/models/file.d.ts +25 -10
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/file.js +2 -5
- package/dist/models/file.js.map +1 -1
- package/dist/models/folder.d.ts +47 -0
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +5 -3
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/message.d.ts +46 -12
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/message.js +19 -10
- package/dist/models/message.js.map +1 -1
- package/dist/models/newtonChat.d.ts +2 -1
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/school.d.ts +45 -12
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +19 -2
- package/dist/models/school.js.map +1 -1
- package/dist/models/section.d.ts +1 -0
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/tag.d.ts +117 -0
- package/dist/models/tag.d.ts.map +1 -0
- package/dist/models/tag.js +113 -0
- package/dist/models/tag.js.map +1 -0
- package/dist/models/worksheet.d.ts +3 -0
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +8 -8
- package/dist/routers/_app.d.ts +3200 -1972
- 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/agenda.d.ts +2 -0
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +3 -5
- package/dist/routers/announcement.js.map +1 -1
- package/dist/routers/assignment.d.ts +18 -9
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +2 -3
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/class.d.ts +87 -14
- 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/conversation.d.ts +5 -5
- package/dist/routers/department.d.ts +293 -0
- package/dist/routers/department.d.ts.map +1 -0
- package/dist/routers/department.js +93 -0
- package/dist/routers/department.js.map +1 -0
- package/dist/routers/event.d.ts +2 -1
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +8 -6
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +26 -17
- package/dist/routers/file.js.map +1 -1
- package/dist/routers/folder.d.ts +139 -186
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +82 -90
- package/dist/routers/folder.js.map +1 -1
- package/dist/routers/labChat.d.ts +11 -2
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/message.d.ts +20 -2
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/newtonChat.d.ts +9 -0
- package/dist/routers/newtonChat.d.ts.map +1 -1
- package/dist/routers/school.d.ts +160 -17
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +46 -3
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/tag.d.ts +119 -0
- package/dist/routers/tag.d.ts.map +1 -0
- package/dist/routers/tag.js +52 -0
- package/dist/routers/tag.js.map +1 -0
- package/dist/routers/worksheet.d.ts +1 -0
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/seedDatabase.d.ts +0 -1
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/services/agenda.d.ts +2 -0
- package/dist/services/agenda.d.ts.map +1 -1
- package/dist/services/assignment.d.ts +18 -9
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/class.d.ts +87 -14
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +91 -4
- package/dist/services/class.js.map +1 -1
- package/dist/services/conversation.d.ts +5 -5
- package/dist/services/department.d.ts +200 -0
- package/dist/services/department.d.ts.map +1 -0
- package/dist/services/department.js +368 -0
- package/dist/services/department.js.map +1 -0
- package/dist/services/event.d.ts +2 -1
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/file.d.ts +85 -0
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/file.js +92 -2
- package/dist/services/file.js.map +1 -1
- package/dist/services/folder.d.ts +125 -149
- package/dist/services/folder.d.ts.map +1 -1
- package/dist/services/folder.js +258 -199
- package/dist/services/folder.js.map +1 -1
- package/dist/services/labChat.d.ts +11 -2
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +4 -3
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/message.d.ts +20 -2
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +7 -23
- package/dist/services/message.js.map +1 -1
- package/dist/services/newtonChat.d.ts +9 -0
- package/dist/services/newtonChat.d.ts.map +1 -1
- package/dist/services/newtonChat.js +4 -3
- package/dist/services/newtonChat.js.map +1 -1
- package/dist/services/school.d.ts +142 -15
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +308 -54
- package/dist/services/school.js.map +1 -1
- package/dist/services/tag.d.ts +71 -0
- package/dist/services/tag.d.ts.map +1 -0
- package/dist/services/tag.js +178 -0
- package/dist/services/tag.js.map +1 -0
- package/dist/services/worksheet.d.ts +1 -0
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/utils/aiUser.d.ts.map +1 -1
- package/dist/utils/aiUser.js +2 -3
- package/dist/utils/aiUser.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +184 -0
- package/prisma/schema.prisma +119 -35
- package/src/models/access.ts +8 -24
- package/src/models/announcement.ts +0 -1
- package/src/models/auth.ts +0 -1
- package/src/models/class.ts +13 -0
- package/src/models/cloudStorage.ts +194 -0
- package/src/models/comment.ts +0 -4
- package/src/models/department.ts +159 -0
- package/src/models/event.ts +0 -3
- package/src/models/file.ts +0 -3
- package/src/models/message.ts +19 -5
- package/src/models/school.ts +26 -0
- package/src/models/tag.ts +140 -0
- package/src/routers/_app.ts +4 -0
- package/src/routers/announcement.ts +1 -3
- package/src/routers/assignment.ts +0 -1
- package/src/routers/class.ts +3 -0
- package/src/routers/department.ts +174 -0
- package/src/routers/file.ts +29 -26
- package/src/routers/folder.ts +100 -121
- package/src/routers/school.ts +83 -0
- package/src/routers/tag.ts +78 -0
- package/src/services/class.ts +108 -3
- package/src/services/department.ts +478 -0
- package/src/services/file.ts +100 -0
- package/src/services/folder.ts +313 -240
- package/src/services/labChat.ts +2 -1
- package/src/services/message.ts +5 -21
- package/src/services/newtonChat.ts +2 -1
- package/src/services/school.ts +369 -57
- package/src/services/tag.ts +229 -0
- package/src/utils/aiUser.ts +0 -1
package/prisma/schema.prisma
CHANGED
|
@@ -111,18 +111,70 @@ enum SchoolInviteStatus {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
model School {
|
|
114
|
-
id
|
|
115
|
-
name
|
|
116
|
-
|
|
117
|
-
logoId
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
114
|
+
id String @id @default(uuid())
|
|
115
|
+
name String
|
|
116
|
+
description String?
|
|
117
|
+
logo File? @relation("SchoolLogo", references: [id], fields: [logoId])
|
|
118
|
+
logoId String?
|
|
119
|
+
subdomain String? @unique
|
|
120
|
+
createdAt DateTime @default(now())
|
|
121
|
+
updatedAt DateTime @updatedAt
|
|
122
|
+
|
|
123
|
+
users User[]
|
|
124
|
+
memberships SchoolMembership[]
|
|
125
|
+
invites SchoolInvite[]
|
|
126
|
+
classes Class[]
|
|
127
|
+
departments Department[]
|
|
128
|
+
tags Tag[]
|
|
129
|
+
storageRoot Folder? @relation("SchoolStorageRoot")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
model Department {
|
|
133
|
+
id String @id @default(uuid())
|
|
134
|
+
schoolId String
|
|
135
|
+
name String
|
|
136
|
+
description String?
|
|
137
|
+
color String? @default("#3B82F6")
|
|
138
|
+
createdAt DateTime @default(now())
|
|
139
|
+
updatedAt DateTime @updatedAt
|
|
140
|
+
|
|
141
|
+
school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
|
|
142
|
+
members User[] @relation("UserDepartments")
|
|
143
|
+
classes Class[]
|
|
144
|
+
tags Tag[]
|
|
145
|
+
rubricTemplates MarkScheme[]
|
|
146
|
+
boundaryTemplates GradingBoundary[]
|
|
147
|
+
|
|
148
|
+
@@unique([schoolId, name])
|
|
149
|
+
@@index([schoolId])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
enum TagTarget {
|
|
153
|
+
USER
|
|
154
|
+
CLASS
|
|
155
|
+
RUBRIC
|
|
156
|
+
BOUNDARY
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
model Tag {
|
|
160
|
+
id String @id @default(uuid())
|
|
161
|
+
schoolId String
|
|
162
|
+
departmentId String?
|
|
163
|
+
name String
|
|
164
|
+
color String @default("#3B82F6")
|
|
165
|
+
createdAt DateTime @default(now())
|
|
166
|
+
updatedAt DateTime @updatedAt
|
|
167
|
+
|
|
168
|
+
school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
|
|
169
|
+
department Department? @relation(fields: [departmentId], references: [id], onDelete: SetNull)
|
|
170
|
+
users User[] @relation("UserTags")
|
|
171
|
+
classes Class[] @relation("ClassTags")
|
|
172
|
+
rubrics MarkScheme[] @relation("MarkSchemeTags")
|
|
173
|
+
boundaries GradingBoundary[] @relation("GradingBoundaryTags")
|
|
174
|
+
|
|
175
|
+
@@unique([schoolId, name])
|
|
176
|
+
@@index([schoolId])
|
|
177
|
+
@@index([departmentId])
|
|
126
178
|
}
|
|
127
179
|
|
|
128
180
|
model User {
|
|
@@ -132,7 +184,6 @@ model User {
|
|
|
132
184
|
password String
|
|
133
185
|
profile UserProfile?
|
|
134
186
|
verified Boolean @default(false)
|
|
135
|
-
role UserRole @default(NONE)
|
|
136
187
|
|
|
137
188
|
teacherIn Class[] @relation("UserTeacherToClass")
|
|
138
189
|
studentIn Class[] @relation("UserStudentToClass")
|
|
@@ -158,6 +209,10 @@ model User {
|
|
|
158
209
|
schoolMemberships SchoolMembership[]
|
|
159
210
|
sentSchoolInvites SchoolInvite[] @relation("SchoolInviteInvitedBy")
|
|
160
211
|
|
|
212
|
+
departments Department[] @relation("UserDepartments")
|
|
213
|
+
tags Tag[] @relation("UserTags")
|
|
214
|
+
personalStorage Folder? @relation("UserStorageRoot")
|
|
215
|
+
|
|
161
216
|
// Chat relations
|
|
162
217
|
conversationMemberships ConversationMember[]
|
|
163
218
|
sentMessages Message[] @relation("SentMessages")
|
|
@@ -238,8 +293,6 @@ model Class {
|
|
|
238
293
|
worksheets Worksheet[]
|
|
239
294
|
students User[] @relation("UserStudentToClass")
|
|
240
295
|
teachers User[] @relation("UserTeacherToClass")
|
|
241
|
-
// markSchemes MarkScheme[] @relation("ClassToMarkScheme")
|
|
242
|
-
// gradingBoundaries GradingBoundary[] @relation("ClassToGradingBoundary")
|
|
243
296
|
draftFiles File[] @relation("ClassDraftFiles")
|
|
244
297
|
classFiles Folder? @relation("ClassFiles")
|
|
245
298
|
labChats LabChat[] @relation("ClassLabChats")
|
|
@@ -247,36 +300,67 @@ model Class {
|
|
|
247
300
|
gradingBoundaries GradingBoundary[]
|
|
248
301
|
school School? @relation(fields: [schoolId], references: [id])
|
|
249
302
|
schoolId String?
|
|
303
|
+
department Department? @relation(fields: [departmentId], references: [id], onDelete: SetNull)
|
|
304
|
+
departmentId String?
|
|
305
|
+
tags Tag[] @relation("ClassTags")
|
|
306
|
+
|
|
307
|
+
@@index([schoolId])
|
|
308
|
+
@@index([departmentId])
|
|
250
309
|
}
|
|
251
310
|
|
|
252
311
|
model MarkScheme {
|
|
253
|
-
id
|
|
254
|
-
classId
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
312
|
+
id String @id @default(uuid())
|
|
313
|
+
classId String?
|
|
314
|
+
class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
315
|
+
departmentId String?
|
|
316
|
+
department Department? @relation(fields: [departmentId], references: [id], onDelete: Cascade)
|
|
317
|
+
name String?
|
|
318
|
+
structured String
|
|
319
|
+
assignments Assignment[]
|
|
320
|
+
tags Tag[] @relation("MarkSchemeTags")
|
|
321
|
+
|
|
322
|
+
@@index([classId])
|
|
323
|
+
@@index([departmentId])
|
|
259
324
|
}
|
|
260
325
|
|
|
261
326
|
model GradingBoundary {
|
|
262
|
-
id
|
|
263
|
-
classId
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
327
|
+
id String @id @default(uuid())
|
|
328
|
+
classId String?
|
|
329
|
+
class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
330
|
+
departmentId String?
|
|
331
|
+
department Department? @relation(fields: [departmentId], references: [id], onDelete: Cascade)
|
|
332
|
+
name String?
|
|
333
|
+
structured String
|
|
334
|
+
assignments Assignment[]
|
|
335
|
+
tags Tag[] @relation("GradingBoundaryTags")
|
|
336
|
+
|
|
337
|
+
@@index([classId])
|
|
338
|
+
@@index([departmentId])
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
enum FolderScope {
|
|
342
|
+
CLASS
|
|
343
|
+
SCHOOL_STORAGE
|
|
344
|
+
PERSONAL
|
|
345
|
+
GENERIC
|
|
268
346
|
}
|
|
269
347
|
|
|
270
348
|
model Folder {
|
|
271
|
-
id String
|
|
349
|
+
id String @id @default(uuid())
|
|
272
350
|
name String
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
351
|
+
scope FolderScope @default(GENERIC)
|
|
352
|
+
files File[] @relation("FolderFiles")
|
|
353
|
+
childFolders Folder[] @relation("ParentChildFolders")
|
|
354
|
+
parentFolder Folder? @relation("ParentChildFolders", fields: [parentFolderId], references: [id])
|
|
276
355
|
parentFolderId String?
|
|
277
|
-
color String?
|
|
278
|
-
|
|
279
|
-
|
|
356
|
+
color String? @default("#3B82F6")
|
|
357
|
+
|
|
358
|
+
class Class? @relation("ClassFiles", fields: [classId], references: [id])
|
|
359
|
+
classId String? @unique
|
|
360
|
+
schoolStorage School? @relation("SchoolStorageRoot", fields: [schoolStorageId], references: [id], onDelete: Cascade)
|
|
361
|
+
schoolStorageId String? @unique
|
|
362
|
+
personalOwner User? @relation("UserStorageRoot", fields: [personalOwnerId], references: [id], onDelete: Cascade)
|
|
363
|
+
personalOwnerId String? @unique
|
|
280
364
|
}
|
|
281
365
|
|
|
282
366
|
model File {
|
|
@@ -328,7 +412,7 @@ model File {
|
|
|
328
412
|
announcement Announcement? @relation("AnnouncementAttachments", fields: [announcementId], references: [id], onDelete: Cascade)
|
|
329
413
|
announcementId String?
|
|
330
414
|
|
|
331
|
-
schools School[]
|
|
415
|
+
schools School[] @relation("SchoolLogo")
|
|
332
416
|
|
|
333
417
|
schoolDevelopementProgram SchoolDevelopementProgram? @relation("SchoolDevelopementProgramSupportingDocumentation", fields: [schoolDevelopementProgramId], references: [id], onDelete: Cascade)
|
|
334
418
|
schoolDevelopementProgramId String?
|
package/src/models/access.ts
CHANGED
|
@@ -3,33 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export const schoolAdminForClassWhere = (userId: string) => ({
|
|
6
|
-
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
role: "ADMIN" as const,
|
|
14
|
-
status: "ACTIVE" as const,
|
|
15
|
-
},
|
|
16
|
-
},
|
|
6
|
+
school: {
|
|
7
|
+
is: {
|
|
8
|
+
memberships: {
|
|
9
|
+
some: {
|
|
10
|
+
userId,
|
|
11
|
+
role: "ADMIN" as const,
|
|
12
|
+
status: "ACTIVE" as const,
|
|
17
13
|
},
|
|
18
14
|
},
|
|
19
15
|
},
|
|
20
|
-
|
|
21
|
-
school: {
|
|
22
|
-
is: {
|
|
23
|
-
users: {
|
|
24
|
-
some: {
|
|
25
|
-
id: userId,
|
|
26
|
-
role: "ADMIN" as const,
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
],
|
|
16
|
+
},
|
|
33
17
|
});
|
|
34
18
|
|
|
35
19
|
export const classTeacherOrSchoolAdminWhere = (userId: string) => ({
|
package/src/models/auth.ts
CHANGED
package/src/models/class.ts
CHANGED
|
@@ -54,6 +54,19 @@ const classIncludeForGetAll = {
|
|
|
54
54
|
},
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
|
+
school: {
|
|
58
|
+
select: {
|
|
59
|
+
id: true,
|
|
60
|
+
name: true,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
department: {
|
|
64
|
+
select: {
|
|
65
|
+
id: true,
|
|
66
|
+
name: true,
|
|
67
|
+
color: true,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
57
70
|
} as const;
|
|
58
71
|
|
|
59
72
|
/** @returns Classes where user is a teacher. */
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudStorage model — scope-aware folder helpers used by the institution and
|
|
3
|
+
* personal cloud storage UI. Sits on top of the same Folder/File tables that
|
|
4
|
+
* back class folders, but adds scope (school/personal) and ownership semantics.
|
|
5
|
+
*/
|
|
6
|
+
import type { FolderScope } from "@prisma/client";
|
|
7
|
+
import { prisma } from "../lib/prisma.js";
|
|
8
|
+
|
|
9
|
+
export type CloudScope =
|
|
10
|
+
| { kind: "CLASS"; classId: string }
|
|
11
|
+
| { kind: "SCHOOL_STORAGE"; schoolId: string }
|
|
12
|
+
| { kind: "PERSONAL"; userId: string };
|
|
13
|
+
|
|
14
|
+
const folderSelect = {
|
|
15
|
+
id: true,
|
|
16
|
+
name: true,
|
|
17
|
+
color: true,
|
|
18
|
+
scope: true,
|
|
19
|
+
classId: true,
|
|
20
|
+
schoolStorageId: true,
|
|
21
|
+
personalOwnerId: true,
|
|
22
|
+
parentFolderId: true,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export async function findOrCreateScopeRoot(scope: CloudScope) {
|
|
26
|
+
switch (scope.kind) {
|
|
27
|
+
case "CLASS": {
|
|
28
|
+
let root = await prisma.folder.findFirst({
|
|
29
|
+
where: { classId: scope.classId, parentFolderId: null },
|
|
30
|
+
});
|
|
31
|
+
if (!root) {
|
|
32
|
+
root = await prisma.folder.create({
|
|
33
|
+
data: {
|
|
34
|
+
name: "Class Files",
|
|
35
|
+
scope: "CLASS" as FolderScope,
|
|
36
|
+
class: { connect: { id: scope.classId } },
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
} else if (root.scope !== "CLASS") {
|
|
40
|
+
root = await prisma.folder.update({
|
|
41
|
+
where: { id: root.id },
|
|
42
|
+
data: { scope: "CLASS" as FolderScope },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return root;
|
|
46
|
+
}
|
|
47
|
+
case "SCHOOL_STORAGE": {
|
|
48
|
+
let root = await prisma.folder.findFirst({
|
|
49
|
+
where: { schoolStorageId: scope.schoolId },
|
|
50
|
+
});
|
|
51
|
+
if (!root) {
|
|
52
|
+
root = await prisma.folder.create({
|
|
53
|
+
data: {
|
|
54
|
+
name: "Cloud Storage",
|
|
55
|
+
scope: "SCHOOL_STORAGE" as FolderScope,
|
|
56
|
+
schoolStorage: { connect: { id: scope.schoolId } },
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return root;
|
|
61
|
+
}
|
|
62
|
+
case "PERSONAL": {
|
|
63
|
+
let root = await prisma.folder.findFirst({
|
|
64
|
+
where: { personalOwnerId: scope.userId },
|
|
65
|
+
});
|
|
66
|
+
if (!root) {
|
|
67
|
+
root = await prisma.folder.create({
|
|
68
|
+
data: {
|
|
69
|
+
name: "My Files",
|
|
70
|
+
scope: "PERSONAL" as FolderScope,
|
|
71
|
+
personalOwner: { connect: { id: scope.userId } },
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return root;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Walk up the folder tree until we reach a root with a scope owner. Used to
|
|
82
|
+
* permission-check sub-folders (which only carry a parentFolderId).
|
|
83
|
+
*/
|
|
84
|
+
export async function resolveFolderScope(
|
|
85
|
+
folderId: string
|
|
86
|
+
): Promise<CloudScope | null> {
|
|
87
|
+
let currentId: string | null = folderId;
|
|
88
|
+
let visits = 0;
|
|
89
|
+
while (currentId && visits < 64) {
|
|
90
|
+
const folder = await prisma.folder.findUnique({
|
|
91
|
+
where: { id: currentId },
|
|
92
|
+
select: folderSelect,
|
|
93
|
+
});
|
|
94
|
+
if (!folder) return null;
|
|
95
|
+
if (folder.classId) return { kind: "CLASS", classId: folder.classId };
|
|
96
|
+
if (folder.schoolStorageId)
|
|
97
|
+
return { kind: "SCHOOL_STORAGE", schoolId: folder.schoolStorageId };
|
|
98
|
+
if (folder.personalOwnerId)
|
|
99
|
+
return { kind: "PERSONAL", userId: folder.personalOwnerId };
|
|
100
|
+
if (!folder.parentFolderId) return null;
|
|
101
|
+
currentId = folder.parentFolderId;
|
|
102
|
+
visits += 1;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const fileSelect = {
|
|
108
|
+
id: true,
|
|
109
|
+
name: true,
|
|
110
|
+
type: true,
|
|
111
|
+
size: true,
|
|
112
|
+
path: true,
|
|
113
|
+
uploadedAt: true,
|
|
114
|
+
user: { select: { id: true, username: true } },
|
|
115
|
+
} as const;
|
|
116
|
+
|
|
117
|
+
const childFolderSelect = {
|
|
118
|
+
id: true,
|
|
119
|
+
name: true,
|
|
120
|
+
color: true,
|
|
121
|
+
_count: { select: { files: true, childFolders: true } },
|
|
122
|
+
} as const;
|
|
123
|
+
|
|
124
|
+
export function findFolderWithContents(folderId: string) {
|
|
125
|
+
return prisma.folder.findFirst({
|
|
126
|
+
where: { id: folderId },
|
|
127
|
+
include: {
|
|
128
|
+
files: {
|
|
129
|
+
where: { uploadStatus: "COMPLETED" },
|
|
130
|
+
select: fileSelect,
|
|
131
|
+
orderBy: { uploadedAt: "desc" },
|
|
132
|
+
},
|
|
133
|
+
childFolders: {
|
|
134
|
+
select: childFolderSelect,
|
|
135
|
+
orderBy: { name: "asc" },
|
|
136
|
+
},
|
|
137
|
+
parentFolder: { select: { id: true, name: true } },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function createSubFolder(data: {
|
|
143
|
+
name: string;
|
|
144
|
+
parentFolderId: string;
|
|
145
|
+
color?: string;
|
|
146
|
+
}) {
|
|
147
|
+
return prisma.folder.create({
|
|
148
|
+
data: {
|
|
149
|
+
name: data.name,
|
|
150
|
+
color: data.color,
|
|
151
|
+
parentFolder: { connect: { id: data.parentFolderId } },
|
|
152
|
+
},
|
|
153
|
+
select: folderSelect,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function findFolderById(folderId: string) {
|
|
158
|
+
return prisma.folder.findUnique({
|
|
159
|
+
where: { id: folderId },
|
|
160
|
+
select: folderSelect,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function deleteFolderById(folderId: string) {
|
|
165
|
+
return prisma.folder.delete({ where: { id: folderId } });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function renameFolder(folderId: string, name: string, color?: string) {
|
|
169
|
+
return prisma.folder.update({
|
|
170
|
+
where: { id: folderId },
|
|
171
|
+
data: {
|
|
172
|
+
name,
|
|
173
|
+
...(color ? { color } : {}),
|
|
174
|
+
},
|
|
175
|
+
select: folderSelect,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function listFolderBreadcrumbs(folderId: string) {
|
|
180
|
+
const breadcrumbs: { id: string; name: string }[] = [];
|
|
181
|
+
let currentId: string | null = folderId;
|
|
182
|
+
let visits = 0;
|
|
183
|
+
while (currentId && visits < 64) {
|
|
184
|
+
const folder = await prisma.folder.findUnique({
|
|
185
|
+
where: { id: currentId },
|
|
186
|
+
select: { id: true, name: true, parentFolderId: true },
|
|
187
|
+
});
|
|
188
|
+
if (!folder) break;
|
|
189
|
+
breadcrumbs.unshift({ id: folder.id, name: folder.name });
|
|
190
|
+
currentId = folder.parentFolderId;
|
|
191
|
+
visits += 1;
|
|
192
|
+
}
|
|
193
|
+
return breadcrumbs;
|
|
194
|
+
}
|
package/src/models/comment.ts
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Department model — department CRUD, members, and template artefacts.
|
|
3
|
+
*
|
|
4
|
+
* Departments scope rubric/boundary templates and tags inside an institution.
|
|
5
|
+
* They are an admin-managed taxonomy; teachers/students can be members of
|
|
6
|
+
* many departments.
|
|
7
|
+
*/
|
|
8
|
+
import { prisma } from "../lib/prisma.js";
|
|
9
|
+
|
|
10
|
+
const departmentSelect = {
|
|
11
|
+
id: true,
|
|
12
|
+
schoolId: true,
|
|
13
|
+
name: true,
|
|
14
|
+
description: true,
|
|
15
|
+
color: true,
|
|
16
|
+
createdAt: true,
|
|
17
|
+
updatedAt: true,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
const memberSelect = {
|
|
21
|
+
id: true,
|
|
22
|
+
username: true,
|
|
23
|
+
email: true,
|
|
24
|
+
profile: {
|
|
25
|
+
select: {
|
|
26
|
+
displayName: true,
|
|
27
|
+
profilePicture: true,
|
|
28
|
+
profilePictureThumbnail: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export function listDepartmentsForSchool(schoolId: string) {
|
|
34
|
+
return prisma.department.findMany({
|
|
35
|
+
where: { schoolId },
|
|
36
|
+
orderBy: { name: "asc" },
|
|
37
|
+
select: {
|
|
38
|
+
...departmentSelect,
|
|
39
|
+
_count: {
|
|
40
|
+
select: {
|
|
41
|
+
members: true,
|
|
42
|
+
classes: true,
|
|
43
|
+
tags: true,
|
|
44
|
+
rubricTemplates: true,
|
|
45
|
+
boundaryTemplates: true,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function listDepartmentsForUser(userId: string, schoolId: string) {
|
|
53
|
+
return prisma.department.findMany({
|
|
54
|
+
where: {
|
|
55
|
+
schoolId,
|
|
56
|
+
members: { some: { id: userId } },
|
|
57
|
+
},
|
|
58
|
+
orderBy: { name: "asc" },
|
|
59
|
+
select: departmentSelect,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getDepartment(departmentId: string) {
|
|
64
|
+
return prisma.department.findUnique({
|
|
65
|
+
where: { id: departmentId },
|
|
66
|
+
select: {
|
|
67
|
+
...departmentSelect,
|
|
68
|
+
members: { select: memberSelect },
|
|
69
|
+
tags: {
|
|
70
|
+
select: { id: true, name: true, color: true },
|
|
71
|
+
},
|
|
72
|
+
rubricTemplates: {
|
|
73
|
+
select: { id: true, name: true, structured: true },
|
|
74
|
+
},
|
|
75
|
+
boundaryTemplates: {
|
|
76
|
+
select: { id: true, name: true, structured: true },
|
|
77
|
+
},
|
|
78
|
+
classes: {
|
|
79
|
+
select: {
|
|
80
|
+
id: true,
|
|
81
|
+
name: true,
|
|
82
|
+
subject: true,
|
|
83
|
+
section: true,
|
|
84
|
+
color: true,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createDepartment(data: {
|
|
92
|
+
schoolId: string;
|
|
93
|
+
name: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
color?: string;
|
|
96
|
+
}) {
|
|
97
|
+
return prisma.department.create({
|
|
98
|
+
data: {
|
|
99
|
+
schoolId: data.schoolId,
|
|
100
|
+
name: data.name,
|
|
101
|
+
description: data.description,
|
|
102
|
+
color: data.color,
|
|
103
|
+
},
|
|
104
|
+
select: departmentSelect,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function updateDepartment(
|
|
109
|
+
departmentId: string,
|
|
110
|
+
data: { name?: string; description?: string | null; color?: string }
|
|
111
|
+
) {
|
|
112
|
+
return prisma.department.update({
|
|
113
|
+
where: { id: departmentId },
|
|
114
|
+
data,
|
|
115
|
+
select: departmentSelect,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function deleteDepartment(departmentId: string) {
|
|
120
|
+
return prisma.department.delete({
|
|
121
|
+
where: { id: departmentId },
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function addDepartmentMembers(
|
|
126
|
+
departmentId: string,
|
|
127
|
+
userIds: string[]
|
|
128
|
+
) {
|
|
129
|
+
return prisma.department.update({
|
|
130
|
+
where: { id: departmentId },
|
|
131
|
+
data: {
|
|
132
|
+
members: { connect: userIds.map((id) => ({ id })) },
|
|
133
|
+
},
|
|
134
|
+
select: { id: true },
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function removeDepartmentMember(
|
|
139
|
+
departmentId: string,
|
|
140
|
+
userId: string
|
|
141
|
+
) {
|
|
142
|
+
return prisma.department.update({
|
|
143
|
+
where: { id: departmentId },
|
|
144
|
+
data: {
|
|
145
|
+
members: { disconnect: { id: userId } },
|
|
146
|
+
},
|
|
147
|
+
select: { id: true },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function findDepartmentMembership(
|
|
152
|
+
departmentId: string,
|
|
153
|
+
userId: string
|
|
154
|
+
) {
|
|
155
|
+
return prisma.department.findFirst({
|
|
156
|
+
where: { id: departmentId, members: { some: { id: userId } } },
|
|
157
|
+
select: { id: true },
|
|
158
|
+
});
|
|
159
|
+
}
|
package/src/models/event.ts
CHANGED
|
@@ -11,7 +11,6 @@ const eventGetInclude = {
|
|
|
11
11
|
school: {
|
|
12
12
|
include: {
|
|
13
13
|
users: {
|
|
14
|
-
where: { role: "ADMIN" as const },
|
|
15
14
|
select: { id: true },
|
|
16
15
|
},
|
|
17
16
|
memberships: {
|
|
@@ -134,7 +133,6 @@ export function findEventWithClass(eventId: string) {
|
|
|
134
133
|
school: {
|
|
135
134
|
include: {
|
|
136
135
|
users: {
|
|
137
|
-
where: { role: "ADMIN" as const },
|
|
138
136
|
select: { id: true },
|
|
139
137
|
},
|
|
140
138
|
memberships: {
|
|
@@ -163,7 +161,6 @@ export function findAssignmentWithClass(assignmentId: string) {
|
|
|
163
161
|
school: {
|
|
164
162
|
include: {
|
|
165
163
|
users: {
|
|
166
|
-
where: { role: "ADMIN" as const },
|
|
167
164
|
select: { id: true },
|
|
168
165
|
},
|
|
169
166
|
memberships: {
|
package/src/models/file.ts
CHANGED
|
@@ -9,7 +9,6 @@ const classFileAccessInclude = {
|
|
|
9
9
|
school: {
|
|
10
10
|
include: {
|
|
11
11
|
users: {
|
|
12
|
-
where: { role: "ADMIN" as const },
|
|
13
12
|
select: { id: true },
|
|
14
13
|
},
|
|
15
14
|
memberships: {
|
|
@@ -46,7 +45,6 @@ export function findFileById(fileId: string) {
|
|
|
46
45
|
school: {
|
|
47
46
|
include: {
|
|
48
47
|
users: {
|
|
49
|
-
where: { role: "ADMIN" as const },
|
|
50
48
|
select: { id: true },
|
|
51
49
|
},
|
|
52
50
|
memberships: {
|
|
@@ -75,7 +73,6 @@ export function findFileById(fileId: string) {
|
|
|
75
73
|
school: {
|
|
76
74
|
include: {
|
|
77
75
|
users: {
|
|
78
|
-
where: { role: "ADMIN" as const },
|
|
79
76
|
select: { id: true },
|
|
80
77
|
},
|
|
81
78
|
memberships: {
|