@studious-lms/server 1.2.45 → 1.2.46

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 (231) hide show
  1. package/.env.example +45 -0
  2. package/.env.test.example +37 -0
  3. package/README.md +34 -7
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/clover.xml +12110 -0
  7. package/coverage/coverage-final.json +44 -0
  8. package/coverage/favicon.png +0 -0
  9. package/coverage/index.html +221 -0
  10. package/coverage/prettify.css +1 -0
  11. package/coverage/prettify.js +2 -0
  12. package/coverage/server/index.html +116 -0
  13. package/coverage/server/src/exportType.ts.html +109 -0
  14. package/coverage/server/src/index.html +161 -0
  15. package/coverage/server/src/index.ts.html +1702 -0
  16. package/coverage/server/src/instrument.ts.html +130 -0
  17. package/coverage/server/src/lib/config/env.ts.html +448 -0
  18. package/coverage/server/src/lib/config/index.html +116 -0
  19. package/coverage/server/src/lib/fileUpload.ts.html +1138 -0
  20. package/coverage/server/src/lib/googleCloudStorage.ts.html +334 -0
  21. package/coverage/server/src/lib/index.html +206 -0
  22. package/coverage/server/src/lib/jsonConversion.ts.html +2323 -0
  23. package/coverage/server/src/lib/jsonStyles.ts.html +193 -0
  24. package/coverage/server/src/lib/notificationHandler.ts.html +193 -0
  25. package/coverage/server/src/lib/pusher.ts.html +121 -0
  26. package/coverage/server/src/lib/thumbnailGenerator.ts.html +592 -0
  27. package/coverage/server/src/middleware/auth.ts.html +646 -0
  28. package/coverage/server/src/middleware/index.html +146 -0
  29. package/coverage/server/src/middleware/logging.ts.html +244 -0
  30. package/coverage/server/src/middleware/security.ts.html +271 -0
  31. package/coverage/server/src/routers/_app.ts.html +232 -0
  32. package/coverage/server/src/routers/agenda.ts.html +319 -0
  33. package/coverage/server/src/routers/announcement.ts.html +3481 -0
  34. package/coverage/server/src/routers/assignment.ts.html +7633 -0
  35. package/coverage/server/src/routers/attendance.ts.html +1030 -0
  36. package/coverage/server/src/routers/auth.ts.html +1081 -0
  37. package/coverage/server/src/routers/class.ts.html +3535 -0
  38. package/coverage/server/src/routers/comment.ts.html +991 -0
  39. package/coverage/server/src/routers/conversation.ts.html +982 -0
  40. package/coverage/server/src/routers/event.ts.html +1609 -0
  41. package/coverage/server/src/routers/file.ts.html +1144 -0
  42. package/coverage/server/src/routers/folder.ts.html +2797 -0
  43. package/coverage/server/src/routers/index.html +386 -0
  44. package/coverage/server/src/routers/labChat.ts.html +3073 -0
  45. package/coverage/server/src/routers/marketing.ts.html +340 -0
  46. package/coverage/server/src/routers/message.ts.html +1912 -0
  47. package/coverage/server/src/routers/notifications.ts.html +364 -0
  48. package/coverage/server/src/routers/section.ts.html +1120 -0
  49. package/coverage/server/src/routers/user.ts.html +862 -0
  50. package/coverage/server/src/routers/worksheet.ts.html +1729 -0
  51. package/coverage/server/src/trpc.ts.html +397 -0
  52. package/coverage/server/src/types/index.html +116 -0
  53. package/coverage/server/src/types/trpc.ts.html +127 -0
  54. package/coverage/server/src/utils/aiUser.ts.html +280 -0
  55. package/coverage/server/src/utils/email.ts.html +121 -0
  56. package/coverage/server/src/utils/generateInviteCode.ts.html +106 -0
  57. package/coverage/server/src/utils/index.html +206 -0
  58. package/coverage/server/src/utils/inference.ts.html +709 -0
  59. package/coverage/server/src/utils/logger.ts.html +664 -0
  60. package/coverage/server/src/utils/prismaErrorHandler.ts.html +907 -0
  61. package/coverage/server/src/utils/prismaWrapper.ts.html +355 -0
  62. package/coverage/server/vitest.config.ts.html +196 -0
  63. package/coverage/sort-arrow-sprite.png +0 -0
  64. package/coverage/sorter.js +210 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +83 -52
  67. package/dist/index.js.map +1 -1
  68. package/dist/instrument.js +15 -8
  69. package/dist/instrument.js.map +1 -1
  70. package/dist/lib/config/env.d.ts +169 -0
  71. package/dist/lib/config/env.d.ts.map +1 -0
  72. package/dist/lib/config/env.js +115 -0
  73. package/dist/lib/config/env.js.map +1 -0
  74. package/dist/lib/fileUpload.d.ts.map +1 -1
  75. package/dist/lib/fileUpload.js +5 -4
  76. package/dist/lib/fileUpload.js.map +1 -1
  77. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  78. package/dist/lib/googleCloudStorage.js +7 -8
  79. package/dist/lib/googleCloudStorage.js.map +1 -1
  80. package/dist/lib/jsonConversion.d.ts.map +1 -1
  81. package/dist/lib/jsonConversion.js +14 -16
  82. package/dist/lib/jsonConversion.js.map +1 -1
  83. package/dist/lib/notificationHandler.d.ts +2 -2
  84. package/dist/lib/prisma.d.ts +2 -2
  85. package/dist/lib/prisma.d.ts.map +1 -1
  86. package/dist/lib/prisma.js +22 -3
  87. package/dist/lib/prisma.js.map +1 -1
  88. package/dist/lib/pusher.d.ts.map +1 -1
  89. package/dist/lib/pusher.js +8 -7
  90. package/dist/lib/pusher.js.map +1 -1
  91. package/dist/middleware/auth.d.ts.map +1 -1
  92. package/dist/middleware/auth.js +6 -5
  93. package/dist/middleware/auth.js.map +1 -1
  94. package/dist/middleware/security.d.ts +5 -0
  95. package/dist/middleware/security.d.ts.map +1 -0
  96. package/dist/middleware/security.js +77 -0
  97. package/dist/middleware/security.js.map +1 -0
  98. package/dist/routers/_app.d.ts +294 -98
  99. package/dist/routers/_app.d.ts.map +1 -1
  100. package/dist/routers/_app.js +4 -2
  101. package/dist/routers/_app.js.map +1 -1
  102. package/dist/routers/agenda.d.ts.map +1 -1
  103. package/dist/routers/agenda.js +12 -9
  104. package/dist/routers/agenda.js.map +1 -1
  105. package/dist/routers/announcement.d.ts +8 -0
  106. package/dist/routers/announcement.d.ts.map +1 -1
  107. package/dist/routers/announcement.js +6 -4
  108. package/dist/routers/announcement.js.map +1 -1
  109. package/dist/routers/assignment.d.ts +7 -4
  110. package/dist/routers/assignment.d.ts.map +1 -1
  111. package/dist/routers/assignment.js +35 -18
  112. package/dist/routers/assignment.js.map +1 -1
  113. package/dist/routers/attendance.d.ts +1 -0
  114. package/dist/routers/attendance.d.ts.map +1 -1
  115. package/dist/routers/attendance.js +4 -4
  116. package/dist/routers/attendance.js.map +1 -1
  117. package/dist/routers/auth.d.ts +20 -0
  118. package/dist/routers/auth.d.ts.map +1 -1
  119. package/dist/routers/auth.js +132 -15
  120. package/dist/routers/auth.js.map +1 -1
  121. package/dist/routers/class.d.ts +10 -0
  122. package/dist/routers/class.d.ts.map +1 -1
  123. package/dist/routers/class.js +49 -5
  124. package/dist/routers/class.js.map +1 -1
  125. package/dist/routers/comment.d.ts +2 -0
  126. package/dist/routers/comment.d.ts.map +1 -1
  127. package/dist/routers/conversation.d.ts +1 -0
  128. package/dist/routers/conversation.d.ts.map +1 -1
  129. package/dist/routers/conversation.js +46 -31
  130. package/dist/routers/conversation.js.map +1 -1
  131. package/dist/routers/file.d.ts.map +1 -1
  132. package/dist/routers/file.js +30 -7
  133. package/dist/routers/file.js.map +1 -1
  134. package/dist/routers/labChat.d.ts +1 -0
  135. package/dist/routers/labChat.d.ts.map +1 -1
  136. package/dist/routers/labChat.js +2 -3
  137. package/dist/routers/labChat.js.map +1 -1
  138. package/dist/routers/marketing.d.ts +1 -1
  139. package/dist/routers/newtonChat.d.ts +55 -0
  140. package/dist/routers/newtonChat.d.ts.map +1 -0
  141. package/dist/routers/newtonChat.js +438 -0
  142. package/dist/routers/newtonChat.js.map +1 -0
  143. package/dist/routers/notifications.d.ts +4 -4
  144. package/dist/routers/section.d.ts +9 -4
  145. package/dist/routers/section.d.ts.map +1 -1
  146. package/dist/routers/section.js +8 -8
  147. package/dist/routers/section.js.map +1 -1
  148. package/dist/routers/user.d.ts.map +1 -1
  149. package/dist/routers/user.js +5 -4
  150. package/dist/routers/user.js.map +1 -1
  151. package/dist/routers/worksheet.d.ts +30 -36
  152. package/dist/routers/worksheet.d.ts.map +1 -1
  153. package/dist/routers/worksheet.js +11 -33
  154. package/dist/routers/worksheet.js.map +1 -1
  155. package/dist/seedDatabase.d.ts +1 -1
  156. package/dist/seedDatabase.js +275 -284
  157. package/dist/seedDatabase.js.map +1 -1
  158. package/dist/server/pipelines/aiLabChat.d.ts +10 -0
  159. package/dist/server/pipelines/aiLabChat.d.ts.map +1 -0
  160. package/dist/server/pipelines/aiLabChat.js +83 -0
  161. package/dist/server/pipelines/aiLabChat.js.map +1 -0
  162. package/dist/server/pipelines/gradeWorksheet.d.ts +2 -0
  163. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -0
  164. package/dist/server/pipelines/gradeWorksheet.js +138 -0
  165. package/dist/server/pipelines/gradeWorksheet.js.map +1 -0
  166. package/dist/trpc.d.ts.map +1 -1
  167. package/dist/trpc.js +2 -2
  168. package/dist/trpc.js.map +1 -1
  169. package/dist/utils/email.d.ts +9 -1
  170. package/dist/utils/email.d.ts.map +1 -1
  171. package/dist/utils/email.js +20 -5
  172. package/dist/utils/email.js.map +1 -1
  173. package/dist/utils/inference.d.ts +3 -0
  174. package/dist/utils/inference.d.ts.map +1 -1
  175. package/dist/utils/inference.js +41 -7
  176. package/dist/utils/inference.js.map +1 -1
  177. package/dist/utils/logger.d.ts.map +1 -1
  178. package/dist/utils/logger.js +3 -3
  179. package/dist/utils/logger.js.map +1 -1
  180. package/docker-compose.yml +14 -0
  181. package/package.json +13 -4
  182. package/prisma/schema.prisma +32 -5
  183. package/scripts/test-pre-push.ts +14 -0
  184. package/src/index.ts +98 -54
  185. package/src/instrument.ts +13 -6
  186. package/src/lib/config/env.ts +126 -0
  187. package/src/lib/fileUpload.ts +3 -2
  188. package/src/lib/googleCloudStorage.ts +6 -6
  189. package/src/lib/jsonConversion.ts +12 -14
  190. package/src/lib/prisma.ts +23 -2
  191. package/src/lib/pusher.ts +6 -5
  192. package/src/middleware/auth.ts +4 -3
  193. package/src/middleware/security.ts +80 -0
  194. package/src/routers/_app.ts +2 -0
  195. package/src/routers/agenda.ts +10 -7
  196. package/src/routers/announcement.ts +4 -2
  197. package/src/routers/assignment.ts +58 -40
  198. package/src/routers/attendance.ts +2 -2
  199. package/src/routers/auth.ts +143 -14
  200. package/src/routers/class.ts +52 -3
  201. package/src/routers/conversation.ts +49 -29
  202. package/src/routers/file.ts +29 -5
  203. package/src/routers/labChat.ts +0 -1
  204. package/src/routers/newtonChat.ts +520 -0
  205. package/src/routers/section.ts +6 -6
  206. package/src/routers/user.ts +3 -2
  207. package/src/routers/worksheet.ts +9 -37
  208. package/src/seedDatabase.ts +290 -283
  209. package/src/server/pipelines/aiLabChat.ts +92 -0
  210. package/src/server/pipelines/gradeWorksheet.ts +152 -0
  211. package/src/trpc.ts +2 -0
  212. package/src/utils/email.ts +30 -3
  213. package/src/utils/inference.ts +50 -5
  214. package/src/utils/logger.ts +2 -1
  215. package/tests/announcement.test.ts +164 -0
  216. package/tests/assignment.test.ts +296 -0
  217. package/tests/attendance.test.ts +168 -0
  218. package/tests/auth.test.ts +33 -10
  219. package/tests/class.test.ts +34 -9
  220. package/tests/event.test.ts +228 -0
  221. package/tests/section.test.ts +216 -0
  222. package/tests/setup.ts +70 -16
  223. package/tests/user.test.ts +158 -0
  224. package/vitest.config.ts +26 -0
  225. package/API_SPECIFICATION.md +0 -1597
  226. package/BASE64_REMOVAL_SUMMARY.md +0 -164
  227. package/CHAT_API_SPEC.md +0 -579
  228. package/LAB_CHAT_API_SPEC.md +0 -518
  229. package/dist/routers/school.d.ts +0 -208
  230. package/dist/routers/school.d.ts.map +0 -1
  231. package/dist/routers/school.js +0 -483
@@ -0,0 +1,438 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="18a2ffea-1d1f-50ee-b65a-c24303a7a9d9")}catch(e){}}();
3
+ import { z } from 'zod';
4
+ import { createTRPCRouter, protectedProcedure } from '../trpc.js';
5
+ import { prisma } from '../lib/prisma.js';
6
+ import { pusher } from '../lib/pusher.js';
7
+ import { TRPCError } from '@trpc/server';
8
+ import { inferenceClient, openAIClient, sendAIMessage, } from '../utils/inference.js';
9
+ import { logger } from '../utils/logger.js';
10
+ import { isAIUser } from '../utils/aiUser.js';
11
+ export const newtonChatRouter = createTRPCRouter({
12
+ getTutorConversation: protectedProcedure
13
+ .input(z.object({
14
+ assignmentId: z.string(),
15
+ classId: z.string(),
16
+ }))
17
+ .query(async ({ input, ctx }) => {
18
+ const userId = ctx.user.id;
19
+ const { assignmentId, classId } = input;
20
+ // Verify user is a student in the class
21
+ const classMembership = await prisma.class.findFirst({
22
+ where: {
23
+ id: classId,
24
+ students: {
25
+ some: {
26
+ id: userId,
27
+ },
28
+ },
29
+ },
30
+ });
31
+ if (!classMembership) {
32
+ throw new TRPCError({
33
+ code: 'FORBIDDEN',
34
+ message: 'Not a student in this class',
35
+ });
36
+ }
37
+ // Find or create submission for this student and assignment
38
+ const submission = await prisma.submission.findFirst({
39
+ where: {
40
+ assignmentId,
41
+ studentId: userId,
42
+ },
43
+ });
44
+ if (!submission) {
45
+ throw new TRPCError({
46
+ code: 'NOT_FOUND',
47
+ message: 'Submission not found. Please create a submission first.',
48
+ });
49
+ }
50
+ // Find the latest NewtonChat for this submission, or create a new one
51
+ const result = await prisma.$transaction(async (tx) => {
52
+ // Get the latest NewtonChat for this submission
53
+ const existingNewtonChat = await tx.newtonChat.findFirst({
54
+ where: {
55
+ submissionId: submission.id,
56
+ },
57
+ include: {
58
+ conversation: {
59
+ include: {
60
+ members: {
61
+ where: {
62
+ userId,
63
+ },
64
+ },
65
+ },
66
+ },
67
+ },
68
+ orderBy: {
69
+ createdAt: 'desc',
70
+ },
71
+ });
72
+ // If exists and user is already a member, return it
73
+ if (existingNewtonChat && existingNewtonChat.conversation.members.length > 0) {
74
+ return existingNewtonChat;
75
+ }
76
+ // If exists but user is not a member, add them
77
+ if (existingNewtonChat) {
78
+ await tx.conversationMember.create({
79
+ data: {
80
+ userId,
81
+ conversationId: existingNewtonChat.conversationId,
82
+ role: 'MEMBER',
83
+ },
84
+ });
85
+ return existingNewtonChat;
86
+ }
87
+ // Create new NewtonChat with associated conversation
88
+ const conversation = await tx.conversation.create({
89
+ data: {
90
+ type: 'DM',
91
+ name: 'Session with Newton Tutor',
92
+ displayInChat: false, // Newton chats don't show in regular chat list
93
+ },
94
+ });
95
+ // Add student to the conversation
96
+ await tx.conversationMember.create({
97
+ data: {
98
+ userId,
99
+ conversationId: conversation.id,
100
+ role: 'MEMBER',
101
+ },
102
+ });
103
+ // Create the NewtonChat
104
+ const newtonChat = await tx.newtonChat.create({
105
+ data: {
106
+ submissionId: submission.id,
107
+ conversationId: conversation.id,
108
+ title: 'Session with Newton Tutor',
109
+ },
110
+ });
111
+ return newtonChat;
112
+ });
113
+ // Generate AI introduction message in parallel (don't await - fire and forget)
114
+ generateAndSendNewtonIntroduction(result.id, result.conversationId, submission.id).catch(error => {
115
+ logger.error('Failed to generate AI introduction:', { error, newtonChatId: result.id });
116
+ });
117
+ return {
118
+ conversationId: result.conversationId,
119
+ newtonChatId: result.id,
120
+ };
121
+ }),
122
+ postToNewtonChat: protectedProcedure
123
+ .input(z.object({
124
+ newtonChatId: z.string(),
125
+ content: z.string().min(1).max(4000),
126
+ mentionedUserIds: z.array(z.string()).optional(),
127
+ }))
128
+ .mutation(async ({ input, ctx }) => {
129
+ const userId = ctx.user.id;
130
+ const { newtonChatId, content, mentionedUserIds = [] } = input;
131
+ // Get newton chat and verify user is a member
132
+ const newtonChat = await prisma.newtonChat.findFirst({
133
+ where: {
134
+ id: newtonChatId,
135
+ conversation: {
136
+ members: {
137
+ some: {
138
+ userId,
139
+ },
140
+ },
141
+ },
142
+ },
143
+ include: {
144
+ conversation: {
145
+ select: {
146
+ id: true,
147
+ },
148
+ },
149
+ submission: {
150
+ include: {
151
+ assignment: {
152
+ select: {
153
+ id: true,
154
+ title: true,
155
+ instructions: true,
156
+ class: {
157
+ select: {
158
+ subject: true,
159
+ },
160
+ },
161
+ },
162
+ },
163
+ },
164
+ },
165
+ },
166
+ });
167
+ if (!newtonChat) {
168
+ throw new TRPCError({
169
+ code: 'FORBIDDEN',
170
+ message: 'Newton chat not found or access denied',
171
+ });
172
+ }
173
+ // Verify mentioned users are members of the conversation
174
+ if (mentionedUserIds.length > 0) {
175
+ const mentionedMemberships = await prisma.conversationMember.findMany({
176
+ where: {
177
+ conversationId: newtonChat.conversationId,
178
+ userId: { in: mentionedUserIds },
179
+ },
180
+ });
181
+ if (mentionedMemberships.length !== mentionedUserIds.length) {
182
+ throw new TRPCError({
183
+ code: 'BAD_REQUEST',
184
+ message: 'Some mentioned users are not members of this conversation',
185
+ });
186
+ }
187
+ }
188
+ // Create message and mentions
189
+ const result = await prisma.$transaction(async (tx) => {
190
+ const message = await tx.message.create({
191
+ data: {
192
+ content,
193
+ senderId: userId,
194
+ conversationId: newtonChat.conversationId,
195
+ },
196
+ include: {
197
+ sender: {
198
+ select: {
199
+ id: true,
200
+ username: true,
201
+ profile: {
202
+ select: {
203
+ displayName: true,
204
+ profilePicture: true,
205
+ },
206
+ },
207
+ },
208
+ },
209
+ },
210
+ });
211
+ // Create mentions
212
+ if (mentionedUserIds.length > 0) {
213
+ await tx.mention.createMany({
214
+ data: mentionedUserIds.map((mentionedUserId) => ({
215
+ messageId: message.id,
216
+ userId: mentionedUserId,
217
+ })),
218
+ });
219
+ }
220
+ // Update newton chat timestamp
221
+ await tx.newtonChat.update({
222
+ where: { id: newtonChatId },
223
+ data: { updatedAt: new Date() },
224
+ });
225
+ return message;
226
+ });
227
+ // Broadcast to Pusher channel (same format as regular chat)
228
+ try {
229
+ await pusher.trigger(`conversation-${newtonChat.conversationId}`, 'new-message', {
230
+ id: result.id,
231
+ content: result.content,
232
+ senderId: result.senderId,
233
+ conversationId: result.conversationId,
234
+ createdAt: result.createdAt,
235
+ sender: result.sender,
236
+ mentionedUserIds,
237
+ });
238
+ }
239
+ catch (error) {
240
+ console.error('Failed to broadcast newton chat message:', error);
241
+ // Don't fail the request if Pusher fails
242
+ }
243
+ // Generate AI response in parallel (don't await - fire and forget)
244
+ if (!isAIUser(userId)) {
245
+ // Run AI response generation in background
246
+ generateAndSendNewtonResponse(newtonChatId, content, newtonChat.conversationId, newtonChat.submission).catch(error => {
247
+ logger.error('Failed to generate AI response:', { error });
248
+ });
249
+ }
250
+ return {
251
+ id: result.id,
252
+ content: result.content,
253
+ senderId: result.senderId,
254
+ conversationId: result.conversationId,
255
+ createdAt: result.createdAt,
256
+ sender: result.sender,
257
+ mentionedUserIds,
258
+ };
259
+ }),
260
+ });
261
+ /**
262
+ * Generate and send AI introduction for Newton chat
263
+ */
264
+ async function generateAndSendNewtonIntroduction(newtonChatId, conversationId, submissionId) {
265
+ try {
266
+ // Get submission details for context
267
+ const submission = await prisma.submission.findUnique({
268
+ where: { id: submissionId },
269
+ include: {
270
+ assignment: {
271
+ select: {
272
+ title: true,
273
+ instructions: true,
274
+ class: {
275
+ select: {
276
+ subject: true,
277
+ name: true,
278
+ },
279
+ },
280
+ },
281
+ },
282
+ attachments: {
283
+ select: {
284
+ id: true,
285
+ name: true,
286
+ type: true,
287
+ },
288
+ },
289
+ },
290
+ });
291
+ if (!submission) {
292
+ throw new Error('Submission not found');
293
+ }
294
+ const systemPrompt = `You are Newton, an AI tutor helping a student with their assignment submission.
295
+
296
+ Assignment: ${submission.assignment.title}
297
+ Subject: ${submission.assignment.class.subject}
298
+ Instructions: ${submission.assignment.instructions || 'No specific instructions provided'}
299
+
300
+ Your role:
301
+ - Help the student understand concepts related to their assignment
302
+ - Provide guidance and explanations without giving away direct answers
303
+ - Encourage learning and critical thinking
304
+ - Be supportive and encouraging
305
+ - Use clear, educational language appropriate for the subject
306
+
307
+ Do not use markdown formatting in your responses - use plain text only.`;
308
+ const completion = await inferenceClient.chat.completions.create({
309
+ model: 'command-a-03-2025',
310
+ messages: [
311
+ { role: 'system', content: systemPrompt },
312
+ {
313
+ role: 'user',
314
+ content: 'Please introduce yourself to the student. Explain that you are Newton, their AI tutor, and you are here to help them with their assignment. Ask them what they would like help with.'
315
+ },
316
+ ],
317
+ max_tokens: 300,
318
+ temperature: 0.8,
319
+ });
320
+ const response = completion.choices[0]?.message?.content;
321
+ if (!response) {
322
+ throw new Error('No response generated from inference API');
323
+ }
324
+ // Send AI introduction using centralized sender
325
+ await sendAIMessage(response, conversationId, {
326
+ subject: submission.assignment.class.subject || 'Assignment',
327
+ });
328
+ logger.info('AI Introduction sent', { newtonChatId, conversationId });
329
+ }
330
+ catch (error) {
331
+ logger.error('Failed to generate AI introduction:', { error, newtonChatId });
332
+ // Send fallback introduction
333
+ try {
334
+ const fallbackIntro = `Hello! I'm Newton, your AI tutor. I'm here to help you with your assignment. I can answer questions, explain concepts, and guide you through your work. What would you like help with today?`;
335
+ await sendAIMessage(fallbackIntro, conversationId, {
336
+ subject: 'Assignment',
337
+ });
338
+ logger.info('Fallback AI introduction sent', { newtonChatId });
339
+ }
340
+ catch (fallbackError) {
341
+ logger.error('Failed to send fallback AI introduction:', { error: fallbackError, newtonChatId });
342
+ }
343
+ }
344
+ }
345
+ /**
346
+ * Generate and send AI response to student message
347
+ */
348
+ async function generateAndSendNewtonResponse(newtonChatId, studentMessage, conversationId, submission) {
349
+ try {
350
+ // Get recent conversation history
351
+ const recentMessages = await prisma.message.findMany({
352
+ where: {
353
+ conversationId,
354
+ },
355
+ include: {
356
+ sender: {
357
+ select: {
358
+ id: true,
359
+ username: true,
360
+ profile: {
361
+ select: {
362
+ displayName: true,
363
+ },
364
+ },
365
+ },
366
+ },
367
+ },
368
+ orderBy: {
369
+ createdAt: 'desc',
370
+ },
371
+ take: 10, // Last 10 messages for context
372
+ });
373
+ const systemPrompt = `You are Newton, an AI tutor helping a student with their assignment submission.
374
+
375
+ Assignment: ${submission.assignment.title}
376
+ Subject: ${submission.assignment.class.subject || 'General'}
377
+ Instructions: ${submission.assignment.instructions || 'No specific instructions provided'}
378
+
379
+ Your role:
380
+ - Help the student understand concepts related to their assignment
381
+ - Provide guidance and explanations without giving away direct answers
382
+ - Encourage learning and critical thinking
383
+ - Be supportive and encouraging
384
+ - Use clear, educational language appropriate for the subject
385
+ - If the student asks for direct answers, guide them to think through the problem instead
386
+ - Break down complex concepts into simpler parts
387
+ - Use examples and analogies when helpful
388
+
389
+ IMPORTANT:
390
+ - Do not use markdown formatting in your responses - use plain text only
391
+ - Keep responses conversational and educational
392
+ - Focus on helping the student learn, not just completing the assignment`;
393
+ const messages = [
394
+ { role: 'system', content: systemPrompt },
395
+ ];
396
+ // Add recent conversation history
397
+ recentMessages.reverse().forEach(msg => {
398
+ const role = isAIUser(msg.senderId) ? 'assistant' : 'user';
399
+ const senderName = msg.sender?.profile?.displayName || msg.sender?.username || 'Student';
400
+ const content = isAIUser(msg.senderId) ? msg.content : `${senderName}: ${msg.content}`;
401
+ messages.push({
402
+ role: role,
403
+ content,
404
+ });
405
+ });
406
+ // Add the new student message
407
+ messages.push({
408
+ role: 'user',
409
+ content: `Student: ${studentMessage}`,
410
+ });
411
+ const completion = await openAIClient.chat.completions.create({
412
+ model: 'gpt-5-nano',
413
+ messages,
414
+ temperature: 0.7,
415
+ });
416
+ const response = completion.choices[0]?.message?.content;
417
+ if (!response) {
418
+ throw new Error('No response generated from inference API');
419
+ }
420
+ // Send the text response to the conversation
421
+ await sendAIMessage(response, conversationId, {
422
+ subject: submission.assignment.class.subject || 'Assignment',
423
+ });
424
+ logger.info('AI response sent', { newtonChatId, conversationId });
425
+ }
426
+ catch (error) {
427
+ logger.error('Failed to generate AI response:', {
428
+ error: error instanceof Error ? {
429
+ message: error.message,
430
+ stack: error.stack,
431
+ name: error.name
432
+ } : error,
433
+ newtonChatId
434
+ });
435
+ }
436
+ }
437
+ //# sourceMappingURL=newtonChat.js.map
438
+ //# debugId=18a2ffea-1d1f-50ee-b65a-c24303a7a9d9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"newtonChat.js","sources":["routers/newtonChat.ts"],"sourceRoot":"/","sourcesContent":["import { z } from 'zod';\nimport { createTRPCRouter, protectedProcedure } from '../trpc.js';\nimport { prisma } from '../lib/prisma.js';\nimport { pusher } from '../lib/pusher.js';\nimport { TRPCError } from '@trpc/server';\nimport { \n inferenceClient,\n openAIClient,\n sendAIMessage,\n} from '../utils/inference.js';\nimport { logger } from '../utils/logger.js';\nimport { isAIUser } from '../utils/aiUser.js';\n\nexport const newtonChatRouter = createTRPCRouter({\n getTutorConversation: protectedProcedure\n .input(\n z.object({\n assignmentId: z.string(),\n classId: z.string(),\n })\n )\n .query(async ({ input, ctx }) => {\n const userId = ctx.user!.id;\n const { assignmentId, classId } = input;\n\n // Verify user is a student in the class\n const classMembership = await prisma.class.findFirst({\n where: {\n id: classId,\n students: {\n some: {\n id: userId,\n },\n },\n },\n });\n\n if (!classMembership) {\n throw new TRPCError({\n code: 'FORBIDDEN',\n message: 'Not a student in this class',\n });\n }\n\n // Find or create submission for this student and assignment\n const submission = await prisma.submission.findFirst({\n where: {\n assignmentId,\n studentId: userId,\n },\n });\n\n if (!submission) {\n throw new TRPCError({\n code: 'NOT_FOUND',\n message: 'Submission not found. Please create a submission first.',\n });\n }\n\n // Find the latest NewtonChat for this submission, or create a new one\n const result = await prisma.$transaction(async (tx) => {\n // Get the latest NewtonChat for this submission\n const existingNewtonChat = await tx.newtonChat.findFirst({\n where: {\n submissionId: submission.id,\n },\n include: {\n conversation: {\n include: {\n members: {\n where: {\n userId,\n },\n },\n },\n },\n },\n orderBy: {\n createdAt: 'desc',\n },\n });\n\n // If exists and user is already a member, return it\n if (existingNewtonChat && existingNewtonChat.conversation.members.length > 0) {\n return existingNewtonChat;\n }\n\n // If exists but user is not a member, add them\n if (existingNewtonChat) {\n await tx.conversationMember.create({\n data: {\n userId,\n conversationId: existingNewtonChat.conversationId,\n role: 'MEMBER',\n },\n });\n\n return existingNewtonChat;\n }\n\n // Create new NewtonChat with associated conversation\n const conversation = await tx.conversation.create({\n data: {\n type: 'DM',\n name: 'Session with Newton Tutor',\n displayInChat: false, // Newton chats don't show in regular chat list\n },\n });\n\n // Add student to the conversation\n await tx.conversationMember.create({\n data: {\n userId,\n conversationId: conversation.id,\n role: 'MEMBER',\n },\n });\n\n // Create the NewtonChat\n const newtonChat = await tx.newtonChat.create({\n data: {\n submissionId: submission.id,\n conversationId: conversation.id,\n title: 'Session with Newton Tutor',\n },\n });\n\n return newtonChat;\n });\n\n // Generate AI introduction message in parallel (don't await - fire and forget)\n generateAndSendNewtonIntroduction(\n result.id,\n result.conversationId,\n submission.id\n ).catch(error => {\n logger.error('Failed to generate AI introduction:', { error, newtonChatId: result.id });\n });\n\n return {\n conversationId: result.conversationId,\n newtonChatId: result.id,\n };\n }),\n\n postToNewtonChat: protectedProcedure\n .input(\n z.object({\n newtonChatId: z.string(),\n content: z.string().min(1).max(4000),\n mentionedUserIds: z.array(z.string()).optional(),\n })\n )\n .mutation(async ({ input, ctx }) => {\n const userId = ctx.user!.id;\n const { newtonChatId, content, mentionedUserIds = [] } = input;\n\n // Get newton chat and verify user is a member\n const newtonChat = await prisma.newtonChat.findFirst({\n where: {\n id: newtonChatId,\n conversation: {\n members: {\n some: {\n userId,\n },\n },\n },\n },\n include: {\n conversation: {\n select: {\n id: true,\n },\n },\n submission: {\n include: {\n assignment: {\n select: {\n id: true,\n title: true,\n instructions: true,\n class: {\n select: {\n subject: true,\n },\n },\n },\n },\n },\n },\n },\n });\n\n if (!newtonChat) {\n throw new TRPCError({\n code: 'FORBIDDEN',\n message: 'Newton chat not found or access denied',\n });\n }\n\n // Verify mentioned users are members of the conversation\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await prisma.conversationMember.findMany({\n where: {\n conversationId: newtonChat.conversationId,\n userId: { in: mentionedUserIds },\n },\n });\n\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: 'BAD_REQUEST',\n message: 'Some mentioned users are not members of this conversation',\n });\n }\n }\n\n // Create message and mentions\n const result = await prisma.$transaction(async (tx) => {\n const message = await tx.message.create({\n data: {\n content,\n senderId: userId,\n conversationId: newtonChat.conversationId,\n },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n },\n });\n\n // Create mentions\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId: message.id,\n userId: mentionedUserId,\n })),\n });\n }\n\n // Update newton chat timestamp\n await tx.newtonChat.update({\n where: { id: newtonChatId },\n data: { updatedAt: new Date() },\n });\n\n return message;\n });\n\n // Broadcast to Pusher channel (same format as regular chat)\n try {\n await pusher.trigger(`conversation-${newtonChat.conversationId}`, 'new-message', {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n createdAt: result.createdAt,\n sender: result.sender,\n mentionedUserIds,\n });\n } catch (error) {\n console.error('Failed to broadcast newton chat message:', error);\n // Don't fail the request if Pusher fails\n }\n\n // Generate AI response in parallel (don't await - fire and forget)\n if (!isAIUser(userId)) {\n // Run AI response generation in background\n generateAndSendNewtonResponse(\n newtonChatId,\n content,\n newtonChat.conversationId,\n newtonChat.submission\n ).catch(error => {\n logger.error('Failed to generate AI response:', { error });\n });\n }\n\n return {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n createdAt: result.createdAt,\n sender: result.sender,\n mentionedUserIds,\n };\n }),\n});\n\n/**\n * Generate and send AI introduction for Newton chat\n */\nasync function generateAndSendNewtonIntroduction(\n newtonChatId: string,\n conversationId: string,\n submissionId: string\n): Promise<void> {\n try {\n // Get submission details for context\n const submission = await prisma.submission.findUnique({\n where: { id: submissionId },\n include: {\n assignment: {\n select: {\n title: true,\n instructions: true,\n class: {\n select: {\n subject: true,\n name: true,\n },\n },\n },\n },\n attachments: {\n select: {\n id: true,\n name: true,\n type: true,\n },\n },\n },\n });\n\n if (!submission) {\n throw new Error('Submission not found');\n }\n\n const systemPrompt = `You are Newton, an AI tutor helping a student with their assignment submission. \n\nAssignment: ${submission.assignment.title}\nSubject: ${submission.assignment.class.subject}\nInstructions: ${submission.assignment.instructions || 'No specific instructions provided'}\n\nYour role:\n- Help the student understand concepts related to their assignment\n- Provide guidance and explanations without giving away direct answers\n- Encourage learning and critical thinking\n- Be supportive and encouraging\n- Use clear, educational language appropriate for the subject\n\nDo not use markdown formatting in your responses - use plain text only.`;\n\n const completion = await inferenceClient.chat.completions.create({\n model: 'command-a-03-2025',\n messages: [\n { role: 'system', content: systemPrompt },\n { \n role: 'user', \n content: 'Please introduce yourself to the student. Explain that you are Newton, their AI tutor, and you are here to help them with their assignment. Ask them what they would like help with.' \n },\n ],\n max_tokens: 300,\n temperature: 0.8,\n });\n\n const response = completion.choices[0]?.message?.content;\n \n if (!response) {\n throw new Error('No response generated from inference API');\n }\n\n // Send AI introduction using centralized sender\n await sendAIMessage(response, conversationId, {\n subject: submission.assignment.class.subject || 'Assignment',\n });\n\n logger.info('AI Introduction sent', { newtonChatId, conversationId });\n\n } catch (error) {\n logger.error('Failed to generate AI introduction:', { error, newtonChatId });\n \n // Send fallback introduction\n try {\n const fallbackIntro = `Hello! I'm Newton, your AI tutor. I'm here to help you with your assignment. I can answer questions, explain concepts, and guide you through your work. What would you like help with today?`;\n \n await sendAIMessage(fallbackIntro, conversationId, {\n subject: 'Assignment',\n });\n\n logger.info('Fallback AI introduction sent', { newtonChatId });\n\n } catch (fallbackError) {\n logger.error('Failed to send fallback AI introduction:', { error: fallbackError, newtonChatId });\n }\n }\n}\n\n/**\n * Generate and send AI response to student message\n */\nasync function generateAndSendNewtonResponse(\n newtonChatId: string,\n studentMessage: string,\n conversationId: string,\n submission: {\n id: string;\n assignment: {\n id: string;\n title: string;\n instructions: string | null;\n class: {\n subject: string | null;\n };\n };\n }\n): Promise<void> {\n try {\n // Get recent conversation history\n const recentMessages = await prisma.message.findMany({\n where: {\n conversationId,\n },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n },\n },\n },\n },\n },\n orderBy: {\n createdAt: 'desc',\n },\n take: 10, // Last 10 messages for context\n });\n\n const systemPrompt = `You are Newton, an AI tutor helping a student with their assignment submission. \n\nAssignment: ${submission.assignment.title}\nSubject: ${submission.assignment.class.subject || 'General'}\nInstructions: ${submission.assignment.instructions || 'No specific instructions provided'}\n\nYour role:\n- Help the student understand concepts related to their assignment\n- Provide guidance and explanations without giving away direct answers\n- Encourage learning and critical thinking\n- Be supportive and encouraging\n- Use clear, educational language appropriate for the subject\n- If the student asks for direct answers, guide them to think through the problem instead\n- Break down complex concepts into simpler parts\n- Use examples and analogies when helpful\n\nIMPORTANT:\n- Do not use markdown formatting in your responses - use plain text only\n- Keep responses conversational and educational\n- Focus on helping the student learn, not just completing the assignment`;\n\n const messages: Array<{ role: 'user' | 'assistant' | 'system'; content: string }> = [\n { role: 'system', content: systemPrompt },\n ];\n\n // Add recent conversation history\n recentMessages.reverse().forEach(msg => {\n const role = isAIUser(msg.senderId) ? 'assistant' : 'user';\n const senderName = msg.sender?.profile?.displayName || msg.sender?.username || 'Student';\n const content = isAIUser(msg.senderId) ? msg.content : `${senderName}: ${msg.content}`;\n \n messages.push({\n role: role as 'user' | 'assistant',\n content,\n });\n });\n\n // Add the new student message\n messages.push({\n role: 'user',\n content: `Student: ${studentMessage}`,\n });\n\n const completion = await openAIClient.chat.completions.create({\n model: 'gpt-5-nano',\n messages,\n temperature: 0.7,\n });\n\n const response = completion.choices[0]?.message?.content;\n \n if (!response) {\n throw new Error('No response generated from inference API');\n }\n\n // Send the text response to the conversation\n await sendAIMessage(response, conversationId, {\n subject: submission.assignment.class.subject || 'Assignment',\n });\n\n logger.info('AI response sent', { newtonChatId, conversationId });\n\n } catch (error) {\n logger.error('Failed to generate AI response:', { \n error: error instanceof Error ? {\n message: error.message,\n stack: error.stack,\n name: error.name\n } : error,\n newtonChatId \n });\n }\n}\n\n\n"],"names":[],"mappings":"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","debug_id":"18a2ffea-1d1f-50ee-b65a-c24303a7a9d9"}
@@ -30,9 +30,9 @@ export declare const notificationRouter: import("@trpc/server").TRPCBuiltRouter<
30
30
  title: string;
31
31
  content: string;
32
32
  createdAt: Date;
33
+ read: boolean;
33
34
  senderId: string | null;
34
35
  receiverId: string;
35
- read: boolean;
36
36
  })[];
37
37
  meta: object;
38
38
  }>;
@@ -52,9 +52,9 @@ export declare const notificationRouter: import("@trpc/server").TRPCBuiltRouter<
52
52
  title: string;
53
53
  content: string;
54
54
  createdAt: Date;
55
+ read: boolean;
55
56
  senderId: string | null;
56
57
  receiverId: string;
57
- read: boolean;
58
58
  }) | null;
59
59
  meta: object;
60
60
  }>;
@@ -69,9 +69,9 @@ export declare const notificationRouter: import("@trpc/server").TRPCBuiltRouter<
69
69
  title: string;
70
70
  content: string;
71
71
  createdAt: Date;
72
+ read: boolean;
72
73
  senderId: string | null;
73
74
  receiverId: string;
74
- read: boolean;
75
75
  };
76
76
  meta: object;
77
77
  }>;
@@ -93,9 +93,9 @@ export declare const notificationRouter: import("@trpc/server").TRPCBuiltRouter<
93
93
  title: string;
94
94
  content: string;
95
95
  createdAt: Date;
96
+ read: boolean;
96
97
  senderId: string | null;
97
98
  receiverId: string;
98
- read: boolean;
99
99
  };
100
100
  meta: object;
101
101
  }>;
@@ -18,8 +18,9 @@ export declare const sectionRouter: import("@trpc/server").TRPCBuiltRouter<{
18
18
  }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
19
19
  create: import("@trpc/server").TRPCMutationProcedure<{
20
20
  input: {
21
- name: string;
21
+ [x: string]: unknown;
22
22
  classId: string;
23
+ name: string;
23
24
  color?: string | undefined;
24
25
  };
25
26
  output: {
@@ -33,6 +34,7 @@ export declare const sectionRouter: import("@trpc/server").TRPCBuiltRouter<{
33
34
  }>;
34
35
  reorder: import("@trpc/server").TRPCMutationProcedure<{
35
36
  input: {
37
+ [x: string]: unknown;
36
38
  classId: string;
37
39
  movedId: string;
38
40
  position: "start" | "end" | "before" | "after";
@@ -49,9 +51,10 @@ export declare const sectionRouter: import("@trpc/server").TRPCBuiltRouter<{
49
51
  }>;
50
52
  update: import("@trpc/server").TRPCMutationProcedure<{
51
53
  input: {
54
+ [x: string]: unknown;
55
+ classId: string;
52
56
  id: string;
53
57
  name: string;
54
- classId: string;
55
58
  color?: string | undefined;
56
59
  };
57
60
  output: {
@@ -65,8 +68,9 @@ export declare const sectionRouter: import("@trpc/server").TRPCBuiltRouter<{
65
68
  }>;
66
69
  reOrder: import("@trpc/server").TRPCMutationProcedure<{
67
70
  input: {
68
- id: string;
71
+ [x: string]: unknown;
69
72
  classId: string;
73
+ id: string;
70
74
  order: number;
71
75
  };
72
76
  output: {
@@ -76,8 +80,9 @@ export declare const sectionRouter: import("@trpc/server").TRPCBuiltRouter<{
76
80
  }>;
77
81
  delete: import("@trpc/server").TRPCMutationProcedure<{
78
82
  input: {
79
- id: string;
83
+ [x: string]: unknown;
80
84
  classId: string;
85
+ id: string;
81
86
  };
82
87
  output: {
83
88
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"section.d.ts","sourceRoot":"/","sources":["routers/section.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuBxB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkUxB,CAAC"}
1
+ {"version":3,"file":"section.d.ts","sourceRoot":"/","sources":["routers/section.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuBxB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkUxB,CAAC"}
@@ -1,7 +1,7 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ac1474d5-7092-5f48-9b93-aa7c2055bdb3")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cc26fa87-90ce-5e16-b19c-83e9fbfd0a40")}catch(e){}}();
3
3
  import { z } from "zod";
4
- import { createTRPCRouter, protectedProcedure } from "../trpc.js";
4
+ import { createTRPCRouter, protectedTeacherProcedure } from "../trpc.js";
5
5
  import { TRPCError } from "@trpc/server";
6
6
  import { prisma } from "../lib/prisma.js";
7
7
  const createSectionSchema = z.object({
@@ -20,7 +20,7 @@ const deleteSectionSchema = z.object({
20
20
  classId: z.string(),
21
21
  });
22
22
  export const sectionRouter = createTRPCRouter({
23
- create: protectedProcedure
23
+ create: protectedTeacherProcedure
24
24
  .input(createSectionSchema)
25
25
  .mutation(async ({ ctx, input }) => {
26
26
  if (!ctx.user) {
@@ -85,7 +85,7 @@ export const sectionRouter = createTRPCRouter({
85
85
  }));
86
86
  return section;
87
87
  }),
88
- reorder: protectedProcedure
88
+ reorder: protectedTeacherProcedure
89
89
  .input(z.object({
90
90
  classId: z.string(),
91
91
  movedId: z.string(), // Section ID
@@ -171,7 +171,7 @@ export const sectionRouter = createTRPCRouter({
171
171
  });
172
172
  return result;
173
173
  }),
174
- update: protectedProcedure
174
+ update: protectedTeacherProcedure
175
175
  .input(updateSectionSchema)
176
176
  .mutation(async ({ ctx, input }) => {
177
177
  if (!ctx.user) {
@@ -208,7 +208,7 @@ export const sectionRouter = createTRPCRouter({
208
208
  });
209
209
  return section;
210
210
  }),
211
- reOrder: protectedProcedure
211
+ reOrder: protectedTeacherProcedure
212
212
  .input(z.object({
213
213
  id: z.string(),
214
214
  classId: z.string(),
@@ -270,7 +270,7 @@ export const sectionRouter = createTRPCRouter({
270
270
  });
271
271
  return { id: input.id };
272
272
  }),
273
- delete: protectedProcedure
273
+ delete: protectedTeacherProcedure
274
274
  .input(deleteSectionSchema)
275
275
  .mutation(async ({ ctx, input }) => {
276
276
  if (!ctx.user) {
@@ -303,4 +303,4 @@ export const sectionRouter = createTRPCRouter({
303
303
  }),
304
304
  });
305
305
  //# sourceMappingURL=section.js.map
306
- //# debugId=ac1474d5-7092-5f48-9b93-aa7c2055bdb3
306
+ //# debugId=cc26fa87-90ce-5e16-b19c-83e9fbfd0a40