@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
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Tag service — institution-scoped tag CRUD and assignment to taggable
3
+ * targets (users, classes, rubrics, boundaries).
4
+ */
5
+ import { TRPCError } from "@trpc/server";
6
+ import { prisma } from "../lib/prisma.js";
7
+ import {
8
+ attachTagToTarget,
9
+ createTag,
10
+ deleteTag,
11
+ detachTagFromTarget,
12
+ getTag,
13
+ listTagsAttachedTo,
14
+ listTagsForSchool,
15
+ type TagTarget,
16
+ updateTag,
17
+ } from "../models/tag.js";
18
+ import {
19
+ findActiveSchoolAdminMembership,
20
+ findSchoolMembership,
21
+ } from "../models/school.js";
22
+
23
+ async function requireAdmin(schoolId: string, userId: string) {
24
+ const membership = await findActiveSchoolAdminMembership(schoolId, userId);
25
+ if (!membership) {
26
+ throw new TRPCError({
27
+ code: "FORBIDDEN",
28
+ message: "Only school admins can manage tags",
29
+ });
30
+ }
31
+ }
32
+
33
+ async function requireMember(schoolId: string, userId: string) {
34
+ const membership = await findSchoolMembership(schoolId, userId);
35
+ if (!membership || membership.status !== "ACTIVE") {
36
+ throw new TRPCError({
37
+ code: "FORBIDDEN",
38
+ message: "You aren't a member of this institution",
39
+ });
40
+ }
41
+ return membership;
42
+ }
43
+
44
+ export async function listTags(
45
+ userId: string,
46
+ input: { schoolId: string; departmentId?: string | null }
47
+ ) {
48
+ await requireMember(input.schoolId, userId);
49
+ return listTagsForSchool(input.schoolId, {
50
+ departmentId: input.departmentId,
51
+ });
52
+ }
53
+
54
+ export async function createTagRecord(
55
+ userId: string,
56
+ input: {
57
+ schoolId: string;
58
+ departmentId?: string | null;
59
+ name: string;
60
+ color?: string;
61
+ }
62
+ ) {
63
+ await requireAdmin(input.schoolId, userId);
64
+ if (input.departmentId) {
65
+ const dept = await prisma.department.findUnique({
66
+ where: { id: input.departmentId },
67
+ select: { schoolId: true },
68
+ });
69
+ if (!dept || dept.schoolId !== input.schoolId) {
70
+ throw new TRPCError({
71
+ code: "BAD_REQUEST",
72
+ message: "Department does not belong to this institution",
73
+ });
74
+ }
75
+ }
76
+ return createTag(input);
77
+ }
78
+
79
+ export async function updateTagRecord(
80
+ userId: string,
81
+ input: {
82
+ tagId: string;
83
+ name?: string;
84
+ color?: string;
85
+ departmentId?: string | null;
86
+ }
87
+ ) {
88
+ const tag = await getTag(input.tagId);
89
+ if (!tag) {
90
+ throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
91
+ }
92
+ await requireAdmin(tag.schoolId, userId);
93
+ if (input.departmentId) {
94
+ const dept = await prisma.department.findUnique({
95
+ where: { id: input.departmentId },
96
+ select: { schoolId: true },
97
+ });
98
+ if (!dept || dept.schoolId !== tag.schoolId) {
99
+ throw new TRPCError({
100
+ code: "BAD_REQUEST",
101
+ message: "Department does not belong to this institution",
102
+ });
103
+ }
104
+ }
105
+ return updateTag(input.tagId, {
106
+ name: input.name,
107
+ color: input.color,
108
+ departmentId: input.departmentId,
109
+ });
110
+ }
111
+
112
+ export async function deleteTagRecord(userId: string, tagId: string) {
113
+ const tag = await getTag(tagId);
114
+ if (!tag) {
115
+ throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
116
+ }
117
+ await requireAdmin(tag.schoolId, userId);
118
+ await deleteTag(tagId);
119
+ return { success: true };
120
+ }
121
+
122
+ async function ensureTargetInScope(
123
+ schoolId: string,
124
+ target: TagTarget,
125
+ targetId: string
126
+ ) {
127
+ switch (target) {
128
+ case "USER": {
129
+ const inSchool = await prisma.schoolMembership.findFirst({
130
+ where: { schoolId, userId: targetId, status: "ACTIVE" },
131
+ select: { id: true },
132
+ });
133
+ if (!inSchool) {
134
+ throw new TRPCError({
135
+ code: "BAD_REQUEST",
136
+ message: "User is not in this institution",
137
+ });
138
+ }
139
+ return;
140
+ }
141
+ case "CLASS": {
142
+ const inSchool = await prisma.class.findFirst({
143
+ where: { id: targetId, schoolId },
144
+ select: { id: true },
145
+ });
146
+ if (!inSchool) {
147
+ throw new TRPCError({
148
+ code: "BAD_REQUEST",
149
+ message: "Class is not in this institution",
150
+ });
151
+ }
152
+ return;
153
+ }
154
+ case "RUBRIC": {
155
+ const inSchool = await prisma.markScheme.findFirst({
156
+ where: {
157
+ id: targetId,
158
+ OR: [
159
+ { class: { schoolId } },
160
+ { department: { schoolId } },
161
+ ],
162
+ },
163
+ select: { id: true },
164
+ });
165
+ if (!inSchool) {
166
+ throw new TRPCError({
167
+ code: "BAD_REQUEST",
168
+ message: "Rubric is not in this institution",
169
+ });
170
+ }
171
+ return;
172
+ }
173
+ case "BOUNDARY": {
174
+ const inSchool = await prisma.gradingBoundary.findFirst({
175
+ where: {
176
+ id: targetId,
177
+ OR: [
178
+ { class: { schoolId } },
179
+ { department: { schoolId } },
180
+ ],
181
+ },
182
+ select: { id: true },
183
+ });
184
+ if (!inSchool) {
185
+ throw new TRPCError({
186
+ code: "BAD_REQUEST",
187
+ message: "Boundary is not in this institution",
188
+ });
189
+ }
190
+ return;
191
+ }
192
+ }
193
+ }
194
+
195
+ export async function attachTag(
196
+ userId: string,
197
+ input: { tagId: string; target: TagTarget; targetId: string }
198
+ ) {
199
+ const tag = await getTag(input.tagId);
200
+ if (!tag) {
201
+ throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
202
+ }
203
+ await requireMember(tag.schoolId, userId);
204
+ await ensureTargetInScope(tag.schoolId, input.target, input.targetId);
205
+ return attachTagToTarget(input);
206
+ }
207
+
208
+ export async function detachTag(
209
+ userId: string,
210
+ input: { tagId: string; target: TagTarget; targetId: string }
211
+ ) {
212
+ const tag = await getTag(input.tagId);
213
+ if (!tag) {
214
+ throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
215
+ }
216
+ await requireMember(tag.schoolId, userId);
217
+ return detachTagFromTarget(input);
218
+ }
219
+
220
+ export async function getAttachedTags(
221
+ userId: string,
222
+ input: { target: TagTarget; targetId: string }
223
+ ) {
224
+ const tags = await listTagsAttachedTo(input.target, input.targetId);
225
+ if (tags[0]) {
226
+ await requireMember(tags[0].schoolId, userId);
227
+ }
228
+ return tags;
229
+ }
@@ -26,7 +26,6 @@ export async function ensureAIUserExists(): Promise<void> {
26
26
  email: 'ai@studious-lms.com',
27
27
  password: 'ai-system-user', // Not used for login
28
28
  verified: true,
29
- role: 'NONE', // Special role for AI
30
29
  profile: {
31
30
  create: {
32
31
  displayName: 'AI Assistant',