@studious-lms/server 1.23.0 → 1.25.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/.env.example +0 -1
- package/data/curriculum/nepal-cdc-g4-g5/README.md +90 -0
- package/data/curriculum/nepal-cdc-g4-g5/manifest.json +97 -0
- package/data/curriculum/nepal-cdc-g4-g5/proficiency-scale.json +76 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/english.json +6936 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/health-physical-creative-arts.json +3401 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/mathematics.json +1511 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/nepali.json +7928 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/science-technology.json +2420 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/social-studies.json +2177 -0
- package/dist/lib/assignmentTypeDefaults.d.ts +41 -0
- package/dist/lib/assignmentTypeDefaults.d.ts.map +1 -0
- package/dist/lib/assignmentTypeDefaults.js +167 -0
- package/dist/lib/assignmentTypeDefaults.js.map +1 -0
- package/dist/lib/config/env.d.ts +0 -8
- package/dist/lib/config/env.d.ts.map +1 -1
- package/dist/lib/config/env.js +2 -5
- package/dist/lib/config/env.js.map +1 -1
- package/dist/lib/feePdf.d.ts +19 -0
- package/dist/lib/feePdf.d.ts.map +1 -0
- package/dist/lib/feePdf.js +167 -0
- package/dist/lib/feePdf.js.map +1 -0
- package/dist/lib/feePdfFormat.d.ts +4 -0
- package/dist/lib/feePdfFormat.d.ts.map +1 -0
- package/dist/lib/feePdfFormat.js +16 -0
- package/dist/lib/feePdfFormat.js.map +1 -0
- package/dist/lib/idCardPdf.d.ts +26 -0
- package/dist/lib/idCardPdf.d.ts.map +1 -0
- package/dist/lib/idCardPdf.js +272 -0
- package/dist/lib/idCardPdf.js.map +1 -0
- package/dist/lib/pdfLetterhead.d.ts +41 -0
- package/dist/lib/pdfLetterhead.d.ts.map +1 -0
- package/dist/lib/pdfLetterhead.js +86 -0
- package/dist/lib/pdfLetterhead.js.map +1 -0
- package/dist/lib/transcriptPdf.d.ts +55 -0
- package/dist/lib/transcriptPdf.d.ts.map +1 -0
- package/dist/lib/transcriptPdf.js +303 -0
- package/dist/lib/transcriptPdf.js.map +1 -0
- package/dist/models/academicCalendar.d.ts +198 -0
- package/dist/models/academicCalendar.d.ts.map +1 -0
- package/dist/models/academicCalendar.js +106 -0
- package/dist/models/academicCalendar.js.map +1 -0
- package/dist/models/agenda.d.ts +8 -0
- package/dist/models/agenda.d.ts.map +1 -1
- package/dist/models/announcement.d.ts +2 -0
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/assignment.d.ts +66 -0
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/attendance.d.ts +659 -21
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/attendance.js +547 -75
- package/dist/models/attendance.js.map +1 -1
- package/dist/models/auth.d.ts +2 -0
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/class.d.ts +12 -0
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +3 -2
- package/dist/models/class.js.map +1 -1
- package/dist/models/event.d.ts +16 -0
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/fees.d.ts +554 -0
- package/dist/models/fees.d.ts.map +1 -0
- package/dist/models/fees.js +278 -0
- package/dist/models/fees.js.map +1 -0
- package/dist/models/file.d.ts +29 -0
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/folder.d.ts +2 -0
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +2 -0
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/marketplace.d.ts +180 -0
- package/dist/models/marketplace.d.ts.map +1 -0
- package/dist/models/marketplace.js +116 -0
- package/dist/models/marketplace.js.map +1 -0
- package/dist/models/moduleItem.d.ts +308 -0
- package/dist/models/moduleItem.d.ts.map +1 -0
- package/dist/models/moduleItem.js +93 -0
- package/dist/models/moduleItem.js.map +1 -0
- package/dist/models/newtonChat.d.ts +1 -0
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/school.d.ts +18 -0
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +7 -2
- package/dist/models/school.js.map +1 -1
- package/dist/models/section.d.ts +4 -0
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/worksheet.d.ts +19 -0
- 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/routers/_app.d.ts +12024 -7778
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +12 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/academicCalendar.d.ts +229 -0
- package/dist/routers/academicCalendar.d.ts.map +1 -0
- package/dist/routers/academicCalendar.js +75 -0
- package/dist/routers/academicCalendar.js.map +1 -0
- package/dist/routers/admin.d.ts +2 -0
- package/dist/routers/admin.d.ts.map +1 -1
- package/dist/routers/agenda.d.ts +8 -0
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +75 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +10 -2
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/attendance.d.ts +103 -0
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +8 -5
- package/dist/routers/attendance.js.map +1 -1
- package/dist/routers/class.d.ts +27 -0
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +6 -2
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/event.d.ts +8 -0
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/fees.d.ts +513 -0
- package/dist/routers/fees.d.ts.map +1 -0
- package/dist/routers/fees.js +88 -0
- package/dist/routers/fees.js.map +1 -0
- package/dist/routers/file.d.ts +2 -0
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/gradebook.d.ts +4 -1
- package/dist/routers/gradebook.d.ts.map +1 -1
- package/dist/routers/marketplace.d.ts +371 -0
- package/dist/routers/marketplace.d.ts.map +1 -0
- package/dist/routers/marketplace.js +87 -0
- package/dist/routers/marketplace.js.map +1 -0
- package/dist/routers/moduleItem.d.ts +263 -0
- package/dist/routers/moduleItem.d.ts.map +1 -0
- package/dist/routers/moduleItem.js +61 -0
- package/dist/routers/moduleItem.js.map +1 -0
- package/dist/routers/parent.d.ts +170 -0
- package/dist/routers/parent.d.ts.map +1 -0
- package/dist/routers/parent.js +49 -0
- package/dist/routers/parent.js.map +1 -0
- package/dist/routers/school.d.ts +363 -7
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +110 -3
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/worksheet.d.ts +27 -0
- 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/academicCalendar.d.ts +173 -0
- package/dist/services/academicCalendar.d.ts.map +1 -0
- package/dist/services/academicCalendar.js +242 -0
- package/dist/services/academicCalendar.js.map +1 -0
- package/dist/services/adminInstitution.d.ts +2 -0
- package/dist/services/adminInstitution.d.ts.map +1 -1
- package/dist/services/agenda.d.ts +8 -0
- package/dist/services/agenda.d.ts.map +1 -1
- package/dist/services/assignment.d.ts +75 -0
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +91 -12
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/attendance.d.ts +327 -10
- package/dist/services/attendance.d.ts.map +1 -1
- package/dist/services/attendance.js +389 -25
- package/dist/services/attendance.js.map +1 -1
- package/dist/services/attendanceMerge.d.ts +53 -0
- package/dist/services/attendanceMerge.d.ts.map +1 -0
- package/dist/services/attendanceMerge.js +87 -0
- package/dist/services/attendanceMerge.js.map +1 -0
- package/dist/services/attendanceRate.d.ts +24 -0
- package/dist/services/attendanceRate.d.ts.map +1 -0
- package/dist/services/attendanceRate.js +31 -0
- package/dist/services/attendanceRate.js.map +1 -0
- package/dist/services/attendanceStats.d.ts +22 -0
- package/dist/services/attendanceStats.d.ts.map +1 -0
- package/dist/services/attendanceStats.js +45 -0
- package/dist/services/attendanceStats.js.map +1 -0
- package/dist/services/class.d.ts +34 -2
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +27 -4
- package/dist/services/class.js.map +1 -1
- package/dist/services/event.d.ts +8 -0
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/fees.d.ts +422 -0
- package/dist/services/fees.d.ts.map +1 -0
- package/dist/services/fees.js +330 -0
- package/dist/services/fees.js.map +1 -0
- package/dist/services/feesAuthz.d.ts +46 -0
- package/dist/services/feesAuthz.d.ts.map +1 -0
- package/dist/services/feesAuthz.js +49 -0
- package/dist/services/feesAuthz.js.map +1 -0
- package/dist/services/file.d.ts +4 -0
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/gradebook.d.ts +18 -1
- package/dist/services/gradebook.d.ts.map +1 -1
- package/dist/services/gradebook.js +57 -2
- package/dist/services/gradebook.js.map +1 -1
- package/dist/services/idCard.d.ts +11 -0
- package/dist/services/idCard.d.ts.map +1 -0
- package/dist/services/idCard.js +122 -0
- package/dist/services/idCard.js.map +1 -0
- package/dist/services/marketplace.d.ts +301 -0
- package/dist/services/marketplace.d.ts.map +1 -0
- package/dist/services/marketplace.js +631 -0
- package/dist/services/marketplace.js.map +1 -0
- package/dist/services/moduleItem.d.ts +196 -0
- package/dist/services/moduleItem.d.ts.map +1 -0
- package/dist/services/moduleItem.js +204 -0
- package/dist/services/moduleItem.js.map +1 -0
- package/dist/services/notification.d.ts +58 -0
- package/dist/services/notification.d.ts.map +1 -1
- package/dist/services/notification.js +30 -2
- package/dist/services/notification.js.map +1 -1
- package/dist/services/parentPortal.d.ts +131 -0
- package/dist/services/parentPortal.d.ts.map +1 -0
- package/dist/services/parentPortal.js +314 -0
- package/dist/services/parentPortal.js.map +1 -0
- package/dist/services/school.d.ts +28 -3
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +98 -54
- package/dist/services/school.js.map +1 -1
- package/dist/services/studentImport.d.ts +34 -0
- package/dist/services/studentImport.d.ts.map +1 -0
- package/dist/services/studentImport.js +249 -0
- package/dist/services/studentImport.js.map +1 -0
- package/dist/services/transcript.d.ts +60 -0
- package/dist/services/transcript.d.ts.map +1 -0
- package/dist/services/transcript.js +231 -0
- package/dist/services/transcript.js.map +1 -0
- package/dist/services/worksheet.d.ts +22 -0
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +94 -2
- package/dist/services/worksheet.js.map +1 -1
- package/dist/trpc.d.ts +1 -1
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +5 -5
- package/dist/trpc.js.map +1 -1
- package/dist/utils/adminPanelAccess.d.ts +4 -5
- package/dist/utils/adminPanelAccess.d.ts.map +1 -1
- package/dist/utils/adminPanelAccess.js +12 -34
- package/dist/utils/adminPanelAccess.js.map +1 -1
- package/package.json +2 -1
- package/prisma/migrations/20260804120000_add_attendance_records_and_excusals/migration.sql +114 -0
- package/prisma/migrations/20260804120100_add_fee_plans_invoices_payments/migration.sql +101 -0
- package/prisma/migrations/20260804120200_add_marketplace_listings/migration.sql +54 -0
- package/prisma/migrations/20260804120300_content_platform_b3_b9/migration.sql +63 -0
- package/prisma/migrations/20260804120400_c_academic_ops/migration.sql +77 -0
- package/prisma/schema.prisma +371 -11
- package/scripts/import-nepal-cdc-curriculum.ts +402 -0
- package/src/lib/assignmentTypeDefaults.ts +222 -0
- package/src/lib/config/env.ts +0 -3
- package/src/lib/feePdf.ts +199 -0
- package/src/lib/feePdfFormat.ts +14 -0
- package/src/lib/idCardPdf.ts +336 -0
- package/src/lib/pdfLetterhead.ts +124 -0
- package/src/lib/transcriptPdf.ts +502 -0
- package/src/models/academicCalendar.ts +139 -0
- package/src/models/attendance.ts +702 -73
- package/src/models/class.ts +1 -0
- package/src/models/fees.ts +349 -0
- package/src/models/marketplace.ts +161 -0
- package/src/models/moduleItem.ts +125 -0
- package/src/models/school.ts +6 -0
- package/src/models/worksheet.ts +2 -0
- package/src/routers/_app.ts +10 -0
- package/src/routers/academicCalendar.ts +103 -0
- package/src/routers/assignment.ts +8 -0
- package/src/routers/attendance.ts +7 -3
- package/src/routers/class.ts +4 -0
- package/src/routers/fees.ts +126 -0
- package/src/routers/marketplace.ts +134 -0
- package/src/routers/moduleItem.ts +91 -0
- package/src/routers/parent.ts +68 -0
- package/src/routers/school.ts +149 -1
- package/src/routers/worksheet.ts +11 -0
- package/src/services/academicCalendar.ts +327 -0
- package/src/services/assignment.ts +122 -12
- package/src/services/attendance.ts +552 -26
- package/src/services/attendanceMerge.ts +139 -0
- package/src/services/attendanceRate.ts +41 -0
- package/src/services/attendanceStats.ts +63 -0
- package/src/services/class.ts +26 -2
- package/src/services/fees.ts +447 -0
- package/src/services/feesAuthz.ts +73 -0
- package/src/services/gradebook.ts +70 -0
- package/src/services/idCard.ts +147 -0
- package/src/services/marketplace.ts +790 -0
- package/src/services/moduleItem.ts +298 -0
- package/src/services/notification.ts +43 -0
- package/src/services/parentPortal.ts +367 -0
- package/src/services/school.ts +100 -54
- package/src/services/studentImport.ts +285 -0
- package/src/services/transcript.ts +301 -0
- package/src/services/worksheet.ts +111 -0
- package/src/trpc.ts +3 -3
- package/src/utils/adminPanelAccess.ts +12 -35
- package/tests/unit/assignmentTypeDefaults.test.ts +52 -0
- package/tests/unit/lib/feePdf.test.ts +15 -0
- package/tests/unit/lib/idCardPdf.test.ts +40 -0
- package/tests/unit/lib/transcriptPdf.test.ts +111 -0
- package/tests/unit/services/attendanceMerge.test.ts +90 -0
- package/tests/unit/services/attendanceRate.test.ts +38 -0
- package/tests/unit/services/attendanceStats.test.ts +54 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace service — publish / search / import for shareable markschemes,
|
|
3
|
+
* worksheets, and files. Exercise/poll types are out of scope for B1 v1.
|
|
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]="b0ba201b-83d8-5de3-a820-a6105cef2863")}catch(e){}}();
|
|
7
|
+
import { TRPCError } from "@trpc/server";
|
|
8
|
+
import { v4 as uuidv4 } from "uuid";
|
|
9
|
+
import { prisma } from "../lib/prisma.js";
|
|
10
|
+
import { copyFile } from "../lib/googleCloudStorage.js";
|
|
11
|
+
import { classTeacherOrSchoolAdminWhere } from "../models/access.js";
|
|
12
|
+
import { findOrCreateScopeRoot } from "../models/cloudStorage.js";
|
|
13
|
+
import { createMarketplaceListing, deleteMarketplaceListing, findMarketplaceListingById, incrementMarketplaceReportCount, searchMarketplaceListings, setMarketplaceListingHidden, } from "../models/marketplace.js";
|
|
14
|
+
import { findActiveSchoolAdminMembership, findSchoolMembership, listUserSchoolMemberships, } from "../models/school.js";
|
|
15
|
+
import { duplicateWorksheetWithClient } from "../models/worksheet.js";
|
|
16
|
+
function normalizeTags(tags) {
|
|
17
|
+
if (!tags?.length)
|
|
18
|
+
return [];
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const raw of tags) {
|
|
22
|
+
const tag = raw.trim().replace(/\s+/g, " ").slice(0, 40);
|
|
23
|
+
if (!tag)
|
|
24
|
+
continue;
|
|
25
|
+
const key = tag.toLowerCase();
|
|
26
|
+
if (seen.has(key))
|
|
27
|
+
continue;
|
|
28
|
+
seen.add(key);
|
|
29
|
+
out.push(tag);
|
|
30
|
+
if (out.length >= 12)
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
async function requireActiveSchoolMember(schoolId, userId) {
|
|
36
|
+
const membership = await findSchoolMembership(schoolId, userId);
|
|
37
|
+
if (!membership || membership.status !== "ACTIVE") {
|
|
38
|
+
throw new TRPCError({
|
|
39
|
+
code: "FORBIDDEN",
|
|
40
|
+
message: "You are not a member of this school",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return membership;
|
|
44
|
+
}
|
|
45
|
+
async function assertCanTeachClass(classId, userId) {
|
|
46
|
+
const cls = await prisma.class.findFirst({
|
|
47
|
+
where: { id: classId, ...classTeacherOrSchoolAdminWhere(userId) },
|
|
48
|
+
select: { id: true, schoolId: true, departmentId: true },
|
|
49
|
+
});
|
|
50
|
+
if (!cls) {
|
|
51
|
+
throw new TRPCError({
|
|
52
|
+
code: "FORBIDDEN",
|
|
53
|
+
message: "You don't have permission to use this class",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return cls;
|
|
57
|
+
}
|
|
58
|
+
async function assertCanAccessListing(listing, userId) {
|
|
59
|
+
if (listing.authorId === userId)
|
|
60
|
+
return;
|
|
61
|
+
if (listing.isHidden) {
|
|
62
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Listing not found" });
|
|
63
|
+
}
|
|
64
|
+
if (listing.visibility === "PUBLIC")
|
|
65
|
+
return;
|
|
66
|
+
if (!listing.schoolId) {
|
|
67
|
+
throw new TRPCError({ code: "FORBIDDEN", message: "Listing is not available" });
|
|
68
|
+
}
|
|
69
|
+
await requireActiveSchoolMember(listing.schoolId, userId);
|
|
70
|
+
}
|
|
71
|
+
function asPayload(raw) {
|
|
72
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
73
|
+
throw new TRPCError({
|
|
74
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
75
|
+
message: "Listing payload is invalid",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const kind = raw.kind;
|
|
79
|
+
if (kind === "MARKSCHEME" || kind === "WORKSHEET" || kind === "FILE") {
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
82
|
+
throw new TRPCError({
|
|
83
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
84
|
+
message: "Listing payload is invalid",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export async function listMarketplace(userId, input) {
|
|
88
|
+
const memberships = await listUserSchoolMemberships(userId);
|
|
89
|
+
if (input.schoolId &&
|
|
90
|
+
!memberships.some((m) => m.schoolId === input.schoolId)) {
|
|
91
|
+
return { items: [], nextCursor: undefined };
|
|
92
|
+
}
|
|
93
|
+
const schoolIds = input.schoolId
|
|
94
|
+
? [input.schoolId]
|
|
95
|
+
: memberships.map((m) => m.schoolId);
|
|
96
|
+
return searchMarketplaceListings({
|
|
97
|
+
schoolIds,
|
|
98
|
+
scopeSchoolId: input.schoolId,
|
|
99
|
+
type: input.type,
|
|
100
|
+
search: input.search,
|
|
101
|
+
tags: input.tags,
|
|
102
|
+
cursor: input.cursor,
|
|
103
|
+
limit: input.limit ?? 20,
|
|
104
|
+
authorId: userId,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
export async function getMarketplaceListing(userId, listingId) {
|
|
108
|
+
const listing = await findMarketplaceListingById(listingId);
|
|
109
|
+
if (!listing) {
|
|
110
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Listing not found" });
|
|
111
|
+
}
|
|
112
|
+
await assertCanAccessListing(listing, userId);
|
|
113
|
+
return listing;
|
|
114
|
+
}
|
|
115
|
+
export async function publishMarkSchemeListing(userId, input) {
|
|
116
|
+
const markScheme = await prisma.markScheme.findUnique({
|
|
117
|
+
where: { id: input.markSchemeId },
|
|
118
|
+
select: {
|
|
119
|
+
id: true,
|
|
120
|
+
name: true,
|
|
121
|
+
structured: true,
|
|
122
|
+
classId: true,
|
|
123
|
+
departmentId: true,
|
|
124
|
+
class: { select: { schoolId: true } },
|
|
125
|
+
department: { select: { schoolId: true } },
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
if (!markScheme) {
|
|
129
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Mark scheme not found" });
|
|
130
|
+
}
|
|
131
|
+
let schoolId = input.schoolId ??
|
|
132
|
+
markScheme.class?.schoolId ??
|
|
133
|
+
markScheme.department?.schoolId ??
|
|
134
|
+
null;
|
|
135
|
+
if (markScheme.classId) {
|
|
136
|
+
await assertCanTeachClass(markScheme.classId, userId);
|
|
137
|
+
}
|
|
138
|
+
else if (markScheme.departmentId && schoolId) {
|
|
139
|
+
const admin = await findActiveSchoolAdminMembership(schoolId, userId);
|
|
140
|
+
if (!admin) {
|
|
141
|
+
throw new TRPCError({
|
|
142
|
+
code: "FORBIDDEN",
|
|
143
|
+
message: "Only school admins can publish department mark schemes",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
throw new TRPCError({
|
|
149
|
+
code: "BAD_REQUEST",
|
|
150
|
+
message: "Mark scheme is not scoped to a class or department",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (input.visibility === "SCHOOL") {
|
|
154
|
+
if (!schoolId) {
|
|
155
|
+
throw new TRPCError({
|
|
156
|
+
code: "BAD_REQUEST",
|
|
157
|
+
message: "schoolId is required for SCHOOL visibility",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
await requireActiveSchoolMember(schoolId, userId);
|
|
161
|
+
}
|
|
162
|
+
else if (schoolId) {
|
|
163
|
+
await requireActiveSchoolMember(schoolId, userId);
|
|
164
|
+
}
|
|
165
|
+
const payload = {
|
|
166
|
+
kind: "MARKSCHEME",
|
|
167
|
+
name: markScheme.name,
|
|
168
|
+
structured: markScheme.structured,
|
|
169
|
+
};
|
|
170
|
+
return createMarketplaceListing({
|
|
171
|
+
type: "MARKSCHEME",
|
|
172
|
+
title: input.title.trim(),
|
|
173
|
+
description: input.description?.trim() || null,
|
|
174
|
+
authorId: userId,
|
|
175
|
+
schoolId,
|
|
176
|
+
visibility: input.visibility,
|
|
177
|
+
markSchemeId: markScheme.id,
|
|
178
|
+
payload,
|
|
179
|
+
tags: normalizeTags(input.tags),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export async function publishWorksheetListing(userId, input) {
|
|
183
|
+
const worksheet = await prisma.worksheet.findFirst({
|
|
184
|
+
where: {
|
|
185
|
+
id: input.worksheetId,
|
|
186
|
+
class: classTeacherOrSchoolAdminWhere(userId),
|
|
187
|
+
},
|
|
188
|
+
include: {
|
|
189
|
+
questions: { orderBy: { order: "asc" } },
|
|
190
|
+
class: { select: { schoolId: true } },
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
if (!worksheet) {
|
|
194
|
+
throw new TRPCError({
|
|
195
|
+
code: "NOT_FOUND",
|
|
196
|
+
message: "Worksheet not found or you don't have permission to publish it",
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const schoolId = input.schoolId ?? worksheet.class?.schoolId ?? null;
|
|
200
|
+
if (input.visibility === "SCHOOL") {
|
|
201
|
+
if (!schoolId) {
|
|
202
|
+
throw new TRPCError({
|
|
203
|
+
code: "BAD_REQUEST",
|
|
204
|
+
message: "schoolId is required for SCHOOL visibility",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
await requireActiveSchoolMember(schoolId, userId);
|
|
208
|
+
}
|
|
209
|
+
else if (schoolId) {
|
|
210
|
+
await requireActiveSchoolMember(schoolId, userId);
|
|
211
|
+
}
|
|
212
|
+
const payload = {
|
|
213
|
+
kind: "WORKSHEET",
|
|
214
|
+
name: worksheet.name,
|
|
215
|
+
questions: worksheet.questions.map((q) => ({
|
|
216
|
+
type: q.type,
|
|
217
|
+
question: q.question,
|
|
218
|
+
answer: q.answer,
|
|
219
|
+
options: q.options ?? {},
|
|
220
|
+
markScheme: q.markScheme ?? {},
|
|
221
|
+
points: q.points,
|
|
222
|
+
order: q.order,
|
|
223
|
+
})),
|
|
224
|
+
};
|
|
225
|
+
return createMarketplaceListing({
|
|
226
|
+
type: "WORKSHEET",
|
|
227
|
+
title: input.title.trim(),
|
|
228
|
+
description: input.description?.trim() || null,
|
|
229
|
+
authorId: userId,
|
|
230
|
+
schoolId,
|
|
231
|
+
visibility: input.visibility,
|
|
232
|
+
worksheetId: worksheet.id,
|
|
233
|
+
payload,
|
|
234
|
+
tags: normalizeTags(input.tags),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
export async function publishFileListing(userId, input) {
|
|
238
|
+
const file = await prisma.file.findUnique({
|
|
239
|
+
where: { id: input.fileId },
|
|
240
|
+
select: {
|
|
241
|
+
id: true,
|
|
242
|
+
name: true,
|
|
243
|
+
type: true,
|
|
244
|
+
size: true,
|
|
245
|
+
path: true,
|
|
246
|
+
userId: true,
|
|
247
|
+
folderId: true,
|
|
248
|
+
uploadStatus: true,
|
|
249
|
+
folder: {
|
|
250
|
+
select: {
|
|
251
|
+
classId: true,
|
|
252
|
+
schoolStorageId: true,
|
|
253
|
+
personalOwnerId: true,
|
|
254
|
+
class: { select: { schoolId: true } },
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
if (!file || file.uploadStatus === "FAILED" || file.uploadStatus === "CANCELLED") {
|
|
260
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "File not found" });
|
|
261
|
+
}
|
|
262
|
+
const isOwner = file.userId === userId;
|
|
263
|
+
const isPersonal = file.folder?.personalOwnerId === userId;
|
|
264
|
+
let canPublish = isOwner || isPersonal;
|
|
265
|
+
let schoolId = input.schoolId ?? file.folder?.schoolStorageId ?? file.folder?.class?.schoolId ?? null;
|
|
266
|
+
if (!canPublish && file.folder?.classId) {
|
|
267
|
+
await assertCanTeachClass(file.folder.classId, userId);
|
|
268
|
+
canPublish = true;
|
|
269
|
+
}
|
|
270
|
+
if (!canPublish && file.folder?.schoolStorageId) {
|
|
271
|
+
const admin = await findActiveSchoolAdminMembership(file.folder.schoolStorageId, userId);
|
|
272
|
+
if (!admin) {
|
|
273
|
+
throw new TRPCError({
|
|
274
|
+
code: "FORBIDDEN",
|
|
275
|
+
message: "You don't have permission to publish this file",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
canPublish = true;
|
|
279
|
+
schoolId = schoolId ?? file.folder.schoolStorageId;
|
|
280
|
+
}
|
|
281
|
+
if (!canPublish) {
|
|
282
|
+
throw new TRPCError({
|
|
283
|
+
code: "FORBIDDEN",
|
|
284
|
+
message: "You don't have permission to publish this file",
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (input.visibility === "SCHOOL") {
|
|
288
|
+
if (!schoolId) {
|
|
289
|
+
throw new TRPCError({
|
|
290
|
+
code: "BAD_REQUEST",
|
|
291
|
+
message: "schoolId is required for SCHOOL visibility",
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
await requireActiveSchoolMember(schoolId, userId);
|
|
295
|
+
}
|
|
296
|
+
else if (schoolId) {
|
|
297
|
+
await requireActiveSchoolMember(schoolId, userId);
|
|
298
|
+
}
|
|
299
|
+
const payload = {
|
|
300
|
+
kind: "FILE",
|
|
301
|
+
name: file.name,
|
|
302
|
+
type: file.type,
|
|
303
|
+
size: file.size,
|
|
304
|
+
path: file.path,
|
|
305
|
+
};
|
|
306
|
+
return createMarketplaceListing({
|
|
307
|
+
type: "FILE",
|
|
308
|
+
title: input.title.trim(),
|
|
309
|
+
description: input.description?.trim() || null,
|
|
310
|
+
authorId: userId,
|
|
311
|
+
schoolId,
|
|
312
|
+
visibility: input.visibility,
|
|
313
|
+
fileId: file.id,
|
|
314
|
+
payload,
|
|
315
|
+
tags: normalizeTags(input.tags),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Import a listing into the caller's workspace:
|
|
320
|
+
* - MARKSCHEME → class mark scheme (or department template when departmentId given)
|
|
321
|
+
* - WORKSHEET → class worksheet copy
|
|
322
|
+
* - FILE → personal / class / school folder copy (GCS object copied)
|
|
323
|
+
*/
|
|
324
|
+
export async function importMarketplaceListing(userId, input) {
|
|
325
|
+
const listing = await findMarketplaceListingById(input.listingId);
|
|
326
|
+
if (!listing) {
|
|
327
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Listing not found" });
|
|
328
|
+
}
|
|
329
|
+
await assertCanAccessListing(listing, userId);
|
|
330
|
+
const payload = asPayload(listing.payload);
|
|
331
|
+
const name = input.name?.trim();
|
|
332
|
+
if (listing.type === "MARKSCHEME") {
|
|
333
|
+
if (payload.kind !== "MARKSCHEME") {
|
|
334
|
+
throw new TRPCError({
|
|
335
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
336
|
+
message: "Mark scheme payload mismatch",
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
if (input.departmentId) {
|
|
340
|
+
const department = await prisma.department.findUnique({
|
|
341
|
+
where: { id: input.departmentId },
|
|
342
|
+
select: { id: true, schoolId: true },
|
|
343
|
+
});
|
|
344
|
+
if (!department) {
|
|
345
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Department not found" });
|
|
346
|
+
}
|
|
347
|
+
const admin = await findActiveSchoolAdminMembership(department.schoolId, userId);
|
|
348
|
+
if (!admin) {
|
|
349
|
+
throw new TRPCError({
|
|
350
|
+
code: "FORBIDDEN",
|
|
351
|
+
message: "Only school admins can import into a department",
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
type: "MARKSCHEME",
|
|
356
|
+
markScheme: await prisma.markScheme.create({
|
|
357
|
+
data: {
|
|
358
|
+
department: { connect: { id: department.id } },
|
|
359
|
+
name: name || payload.name || listing.title,
|
|
360
|
+
structured: payload.structured,
|
|
361
|
+
},
|
|
362
|
+
select: { id: true, name: true, departmentId: true, classId: true },
|
|
363
|
+
}),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (!input.classId) {
|
|
367
|
+
throw new TRPCError({
|
|
368
|
+
code: "BAD_REQUEST",
|
|
369
|
+
message: "classId is required to import a mark scheme",
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
await assertCanTeachClass(input.classId, userId);
|
|
373
|
+
return {
|
|
374
|
+
type: "MARKSCHEME",
|
|
375
|
+
markScheme: await prisma.markScheme.create({
|
|
376
|
+
data: {
|
|
377
|
+
class: { connect: { id: input.classId } },
|
|
378
|
+
name: name || payload.name || listing.title,
|
|
379
|
+
structured: payload.structured,
|
|
380
|
+
},
|
|
381
|
+
select: { id: true, name: true, departmentId: true, classId: true },
|
|
382
|
+
}),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (listing.type === "WORKSHEET") {
|
|
386
|
+
if (payload.kind !== "WORKSHEET") {
|
|
387
|
+
throw new TRPCError({
|
|
388
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
389
|
+
message: "Worksheet payload mismatch",
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (!input.classId) {
|
|
393
|
+
throw new TRPCError({
|
|
394
|
+
code: "BAD_REQUEST",
|
|
395
|
+
message: "classId is required to import a worksheet",
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
await assertCanTeachClass(input.classId, userId);
|
|
399
|
+
// Prefer live source clone when still available; otherwise rebuild from snapshot.
|
|
400
|
+
if (listing.worksheetId) {
|
|
401
|
+
const created = await prisma.$transaction((tx) => duplicateWorksheetWithClient(tx, listing.worksheetId, input.classId, name || payload.name || listing.title));
|
|
402
|
+
if (created) {
|
|
403
|
+
return { type: "WORKSHEET", worksheet: created };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const created = await prisma.worksheet.create({
|
|
407
|
+
data: {
|
|
408
|
+
classId: input.classId,
|
|
409
|
+
name: name || payload.name || listing.title,
|
|
410
|
+
questions: {
|
|
411
|
+
create: payload.questions.map((q) => ({
|
|
412
|
+
type: q.type,
|
|
413
|
+
question: q.question,
|
|
414
|
+
answer: q.answer,
|
|
415
|
+
options: q.options ?? {},
|
|
416
|
+
markScheme: q.markScheme ?? {},
|
|
417
|
+
points: q.points,
|
|
418
|
+
order: q.order,
|
|
419
|
+
})),
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
select: { id: true, name: true, classId: true },
|
|
423
|
+
});
|
|
424
|
+
return { type: "WORKSHEET", worksheet: created };
|
|
425
|
+
}
|
|
426
|
+
if (listing.type === "FILE") {
|
|
427
|
+
if (payload.kind !== "FILE") {
|
|
428
|
+
throw new TRPCError({
|
|
429
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
430
|
+
message: "File payload mismatch",
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
let targetFolderId = input.folderId;
|
|
434
|
+
if (!targetFolderId) {
|
|
435
|
+
if (input.classId) {
|
|
436
|
+
await assertCanTeachClass(input.classId, userId);
|
|
437
|
+
const root = await findOrCreateScopeRoot({
|
|
438
|
+
kind: "CLASS",
|
|
439
|
+
classId: input.classId,
|
|
440
|
+
});
|
|
441
|
+
targetFolderId = root.id;
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
const root = await findOrCreateScopeRoot({
|
|
445
|
+
kind: "PERSONAL",
|
|
446
|
+
userId,
|
|
447
|
+
});
|
|
448
|
+
targetFolderId = root.id;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
const folder = await prisma.folder.findUnique({
|
|
453
|
+
where: { id: targetFolderId },
|
|
454
|
+
select: {
|
|
455
|
+
id: true,
|
|
456
|
+
classId: true,
|
|
457
|
+
schoolStorageId: true,
|
|
458
|
+
personalOwnerId: true,
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
if (!folder) {
|
|
462
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Folder not found" });
|
|
463
|
+
}
|
|
464
|
+
if (folder.personalOwnerId && folder.personalOwnerId !== userId) {
|
|
465
|
+
throw new TRPCError({
|
|
466
|
+
code: "FORBIDDEN",
|
|
467
|
+
message: "You don't have permission to import into this folder",
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (folder.classId) {
|
|
471
|
+
await assertCanTeachClass(folder.classId, userId);
|
|
472
|
+
}
|
|
473
|
+
if (folder.schoolStorageId) {
|
|
474
|
+
const admin = await findActiveSchoolAdminMembership(folder.schoolStorageId, userId);
|
|
475
|
+
if (!admin) {
|
|
476
|
+
throw new TRPCError({
|
|
477
|
+
code: "FORBIDDEN",
|
|
478
|
+
message: "Only school admins can import into school storage",
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const ext = payload.name.includes(".")
|
|
484
|
+
? payload.name.slice(payload.name.lastIndexOf("."))
|
|
485
|
+
: "";
|
|
486
|
+
const newPath = `marketplace/${uuidv4()}${ext}`;
|
|
487
|
+
await copyFile(payload.path, newPath);
|
|
488
|
+
const created = await prisma.file.create({
|
|
489
|
+
data: {
|
|
490
|
+
name: name || payload.name || listing.title,
|
|
491
|
+
type: payload.type,
|
|
492
|
+
size: payload.size ?? undefined,
|
|
493
|
+
path: newPath,
|
|
494
|
+
userId,
|
|
495
|
+
folderId: targetFolderId,
|
|
496
|
+
uploadStatus: "COMPLETED",
|
|
497
|
+
uploadedAt: new Date(),
|
|
498
|
+
},
|
|
499
|
+
select: { id: true, name: true, folderId: true, path: true, type: true },
|
|
500
|
+
});
|
|
501
|
+
return { type: "FILE", file: created };
|
|
502
|
+
}
|
|
503
|
+
throw new TRPCError({
|
|
504
|
+
code: "BAD_REQUEST",
|
|
505
|
+
message: "Unsupported listing type",
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
export async function reportMarketplaceListing(userId, listingId) {
|
|
509
|
+
const listing = await findMarketplaceListingById(listingId);
|
|
510
|
+
if (!listing) {
|
|
511
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Listing not found" });
|
|
512
|
+
}
|
|
513
|
+
await assertCanAccessListing(listing, userId);
|
|
514
|
+
// C10 light stub — increment only; no reporter dedupe table yet.
|
|
515
|
+
return incrementMarketplaceReportCount(listingId);
|
|
516
|
+
}
|
|
517
|
+
export async function hideMarketplaceListing(userId, listingId, isHidden) {
|
|
518
|
+
const listing = await findMarketplaceListingById(listingId);
|
|
519
|
+
if (!listing) {
|
|
520
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Listing not found" });
|
|
521
|
+
}
|
|
522
|
+
const isAuthor = listing.authorId === userId;
|
|
523
|
+
const isSchoolAdmin = listing.schoolId != null &&
|
|
524
|
+
Boolean(await findActiveSchoolAdminMembership(listing.schoolId, userId));
|
|
525
|
+
if (!isAuthor && !isSchoolAdmin) {
|
|
526
|
+
throw new TRPCError({
|
|
527
|
+
code: "FORBIDDEN",
|
|
528
|
+
message: "Only the author or a school admin can hide this listing",
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return setMarketplaceListingHidden(listingId, isHidden);
|
|
532
|
+
}
|
|
533
|
+
export async function unpublishMarketplaceListing(userId, listingId) {
|
|
534
|
+
const listing = await findMarketplaceListingById(listingId);
|
|
535
|
+
if (!listing) {
|
|
536
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Listing not found" });
|
|
537
|
+
}
|
|
538
|
+
if (listing.authorId !== userId) {
|
|
539
|
+
const isSchoolAdmin = listing.schoolId != null &&
|
|
540
|
+
Boolean(await findActiveSchoolAdminMembership(listing.schoolId, userId));
|
|
541
|
+
if (!isSchoolAdmin) {
|
|
542
|
+
throw new TRPCError({
|
|
543
|
+
code: "FORBIDDEN",
|
|
544
|
+
message: "Only the author or a school admin can unpublish this listing",
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
await setMarketplaceListingHidden(listingId, true);
|
|
548
|
+
return { success: true, deleted: false };
|
|
549
|
+
}
|
|
550
|
+
const result = await deleteMarketplaceListing(listingId, userId);
|
|
551
|
+
return { success: true, deleted: result.count > 0 };
|
|
552
|
+
}
|
|
553
|
+
/** Publishable sources for the current user (for the publish dialog). */
|
|
554
|
+
export async function listPublishableSources(userId, input) {
|
|
555
|
+
const classFilter = input.classId
|
|
556
|
+
? { id: input.classId, ...classTeacherOrSchoolAdminWhere(userId) }
|
|
557
|
+
: classTeacherOrSchoolAdminWhere(userId);
|
|
558
|
+
const [markSchemes, worksheets, files] = await Promise.all([
|
|
559
|
+
prisma.markScheme.findMany({
|
|
560
|
+
where: {
|
|
561
|
+
OR: [
|
|
562
|
+
{ class: classFilter },
|
|
563
|
+
...(input.schoolId
|
|
564
|
+
? [
|
|
565
|
+
{
|
|
566
|
+
department: {
|
|
567
|
+
schoolId: input.schoolId,
|
|
568
|
+
school: {
|
|
569
|
+
memberships: {
|
|
570
|
+
some: {
|
|
571
|
+
userId,
|
|
572
|
+
role: "ADMIN",
|
|
573
|
+
status: "ACTIVE",
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
]
|
|
580
|
+
: []),
|
|
581
|
+
],
|
|
582
|
+
},
|
|
583
|
+
select: {
|
|
584
|
+
id: true,
|
|
585
|
+
name: true,
|
|
586
|
+
classId: true,
|
|
587
|
+
departmentId: true,
|
|
588
|
+
class: { select: { name: true } },
|
|
589
|
+
department: { select: { name: true } },
|
|
590
|
+
},
|
|
591
|
+
take: 50,
|
|
592
|
+
orderBy: { id: "desc" },
|
|
593
|
+
}),
|
|
594
|
+
prisma.worksheet.findMany({
|
|
595
|
+
where: { class: classFilter },
|
|
596
|
+
select: {
|
|
597
|
+
id: true,
|
|
598
|
+
name: true,
|
|
599
|
+
classId: true,
|
|
600
|
+
class: { select: { name: true } },
|
|
601
|
+
_count: { select: { questions: true } },
|
|
602
|
+
},
|
|
603
|
+
take: 50,
|
|
604
|
+
orderBy: { updatedAt: "desc" },
|
|
605
|
+
}),
|
|
606
|
+
prisma.file.findMany({
|
|
607
|
+
where: {
|
|
608
|
+
uploadStatus: "COMPLETED",
|
|
609
|
+
OR: [
|
|
610
|
+
{ userId },
|
|
611
|
+
{ folder: { personalOwnerId: userId } },
|
|
612
|
+
...(input.classId
|
|
613
|
+
? [{ folder: { classId: input.classId, class: classFilter } }]
|
|
614
|
+
: [{ folder: { class: classFilter } }]),
|
|
615
|
+
],
|
|
616
|
+
},
|
|
617
|
+
select: {
|
|
618
|
+
id: true,
|
|
619
|
+
name: true,
|
|
620
|
+
type: true,
|
|
621
|
+
size: true,
|
|
622
|
+
folderId: true,
|
|
623
|
+
},
|
|
624
|
+
take: 50,
|
|
625
|
+
orderBy: { uploadedAt: "desc" },
|
|
626
|
+
}),
|
|
627
|
+
]);
|
|
628
|
+
return { markSchemes, worksheets, files };
|
|
629
|
+
}
|
|
630
|
+
//# sourceMappingURL=marketplace.js.map
|
|
631
|
+
//# debugId=b0ba201b-83d8-5de3-a820-a6105cef2863
|