@studious-lms/server 1.4.9 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/config/cors.d.ts.map +1 -1
- package/dist/lib/config/cors.js +2 -4
- package/dist/lib/config/cors.js.map +1 -1
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +4 -5
- package/dist/lib/fileUpload.js.map +1 -1
- package/dist/models/auth.d.ts +1 -0
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +3 -2
- package/dist/models/auth.js.map +1 -1
- package/dist/models/comment.d.ts +0 -5
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/conversation.d.ts +2 -1
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +2 -1
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/message.d.ts +6 -3
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/school.d.ts +0 -22
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +2 -14
- package/dist/models/school.js.map +1 -1
- package/dist/models/worksheet.d.ts +76 -19
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +2 -2
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/aiLabChat.js +6 -6
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +5 -3
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +60 -24
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/pipelines/pdfToWorksheet.d.ts +3 -0
- package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
- package/dist/pipelines/pdfToWorksheet.js +65 -24
- package/dist/pipelines/pdfToWorksheet.js.map +1 -1
- package/dist/routers/_app.d.ts +92 -48
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/announcement.d.ts +0 -4
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +0 -8
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/auth.d.ts +1 -0
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/comment.d.ts +0 -3
- package/dist/routers/comment.d.ts.map +1 -1
- package/dist/routers/conversation.d.ts +2 -1
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/labChat.d.ts +2 -1
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/school.d.ts +6 -0
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +7 -2
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/worksheet.d.ts +35 -7
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/services/auth.d.ts +1 -0
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/comment.d.ts +0 -3
- package/dist/services/comment.d.ts.map +1 -1
- package/dist/services/conversation.d.ts +2 -1
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/labChat.d.ts +2 -1
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +6 -6
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +14 -2
- package/dist/services/message.js.map +1 -1
- package/dist/services/school.d.ts +6 -0
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +71 -6
- package/dist/services/school.js.map +1 -1
- package/dist/services/worksheet.d.ts +35 -7
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +17 -19
- package/dist/services/worksheet.js.map +1 -1
- package/dist/utils/generateUsername.d.ts +8 -0
- package/dist/utils/generateUsername.d.ts.map +1 -0
- package/dist/utils/generateUsername.js +103 -0
- package/dist/utils/generateUsername.js.map +1 -0
- package/dist/workers/connection.d.ts +3 -0
- package/dist/workers/connection.d.ts.map +1 -0
- package/dist/workers/connection.js +33 -0
- package/dist/workers/connection.js.map +1 -0
- package/dist/workers/index.d.ts +10 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +53 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/processors/pdfToWorksheet.d.ts +9 -0
- package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -0
- package/dist/workers/processors/pdfToWorksheet.js +100 -0
- package/dist/workers/processors/pdfToWorksheet.js.map +1 -0
- package/dist/workers/queues.d.ts +69 -0
- package/dist/workers/queues.d.ts.map +1 -0
- package/dist/workers/queues.js +119 -0
- package/dist/workers/queues.js.map +1 -0
- package/package.json +4 -1
- package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +166 -0
- package/prisma/schema.prisma +465 -387
- package/src/lib/config/cors.ts +0 -2
- package/src/lib/fileUpload.ts +2 -4
- package/src/models/auth.ts +1 -0
- package/src/models/school.ts +0 -14
- package/src/models/worksheet.ts +2 -2
- package/src/pipelines/aiLabChat.ts +4 -4
- package/src/pipelines/gradeWorksheet.ts +63 -25
- package/src/pipelines/pdfToWorksheet.ts +68 -22
- package/src/routers/school.ts +5 -0
- package/src/services/labChat.ts +4 -4
- package/src/services/message.ts +13 -0
- package/src/services/school.ts +89 -6
- package/src/services/worksheet.ts +16 -18
- package/src/utils/generateUsername.ts +108 -0
- package/src/workers/connection.ts +37 -0
- package/src/workers/index.ts +61 -0
- package/src/workers/processors/pdfToWorksheet.ts +119 -0
- package/src/workers/queues.ts +159 -0
- package/tests/{globalSetup.ts → integration/globalSetup.ts} +4 -4
- package/tests/{helpers.ts → integration/helpers.ts} +2 -2
- package/tests/{pipelines → integration/pipelines}/pdfToWorksheet.test.ts +55 -43
- package/tests/{routers → integration/routers}/attendance.test.ts +1 -1
- package/tests/{routers → integration/routers}/auth.test.ts +3 -3
- package/tests/{routers → integration/routers}/class.test.ts +3 -17
- package/tests/{routers → integration/routers}/conversation.test.ts +1 -1
- package/tests/{routers → integration/routers}/event.test.ts +1 -1
- package/tests/{routers → integration/routers}/message.test.ts +1 -1
- package/tests/{routers → integration/routers}/newtonChat.test.ts +1 -1
- package/tests/{routers → integration/routers}/studentProgress.test.ts +1 -1
- package/tests/{user.test.ts → integration/routers/user.test.ts} +2 -2
- package/tests/integration/routers/worksheet.test.ts +448 -0
- package/tests/{setup.ts → integration/setup.ts} +4 -4
- package/tests/{lib → unit/lib}/cors.test.ts +1 -1
- package/tests/{middleware → unit/middleware}/security.test.ts +1 -1
- package/tests/{pipelines → unit/pipelines}/aiLabChat.test.ts +1 -1
- package/tests/{lib → unit/pipelines}/aiLabChatContract.test.ts +1 -1
- package/tests/{utils/aiLabChatPrompt.test.ts → unit/pipelines/labChatPrompt.test.ts} +2 -2
- package/tests/{utils → unit/services}/studentProgress.test.ts +5 -5
- package/tests/{utils → unit/utils}/aiUser.test.ts +1 -1
- package/tests/{utils → unit/utils}/generateInviteCode.test.ts +1 -1
- package/tests/{utils → unit/utils}/inference.test.ts +6 -6
- package/tests/{utils → unit/utils}/logger.test.ts +1 -1
- package/tests/{utils → unit/utils}/prismaErrorHandler.test.ts +1 -1
- package/tests/{utils → unit/utils}/prismaWrapper.test.ts +1 -1
- package/vitest.config.ts +11 -3
- package/vitest.unit.config.ts +10 -10
- package/tests/routers/worksheet.test.ts +0 -194
- package/tests/startup.test.ts +0 -5
- package/tests/worksheet.test.ts +0 -181
- /package/tests/{routers → integration/routers}/agenda.test.ts +0 -0
- /package/tests/{routers → integration/routers}/announcement.test.ts +0 -0
- /package/tests/{routers → integration/routers}/assignment.test.ts +0 -0
- /package/tests/{routers → integration/routers}/comment.test.ts +0 -0
- /package/tests/{routers → integration/routers}/folder.test.ts +0 -0
- /package/tests/{routers → integration/routers}/labChat.test.ts +0 -0
- /package/tests/{routers → integration/routers}/marketing.test.ts +0 -0
- /package/tests/{routers → integration/routers}/notification.test.ts +0 -0
- /package/tests/{routers → integration/routers}/search.test.ts +0 -0
- /package/tests/{routers → integration/routers}/section.test.ts +0 -0
- /package/tests/{server → unit/middleware}/rateLimit.test.ts +0 -0
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"file":"school.js","sources":["services/school.ts"],"sourceRoot":"/","sourcesContent":["/**\n * School service - school admin workflow, memberships, and invites.\n */\nimport { SchoolMembershipRole, SessionType } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { hash } from \"bcryptjs\";\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 { generateUniqueUsername } from \"../utils/generateUsername.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n createSession,\n createUser,\n findUserByEmail,\n findUserById,\n findUserByUsername,\n} from \"../models/auth.js\";\nimport { upsertUserProfile } from \"../models/user.js\";\nimport {\n acceptSchoolInvite,\n countActiveAdmins,\n createSchoolInvite,\n createSchoolWithAdmin,\n findActiveSchoolAdminMembership,\n findPendingInviteByToken,\n findSchoolMembership,\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: {\n schoolId: string;\n email: string;\n role: SchoolMembershipRole;\n username?: string;\n displayName?: string;\n }\n) {\n await requireSchoolAdmin(input.schoolId, userId);\n\n const existingUser = await findUserByEmail(input.email);\n\n let createdUser: { id: string; username: string } | null = null;\n let passwordResetToken: string | null = null;\n\n if (!existingUser) {\n let username: string;\n if (input.username) {\n const taken = await findUserByUsername(input.username);\n if (taken) {\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Username already taken\",\n });\n }\n username = input.username;\n } else {\n username = await generateUniqueUsername(async (candidate) => {\n const existing = await findUserByUsername(candidate);\n return Boolean(existing);\n });\n }\n\n const placeholderPassword = await hash(uuidv4(), 10);\n const user = await createUser({\n username,\n email: input.email,\n password: placeholderPassword,\n verified: true,\n });\n createdUser = { id: user.id, username: user.username };\n\n if (input.displayName) {\n await upsertUserProfile(user.id, { displayName: input.displayName });\n }\n\n const resetSession = await createSession({\n id: uuidv4(),\n type: SessionType.PASSWORD_RESET,\n userId: user.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),\n });\n passwordResetToken = resetSession.id;\n }\n\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 if (createdUser) {\n await acceptSchoolInvite(invite.id, createdUser.id);\n }\n\n const inviteUrl = `${env.NEXT_PUBLIC_APP_URL}/school/invite/${token}`;\n const passwordResetUrl = passwordResetToken\n ? `${env.NEXT_PUBLIC_APP_URL}/reset-password/${passwordResetToken}`\n : null;\n\n const emailLines = [\n `You've been invited to ${invite.school.name} on Studious.`,\n \"\",\n ];\n if (createdUser && passwordResetUrl) {\n emailLines.push(\n `We created an account for you with the username \"${createdUser.username}\" and added you to ${invite.school.name}.`,\n `Set your password to get started: ${passwordResetUrl}`\n );\n } else {\n emailLines.push(`Click the link to confirm: ${inviteUrl}`);\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: emailLines.join(\"\\n\"),\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, createdUser };\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 findUserById(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":"eab19eca-0947-5015-a1af-d1dcf6c40a4d"}
|
|
@@ -24,11 +24,14 @@ export declare function getWorksheet(worksheetId: string, userId: string): Promi
|
|
|
24
24
|
answer: string;
|
|
25
25
|
}[];
|
|
26
26
|
} & {
|
|
27
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
27
28
|
name: string;
|
|
28
29
|
id: string;
|
|
29
30
|
createdAt: Date;
|
|
30
31
|
updatedAt: Date;
|
|
31
32
|
classId: string;
|
|
33
|
+
runJobId: string | null;
|
|
34
|
+
sourceUrl: string | null;
|
|
32
35
|
}>;
|
|
33
36
|
/** Check if worksheet exists. */
|
|
34
37
|
export declare function worksheetExists(id: string): Promise<boolean>;
|
|
@@ -37,66 +40,87 @@ export declare function listWorksheets(classId: string): Promise<{
|
|
|
37
40
|
questions: {
|
|
38
41
|
id: string;
|
|
39
42
|
}[];
|
|
43
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
40
44
|
name: string;
|
|
41
45
|
id: string;
|
|
42
46
|
createdAt: Date;
|
|
43
47
|
updatedAt: Date;
|
|
44
48
|
classId: string;
|
|
49
|
+
runJobId: string | null;
|
|
50
|
+
sourceUrl: string | null;
|
|
45
51
|
}[]>;
|
|
46
52
|
export declare function updateWorksheetRecord(worksheetId: string, userId: string, data: {
|
|
47
53
|
name?: string;
|
|
48
54
|
}): Promise<{
|
|
55
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
49
56
|
name: string;
|
|
50
57
|
id: string;
|
|
51
58
|
createdAt: Date;
|
|
52
59
|
updatedAt: Date;
|
|
53
60
|
classId: string;
|
|
61
|
+
runJobId: string | null;
|
|
62
|
+
sourceUrl: string | null;
|
|
54
63
|
}>;
|
|
55
64
|
export declare function deleteWorksheetRecord(worksheetId: string, userId: string): Promise<{
|
|
65
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
56
66
|
name: string;
|
|
57
67
|
id: string;
|
|
58
68
|
createdAt: Date;
|
|
59
69
|
updatedAt: Date;
|
|
60
70
|
classId: string;
|
|
71
|
+
runJobId: string | null;
|
|
72
|
+
sourceUrl: string | null;
|
|
61
73
|
}>;
|
|
62
74
|
export declare function createWorksheetRecord(classId: string, name: string): Promise<{
|
|
75
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
63
76
|
name: string;
|
|
64
77
|
id: string;
|
|
65
78
|
createdAt: Date;
|
|
66
79
|
updatedAt: Date;
|
|
67
80
|
classId: string;
|
|
81
|
+
runJobId: string | null;
|
|
82
|
+
sourceUrl: string | null;
|
|
68
83
|
}>;
|
|
69
84
|
/**
|
|
70
85
|
* Duplicate a worksheet into another class. Caller must teach both the source worksheet's class
|
|
71
86
|
* and the target class.
|
|
72
87
|
*/
|
|
73
88
|
export declare function duplicateWorksheetRecord(worksheetId: string, classId: string, name: string, userId: string): Promise<{
|
|
89
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
74
90
|
name: string;
|
|
75
91
|
id: string;
|
|
76
92
|
createdAt: Date;
|
|
77
93
|
updatedAt: Date;
|
|
78
94
|
classId: string;
|
|
95
|
+
runJobId: string | null;
|
|
96
|
+
sourceUrl: string | null;
|
|
79
97
|
}>;
|
|
80
98
|
/**
|
|
81
99
|
* Create a worksheet by converting a PDF (URL) into structured questions.
|
|
82
100
|
* Uses the `pdfToWorksheet` pipeline to do inference + persistence.
|
|
83
101
|
*/
|
|
84
102
|
export declare function createWorksheetFromPdf(classId: string, url: string): Promise<{
|
|
103
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
85
104
|
name: string;
|
|
86
105
|
id: string;
|
|
87
106
|
createdAt: Date;
|
|
88
107
|
updatedAt: Date;
|
|
89
108
|
classId: string;
|
|
109
|
+
runJobId: string | null;
|
|
110
|
+
sourceUrl: string | null;
|
|
90
111
|
}>;
|
|
91
112
|
/**
|
|
92
113
|
* Create a worksheet from a PDF that was uploaded through the direct-upload flow.
|
|
93
114
|
*/
|
|
94
115
|
export declare function createWorksheetFromUploadedPdf(classId: string, fileId: string, userId: string): Promise<{
|
|
116
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
95
117
|
name: string;
|
|
96
118
|
id: string;
|
|
97
119
|
createdAt: Date;
|
|
98
120
|
updatedAt: Date;
|
|
99
121
|
classId: string;
|
|
122
|
+
runJobId: string | null;
|
|
123
|
+
sourceUrl: string | null;
|
|
100
124
|
}>;
|
|
101
125
|
export declare function addQuestionToWorksheet(worksheetId: string, userId: string, data: {
|
|
102
126
|
question: string;
|
|
@@ -160,7 +184,7 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
|
|
|
160
184
|
id: string;
|
|
161
185
|
}[];
|
|
162
186
|
} & {
|
|
163
|
-
status: import(".prisma/client").$Enums.
|
|
187
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
164
188
|
id: string;
|
|
165
189
|
createdAt: Date;
|
|
166
190
|
updatedAt: Date | null;
|
|
@@ -172,6 +196,7 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
|
|
|
172
196
|
points: number;
|
|
173
197
|
feedback: string | null;
|
|
174
198
|
studentWorksheetResponseId: string | null;
|
|
199
|
+
runJobId: string | null;
|
|
175
200
|
})[];
|
|
176
201
|
} & {
|
|
177
202
|
id: string;
|
|
@@ -185,7 +210,7 @@ export declare function getWorksheetSubmission(worksheetId: string, submissionId
|
|
|
185
210
|
}>;
|
|
186
211
|
export declare function answerWorksheetQuestion(worksheetResponseId: string, questionId: string, response: string, userId: string): Promise<{
|
|
187
212
|
responses: {
|
|
188
|
-
status: import(".prisma/client").$Enums.
|
|
213
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
189
214
|
id: string;
|
|
190
215
|
createdAt: Date;
|
|
191
216
|
updatedAt: Date | null;
|
|
@@ -197,6 +222,7 @@ export declare function answerWorksheetQuestion(worksheetResponseId: string, que
|
|
|
197
222
|
points: number;
|
|
198
223
|
feedback: string | null;
|
|
199
224
|
studentWorksheetResponseId: string | null;
|
|
225
|
+
runJobId: string | null;
|
|
200
226
|
}[];
|
|
201
227
|
} & {
|
|
202
228
|
id: string;
|
|
@@ -209,7 +235,7 @@ export declare function answerWorksheetQuestion(worksheetResponseId: string, que
|
|
|
209
235
|
worksheetId: string;
|
|
210
236
|
}>;
|
|
211
237
|
export declare function cancelGrading(worksheetResponseId: string, progressId: string, userId: string): Promise<{
|
|
212
|
-
status: import(".prisma/client").$Enums.
|
|
238
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
213
239
|
id: string;
|
|
214
240
|
createdAt: Date;
|
|
215
241
|
updatedAt: Date | null;
|
|
@@ -221,9 +247,10 @@ export declare function cancelGrading(worksheetResponseId: string, progressId: s
|
|
|
221
247
|
points: number;
|
|
222
248
|
feedback: string | null;
|
|
223
249
|
studentWorksheetResponseId: string | null;
|
|
250
|
+
runJobId: string | null;
|
|
224
251
|
}>;
|
|
225
252
|
export declare function regradeQuestion(worksheetResponseId: string, progressId: string, userId: string): Promise<{
|
|
226
|
-
status: import(".prisma/client").$Enums.
|
|
253
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
227
254
|
id: string;
|
|
228
255
|
createdAt: Date;
|
|
229
256
|
updatedAt: Date | null;
|
|
@@ -235,7 +262,8 @@ export declare function regradeQuestion(worksheetResponseId: string, progressId:
|
|
|
235
262
|
points: number;
|
|
236
263
|
feedback: string | null;
|
|
237
264
|
studentWorksheetResponseId: string | null;
|
|
238
|
-
|
|
265
|
+
runJobId: string | null;
|
|
266
|
+
} | null>;
|
|
239
267
|
export declare function gradeAnswer(questionId: string, studentWorksheetResponseId: string, userId: string, data: {
|
|
240
268
|
responseId?: string;
|
|
241
269
|
response?: string;
|
|
@@ -244,7 +272,7 @@ export declare function gradeAnswer(questionId: string, studentWorksheetResponse
|
|
|
244
272
|
markschemeState?: object;
|
|
245
273
|
points?: number;
|
|
246
274
|
}): Promise<{
|
|
247
|
-
status: import(".prisma/client").$Enums.
|
|
275
|
+
status: import(".prisma/client").$Enums.JobStatus | null;
|
|
248
276
|
id: string;
|
|
249
277
|
createdAt: Date;
|
|
250
278
|
updatedAt: Date | null;
|
|
@@ -256,9 +284,9 @@ export declare function gradeAnswer(questionId: string, studentWorksheetResponse
|
|
|
256
284
|
points: number;
|
|
257
285
|
feedback: string | null;
|
|
258
286
|
studentWorksheetResponseId: string | null;
|
|
287
|
+
runJobId: string | null;
|
|
259
288
|
}>;
|
|
260
289
|
export declare function addCommentToResponse(responseId: string, comment: string, authorId: string): Promise<{
|
|
261
|
-
status: import(".prisma/client").$Enums.GenerationStatus | null;
|
|
262
290
|
id: string;
|
|
263
291
|
createdAt: Date;
|
|
264
292
|
assignmentId: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"AAKA,OAAO,
|
|
1
|
+
{"version":3,"file":"worksheet.d.ts","sourceRoot":"/","sources":["services/worksheet.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,9 +3,9 @@
|
|
|
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]="50a37b50-4ec1-5efd-8393-2b0082129103")}catch(e){}}();
|
|
7
7
|
import { TRPCError } from "@trpc/server";
|
|
8
|
-
import {
|
|
8
|
+
import { JobStatus } 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";
|
|
@@ -196,22 +196,20 @@ export async function reorderWorksheetQuestions(worksheetId, userId, movedId, po
|
|
|
196
196
|
message: "Target question not found",
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
|
+
if (movedId === targetId) {
|
|
200
|
+
throw new TRPCError({
|
|
201
|
+
code: "BAD_REQUEST",
|
|
202
|
+
message: "Moved question and target question must be different",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
199
205
|
const withoutMoved = questions.filter((q) => q.id !== movedId);
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
...withoutMoved
|
|
208
|
-
.slice(0, targetIdx + 1)
|
|
209
|
-
.map((item) => ({ id: item.id })),
|
|
210
|
-
{ id: movedId },
|
|
211
|
-
...withoutMoved
|
|
212
|
-
.slice(targetIdx + 1)
|
|
213
|
-
.map((item) => ({ id: item.id })),
|
|
214
|
-
];
|
|
206
|
+
const targetIdxWithoutMoved = withoutMoved.findIndex((q) => q.id === targetId);
|
|
207
|
+
const insertIdx = position === "before" ? targetIdxWithoutMoved : targetIdxWithoutMoved + 1;
|
|
208
|
+
const next = [
|
|
209
|
+
...withoutMoved.slice(0, insertIdx).map((item) => ({ id: item.id })),
|
|
210
|
+
{ id: movedId },
|
|
211
|
+
...withoutMoved.slice(insertIdx).map((item) => ({ id: item.id })),
|
|
212
|
+
];
|
|
215
213
|
await prisma.$transaction(next.map((item, index) => prisma.worksheetQuestion.update({
|
|
216
214
|
where: { id: item.id },
|
|
217
215
|
data: { order: index },
|
|
@@ -287,7 +285,7 @@ export async function answerWorksheetQuestion(worksheetResponseId, questionId, r
|
|
|
287
285
|
...(isMarkableByAlgo && {
|
|
288
286
|
points: response === correctAnswer ? marksAwardedIfCorrect : 0,
|
|
289
287
|
}),
|
|
290
|
-
status:
|
|
288
|
+
status: JobStatus.NOT_STARTED,
|
|
291
289
|
});
|
|
292
290
|
}
|
|
293
291
|
else {
|
|
@@ -398,4 +396,4 @@ export async function addCommentToResponse(responseId, comment, authorId) {
|
|
|
398
396
|
});
|
|
399
397
|
}
|
|
400
398
|
//# sourceMappingURL=worksheet.js.map
|
|
401
|
-
//# debugId=
|
|
399
|
+
//# debugId=50a37b50-4ec1-5efd-8393-2b0082129103
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worksheet.js","sources":["services/worksheet.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Worksheet service – CRUD for worksheets, questions, submissions, and AI grading.\n * Integrates with gradeWorksheet pipeline for auto-grading.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { GenerationStatus, type WorksheetQuestionType } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { getFileUrl } from \"../lib/fileUpload.js\";\nimport { cancelGradePipeline, regradeWorksheetPipeline } from \"../pipelines/gradeWorksheet.js\";\nimport { pdfToWorksheet } from \"../pipelines/pdfToWorksheet.js\";\nimport {\n findWorksheetByIdMinimal,\n findWorksheetByIdForClassMember,\n findWorksheetByIdForTeacher,\n findWorksheetsByClassId,\n createWorksheet,\n duplicateWorksheet,\n updateWorksheet,\n deleteWorksheet,\n findQuestionsByWorksheetId,\n createWorksheetQuestion,\n updateWorksheetQuestion,\n deleteWorksheetQuestion,\n findSubmissionByIdForWorksheetAccess,\n findOrCreateWorksheetResponse,\n findWorksheetResponseByIdForTeacher,\n findWorksheetResponseWithResponsesForStudent,\n findWorksheetQuestionByIdForTeacher,\n findWorksheetQuestionByIdInWorksheet,\n updateStudentQuestionProgress,\n createStudentQuestionProgress,\n findStudentQuestionProgress,\n updateStudentQuestionProgressForGrading,\n createStudentQuestionProgressForGrading,\n createComment,\n findStudentQuestionProgressByIdForTeacher,\n findStudentQuestionProgressByIdForTeacherAndResponse,\n} from \"../models/worksheet.js\";\n\ntype MCQOptions = { id: string; text: string; isCorrect: boolean }[];\n\nconst sanitizeQuestionOptionsForStudent = (options: unknown) => {\n if (!Array.isArray(options)) return options;\n\n return options.map((option) => {\n if (!option || typeof option !== \"object\") return option;\n\n const { isCorrect: _isCorrect, ...rest } = option as Record<string, unknown>;\n return rest;\n });\n};\n\nconst sanitizeWorksheetForStudent = <T extends {\n questions: Array<Record<string, unknown>>;\n}>(worksheet: T) => ({\n ...worksheet,\n questions: worksheet.questions.map((question) => ({\n ...question,\n answer: \"\",\n markScheme: null,\n options: sanitizeQuestionOptionsForStudent(question.options),\n })),\n});\n\n/** Get worksheet with questions and mark schemes. */\nexport async function getWorksheet(worksheetId: string, userId: string) {\n const teacherWorksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (teacherWorksheet) {\n return teacherWorksheet;\n }\n\n const studentWorksheet = await findWorksheetByIdForClassMember(worksheetId, userId);\n if (!studentWorksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet\",\n });\n }\n\n return sanitizeWorksheetForStudent(studentWorksheet);\n}\n\n/** Check if worksheet exists. */\nexport async function worksheetExists(id: string) {\n const worksheet = await findWorksheetByIdMinimal(id);\n return !!worksheet;\n}\n\nexport async function listWorksheets(classId: string) {\n const worksheets = await findWorksheetsByClassId(classId);\n return worksheets.map((w) => ({\n ...w,\n questionCount: w.questions.length,\n }));\n}\n\nexport async function updateWorksheetRecord(\n worksheetId: string,\n userId: string,\n data: { name?: string }\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update worksheets\",\n });\n }\n return updateWorksheet(worksheetId, data);\n}\n\nexport async function deleteWorksheetRecord(worksheetId: string, userId: string) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete worksheets\",\n });\n }\n return deleteWorksheet(worksheetId);\n}\n\nexport async function createWorksheetRecord(classId: string, name: string) {\n return createWorksheet({ classId, name });\n}\n\n/**\n * Duplicate a worksheet into another class. Caller must teach both the source worksheet's class\n * and the target class.\n */\nexport async function duplicateWorksheetRecord(\n worksheetId: string,\n classId: string,\n name: string,\n userId: string\n) {\n const source = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!source) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message:\n \"Worksheet not found or you are not a teacher of the class it belongs to\",\n });\n }\n\n const targetOk = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: { some: { id: userId } },\n },\n select: { id: true },\n });\n\n if (!targetOk) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not a teacher of the target class\",\n });\n }\n\n const created = await duplicateWorksheet(worksheetId, classId, name);\n if (!created) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet not found\",\n });\n }\n\n return created;\n}\n\n/**\n * Create a worksheet by converting a PDF (URL) into structured questions.\n * Uses the `pdfToWorksheet` pipeline to do inference + persistence.\n */\nexport async function createWorksheetFromPdf(\n classId: string,\n url: string\n) {\n const created = await pdfToWorksheet(url, classId);\n if (!created) {\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to create worksheet from PDF\",\n });\n }\n return created;\n}\n\n/**\n * Create a worksheet from a PDF that was uploaded through the direct-upload flow.\n */\nexport async function createWorksheetFromUploadedPdf(\n classId: string,\n fileId: string,\n userId: string\n) {\n const file = await prisma.file.findFirst({\n where: {\n id: fileId,\n userId,\n uploadStatus: \"COMPLETED\",\n },\n select: {\n id: true,\n path: true,\n type: true,\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Uploaded file not found or not ready\",\n });\n }\n\n const isPdf =\n file.type === \"application/pdf\" || file.path.toLowerCase().endsWith(\".pdf\");\n if (!isPdf) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Only PDF files can be converted into worksheets\",\n });\n }\n\n const signedUrl = await getFileUrl(file.path);\n return createWorksheetFromPdf(classId, signedUrl);\n}\n\nexport async function addQuestionToWorksheet(\n worksheetId: string,\n userId: string,\n data: {\n question: string;\n answer: string;\n points?: number;\n options?: object;\n markScheme?: object;\n type: WorksheetQuestionType;\n }\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can modify worksheet questions\",\n });\n }\n return createWorksheetQuestion({\n worksheetId,\n ...data,\n });\n}\n\nexport async function reorderWorksheetQuestions(\n worksheetId: string,\n userId: string,\n movedId: string,\n position: \"before\" | \"after\",\n targetId: string\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can reorder worksheet questions\",\n });\n }\n\n const questions = await findQuestionsByWorksheetId(worksheetId);\n const movedIdx = questions.findIndex((q) => q.id === movedId);\n if (movedIdx === -1) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Moved question not found\",\n });\n }\n const targetIdx = questions.findIndex((q) => q.id === targetId);\n if (targetIdx === -1) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Target question not found\",\n });\n }\n\n const withoutMoved = questions.filter((q) => q.id !== movedId);\n const next =\n position === \"before\"\n ? [\n ...withoutMoved.slice(0, targetIdx).map((item) => ({ id: item.id })),\n { id: movedId },\n ...withoutMoved.slice(targetIdx).map((item) => ({ id: item.id })),\n ]\n : [\n ...withoutMoved\n .slice(0, targetIdx + 1)\n .map((item) => ({ id: item.id })),\n { id: movedId },\n ...withoutMoved\n .slice(targetIdx + 1)\n .map((item) => ({ id: item.id })),\n ];\n\n await prisma.$transaction(\n next.map((item, index) =>\n prisma.worksheetQuestion.update({\n where: { id: item.id },\n data: { order: index },\n })\n )\n );\n return next;\n}\n\nexport async function updateWorksheetQuestionRecord(\n worksheetId: string,\n questionId: string,\n userId: string,\n data: {\n question?: string;\n answer?: string;\n points?: number;\n options?: object;\n markScheme?: object;\n type?: WorksheetQuestionType;\n }\n) {\n const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);\n if (!question || question.worksheetId !== worksheetId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update worksheet questions\",\n });\n }\n return updateWorksheetQuestion(questionId, data);\n}\n\nexport async function deleteWorksheetQuestionRecord(\n worksheetId: string,\n questionId: string,\n userId: string\n) {\n const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);\n if (!question || question.worksheetId !== worksheetId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete worksheet questions\",\n });\n }\n return deleteWorksheetQuestion(questionId);\n}\n\nexport async function getWorksheetSubmission(\n worksheetId: string,\n submissionId: string,\n userId: string\n) {\n const worksheet = await findWorksheetByIdForClassMember(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet\",\n });\n }\n\n const submission = await findSubmissionByIdForWorksheetAccess(\n submissionId,\n worksheetId,\n worksheet.classId,\n userId\n );\n if (!submission) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet submission\",\n });\n }\n return findOrCreateWorksheetResponse(\n submissionId,\n worksheetId,\n submission.studentId\n );\n}\n\nexport async function answerWorksheetQuestion(\n worksheetResponseId: string,\n questionId: string,\n response: string,\n userId: string\n) {\n const worksheetResponse = await findWorksheetResponseWithResponsesForStudent(\n worksheetResponseId,\n userId,\n questionId\n );\n if (!worksheetResponse) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You can only answer your own worksheet responses\",\n });\n }\n\n const question = await findWorksheetQuestionByIdInWorksheet(\n questionId,\n worksheetResponse.worksheetId\n );\n if (!question) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet question not found\",\n });\n }\n\n const isMarkableByAlgo =\n question.type === \"MULTIPLE_CHOICE\" || question.type === \"TRUE_FALSE\";\n const marksAwardedIfCorrect = question.points || 0;\n const correctAnswer = isMarkableByAlgo\n ? question.type === \"MULTIPLE_CHOICE\"\n ? (question.options as MCQOptions)?.find((o) => o.isCorrect)?.id\n : question.answer?.toString()\n : null;\n\n const existingResponse = worksheetResponse.responses[0];\n\n if (existingResponse) {\n await updateStudentQuestionProgress(existingResponse.id, {\n response,\n ...(isMarkableByAlgo && {\n isCorrect: response === correctAnswer,\n }),\n ...(isMarkableByAlgo && {\n points: response === correctAnswer ? marksAwardedIfCorrect : 0,\n }),\n status: GenerationStatus.NOT_STARTED,\n });\n } else {\n await createStudentQuestionProgress({\n studentId: worksheetResponse.studentId,\n questionId,\n response,\n studentWorksheetResponseId: worksheetResponseId,\n ...(isMarkableByAlgo && {\n isCorrect: response === correctAnswer,\n }),\n ...(isMarkableByAlgo && {\n points: response === correctAnswer ? marksAwardedIfCorrect : 0,\n }),\n });\n }\n\n const updated = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: { responses: true },\n });\n return updated!;\n}\n\nexport async function cancelGrading(\n worksheetResponseId: string,\n progressId: string,\n userId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(\n progressId,\n worksheetResponseId,\n userId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can cancel worksheet grading\",\n });\n }\n return cancelGradePipeline(worksheetResponseId, progressId);\n}\n\nexport async function regradeQuestion(\n worksheetResponseId: string,\n progressId: string,\n userId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(\n progressId,\n worksheetResponseId,\n userId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can regrade worksheet answers\",\n });\n }\n return regradeWorksheetPipeline(worksheetResponseId, progressId);\n}\n\nexport async function gradeAnswer(\n questionId: string,\n studentWorksheetResponseId: string,\n userId: string,\n data: {\n responseId?: string;\n response?: string;\n isCorrect: boolean;\n feedback?: string;\n markschemeState?: object;\n points?: number;\n }\n) {\n const worksheetResponse = await findWorksheetResponseByIdForTeacher(\n studentWorksheetResponseId,\n userId\n );\n if (!worksheetResponse) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can grade worksheet answers\",\n });\n }\n\n const question = await findWorksheetQuestionByIdInWorksheet(\n questionId,\n worksheetResponse.worksheetId\n );\n if (!question) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet question not found\",\n });\n }\n\n if (data.responseId) {\n const existingProgress = await findStudentQuestionProgressByIdForTeacher(\n data.responseId,\n userId\n );\n if (\n !existingProgress ||\n existingProgress.studentWorksheetResponseId !== studentWorksheetResponseId ||\n existingProgress.questionId !== questionId\n ) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can grade worksheet answers\",\n });\n }\n\n return updateStudentQuestionProgressForGrading(data.responseId, {\n isCorrect: data.isCorrect,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points,\n });\n }\n\n const existing = await findStudentQuestionProgress(\n worksheetResponse.studentId,\n questionId,\n studentWorksheetResponseId\n );\n\n if (existing) {\n return updateStudentQuestionProgressForGrading(existing.id, {\n isCorrect: data.isCorrect,\n response: data.response,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points,\n });\n }\n\n return createStudentQuestionProgressForGrading({\n studentId: worksheetResponse.studentId,\n questionId,\n studentWorksheetResponseId,\n response: data.response || \"\",\n isCorrect: data.isCorrect,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points || 0,\n });\n}\n\nexport async function addCommentToResponse(\n responseId: string,\n comment: string,\n authorId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacher(\n responseId,\n authorId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can comment on worksheet responses\",\n });\n }\n\n return createComment({\n studentQuestionProgressId: responseId,\n content: comment,\n authorId,\n });\n}\n"],"names":[],"mappings":"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","debug_id":"d15dc113-0231-5e3c-b956-9b5c549c4073"}
|
|
1
|
+
{"version":3,"file":"worksheet.js","sources":["services/worksheet.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Worksheet service – CRUD for worksheets, questions, submissions, and AI grading.\n * Integrates with gradeWorksheet pipeline for auto-grading.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { JobStatus, type WorksheetQuestionType } from \"@prisma/client\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { getFileUrl } from \"../lib/fileUpload.js\";\nimport { cancelGradePipeline, regradeWorksheetPipeline } from \"../pipelines/gradeWorksheet.js\";\nimport { pdfToWorksheet } from \"../pipelines/pdfToWorksheet.js\";\nimport {\n findWorksheetByIdMinimal,\n findWorksheetByIdForClassMember,\n findWorksheetByIdForTeacher,\n findWorksheetsByClassId,\n createWorksheet,\n duplicateWorksheet,\n updateWorksheet,\n deleteWorksheet,\n findQuestionsByWorksheetId,\n createWorksheetQuestion,\n updateWorksheetQuestion,\n deleteWorksheetQuestion,\n findSubmissionByIdForWorksheetAccess,\n findOrCreateWorksheetResponse,\n findWorksheetResponseByIdForTeacher,\n findWorksheetResponseWithResponsesForStudent,\n findWorksheetQuestionByIdForTeacher,\n findWorksheetQuestionByIdInWorksheet,\n updateStudentQuestionProgress,\n createStudentQuestionProgress,\n findStudentQuestionProgress,\n updateStudentQuestionProgressForGrading,\n createStudentQuestionProgressForGrading,\n createComment,\n findStudentQuestionProgressByIdForTeacher,\n findStudentQuestionProgressByIdForTeacherAndResponse,\n} from \"../models/worksheet.js\";\n\ntype MCQOptions = { id: string; text: string; isCorrect: boolean }[];\n\nconst sanitizeQuestionOptionsForStudent = (options: unknown) => {\n if (!Array.isArray(options)) return options;\n\n return options.map((option) => {\n if (!option || typeof option !== \"object\") return option;\n\n const { isCorrect: _isCorrect, ...rest } = option as Record<string, unknown>;\n return rest;\n });\n};\n\nconst sanitizeWorksheetForStudent = <T extends {\n questions: Array<Record<string, unknown>>;\n}>(worksheet: T) => ({\n ...worksheet,\n questions: worksheet.questions.map((question) => ({\n ...question,\n answer: \"\",\n markScheme: null,\n options: sanitizeQuestionOptionsForStudent(question.options),\n })),\n});\n\n/** Get worksheet with questions and mark schemes. */\nexport async function getWorksheet(worksheetId: string, userId: string) {\n const teacherWorksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (teacherWorksheet) {\n return teacherWorksheet;\n }\n\n const studentWorksheet = await findWorksheetByIdForClassMember(worksheetId, userId);\n if (!studentWorksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet\",\n });\n }\n\n return sanitizeWorksheetForStudent(studentWorksheet);\n}\n\n/** Check if worksheet exists. */\nexport async function worksheetExists(id: string) {\n const worksheet = await findWorksheetByIdMinimal(id);\n return !!worksheet;\n}\n\nexport async function listWorksheets(classId: string) {\n const worksheets = await findWorksheetsByClassId(classId);\n return worksheets.map((w) => ({\n ...w,\n questionCount: w.questions.length,\n }));\n}\n\nexport async function updateWorksheetRecord(\n worksheetId: string,\n userId: string,\n data: { name?: string }\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update worksheets\",\n });\n }\n return updateWorksheet(worksheetId, data);\n}\n\nexport async function deleteWorksheetRecord(worksheetId: string, userId: string) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete worksheets\",\n });\n }\n return deleteWorksheet(worksheetId);\n}\n\nexport async function createWorksheetRecord(classId: string, name: string) {\n return createWorksheet({ classId, name });\n}\n\n/**\n * Duplicate a worksheet into another class. Caller must teach both the source worksheet's class\n * and the target class.\n */\nexport async function duplicateWorksheetRecord(\n worksheetId: string,\n classId: string,\n name: string,\n userId: string\n) {\n const source = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!source) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message:\n \"Worksheet not found or you are not a teacher of the class it belongs to\",\n });\n }\n\n const targetOk = await prisma.class.findFirst({\n where: {\n id: classId,\n teachers: { some: { id: userId } },\n },\n select: { id: true },\n });\n\n if (!targetOk) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not a teacher of the target class\",\n });\n }\n\n const created = await duplicateWorksheet(worksheetId, classId, name);\n if (!created) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet not found\",\n });\n }\n\n return created;\n}\n\n/**\n * Create a worksheet by converting a PDF (URL) into structured questions.\n * Uses the `pdfToWorksheet` pipeline to do inference + persistence.\n */\nexport async function createWorksheetFromPdf(\n classId: string,\n url: string\n) {\n const created = await pdfToWorksheet(url, classId);\n if (!created) {\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to create worksheet from PDF\",\n });\n }\n return created;\n}\n\n/**\n * Create a worksheet from a PDF that was uploaded through the direct-upload flow.\n */\nexport async function createWorksheetFromUploadedPdf(\n classId: string,\n fileId: string,\n userId: string\n) {\n const file = await prisma.file.findFirst({\n where: {\n id: fileId,\n userId,\n uploadStatus: \"COMPLETED\",\n },\n select: {\n id: true,\n path: true,\n type: true,\n },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Uploaded file not found or not ready\",\n });\n }\n\n const isPdf =\n file.type === \"application/pdf\" || file.path.toLowerCase().endsWith(\".pdf\");\n if (!isPdf) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Only PDF files can be converted into worksheets\",\n });\n }\n\n const signedUrl = await getFileUrl(file.path);\n return createWorksheetFromPdf(classId, signedUrl);\n}\n\nexport async function addQuestionToWorksheet(\n worksheetId: string,\n userId: string,\n data: {\n question: string;\n answer: string;\n points?: number;\n options?: object;\n markScheme?: object;\n type: WorksheetQuestionType;\n }\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can modify worksheet questions\",\n });\n }\n return createWorksheetQuestion({\n worksheetId,\n ...data,\n });\n}\n\nexport async function reorderWorksheetQuestions(\n worksheetId: string,\n userId: string,\n movedId: string,\n position: \"before\" | \"after\",\n targetId: string\n) {\n const worksheet = await findWorksheetByIdForTeacher(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can reorder worksheet questions\",\n });\n }\n\n const questions = await findQuestionsByWorksheetId(worksheetId);\n const movedIdx = questions.findIndex((q) => q.id === movedId);\n if (movedIdx === -1) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Moved question not found\",\n });\n }\n const targetIdx = questions.findIndex((q) => q.id === targetId);\n if (targetIdx === -1) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Target question not found\",\n });\n }\n if (movedId === targetId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Moved question and target question must be different\",\n });\n }\n\n const withoutMoved = questions.filter((q) => q.id !== movedId);\n const targetIdxWithoutMoved = withoutMoved.findIndex((q) => q.id === targetId);\n const insertIdx =\n position === \"before\" ? targetIdxWithoutMoved : targetIdxWithoutMoved + 1;\n const next = [\n ...withoutMoved.slice(0, insertIdx).map((item) => ({ id: item.id })),\n { id: movedId },\n ...withoutMoved.slice(insertIdx).map((item) => ({ id: item.id })),\n ];\n\n await prisma.$transaction(\n next.map((item, index) =>\n prisma.worksheetQuestion.update({\n where: { id: item.id },\n data: { order: index },\n })\n )\n );\n return next;\n}\n\nexport async function updateWorksheetQuestionRecord(\n worksheetId: string,\n questionId: string,\n userId: string,\n data: {\n question?: string;\n answer?: string;\n points?: number;\n options?: object;\n markScheme?: object;\n type?: WorksheetQuestionType;\n }\n) {\n const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);\n if (!question || question.worksheetId !== worksheetId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can update worksheet questions\",\n });\n }\n return updateWorksheetQuestion(questionId, data);\n}\n\nexport async function deleteWorksheetQuestionRecord(\n worksheetId: string,\n questionId: string,\n userId: string\n) {\n const question = await findWorksheetQuestionByIdForTeacher(questionId, userId);\n if (!question || question.worksheetId !== worksheetId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can delete worksheet questions\",\n });\n }\n return deleteWorksheetQuestion(questionId);\n}\n\nexport async function getWorksheetSubmission(\n worksheetId: string,\n submissionId: string,\n userId: string\n) {\n const worksheet = await findWorksheetByIdForClassMember(worksheetId, userId);\n if (!worksheet) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet\",\n });\n }\n\n const submission = await findSubmissionByIdForWorksheetAccess(\n submissionId,\n worksheetId,\n worksheet.classId,\n userId\n );\n if (!submission) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You do not have access to this worksheet submission\",\n });\n }\n return findOrCreateWorksheetResponse(\n submissionId,\n worksheetId,\n submission.studentId\n );\n}\n\nexport async function answerWorksheetQuestion(\n worksheetResponseId: string,\n questionId: string,\n response: string,\n userId: string\n) {\n const worksheetResponse = await findWorksheetResponseWithResponsesForStudent(\n worksheetResponseId,\n userId,\n questionId\n );\n if (!worksheetResponse) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You can only answer your own worksheet responses\",\n });\n }\n\n const question = await findWorksheetQuestionByIdInWorksheet(\n questionId,\n worksheetResponse.worksheetId\n );\n if (!question) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet question not found\",\n });\n }\n\n const isMarkableByAlgo =\n question.type === \"MULTIPLE_CHOICE\" || question.type === \"TRUE_FALSE\";\n const marksAwardedIfCorrect = question.points || 0;\n const correctAnswer = isMarkableByAlgo\n ? question.type === \"MULTIPLE_CHOICE\"\n ? (question.options as MCQOptions)?.find((o) => o.isCorrect)?.id\n : question.answer?.toString()\n : null;\n\n const existingResponse = worksheetResponse.responses[0];\n\n if (existingResponse) {\n await updateStudentQuestionProgress(existingResponse.id, {\n response,\n ...(isMarkableByAlgo && {\n isCorrect: response === correctAnswer,\n }),\n ...(isMarkableByAlgo && {\n points: response === correctAnswer ? marksAwardedIfCorrect : 0,\n }),\n status: JobStatus.NOT_STARTED,\n });\n } else {\n await createStudentQuestionProgress({\n studentId: worksheetResponse.studentId,\n questionId,\n response,\n studentWorksheetResponseId: worksheetResponseId,\n ...(isMarkableByAlgo && {\n isCorrect: response === correctAnswer,\n }),\n ...(isMarkableByAlgo && {\n points: response === correctAnswer ? marksAwardedIfCorrect : 0,\n }),\n });\n }\n\n const updated = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: { responses: true },\n });\n return updated!;\n}\n\nexport async function cancelGrading(\n worksheetResponseId: string,\n progressId: string,\n userId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(\n progressId,\n worksheetResponseId,\n userId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can cancel worksheet grading\",\n });\n }\n return cancelGradePipeline(worksheetResponseId, progressId);\n}\n\nexport async function regradeQuestion(\n worksheetResponseId: string,\n progressId: string,\n userId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacherAndResponse(\n progressId,\n worksheetResponseId,\n userId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can regrade worksheet answers\",\n });\n }\n return regradeWorksheetPipeline(worksheetResponseId, progressId);\n}\n\nexport async function gradeAnswer(\n questionId: string,\n studentWorksheetResponseId: string,\n userId: string,\n data: {\n responseId?: string;\n response?: string;\n isCorrect: boolean;\n feedback?: string;\n markschemeState?: object;\n points?: number;\n }\n) {\n const worksheetResponse = await findWorksheetResponseByIdForTeacher(\n studentWorksheetResponseId,\n userId\n );\n if (!worksheetResponse) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can grade worksheet answers\",\n });\n }\n\n const question = await findWorksheetQuestionByIdInWorksheet(\n questionId,\n worksheetResponse.worksheetId\n );\n if (!question) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Worksheet question not found\",\n });\n }\n\n if (data.responseId) {\n const existingProgress = await findStudentQuestionProgressByIdForTeacher(\n data.responseId,\n userId\n );\n if (\n !existingProgress ||\n existingProgress.studentWorksheetResponseId !== studentWorksheetResponseId ||\n existingProgress.questionId !== questionId\n ) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can grade worksheet answers\",\n });\n }\n\n return updateStudentQuestionProgressForGrading(data.responseId, {\n isCorrect: data.isCorrect,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points,\n });\n }\n\n const existing = await findStudentQuestionProgress(\n worksheetResponse.studentId,\n questionId,\n studentWorksheetResponseId\n );\n\n if (existing) {\n return updateStudentQuestionProgressForGrading(existing.id, {\n isCorrect: data.isCorrect,\n response: data.response,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points,\n });\n }\n\n return createStudentQuestionProgressForGrading({\n studentId: worksheetResponse.studentId,\n questionId,\n studentWorksheetResponseId,\n response: data.response || \"\",\n isCorrect: data.isCorrect,\n feedback: data.feedback,\n markschemeState: data.markschemeState,\n points: data.points || 0,\n });\n}\n\nexport async function addCommentToResponse(\n responseId: string,\n comment: string,\n authorId: string\n) {\n const progress = await findStudentQuestionProgressByIdForTeacher(\n responseId,\n authorId\n );\n if (!progress) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can comment on worksheet responses\",\n });\n }\n\n return createComment({\n studentQuestionProgressId: responseId,\n content: comment,\n authorId,\n });\n}\n"],"names":[],"mappings":"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","debug_id":"50a37b50-4ec1-5efd-8393-2b0082129103"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Username generator – produces playful adjective+noun+number combos like "greenegg6767".
|
|
3
|
+
* Pass an `isTaken` checker to retry until a unique username is found.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateRandomUsername(): string;
|
|
6
|
+
/** Generate a unique username, calling `isTaken` until one is free. */
|
|
7
|
+
export declare function generateUniqueUsername(isTaken: (username: string) => Promise<boolean>, maxAttempts?: number): Promise<string>;
|
|
8
|
+
//# sourceMappingURL=generateUsername.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateUsername.d.ts","sourceRoot":"/","sources":["utils/generateUsername.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAwFH,wBAAgB,sBAAsB,WAErC;AAED,uEAAuE;AACvE,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAC/C,WAAW,SAAK,mBASjB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Username generator – produces playful adjective+noun+number combos like "greenegg6767".
|
|
3
|
+
* Pass an `isTaken` checker to retry until a unique username is found.
|
|
4
|
+
*/
|
|
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]="bcb62540-cee1-519f-a4b2-9aaf05873b47")}catch(e){}}();
|
|
7
|
+
const ADJECTIVES = [
|
|
8
|
+
"green",
|
|
9
|
+
"blue",
|
|
10
|
+
"red",
|
|
11
|
+
"purple",
|
|
12
|
+
"yellow",
|
|
13
|
+
"orange",
|
|
14
|
+
"pink",
|
|
15
|
+
"silver",
|
|
16
|
+
"golden",
|
|
17
|
+
"fuzzy",
|
|
18
|
+
"happy",
|
|
19
|
+
"sleepy",
|
|
20
|
+
"sneaky",
|
|
21
|
+
"silly",
|
|
22
|
+
"brave",
|
|
23
|
+
"clever",
|
|
24
|
+
"lucky",
|
|
25
|
+
"mighty",
|
|
26
|
+
"quiet",
|
|
27
|
+
"quick",
|
|
28
|
+
"loud",
|
|
29
|
+
"tiny",
|
|
30
|
+
"giant",
|
|
31
|
+
"spicy",
|
|
32
|
+
"jolly",
|
|
33
|
+
"wild",
|
|
34
|
+
"shiny",
|
|
35
|
+
"cosmic",
|
|
36
|
+
"mystic",
|
|
37
|
+
"swift",
|
|
38
|
+
"lazy",
|
|
39
|
+
"grumpy",
|
|
40
|
+
"fluffy",
|
|
41
|
+
"zesty",
|
|
42
|
+
];
|
|
43
|
+
const NOUNS = [
|
|
44
|
+
"egg",
|
|
45
|
+
"cat",
|
|
46
|
+
"dog",
|
|
47
|
+
"fox",
|
|
48
|
+
"owl",
|
|
49
|
+
"bear",
|
|
50
|
+
"duck",
|
|
51
|
+
"frog",
|
|
52
|
+
"wolf",
|
|
53
|
+
"panda",
|
|
54
|
+
"tiger",
|
|
55
|
+
"otter",
|
|
56
|
+
"moose",
|
|
57
|
+
"shark",
|
|
58
|
+
"whale",
|
|
59
|
+
"raven",
|
|
60
|
+
"robin",
|
|
61
|
+
"koala",
|
|
62
|
+
"llama",
|
|
63
|
+
"sloth",
|
|
64
|
+
"narwhal",
|
|
65
|
+
"penguin",
|
|
66
|
+
"dragon",
|
|
67
|
+
"phoenix",
|
|
68
|
+
"muffin",
|
|
69
|
+
"pickle",
|
|
70
|
+
"noodle",
|
|
71
|
+
"potato",
|
|
72
|
+
"taco",
|
|
73
|
+
"pancake",
|
|
74
|
+
"waffle",
|
|
75
|
+
"cookie",
|
|
76
|
+
"biscuit",
|
|
77
|
+
"donut",
|
|
78
|
+
];
|
|
79
|
+
function pick(items) {
|
|
80
|
+
return items[Math.floor(Math.random() * items.length)];
|
|
81
|
+
}
|
|
82
|
+
function randomDigits(n) {
|
|
83
|
+
let out = "";
|
|
84
|
+
for (let i = 0; i < n; i += 1) {
|
|
85
|
+
out += Math.floor(Math.random() * 10).toString();
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
export function generateRandomUsername() {
|
|
90
|
+
return `${pick(ADJECTIVES)}${pick(NOUNS)}${randomDigits(4)}`;
|
|
91
|
+
}
|
|
92
|
+
/** Generate a unique username, calling `isTaken` until one is free. */
|
|
93
|
+
export async function generateUniqueUsername(isTaken, maxAttempts = 10) {
|
|
94
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
95
|
+
const candidate = generateRandomUsername();
|
|
96
|
+
if (!(await isTaken(candidate))) {
|
|
97
|
+
return candidate;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return `${generateRandomUsername()}${randomDigits(4)}`;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=generateUsername.js.map
|
|
103
|
+
//# debugId=bcb62540-cee1-519f-a4b2-9aaf05873b47
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateUsername.js","sources":["utils/generateUsername.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Username generator – produces playful adjective+noun+number combos like \"greenegg6767\".\n * Pass an `isTaken` checker to retry until a unique username is found.\n */\n\nconst ADJECTIVES = [\n \"green\",\n \"blue\",\n \"red\",\n \"purple\",\n \"yellow\",\n \"orange\",\n \"pink\",\n \"silver\",\n \"golden\",\n \"fuzzy\",\n \"happy\",\n \"sleepy\",\n \"sneaky\",\n \"silly\",\n \"brave\",\n \"clever\",\n \"lucky\",\n \"mighty\",\n \"quiet\",\n \"quick\",\n \"loud\",\n \"tiny\",\n \"giant\",\n \"spicy\",\n \"jolly\",\n \"wild\",\n \"shiny\",\n \"cosmic\",\n \"mystic\",\n \"swift\",\n \"lazy\",\n \"grumpy\",\n \"fluffy\",\n \"zesty\",\n];\n\nconst NOUNS = [\n \"egg\",\n \"cat\",\n \"dog\",\n \"fox\",\n \"owl\",\n \"bear\",\n \"duck\",\n \"frog\",\n \"wolf\",\n \"panda\",\n \"tiger\",\n \"otter\",\n \"moose\",\n \"shark\",\n \"whale\",\n \"raven\",\n \"robin\",\n \"koala\",\n \"llama\",\n \"sloth\",\n \"narwhal\",\n \"penguin\",\n \"dragon\",\n \"phoenix\",\n \"muffin\",\n \"pickle\",\n \"noodle\",\n \"potato\",\n \"taco\",\n \"pancake\",\n \"waffle\",\n \"cookie\",\n \"biscuit\",\n \"donut\",\n];\n\nfunction pick<T>(items: readonly T[]) {\n return items[Math.floor(Math.random() * items.length)]!;\n}\n\nfunction randomDigits(n: number) {\n let out = \"\";\n for (let i = 0; i < n; i += 1) {\n out += Math.floor(Math.random() * 10).toString();\n }\n return out;\n}\n\nexport function generateRandomUsername() {\n return `${pick(ADJECTIVES)}${pick(NOUNS)}${randomDigits(4)}`;\n}\n\n/** Generate a unique username, calling `isTaken` until one is free. */\nexport async function generateUniqueUsername(\n isTaken: (username: string) => Promise<boolean>,\n maxAttempts = 10\n) {\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const candidate = generateRandomUsername();\n if (!(await isTaken(candidate))) {\n return candidate;\n }\n }\n return `${generateRandomUsername()}${randomDigits(4)}`;\n}\n"],"names":[],"mappings":"AAAA;;;GAGG;;;AAEH,MAAM,UAAU,GAAG;IACjB,OAAO;IACP,MAAM;IACN,KAAK;IACL,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,OAAO;CACR,CAAC;AAEF,MAAM,KAAK,GAAG;IACZ,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,SAAS;IACT,SAAS;IACT,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO;CACR,CAAC;AAEF,SAAS,IAAI,CAAI,KAAmB;IAClC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAE,CAAC;AAC1D,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/D,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,OAA+C,EAC/C,WAAW,GAAG,EAAE;IAEhB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,sBAAsB,EAAE,CAAC;QAC3C,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YAChC,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,sBAAsB,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;AACzD,CAAC","debug_id":"bcb62540-cee1-519f-a4b2-9aaf05873b47"}
|