@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,225 @@
|
|
|
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]="eb106c0e-5f2a-504b-9b64-f273c0b7478e")}catch(e){}}();
|
|
3
|
+
import { TRPCError } from "@trpc/server";
|
|
4
|
+
import { v4 as uuidv4 } from "uuid";
|
|
5
|
+
import { env } from "../lib/config/env.js";
|
|
6
|
+
import { prisma } from "../lib/prisma.js";
|
|
7
|
+
import { sendMail } from "../utils/email.js";
|
|
8
|
+
import { logger } from "../utils/logger.js";
|
|
9
|
+
import { acceptSchoolInvite, countActiveAdmins, createSchoolInvite, createSchoolWithAdmin, findActiveSchoolAdminMembership, findPendingInviteByToken, findSchoolMembership, findUserByIdForSchool, getSchoolOverview, listSchoolClasses, listSchoolInvites, listSchoolMembers, listUserSchoolMemberships, removeSchoolMembershipPreservingContent, revokeSchoolInvite, updateSchoolMembershipRole, upsertSchoolMembership, } from "../models/school.js";
|
|
10
|
+
async function requireSchoolAdmin(schoolId, userId) {
|
|
11
|
+
const membership = await findActiveSchoolAdminMembership(schoolId, userId);
|
|
12
|
+
if (!membership) {
|
|
13
|
+
throw new TRPCError({
|
|
14
|
+
code: "FORBIDDEN",
|
|
15
|
+
message: "Only school admins can perform this action",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return membership;
|
|
19
|
+
}
|
|
20
|
+
async function requireSchoolMember(schoolId, userId) {
|
|
21
|
+
const membership = await findSchoolMembership(schoolId, userId);
|
|
22
|
+
if (!membership || membership.status !== "ACTIVE") {
|
|
23
|
+
throw new TRPCError({
|
|
24
|
+
code: "FORBIDDEN",
|
|
25
|
+
message: "You are not a member of this school",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return membership;
|
|
29
|
+
}
|
|
30
|
+
export async function getCurrentSchool(userId, schoolId) {
|
|
31
|
+
const memberships = await listUserSchoolMemberships(userId);
|
|
32
|
+
const activeMembership = schoolId
|
|
33
|
+
? memberships.find((membership) => membership.schoolId === schoolId)
|
|
34
|
+
: memberships[0];
|
|
35
|
+
return {
|
|
36
|
+
activeSchool: activeMembership?.school ?? null,
|
|
37
|
+
activeRole: activeMembership?.role ?? null,
|
|
38
|
+
memberships,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function createSchoolRecord(userId, input) {
|
|
42
|
+
const school = await createSchoolWithAdmin({
|
|
43
|
+
userId,
|
|
44
|
+
name: input.name,
|
|
45
|
+
subdomain: input.subdomain,
|
|
46
|
+
logoId: input.logoId,
|
|
47
|
+
});
|
|
48
|
+
return { school };
|
|
49
|
+
}
|
|
50
|
+
export async function getAdminOverview(userId, schoolId) {
|
|
51
|
+
await requireSchoolAdmin(schoolId, userId);
|
|
52
|
+
const school = await getSchoolOverview(schoolId);
|
|
53
|
+
if (!school) {
|
|
54
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "School not found" });
|
|
55
|
+
}
|
|
56
|
+
const roleCounts = school.memberships.reduce((counts, membership) => {
|
|
57
|
+
counts[membership.role] += 1;
|
|
58
|
+
return counts;
|
|
59
|
+
}, { ADMIN: 0, TEACHER: 0, STUDENT: 0 });
|
|
60
|
+
return {
|
|
61
|
+
school: {
|
|
62
|
+
id: school.id,
|
|
63
|
+
name: school.name,
|
|
64
|
+
subdomain: school.subdomain,
|
|
65
|
+
counts: {
|
|
66
|
+
classes: school._count.classes,
|
|
67
|
+
members: school._count.memberships,
|
|
68
|
+
pendingInvites: school._count.invites,
|
|
69
|
+
...roleCounts,
|
|
70
|
+
},
|
|
71
|
+
recentClasses: school.classes,
|
|
72
|
+
recentInvites: school.invites,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export async function listClasses(userId, schoolId) {
|
|
77
|
+
await requireSchoolAdmin(schoolId, userId);
|
|
78
|
+
return { classes: await listSchoolClasses(schoolId) };
|
|
79
|
+
}
|
|
80
|
+
export async function createSchoolClass(userId, input) {
|
|
81
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
82
|
+
const createdClass = await prisma.class.create({
|
|
83
|
+
data: {
|
|
84
|
+
name: input.name,
|
|
85
|
+
section: input.section,
|
|
86
|
+
subject: input.subject,
|
|
87
|
+
color: input.color,
|
|
88
|
+
schoolId: input.schoolId,
|
|
89
|
+
teachers: input.teachers?.length
|
|
90
|
+
? { connect: input.teachers.map((id) => ({ id })) }
|
|
91
|
+
: undefined,
|
|
92
|
+
students: input.students?.length
|
|
93
|
+
? { connect: input.students.map((id) => ({ id })) }
|
|
94
|
+
: undefined,
|
|
95
|
+
},
|
|
96
|
+
include: { teachers: true, students: true },
|
|
97
|
+
});
|
|
98
|
+
return { class: createdClass };
|
|
99
|
+
}
|
|
100
|
+
export async function listMembers(userId, schoolId) {
|
|
101
|
+
await requireSchoolAdmin(schoolId, userId);
|
|
102
|
+
return { members: await listSchoolMembers(schoolId) };
|
|
103
|
+
}
|
|
104
|
+
export async function addMember(userId, input) {
|
|
105
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
106
|
+
const membership = await upsertSchoolMembership({
|
|
107
|
+
schoolId: input.schoolId,
|
|
108
|
+
userId: input.memberUserId,
|
|
109
|
+
role: input.role,
|
|
110
|
+
});
|
|
111
|
+
return { membership };
|
|
112
|
+
}
|
|
113
|
+
export async function updateMemberRole(userId, input) {
|
|
114
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
115
|
+
const current = await findSchoolMembership(input.schoolId, input.memberUserId);
|
|
116
|
+
if (!current) {
|
|
117
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Member not found" });
|
|
118
|
+
}
|
|
119
|
+
if (current.role === "ADMIN" && input.role !== "ADMIN") {
|
|
120
|
+
const adminCount = await countActiveAdmins(input.schoolId);
|
|
121
|
+
if (adminCount <= 1) {
|
|
122
|
+
throw new TRPCError({
|
|
123
|
+
code: "BAD_REQUEST",
|
|
124
|
+
message: "A school must have at least one active admin",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const membership = await updateSchoolMembershipRole(input.schoolId, input.memberUserId, input.role);
|
|
129
|
+
return { membership };
|
|
130
|
+
}
|
|
131
|
+
export async function removeMember(userId, input) {
|
|
132
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
133
|
+
const current = await findSchoolMembership(input.schoolId, input.memberUserId);
|
|
134
|
+
if (!current) {
|
|
135
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Member not found" });
|
|
136
|
+
}
|
|
137
|
+
if (current.role === "ADMIN" && current.status === "ACTIVE") {
|
|
138
|
+
const adminCount = await countActiveAdmins(input.schoolId);
|
|
139
|
+
if (adminCount <= 1) {
|
|
140
|
+
throw new TRPCError({
|
|
141
|
+
code: "BAD_REQUEST",
|
|
142
|
+
message: "A school must have at least one active admin",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
await removeSchoolMembershipPreservingContent(input.schoolId, input.memberUserId);
|
|
147
|
+
return { success: true };
|
|
148
|
+
}
|
|
149
|
+
export async function listInvites(userId, schoolId) {
|
|
150
|
+
await requireSchoolAdmin(schoolId, userId);
|
|
151
|
+
return { invites: await listSchoolInvites(schoolId) };
|
|
152
|
+
}
|
|
153
|
+
export async function createInvite(userId, input) {
|
|
154
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
155
|
+
const token = uuidv4();
|
|
156
|
+
const invite = await createSchoolInvite({
|
|
157
|
+
schoolId: input.schoolId,
|
|
158
|
+
invitedById: userId,
|
|
159
|
+
email: input.email,
|
|
160
|
+
role: input.role,
|
|
161
|
+
token,
|
|
162
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),
|
|
163
|
+
});
|
|
164
|
+
try {
|
|
165
|
+
await sendMail({
|
|
166
|
+
from: "hello@studious.sh",
|
|
167
|
+
to: input.email,
|
|
168
|
+
subject: `Join ${invite.school.name} on Studious`,
|
|
169
|
+
text: `Click the link to join ${invite.school.name}: ${env.NEXT_PUBLIC_APP_URL}/school/invite/${token}`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
logger.error("Failed to send school invite email", {
|
|
174
|
+
schoolId: input.schoolId,
|
|
175
|
+
email: input.email,
|
|
176
|
+
err,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return { invite };
|
|
180
|
+
}
|
|
181
|
+
export async function revokeInvite(userId, input) {
|
|
182
|
+
await requireSchoolAdmin(input.schoolId, userId);
|
|
183
|
+
return { invite: await revokeSchoolInvite(input.inviteId, input.schoolId) };
|
|
184
|
+
}
|
|
185
|
+
export async function getInvite(token) {
|
|
186
|
+
const invite = await findPendingInviteByToken(token);
|
|
187
|
+
if (!invite || invite.status !== "PENDING") {
|
|
188
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Invite not found" });
|
|
189
|
+
}
|
|
190
|
+
if (invite.expiresAt < new Date()) {
|
|
191
|
+
throw new TRPCError({ code: "UNAUTHORIZED", message: "Invite expired" });
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
invite: {
|
|
195
|
+
id: invite.id,
|
|
196
|
+
email: invite.email,
|
|
197
|
+
role: invite.role,
|
|
198
|
+
school: invite.school,
|
|
199
|
+
expiresAt: invite.expiresAt,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
export async function acceptInvite(userId, token) {
|
|
204
|
+
const invite = await findPendingInviteByToken(token);
|
|
205
|
+
if (!invite || invite.status !== "PENDING") {
|
|
206
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Invite not found" });
|
|
207
|
+
}
|
|
208
|
+
if (invite.expiresAt < new Date()) {
|
|
209
|
+
throw new TRPCError({ code: "UNAUTHORIZED", message: "Invite expired" });
|
|
210
|
+
}
|
|
211
|
+
const user = await findUserByIdForSchool(userId);
|
|
212
|
+
if (!user) {
|
|
213
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "User not found" });
|
|
214
|
+
}
|
|
215
|
+
if (user.email.toLowerCase() !== invite.email.toLowerCase()) {
|
|
216
|
+
throw new TRPCError({
|
|
217
|
+
code: "FORBIDDEN",
|
|
218
|
+
message: "This invite was sent to a different account",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const membership = await acceptSchoolInvite(invite.id, userId);
|
|
222
|
+
return { membership };
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=school.js.map
|
|
225
|
+
//# debugId=eb106c0e-5f2a-504b-9b64-f273c0b7478e
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"school.js","sources":["services/school.ts"],"sourceRoot":"/","sourcesContent":["/**\n * School service - school admin workflow, memberships, and invites.\n */\nimport { SchoolMembershipRole } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { env } from \"../lib/config/env.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { sendMail } from \"../utils/email.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n acceptSchoolInvite,\n countActiveAdmins,\n createSchoolInvite,\n createSchoolWithAdmin,\n findActiveSchoolAdminMembership,\n findPendingInviteByToken,\n findSchoolMembership,\n findUserByIdForSchool,\n getSchoolById,\n getSchoolOverview,\n listSchoolClasses,\n listSchoolInvites,\n listSchoolMembers,\n listUserSchoolMemberships,\n removeSchoolMembershipPreservingContent,\n revokeSchoolInvite,\n updateSchoolMembershipRole,\n upsertSchoolMembership,\n} from \"../models/school.js\";\n\nasync function requireSchoolAdmin(schoolId: string, userId: string) {\n const membership = await findActiveSchoolAdminMembership(schoolId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can perform this action\",\n });\n }\n return membership;\n}\n\nasync function requireSchoolMember(schoolId: string, userId: string) {\n const membership = await findSchoolMembership(schoolId, userId);\n if (!membership || membership.status !== \"ACTIVE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not a member of this school\",\n });\n }\n return membership;\n}\n\nexport async function getCurrentSchool(userId: string, schoolId?: string) {\n const memberships = await listUserSchoolMemberships(userId);\n const activeMembership = schoolId\n ? memberships.find((membership) => membership.schoolId === schoolId)\n : memberships[0];\n\n return {\n activeSchool: activeMembership?.school ?? null,\n activeRole: activeMembership?.role ?? null,\n memberships,\n };\n}\n\nexport async function createSchoolRecord(\n userId: string,\n input: { name: string; subdomain?: string; logoId?: string }\n) {\n const school = await createSchoolWithAdmin({\n userId,\n name: input.name,\n subdomain: input.subdomain,\n logoId: input.logoId,\n });\n\n return { school };\n}\n\nexport async function getAdminOverview(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n const school = await getSchoolOverview(schoolId);\n if (!school) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"School not found\" });\n }\n\n const roleCounts = school.memberships.reduce(\n (counts, membership) => {\n counts[membership.role] += 1;\n return counts;\n },\n { ADMIN: 0, TEACHER: 0, STUDENT: 0 } satisfies Record<SchoolMembershipRole, number>\n );\n\n return {\n school: {\n id: school.id,\n name: school.name,\n subdomain: school.subdomain,\n counts: {\n classes: school._count.classes,\n members: school._count.memberships,\n pendingInvites: school._count.invites,\n ...roleCounts,\n },\n recentClasses: school.classes,\n recentInvites: school.invites,\n },\n };\n}\n\nexport async function listClasses(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { classes: await listSchoolClasses(schoolId) };\n}\n\nexport async function createSchoolClass(\n userId: string,\n input: {\n schoolId: string;\n name: string;\n section: string;\n subject: string;\n color?: string;\n teachers?: string[];\n students?: string[];\n }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const createdClass = await prisma.class.create({\n data: {\n name: input.name,\n section: input.section,\n subject: input.subject,\n color: input.color,\n schoolId: input.schoolId,\n teachers: input.teachers?.length\n ? { connect: input.teachers.map((id) => ({ id })) }\n : undefined,\n students: input.students?.length\n ? { connect: input.students.map((id) => ({ id })) }\n : undefined,\n },\n include: { teachers: true, students: true },\n });\n\n return { class: createdClass };\n}\n\nexport async function listMembers(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { members: await listSchoolMembers(schoolId) };\n}\n\nexport async function addMember(\n userId: string,\n input: { schoolId: string; memberUserId: string; role: SchoolMembershipRole }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const membership = await upsertSchoolMembership({\n schoolId: input.schoolId,\n userId: input.memberUserId,\n role: input.role,\n });\n return { membership };\n}\n\nexport async function updateMemberRole(\n userId: string,\n input: { schoolId: string; memberUserId: string; role: SchoolMembershipRole }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const current = await findSchoolMembership(input.schoolId, input.memberUserId);\n if (!current) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Member not found\" });\n }\n\n if (current.role === \"ADMIN\" && input.role !== \"ADMIN\") {\n const adminCount = await countActiveAdmins(input.schoolId);\n if (adminCount <= 1) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"A school must have at least one active admin\",\n });\n }\n }\n\n const membership = await updateSchoolMembershipRole(\n input.schoolId,\n input.memberUserId,\n input.role\n );\n return { membership };\n}\n\nexport async function removeMember(\n userId: string,\n input: { schoolId: string; memberUserId: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const current = await findSchoolMembership(input.schoolId, input.memberUserId);\n if (!current) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Member not found\" });\n }\n\n if (current.role === \"ADMIN\" && current.status === \"ACTIVE\") {\n const adminCount = await countActiveAdmins(input.schoolId);\n if (adminCount <= 1) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"A school must have at least one active admin\",\n });\n }\n }\n\n await removeSchoolMembershipPreservingContent(input.schoolId, input.memberUserId);\n return { success: true };\n}\n\nexport async function listInvites(userId: string, schoolId: string) {\n await requireSchoolAdmin(schoolId, userId);\n return { invites: await listSchoolInvites(schoolId) };\n}\n\nexport async function createInvite(\n userId: string,\n input: { schoolId: string; email: string; role: SchoolMembershipRole }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n const token = uuidv4();\n const invite = await createSchoolInvite({\n schoolId: input.schoolId,\n invitedById: userId,\n email: input.email,\n role: input.role,\n token,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),\n });\n\n try {\n await sendMail({\n from: \"hello@studious.sh\",\n to: input.email,\n subject: `Join ${invite.school.name} on Studious`,\n text: `Click the link to join ${invite.school.name}: ${env.NEXT_PUBLIC_APP_URL}/school/invite/${token}`,\n });\n } catch (err) {\n logger.error(\"Failed to send school invite email\", {\n schoolId: input.schoolId,\n email: input.email,\n err,\n });\n }\n\n return { invite };\n}\n\nexport async function revokeInvite(\n userId: string,\n input: { schoolId: string; inviteId: string }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n return { invite: await revokeSchoolInvite(input.inviteId, input.schoolId) };\n}\n\nexport async function getInvite(token: string) {\n const invite = await findPendingInviteByToken(token);\n if (!invite || invite.status !== \"PENDING\") {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invite not found\" });\n }\n if (invite.expiresAt < new Date()) {\n throw new TRPCError({ code: \"UNAUTHORIZED\", message: \"Invite expired\" });\n }\n return {\n invite: {\n id: invite.id,\n email: invite.email,\n role: invite.role,\n school: invite.school,\n expiresAt: invite.expiresAt,\n },\n };\n}\n\nexport async function acceptInvite(userId: string, token: string) {\n const invite = await findPendingInviteByToken(token);\n if (!invite || invite.status !== \"PENDING\") {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invite not found\" });\n }\n if (invite.expiresAt < new Date()) {\n throw new TRPCError({ code: \"UNAUTHORIZED\", message: \"Invite expired\" });\n }\n\n const user = await findUserByIdForSchool(userId);\n if (!user) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"User not found\" });\n }\n if (user.email.toLowerCase() !== invite.email.toLowerCase()) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"This invite was sent to a different account\",\n });\n }\n\n const membership = await acceptSchoolInvite(invite.id, userId);\n return { membership };\n}\n"],"names":[],"mappings":"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","debug_id":"eb106c0e-5f2a-504b-9b64-f273c0b7478e"}
|
|
@@ -15,9 +15,9 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
|
|
|
15
15
|
options: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
16
16
|
id: string;
|
|
17
17
|
createdAt: Date;
|
|
18
|
+
updatedAt: Date;
|
|
18
19
|
order: number | null;
|
|
19
20
|
points: number;
|
|
20
|
-
updatedAt: Date;
|
|
21
21
|
worksheetId: string;
|
|
22
22
|
markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
23
23
|
question: string;
|
|
@@ -27,8 +27,8 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
|
|
|
27
27
|
name: string;
|
|
28
28
|
id: string;
|
|
29
29
|
createdAt: Date;
|
|
30
|
-
classId: string;
|
|
31
30
|
updatedAt: Date;
|
|
31
|
+
classId: string;
|
|
32
32
|
}>;
|
|
33
33
|
/** Check if worksheet exists. */
|
|
34
34
|
export declare function worksheetExists(id: string): Promise<boolean>;
|
|
@@ -40,8 +40,8 @@ export declare function listWorksheets(classId: string): Promise<{
|
|
|
40
40
|
name: string;
|
|
41
41
|
id: string;
|
|
42
42
|
createdAt: Date;
|
|
43
|
-
classId: string;
|
|
44
43
|
updatedAt: Date;
|
|
44
|
+
classId: string;
|
|
45
45
|
}[]>;
|
|
46
46
|
export declare function updateWorksheetRecord(worksheetId: string, userId: string, data: {
|
|
47
47
|
name?: string;
|
|
@@ -49,22 +49,33 @@ export declare function updateWorksheetRecord(worksheetId: string, userId: strin
|
|
|
49
49
|
name: string;
|
|
50
50
|
id: string;
|
|
51
51
|
createdAt: Date;
|
|
52
|
-
classId: string;
|
|
53
52
|
updatedAt: Date;
|
|
53
|
+
classId: string;
|
|
54
54
|
}>;
|
|
55
55
|
export declare function deleteWorksheetRecord(worksheetId: string, userId: string): Promise<{
|
|
56
56
|
name: string;
|
|
57
57
|
id: string;
|
|
58
58
|
createdAt: Date;
|
|
59
|
-
classId: string;
|
|
60
59
|
updatedAt: Date;
|
|
60
|
+
classId: string;
|
|
61
61
|
}>;
|
|
62
62
|
export declare function createWorksheetRecord(classId: string, name: string): Promise<{
|
|
63
63
|
name: string;
|
|
64
64
|
id: string;
|
|
65
65
|
createdAt: Date;
|
|
66
|
+
updatedAt: Date;
|
|
66
67
|
classId: string;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Duplicate a worksheet into another class. Caller must teach both the source worksheet's class
|
|
71
|
+
* and the target class.
|
|
72
|
+
*/
|
|
73
|
+
export declare function duplicateWorksheetRecord(worksheetId: string, classId: string, name: string, userId: string): Promise<{
|
|
74
|
+
name: string;
|
|
75
|
+
id: string;
|
|
76
|
+
createdAt: Date;
|
|
67
77
|
updatedAt: Date;
|
|
78
|
+
classId: string;
|
|
68
79
|
}>;
|
|
69
80
|
/**
|
|
70
81
|
* Create a worksheet by converting a PDF (URL) into structured questions.
|
|
@@ -74,8 +85,8 @@ export declare function createWorksheetFromPdf(classId: string, url: string): Pr
|
|
|
74
85
|
name: string;
|
|
75
86
|
id: string;
|
|
76
87
|
createdAt: Date;
|
|
77
|
-
classId: string;
|
|
78
88
|
updatedAt: Date;
|
|
89
|
+
classId: string;
|
|
79
90
|
}>;
|
|
80
91
|
/**
|
|
81
92
|
* Create a worksheet from a PDF that was uploaded through the direct-upload flow.
|
|
@@ -84,8 +95,8 @@ export declare function createWorksheetFromUploadedPdf(classId: string, fileId:
|
|
|
84
95
|
name: string;
|
|
85
96
|
id: string;
|
|
86
97
|
createdAt: Date;
|
|
87
|
-
classId: string;
|
|
88
98
|
updatedAt: Date;
|
|
99
|
+
classId: string;
|
|
89
100
|
}>;
|
|
90
101
|
export declare function addQuestionToWorksheet(worksheetId: string, userId: string, data: {
|
|
91
102
|
question: string;
|
|
@@ -99,9 +110,9 @@ export declare function addQuestionToWorksheet(worksheetId: string, userId: stri
|
|
|
99
110
|
options: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
100
111
|
id: string;
|
|
101
112
|
createdAt: Date;
|
|
113
|
+
updatedAt: Date;
|
|
102
114
|
order: number | null;
|
|
103
115
|
points: number;
|
|
104
|
-
updatedAt: Date;
|
|
105
116
|
worksheetId: string;
|
|
106
117
|
markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
107
118
|
question: string;
|
|
@@ -122,9 +133,9 @@ export declare function updateWorksheetQuestionRecord(worksheetId: string, quest
|
|
|
122
133
|
options: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
123
134
|
id: string;
|
|
124
135
|
createdAt: Date;
|
|
136
|
+
updatedAt: Date;
|
|
125
137
|
order: number | null;
|
|
126
138
|
points: number;
|
|
127
|
-
updatedAt: Date;
|
|
128
139
|
worksheetId: string;
|
|
129
140
|
markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
130
141
|
question: string;
|
|
@@ -135,9 +146,9 @@ export declare function deleteWorksheetQuestionRecord(worksheetId: string, quest
|
|
|
135
146
|
options: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
136
147
|
id: string;
|
|
137
148
|
createdAt: Date;
|
|
149
|
+
updatedAt: Date;
|
|
138
150
|
order: number | null;
|
|
139
151
|
points: number;
|
|
140
|
-
updatedAt: Date;
|
|
141
152
|
worksheetId: string;
|
|
142
153
|
markScheme: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
143
154
|
question: string;
|
|
@@ -152,6 +163,7 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
|
|
|
152
163
|
status: import(".prisma/client").$Enums.GenerationStatus | null;
|
|
153
164
|
id: string;
|
|
154
165
|
createdAt: Date;
|
|
166
|
+
updatedAt: Date | null;
|
|
155
167
|
studentId: string;
|
|
156
168
|
questionId: string;
|
|
157
169
|
response: string;
|
|
@@ -159,17 +171,16 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
|
|
|
159
171
|
markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
160
172
|
points: number;
|
|
161
173
|
feedback: string | null;
|
|
162
|
-
updatedAt: Date | null;
|
|
163
174
|
studentWorksheetResponseId: string | null;
|
|
164
175
|
})[];
|
|
165
176
|
} & {
|
|
166
177
|
id: string;
|
|
167
|
-
submissionId: string | null;
|
|
168
178
|
createdAt: Date;
|
|
179
|
+
updatedAt: Date;
|
|
180
|
+
submissionId: string | null;
|
|
169
181
|
studentId: string;
|
|
170
182
|
submittedAt: Date | null;
|
|
171
183
|
submitted: boolean;
|
|
172
|
-
updatedAt: Date;
|
|
173
184
|
worksheetId: string;
|
|
174
185
|
}>;
|
|
175
186
|
export declare function answerWorksheetQuestion(worksheetResponseId: string, questionId: string, response: string, userId: string): Promise<{
|
|
@@ -177,6 +188,7 @@ export declare function answerWorksheetQuestion(worksheetResponseId: string, que
|
|
|
177
188
|
status: import(".prisma/client").$Enums.GenerationStatus | null;
|
|
178
189
|
id: string;
|
|
179
190
|
createdAt: Date;
|
|
191
|
+
updatedAt: Date | null;
|
|
180
192
|
studentId: string;
|
|
181
193
|
questionId: string;
|
|
182
194
|
response: string;
|
|
@@ -184,23 +196,23 @@ export declare function answerWorksheetQuestion(worksheetResponseId: string, que
|
|
|
184
196
|
markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
185
197
|
points: number;
|
|
186
198
|
feedback: string | null;
|
|
187
|
-
updatedAt: Date | null;
|
|
188
199
|
studentWorksheetResponseId: string | null;
|
|
189
200
|
}[];
|
|
190
201
|
} & {
|
|
191
202
|
id: string;
|
|
192
|
-
submissionId: string | null;
|
|
193
203
|
createdAt: Date;
|
|
204
|
+
updatedAt: Date;
|
|
205
|
+
submissionId: string | null;
|
|
194
206
|
studentId: string;
|
|
195
207
|
submittedAt: Date | null;
|
|
196
208
|
submitted: boolean;
|
|
197
|
-
updatedAt: Date;
|
|
198
209
|
worksheetId: string;
|
|
199
210
|
}>;
|
|
200
211
|
export declare function cancelGrading(worksheetResponseId: string, progressId: string, userId: string): Promise<{
|
|
201
212
|
status: import(".prisma/client").$Enums.GenerationStatus | null;
|
|
202
213
|
id: string;
|
|
203
214
|
createdAt: Date;
|
|
215
|
+
updatedAt: Date | null;
|
|
204
216
|
studentId: string;
|
|
205
217
|
questionId: string;
|
|
206
218
|
response: string;
|
|
@@ -208,13 +220,13 @@ export declare function cancelGrading(worksheetResponseId: string, progressId: s
|
|
|
208
220
|
markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
209
221
|
points: number;
|
|
210
222
|
feedback: string | null;
|
|
211
|
-
updatedAt: Date | null;
|
|
212
223
|
studentWorksheetResponseId: string | null;
|
|
213
224
|
}>;
|
|
214
225
|
export declare function regradeQuestion(worksheetResponseId: string, progressId: string, userId: string): Promise<{
|
|
215
226
|
status: import(".prisma/client").$Enums.GenerationStatus | null;
|
|
216
227
|
id: string;
|
|
217
228
|
createdAt: Date;
|
|
229
|
+
updatedAt: Date | null;
|
|
218
230
|
studentId: string;
|
|
219
231
|
questionId: string;
|
|
220
232
|
response: string;
|
|
@@ -222,7 +234,6 @@ export declare function regradeQuestion(worksheetResponseId: string, progressId:
|
|
|
222
234
|
markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
223
235
|
points: number;
|
|
224
236
|
feedback: string | null;
|
|
225
|
-
updatedAt: Date | null;
|
|
226
237
|
studentWorksheetResponseId: string | null;
|
|
227
238
|
}>;
|
|
228
239
|
export declare function gradeAnswer(questionId: string, studentWorksheetResponseId: string, userId: string, data: {
|
|
@@ -236,6 +247,7 @@ export declare function gradeAnswer(questionId: string, studentWorksheetResponse
|
|
|
236
247
|
status: import(".prisma/client").$Enums.GenerationStatus | null;
|
|
237
248
|
id: string;
|
|
238
249
|
createdAt: Date;
|
|
250
|
+
updatedAt: Date | null;
|
|
239
251
|
studentId: string;
|
|
240
252
|
questionId: string;
|
|
241
253
|
response: string;
|
|
@@ -243,16 +255,15 @@ export declare function gradeAnswer(questionId: string, studentWorksheetResponse
|
|
|
243
255
|
markschemeState: import("@prisma/client/runtime/library.js").JsonValue | null;
|
|
244
256
|
points: number;
|
|
245
257
|
feedback: string | null;
|
|
246
|
-
updatedAt: Date | null;
|
|
247
258
|
studentWorksheetResponseId: string | null;
|
|
248
259
|
}>;
|
|
249
260
|
export declare function addCommentToResponse(responseId: string, comment: string, authorId: string): Promise<{
|
|
250
261
|
status: import(".prisma/client").$Enums.GenerationStatus | null;
|
|
251
262
|
id: string;
|
|
263
|
+
createdAt: Date;
|
|
252
264
|
assignmentId: string | null;
|
|
253
265
|
submissionId: string | null;
|
|
254
266
|
announcementId: string | null;
|
|
255
|
-
createdAt: Date;
|
|
256
267
|
modifiedAt: Date | null;
|
|
257
268
|
content: string;
|
|
258
269
|
authorId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"AAKA,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* Integrates with gradeWorksheet pipeline for auto-grading.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
!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]="
|
|
6
|
+
!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]="d15dc113-0231-5e3c-b956-9b5c549c4073")}catch(e){}}();
|
|
7
7
|
import { TRPCError } from "@trpc/server";
|
|
8
8
|
import { GenerationStatus } from "@prisma/client";
|
|
9
9
|
import { prisma } from "../lib/prisma.js";
|
|
10
10
|
import { getFileUrl } from "../lib/fileUpload.js";
|
|
11
11
|
import { cancelGradePipeline, regradeWorksheetPipeline } from "../pipelines/gradeWorksheet.js";
|
|
12
12
|
import { pdfToWorksheet } from "../pipelines/pdfToWorksheet.js";
|
|
13
|
-
import { findWorksheetByIdMinimal, findWorksheetByIdForClassMember, findWorksheetByIdForTeacher, findWorksheetsByClassId, createWorksheet, updateWorksheet, deleteWorksheet, findQuestionsByWorksheetId, createWorksheetQuestion, updateWorksheetQuestion, deleteWorksheetQuestion, findSubmissionByIdForWorksheetAccess, findOrCreateWorksheetResponse, findWorksheetResponseByIdForTeacher, findWorksheetResponseWithResponsesForStudent, findWorksheetQuestionByIdForTeacher, findWorksheetQuestionByIdInWorksheet, updateStudentQuestionProgress, createStudentQuestionProgress, findStudentQuestionProgress, updateStudentQuestionProgressForGrading, createStudentQuestionProgressForGrading, createComment, findStudentQuestionProgressByIdForTeacher, findStudentQuestionProgressByIdForTeacherAndResponse, } from "../models/worksheet.js";
|
|
13
|
+
import { findWorksheetByIdMinimal, findWorksheetByIdForClassMember, findWorksheetByIdForTeacher, findWorksheetsByClassId, createWorksheet, duplicateWorksheet, updateWorksheet, deleteWorksheet, findQuestionsByWorksheetId, createWorksheetQuestion, updateWorksheetQuestion, deleteWorksheetQuestion, findSubmissionByIdForWorksheetAccess, findOrCreateWorksheetResponse, findWorksheetResponseByIdForTeacher, findWorksheetResponseWithResponsesForStudent, findWorksheetQuestionByIdForTeacher, findWorksheetQuestionByIdInWorksheet, updateStudentQuestionProgress, createStudentQuestionProgress, findStudentQuestionProgress, updateStudentQuestionProgressForGrading, createStudentQuestionProgressForGrading, createComment, findStudentQuestionProgressByIdForTeacher, findStudentQuestionProgressByIdForTeacherAndResponse, } from "../models/worksheet.js";
|
|
14
14
|
const sanitizeQuestionOptionsForStudent = (options) => {
|
|
15
15
|
if (!Array.isArray(options))
|
|
16
16
|
return options;
|
|
@@ -80,6 +80,40 @@ export async function deleteWorksheetRecord(worksheetId, userId) {
|
|
|
80
80
|
export async function createWorksheetRecord(classId, name) {
|
|
81
81
|
return createWorksheet({ classId, name });
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Duplicate a worksheet into another class. Caller must teach both the source worksheet's class
|
|
85
|
+
* and the target class.
|
|
86
|
+
*/
|
|
87
|
+
export async function duplicateWorksheetRecord(worksheetId, classId, name, userId) {
|
|
88
|
+
const source = await findWorksheetByIdForTeacher(worksheetId, userId);
|
|
89
|
+
if (!source) {
|
|
90
|
+
throw new TRPCError({
|
|
91
|
+
code: "NOT_FOUND",
|
|
92
|
+
message: "Worksheet not found or you are not a teacher of the class it belongs to",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const targetOk = await prisma.class.findFirst({
|
|
96
|
+
where: {
|
|
97
|
+
id: classId,
|
|
98
|
+
teachers: { some: { id: userId } },
|
|
99
|
+
},
|
|
100
|
+
select: { id: true },
|
|
101
|
+
});
|
|
102
|
+
if (!targetOk) {
|
|
103
|
+
throw new TRPCError({
|
|
104
|
+
code: "FORBIDDEN",
|
|
105
|
+
message: "You are not a teacher of the target class",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const created = await duplicateWorksheet(worksheetId, classId, name);
|
|
109
|
+
if (!created) {
|
|
110
|
+
throw new TRPCError({
|
|
111
|
+
code: "NOT_FOUND",
|
|
112
|
+
message: "Worksheet not found",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return created;
|
|
116
|
+
}
|
|
83
117
|
/**
|
|
84
118
|
* Create a worksheet by converting a PDF (URL) into structured questions.
|
|
85
119
|
* Uses the `pdfToWorksheet` pipeline to do inference + persistence.
|
|
@@ -364,4 +398,4 @@ export async function addCommentToResponse(responseId, comment, authorId) {
|
|
|
364
398
|
});
|
|
365
399
|
}
|
|
366
400
|
//# sourceMappingURL=worksheet.js.map
|
|
367
|
-
//# debugId=
|
|
401
|
+
//# debugId=d15dc113-0231-5e3c-b956-9b5c549c4073
|