@studious-lms/server 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) 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 +22 -22
  30. package/dist/models/department.d.ts +154 -0
  31. package/dist/models/department.d.ts.map +1 -0
  32. package/dist/models/department.js +135 -0
  33. package/dist/models/department.js.map +1 -0
  34. package/dist/models/event.d.ts +7 -1
  35. package/dist/models/event.d.ts.map +1 -1
  36. package/dist/models/event.js +2 -5
  37. package/dist/models/event.js.map +1 -1
  38. package/dist/models/file.d.ts +25 -10
  39. package/dist/models/file.d.ts.map +1 -1
  40. package/dist/models/file.js +2 -5
  41. package/dist/models/file.js.map +1 -1
  42. package/dist/models/folder.d.ts +47 -0
  43. package/dist/models/folder.d.ts.map +1 -1
  44. package/dist/models/labChat.d.ts +5 -3
  45. package/dist/models/labChat.d.ts.map +1 -1
  46. package/dist/models/message.d.ts +46 -12
  47. package/dist/models/message.d.ts.map +1 -1
  48. package/dist/models/message.js +19 -10
  49. package/dist/models/message.js.map +1 -1
  50. package/dist/models/newtonChat.d.ts +2 -1
  51. package/dist/models/newtonChat.d.ts.map +1 -1
  52. package/dist/models/school.d.ts +45 -12
  53. package/dist/models/school.d.ts.map +1 -1
  54. package/dist/models/school.js +19 -2
  55. package/dist/models/school.js.map +1 -1
  56. package/dist/models/section.d.ts +1 -0
  57. package/dist/models/section.d.ts.map +1 -1
  58. package/dist/models/tag.d.ts +117 -0
  59. package/dist/models/tag.d.ts.map +1 -0
  60. package/dist/models/tag.js +113 -0
  61. package/dist/models/tag.js.map +1 -0
  62. package/dist/models/worksheet.d.ts +3 -0
  63. package/dist/models/worksheet.d.ts.map +1 -1
  64. package/dist/pipelines/aiLabChatContract.d.ts +8 -8
  65. package/dist/routers/_app.d.ts +3200 -1972
  66. package/dist/routers/_app.d.ts.map +1 -1
  67. package/dist/routers/_app.js +6 -2
  68. package/dist/routers/_app.js.map +1 -1
  69. package/dist/routers/agenda.d.ts +2 -0
  70. package/dist/routers/agenda.d.ts.map +1 -1
  71. package/dist/routers/announcement.d.ts.map +1 -1
  72. package/dist/routers/announcement.js +3 -5
  73. package/dist/routers/announcement.js.map +1 -1
  74. package/dist/routers/assignment.d.ts +18 -9
  75. package/dist/routers/assignment.d.ts.map +1 -1
  76. package/dist/routers/assignment.js +2 -3
  77. package/dist/routers/assignment.js.map +1 -1
  78. package/dist/routers/class.d.ts +87 -14
  79. package/dist/routers/class.d.ts.map +1 -1
  80. package/dist/routers/class.js +5 -2
  81. package/dist/routers/class.js.map +1 -1
  82. package/dist/routers/conversation.d.ts +5 -5
  83. package/dist/routers/department.d.ts +293 -0
  84. package/dist/routers/department.d.ts.map +1 -0
  85. package/dist/routers/department.js +93 -0
  86. package/dist/routers/department.js.map +1 -0
  87. package/dist/routers/event.d.ts +2 -1
  88. package/dist/routers/event.d.ts.map +1 -1
  89. package/dist/routers/file.d.ts +8 -6
  90. package/dist/routers/file.d.ts.map +1 -1
  91. package/dist/routers/file.js +26 -17
  92. package/dist/routers/file.js.map +1 -1
  93. package/dist/routers/folder.d.ts +139 -186
  94. package/dist/routers/folder.d.ts.map +1 -1
  95. package/dist/routers/folder.js +82 -90
  96. package/dist/routers/folder.js.map +1 -1
  97. package/dist/routers/labChat.d.ts +11 -2
  98. package/dist/routers/labChat.d.ts.map +1 -1
  99. package/dist/routers/message.d.ts +20 -2
  100. package/dist/routers/message.d.ts.map +1 -1
  101. package/dist/routers/newtonChat.d.ts +9 -0
  102. package/dist/routers/newtonChat.d.ts.map +1 -1
  103. package/dist/routers/school.d.ts +160 -17
  104. package/dist/routers/school.d.ts.map +1 -1
  105. package/dist/routers/school.js +46 -3
  106. package/dist/routers/school.js.map +1 -1
  107. package/dist/routers/tag.d.ts +119 -0
  108. package/dist/routers/tag.d.ts.map +1 -0
  109. package/dist/routers/tag.js +52 -0
  110. package/dist/routers/tag.js.map +1 -0
  111. package/dist/routers/worksheet.d.ts +1 -0
  112. package/dist/routers/worksheet.d.ts.map +1 -1
  113. package/dist/seedDatabase.d.ts +0 -1
  114. package/dist/seedDatabase.d.ts.map +1 -1
  115. package/dist/services/agenda.d.ts +2 -0
  116. package/dist/services/agenda.d.ts.map +1 -1
  117. package/dist/services/assignment.d.ts +18 -9
  118. package/dist/services/assignment.d.ts.map +1 -1
  119. package/dist/services/class.d.ts +87 -14
  120. package/dist/services/class.d.ts.map +1 -1
  121. package/dist/services/class.js +91 -4
  122. package/dist/services/class.js.map +1 -1
  123. package/dist/services/conversation.d.ts +5 -5
  124. package/dist/services/department.d.ts +200 -0
  125. package/dist/services/department.d.ts.map +1 -0
  126. package/dist/services/department.js +368 -0
  127. package/dist/services/department.js.map +1 -0
  128. package/dist/services/event.d.ts +2 -1
  129. package/dist/services/event.d.ts.map +1 -1
  130. package/dist/services/file.d.ts +85 -0
  131. package/dist/services/file.d.ts.map +1 -1
  132. package/dist/services/file.js +92 -2
  133. package/dist/services/file.js.map +1 -1
  134. package/dist/services/folder.d.ts +125 -149
  135. package/dist/services/folder.d.ts.map +1 -1
  136. package/dist/services/folder.js +258 -199
  137. package/dist/services/folder.js.map +1 -1
  138. package/dist/services/labChat.d.ts +11 -2
  139. package/dist/services/labChat.d.ts.map +1 -1
  140. package/dist/services/labChat.js +4 -3
  141. package/dist/services/labChat.js.map +1 -1
  142. package/dist/services/message.d.ts +20 -2
  143. package/dist/services/message.d.ts.map +1 -1
  144. package/dist/services/message.js +7 -23
  145. package/dist/services/message.js.map +1 -1
  146. package/dist/services/newtonChat.d.ts +9 -0
  147. package/dist/services/newtonChat.d.ts.map +1 -1
  148. package/dist/services/newtonChat.js +4 -3
  149. package/dist/services/newtonChat.js.map +1 -1
  150. package/dist/services/school.d.ts +142 -15
  151. package/dist/services/school.d.ts.map +1 -1
  152. package/dist/services/school.js +308 -54
  153. package/dist/services/school.js.map +1 -1
  154. package/dist/services/tag.d.ts +71 -0
  155. package/dist/services/tag.d.ts.map +1 -0
  156. package/dist/services/tag.js +178 -0
  157. package/dist/services/tag.js.map +1 -0
  158. package/dist/services/worksheet.d.ts +1 -0
  159. package/dist/services/worksheet.d.ts.map +1 -1
  160. package/dist/utils/aiUser.d.ts.map +1 -1
  161. package/dist/utils/aiUser.js +2 -3
  162. package/dist/utils/aiUser.js.map +1 -1
  163. package/package.json +1 -1
  164. package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +184 -0
  165. package/prisma/schema.prisma +119 -35
  166. package/src/models/access.ts +8 -24
  167. package/src/models/announcement.ts +0 -1
  168. package/src/models/auth.ts +0 -1
  169. package/src/models/class.ts +13 -0
  170. package/src/models/cloudStorage.ts +194 -0
  171. package/src/models/comment.ts +0 -4
  172. package/src/models/department.ts +159 -0
  173. package/src/models/event.ts +0 -3
  174. package/src/models/file.ts +0 -3
  175. package/src/models/message.ts +19 -5
  176. package/src/models/school.ts +26 -0
  177. package/src/models/tag.ts +140 -0
  178. package/src/routers/_app.ts +4 -0
  179. package/src/routers/announcement.ts +1 -3
  180. package/src/routers/assignment.ts +0 -1
  181. package/src/routers/class.ts +3 -0
  182. package/src/routers/department.ts +174 -0
  183. package/src/routers/file.ts +29 -26
  184. package/src/routers/folder.ts +100 -121
  185. package/src/routers/school.ts +83 -0
  186. package/src/routers/tag.ts +78 -0
  187. package/src/services/class.ts +108 -3
  188. package/src/services/department.ts +478 -0
  189. package/src/services/file.ts +100 -0
  190. package/src/services/folder.ts +313 -240
  191. package/src/services/labChat.ts +2 -1
  192. package/src/services/message.ts +5 -21
  193. package/src/services/newtonChat.ts +2 -1
  194. package/src/services/school.ts +369 -57
  195. package/src/services/tag.ts +229 -0
  196. package/src/utils/aiUser.ts +0 -1
@@ -1,15 +1,29 @@
1
1
  /**
2
2
  * Message model – messages, attachments, mentions, unread counts.
3
3
  */
4
+ import type { Prisma } from "@prisma/client";
4
5
  import { prisma } from "../lib/prisma.js";
5
6
 
7
+ /**
8
+ * Canonical shape for File rows surfaced as message attachments.
9
+ * Use this everywhere a message (list, send, update, suggestion broadcast)
10
+ * exposes attached files so the wire shape stays in sync.
11
+ */
12
+ export const messageFileSelect = {
13
+ id: true,
14
+ name: true,
15
+ type: true,
16
+ size: true,
17
+ path: true,
18
+ uploadedAt: true,
19
+ uploadStatus: true,
20
+ thumbnailId: true,
21
+ thumbnail: { select: { id: true, path: true } },
22
+ } as const satisfies Prisma.FileSelect;
23
+
6
24
  const messageInclude = {
7
25
  attachments: {
8
- select: {
9
- id: true,
10
- name: true,
11
- type: true,
12
- },
26
+ select: messageFileSelect,
13
27
  },
14
28
  sender: {
15
29
  select: {
@@ -52,8 +52,10 @@ export function listUserSchoolMemberships(userId: string) {
52
52
  select: {
53
53
  id: true,
54
54
  name: true,
55
+ description: true,
55
56
  subdomain: true,
56
57
  logoId: true,
58
+ logo: { select: { id: true, path: true, type: true } },
57
59
  createdAt: true,
58
60
  updatedAt: true,
59
61
  },
@@ -96,14 +98,38 @@ export function getSchoolById(schoolId: string) {
96
98
  select: {
97
99
  id: true,
98
100
  name: true,
101
+ description: true,
99
102
  subdomain: true,
100
103
  logoId: true,
104
+ logo: { select: { id: true, name: true, type: true, path: true } },
101
105
  createdAt: true,
102
106
  updatedAt: true,
103
107
  },
104
108
  });
105
109
  }
106
110
 
111
+ export function updateSchoolRecord(
112
+ schoolId: string,
113
+ data: {
114
+ name?: string;
115
+ description?: string | null;
116
+ subdomain?: string | null;
117
+ logoId?: string | null;
118
+ }
119
+ ) {
120
+ return prisma.school.update({
121
+ where: { id: schoolId },
122
+ data,
123
+ select: {
124
+ id: true,
125
+ name: true,
126
+ description: true,
127
+ subdomain: true,
128
+ logoId: true,
129
+ },
130
+ });
131
+ }
132
+
107
133
  export function getSchoolOverview(schoolId: string) {
108
134
  return prisma.school.findUnique({
109
135
  where: { id: schoolId },
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Tag model — institution-scoped tags applicable to users, classes, rubrics,
3
+ * and grading boundaries. Optionally associated with a department.
4
+ */
5
+ import { prisma } from "../lib/prisma.js";
6
+
7
+ const tagSelect = {
8
+ id: true,
9
+ schoolId: true,
10
+ departmentId: true,
11
+ name: true,
12
+ color: true,
13
+ createdAt: true,
14
+ updatedAt: true,
15
+ } as const;
16
+
17
+ export type TagTarget = "USER" | "CLASS" | "RUBRIC" | "BOUNDARY";
18
+
19
+ export function listTagsForSchool(
20
+ schoolId: string,
21
+ options?: { departmentId?: string | null }
22
+ ) {
23
+ return prisma.tag.findMany({
24
+ where: {
25
+ schoolId,
26
+ ...(options?.departmentId === null
27
+ ? { departmentId: null }
28
+ : options?.departmentId
29
+ ? { departmentId: options.departmentId }
30
+ : {}),
31
+ },
32
+ orderBy: [{ name: "asc" }],
33
+ select: tagSelect,
34
+ });
35
+ }
36
+
37
+ export function getTag(tagId: string) {
38
+ return prisma.tag.findUnique({
39
+ where: { id: tagId },
40
+ select: tagSelect,
41
+ });
42
+ }
43
+
44
+ export function createTag(data: {
45
+ schoolId: string;
46
+ departmentId?: string | null;
47
+ name: string;
48
+ color?: string;
49
+ }) {
50
+ return prisma.tag.create({
51
+ data: {
52
+ schoolId: data.schoolId,
53
+ departmentId: data.departmentId ?? null,
54
+ name: data.name,
55
+ color: data.color ?? "#3B82F6",
56
+ },
57
+ select: tagSelect,
58
+ });
59
+ }
60
+
61
+ export function updateTag(
62
+ tagId: string,
63
+ data: { name?: string; color?: string; departmentId?: string | null }
64
+ ) {
65
+ return prisma.tag.update({
66
+ where: { id: tagId },
67
+ data,
68
+ select: tagSelect,
69
+ });
70
+ }
71
+
72
+ export function deleteTag(tagId: string) {
73
+ return prisma.tag.delete({ where: { id: tagId } });
74
+ }
75
+
76
+ const targetRelation: Record<TagTarget, "users" | "classes" | "rubrics" | "boundaries"> = {
77
+ USER: "users",
78
+ CLASS: "classes",
79
+ RUBRIC: "rubrics",
80
+ BOUNDARY: "boundaries",
81
+ };
82
+
83
+ export function attachTagToTarget(params: {
84
+ tagId: string;
85
+ target: TagTarget;
86
+ targetId: string;
87
+ }) {
88
+ const relation = targetRelation[params.target];
89
+ return prisma.tag.update({
90
+ where: { id: params.tagId },
91
+ data: {
92
+ [relation]: { connect: { id: params.targetId } },
93
+ },
94
+ select: { id: true },
95
+ });
96
+ }
97
+
98
+ export function detachTagFromTarget(params: {
99
+ tagId: string;
100
+ target: TagTarget;
101
+ targetId: string;
102
+ }) {
103
+ const relation = targetRelation[params.target];
104
+ return prisma.tag.update({
105
+ where: { id: params.tagId },
106
+ data: {
107
+ [relation]: { disconnect: { id: params.targetId } },
108
+ },
109
+ select: { id: true },
110
+ });
111
+ }
112
+
113
+ export function listTagsAttachedTo(target: TagTarget, targetId: string) {
114
+ switch (target) {
115
+ case "USER":
116
+ return prisma.tag.findMany({
117
+ where: { users: { some: { id: targetId } } },
118
+ select: tagSelect,
119
+ orderBy: { name: "asc" },
120
+ });
121
+ case "CLASS":
122
+ return prisma.tag.findMany({
123
+ where: { classes: { some: { id: targetId } } },
124
+ select: tagSelect,
125
+ orderBy: { name: "asc" },
126
+ });
127
+ case "RUBRIC":
128
+ return prisma.tag.findMany({
129
+ where: { rubrics: { some: { id: targetId } } },
130
+ select: tagSelect,
131
+ orderBy: { name: "asc" },
132
+ });
133
+ case "BOUNDARY":
134
+ return prisma.tag.findMany({
135
+ where: { boundaries: { some: { id: targetId } } },
136
+ select: tagSelect,
137
+ orderBy: { name: "asc" },
138
+ });
139
+ }
140
+ }
@@ -23,6 +23,8 @@ import { commentRouter } from "./comment.js";
23
23
  import { studentProgressRouter } from "./studentProgress.js";
24
24
  import { searchRouter } from "./search.js";
25
25
  import { schoolRouter } from "./school.js";
26
+ import { departmentRouter } from "./department.js";
27
+ import { tagRouter } from "./tag.js";
26
28
 
27
29
  export const appRouter = createTRPCRouter({
28
30
  class: classRouter,
@@ -47,6 +49,8 @@ export const appRouter = createTRPCRouter({
47
49
  studentProgress: studentProgressRouter,
48
50
  search: searchRouter,
49
51
  school: schoolRouter,
52
+ department: departmentRouter,
53
+ tag: tagRouter,
50
54
  });
51
55
 
52
56
  // Export type router type definition
@@ -342,9 +342,7 @@ export const announcementRouter = createTRPCRouter({
342
342
  school: {
343
343
  include: {
344
344
  users: {
345
- where: {
346
- role: "ADMIN",
347
- },
345
+ select: { id: true },
348
346
  },
349
347
  memberships: {
350
348
  where: {
@@ -722,7 +722,6 @@ export const assignmentRouter = createTRPCRouter({
722
722
  school: {
723
723
  include: {
724
724
  users: {
725
- where: { role: "ADMIN" },
726
725
  select: { id: true },
727
726
  },
728
727
  memberships: {
@@ -58,6 +58,8 @@ export const classRouter = createTRPCRouter({
58
58
  name: z.string().optional(),
59
59
  section: z.string().optional(),
60
60
  subject: z.string().optional(),
61
+ color: z.string().optional(),
62
+ departmentId: z.string().nullable().optional(),
61
63
  })
62
64
  )
63
65
  .mutation(({ input }) => {
@@ -75,6 +77,7 @@ export const classRouter = createTRPCRouter({
75
77
  subject: z.string(),
76
78
  color: z.string().optional(),
77
79
  schoolId: z.string().optional(),
80
+ departmentId: z.string().optional(),
78
81
  })
79
82
  )
80
83
  .mutation(({ ctx, input }) =>
@@ -0,0 +1,174 @@
1
+ import { z } from "zod";
2
+ import { createTRPCRouter, protectedProcedure } from "../trpc.js";
3
+ import {
4
+ addMembersToDepartment,
5
+ applyBoundaryTemplate,
6
+ applyRubricTemplate,
7
+ createBoundaryTemplate,
8
+ createDepartmentRecord,
9
+ createRubricTemplate,
10
+ deleteBoundaryTemplate,
11
+ deleteDepartmentRecord,
12
+ deleteRubricTemplate,
13
+ getDepartmentDetails,
14
+ listDepartments,
15
+ listMyDepartments,
16
+ removeMemberFromDepartment,
17
+ setClassDepartment,
18
+ updateBoundaryTemplate,
19
+ updateDepartmentRecord,
20
+ updateRubricTemplate,
21
+ } from "../services/department.js";
22
+
23
+ export const departmentRouter = createTRPCRouter({
24
+ list: protectedProcedure
25
+ .input(z.object({ schoolId: z.string() }))
26
+ .query(({ ctx, input }) => listDepartments(ctx.user!.id, input.schoolId)),
27
+
28
+ listMine: protectedProcedure
29
+ .input(z.object({ schoolId: z.string() }))
30
+ .query(({ ctx, input }) =>
31
+ listMyDepartments(ctx.user!.id, input.schoolId)
32
+ ),
33
+
34
+ get: protectedProcedure
35
+ .input(z.object({ departmentId: z.string() }))
36
+ .query(({ ctx, input }) =>
37
+ getDepartmentDetails(ctx.user!.id, input.departmentId)
38
+ ),
39
+
40
+ create: protectedProcedure
41
+ .input(
42
+ z.object({
43
+ schoolId: z.string(),
44
+ name: z.string().min(1),
45
+ description: z.string().optional(),
46
+ color: z.string().optional(),
47
+ })
48
+ )
49
+ .mutation(({ ctx, input }) =>
50
+ createDepartmentRecord(ctx.user!.id, input)
51
+ ),
52
+
53
+ update: protectedProcedure
54
+ .input(
55
+ z.object({
56
+ departmentId: z.string(),
57
+ name: z.string().min(1).optional(),
58
+ description: z.string().nullable().optional(),
59
+ color: z.string().optional(),
60
+ })
61
+ )
62
+ .mutation(({ ctx, input }) =>
63
+ updateDepartmentRecord(ctx.user!.id, input)
64
+ ),
65
+
66
+ delete: protectedProcedure
67
+ .input(z.object({ departmentId: z.string() }))
68
+ .mutation(({ ctx, input }) =>
69
+ deleteDepartmentRecord(ctx.user!.id, input.departmentId)
70
+ ),
71
+
72
+ addMembers: protectedProcedure
73
+ .input(
74
+ z.object({
75
+ departmentId: z.string(),
76
+ userIds: z.array(z.string()),
77
+ })
78
+ )
79
+ .mutation(({ ctx, input }) =>
80
+ addMembersToDepartment(ctx.user!.id, input)
81
+ ),
82
+
83
+ removeMember: protectedProcedure
84
+ .input(z.object({ departmentId: z.string(), memberId: z.string() }))
85
+ .mutation(({ ctx, input }) =>
86
+ removeMemberFromDepartment(ctx.user!.id, input)
87
+ ),
88
+
89
+ setClassDepartment: protectedProcedure
90
+ .input(
91
+ z.object({
92
+ classId: z.string(),
93
+ departmentId: z.string().nullable(),
94
+ })
95
+ )
96
+ .mutation(({ ctx, input }) => setClassDepartment(ctx.user!.id, input)),
97
+
98
+ /** Template authoring. */
99
+ createRubricTemplate: protectedProcedure
100
+ .input(
101
+ z.object({
102
+ departmentId: z.string(),
103
+ name: z.string(),
104
+ structured: z.string(),
105
+ })
106
+ )
107
+ .mutation(({ ctx, input }) =>
108
+ createRubricTemplate(ctx.user!.id, input)
109
+ ),
110
+
111
+ updateRubricTemplate: protectedProcedure
112
+ .input(
113
+ z.object({
114
+ templateId: z.string(),
115
+ name: z.string().optional(),
116
+ structured: z.string().optional(),
117
+ })
118
+ )
119
+ .mutation(({ ctx, input }) =>
120
+ updateRubricTemplate(ctx.user!.id, input)
121
+ ),
122
+
123
+ deleteRubricTemplate: protectedProcedure
124
+ .input(z.object({ templateId: z.string() }))
125
+ .mutation(({ ctx, input }) =>
126
+ deleteRubricTemplate(ctx.user!.id, input.templateId)
127
+ ),
128
+
129
+ createBoundaryTemplate: protectedProcedure
130
+ .input(
131
+ z.object({
132
+ departmentId: z.string(),
133
+ name: z.string(),
134
+ structured: z.string(),
135
+ })
136
+ )
137
+ .mutation(({ ctx, input }) =>
138
+ createBoundaryTemplate(ctx.user!.id, input)
139
+ ),
140
+
141
+ updateBoundaryTemplate: protectedProcedure
142
+ .input(
143
+ z.object({
144
+ templateId: z.string(),
145
+ name: z.string().optional(),
146
+ structured: z.string().optional(),
147
+ })
148
+ )
149
+ .mutation(({ ctx, input }) =>
150
+ updateBoundaryTemplate(ctx.user!.id, input)
151
+ ),
152
+
153
+ deleteBoundaryTemplate: protectedProcedure
154
+ .input(z.object({ templateId: z.string() }))
155
+ .mutation(({ ctx, input }) =>
156
+ deleteBoundaryTemplate(ctx.user!.id, input.templateId)
157
+ ),
158
+
159
+ applyRubricTemplate: protectedProcedure
160
+ .input(
161
+ z.object({ classId: z.string(), templateId: z.string() })
162
+ )
163
+ .mutation(({ ctx, input }) =>
164
+ applyRubricTemplate(ctx.user!.id, input)
165
+ ),
166
+
167
+ applyBoundaryTemplate: protectedProcedure
168
+ .input(
169
+ z.object({ classId: z.string(), templateId: z.string() })
170
+ )
171
+ .mutation(({ ctx, input }) =>
172
+ applyBoundaryTemplate(ctx.user!.id, input)
173
+ ),
174
+ });
@@ -1,16 +1,20 @@
1
1
  import { z } from "zod";
2
+ import { createTRPCRouter, protectedProcedure } from "../trpc.js";
2
3
  import {
3
- createTRPCRouter,
4
- protectedProcedure,
5
- protectedTeacherProcedure,
6
- } from "../trpc.js";
7
- import {
4
+ deleteFileGeneric,
8
5
  getFileSignedUrl,
9
- moveFile,
10
- renameFile,
11
- deleteFileRecord,
6
+ moveFileScoped,
7
+ renameFileScoped,
12
8
  } from "../services/file.js";
13
9
 
10
+ /**
11
+ * File router. Backed by the same unified scope-aware permission model as the
12
+ * folder router. The class files page and the cloud-storage page (personal /
13
+ * institution) both run through the same endpoints.
14
+ *
15
+ * Permissions are derived from the file's parent folder scope, so callers
16
+ * never need to send `classId` / `scope` — just the file id.
17
+ */
14
18
  export const fileRouter = createTRPCRouter({
15
19
  getSignedUrl: protectedProcedure
16
20
  .input(z.object({ fileId: z.string() }))
@@ -18,38 +22,37 @@ export const fileRouter = createTRPCRouter({
18
22
  getFileSignedUrl(ctx.user!.id, input.fileId)
19
23
  ),
20
24
 
21
- move: protectedTeacherProcedure
25
+ move: protectedProcedure
22
26
  .input(
23
27
  z.object({
24
28
  fileId: z.string(),
25
29
  targetFolderId: z.string(),
26
- classId: z.string(),
27
30
  })
28
31
  )
29
- .mutation(({ input }) =>
30
- moveFile(input.fileId, input.targetFolderId)
32
+ .mutation(({ ctx, input }) =>
33
+ moveFileScoped(ctx.user!.id, {
34
+ fileId: input.fileId,
35
+ targetFolderId: input.targetFolderId,
36
+ })
31
37
  ),
32
38
 
33
- rename: protectedTeacherProcedure
39
+ rename: protectedProcedure
34
40
  .input(
35
41
  z.object({
36
42
  fileId: z.string(),
37
- newName: z.string(),
38
- classId: z.string(),
43
+ newName: z.string().min(1),
39
44
  })
40
45
  )
41
- .mutation(({ input }) =>
42
- renameFile(input.fileId, input.newName)
46
+ .mutation(({ ctx, input }) =>
47
+ renameFileScoped(ctx.user!.id, {
48
+ fileId: input.fileId,
49
+ newName: input.newName,
50
+ })
43
51
  ),
44
52
 
45
- delete: protectedTeacherProcedure
46
- .input(
47
- z.object({
48
- fileId: z.string(),
49
- classId: z.string(),
50
- })
51
- )
52
- .mutation(({ input }) =>
53
- deleteFileRecord(input.fileId, input.classId)
53
+ delete: protectedProcedure
54
+ .input(z.object({ fileId: z.string() }))
55
+ .mutation(({ ctx, input }) =>
56
+ deleteFileGeneric(ctx.user!.id, input.fileId)
54
57
  ),
55
58
  });