@studious-lms/server 1.1.0 → 1.1.2
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/routers/_app.d.ts +256 -4
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +86 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +57 -0
- package/dist/routers/class.d.ts +10 -0
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +1 -0
- package/dist/routers/event.d.ts +2 -0
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/folder.d.ts +13 -2
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +19 -6
- package/dist/routers/section.d.ts +17 -0
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +73 -0
- package/package.json +1 -1
- package/prisma/migrations/20250919063704_init2/migration.sql +24 -0
- package/prisma/schema.prisma +5 -0
- package/src/routers/assignment.ts +90 -12
- package/src/routers/class.ts +2 -1
- package/src/routers/folder.ts +19 -6
- package/src/routers/section.ts +86 -0
|
@@ -20,11 +20,14 @@ export declare const sectionRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
20
20
|
input: {
|
|
21
21
|
name: string;
|
|
22
22
|
classId: string;
|
|
23
|
+
color?: string | undefined;
|
|
23
24
|
};
|
|
24
25
|
output: {
|
|
25
26
|
id: string;
|
|
26
27
|
name: string;
|
|
28
|
+
color: string | null;
|
|
27
29
|
classId: string;
|
|
30
|
+
order: number | null;
|
|
28
31
|
};
|
|
29
32
|
meta: object;
|
|
30
33
|
}>;
|
|
@@ -33,11 +36,25 @@ export declare const sectionRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
33
36
|
id: string;
|
|
34
37
|
name: string;
|
|
35
38
|
classId: string;
|
|
39
|
+
color?: string | undefined;
|
|
36
40
|
};
|
|
37
41
|
output: {
|
|
38
42
|
id: string;
|
|
39
43
|
name: string;
|
|
44
|
+
color: string | null;
|
|
40
45
|
classId: string;
|
|
46
|
+
order: number | null;
|
|
47
|
+
};
|
|
48
|
+
meta: object;
|
|
49
|
+
}>;
|
|
50
|
+
reOrder: import("@trpc/server").TRPCMutationProcedure<{
|
|
51
|
+
input: {
|
|
52
|
+
id: string;
|
|
53
|
+
classId: string;
|
|
54
|
+
order: number;
|
|
55
|
+
};
|
|
56
|
+
output: {
|
|
57
|
+
id: string;
|
|
41
58
|
};
|
|
42
59
|
meta: object;
|
|
43
60
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../src/routers/section.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../src/routers/section.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuBxB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwMxB,CAAC"}
|
package/dist/routers/section.js
CHANGED
|
@@ -5,11 +5,13 @@ import { prisma } from "../lib/prisma.js";
|
|
|
5
5
|
const createSectionSchema = z.object({
|
|
6
6
|
classId: z.string(),
|
|
7
7
|
name: z.string(),
|
|
8
|
+
color: z.string().optional(),
|
|
8
9
|
});
|
|
9
10
|
const updateSectionSchema = z.object({
|
|
10
11
|
id: z.string(),
|
|
11
12
|
classId: z.string(),
|
|
12
13
|
name: z.string(),
|
|
14
|
+
color: z.string().optional(),
|
|
13
15
|
});
|
|
14
16
|
const deleteSectionSchema = z.object({
|
|
15
17
|
id: z.string(),
|
|
@@ -45,11 +47,41 @@ export const sectionRouter = createTRPCRouter({
|
|
|
45
47
|
const section = await prisma.section.create({
|
|
46
48
|
data: {
|
|
47
49
|
name: input.name,
|
|
50
|
+
order: 0,
|
|
48
51
|
class: {
|
|
49
52
|
connect: { id: input.classId },
|
|
50
53
|
},
|
|
54
|
+
...(input.color && {
|
|
55
|
+
color: input.color,
|
|
56
|
+
}),
|
|
51
57
|
},
|
|
52
58
|
});
|
|
59
|
+
// find all root items in the class and reorder them
|
|
60
|
+
const sections = await prisma.section.findMany({
|
|
61
|
+
where: {
|
|
62
|
+
classId: input.classId,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
const assignments = await prisma.assignment.findMany({
|
|
66
|
+
where: {
|
|
67
|
+
classId: input.classId,
|
|
68
|
+
sectionId: null,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
const stack = [...sections, ...assignments].sort((a, b) => (a.order || 0) - (b.order || 0)).map((item, index) => ({
|
|
72
|
+
id: item.id,
|
|
73
|
+
order: index + 1,
|
|
74
|
+
})).map((item) => ({
|
|
75
|
+
where: { id: item.id },
|
|
76
|
+
data: { order: item.order },
|
|
77
|
+
}));
|
|
78
|
+
// Update sections and assignments with their new order
|
|
79
|
+
await Promise.all([
|
|
80
|
+
...stack.filter(item => sections.some(s => s.id === item.where.id))
|
|
81
|
+
.map(({ where, data }) => prisma.section.update({ where, data })),
|
|
82
|
+
...stack.filter(item => assignments.some(a => a.id === item.where.id))
|
|
83
|
+
.map(({ where, data }) => prisma.assignment.update({ where, data }))
|
|
84
|
+
]);
|
|
53
85
|
return section;
|
|
54
86
|
}),
|
|
55
87
|
update: protectedProcedure
|
|
@@ -82,10 +114,51 @@ export const sectionRouter = createTRPCRouter({
|
|
|
82
114
|
where: { id: input.id },
|
|
83
115
|
data: {
|
|
84
116
|
name: input.name,
|
|
117
|
+
...(input.color && {
|
|
118
|
+
color: input.color,
|
|
119
|
+
}),
|
|
85
120
|
},
|
|
86
121
|
});
|
|
87
122
|
return section;
|
|
88
123
|
}),
|
|
124
|
+
reOrder: protectedProcedure
|
|
125
|
+
.input(z.object({
|
|
126
|
+
id: z.string(),
|
|
127
|
+
classId: z.string(),
|
|
128
|
+
order: z.number(),
|
|
129
|
+
}))
|
|
130
|
+
.mutation(async ({ ctx, input }) => {
|
|
131
|
+
if (!ctx.user) {
|
|
132
|
+
throw new TRPCError({
|
|
133
|
+
code: "UNAUTHORIZED",
|
|
134
|
+
message: "User must be authenticated",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// Verify user is a teacher of the class
|
|
138
|
+
const classData = await prisma.class.findFirst({
|
|
139
|
+
where: {
|
|
140
|
+
id: input.classId,
|
|
141
|
+
teachers: {
|
|
142
|
+
some: {
|
|
143
|
+
id: ctx.user.id,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
if (!classData) {
|
|
149
|
+
throw new TRPCError({
|
|
150
|
+
code: "NOT_FOUND",
|
|
151
|
+
message: "Class not found or you are not a teacher",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
await prisma.section.update({
|
|
155
|
+
where: { id: input.id },
|
|
156
|
+
data: {
|
|
157
|
+
order: input.order,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
return { id: input.id };
|
|
161
|
+
}),
|
|
89
162
|
delete: protectedProcedure
|
|
90
163
|
.input(deleteSectionSchema)
|
|
91
164
|
.mutation(async ({ ctx, input }) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- DropForeignKey
|
|
2
|
+
ALTER TABLE "Notification" DROP CONSTRAINT "Notification_receiverId_fkey";
|
|
3
|
+
|
|
4
|
+
-- DropForeignKey
|
|
5
|
+
ALTER TABLE "UserProfile" DROP CONSTRAINT "UserProfile_userId_fkey";
|
|
6
|
+
|
|
7
|
+
-- AlterTable
|
|
8
|
+
ALTER TABLE "Assignment" ADD COLUMN "order" INTEGER;
|
|
9
|
+
|
|
10
|
+
-- AlterTable
|
|
11
|
+
ALTER TABLE "Folder" ADD COLUMN "color" TEXT DEFAULT '#3B82F6';
|
|
12
|
+
|
|
13
|
+
-- AlterTable
|
|
14
|
+
ALTER TABLE "Section" ADD COLUMN "color" TEXT DEFAULT '#3B82F6',
|
|
15
|
+
ADD COLUMN "order" INTEGER;
|
|
16
|
+
|
|
17
|
+
-- AlterTable
|
|
18
|
+
ALTER TABLE "Submission" ADD COLUMN "teacherComments" TEXT;
|
|
19
|
+
|
|
20
|
+
-- AddForeignKey
|
|
21
|
+
ALTER TABLE "UserProfile" ADD CONSTRAINT "UserProfile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
22
|
+
|
|
23
|
+
-- AddForeignKey
|
|
24
|
+
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_receiverId_fkey" FOREIGN KEY ("receiverId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
package/prisma/schema.prisma
CHANGED
|
@@ -132,6 +132,7 @@ model Folder {
|
|
|
132
132
|
childFolders Folder[] @relation("ParentChildFolders")
|
|
133
133
|
parentFolder Folder? @relation("ParentChildFolders", fields: [parentFolderId], references: [id])
|
|
134
134
|
parentFolderId String?
|
|
135
|
+
color String? @default("#3B82F6")
|
|
135
136
|
class Class? @relation("ClassFiles", fields: [classId], references: [id])
|
|
136
137
|
classId String? @unique
|
|
137
138
|
}
|
|
@@ -194,6 +195,7 @@ model Assignment {
|
|
|
194
195
|
eventId String?
|
|
195
196
|
markScheme MarkScheme? @relation(fields: [markSchemeId], references: [id], onDelete: Cascade)
|
|
196
197
|
markSchemeId String?
|
|
198
|
+
order Int?
|
|
197
199
|
gradingBoundary GradingBoundary? @relation(fields: [gradingBoundaryId], references: [id], onDelete: Cascade)
|
|
198
200
|
gradingBoundaryId String?
|
|
199
201
|
}
|
|
@@ -227,6 +229,7 @@ model Submission {
|
|
|
227
229
|
gradeReceived Int?
|
|
228
230
|
|
|
229
231
|
rubricState String?
|
|
232
|
+
teacherComments String?
|
|
230
233
|
|
|
231
234
|
submittedAt DateTime?
|
|
232
235
|
submitted Boolean? @default(false)
|
|
@@ -239,6 +242,8 @@ model Section {
|
|
|
239
242
|
classId String
|
|
240
243
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
241
244
|
assignments Assignment[]
|
|
245
|
+
color String? @default("#3B82F6")
|
|
246
|
+
order Int?
|
|
242
247
|
}
|
|
243
248
|
|
|
244
249
|
model Session {
|
|
@@ -84,6 +84,59 @@ const updateSubmissionSchema = z.object({
|
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
export const assignmentRouter = createTRPCRouter({
|
|
87
|
+
order: protectedTeacherProcedure
|
|
88
|
+
.input(z.object({
|
|
89
|
+
id: z.string(),
|
|
90
|
+
classId: z.string(),
|
|
91
|
+
order: z.number(),
|
|
92
|
+
}))
|
|
93
|
+
.mutation(async ({ ctx, input }) => {
|
|
94
|
+
const { id, order } = input;
|
|
95
|
+
|
|
96
|
+
const assignment = await prisma.assignment.update({
|
|
97
|
+
where: { id },
|
|
98
|
+
data: { order },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return assignment;
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
move: protectedTeacherProcedure
|
|
105
|
+
.input(z.object({
|
|
106
|
+
id: z.string(),
|
|
107
|
+
classId: z.string(),
|
|
108
|
+
targetSectionId: z.string(),
|
|
109
|
+
}))
|
|
110
|
+
.mutation(async ({ ctx, input }) => {
|
|
111
|
+
const { id, targetSectionId, } = input;
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
const assignments = await prisma.assignment.findMany({
|
|
115
|
+
where: { sectionId: targetSectionId },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const stack = assignments.sort((a, b) => (a.order || 0) - (b.order || 0)).map((assignment, index) => ({
|
|
119
|
+
id: assignment.id,
|
|
120
|
+
order: index + 1,
|
|
121
|
+
})).map((assignment) => ({
|
|
122
|
+
where: { id: assignment.id },
|
|
123
|
+
data: { order: assignment.order },
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
await Promise.all(
|
|
127
|
+
stack.map(({ where, data }) =>
|
|
128
|
+
prisma.assignment.update({ where, data })
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const assignment = await prisma.assignment.update({
|
|
133
|
+
where: { id },
|
|
134
|
+
data: { sectionId: targetSectionId, order: 0 },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return assignment;
|
|
138
|
+
}),
|
|
139
|
+
|
|
87
140
|
create: protectedProcedure
|
|
88
141
|
.input(createAssignmentSchema)
|
|
89
142
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -132,16 +185,35 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
132
185
|
}
|
|
133
186
|
console.log(markSchemeId, gradingBoundaryId);
|
|
134
187
|
|
|
188
|
+
// find all assignments in the section it is in (or none) and reorder them
|
|
189
|
+
const assignments = await prisma.assignment.findMany({
|
|
190
|
+
where: {
|
|
191
|
+
classId: classId,
|
|
192
|
+
...(sectionId && {
|
|
193
|
+
sectionId: sectionId,
|
|
194
|
+
}),
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const stack = assignments.sort((a, b) => (a.order || 0) - (b.order || 0)).map((assignment, index) => ({
|
|
199
|
+
id: assignment.id,
|
|
200
|
+
order: index + 1,
|
|
201
|
+
})).map((assignment) => ({
|
|
202
|
+
where: { id: assignment.id },
|
|
203
|
+
data: { order: assignment.order },
|
|
204
|
+
}));
|
|
205
|
+
|
|
135
206
|
// Create assignment with submissions for all students
|
|
136
207
|
const assignment = await prisma.assignment.create({
|
|
137
208
|
data: {
|
|
138
209
|
title,
|
|
139
210
|
instructions,
|
|
140
211
|
dueDate: new Date(dueDate),
|
|
141
|
-
maxGrade: markSchemeId ?
|
|
212
|
+
maxGrade: markSchemeId ? computedMaxGrade : maxGrade,
|
|
142
213
|
graded,
|
|
143
214
|
weight,
|
|
144
215
|
type,
|
|
216
|
+
order: 0,
|
|
145
217
|
inProgress: inProgress || false,
|
|
146
218
|
class: {
|
|
147
219
|
connect: { id: classId }
|
|
@@ -156,7 +228,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
156
228
|
connect: { id: markSchemeId }
|
|
157
229
|
}
|
|
158
230
|
}),
|
|
159
|
-
...(gradingBoundaryId && {
|
|
231
|
+
...(gradingBoundaryId && {
|
|
160
232
|
gradingBoundary: {
|
|
161
233
|
connect: { id: gradingBoundaryId }
|
|
162
234
|
}
|
|
@@ -209,6 +281,12 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
209
281
|
}
|
|
210
282
|
});
|
|
211
283
|
|
|
284
|
+
await Promise.all(
|
|
285
|
+
stack.map(({ where, data }) =>
|
|
286
|
+
prisma.assignment.update({ where, data })
|
|
287
|
+
)
|
|
288
|
+
);
|
|
289
|
+
|
|
212
290
|
// Upload files if provided
|
|
213
291
|
let uploadedFiles: UploadedFile[] = [];
|
|
214
292
|
if (files && files.length > 0) {
|
|
@@ -478,7 +556,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
478
556
|
try {
|
|
479
557
|
// Delete the main file
|
|
480
558
|
await deleteFile(file.path);
|
|
481
|
-
|
|
559
|
+
|
|
482
560
|
// Delete thumbnail if it exists
|
|
483
561
|
if (file.thumbnail) {
|
|
484
562
|
await deleteFile(file.thumbnail.path);
|
|
@@ -493,7 +571,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
493
571
|
where: { id },
|
|
494
572
|
});
|
|
495
573
|
|
|
496
|
-
return {
|
|
574
|
+
return {
|
|
497
575
|
id,
|
|
498
576
|
};
|
|
499
577
|
}),
|
|
@@ -595,7 +673,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
595
673
|
id: true,
|
|
596
674
|
name: true,
|
|
597
675
|
},
|
|
598
|
-
});
|
|
676
|
+
});
|
|
599
677
|
|
|
600
678
|
return { ...assignment, sections };
|
|
601
679
|
}),
|
|
@@ -913,7 +991,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
913
991
|
|
|
914
992
|
// Delete removed attachments if any
|
|
915
993
|
if (removedAttachments && removedAttachments.length > 0) {
|
|
916
|
-
const filesToDelete = submission.attachments.filter((file) =>
|
|
994
|
+
const filesToDelete = submission.attachments.filter((file) =>
|
|
917
995
|
removedAttachments.includes(file.id)
|
|
918
996
|
);
|
|
919
997
|
|
|
@@ -922,7 +1000,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
922
1000
|
try {
|
|
923
1001
|
// Delete the main file
|
|
924
1002
|
await deleteFile(file.path);
|
|
925
|
-
|
|
1003
|
+
|
|
926
1004
|
// Delete thumbnail if it exists
|
|
927
1005
|
if (file.thumbnail?.path) {
|
|
928
1006
|
await deleteFile(file.thumbnail.path);
|
|
@@ -1181,7 +1259,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
1181
1259
|
|
|
1182
1260
|
// Delete removed attachments if any
|
|
1183
1261
|
if (removedAttachments && removedAttachments.length > 0) {
|
|
1184
|
-
const filesToDelete = submission.annotations.filter((file) =>
|
|
1262
|
+
const filesToDelete = submission.annotations.filter((file) =>
|
|
1185
1263
|
removedAttachments.includes(file.id)
|
|
1186
1264
|
);
|
|
1187
1265
|
|
|
@@ -1190,7 +1268,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
1190
1268
|
try {
|
|
1191
1269
|
// Delete the main file
|
|
1192
1270
|
await deleteFile(file.path);
|
|
1193
|
-
|
|
1271
|
+
|
|
1194
1272
|
// Delete thumbnail if it exists
|
|
1195
1273
|
if (file.thumbnail?.path) {
|
|
1196
1274
|
await deleteFile(file.thumbnail.path);
|
|
@@ -1575,7 +1653,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
1575
1653
|
|
|
1576
1654
|
return updatedAssignment;
|
|
1577
1655
|
}),
|
|
1578
|
-
|
|
1656
|
+
detachMarkScheme: protectedTeacherProcedure
|
|
1579
1657
|
.input(z.object({
|
|
1580
1658
|
assignmentId: z.string(),
|
|
1581
1659
|
}))
|
|
@@ -1594,7 +1672,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
1594
1672
|
message: "Assignment not found",
|
|
1595
1673
|
});
|
|
1596
1674
|
}
|
|
1597
|
-
|
|
1675
|
+
|
|
1598
1676
|
const updatedAssignment = await prisma.assignment.update({
|
|
1599
1677
|
where: { id: assignmentId },
|
|
1600
1678
|
data: {
|
|
@@ -1613,7 +1691,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
1613
1691
|
|
|
1614
1692
|
return updatedAssignment;
|
|
1615
1693
|
}),
|
|
1616
|
-
|
|
1694
|
+
attachGradingBoundary: protectedTeacherProcedure
|
|
1617
1695
|
.input(z.object({
|
|
1618
1696
|
assignmentId: z.string(),
|
|
1619
1697
|
gradingBoundaryId: z.string().nullable(),
|
package/src/routers/class.ts
CHANGED
|
@@ -128,6 +128,7 @@ export const classRouter = createTRPCRouter({
|
|
|
128
128
|
dueDate: true,
|
|
129
129
|
createdAt: true,
|
|
130
130
|
weight: true,
|
|
131
|
+
order: true,
|
|
131
132
|
graded: true,
|
|
132
133
|
maxGrade: true,
|
|
133
134
|
instructions: true,
|
|
@@ -172,7 +173,7 @@ export const classRouter = createTRPCRouter({
|
|
|
172
173
|
if (!classData) {
|
|
173
174
|
throw new Error('Class not found');
|
|
174
175
|
}
|
|
175
|
-
|
|
176
|
+
|
|
176
177
|
const formattedClassData = {
|
|
177
178
|
...classData,
|
|
178
179
|
assignments: classData.assignments.map(assignment => ({
|
package/src/routers/folder.ts
CHANGED
|
@@ -14,6 +14,7 @@ const fileSchema = z.object({
|
|
|
14
14
|
const createFolderSchema = z.object({
|
|
15
15
|
name: z.string(),
|
|
16
16
|
parentFolderId: z.string().optional(),
|
|
17
|
+
color: z.string().optional(),
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
const uploadFilesToFolderSchema = z.object({
|
|
@@ -29,7 +30,7 @@ export const folderRouter = createTRPCRouter({
|
|
|
29
30
|
create: protectedTeacherProcedure
|
|
30
31
|
.input(createFolderSchema)
|
|
31
32
|
.mutation(async ({ ctx, input }) => {
|
|
32
|
-
const { classId, name } = input;
|
|
33
|
+
const { classId, name, color } = input;
|
|
33
34
|
let parentFolderId = input.parentFolderId || null;
|
|
34
35
|
|
|
35
36
|
if (!ctx.user) {
|
|
@@ -75,6 +76,9 @@ export const folderRouter = createTRPCRouter({
|
|
|
75
76
|
class: {
|
|
76
77
|
connect: { id: classId },
|
|
77
78
|
},
|
|
79
|
+
...(color && {
|
|
80
|
+
color: color,
|
|
81
|
+
}),
|
|
78
82
|
},
|
|
79
83
|
});
|
|
80
84
|
}
|
|
@@ -104,6 +108,9 @@ export const folderRouter = createTRPCRouter({
|
|
|
104
108
|
connect: { id: parentFolderId },
|
|
105
109
|
},
|
|
106
110
|
}),
|
|
111
|
+
...(color && {
|
|
112
|
+
color: color,
|
|
113
|
+
}),
|
|
107
114
|
},
|
|
108
115
|
include: {
|
|
109
116
|
files: {
|
|
@@ -317,6 +324,7 @@ export const folderRouter = createTRPCRouter({
|
|
|
317
324
|
select: {
|
|
318
325
|
id: true,
|
|
319
326
|
name: true,
|
|
327
|
+
color: true,
|
|
320
328
|
_count: {
|
|
321
329
|
select: {
|
|
322
330
|
files: true,
|
|
@@ -391,6 +399,7 @@ export const folderRouter = createTRPCRouter({
|
|
|
391
399
|
select: {
|
|
392
400
|
id: true,
|
|
393
401
|
name: true,
|
|
402
|
+
color: true,
|
|
394
403
|
files: {
|
|
395
404
|
select: {
|
|
396
405
|
id: true,
|
|
@@ -682,14 +691,15 @@ export const folderRouter = createTRPCRouter({
|
|
|
682
691
|
return updatedFolder;
|
|
683
692
|
}),
|
|
684
693
|
|
|
685
|
-
|
|
694
|
+
update: protectedTeacherProcedure
|
|
686
695
|
.input(z.object({
|
|
687
696
|
folderId: z.string(),
|
|
688
|
-
|
|
697
|
+
name: z.string(),
|
|
698
|
+
color: z.string().optional(),
|
|
689
699
|
classId: z.string(),
|
|
690
700
|
}))
|
|
691
701
|
.mutation(async ({ ctx, input }) => {
|
|
692
|
-
const { folderId,
|
|
702
|
+
const { folderId, name, color, classId } = input;
|
|
693
703
|
|
|
694
704
|
// Get the folder
|
|
695
705
|
const folder = await prisma.folder.findFirst({
|
|
@@ -706,7 +716,7 @@ export const folderRouter = createTRPCRouter({
|
|
|
706
716
|
}
|
|
707
717
|
|
|
708
718
|
// Validate new name
|
|
709
|
-
if (!
|
|
719
|
+
if (!name.trim()) {
|
|
710
720
|
throw new TRPCError({
|
|
711
721
|
code: "BAD_REQUEST",
|
|
712
722
|
message: "Folder name cannot be empty",
|
|
@@ -717,7 +727,10 @@ export const folderRouter = createTRPCRouter({
|
|
|
717
727
|
const updatedFolder = await prisma.folder.update({
|
|
718
728
|
where: { id: folderId },
|
|
719
729
|
data: {
|
|
720
|
-
name:
|
|
730
|
+
name: name.trim(),
|
|
731
|
+
...(color && {
|
|
732
|
+
color: color,
|
|
733
|
+
}),
|
|
721
734
|
},
|
|
722
735
|
include: {
|
|
723
736
|
files: {
|
package/src/routers/section.ts
CHANGED
|
@@ -6,12 +6,14 @@ import { prisma } from "../lib/prisma.js";
|
|
|
6
6
|
const createSectionSchema = z.object({
|
|
7
7
|
classId: z.string(),
|
|
8
8
|
name: z.string(),
|
|
9
|
+
color: z.string().optional(),
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
const updateSectionSchema = z.object({
|
|
12
13
|
id: z.string(),
|
|
13
14
|
classId: z.string(),
|
|
14
15
|
name: z.string(),
|
|
16
|
+
color: z.string().optional(),
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
const deleteSectionSchema = z.object({
|
|
@@ -52,12 +54,50 @@ export const sectionRouter = createTRPCRouter({
|
|
|
52
54
|
const section = await prisma.section.create({
|
|
53
55
|
data: {
|
|
54
56
|
name: input.name,
|
|
57
|
+
order: 0,
|
|
55
58
|
class: {
|
|
56
59
|
connect: { id: input.classId },
|
|
57
60
|
},
|
|
61
|
+
...(input.color && {
|
|
62
|
+
color: input.color,
|
|
63
|
+
}),
|
|
58
64
|
},
|
|
59
65
|
});
|
|
60
66
|
|
|
67
|
+
// find all root items in the class and reorder them
|
|
68
|
+
const sections = await prisma.section.findMany({
|
|
69
|
+
where: {
|
|
70
|
+
classId: input.classId,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const assignments = await prisma.assignment.findMany({
|
|
75
|
+
where: {
|
|
76
|
+
classId: input.classId,
|
|
77
|
+
sectionId: null,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const stack = [...sections, ...assignments].sort((a, b) => (a.order || 0) - (b.order || 0)).map((item, index) => ({
|
|
82
|
+
id: item.id,
|
|
83
|
+
order: index + 1,
|
|
84
|
+
})).map((item) => ({
|
|
85
|
+
where: { id: item.id },
|
|
86
|
+
data: { order: item.order },
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
// Update sections and assignments with their new order
|
|
90
|
+
await Promise.all([
|
|
91
|
+
...stack.filter(item => sections.some(s => s.id === item.where.id))
|
|
92
|
+
.map(({ where, data }) =>
|
|
93
|
+
prisma.section.update({ where, data })
|
|
94
|
+
),
|
|
95
|
+
...stack.filter(item => assignments.some(a => a.id === item.where.id))
|
|
96
|
+
.map(({ where, data }) =>
|
|
97
|
+
prisma.assignment.update({ where, data })
|
|
98
|
+
)
|
|
99
|
+
]);
|
|
100
|
+
|
|
61
101
|
return section;
|
|
62
102
|
}),
|
|
63
103
|
|
|
@@ -94,12 +134,58 @@ export const sectionRouter = createTRPCRouter({
|
|
|
94
134
|
where: { id: input.id },
|
|
95
135
|
data: {
|
|
96
136
|
name: input.name,
|
|
137
|
+
...(input.color && {
|
|
138
|
+
color: input.color,
|
|
139
|
+
}),
|
|
97
140
|
},
|
|
98
141
|
});
|
|
99
142
|
|
|
100
143
|
return section;
|
|
101
144
|
}),
|
|
102
145
|
|
|
146
|
+
reOrder: protectedProcedure
|
|
147
|
+
.input(z.object({
|
|
148
|
+
id: z.string(),
|
|
149
|
+
classId: z.string(),
|
|
150
|
+
order: z.number(),
|
|
151
|
+
}))
|
|
152
|
+
.mutation(async ({ ctx, input }) => {
|
|
153
|
+
if (!ctx.user) {
|
|
154
|
+
throw new TRPCError({
|
|
155
|
+
code: "UNAUTHORIZED",
|
|
156
|
+
message: "User must be authenticated",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Verify user is a teacher of the class
|
|
161
|
+
const classData = await prisma.class.findFirst({
|
|
162
|
+
where: {
|
|
163
|
+
id: input.classId,
|
|
164
|
+
teachers: {
|
|
165
|
+
some: {
|
|
166
|
+
id: ctx.user.id,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!classData) {
|
|
173
|
+
throw new TRPCError({
|
|
174
|
+
code: "NOT_FOUND",
|
|
175
|
+
message: "Class not found or you are not a teacher",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await prisma.section.update({
|
|
180
|
+
where: { id: input.id },
|
|
181
|
+
data: {
|
|
182
|
+
order: input.order,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return { id: input.id };
|
|
187
|
+
}),
|
|
188
|
+
|
|
103
189
|
delete: protectedProcedure
|
|
104
190
|
.input(deleteSectionSchema)
|
|
105
191
|
.mutation(async ({ ctx, input }) => {
|