@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
@@ -7,18 +7,24 @@ import {
7
7
  import {
8
8
  acceptInvite,
9
9
  addMember,
10
+ confirmSchoolAssetUpload,
10
11
  createInvite,
11
12
  createSchoolClass,
12
13
  createSchoolRecord,
13
14
  getAdminOverview,
14
15
  getCurrentSchool,
15
16
  getInvite,
17
+ getSchoolAssetUploadUrl,
18
+ getStudentTimeline,
16
19
  listClasses,
17
20
  listInvites,
21
+ listMemberIds,
18
22
  listMembers,
19
23
  removeMember,
20
24
  revokeInvite,
25
+ searchMembers,
21
26
  updateMemberRole,
27
+ updateSchoolDetails,
22
28
  } from "../services/school.js";
23
29
 
24
30
  const schoolRoleSchema = z.enum(["ADMIN", "TEACHER", "STUDENT"]);
@@ -38,12 +44,88 @@ export const schoolRouter = createTRPCRouter({
38
44
  .input(
39
45
  z.object({
40
46
  name: z.string().min(1),
47
+ description: z.string().optional(),
41
48
  subdomain: z.string().min(1).optional(),
42
49
  logoId: z.string().optional(),
43
50
  })
44
51
  )
45
52
  .mutation(({ ctx, input }) => createSchoolRecord(ctx.user!.id, input)),
46
53
 
54
+ update: protectedProcedure
55
+ .input(
56
+ z.object({
57
+ schoolId: z.string(),
58
+ name: z.string().min(1).optional(),
59
+ description: z.string().nullable().optional(),
60
+ subdomain: z.string().nullable().optional(),
61
+ logoId: z.string().nullable().optional(),
62
+ })
63
+ )
64
+ .mutation(({ ctx, input }) => updateSchoolDetails(ctx.user!.id, input)),
65
+
66
+ searchMembers: protectedProcedure
67
+ .input(
68
+ z.object({
69
+ schoolId: z.string(),
70
+ query: z.string(),
71
+ limit: z.number().min(1).max(50).optional(),
72
+ role: schoolRoleSchema.optional(),
73
+ })
74
+ )
75
+ .query(({ ctx, input }) =>
76
+ searchMembers(
77
+ ctx.user!.id,
78
+ input.schoolId,
79
+ input.query,
80
+ input.limit,
81
+ input.role
82
+ )
83
+ ),
84
+
85
+ listMemberIds: protectedProcedure
86
+ .input(schoolIdSchema)
87
+ .query(({ ctx, input }) => listMemberIds(ctx.user!.id, input.schoolId)),
88
+
89
+ getAssetUploadUrl: protectedProcedure
90
+ .input(
91
+ z.object({
92
+ fileName: z.string(),
93
+ fileType: z.string(),
94
+ fileSize: z.number(),
95
+ })
96
+ )
97
+ .mutation(({ ctx, input }) =>
98
+ getSchoolAssetUploadUrl(ctx.user!.id, input)
99
+ ),
100
+
101
+ confirmAssetUpload: protectedProcedure
102
+ .input(
103
+ z.object({
104
+ fileId: z.string(),
105
+ uploadSuccess: z.boolean(),
106
+ })
107
+ )
108
+ .mutation(({ ctx, input }) =>
109
+ confirmSchoolAssetUpload(
110
+ ctx.user!.id,
111
+ input.fileId,
112
+ input.uploadSuccess
113
+ )
114
+ ),
115
+
116
+ getStudentTimeline: protectedProcedure
117
+ .input(
118
+ z.object({
119
+ schoolId: z.string(),
120
+ studentId: z.string(),
121
+ from: z.date().optional(),
122
+ to: z.date().optional(),
123
+ })
124
+ )
125
+ .query(({ ctx, input }) =>
126
+ getStudentTimeline(ctx.user!.id, input)
127
+ ),
128
+
47
129
  getAdminOverview: protectedProcedure
48
130
  .input(schoolIdSchema)
49
131
  .query(({ ctx, input }) => getAdminOverview(ctx.user!.id, input.schoolId)),
@@ -60,6 +142,7 @@ export const schoolRouter = createTRPCRouter({
60
142
  section: z.string(),
61
143
  subject: z.string(),
62
144
  color: z.string().optional(),
145
+ departmentId: z.string().optional(),
63
146
  teachers: z.array(z.string()).optional(),
64
147
  students: z.array(z.string()).optional(),
65
148
  })
@@ -0,0 +1,78 @@
1
+ import { z } from "zod";
2
+ import { createTRPCRouter, protectedProcedure } from "../trpc.js";
3
+ import {
4
+ attachTag,
5
+ createTagRecord,
6
+ deleteTagRecord,
7
+ detachTag,
8
+ getAttachedTags,
9
+ listTags,
10
+ updateTagRecord,
11
+ } from "../services/tag.js";
12
+
13
+ const tagTargetSchema = z.enum(["USER", "CLASS", "RUBRIC", "BOUNDARY"]);
14
+
15
+ export const tagRouter = createTRPCRouter({
16
+ list: protectedProcedure
17
+ .input(
18
+ z.object({
19
+ schoolId: z.string(),
20
+ departmentId: z.string().nullable().optional(),
21
+ })
22
+ )
23
+ .query(({ ctx, input }) => listTags(ctx.user!.id, input)),
24
+
25
+ create: protectedProcedure
26
+ .input(
27
+ z.object({
28
+ schoolId: z.string(),
29
+ departmentId: z.string().nullable().optional(),
30
+ name: z.string().min(1),
31
+ color: z.string().optional(),
32
+ })
33
+ )
34
+ .mutation(({ ctx, input }) => createTagRecord(ctx.user!.id, input)),
35
+
36
+ update: protectedProcedure
37
+ .input(
38
+ z.object({
39
+ tagId: z.string(),
40
+ name: z.string().min(1).optional(),
41
+ color: z.string().optional(),
42
+ departmentId: z.string().nullable().optional(),
43
+ })
44
+ )
45
+ .mutation(({ ctx, input }) => updateTagRecord(ctx.user!.id, input)),
46
+
47
+ delete: protectedProcedure
48
+ .input(z.object({ tagId: z.string() }))
49
+ .mutation(({ ctx, input }) =>
50
+ deleteTagRecord(ctx.user!.id, input.tagId)
51
+ ),
52
+
53
+ attach: protectedProcedure
54
+ .input(
55
+ z.object({
56
+ tagId: z.string(),
57
+ target: tagTargetSchema,
58
+ targetId: z.string(),
59
+ })
60
+ )
61
+ .mutation(({ ctx, input }) => attachTag(ctx.user!.id, input)),
62
+
63
+ detach: protectedProcedure
64
+ .input(
65
+ z.object({
66
+ tagId: z.string(),
67
+ target: tagTargetSchema,
68
+ targetId: z.string(),
69
+ })
70
+ )
71
+ .mutation(({ ctx, input }) => detachTag(ctx.user!.id, input)),
72
+
73
+ getAttached: protectedProcedure
74
+ .input(
75
+ z.object({ target: tagTargetSchema, targetId: z.string() })
76
+ )
77
+ .query(({ ctx, input }) => getAttachedTags(ctx.user!.id, input)),
78
+ });
@@ -71,7 +71,10 @@ export async function getAllClasses(userId: string) {
71
71
  assignments: cls.assignments,
72
72
  members: [...cls.students, ...cls.teachers],
73
73
  color: cls.color,
74
- })),
74
+ schoolId: cls.schoolId ?? null,
75
+ school: cls.school ?? null,
76
+ department: cls.department ?? null,
77
+ })).filter((cls) => cls.members.some((member) => member.id === userId)),
75
78
  studentInClass: studentClasses.map((cls) => ({
76
79
  id: cls.id,
77
80
  name: cls.name,
@@ -81,6 +84,9 @@ export async function getAllClasses(userId: string) {
81
84
  assignments: cls.assignments,
82
85
  members: [...cls.students, ...cls.teachers],
83
86
  color: cls.color,
87
+ schoolId: cls.schoolId ?? null,
88
+ school: cls.school ?? null,
89
+ department: cls.department ?? null,
84
90
  })),
85
91
  };
86
92
  }
@@ -112,20 +118,87 @@ export async function getClass(userId: string, classId: string) {
112
118
  })),
113
119
  };
114
120
 
115
- const sections = await findSectionsByClassId(classId);
121
+ const [sections, affiliation] = await Promise.all([
122
+ findSectionsByClassId(classId),
123
+ prisma.class.findUnique({
124
+ where: { id: classId },
125
+ select: {
126
+ schoolId: true,
127
+ departmentId: true,
128
+ school: {
129
+ select: {
130
+ id: true,
131
+ name: true,
132
+ description: true,
133
+ logo: { select: { id: true, path: true, type: true } },
134
+ },
135
+ },
136
+ department: {
137
+ select: {
138
+ id: true,
139
+ name: true,
140
+ color: true,
141
+ description: true,
142
+ },
143
+ },
144
+ tags: {
145
+ select: { id: true, name: true, color: true, departmentId: true },
146
+ },
147
+ },
148
+ }),
149
+ ]);
116
150
 
117
151
  return {
118
152
  class: {
119
153
  ...formattedClassData,
120
154
  sections,
155
+ affiliation: affiliation
156
+ ? {
157
+ school: affiliation.school,
158
+ department: affiliation.department,
159
+ tags: affiliation.tags,
160
+ }
161
+ : null,
121
162
  },
122
163
  };
123
164
  }
124
165
 
125
166
  export async function updateClass(
126
167
  classId: string,
127
- data: { name?: string; section?: string; subject?: string }
168
+ data: {
169
+ name?: string;
170
+ section?: string;
171
+ subject?: string;
172
+ color?: string;
173
+ departmentId?: string | null;
174
+ }
128
175
  ) {
176
+ if (data.departmentId !== undefined) {
177
+ // Department must belong to the class's institution.
178
+ const cls = await prisma.class.findUnique({
179
+ where: { id: classId },
180
+ select: { schoolId: true },
181
+ });
182
+ if (!cls?.schoolId && data.departmentId) {
183
+ throw new TRPCError({
184
+ code: "BAD_REQUEST",
185
+ message: "Personal classes can't have a department",
186
+ });
187
+ }
188
+ if (data.departmentId && cls?.schoolId) {
189
+ const dept = await prisma.department.findUnique({
190
+ where: { id: data.departmentId },
191
+ select: { schoolId: true },
192
+ });
193
+ if (!dept || dept.schoolId !== cls.schoolId) {
194
+ throw new TRPCError({
195
+ code: "BAD_REQUEST",
196
+ message: "Department does not belong to this institution",
197
+ });
198
+ }
199
+ }
200
+ }
201
+
129
202
  const updatedClass = await prisma.class.update({
130
203
  where: { id: classId },
131
204
  data,
@@ -134,6 +207,8 @@ export async function updateClass(
134
207
  name: true,
135
208
  section: true,
136
209
  subject: true,
210
+ color: true,
211
+ departmentId: true,
137
212
  },
138
213
  });
139
214
  return { updatedClass };
@@ -149,9 +224,19 @@ export async function createClass(
149
224
  subject: string;
150
225
  color?: string;
151
226
  schoolId?: string;
227
+ departmentId?: string;
152
228
  }
153
229
  ) {
154
- const { students, teachers, name, section, subject, color, schoolId } = input;
230
+ const {
231
+ students,
232
+ teachers,
233
+ name,
234
+ section,
235
+ subject,
236
+ color,
237
+ schoolId,
238
+ departmentId,
239
+ } = input;
155
240
 
156
241
  if (schoolId) {
157
242
  const membership = await prisma.schoolMembership.findFirst({
@@ -174,6 +259,25 @@ export async function createClass(
174
259
  }
175
260
  }
176
261
 
262
+ if (departmentId) {
263
+ if (!schoolId) {
264
+ throw new TRPCError({
265
+ code: "BAD_REQUEST",
266
+ message: "Personal classes can't have a department",
267
+ });
268
+ }
269
+ const dept = await prisma.department.findUnique({
270
+ where: { id: departmentId },
271
+ select: { schoolId: true },
272
+ });
273
+ if (!dept || dept.schoolId !== schoolId) {
274
+ throw new TRPCError({
275
+ code: "BAD_REQUEST",
276
+ message: "Department does not belong to this institution",
277
+ });
278
+ }
279
+ }
280
+
177
281
  const teacherIds = teachers?.length ? teachers : [userId];
178
282
  const studentIds = students?.length ? students : [];
179
283
 
@@ -188,6 +292,7 @@ export async function createClass(
188
292
  subject,
189
293
  color,
190
294
  schoolId,
295
+ departmentId,
191
296
  teachers: { connect: teacherIds.map((id) => ({ id })) },
192
297
  ...(studentIds.length
193
298
  ? { students: { connect: studentIds.map((id) => ({ id })) } }