@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.
Files changed (212) hide show
  1. package/dist/models/access.d.ts +21 -66
  2. package/dist/models/access.d.ts.map +1 -1
  3. package/dist/models/access.js +10 -26
  4. package/dist/models/access.js.map +1 -1
  5. package/dist/models/agenda.d.ts +2 -0
  6. package/dist/models/agenda.d.ts.map +1 -1
  7. package/dist/models/announcement.d.ts +2 -1
  8. package/dist/models/announcement.d.ts.map +1 -1
  9. package/dist/models/announcement.js +2 -3
  10. package/dist/models/announcement.js.map +1 -1
  11. package/dist/models/assignment.d.ts +7 -0
  12. package/dist/models/assignment.d.ts.map +1 -1
  13. package/dist/models/auth.d.ts +2 -4
  14. package/dist/models/auth.d.ts.map +1 -1
  15. package/dist/models/auth.js +2 -3
  16. package/dist/models/auth.js.map +1 -1
  17. package/dist/models/class.d.ts +32 -4
  18. package/dist/models/class.d.ts.map +1 -1
  19. package/dist/models/class.js +15 -2
  20. package/dist/models/class.js.map +1 -1
  21. package/dist/models/cloudStorage.d.ts +140 -0
  22. package/dist/models/cloudStorage.d.ts.map +1 -0
  23. package/dist/models/cloudStorage.js +175 -0
  24. package/dist/models/cloudStorage.js.map +1 -0
  25. package/dist/models/comment.d.ts +0 -24
  26. package/dist/models/comment.d.ts.map +1 -1
  27. package/dist/models/comment.js +2 -6
  28. package/dist/models/comment.js.map +1 -1
  29. package/dist/models/conversation.d.ts +38 -22
  30. package/dist/models/conversation.d.ts.map +1 -1
  31. package/dist/models/conversation.js +50 -2
  32. package/dist/models/conversation.js.map +1 -1
  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 +7 -1
  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 +25 -10
  42. package/dist/models/file.d.ts.map +1 -1
  43. package/dist/models/file.js +2 -5
  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 +5 -3
  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 +1 -0
  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 +3556 -2298
  69. package/dist/routers/_app.d.ts.map +1 -1
  70. package/dist/routers/_app.js +6 -2
  71. package/dist/routers/_app.js.map +1 -1
  72. package/dist/routers/agenda.d.ts +2 -0
  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 +18 -9
  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 +87 -14
  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 +20 -5
  86. package/dist/routers/conversation.d.ts.map +1 -1
  87. package/dist/routers/conversation.js +6 -3
  88. package/dist/routers/conversation.js.map +1 -1
  89. package/dist/routers/department.d.ts +293 -0
  90. package/dist/routers/department.d.ts.map +1 -0
  91. package/dist/routers/department.js +93 -0
  92. package/dist/routers/department.js.map +1 -0
  93. package/dist/routers/event.d.ts +2 -1
  94. package/dist/routers/event.d.ts.map +1 -1
  95. package/dist/routers/file.d.ts +8 -6
  96. package/dist/routers/file.d.ts.map +1 -1
  97. package/dist/routers/file.js +26 -17
  98. package/dist/routers/file.js.map +1 -1
  99. package/dist/routers/folder.d.ts +139 -186
  100. package/dist/routers/folder.d.ts.map +1 -1
  101. package/dist/routers/folder.js +82 -90
  102. package/dist/routers/folder.js.map +1 -1
  103. package/dist/routers/labChat.d.ts +11 -2
  104. package/dist/routers/labChat.d.ts.map +1 -1
  105. package/dist/routers/message.d.ts +20 -2
  106. package/dist/routers/message.d.ts.map +1 -1
  107. package/dist/routers/newtonChat.d.ts +9 -0
  108. package/dist/routers/newtonChat.d.ts.map +1 -1
  109. package/dist/routers/school.d.ts +160 -17
  110. package/dist/routers/school.d.ts.map +1 -1
  111. package/dist/routers/school.js +46 -3
  112. package/dist/routers/school.js.map +1 -1
  113. package/dist/routers/tag.d.ts +119 -0
  114. package/dist/routers/tag.d.ts.map +1 -0
  115. package/dist/routers/tag.js +52 -0
  116. package/dist/routers/tag.js.map +1 -0
  117. package/dist/routers/worksheet.d.ts +1 -0
  118. package/dist/routers/worksheet.d.ts.map +1 -1
  119. package/dist/seedDatabase.d.ts +0 -1
  120. package/dist/seedDatabase.d.ts.map +1 -1
  121. package/dist/services/agenda.d.ts +2 -0
  122. package/dist/services/agenda.d.ts.map +1 -1
  123. package/dist/services/assignment.d.ts +18 -9
  124. package/dist/services/assignment.d.ts.map +1 -1
  125. package/dist/services/assignment.js +4 -4
  126. package/dist/services/assignment.js.map +1 -1
  127. package/dist/services/class.d.ts +87 -14
  128. package/dist/services/class.d.ts.map +1 -1
  129. package/dist/services/class.js +91 -4
  130. package/dist/services/class.js.map +1 -1
  131. package/dist/services/conversation.d.ts +21 -5
  132. package/dist/services/conversation.d.ts.map +1 -1
  133. package/dist/services/conversation.js +22 -3
  134. package/dist/services/conversation.js.map +1 -1
  135. package/dist/services/department.d.ts +200 -0
  136. package/dist/services/department.d.ts.map +1 -0
  137. package/dist/services/department.js +368 -0
  138. package/dist/services/department.js.map +1 -0
  139. package/dist/services/event.d.ts +2 -1
  140. package/dist/services/event.d.ts.map +1 -1
  141. package/dist/services/file.d.ts +85 -0
  142. package/dist/services/file.d.ts.map +1 -1
  143. package/dist/services/file.js +92 -2
  144. package/dist/services/file.js.map +1 -1
  145. package/dist/services/folder.d.ts +125 -149
  146. package/dist/services/folder.d.ts.map +1 -1
  147. package/dist/services/folder.js +258 -199
  148. package/dist/services/folder.js.map +1 -1
  149. package/dist/services/labChat.d.ts +11 -2
  150. package/dist/services/labChat.d.ts.map +1 -1
  151. package/dist/services/labChat.js +8 -7
  152. package/dist/services/labChat.js.map +1 -1
  153. package/dist/services/message.d.ts +20 -2
  154. package/dist/services/message.d.ts.map +1 -1
  155. package/dist/services/message.js +7 -23
  156. package/dist/services/message.js.map +1 -1
  157. package/dist/services/newtonChat.d.ts +9 -0
  158. package/dist/services/newtonChat.d.ts.map +1 -1
  159. package/dist/services/newtonChat.js +7 -6
  160. package/dist/services/newtonChat.js.map +1 -1
  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/schema.prisma +119 -35
  177. package/src/models/access.ts +8 -24
  178. package/src/models/announcement.ts +0 -1
  179. package/src/models/auth.ts +0 -1
  180. package/src/models/class.ts +13 -0
  181. package/src/models/cloudStorage.ts +194 -0
  182. package/src/models/comment.ts +0 -4
  183. package/src/models/conversation.ts +54 -0
  184. package/src/models/department.ts +159 -0
  185. package/src/models/event.ts +0 -3
  186. package/src/models/file.ts +0 -3
  187. package/src/models/message.ts +19 -5
  188. package/src/models/school.ts +26 -0
  189. package/src/models/tag.ts +140 -0
  190. package/src/routers/_app.ts +4 -0
  191. package/src/routers/announcement.ts +1 -3
  192. package/src/routers/assignment.ts +0 -1
  193. package/src/routers/class.ts +3 -0
  194. package/src/routers/conversation.ts +7 -0
  195. package/src/routers/department.ts +174 -0
  196. package/src/routers/file.ts +29 -26
  197. package/src/routers/folder.ts +100 -121
  198. package/src/routers/school.ts +83 -0
  199. package/src/routers/tag.ts +78 -0
  200. package/src/services/assignment.ts +2 -3
  201. package/src/services/class.ts +108 -3
  202. package/src/services/conversation.ts +30 -0
  203. package/src/services/department.ts +478 -0
  204. package/src/services/file.ts +100 -0
  205. package/src/services/folder.ts +313 -240
  206. package/src/services/labChat.ts +7 -6
  207. package/src/services/message.ts +5 -21
  208. package/src/services/newtonChat.ts +5 -5
  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/integration/routers/conversation.test.ts +121 -2
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Department service — admin CRUD, members, and template management for
3
+ * department-level rubrics and grading boundaries.
4
+ */
5
+
6
+ !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]="a61f9d68-eb30-571c-908d-8c1fb445f85c")}catch(e){}}();
7
+ import { TRPCError } from "@trpc/server";
8
+ import { prisma } from "../lib/prisma.js";
9
+ import { addDepartmentMembers, createDepartment, deleteDepartment, findDepartmentMembership, getDepartment, listDepartmentsForSchool, listDepartmentsForUser, removeDepartmentMember, updateDepartment, } from "../models/department.js";
10
+ import { findActiveSchoolAdminMembership, findSchoolMembership, } from "../models/school.js";
11
+ async function requireAdmin(schoolId, userId) {
12
+ const membership = await findActiveSchoolAdminMembership(schoolId, userId);
13
+ if (!membership) {
14
+ throw new TRPCError({
15
+ code: "FORBIDDEN",
16
+ message: "Only school admins can manage departments",
17
+ });
18
+ }
19
+ }
20
+ async function requireMember(schoolId, userId) {
21
+ const membership = await findSchoolMembership(schoolId, userId);
22
+ if (!membership || membership.status !== "ACTIVE") {
23
+ throw new TRPCError({
24
+ code: "FORBIDDEN",
25
+ message: "You aren't a member of this institution",
26
+ });
27
+ }
28
+ }
29
+ export async function listDepartments(userId, schoolId) {
30
+ await requireMember(schoolId, userId);
31
+ return listDepartmentsForSchool(schoolId);
32
+ }
33
+ export async function listMyDepartments(userId, schoolId) {
34
+ await requireMember(schoolId, userId);
35
+ return listDepartmentsForUser(userId, schoolId);
36
+ }
37
+ export async function getDepartmentDetails(userId, departmentId) {
38
+ const department = await getDepartment(departmentId);
39
+ if (!department) {
40
+ throw new TRPCError({
41
+ code: "NOT_FOUND",
42
+ message: "Department not found",
43
+ });
44
+ }
45
+ await requireMember(department.schoolId, userId);
46
+ return department;
47
+ }
48
+ export async function createDepartmentRecord(userId, input) {
49
+ await requireAdmin(input.schoolId, userId);
50
+ return createDepartment(input);
51
+ }
52
+ export async function updateDepartmentRecord(userId, input) {
53
+ const department = await getDepartment(input.departmentId);
54
+ if (!department) {
55
+ throw new TRPCError({
56
+ code: "NOT_FOUND",
57
+ message: "Department not found",
58
+ });
59
+ }
60
+ await requireAdmin(department.schoolId, userId);
61
+ return updateDepartment(input.departmentId, {
62
+ name: input.name,
63
+ description: input.description,
64
+ color: input.color,
65
+ });
66
+ }
67
+ export async function deleteDepartmentRecord(userId, departmentId) {
68
+ const department = await getDepartment(departmentId);
69
+ if (!department) {
70
+ throw new TRPCError({
71
+ code: "NOT_FOUND",
72
+ message: "Department not found",
73
+ });
74
+ }
75
+ await requireAdmin(department.schoolId, userId);
76
+ await deleteDepartment(departmentId);
77
+ return { success: true };
78
+ }
79
+ export async function addMembersToDepartment(userId, input) {
80
+ const department = await getDepartment(input.departmentId);
81
+ if (!department) {
82
+ throw new TRPCError({
83
+ code: "NOT_FOUND",
84
+ message: "Department not found",
85
+ });
86
+ }
87
+ await requireAdmin(department.schoolId, userId);
88
+ // Members must already be in the institution.
89
+ const memberships = await prisma.schoolMembership.findMany({
90
+ where: {
91
+ schoolId: department.schoolId,
92
+ userId: { in: input.userIds },
93
+ status: "ACTIVE",
94
+ },
95
+ select: { userId: true },
96
+ });
97
+ const validIds = new Set(memberships.map((m) => m.userId));
98
+ const validUserIds = input.userIds.filter((id) => validIds.has(id));
99
+ if (validUserIds.length === 0) {
100
+ throw new TRPCError({
101
+ code: "BAD_REQUEST",
102
+ message: "None of the supplied users are institution members",
103
+ });
104
+ }
105
+ return addDepartmentMembers(input.departmentId, validUserIds);
106
+ }
107
+ export async function removeMemberFromDepartment(userId, input) {
108
+ const department = await getDepartment(input.departmentId);
109
+ if (!department) {
110
+ throw new TRPCError({
111
+ code: "NOT_FOUND",
112
+ message: "Department not found",
113
+ });
114
+ }
115
+ await requireAdmin(department.schoolId, userId);
116
+ return removeDepartmentMember(input.departmentId, input.memberId);
117
+ }
118
+ /** Department template authoring (rubrics + boundaries). */
119
+ export async function createRubricTemplate(userId, input) {
120
+ const department = await getDepartment(input.departmentId);
121
+ if (!department) {
122
+ throw new TRPCError({
123
+ code: "NOT_FOUND",
124
+ message: "Department not found",
125
+ });
126
+ }
127
+ await requireAdmin(department.schoolId, userId);
128
+ return prisma.markScheme.create({
129
+ data: {
130
+ name: input.name,
131
+ structured: input.structured.replace(/\\n/g, "\n"),
132
+ department: { connect: { id: input.departmentId } },
133
+ },
134
+ select: { id: true, name: true, structured: true, departmentId: true },
135
+ });
136
+ }
137
+ export async function updateRubricTemplate(userId, input) {
138
+ const template = await prisma.markScheme.findUnique({
139
+ where: { id: input.templateId },
140
+ select: { departmentId: true, department: { select: { schoolId: true } } },
141
+ });
142
+ if (!template?.departmentId || !template.department) {
143
+ throw new TRPCError({
144
+ code: "NOT_FOUND",
145
+ message: "Template not found",
146
+ });
147
+ }
148
+ await requireAdmin(template.department.schoolId, userId);
149
+ return prisma.markScheme.update({
150
+ where: { id: input.templateId },
151
+ data: {
152
+ ...(input.name !== undefined ? { name: input.name } : {}),
153
+ ...(input.structured !== undefined
154
+ ? { structured: input.structured.replace(/\\n/g, "\n") }
155
+ : {}),
156
+ },
157
+ });
158
+ }
159
+ export async function deleteRubricTemplate(userId, templateId) {
160
+ const template = await prisma.markScheme.findUnique({
161
+ where: { id: templateId },
162
+ select: { departmentId: true, department: { select: { schoolId: true } } },
163
+ });
164
+ if (!template?.departmentId || !template.department) {
165
+ throw new TRPCError({
166
+ code: "NOT_FOUND",
167
+ message: "Template not found",
168
+ });
169
+ }
170
+ await requireAdmin(template.department.schoolId, userId);
171
+ return prisma.markScheme.delete({ where: { id: templateId } });
172
+ }
173
+ export async function createBoundaryTemplate(userId, input) {
174
+ const department = await getDepartment(input.departmentId);
175
+ if (!department) {
176
+ throw new TRPCError({
177
+ code: "NOT_FOUND",
178
+ message: "Department not found",
179
+ });
180
+ }
181
+ await requireAdmin(department.schoolId, userId);
182
+ return prisma.gradingBoundary.create({
183
+ data: {
184
+ name: input.name,
185
+ structured: input.structured.replace(/\\n/g, "\n"),
186
+ department: { connect: { id: input.departmentId } },
187
+ },
188
+ select: { id: true, name: true, structured: true, departmentId: true },
189
+ });
190
+ }
191
+ export async function updateBoundaryTemplate(userId, input) {
192
+ const template = await prisma.gradingBoundary.findUnique({
193
+ where: { id: input.templateId },
194
+ select: { departmentId: true, department: { select: { schoolId: true } } },
195
+ });
196
+ if (!template?.departmentId || !template.department) {
197
+ throw new TRPCError({
198
+ code: "NOT_FOUND",
199
+ message: "Template not found",
200
+ });
201
+ }
202
+ await requireAdmin(template.department.schoolId, userId);
203
+ return prisma.gradingBoundary.update({
204
+ where: { id: input.templateId },
205
+ data: {
206
+ ...(input.name !== undefined ? { name: input.name } : {}),
207
+ ...(input.structured !== undefined
208
+ ? { structured: input.structured.replace(/\\n/g, "\n") }
209
+ : {}),
210
+ },
211
+ });
212
+ }
213
+ export async function deleteBoundaryTemplate(userId, templateId) {
214
+ const template = await prisma.gradingBoundary.findUnique({
215
+ where: { id: templateId },
216
+ select: { departmentId: true, department: { select: { schoolId: true } } },
217
+ });
218
+ if (!template?.departmentId || !template.department) {
219
+ throw new TRPCError({
220
+ code: "NOT_FOUND",
221
+ message: "Template not found",
222
+ });
223
+ }
224
+ await requireAdmin(template.department.schoolId, userId);
225
+ return prisma.gradingBoundary.delete({ where: { id: templateId } });
226
+ }
227
+ /**
228
+ * Apply a department template to a class by *copying* the structured payload
229
+ * into a class-scoped record. Teacher-only (the class teacher).
230
+ */
231
+ export async function applyRubricTemplate(userId, input) {
232
+ const cls = await prisma.class.findFirst({
233
+ where: {
234
+ id: input.classId,
235
+ OR: [
236
+ { teachers: { some: { id: userId } } },
237
+ {
238
+ school: {
239
+ memberships: {
240
+ some: { userId, role: "ADMIN", status: "ACTIVE" },
241
+ },
242
+ },
243
+ },
244
+ ],
245
+ },
246
+ select: { id: true, departmentId: true },
247
+ });
248
+ if (!cls) {
249
+ throw new TRPCError({
250
+ code: "FORBIDDEN",
251
+ message: "You don't have permission to apply templates to this class",
252
+ });
253
+ }
254
+ const template = await prisma.markScheme.findUnique({
255
+ where: { id: input.templateId },
256
+ select: { id: true, structured: true, departmentId: true, name: true },
257
+ });
258
+ if (!template?.departmentId) {
259
+ throw new TRPCError({
260
+ code: "NOT_FOUND",
261
+ message: "Template not found",
262
+ });
263
+ }
264
+ if (cls.departmentId && cls.departmentId !== template.departmentId) {
265
+ throw new TRPCError({
266
+ code: "FORBIDDEN",
267
+ message: "This template belongs to a different department than the class",
268
+ });
269
+ }
270
+ return prisma.markScheme.create({
271
+ data: {
272
+ class: { connect: { id: input.classId } },
273
+ structured: template.structured,
274
+ name: template.name,
275
+ },
276
+ });
277
+ }
278
+ export async function applyBoundaryTemplate(userId, input) {
279
+ const cls = await prisma.class.findFirst({
280
+ where: {
281
+ id: input.classId,
282
+ OR: [
283
+ { teachers: { some: { id: userId } } },
284
+ {
285
+ school: {
286
+ memberships: {
287
+ some: { userId, role: "ADMIN", status: "ACTIVE" },
288
+ },
289
+ },
290
+ },
291
+ ],
292
+ },
293
+ select: { id: true, departmentId: true },
294
+ });
295
+ if (!cls) {
296
+ throw new TRPCError({
297
+ code: "FORBIDDEN",
298
+ message: "You don't have permission to apply templates to this class",
299
+ });
300
+ }
301
+ const template = await prisma.gradingBoundary.findUnique({
302
+ where: { id: input.templateId },
303
+ select: { id: true, structured: true, departmentId: true, name: true },
304
+ });
305
+ if (!template?.departmentId) {
306
+ throw new TRPCError({
307
+ code: "NOT_FOUND",
308
+ message: "Template not found",
309
+ });
310
+ }
311
+ if (cls.departmentId && cls.departmentId !== template.departmentId) {
312
+ throw new TRPCError({
313
+ code: "FORBIDDEN",
314
+ message: "This template belongs to a different department than the class",
315
+ });
316
+ }
317
+ return prisma.gradingBoundary.create({
318
+ data: {
319
+ class: { connect: { id: input.classId } },
320
+ structured: template.structured,
321
+ name: template.name,
322
+ },
323
+ });
324
+ }
325
+ /** Helper used by the auto-tag flow on class creation. */
326
+ export async function setClassDepartment(userId, input) {
327
+ const cls = await prisma.class.findFirst({
328
+ where: {
329
+ id: input.classId,
330
+ OR: [
331
+ { teachers: { some: { id: userId } } },
332
+ {
333
+ school: {
334
+ memberships: {
335
+ some: { userId, role: "ADMIN", status: "ACTIVE" },
336
+ },
337
+ },
338
+ },
339
+ ],
340
+ },
341
+ select: { id: true, schoolId: true },
342
+ });
343
+ if (!cls?.schoolId) {
344
+ throw new TRPCError({
345
+ code: "BAD_REQUEST",
346
+ message: "Class is not affiliated with an institution",
347
+ });
348
+ }
349
+ if (input.departmentId) {
350
+ const dept = await getDepartment(input.departmentId);
351
+ if (!dept || dept.schoolId !== cls.schoolId) {
352
+ throw new TRPCError({
353
+ code: "BAD_REQUEST",
354
+ message: "Department does not belong to this institution",
355
+ });
356
+ }
357
+ }
358
+ return prisma.class.update({
359
+ where: { id: input.classId },
360
+ data: { departmentId: input.departmentId },
361
+ select: { id: true, departmentId: true },
362
+ });
363
+ }
364
+ export async function checkDepartmentMember(userId, departmentId) {
365
+ return Boolean(await findDepartmentMembership(departmentId, userId));
366
+ }
367
+ //# sourceMappingURL=department.js.map
368
+ //# debugId=a61f9d68-eb30-571c-908d-8c1fb445f85c
@@ -0,0 +1 @@
1
+ {"version":3,"file":"department.js","sources":["services/department.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Department service — admin CRUD, members, and template management for\n * department-level rubrics and grading boundaries.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n addDepartmentMembers,\n createDepartment,\n deleteDepartment,\n findDepartmentMembership,\n getDepartment,\n listDepartmentsForSchool,\n listDepartmentsForUser,\n removeDepartmentMember,\n updateDepartment,\n} from \"../models/department.js\";\nimport {\n findActiveSchoolAdminMembership,\n findSchoolMembership,\n} from \"../models/school.js\";\n\nasync function requireAdmin(schoolId: string, userId: string) {\n const membership = await findActiveSchoolAdminMembership(schoolId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can manage departments\",\n });\n }\n}\n\nasync function requireMember(schoolId: string, userId: string) {\n const membership = await findSchoolMembership(schoolId, userId);\n if (!membership || membership.status !== \"ACTIVE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You aren't a member of this institution\",\n });\n }\n}\n\nexport async function listDepartments(\n userId: string,\n schoolId: string\n) {\n await requireMember(schoolId, userId);\n return listDepartmentsForSchool(schoolId);\n}\n\nexport async function listMyDepartments(\n userId: string,\n schoolId: string\n) {\n await requireMember(schoolId, userId);\n return listDepartmentsForUser(userId, schoolId);\n}\n\nexport async function getDepartmentDetails(\n userId: string,\n departmentId: string\n) {\n const department = await getDepartment(departmentId);\n if (!department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Department not found\",\n });\n }\n await requireMember(department.schoolId, userId);\n return department;\n}\n\nexport async function createDepartmentRecord(\n userId: string,\n input: {\n schoolId: string;\n name: string;\n description?: string;\n color?: string;\n }\n) {\n await requireAdmin(input.schoolId, userId);\n return createDepartment(input);\n}\n\nexport async function updateDepartmentRecord(\n userId: string,\n input: {\n departmentId: string;\n name?: string;\n description?: string | null;\n color?: string;\n }\n) {\n const department = await getDepartment(input.departmentId);\n if (!department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Department not found\",\n });\n }\n await requireAdmin(department.schoolId, userId);\n return updateDepartment(input.departmentId, {\n name: input.name,\n description: input.description,\n color: input.color,\n });\n}\n\nexport async function deleteDepartmentRecord(\n userId: string,\n departmentId: string\n) {\n const department = await getDepartment(departmentId);\n if (!department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Department not found\",\n });\n }\n await requireAdmin(department.schoolId, userId);\n await deleteDepartment(departmentId);\n return { success: true };\n}\n\nexport async function addMembersToDepartment(\n userId: string,\n input: { departmentId: string; userIds: string[] }\n) {\n const department = await getDepartment(input.departmentId);\n if (!department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Department not found\",\n });\n }\n await requireAdmin(department.schoolId, userId);\n\n // Members must already be in the institution.\n const memberships = await prisma.schoolMembership.findMany({\n where: {\n schoolId: department.schoolId,\n userId: { in: input.userIds },\n status: \"ACTIVE\",\n },\n select: { userId: true },\n });\n const validIds = new Set(memberships.map((m) => m.userId));\n const validUserIds = input.userIds.filter((id) => validIds.has(id));\n\n if (validUserIds.length === 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"None of the supplied users are institution members\",\n });\n }\n return addDepartmentMembers(input.departmentId, validUserIds);\n}\n\nexport async function removeMemberFromDepartment(\n userId: string,\n input: { departmentId: string; memberId: string }\n) {\n const department = await getDepartment(input.departmentId);\n if (!department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Department not found\",\n });\n }\n await requireAdmin(department.schoolId, userId);\n return removeDepartmentMember(input.departmentId, input.memberId);\n}\n\n/** Department template authoring (rubrics + boundaries). */\nexport async function createRubricTemplate(\n userId: string,\n input: { departmentId: string; name: string; structured: string }\n) {\n const department = await getDepartment(input.departmentId);\n if (!department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Department not found\",\n });\n }\n await requireAdmin(department.schoolId, userId);\n return prisma.markScheme.create({\n data: {\n name: input.name,\n structured: input.structured.replace(/\\\\n/g, \"\\n\"),\n department: { connect: { id: input.departmentId } },\n },\n select: { id: true, name: true, structured: true, departmentId: true },\n });\n}\n\nexport async function updateRubricTemplate(\n userId: string,\n input: {\n templateId: string;\n name?: string;\n structured?: string;\n }\n) {\n const template = await prisma.markScheme.findUnique({\n where: { id: input.templateId },\n select: { departmentId: true, department: { select: { schoolId: true } } },\n });\n if (!template?.departmentId || !template.department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Template not found\",\n });\n }\n await requireAdmin(template.department.schoolId, userId);\n return prisma.markScheme.update({\n where: { id: input.templateId },\n data: {\n ...(input.name !== undefined ? { name: input.name } : {}),\n ...(input.structured !== undefined\n ? { structured: input.structured.replace(/\\\\n/g, \"\\n\") }\n : {}),\n },\n });\n}\n\nexport async function deleteRubricTemplate(\n userId: string,\n templateId: string\n) {\n const template = await prisma.markScheme.findUnique({\n where: { id: templateId },\n select: { departmentId: true, department: { select: { schoolId: true } } },\n });\n if (!template?.departmentId || !template.department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Template not found\",\n });\n }\n await requireAdmin(template.department.schoolId, userId);\n return prisma.markScheme.delete({ where: { id: templateId } });\n}\n\nexport async function createBoundaryTemplate(\n userId: string,\n input: { departmentId: string; name: string; structured: string }\n) {\n const department = await getDepartment(input.departmentId);\n if (!department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Department not found\",\n });\n }\n await requireAdmin(department.schoolId, userId);\n return prisma.gradingBoundary.create({\n data: {\n name: input.name,\n structured: input.structured.replace(/\\\\n/g, \"\\n\"),\n department: { connect: { id: input.departmentId } },\n },\n select: { id: true, name: true, structured: true, departmentId: true },\n });\n}\n\nexport async function updateBoundaryTemplate(\n userId: string,\n input: {\n templateId: string;\n name?: string;\n structured?: string;\n }\n) {\n const template = await prisma.gradingBoundary.findUnique({\n where: { id: input.templateId },\n select: { departmentId: true, department: { select: { schoolId: true } } },\n });\n if (!template?.departmentId || !template.department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Template not found\",\n });\n }\n await requireAdmin(template.department.schoolId, userId);\n return prisma.gradingBoundary.update({\n where: { id: input.templateId },\n data: {\n ...(input.name !== undefined ? { name: input.name } : {}),\n ...(input.structured !== undefined\n ? { structured: input.structured.replace(/\\\\n/g, \"\\n\") }\n : {}),\n },\n });\n}\n\nexport async function deleteBoundaryTemplate(\n userId: string,\n templateId: string\n) {\n const template = await prisma.gradingBoundary.findUnique({\n where: { id: templateId },\n select: { departmentId: true, department: { select: { schoolId: true } } },\n });\n if (!template?.departmentId || !template.department) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Template not found\",\n });\n }\n await requireAdmin(template.department.schoolId, userId);\n return prisma.gradingBoundary.delete({ where: { id: templateId } });\n}\n\n/**\n * Apply a department template to a class by *copying* the structured payload\n * into a class-scoped record. Teacher-only (the class teacher).\n */\nexport async function applyRubricTemplate(\n userId: string,\n input: { classId: string; templateId: string }\n) {\n const cls = await prisma.class.findFirst({\n where: {\n id: input.classId,\n OR: [\n { teachers: { some: { id: userId } } },\n {\n school: {\n memberships: {\n some: { userId, role: \"ADMIN\", status: \"ACTIVE\" },\n },\n },\n },\n ],\n },\n select: { id: true, departmentId: true },\n });\n if (!cls) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You don't have permission to apply templates to this class\",\n });\n }\n\n const template = await prisma.markScheme.findUnique({\n where: { id: input.templateId },\n select: { id: true, structured: true, departmentId: true, name: true },\n });\n if (!template?.departmentId) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Template not found\",\n });\n }\n if (cls.departmentId && cls.departmentId !== template.departmentId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n \"This template belongs to a different department than the class\",\n });\n }\n\n return prisma.markScheme.create({\n data: {\n class: { connect: { id: input.classId } },\n structured: template.structured,\n name: template.name,\n },\n });\n}\n\nexport async function applyBoundaryTemplate(\n userId: string,\n input: { classId: string; templateId: string }\n) {\n const cls = await prisma.class.findFirst({\n where: {\n id: input.classId,\n OR: [\n { teachers: { some: { id: userId } } },\n {\n school: {\n memberships: {\n some: { userId, role: \"ADMIN\", status: \"ACTIVE\" },\n },\n },\n },\n ],\n },\n select: { id: true, departmentId: true },\n });\n if (!cls) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You don't have permission to apply templates to this class\",\n });\n }\n\n const template = await prisma.gradingBoundary.findUnique({\n where: { id: input.templateId },\n select: { id: true, structured: true, departmentId: true, name: true },\n });\n if (!template?.departmentId) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Template not found\",\n });\n }\n if (cls.departmentId && cls.departmentId !== template.departmentId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n \"This template belongs to a different department than the class\",\n });\n }\n\n return prisma.gradingBoundary.create({\n data: {\n class: { connect: { id: input.classId } },\n structured: template.structured,\n name: template.name,\n },\n });\n}\n\n/** Helper used by the auto-tag flow on class creation. */\nexport async function setClassDepartment(\n userId: string,\n input: { classId: string; departmentId: string | null }\n) {\n const cls = await prisma.class.findFirst({\n where: {\n id: input.classId,\n OR: [\n { teachers: { some: { id: userId } } },\n {\n school: {\n memberships: {\n some: { userId, role: \"ADMIN\", status: \"ACTIVE\" },\n },\n },\n },\n ],\n },\n select: { id: true, schoolId: true },\n });\n if (!cls?.schoolId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Class is not affiliated with an institution\",\n });\n }\n\n if (input.departmentId) {\n const dept = await getDepartment(input.departmentId);\n if (!dept || dept.schoolId !== cls.schoolId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Department does not belong to this institution\",\n });\n }\n }\n return prisma.class.update({\n where: { id: input.classId },\n data: { departmentId: input.departmentId },\n select: { id: true, departmentId: true },\n });\n}\n\nexport async function checkDepartmentMember(\n userId: string,\n departmentId: string\n) {\n return Boolean(await findDepartmentMembership(departmentId, userId));\n}\n"],"names":[],"mappings":"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","debug_id":"a61f9d68-eb30-571c-908d-8c1fb445f85c"}
@@ -7,7 +7,6 @@ export declare function getEvent(userId: string, id: string): Promise<{
7
7
  email: string;
8
8
  password: string;
9
9
  verified: boolean;
10
- role: import(".prisma/client").$Enums.UserRole;
11
10
  schoolId: string | null;
12
11
  } | null;
13
12
  class: ({
@@ -21,6 +20,7 @@ export declare function getEvent(userId: string, id: string): Promise<{
21
20
  } & {
22
21
  name: string;
23
22
  id: string;
23
+ description: string | null;
24
24
  logoId: string | null;
25
25
  subdomain: string | null;
26
26
  createdAt: Date;
@@ -37,6 +37,7 @@ export declare function getEvent(userId: string, id: string): Promise<{
37
37
  color: string | null;
38
38
  section: string;
39
39
  syllabus: string | null;
40
+ departmentId: string | null;
40
41
  }) | null;
41
42
  assignmentsAttached: {
42
43
  type: import(".prisma/client").$Enums.AssignmentType;
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"/","sources":["services/event.ts"],"names":[],"mappings":"AAmBA,6DAA6D;AAC7D,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAexD;AAED,wCAAwC;AACxC,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;GAwBF;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;GA2BF;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;GAiBjE;AAuBD,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CrB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkBrB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;GAkBhB"}
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"/","sources":["services/event.ts"],"names":[],"mappings":"AAmBA,6DAA6D;AAC7D,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAexD;AAED,wCAAwC;AACxC,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;GAwBF;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;GA2BF;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;GAiBjE;AAuBD,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CrB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkBrB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;GAkBhB"}
@@ -71,4 +71,89 @@ export declare function renameFile(fileId: string, newName: string): Promise<{
71
71
  export declare function deleteFileRecord(fileId: string, classId: string): Promise<{
72
72
  success: boolean;
73
73
  }>;
74
+ /**
75
+ * Scope-aware move: works for class, school, and personal storage. Source
76
+ * and target must live in the same scope (no cross-scope moves).
77
+ */
78
+ export declare function moveFileScoped(userId: string, input: {
79
+ fileId: string;
80
+ targetFolderId: string;
81
+ }): Promise<{
82
+ user: {
83
+ id: string;
84
+ username: string;
85
+ } | null;
86
+ } & {
87
+ path: string;
88
+ type: string;
89
+ name: string;
90
+ id: string;
91
+ userId: string | null;
92
+ size: number | null;
93
+ uploadedAt: Date | null;
94
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
95
+ uploadUrl: string | null;
96
+ uploadExpiresAt: Date | null;
97
+ uploadSessionId: string | null;
98
+ uploadProgress: number | null;
99
+ uploadError: string | null;
100
+ uploadRetryCount: number;
101
+ isOrphaned: boolean;
102
+ cleanupAt: Date | null;
103
+ thumbnailId: string | null;
104
+ assignmentId: string | null;
105
+ submissionId: string | null;
106
+ annotationId: string | null;
107
+ classDraftId: string | null;
108
+ folderId: string | null;
109
+ conversationId: string | null;
110
+ messageId: string | null;
111
+ announcementId: string | null;
112
+ schoolDevelopementProgramId: string | null;
113
+ }>;
114
+ /** Scope-aware rename. */
115
+ export declare function renameFileScoped(userId: string, input: {
116
+ fileId: string;
117
+ newName: string;
118
+ }): Promise<{
119
+ user: {
120
+ id: string;
121
+ username: string;
122
+ } | null;
123
+ } & {
124
+ path: string;
125
+ type: string;
126
+ name: string;
127
+ id: string;
128
+ userId: string | null;
129
+ size: number | null;
130
+ uploadedAt: Date | null;
131
+ uploadStatus: import(".prisma/client").$Enums.UploadStatus;
132
+ uploadUrl: string | null;
133
+ uploadExpiresAt: Date | null;
134
+ uploadSessionId: string | null;
135
+ uploadProgress: number | null;
136
+ uploadError: string | null;
137
+ uploadRetryCount: number;
138
+ isOrphaned: boolean;
139
+ cleanupAt: Date | null;
140
+ thumbnailId: string | null;
141
+ assignmentId: string | null;
142
+ submissionId: string | null;
143
+ annotationId: string | null;
144
+ classDraftId: string | null;
145
+ folderId: string | null;
146
+ conversationId: string | null;
147
+ messageId: string | null;
148
+ announcementId: string | null;
149
+ schoolDevelopementProgramId: string | null;
150
+ }>;
151
+ /**
152
+ * Generic delete that works across class / school / personal storage by
153
+ * deriving permissions from the parent folder's CloudScope (or owning user
154
+ * for unfiled files).
155
+ */
156
+ export declare function deleteFileGeneric(userId: string, fileId: string): Promise<{
157
+ success: boolean;
158
+ }>;
74
159
  //# sourceMappingURL=file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"/","sources":["services/file.ts"],"names":[],"mappings":"AA2BA,0EAA0E;AAC1E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;GAkFpE;AAED,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmBvB;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkBhB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;GA6BrE"}
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"/","sources":["services/file.ts"],"names":[],"mappings":"AAmCA,0EAA0E;AAC1E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;GAkFpE;AAED,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmBvB;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkBhB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;GA6BrE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsBlD;AAED,0BAA0B;AAC1B,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsB3C;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;GA4BrE"}
@@ -1,14 +1,21 @@
1
1
  /**
2
2
  * File service – signed URLs, move, rename, delete. Handles permission checks
3
3
  * for assignment, submission, announcement, folder, and user-owned files.
4
+ *
5
+ * The class files page calls the legacy class-scoped helpers; the unified
6
+ * cloud-storage page calls the `*Scoped` / `*Generic` helpers below, which
7
+ * derive permissions from the parent folder's CloudScope so the same endpoint
8
+ * can serve class, school, and personal storage.
4
9
  */
5
10
 
6
- !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]="4ce26498-ba77-5a67-910b-428e64d7047b")}catch(e){}}();
11
+ !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]="461807a5-9bef-532d-8edf-7763337de98b")}catch(e){}}();
7
12
  import { TRPCError } from "@trpc/server";
8
13
  import { getSignedUrl, deleteFile } from "../lib/googleCloudStorage.js";
9
14
  import { logger } from "../utils/logger.js";
10
15
  import { findFileById, findFileWithFolder, findFileWithFolderAndThumbnail, findFolderById, updateFileFolder, updateFileName, deleteFileById, } from "../models/file.js";
11
16
  import { findConversationMembership } from "../models/message.js";
17
+ import { resolveFolderScope } from "../models/cloudStorage.js";
18
+ import { requireFolderAccess } from "./folder.js";
12
19
  function isSchoolAdminForClass(classData, userId) {
13
20
  return Boolean(classData?.school?.memberships?.some((membership) => membership.userId === userId) ||
14
21
  classData?.school?.users?.some((admin) => admin.id === userId));
@@ -143,5 +150,88 @@ export async function deleteFileRecord(fileId, classId) {
143
150
  await deleteFileById(fileId);
144
151
  return { success: true };
145
152
  }
153
+ /**
154
+ * Scope-aware move: works for class, school, and personal storage. Source
155
+ * and target must live in the same scope (no cross-scope moves).
156
+ */
157
+ export async function moveFileScoped(userId, input) {
158
+ const file = await findFileWithFolder(input.fileId);
159
+ if (!file?.folderId) {
160
+ throw new TRPCError({ code: "NOT_FOUND", message: "File not found" });
161
+ }
162
+ const [sourceScope, targetScope] = await Promise.all([
163
+ resolveFolderScope(file.folderId),
164
+ resolveFolderScope(input.targetFolderId),
165
+ ]);
166
+ if (!sourceScope || !targetScope) {
167
+ throw new TRPCError({ code: "NOT_FOUND", message: "Folder not found" });
168
+ }
169
+ if (JSON.stringify(sourceScope) !== JSON.stringify(targetScope)) {
170
+ throw new TRPCError({
171
+ code: "BAD_REQUEST",
172
+ message: "Cannot move files between scopes",
173
+ });
174
+ }
175
+ await requireFolderAccess(userId, file.folderId, "WRITE");
176
+ await requireFolderAccess(userId, input.targetFolderId, "WRITE");
177
+ return updateFileFolder(input.fileId, input.targetFolderId);
178
+ }
179
+ /** Scope-aware rename. */
180
+ export async function renameFileScoped(userId, input) {
181
+ const trimmed = input.newName.trim();
182
+ if (!trimmed) {
183
+ throw new TRPCError({
184
+ code: "BAD_REQUEST",
185
+ message: "File name cannot be empty",
186
+ });
187
+ }
188
+ const file = await findFileWithFolder(input.fileId);
189
+ if (!file) {
190
+ throw new TRPCError({ code: "NOT_FOUND", message: "File not found" });
191
+ }
192
+ if (file.folderId) {
193
+ await requireFolderAccess(userId, file.folderId, "WRITE");
194
+ }
195
+ else if (file.userId !== userId) {
196
+ throw new TRPCError({
197
+ code: "FORBIDDEN",
198
+ message: "You don't own this file",
199
+ });
200
+ }
201
+ return updateFileName(input.fileId, trimmed);
202
+ }
203
+ /**
204
+ * Generic delete that works across class / school / personal storage by
205
+ * deriving permissions from the parent folder's CloudScope (or owning user
206
+ * for unfiled files).
207
+ */
208
+ export async function deleteFileGeneric(userId, fileId) {
209
+ const file = await findFileWithFolderAndThumbnail(fileId);
210
+ if (!file) {
211
+ throw new TRPCError({ code: "NOT_FOUND", message: "File not found" });
212
+ }
213
+ if (file.folderId) {
214
+ await requireFolderAccess(userId, file.folderId, "WRITE");
215
+ }
216
+ else if (file.userId !== userId) {
217
+ throw new TRPCError({
218
+ code: "FORBIDDEN",
219
+ message: "You don't own this file",
220
+ });
221
+ }
222
+ try {
223
+ if (file.uploadStatus === "COMPLETED") {
224
+ await deleteFile(file.path);
225
+ if (file.thumbnail) {
226
+ await deleteFile(file.thumbnail.path);
227
+ }
228
+ }
229
+ }
230
+ catch (error) {
231
+ logger.warn(`Failed to delete file ${file.path}:`, error);
232
+ }
233
+ await deleteFileById(fileId);
234
+ return { success: true };
235
+ }
146
236
  //# sourceMappingURL=file.js.map
147
- //# debugId=4ce26498-ba77-5a67-910b-428e64d7047b
237
+ //# debugId=461807a5-9bef-532d-8edf-7763337de98b