@studious-lms/server 1.4.6 → 1.4.9
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.
- package/.github/workflows/npm.yml +28 -0
- package/dist/models/access.d.ts +112 -0
- package/dist/models/access.d.ts.map +1 -0
- package/dist/models/access.js +49 -0
- package/dist/models/access.js.map +1 -0
- package/dist/models/announcement.d.ts +17 -2
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/announcement.js +21 -4
- package/dist/models/announcement.js.map +1 -1
- package/dist/models/assignment.d.ts +69 -37
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/assignment.js +124 -15
- package/dist/models/assignment.js.map +1 -1
- package/dist/models/attendance.d.ts +1 -1
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/attendance.js +6 -5
- package/dist/models/attendance.js.map +1 -1
- package/dist/models/auth.d.ts +5 -5
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +7 -11
- package/dist/models/auth.js.map +1 -1
- package/dist/models/class.d.ts +26 -26
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +7 -11
- package/dist/models/class.js.map +1 -1
- package/dist/models/comment.d.ts +134 -70
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/comment.js +30 -42
- package/dist/models/comment.js.map +1 -1
- package/dist/models/conversation.d.ts +7 -7
- package/dist/models/event.d.ts +52 -3
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/event.js +56 -6
- package/dist/models/event.js.map +1 -1
- package/dist/models/file.d.ts +178 -103
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/file.js +54 -14
- package/dist/models/file.js.map +1 -1
- package/dist/models/folder.d.ts +22 -22
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/folder.js +5 -4
- package/dist/models/folder.js.map +1 -1
- package/dist/models/labChat.d.ts +20 -20
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/labChat.js +8 -10
- package/dist/models/labChat.js.map +1 -1
- package/dist/models/marketing.d.ts +1 -1
- package/dist/models/message.d.ts +6 -6
- package/dist/models/newtonChat.d.ts +5 -5
- package/dist/models/school.d.ts +361 -0
- package/dist/models/school.d.ts.map +1 -0
- package/dist/models/school.js +314 -0
- package/dist/models/school.js.map +1 -0
- package/dist/models/section.d.ts +1 -1
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/section.js +5 -4
- package/dist/models/section.js.map +1 -1
- package/dist/models/user.d.ts +4 -4
- package/dist/models/worksheet.d.ts +163 -141
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +42 -17
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +2 -2
- package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
- package/dist/routers/_app.d.ts +1271 -327
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +4 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/announcement.d.ts +5 -5
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +25 -5
- package/dist/routers/announcement.js.map +1 -1
- package/dist/routers/assignment.d.ts +79 -44
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +32 -12
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/class.d.ts +40 -20
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +3 -2
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/comment.d.ts +5 -5
- package/dist/routers/conversation.d.ts +6 -6
- package/dist/routers/event.d.ts +21 -2
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +2 -2
- package/dist/routers/folder.d.ts +24 -24
- package/dist/routers/labChat.d.ts +7 -7
- package/dist/routers/school.d.ts +384 -0
- package/dist/routers/school.d.ts.map +1 -0
- package/dist/routers/school.js +79 -0
- package/dist/routers/school.js.map +1 -0
- package/dist/routers/search.d.ts +1 -1
- package/dist/routers/worksheet.d.ts +36 -20
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +10 -3
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/services/announcement.d.ts.map +1 -1
- package/dist/services/announcement.js +10 -6
- package/dist/services/announcement.js.map +1 -1
- package/dist/services/assignment.d.ts +66 -36
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +37 -3
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/class.d.ts +39 -19
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +52 -15
- package/dist/services/class.js.map +1 -1
- package/dist/services/comment.d.ts +5 -5
- package/dist/services/comment.d.ts.map +1 -1
- package/dist/services/comment.js +8 -3
- package/dist/services/comment.js.map +1 -1
- package/dist/services/conversation.d.ts +6 -6
- package/dist/services/event.d.ts +21 -2
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/event.js +11 -7
- package/dist/services/event.js.map +1 -1
- package/dist/services/file.d.ts +2 -2
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/file.js +17 -8
- package/dist/services/file.js.map +1 -1
- package/dist/services/folder.d.ts +24 -24
- package/dist/services/labChat.d.ts +7 -7
- package/dist/services/school.d.ts +299 -0
- package/dist/services/school.d.ts.map +1 -0
- package/dist/services/school.js +225 -0
- package/dist/services/school.js.map +1 -0
- package/dist/services/worksheet.d.ts +31 -20
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +37 -3
- package/dist/services/worksheet.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260504071500_school_memberships/migration.sql +81 -0
- package/prisma/schema.prisma +66 -2
- package/src/models/access.ts +48 -0
- package/src/models/announcement.ts +19 -2
- package/src/models/assignment.ts +149 -13
- package/src/models/attendance.ts +4 -3
- package/src/models/auth.ts +8 -9
- package/src/models/class.ts +8 -9
- package/src/models/comment.ts +29 -40
- package/src/models/event.ts +54 -4
- package/src/models/file.ts +53 -12
- package/src/models/folder.ts +3 -2
- package/src/models/labChat.ts +9 -8
- package/src/models/school.ts +361 -0
- package/src/models/section.ts +3 -2
- package/src/models/worksheet.ts +61 -12
- package/src/routers/_app.ts +2 -0
- package/src/routers/announcement.ts +30 -4
- package/src/routers/assignment.ts +46 -9
- package/src/routers/class.ts +1 -0
- package/src/routers/school.ts +122 -0
- package/src/routers/worksheet.ts +18 -0
- package/src/services/announcement.ts +12 -4
- package/src/services/assignment.ts +47 -1
- package/src/services/class.ts +61 -14
- package/src/services/comment.ts +10 -1
- package/src/services/event.ts +23 -6
- package/src/services/file.ts +20 -6
- package/src/services/school.ts +308 -0
- package/src/services/worksheet.ts +46 -0
- package/dist/lib/notificationHandler.d.ts +0 -25
- package/dist/lib/notificationHandler.d.ts.map +0 -1
- package/dist/lib/notificationHandler.js +0 -32
- package/dist/lib/notificationHandler.js.map +0 -1
- package/dist/server/pipelines/aiLabChat.d.ts +0 -21
- package/dist/server/pipelines/aiLabChat.d.ts.map +0 -1
- package/dist/server/pipelines/aiLabChat.js +0 -456
- package/dist/server/pipelines/aiLabChat.js.map +0 -1
- package/dist/server/pipelines/aiNewtonChat.d.ts +0 -30
- package/dist/server/pipelines/aiNewtonChat.d.ts.map +0 -1
- package/dist/server/pipelines/aiNewtonChat.js +0 -285
- package/dist/server/pipelines/aiNewtonChat.js.map +0 -1
- package/dist/server/pipelines/gradeWorksheet.d.ts +0 -30
- package/dist/server/pipelines/gradeWorksheet.d.ts.map +0 -1
- package/dist/server/pipelines/gradeWorksheet.js +0 -248
- package/dist/server/pipelines/gradeWorksheet.js.map +0 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* School service - school admin workflow, memberships, and invites.
|
|
3
|
+
*/
|
|
4
|
+
import { SchoolMembershipRole } from "@prisma/client";
|
|
5
|
+
import { TRPCError } from "@trpc/server";
|
|
6
|
+
import { v4 as uuidv4 } from "uuid";
|
|
7
|
+
import { env } from "../lib/config/env.js";
|
|
8
|
+
import { prisma } from "../lib/prisma.js";
|
|
9
|
+
import { sendMail } from "../utils/email.js";
|
|
10
|
+
import { logger } from "../utils/logger.js";
|
|
11
|
+
import {
|
|
12
|
+
acceptSchoolInvite,
|
|
13
|
+
countActiveAdmins,
|
|
14
|
+
createSchoolInvite,
|
|
15
|
+
createSchoolWithAdmin,
|
|
16
|
+
findActiveSchoolAdminMembership,
|
|
17
|
+
findPendingInviteByToken,
|
|
18
|
+
findSchoolMembership,
|
|
19
|
+
findUserByIdForSchool,
|
|
20
|
+
getSchoolById,
|
|
21
|
+
getSchoolOverview,
|
|
22
|
+
listSchoolClasses,
|
|
23
|
+
listSchoolInvites,
|
|
24
|
+
listSchoolMembers,
|
|
25
|
+
listUserSchoolMemberships,
|
|
26
|
+
removeSchoolMembershipPreservingContent,
|
|
27
|
+
revokeSchoolInvite,
|
|
28
|
+
updateSchoolMembershipRole,
|
|
29
|
+
upsertSchoolMembership,
|
|
30
|
+
} from "../models/school.js";
|
|
31
|
+
|
|
32
|
+
async function requireSchoolAdmin(schoolId: string, userId: string) {
|
|
33
|
+
const membership = await findActiveSchoolAdminMembership(schoolId, userId);
|
|
34
|
+
if (!membership) {
|
|
35
|
+
throw new TRPCError({
|
|
36
|
+
code: "FORBIDDEN",
|
|
37
|
+
message: "Only school admins can perform this action",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return membership;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function requireSchoolMember(schoolId: string, userId: string) {
|
|
44
|
+
const membership = await findSchoolMembership(schoolId, userId);
|
|
45
|
+
if (!membership || membership.status !== "ACTIVE") {
|
|
46
|
+
throw new TRPCError({
|
|
47
|
+
code: "FORBIDDEN",
|
|
48
|
+
message: "You are not a member of this school",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return membership;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function getCurrentSchool(userId: string, schoolId?: string) {
|
|
55
|
+
const memberships = await listUserSchoolMemberships(userId);
|
|
56
|
+
const activeMembership = schoolId
|
|
57
|
+
? memberships.find((membership) => membership.schoolId === schoolId)
|
|
58
|
+
: memberships[0];
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
activeSchool: activeMembership?.school ?? null,
|
|
62
|
+
activeRole: activeMembership?.role ?? null,
|
|
63
|
+
memberships,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function createSchoolRecord(
|
|
68
|
+
userId: string,
|
|
69
|
+
input: { name: string; subdomain?: string; logoId?: string }
|
|
70
|
+
) {
|
|
71
|
+
const school = await createSchoolWithAdmin({
|
|
72
|
+
userId,
|
|
73
|
+
name: input.name,
|
|
74
|
+
subdomain: input.subdomain,
|
|
75
|
+
logoId: input.logoId,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { school };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function getAdminOverview(userId: string, schoolId: string) {
|
|
82
|
+
await requireSchoolAdmin(schoolId, userId);
|
|
83
|
+
const school = await getSchoolOverview(schoolId);
|
|
84
|
+
if (!school) {
|
|
85
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "School not found" });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const roleCounts = school.memberships.reduce(
|
|
89
|
+
(counts, membership) => {
|
|
90
|
+
counts[membership.role] += 1;
|
|
91
|
+
return counts;
|
|
92
|
+
},
|
|
93
|
+
{ ADMIN: 0, TEACHER: 0, STUDENT: 0 } satisfies Record<SchoolMembershipRole, number>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
school: {
|
|
98
|
+
id: school.id,
|
|
99
|
+
name: school.name,
|
|
100
|
+
subdomain: school.subdomain,
|
|
101
|
+
counts: {
|
|
102
|
+
classes: school._count.classes,
|
|
103
|
+
members: school._count.memberships,
|
|
104
|
+
pendingInvites: school._count.invites,
|
|
105
|
+
...roleCounts,
|
|
106
|
+
},
|
|
107
|
+
recentClasses: school.classes,
|
|
108
|
+
recentInvites: school.invites,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function listClasses(userId: string, schoolId: string) {
|
|
114
|
+
await requireSchoolAdmin(schoolId, userId);
|
|
115
|
+
return { classes: await listSchoolClasses(schoolId) };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function createSchoolClass(
|
|
119
|
+
userId: string,
|
|
120
|
+
input: {
|
|
121
|
+
schoolId: string;
|
|
122
|
+
name: string;
|
|
123
|
+
section: string;
|
|
124
|
+
subject: string;
|
|
125
|
+
color?: string;
|
|
126
|
+
teachers?: string[];
|
|
127
|
+
students?: string[];
|
|
128
|
+
}
|
|
129
|
+
) {
|
|
130
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
131
|
+
const createdClass = await prisma.class.create({
|
|
132
|
+
data: {
|
|
133
|
+
name: input.name,
|
|
134
|
+
section: input.section,
|
|
135
|
+
subject: input.subject,
|
|
136
|
+
color: input.color,
|
|
137
|
+
schoolId: input.schoolId,
|
|
138
|
+
teachers: input.teachers?.length
|
|
139
|
+
? { connect: input.teachers.map((id) => ({ id })) }
|
|
140
|
+
: undefined,
|
|
141
|
+
students: input.students?.length
|
|
142
|
+
? { connect: input.students.map((id) => ({ id })) }
|
|
143
|
+
: undefined,
|
|
144
|
+
},
|
|
145
|
+
include: { teachers: true, students: true },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return { class: createdClass };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function listMembers(userId: string, schoolId: string) {
|
|
152
|
+
await requireSchoolAdmin(schoolId, userId);
|
|
153
|
+
return { members: await listSchoolMembers(schoolId) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function addMember(
|
|
157
|
+
userId: string,
|
|
158
|
+
input: { schoolId: string; memberUserId: string; role: SchoolMembershipRole }
|
|
159
|
+
) {
|
|
160
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
161
|
+
const membership = await upsertSchoolMembership({
|
|
162
|
+
schoolId: input.schoolId,
|
|
163
|
+
userId: input.memberUserId,
|
|
164
|
+
role: input.role,
|
|
165
|
+
});
|
|
166
|
+
return { membership };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function updateMemberRole(
|
|
170
|
+
userId: string,
|
|
171
|
+
input: { schoolId: string; memberUserId: string; role: SchoolMembershipRole }
|
|
172
|
+
) {
|
|
173
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
174
|
+
const current = await findSchoolMembership(input.schoolId, input.memberUserId);
|
|
175
|
+
if (!current) {
|
|
176
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Member not found" });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (current.role === "ADMIN" && input.role !== "ADMIN") {
|
|
180
|
+
const adminCount = await countActiveAdmins(input.schoolId);
|
|
181
|
+
if (adminCount <= 1) {
|
|
182
|
+
throw new TRPCError({
|
|
183
|
+
code: "BAD_REQUEST",
|
|
184
|
+
message: "A school must have at least one active admin",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const membership = await updateSchoolMembershipRole(
|
|
190
|
+
input.schoolId,
|
|
191
|
+
input.memberUserId,
|
|
192
|
+
input.role
|
|
193
|
+
);
|
|
194
|
+
return { membership };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function removeMember(
|
|
198
|
+
userId: string,
|
|
199
|
+
input: { schoolId: string; memberUserId: string }
|
|
200
|
+
) {
|
|
201
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
202
|
+
const current = await findSchoolMembership(input.schoolId, input.memberUserId);
|
|
203
|
+
if (!current) {
|
|
204
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Member not found" });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (current.role === "ADMIN" && current.status === "ACTIVE") {
|
|
208
|
+
const adminCount = await countActiveAdmins(input.schoolId);
|
|
209
|
+
if (adminCount <= 1) {
|
|
210
|
+
throw new TRPCError({
|
|
211
|
+
code: "BAD_REQUEST",
|
|
212
|
+
message: "A school must have at least one active admin",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
await removeSchoolMembershipPreservingContent(input.schoolId, input.memberUserId);
|
|
218
|
+
return { success: true };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function listInvites(userId: string, schoolId: string) {
|
|
222
|
+
await requireSchoolAdmin(schoolId, userId);
|
|
223
|
+
return { invites: await listSchoolInvites(schoolId) };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function createInvite(
|
|
227
|
+
userId: string,
|
|
228
|
+
input: { schoolId: string; email: string; role: SchoolMembershipRole }
|
|
229
|
+
) {
|
|
230
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
231
|
+
const token = uuidv4();
|
|
232
|
+
const invite = await createSchoolInvite({
|
|
233
|
+
schoolId: input.schoolId,
|
|
234
|
+
invitedById: userId,
|
|
235
|
+
email: input.email,
|
|
236
|
+
role: input.role,
|
|
237
|
+
token,
|
|
238
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
await sendMail({
|
|
243
|
+
from: "hello@studious.sh",
|
|
244
|
+
to: input.email,
|
|
245
|
+
subject: `Join ${invite.school.name} on Studious`,
|
|
246
|
+
text: `Click the link to join ${invite.school.name}: ${env.NEXT_PUBLIC_APP_URL}/school/invite/${token}`,
|
|
247
|
+
});
|
|
248
|
+
} catch (err) {
|
|
249
|
+
logger.error("Failed to send school invite email", {
|
|
250
|
+
schoolId: input.schoolId,
|
|
251
|
+
email: input.email,
|
|
252
|
+
err,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { invite };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export async function revokeInvite(
|
|
260
|
+
userId: string,
|
|
261
|
+
input: { schoolId: string; inviteId: string }
|
|
262
|
+
) {
|
|
263
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
264
|
+
return { invite: await revokeSchoolInvite(input.inviteId, input.schoolId) };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function getInvite(token: string) {
|
|
268
|
+
const invite = await findPendingInviteByToken(token);
|
|
269
|
+
if (!invite || invite.status !== "PENDING") {
|
|
270
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Invite not found" });
|
|
271
|
+
}
|
|
272
|
+
if (invite.expiresAt < new Date()) {
|
|
273
|
+
throw new TRPCError({ code: "UNAUTHORIZED", message: "Invite expired" });
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
invite: {
|
|
277
|
+
id: invite.id,
|
|
278
|
+
email: invite.email,
|
|
279
|
+
role: invite.role,
|
|
280
|
+
school: invite.school,
|
|
281
|
+
expiresAt: invite.expiresAt,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function acceptInvite(userId: string, token: string) {
|
|
287
|
+
const invite = await findPendingInviteByToken(token);
|
|
288
|
+
if (!invite || invite.status !== "PENDING") {
|
|
289
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Invite not found" });
|
|
290
|
+
}
|
|
291
|
+
if (invite.expiresAt < new Date()) {
|
|
292
|
+
throw new TRPCError({ code: "UNAUTHORIZED", message: "Invite expired" });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const user = await findUserByIdForSchool(userId);
|
|
296
|
+
if (!user) {
|
|
297
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "User not found" });
|
|
298
|
+
}
|
|
299
|
+
if (user.email.toLowerCase() !== invite.email.toLowerCase()) {
|
|
300
|
+
throw new TRPCError({
|
|
301
|
+
code: "FORBIDDEN",
|
|
302
|
+
message: "This invite was sent to a different account",
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const membership = await acceptSchoolInvite(invite.id, userId);
|
|
307
|
+
return { membership };
|
|
308
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
findWorksheetByIdForTeacher,
|
|
15
15
|
findWorksheetsByClassId,
|
|
16
16
|
createWorksheet,
|
|
17
|
+
duplicateWorksheet,
|
|
17
18
|
updateWorksheet,
|
|
18
19
|
deleteWorksheet,
|
|
19
20
|
findQuestionsByWorksheetId,
|
|
@@ -123,6 +124,51 @@ export async function createWorksheetRecord(classId: string, name: string) {
|
|
|
123
124
|
return createWorksheet({ classId, name });
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Duplicate a worksheet into another class. Caller must teach both the source worksheet's class
|
|
129
|
+
* and the target class.
|
|
130
|
+
*/
|
|
131
|
+
export async function duplicateWorksheetRecord(
|
|
132
|
+
worksheetId: string,
|
|
133
|
+
classId: string,
|
|
134
|
+
name: string,
|
|
135
|
+
userId: string
|
|
136
|
+
) {
|
|
137
|
+
const source = await findWorksheetByIdForTeacher(worksheetId, userId);
|
|
138
|
+
if (!source) {
|
|
139
|
+
throw new TRPCError({
|
|
140
|
+
code: "NOT_FOUND",
|
|
141
|
+
message:
|
|
142
|
+
"Worksheet not found or you are not a teacher of the class it belongs to",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const targetOk = await prisma.class.findFirst({
|
|
147
|
+
where: {
|
|
148
|
+
id: classId,
|
|
149
|
+
teachers: { some: { id: userId } },
|
|
150
|
+
},
|
|
151
|
+
select: { id: true },
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (!targetOk) {
|
|
155
|
+
throw new TRPCError({
|
|
156
|
+
code: "FORBIDDEN",
|
|
157
|
+
message: "You are not a teacher of the target class",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const created = await duplicateWorksheet(worksheetId, classId, name);
|
|
162
|
+
if (!created) {
|
|
163
|
+
throw new TRPCError({
|
|
164
|
+
code: "NOT_FOUND",
|
|
165
|
+
message: "Worksheet not found",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return created;
|
|
170
|
+
}
|
|
171
|
+
|
|
126
172
|
/**
|
|
127
173
|
* Create a worksheet by converting a PDF (URL) into structured questions.
|
|
128
174
|
* Uses the `pdfToWorksheet` pipeline to do inference + persistence.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
interface notificationData {
|
|
2
|
-
title: string;
|
|
3
|
-
content: string;
|
|
4
|
-
}
|
|
5
|
-
export declare function sendNotification(receiver: string, data: notificationData): Promise<{
|
|
6
|
-
id: string;
|
|
7
|
-
title: string;
|
|
8
|
-
content: string;
|
|
9
|
-
createdAt: Date;
|
|
10
|
-
read: boolean;
|
|
11
|
-
senderId: string | null;
|
|
12
|
-
receiverId: string;
|
|
13
|
-
}>;
|
|
14
|
-
export declare function sendNotifications(receiverIds: Array<string>, data: notificationData): Promise<import(".prisma/client").Prisma.BatchPayload>;
|
|
15
|
-
export declare function markRead(id: string, read?: boolean): Promise<{
|
|
16
|
-
id: string;
|
|
17
|
-
title: string;
|
|
18
|
-
content: string;
|
|
19
|
-
createdAt: Date;
|
|
20
|
-
read: boolean;
|
|
21
|
-
senderId: string | null;
|
|
22
|
-
receiverId: string;
|
|
23
|
-
}>;
|
|
24
|
-
export {};
|
|
25
|
-
//# sourceMappingURL=notificationHandler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"notificationHandler.d.ts","sourceRoot":"/","sources":["lib/notificationHandler.ts"],"names":[],"mappings":"AAEA,UAAU,gBAAgB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB;;;;;;;;GAS9E;AAED,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,gBAAgB,yDASzF;AAED,wBAAsB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE,OAAc;;;;;;;;GAM9D"}
|
|
@@ -1,32 +0,0 @@
|
|
|
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]="e0e01d37-e6fd-5751-a9d2-fece72c46422")}catch(e){}}();
|
|
3
|
-
import { prisma } from "./prisma.js";
|
|
4
|
-
export async function sendNotification(receiver, data) {
|
|
5
|
-
const notification = await prisma.notification.create({
|
|
6
|
-
data: {
|
|
7
|
-
receiverId: receiver,
|
|
8
|
-
title: data.title,
|
|
9
|
-
content: data.content,
|
|
10
|
-
},
|
|
11
|
-
});
|
|
12
|
-
return notification;
|
|
13
|
-
}
|
|
14
|
-
export async function sendNotifications(receiverIds, data) {
|
|
15
|
-
const notifications = await prisma.notification.createMany({
|
|
16
|
-
data: receiverIds.map(receiverId => ({
|
|
17
|
-
receiverId: receiverId,
|
|
18
|
-
title: data.title,
|
|
19
|
-
content: data.content,
|
|
20
|
-
})),
|
|
21
|
-
});
|
|
22
|
-
return notifications;
|
|
23
|
-
}
|
|
24
|
-
export async function markRead(id, read = true) {
|
|
25
|
-
const notification = await prisma.notification.update({
|
|
26
|
-
where: { id },
|
|
27
|
-
data: { read: read },
|
|
28
|
-
});
|
|
29
|
-
return notification;
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=notificationHandler.js.map
|
|
32
|
-
//# debugId=e0e01d37-e6fd-5751-a9d2-fece72c46422
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"notificationHandler.js","sources":["lib/notificationHandler.ts"],"sourceRoot":"/","sourcesContent":["import { prisma } from \"./prisma.js\";\n\ninterface notificationData {\n title: string,\n content: string\n}\n\nexport async function sendNotification(receiver: string, data: notificationData) {\n const notification = await prisma.notification.create({\n data: {\n receiverId: receiver,\n title: data.title,\n content: data.content,\n },\n });\n return notification;\n}\n\nexport async function sendNotifications(receiverIds: Array<string>, data: notificationData) {\n const notifications = await prisma.notification.createMany({\n data: receiverIds.map(receiverId => ({\n receiverId: receiverId,\n title: data.title,\n content: data.content,\n })),\n });\n return notifications;\n}\n\nexport async function markRead(id: string, read: boolean = true) {\n const notification = await prisma.notification.update({\n where: {id},\n data: {read: read},\n });\n return notification;\n}\n"],"names":[],"mappings":";;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOrC,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAgB,EAAE,IAAsB;IAC3E,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;QAClD,IAAI,EAAE;YACF,UAAU,EAAE,QAAQ;YACpB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;SACxB;KACJ,CAAC,CAAC;IACH,OAAO,YAAY,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,WAA0B,EAAE,IAAsB;IACtF,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QACvD,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YACjC,UAAU,EAAE,UAAU;YACtB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;SACxB,CAAC,CAAC;KACN,CAAC,CAAC;IACH,OAAO,aAAa,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,EAAU,EAAE,OAAgB,IAAI;IAC3D,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;QAClD,KAAK,EAAE,EAAC,EAAE,EAAC;QACX,IAAI,EAAE,EAAC,IAAI,EAAE,IAAI,EAAC;KACrB,CAAC,CAAC;IACH,OAAO,YAAY,CAAC;AACxB,CAAC","debug_id":"e0e01d37-e6fd-5751-a9d2-fece72c46422"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Assignment, Class, File, Section, User } from "@prisma/client";
|
|
2
|
-
export declare const getBaseSystemPrompt: (context: Class, members: User[], assignments: Assignment[], files: File[], sections: Section[]) => string;
|
|
3
|
-
/**
|
|
4
|
-
* Generate labchat responses
|
|
5
|
-
* Allow for the generation of the following:
|
|
6
|
-
* - Assignment(s) either individual or bulk as an lesson / course plan.
|
|
7
|
-
* - Worksheet(s) either individual or bulk as an lesson / course plan.
|
|
8
|
-
* - Files (PDFs)
|
|
9
|
-
* @param labChatId
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Generate and send AI introduction for lab chat
|
|
13
|
-
* Uses the stored context directly from database
|
|
14
|
-
*/
|
|
15
|
-
export declare const generateAndSendLabIntroduction: (labChatId: string, conversationId: string, contextString: string, subject: string) => Promise<void>;
|
|
16
|
-
/**
|
|
17
|
-
* Generate and send AI response to teacher message
|
|
18
|
-
* Uses the stored context directly from database
|
|
19
|
-
*/
|
|
20
|
-
export declare const generateAndSendLabResponse: (labChatId: string, teacherMessage: string, conversationId: string) => Promise<void>;
|
|
21
|
-
//# sourceMappingURL=aiLabChat.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aiLabChat.d.ts","sourceRoot":"/","sources":["server/pipelines/aiLabChat.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA4ExE,eAAO,MAAM,mBAAmB,GAAI,SAAS,KAAK,EAAE,SAAS,IAAI,EAAE,EAAE,aAAa,UAAU,EAAE,EAAE,OAAO,IAAI,EAAE,EAAE,UAAU,OAAO,EAAE,WA6BjI,CAAA;AAID;;;;;;;GAOG;AAoBH;;;GAGG;AACH,eAAO,MAAM,8BAA8B,GACvC,WAAW,MAAM,EACjB,gBAAgB,MAAM,EACtB,eAAe,MAAM,EACrB,SAAS,MAAM,KACd,OAAO,CAAC,IAAI,CA2Dd,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GACrC,WAAW,MAAM,EACjB,gBAAgB,MAAM,EACtB,gBAAgB,MAAM,KACrB,OAAO,CAAC,IAAI,CAkSd,CAAA"}
|