@studious-lms/server 1.7.0 → 1.9.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.
Files changed (212) hide show
  1. package/dist/middleware/security.d.ts.map +1 -1
  2. package/dist/middleware/security.js +3 -3
  3. package/dist/middleware/security.js.map +1 -1
  4. package/dist/models/access.d.ts +21 -66
  5. package/dist/models/access.d.ts.map +1 -1
  6. package/dist/models/access.js +10 -26
  7. package/dist/models/access.js.map +1 -1
  8. package/dist/models/agenda.d.ts +4 -2
  9. package/dist/models/agenda.d.ts.map +1 -1
  10. package/dist/models/announcement.d.ts +4 -3
  11. package/dist/models/announcement.d.ts.map +1 -1
  12. package/dist/models/announcement.js +2 -3
  13. package/dist/models/announcement.js.map +1 -1
  14. package/dist/models/assignment.d.ts +40 -33
  15. package/dist/models/assignment.d.ts.map +1 -1
  16. package/dist/models/auth.d.ts +2 -4
  17. package/dist/models/auth.d.ts.map +1 -1
  18. package/dist/models/auth.js +2 -3
  19. package/dist/models/auth.js.map +1 -1
  20. package/dist/models/class.d.ts +34 -6
  21. package/dist/models/class.d.ts.map +1 -1
  22. package/dist/models/class.js +15 -2
  23. package/dist/models/class.js.map +1 -1
  24. package/dist/models/cloudStorage.d.ts +153 -0
  25. package/dist/models/cloudStorage.d.ts.map +1 -0
  26. package/dist/models/cloudStorage.js +295 -0
  27. package/dist/models/cloudStorage.js.map +1 -0
  28. package/dist/models/comment.d.ts +0 -24
  29. package/dist/models/comment.d.ts.map +1 -1
  30. package/dist/models/comment.js +2 -6
  31. package/dist/models/comment.js.map +1 -1
  32. package/dist/models/conversation.d.ts +22 -22
  33. package/dist/models/department.d.ts +154 -0
  34. package/dist/models/department.d.ts.map +1 -0
  35. package/dist/models/department.js +135 -0
  36. package/dist/models/department.js.map +1 -0
  37. package/dist/models/event.d.ts +10 -4
  38. package/dist/models/event.d.ts.map +1 -1
  39. package/dist/models/event.js +2 -5
  40. package/dist/models/event.js.map +1 -1
  41. package/dist/models/file.d.ts +52 -36
  42. package/dist/models/file.d.ts.map +1 -1
  43. package/dist/models/file.js +4 -42
  44. package/dist/models/file.js.map +1 -1
  45. package/dist/models/folder.d.ts +47 -0
  46. package/dist/models/folder.d.ts.map +1 -1
  47. package/dist/models/labChat.d.ts +11 -9
  48. package/dist/models/labChat.d.ts.map +1 -1
  49. package/dist/models/message.d.ts +46 -12
  50. package/dist/models/message.d.ts.map +1 -1
  51. package/dist/models/message.js +19 -10
  52. package/dist/models/message.js.map +1 -1
  53. package/dist/models/newtonChat.d.ts +2 -1
  54. package/dist/models/newtonChat.d.ts.map +1 -1
  55. package/dist/models/school.d.ts +45 -12
  56. package/dist/models/school.d.ts.map +1 -1
  57. package/dist/models/school.js +19 -2
  58. package/dist/models/school.js.map +1 -1
  59. package/dist/models/section.d.ts +2 -1
  60. package/dist/models/section.d.ts.map +1 -1
  61. package/dist/models/tag.d.ts +117 -0
  62. package/dist/models/tag.d.ts.map +1 -0
  63. package/dist/models/tag.js +113 -0
  64. package/dist/models/tag.js.map +1 -0
  65. package/dist/models/worksheet.d.ts +3 -0
  66. package/dist/models/worksheet.d.ts.map +1 -1
  67. package/dist/pipelines/aiLabChatContract.d.ts +8 -8
  68. package/dist/routers/_app.d.ts +6704 -5222
  69. package/dist/routers/_app.d.ts.map +1 -1
  70. package/dist/routers/_app.js +8 -2
  71. package/dist/routers/_app.js.map +1 -1
  72. package/dist/routers/agenda.d.ts +4 -2
  73. package/dist/routers/agenda.d.ts.map +1 -1
  74. package/dist/routers/announcement.d.ts.map +1 -1
  75. package/dist/routers/announcement.js +3 -5
  76. package/dist/routers/announcement.js.map +1 -1
  77. package/dist/routers/assignment.d.ts +49 -40
  78. package/dist/routers/assignment.d.ts.map +1 -1
  79. package/dist/routers/assignment.js +2 -3
  80. package/dist/routers/assignment.js.map +1 -1
  81. package/dist/routers/class.d.ts +92 -19
  82. package/dist/routers/class.d.ts.map +1 -1
  83. package/dist/routers/class.js +5 -2
  84. package/dist/routers/class.js.map +1 -1
  85. package/dist/routers/conversation.d.ts +5 -5
  86. package/dist/routers/department.d.ts +293 -0
  87. package/dist/routers/department.d.ts.map +1 -0
  88. package/dist/routers/department.js +93 -0
  89. package/dist/routers/department.js.map +1 -0
  90. package/dist/routers/event.d.ts +4 -3
  91. package/dist/routers/event.d.ts.map +1 -1
  92. package/dist/routers/file.d.ts +10 -8
  93. package/dist/routers/file.d.ts.map +1 -1
  94. package/dist/routers/file.js +26 -17
  95. package/dist/routers/file.js.map +1 -1
  96. package/dist/routers/folder.d.ts +163 -186
  97. package/dist/routers/folder.d.ts.map +1 -1
  98. package/dist/routers/folder.js +102 -88
  99. package/dist/routers/folder.js.map +1 -1
  100. package/dist/routers/labChat.d.ts +13 -4
  101. package/dist/routers/labChat.d.ts.map +1 -1
  102. package/dist/routers/message.d.ts +20 -2
  103. package/dist/routers/message.d.ts.map +1 -1
  104. package/dist/routers/newtonChat.d.ts +10 -1
  105. package/dist/routers/newtonChat.d.ts.map +1 -1
  106. package/dist/routers/permissions.d.ts +120 -0
  107. package/dist/routers/permissions.d.ts.map +1 -0
  108. package/dist/routers/permissions.js +50 -0
  109. package/dist/routers/permissions.js.map +1 -0
  110. package/dist/routers/school.d.ts +160 -17
  111. package/dist/routers/school.d.ts.map +1 -1
  112. package/dist/routers/school.js +46 -3
  113. package/dist/routers/school.js.map +1 -1
  114. package/dist/routers/tag.d.ts +119 -0
  115. package/dist/routers/tag.d.ts.map +1 -0
  116. package/dist/routers/tag.js +52 -0
  117. package/dist/routers/tag.js.map +1 -0
  118. package/dist/routers/worksheet.d.ts +1 -0
  119. package/dist/routers/worksheet.d.ts.map +1 -1
  120. package/dist/seedDatabase.d.ts +0 -1
  121. package/dist/seedDatabase.d.ts.map +1 -1
  122. package/dist/services/agenda.d.ts +4 -2
  123. package/dist/services/agenda.d.ts.map +1 -1
  124. package/dist/services/assignment.d.ts +49 -40
  125. package/dist/services/assignment.d.ts.map +1 -1
  126. package/dist/services/class.d.ts +92 -19
  127. package/dist/services/class.d.ts.map +1 -1
  128. package/dist/services/class.js +92 -5
  129. package/dist/services/class.js.map +1 -1
  130. package/dist/services/conversation.d.ts +5 -5
  131. package/dist/services/department.d.ts +200 -0
  132. package/dist/services/department.d.ts.map +1 -0
  133. package/dist/services/department.js +368 -0
  134. package/dist/services/department.js.map +1 -0
  135. package/dist/services/event.d.ts +4 -3
  136. package/dist/services/event.d.ts.map +1 -1
  137. package/dist/services/file.d.ts +105 -3
  138. package/dist/services/file.d.ts.map +1 -1
  139. package/dist/services/file.js +167 -41
  140. package/dist/services/file.js.map +1 -1
  141. package/dist/services/folder.d.ts +220 -149
  142. package/dist/services/folder.d.ts.map +1 -1
  143. package/dist/services/folder.js +326 -196
  144. package/dist/services/folder.js.map +1 -1
  145. package/dist/services/labChat.d.ts +13 -4
  146. package/dist/services/labChat.d.ts.map +1 -1
  147. package/dist/services/labChat.js +4 -3
  148. package/dist/services/labChat.js.map +1 -1
  149. package/dist/services/message.d.ts +20 -2
  150. package/dist/services/message.d.ts.map +1 -1
  151. package/dist/services/message.js +7 -23
  152. package/dist/services/message.js.map +1 -1
  153. package/dist/services/newtonChat.d.ts +9 -0
  154. package/dist/services/newtonChat.d.ts.map +1 -1
  155. package/dist/services/newtonChat.js +4 -3
  156. package/dist/services/newtonChat.js.map +1 -1
  157. package/dist/services/permissions.d.ts +188 -0
  158. package/dist/services/permissions.d.ts.map +1 -0
  159. package/dist/services/permissions.js +661 -0
  160. package/dist/services/permissions.js.map +1 -0
  161. package/dist/services/school.d.ts +142 -15
  162. package/dist/services/school.d.ts.map +1 -1
  163. package/dist/services/school.js +308 -54
  164. package/dist/services/school.js.map +1 -1
  165. package/dist/services/tag.d.ts +71 -0
  166. package/dist/services/tag.d.ts.map +1 -0
  167. package/dist/services/tag.js +178 -0
  168. package/dist/services/tag.js.map +1 -0
  169. package/dist/services/worksheet.d.ts +1 -0
  170. package/dist/services/worksheet.d.ts.map +1 -1
  171. package/dist/utils/aiUser.d.ts.map +1 -1
  172. package/dist/utils/aiUser.js +2 -3
  173. package/dist/utils/aiUser.js.map +1 -1
  174. package/package.json +1 -1
  175. package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +184 -0
  176. package/prisma/migrations/20260510170000_add_folder_permissions/migration.sql +93 -0
  177. package/prisma/schema.prisma +193 -35
  178. package/src/middleware/security.ts +1 -1
  179. package/src/models/access.ts +8 -24
  180. package/src/models/announcement.ts +0 -1
  181. package/src/models/auth.ts +0 -1
  182. package/src/models/class.ts +13 -0
  183. package/src/models/cloudStorage.ts +327 -0
  184. package/src/models/comment.ts +0 -4
  185. package/src/models/department.ts +159 -0
  186. package/src/models/event.ts +0 -3
  187. package/src/models/file.ts +2 -40
  188. package/src/models/message.ts +19 -5
  189. package/src/models/school.ts +26 -0
  190. package/src/models/tag.ts +140 -0
  191. package/src/routers/_app.ts +6 -0
  192. package/src/routers/announcement.ts +1 -3
  193. package/src/routers/assignment.ts +0 -1
  194. package/src/routers/class.ts +3 -0
  195. package/src/routers/department.ts +174 -0
  196. package/src/routers/file.ts +29 -26
  197. package/src/routers/folder.ts +127 -118
  198. package/src/routers/permissions.ts +71 -0
  199. package/src/routers/school.ts +83 -0
  200. package/src/routers/tag.ts +78 -0
  201. package/src/services/class.ts +109 -4
  202. package/src/services/department.ts +478 -0
  203. package/src/services/file.ts +172 -43
  204. package/src/services/folder.ts +399 -237
  205. package/src/services/labChat.ts +2 -1
  206. package/src/services/message.ts +5 -21
  207. package/src/services/newtonChat.ts +2 -1
  208. package/src/services/permissions.ts +778 -0
  209. package/src/services/school.ts +369 -57
  210. package/src/services/tag.ts +229 -0
  211. package/src/utils/aiUser.ts +0 -1
  212. package/tests/unit/services/permissions.test.ts +463 -0
@@ -0,0 +1,295 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="26810bf7-c72f-5655-8994-96075d84b6fe")}catch(e){}}();
3
+ import { prisma } from "../lib/prisma.js";
4
+ const folderSelect = {
5
+ id: true,
6
+ name: true,
7
+ color: true,
8
+ scope: true,
9
+ classId: true,
10
+ schoolStorageId: true,
11
+ personalOwnerId: true,
12
+ parentFolderId: true,
13
+ };
14
+ export async function findOrCreateScopeRoot(scope) {
15
+ switch (scope.kind) {
16
+ case "CLASS": {
17
+ let root = await prisma.folder.findFirst({
18
+ where: { classId: scope.classId, parentFolderId: null },
19
+ });
20
+ if (!root) {
21
+ root = await prisma.folder.create({
22
+ data: {
23
+ name: "Class Files",
24
+ scope: "CLASS",
25
+ class: { connect: { id: scope.classId } },
26
+ },
27
+ });
28
+ }
29
+ else if (root.scope !== "CLASS") {
30
+ root = await prisma.folder.update({
31
+ where: { id: root.id },
32
+ data: { scope: "CLASS" },
33
+ });
34
+ }
35
+ return root;
36
+ }
37
+ case "SCHOOL_STORAGE": {
38
+ let root = await prisma.folder.findFirst({
39
+ where: { schoolStorageId: scope.schoolId },
40
+ });
41
+ if (!root) {
42
+ root = await prisma.folder.create({
43
+ data: {
44
+ name: "Cloud Storage",
45
+ scope: "SCHOOL_STORAGE",
46
+ schoolStorage: { connect: { id: scope.schoolId } },
47
+ },
48
+ });
49
+ }
50
+ return root;
51
+ }
52
+ case "PERSONAL": {
53
+ let root = await prisma.folder.findFirst({
54
+ where: { personalOwnerId: scope.userId },
55
+ });
56
+ if (!root) {
57
+ root = await prisma.folder.create({
58
+ data: {
59
+ name: "My Files",
60
+ scope: "PERSONAL",
61
+ personalOwner: { connect: { id: scope.userId } },
62
+ },
63
+ });
64
+ }
65
+ return root;
66
+ }
67
+ }
68
+ }
69
+ /**
70
+ * Walk up the folder tree until we reach a root with a scope owner. Used to
71
+ * permission-check sub-folders (which only carry a parentFolderId).
72
+ */
73
+ export async function resolveFolderScope(folderId) {
74
+ let currentId = folderId;
75
+ let visits = 0;
76
+ while (currentId && visits < 64) {
77
+ const folder = await prisma.folder.findUnique({
78
+ where: { id: currentId },
79
+ select: folderSelect,
80
+ });
81
+ if (!folder)
82
+ return null;
83
+ if (folder.classId)
84
+ return { kind: "CLASS", classId: folder.classId };
85
+ if (folder.schoolStorageId)
86
+ return { kind: "SCHOOL_STORAGE", schoolId: folder.schoolStorageId };
87
+ if (folder.personalOwnerId)
88
+ return { kind: "PERSONAL", userId: folder.personalOwnerId };
89
+ if (!folder.parentFolderId)
90
+ return null;
91
+ currentId = folder.parentFolderId;
92
+ visits += 1;
93
+ }
94
+ return null;
95
+ }
96
+ const fileSelect = {
97
+ id: true,
98
+ name: true,
99
+ type: true,
100
+ size: true,
101
+ path: true,
102
+ uploadedAt: true,
103
+ user: { select: { id: true, username: true } },
104
+ };
105
+ const childFolderSelect = {
106
+ id: true,
107
+ name: true,
108
+ color: true,
109
+ _count: { select: { files: true, childFolders: true } },
110
+ };
111
+ export function findFolderWithContents(folderId) {
112
+ return prisma.folder.findFirst({
113
+ where: { id: folderId },
114
+ include: {
115
+ files: {
116
+ where: { uploadStatus: "COMPLETED" },
117
+ select: fileSelect,
118
+ orderBy: { uploadedAt: "desc" },
119
+ },
120
+ childFolders: {
121
+ select: childFolderSelect,
122
+ orderBy: { name: "asc" },
123
+ },
124
+ parentFolder: { select: { id: true, name: true } },
125
+ },
126
+ });
127
+ }
128
+ export function createSubFolder(data) {
129
+ return prisma.folder.create({
130
+ data: {
131
+ name: data.name,
132
+ color: data.color,
133
+ parentFolder: { connect: { id: data.parentFolderId } },
134
+ },
135
+ select: folderSelect,
136
+ });
137
+ }
138
+ export function findFolderById(folderId) {
139
+ return prisma.folder.findUnique({
140
+ where: { id: folderId },
141
+ select: folderSelect,
142
+ });
143
+ }
144
+ export function deleteFolderById(folderId) {
145
+ return prisma.folder.delete({ where: { id: folderId } });
146
+ }
147
+ export function renameFolder(folderId, name, color) {
148
+ return prisma.folder.update({
149
+ where: { id: folderId },
150
+ data: {
151
+ name,
152
+ ...(color ? { color } : {}),
153
+ },
154
+ select: folderSelect,
155
+ });
156
+ }
157
+ /**
158
+ * Build the breadcrumb chain for a folder.
159
+ *
160
+ * Without `viewerId`: walks all the way up to the scope root (owner-style
161
+ * view).
162
+ *
163
+ * With `viewerId`: walks up but stops at the highest folder the viewer is
164
+ * actually granted on, OR at the scope root if the viewer is a scope member.
165
+ * This is the Drive-style "Shared with me" behavior — a grantee never sees
166
+ * ancestor folders they weren't shared on. The result for a grantee browsing
167
+ * `Owner/PrivateA/SharedB/C` (granted on B) reads `SharedB / C` — the ancestor
168
+ * `PrivateA` is correctly hidden.
169
+ */
170
+ export async function listFolderBreadcrumbs(folderId, viewerId) {
171
+ const breadcrumbs = [];
172
+ let currentId = folderId;
173
+ let visits = 0;
174
+ let viewerGrantedFolderIds = null;
175
+ let viewerScopeMember = false;
176
+ if (viewerId) {
177
+ const grants = await prisma.permission.findMany({
178
+ where: {
179
+ OR: [
180
+ { principalType: "USER", userId: viewerId },
181
+ {
182
+ principalType: "TAG",
183
+ tag: { users: { some: { id: viewerId } } },
184
+ },
185
+ {
186
+ principalType: "CLASS",
187
+ class: {
188
+ OR: [
189
+ { teachers: { some: { id: viewerId } } },
190
+ { students: { some: { id: viewerId } } },
191
+ ],
192
+ },
193
+ },
194
+ {
195
+ principalType: "CLASS_TEACHERS",
196
+ class: { teachers: { some: { id: viewerId } } },
197
+ },
198
+ {
199
+ principalType: "CLASS_STUDENTS",
200
+ class: { students: { some: { id: viewerId } } },
201
+ },
202
+ {
203
+ principalType: "DEPARTMENT",
204
+ department: { members: { some: { id: viewerId } } },
205
+ },
206
+ {
207
+ principalType: "SCHOOL",
208
+ school: {
209
+ memberships: { some: { userId: viewerId, status: "ACTIVE" } },
210
+ },
211
+ },
212
+ ],
213
+ },
214
+ select: { folderId: true },
215
+ });
216
+ viewerGrantedFolderIds = new Set(grants.map((g) => g.folderId).filter((x) => Boolean(x)));
217
+ // Determine if the viewer is a member of the *target* folder's scope —
218
+ // if so, breadcrumbs go all the way to the scope root as normal.
219
+ const scope = await resolveFolderScope(folderId);
220
+ if (scope) {
221
+ switch (scope.kind) {
222
+ case "CLASS": {
223
+ const c = await prisma.class.findFirst({
224
+ where: {
225
+ id: scope.classId,
226
+ OR: [
227
+ { teachers: { some: { id: viewerId } } },
228
+ { students: { some: { id: viewerId } } },
229
+ ],
230
+ },
231
+ select: { id: true },
232
+ });
233
+ viewerScopeMember = Boolean(c);
234
+ if (!viewerScopeMember && scope.classId) {
235
+ const klass = await prisma.class.findUnique({
236
+ where: { id: scope.classId },
237
+ select: { schoolId: true },
238
+ });
239
+ if (klass?.schoolId) {
240
+ const m = await prisma.schoolMembership.findFirst({
241
+ where: {
242
+ schoolId: klass.schoolId,
243
+ userId: viewerId,
244
+ role: "ADMIN",
245
+ status: "ACTIVE",
246
+ },
247
+ select: { id: true },
248
+ });
249
+ viewerScopeMember = Boolean(m);
250
+ }
251
+ }
252
+ break;
253
+ }
254
+ case "SCHOOL_STORAGE": {
255
+ const m = await prisma.schoolMembership.findFirst({
256
+ where: {
257
+ schoolId: scope.schoolId,
258
+ userId: viewerId,
259
+ status: "ACTIVE",
260
+ },
261
+ select: { id: true },
262
+ });
263
+ viewerScopeMember = Boolean(m);
264
+ break;
265
+ }
266
+ case "PERSONAL":
267
+ viewerScopeMember = scope.userId === viewerId;
268
+ break;
269
+ }
270
+ }
271
+ }
272
+ while (currentId && visits < 64) {
273
+ const folder = await prisma.folder.findUnique({
274
+ where: { id: currentId },
275
+ select: { id: true, name: true, parentFolderId: true },
276
+ });
277
+ if (!folder)
278
+ break;
279
+ breadcrumbs.unshift({ id: folder.id, name: folder.name });
280
+ // Viewer-aware truncation: when the viewer is *not* a scope member but
281
+ // is a grantee, stop at the highest granted ancestor (we walk up one
282
+ // more step into the granted folder, then stop there).
283
+ if (viewerId &&
284
+ !viewerScopeMember &&
285
+ viewerGrantedFolderIds &&
286
+ viewerGrantedFolderIds.has(folder.id)) {
287
+ break;
288
+ }
289
+ currentId = folder.parentFolderId;
290
+ visits += 1;
291
+ }
292
+ return breadcrumbs;
293
+ }
294
+ //# sourceMappingURL=cloudStorage.js.map
295
+ //# debugId=26810bf7-c72f-5655-8994-96075d84b6fe
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudStorage.js","sources":["models/cloudStorage.ts"],"sourceRoot":"/","sourcesContent":["/**\n * CloudStorage model — scope-aware folder helpers used by the institution and\n * personal cloud storage UI. Sits on top of the same Folder/File tables that\n * back class folders, but adds scope (school/personal) and ownership semantics.\n */\nimport type { FolderScope } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\n\nexport type CloudScope =\n | { kind: \"CLASS\"; classId: string }\n | { kind: \"SCHOOL_STORAGE\"; schoolId: string }\n | { kind: \"PERSONAL\"; userId: string };\n\nconst folderSelect = {\n id: true,\n name: true,\n color: true,\n scope: true,\n classId: true,\n schoolStorageId: true,\n personalOwnerId: true,\n parentFolderId: true,\n} as const;\n\nexport async function findOrCreateScopeRoot(scope: CloudScope) {\n switch (scope.kind) {\n case \"CLASS\": {\n let root = await prisma.folder.findFirst({\n where: { classId: scope.classId, parentFolderId: null },\n });\n if (!root) {\n root = await prisma.folder.create({\n data: {\n name: \"Class Files\",\n scope: \"CLASS\" as FolderScope,\n class: { connect: { id: scope.classId } },\n },\n });\n } else if (root.scope !== \"CLASS\") {\n root = await prisma.folder.update({\n where: { id: root.id },\n data: { scope: \"CLASS\" as FolderScope },\n });\n }\n return root;\n }\n case \"SCHOOL_STORAGE\": {\n let root = await prisma.folder.findFirst({\n where: { schoolStorageId: scope.schoolId },\n });\n if (!root) {\n root = await prisma.folder.create({\n data: {\n name: \"Cloud Storage\",\n scope: \"SCHOOL_STORAGE\" as FolderScope,\n schoolStorage: { connect: { id: scope.schoolId } },\n },\n });\n }\n return root;\n }\n case \"PERSONAL\": {\n let root = await prisma.folder.findFirst({\n where: { personalOwnerId: scope.userId },\n });\n if (!root) {\n root = await prisma.folder.create({\n data: {\n name: \"My Files\",\n scope: \"PERSONAL\" as FolderScope,\n personalOwner: { connect: { id: scope.userId } },\n },\n });\n }\n return root;\n }\n }\n}\n\n/**\n * Walk up the folder tree until we reach a root with a scope owner. Used to\n * permission-check sub-folders (which only carry a parentFolderId).\n */\nexport async function resolveFolderScope(\n folderId: string\n): Promise<CloudScope | null> {\n let currentId: string | null = folderId;\n let visits = 0;\n while (currentId && visits < 64) {\n const folder = await prisma.folder.findUnique({\n where: { id: currentId },\n select: folderSelect,\n });\n if (!folder) return null;\n if (folder.classId) return { kind: \"CLASS\", classId: folder.classId };\n if (folder.schoolStorageId)\n return { kind: \"SCHOOL_STORAGE\", schoolId: folder.schoolStorageId };\n if (folder.personalOwnerId)\n return { kind: \"PERSONAL\", userId: folder.personalOwnerId };\n if (!folder.parentFolderId) return null;\n currentId = folder.parentFolderId;\n visits += 1;\n }\n return null;\n}\n\nconst fileSelect = {\n id: true,\n name: true,\n type: true,\n size: true,\n path: true,\n uploadedAt: true,\n user: { select: { id: true, username: true } },\n} as const;\n\nconst childFolderSelect = {\n id: true,\n name: true,\n color: true,\n _count: { select: { files: true, childFolders: true } },\n} as const;\n\nexport function findFolderWithContents(folderId: string) {\n return prisma.folder.findFirst({\n where: { id: folderId },\n include: {\n files: {\n where: { uploadStatus: \"COMPLETED\" },\n select: fileSelect,\n orderBy: { uploadedAt: \"desc\" },\n },\n childFolders: {\n select: childFolderSelect,\n orderBy: { name: \"asc\" },\n },\n parentFolder: { select: { id: true, name: true } },\n },\n });\n}\n\nexport function createSubFolder(data: {\n name: string;\n parentFolderId: string;\n color?: string;\n}) {\n return prisma.folder.create({\n data: {\n name: data.name,\n color: data.color,\n parentFolder: { connect: { id: data.parentFolderId } },\n },\n select: folderSelect,\n });\n}\n\nexport function findFolderById(folderId: string) {\n return prisma.folder.findUnique({\n where: { id: folderId },\n select: folderSelect,\n });\n}\n\nexport function deleteFolderById(folderId: string) {\n return prisma.folder.delete({ where: { id: folderId } });\n}\n\nexport function renameFolder(folderId: string, name: string, color?: string) {\n return prisma.folder.update({\n where: { id: folderId },\n data: {\n name,\n ...(color ? { color } : {}),\n },\n select: folderSelect,\n });\n}\n\n/**\n * Build the breadcrumb chain for a folder.\n *\n * Without `viewerId`: walks all the way up to the scope root (owner-style\n * view).\n *\n * With `viewerId`: walks up but stops at the highest folder the viewer is\n * actually granted on, OR at the scope root if the viewer is a scope member.\n * This is the Drive-style \"Shared with me\" behavior — a grantee never sees\n * ancestor folders they weren't shared on. The result for a grantee browsing\n * `Owner/PrivateA/SharedB/C` (granted on B) reads `SharedB / C` — the ancestor\n * `PrivateA` is correctly hidden.\n */\nexport async function listFolderBreadcrumbs(\n folderId: string,\n viewerId?: string\n) {\n const breadcrumbs: { id: string; name: string }[] = [];\n let currentId: string | null = folderId;\n let visits = 0;\n\n let viewerGrantedFolderIds: Set<string> | null = null;\n let viewerScopeMember = false;\n\n if (viewerId) {\n const grants = await prisma.permission.findMany({\n where: {\n OR: [\n { principalType: \"USER\", userId: viewerId },\n {\n principalType: \"TAG\",\n tag: { users: { some: { id: viewerId } } },\n },\n {\n principalType: \"CLASS\",\n class: {\n OR: [\n { teachers: { some: { id: viewerId } } },\n { students: { some: { id: viewerId } } },\n ],\n },\n },\n {\n principalType: \"CLASS_TEACHERS\",\n class: { teachers: { some: { id: viewerId } } },\n },\n {\n principalType: \"CLASS_STUDENTS\",\n class: { students: { some: { id: viewerId } } },\n },\n {\n principalType: \"DEPARTMENT\",\n department: { members: { some: { id: viewerId } } },\n },\n {\n principalType: \"SCHOOL\",\n school: {\n memberships: { some: { userId: viewerId, status: \"ACTIVE\" } },\n },\n },\n ],\n },\n select: { folderId: true },\n });\n viewerGrantedFolderIds = new Set(\n grants.map((g) => g.folderId).filter((x): x is string => Boolean(x))\n );\n\n // Determine if the viewer is a member of the *target* folder's scope —\n // if so, breadcrumbs go all the way to the scope root as normal.\n const scope = await resolveFolderScope(folderId);\n if (scope) {\n switch (scope.kind) {\n case \"CLASS\": {\n const c = await prisma.class.findFirst({\n where: {\n id: scope.classId,\n OR: [\n { teachers: { some: { id: viewerId } } },\n { students: { some: { id: viewerId } } },\n ],\n },\n select: { id: true },\n });\n viewerScopeMember = Boolean(c);\n if (!viewerScopeMember && scope.classId) {\n const klass = await prisma.class.findUnique({\n where: { id: scope.classId },\n select: { schoolId: true },\n });\n if (klass?.schoolId) {\n const m = await prisma.schoolMembership.findFirst({\n where: {\n schoolId: klass.schoolId,\n userId: viewerId,\n role: \"ADMIN\",\n status: \"ACTIVE\",\n },\n select: { id: true },\n });\n viewerScopeMember = Boolean(m);\n }\n }\n break;\n }\n case \"SCHOOL_STORAGE\": {\n const m = await prisma.schoolMembership.findFirst({\n where: {\n schoolId: scope.schoolId,\n userId: viewerId,\n status: \"ACTIVE\",\n },\n select: { id: true },\n });\n viewerScopeMember = Boolean(m);\n break;\n }\n case \"PERSONAL\":\n viewerScopeMember = scope.userId === viewerId;\n break;\n }\n }\n }\n\n while (currentId && visits < 64) {\n const folder = await prisma.folder.findUnique({\n where: { id: currentId },\n select: { id: true, name: true, parentFolderId: true },\n });\n if (!folder) break;\n breadcrumbs.unshift({ id: folder.id, name: folder.name });\n\n // Viewer-aware truncation: when the viewer is *not* a scope member but\n // is a grantee, stop at the highest granted ancestor (we walk up one\n // more step into the granted folder, then stop there).\n if (\n viewerId &&\n !viewerScopeMember &&\n viewerGrantedFolderIds &&\n viewerGrantedFolderIds.has(folder.id)\n ) {\n break;\n }\n\n currentId = folder.parentFolderId;\n visits += 1;\n }\n return breadcrumbs;\n}\n"],"names":[],"mappings":"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","debug_id":"26810bf7-c72f-5655-8994-96075d84b6fe"}
@@ -214,9 +214,6 @@ export declare function findCommentWithContext(commentId: string): import(".pris
214
214
  assignment: {
215
215
  class: {
216
216
  school: {
217
- users: {
218
- id: string;
219
- }[];
220
217
  memberships: {
221
218
  userId: string;
222
219
  }[];
@@ -234,9 +231,6 @@ export declare function findCommentWithContext(commentId: string): import(".pris
234
231
  assignment: {
235
232
  class: {
236
233
  school: {
237
- users: {
238
- id: string;
239
- }[];
240
234
  memberships: {
241
235
  userId: string;
242
236
  }[];
@@ -255,9 +249,6 @@ export declare function findCommentWithContext(commentId: string): import(".pris
255
249
  announcement: {
256
250
  class: {
257
251
  school: {
258
- users: {
259
- id: string;
260
- }[];
261
252
  memberships: {
262
253
  userId: string;
263
254
  }[];
@@ -278,9 +269,6 @@ export declare function findCommentWithContext(commentId: string): import(".pris
278
269
  assignment: {
279
270
  class: {
280
271
  school: {
281
- users: {
282
- id: string;
283
- }[];
284
272
  memberships: {
285
273
  userId: string;
286
274
  }[];
@@ -302,9 +290,6 @@ export declare function findCommentWithContext(commentId: string): import(".pris
302
290
  assignment: {
303
291
  class: {
304
292
  school: {
305
- users: {
306
- id: string;
307
- }[];
308
293
  memberships: {
309
294
  userId: string;
310
295
  }[];
@@ -322,9 +307,6 @@ export declare function findCommentWithContext(commentId: string): import(".pris
322
307
  assignment: {
323
308
  class: {
324
309
  school: {
325
- users: {
326
- id: string;
327
- }[];
328
310
  memberships: {
329
311
  userId: string;
330
312
  }[];
@@ -343,9 +325,6 @@ export declare function findCommentWithContext(commentId: string): import(".pris
343
325
  announcement: {
344
326
  class: {
345
327
  school: {
346
- users: {
347
- id: string;
348
- }[];
349
328
  memberships: {
350
329
  userId: string;
351
330
  }[];
@@ -366,9 +345,6 @@ export declare function findCommentWithContext(commentId: string): import(".pris
366
345
  assignment: {
367
346
  class: {
368
347
  school: {
369
- users: {
370
- id: string;
371
- }[];
372
348
  memberships: {
373
349
  userId: string;
374
350
  }[];
@@ -1 +1 @@
1
- {"version":3,"file":"comment.d.ts","sourceRoot":"/","sources":["models/comment.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwChB,CAAC;AAuBX,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKzC;AAED,qCAAqC;AACrC,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;MAmBvD;AAED,gCAAgC;AAChC,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,yBAAyB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;;;;;;;;;;;;;;;;;GAIA;AAED,6CAA6C;AAC7C,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;GAM7E;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;;;;;;;;;;;;;;;;;;;;;;;GA0BA;AAED,yBAAyB;AACzB,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;GAI5C;AAED,kDAAkD;AAClD,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;GAO5D;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGvD;AAED,sDAAsD;AACtD,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM;;;;;;;;GAM7D"}
1
+ {"version":3,"file":"comment.d.ts","sourceRoot":"/","sources":["models/comment.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwChB,CAAC;AAmBX,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKzC;AAED,qCAAqC;AACrC,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;MAmBvD;AAED,gCAAgC;AAChC,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,yBAAyB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;;;;;;;;;;;;;;;;;GAIA;AAED,6CAA6C;AAC7C,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;GAM7E;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;;;;;;;;;;;;;;;;;;;;;;;GA0BA;AAED,yBAAyB;AACzB,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;GAI5C;AAED,kDAAkD;AAClD,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;GAO5D;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGvD;AAED,sDAAsD;AACtD,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM;;;;;;;;GAM7D"}
@@ -2,7 +2,7 @@
2
2
  * Comment model – comments, replies, and reactions.
3
3
  */
4
4
 
5
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4e10262a-c8cd-5388-9b97-3cf8d34902b6")}catch(e){}}();
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="8dbc1c18-53a6-535e-8af0-25c781796a36")}catch(e){}}();
6
6
  import { prisma } from "../lib/prisma.js";
7
7
  export const commentSelect = {
8
8
  id: true,
@@ -51,10 +51,6 @@ const commentClassAuthSelect = {
51
51
  students: { select: { id: true } },
52
52
  school: {
53
53
  select: {
54
- users: {
55
- where: { role: "ADMIN" },
56
- select: { id: true },
57
- },
58
54
  memberships: {
59
55
  where: {
60
56
  role: "ADMIN",
@@ -265,4 +261,4 @@ export function getReactionCountsByCommentId(commentId) {
265
261
  });
266
262
  }
267
263
  //# sourceMappingURL=comment.js.map
268
- //# debugId=4e10262a-c8cd-5388-9b97-3cf8d34902b6
264
+ //# debugId=8dbc1c18-53a6-535e-8af0-25c781796a36
@@ -1 +1 @@
1
- {"version":3,"file":"comment.js","sources":["models/comment.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Comment model – comments, replies, and reactions.\n */\nimport { prisma } from \"../lib/prisma.js\";\nimport type { ReactionType } from \"@prisma/client\";\n\nexport const commentSelect = {\n id: true,\n content: true,\n announcementId: true,\n assignmentId: true,\n submissionId: true,\n parentCommentId: true,\n createdAt: true,\n modifiedAt: true,\n author: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n reactions: {\n select: {\n type: true,\n user: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n },\n },\n} as const;\n\nconst commentClassAuthSelect = {\n id: true,\n teachers: { select: { id: true } },\n students: { select: { id: true } },\n school: {\n select: {\n users: {\n where: { role: \"ADMIN\" as const },\n select: { id: true },\n },\n memberships: {\n where: {\n role: \"ADMIN\" as const,\n status: \"ACTIVE\" as const,\n },\n select: { userId: true },\n },\n },\n },\n} as const;\n\n/** @returns Comment by ID with author and reactions. */\nexport function findCommentById(id: string) {\n return prisma.comment.findUnique({\n where: { id },\n select: commentSelect,\n });\n}\n\n/** @returns Replies to a comment. */\nexport function findRepliesByCommentId(commentId: string) {\n return prisma.comment.findMany({\n where: { parentCommentId: commentId },\n include: {\n author: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n },\n });\n}\n\n/** Create a comment (reply). */\nexport function createComment(data: {\n parentCommentId: string;\n content: string;\n authorId: string;\n announcementId?: string | null;\n assignmentId?: string | null;\n submissionId?: string | null;\n studentQuestionProgressId?: string | null;\n}) {\n return prisma.comment.create({\n data,\n });\n}\n\n/** @returns User's reaction on a comment. */\nexport function findReactionByUserAndComment(userId: string, commentId: string) {\n return prisma.reaction.findUnique({\n where: {\n userId_commentId: { userId, commentId },\n },\n });\n}\n\n/** Add or update reaction on a comment. */\nexport function upsertReaction(data: {\n userId: string;\n commentId: string;\n type: string;\n}) {\n return prisma.reaction.upsert({\n where: {\n userId_commentId: {\n userId: data.userId,\n commentId: data.commentId,\n },\n },\n update: { type: data.type as ReactionType },\n create: { ...data, type: data.type as ReactionType },\n include: {\n user: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n },\n });\n}\n\n/** Delete a reaction. */\nexport function deleteReactionById(id: string) {\n return prisma.reaction.delete({\n where: { id },\n });\n}\n\n/** @returns Comment with announcement classId. */\nexport function findCommentWithAnnouncement(commentId: string) {\n return prisma.comment.findUnique({\n where: { id: commentId },\n include: {\n announcement: { select: { classId: true } },\n },\n });\n}\n\n/** @returns Comment with the owning class context for authorization. */\nexport function findCommentWithContext(commentId: string) {\n return prisma.comment.findUnique({\n where: { id: commentId },\n include: {\n announcement: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n submission: {\n select: {\n studentId: true,\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n },\n },\n studentQuestionProgress: {\n select: {\n studentId: true,\n studentWorksheetResponse: {\n select: {\n submission: {\n select: {\n studentId: true,\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n },\n },\n },\n },\n },\n },\n parentComment: {\n include: {\n announcement: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n submission: {\n select: {\n studentId: true,\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n },\n },\n studentQuestionProgress: {\n select: {\n studentId: true,\n studentWorksheetResponse: {\n select: {\n submission: {\n select: {\n studentId: true,\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n });\n}\n\n/** @returns Reaction counts by type for a comment. */\nexport function getReactionCountsByCommentId(commentId: string) {\n return prisma.reaction.groupBy({\n by: [\"type\"],\n where: { commentId },\n _count: { type: true },\n });\n}\n"],"names":[],"mappings":"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","debug_id":"4e10262a-c8cd-5388-9b97-3cf8d34902b6"}
1
+ {"version":3,"file":"comment.js","sources":["models/comment.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Comment model – comments, replies, and reactions.\n */\nimport { prisma } from \"../lib/prisma.js\";\nimport type { ReactionType } from \"@prisma/client\";\n\nexport const commentSelect = {\n id: true,\n content: true,\n announcementId: true,\n assignmentId: true,\n submissionId: true,\n parentCommentId: true,\n createdAt: true,\n modifiedAt: true,\n author: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n reactions: {\n select: {\n type: true,\n user: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n },\n },\n} as const;\n\nconst commentClassAuthSelect = {\n id: true,\n teachers: { select: { id: true } },\n students: { select: { id: true } },\n school: {\n select: {\n memberships: {\n where: {\n role: \"ADMIN\" as const,\n status: \"ACTIVE\" as const,\n },\n select: { userId: true },\n },\n },\n },\n} as const;\n\n/** @returns Comment by ID with author and reactions. */\nexport function findCommentById(id: string) {\n return prisma.comment.findUnique({\n where: { id },\n select: commentSelect,\n });\n}\n\n/** @returns Replies to a comment. */\nexport function findRepliesByCommentId(commentId: string) {\n return prisma.comment.findMany({\n where: { parentCommentId: commentId },\n include: {\n author: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n },\n });\n}\n\n/** Create a comment (reply). */\nexport function createComment(data: {\n parentCommentId: string;\n content: string;\n authorId: string;\n announcementId?: string | null;\n assignmentId?: string | null;\n submissionId?: string | null;\n studentQuestionProgressId?: string | null;\n}) {\n return prisma.comment.create({\n data,\n });\n}\n\n/** @returns User's reaction on a comment. */\nexport function findReactionByUserAndComment(userId: string, commentId: string) {\n return prisma.reaction.findUnique({\n where: {\n userId_commentId: { userId, commentId },\n },\n });\n}\n\n/** Add or update reaction on a comment. */\nexport function upsertReaction(data: {\n userId: string;\n commentId: string;\n type: string;\n}) {\n return prisma.reaction.upsert({\n where: {\n userId_commentId: {\n userId: data.userId,\n commentId: data.commentId,\n },\n },\n update: { type: data.type as ReactionType },\n create: { ...data, type: data.type as ReactionType },\n include: {\n user: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n },\n },\n });\n}\n\n/** Delete a reaction. */\nexport function deleteReactionById(id: string) {\n return prisma.reaction.delete({\n where: { id },\n });\n}\n\n/** @returns Comment with announcement classId. */\nexport function findCommentWithAnnouncement(commentId: string) {\n return prisma.comment.findUnique({\n where: { id: commentId },\n include: {\n announcement: { select: { classId: true } },\n },\n });\n}\n\n/** @returns Comment with the owning class context for authorization. */\nexport function findCommentWithContext(commentId: string) {\n return prisma.comment.findUnique({\n where: { id: commentId },\n include: {\n announcement: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n submission: {\n select: {\n studentId: true,\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n },\n },\n studentQuestionProgress: {\n select: {\n studentId: true,\n studentWorksheetResponse: {\n select: {\n submission: {\n select: {\n studentId: true,\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n },\n },\n },\n },\n },\n },\n parentComment: {\n include: {\n announcement: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n submission: {\n select: {\n studentId: true,\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n },\n },\n studentQuestionProgress: {\n select: {\n studentId: true,\n studentWorksheetResponse: {\n select: {\n submission: {\n select: {\n studentId: true,\n assignment: {\n select: {\n class: {\n select: commentClassAuthSelect,\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n });\n}\n\n/** @returns Reaction counts by type for a comment. */\nexport function getReactionCountsByCommentId(commentId: string) {\n return prisma.reaction.groupBy({\n by: [\"type\"],\n where: { commentId },\n _count: { type: true },\n });\n}\n"],"names":[],"mappings":"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","debug_id":"8dbc1c18-53a6-535e-8af0-25c781796a36"}
@@ -4,42 +4,42 @@ export declare function findConversationsByUserId(userId: string): import(".pris
4
4
  id: string;
5
5
  title: string;
6
6
  } | null;
7
- messages: ({
8
- sender: {
7
+ members: ({
8
+ user: {
9
9
  id: string;
10
10
  username: string;
11
11
  profile: {
12
12
  displayName: string | null;
13
+ profilePicture: string | null;
13
14
  } | null;
14
15
  };
15
16
  } & {
16
- status: import(".prisma/client").$Enums.JobStatus | null;
17
- meta: import("@prisma/client/runtime/library.js").JsonValue | null;
18
17
  id: string;
19
- createdAt: Date;
18
+ userId: string;
19
+ role: import(".prisma/client").$Enums.ConversationRole;
20
20
  conversationId: string;
21
- content: string;
22
- senderId: string;
23
- runJobId: string | null;
21
+ joinedAt: Date;
22
+ hiddenAt: Date | null;
23
+ lastViewedAt: Date | null;
24
+ lastViewedMentionAt: Date | null;
24
25
  })[];
25
- members: ({
26
- user: {
26
+ messages: ({
27
+ sender: {
27
28
  id: string;
28
29
  username: string;
29
30
  profile: {
30
31
  displayName: string | null;
31
- profilePicture: string | null;
32
32
  } | null;
33
33
  };
34
34
  } & {
35
+ status: import(".prisma/client").$Enums.JobStatus | null;
36
+ meta: import("@prisma/client/runtime/library.js").JsonValue | null;
35
37
  id: string;
36
- role: import(".prisma/client").$Enums.ConversationRole;
37
- userId: string;
38
+ createdAt: Date;
38
39
  conversationId: string;
39
- joinedAt: Date;
40
- hiddenAt: Date | null;
41
- lastViewedAt: Date | null;
42
- lastViewedMentionAt: Date | null;
40
+ content: string;
41
+ senderId: string;
42
+ runJobId: string | null;
43
43
  })[];
44
44
  } & {
45
45
  type: import(".prisma/client").$Enums.ConversationType;
@@ -118,8 +118,8 @@ export declare function createConversation(data: {
118
118
  };
119
119
  } & {
120
120
  id: string;
121
- role: import(".prisma/client").$Enums.ConversationRole;
122
121
  userId: string;
122
+ role: import(".prisma/client").$Enums.ConversationRole;
123
123
  conversationId: string;
124
124
  joinedAt: Date;
125
125
  hiddenAt: Date | null;
@@ -153,8 +153,8 @@ export declare function findConversationByIdAndMember(conversationId: string, us
153
153
  };
154
154
  } & {
155
155
  id: string;
156
- role: import(".prisma/client").$Enums.ConversationRole;
157
156
  userId: string;
157
+ role: import(".prisma/client").$Enums.ConversationRole;
158
158
  conversationId: string;
159
159
  joinedAt: Date;
160
160
  hiddenAt: Date | null;
@@ -182,8 +182,8 @@ export declare function createConversationMember(data: {
182
182
  role: string;
183
183
  }): import(".prisma/client").Prisma.Prisma__ConversationMemberClient<{
184
184
  id: string;
185
- role: import(".prisma/client").$Enums.ConversationRole;
186
185
  userId: string;
186
+ role: import(".prisma/client").$Enums.ConversationRole;
187
187
  conversationId: string;
188
188
  joinedAt: Date;
189
189
  hiddenAt: Date | null;
@@ -199,8 +199,8 @@ export declare function createConversationMember(data: {
199
199
  /** Hide a conversation for a single member until new activity occurs. */
200
200
  export declare function hideConversationMember(userId: string, conversationId: string): import(".prisma/client").Prisma.Prisma__ConversationMemberClient<{
201
201
  id: string;
202
- role: import(".prisma/client").$Enums.ConversationRole;
203
202
  userId: string;
203
+ role: import(".prisma/client").$Enums.ConversationRole;
204
204
  conversationId: string;
205
205
  joinedAt: Date;
206
206
  hiddenAt: Date | null;
@@ -216,8 +216,8 @@ export declare function hideConversationMember(userId: string, conversationId: s
216
216
  /** Remove a member from a conversation. */
217
217
  export declare function deleteConversationMember(userId: string, conversationId: string): import(".prisma/client").Prisma.Prisma__ConversationMemberClient<{
218
218
  id: string;
219
- role: import(".prisma/client").$Enums.ConversationRole;
220
219
  userId: string;
220
+ role: import(".prisma/client").$Enums.ConversationRole;
221
221
  conversationId: string;
222
222
  joinedAt: Date;
223
223
  hiddenAt: Date | null;