@studious-lms/server 1.6.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 +38 -22
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/conversation.js +50 -2
- package/dist/models/conversation.js.map +1 -1
- 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 +3556 -2298
- 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 +20 -5
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/conversation.js +6 -3
- package/dist/routers/conversation.js.map +1 -1
- 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/assignment.js +4 -4
- package/dist/services/assignment.js.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 +21 -5
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/conversation.js +22 -3
- package/dist/services/conversation.js.map +1 -1
- 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 +8 -7
- 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 +7 -6
- 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/conversation.ts +54 -0
- 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/conversation.ts +7 -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/assignment.ts +2 -3
- package/src/services/class.ts +108 -3
- package/src/services/conversation.ts +30 -0
- 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 +7 -6
- package/src/services/message.ts +5 -21
- package/src/services/newtonChat.ts +5 -5
- package/src/services/school.ts +369 -57
- package/src/services/tag.ts +229 -0
- package/src/utils/aiUser.ts +0 -1
- package/tests/integration/routers/conversation.test.ts +121 -2
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
|
@@ -85,6 +85,60 @@ export function findUserByUsername(username: string) {
|
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Search users who share at least one active school membership with the
|
|
90
|
+
* requester and match the query by username or profile display name.
|
|
91
|
+
*
|
|
92
|
+
* The requester is always excluded so the result set can be used directly as
|
|
93
|
+
* conversation member suggestions.
|
|
94
|
+
*/
|
|
95
|
+
export async function searchInstitutionConversationMembers(
|
|
96
|
+
userId: string,
|
|
97
|
+
query: string,
|
|
98
|
+
limit: number
|
|
99
|
+
) {
|
|
100
|
+
const memberships = await prisma.schoolMembership.findMany({
|
|
101
|
+
where: { userId, status: "ACTIVE" },
|
|
102
|
+
select: { schoolId: true },
|
|
103
|
+
});
|
|
104
|
+
const schoolIds = memberships.map((membership) => membership.schoolId);
|
|
105
|
+
|
|
106
|
+
if (schoolIds.length === 0) return [];
|
|
107
|
+
|
|
108
|
+
return prisma.user.findMany({
|
|
109
|
+
where: {
|
|
110
|
+
id: { not: userId },
|
|
111
|
+
schoolMemberships: {
|
|
112
|
+
some: {
|
|
113
|
+
schoolId: { in: schoolIds },
|
|
114
|
+
status: "ACTIVE",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
OR: [
|
|
118
|
+
{ username: { contains: query, mode: "insensitive" } },
|
|
119
|
+
{
|
|
120
|
+
profile: {
|
|
121
|
+
is: { displayName: { contains: query, mode: "insensitive" } },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
select: {
|
|
127
|
+
id: true,
|
|
128
|
+
username: true,
|
|
129
|
+
profile: {
|
|
130
|
+
select: {
|
|
131
|
+
displayName: true,
|
|
132
|
+
profilePicture: true,
|
|
133
|
+
profilePictureThumbnail: true,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
orderBy: { username: "asc" },
|
|
138
|
+
take: limit,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
88
142
|
/** @returns User's DM conversations. */
|
|
89
143
|
export function findDmConversationsByUserId(userId: string) {
|
|
90
144
|
return prisma.conversation.findMany({
|