@studious-lms/server 1.1.19 → 1.1.21
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/BASE64_REMOVAL_SUMMARY.md +153 -0
- package/dist/lib/fileUpload.d.ts +45 -12
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +181 -72
- package/dist/lib/googleCloudStorage.d.ts +7 -8
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +21 -33
- package/dist/lib/thumbnailGenerator.d.ts +0 -1
- package/dist/lib/thumbnailGenerator.d.ts.map +1 -1
- package/dist/lib/thumbnailGenerator.js +3 -17
- package/dist/routers/_app.d.ts +476 -10
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +221 -4
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +244 -16
- package/dist/routers/auth.js +1 -1
- package/dist/routers/file.d.ts +18 -0
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/folder.d.ts +0 -1
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +12 -9
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +4 -2
- package/package.json +1 -1
- package/prisma/migrations/20251020151505_add_upload_tracking_fields/migration.sql +57 -0
- package/prisma/schema.prisma +19 -0
- package/src/lib/fileUpload.ts +229 -83
- package/src/lib/googleCloudStorage.ts +21 -40
- package/src/lib/thumbnailGenerator.ts +3 -18
- package/src/routers/assignment.ts +292 -16
- package/src/routers/auth.ts +1 -1
- package/src/routers/folder.ts +13 -9
- package/src/routers/labChat.ts +4 -2
- package/src/routers/user.ts +1 -1
|
@@ -2,20 +2,23 @@ import { z } from "zod";
|
|
|
2
2
|
import { createTRPCRouter, protectedProcedure, protectedClassMemberProcedure, protectedTeacherProcedure } from "../trpc.js";
|
|
3
3
|
import { TRPCError } from "@trpc/server";
|
|
4
4
|
import { prisma } from "../lib/prisma.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createDirectUploadFiles, confirmDirectUpload, updateUploadProgress } from "../lib/fileUpload.js";
|
|
6
6
|
import { deleteFile } from "../lib/googleCloudStorage.js";
|
|
7
|
-
|
|
7
|
+
// DEPRECATED: This schema is no longer used - files are uploaded directly to GCS
|
|
8
|
+
// Use directFileSchema instead
|
|
9
|
+
// New schema for direct file uploads (no base64 data)
|
|
10
|
+
const directFileSchema = z.object({
|
|
8
11
|
name: z.string(),
|
|
9
12
|
type: z.string(),
|
|
10
13
|
size: z.number(),
|
|
11
|
-
data
|
|
14
|
+
// No data field - for direct file uploads
|
|
12
15
|
});
|
|
13
16
|
const createAssignmentSchema = z.object({
|
|
14
17
|
classId: z.string(),
|
|
15
18
|
title: z.string(),
|
|
16
19
|
instructions: z.string(),
|
|
17
20
|
dueDate: z.string(),
|
|
18
|
-
files: z.array(
|
|
21
|
+
files: z.array(directFileSchema).optional(), // Use direct file schema
|
|
19
22
|
existingFileIds: z.array(z.string()).optional(),
|
|
20
23
|
maxGrade: z.number().optional(),
|
|
21
24
|
graded: z.boolean().optional(),
|
|
@@ -32,7 +35,7 @@ const updateAssignmentSchema = z.object({
|
|
|
32
35
|
title: z.string().optional(),
|
|
33
36
|
instructions: z.string().optional(),
|
|
34
37
|
dueDate: z.string().optional(),
|
|
35
|
-
files: z.array(
|
|
38
|
+
files: z.array(directFileSchema).optional(), // Use direct file schema
|
|
36
39
|
existingFileIds: z.array(z.string()).optional(),
|
|
37
40
|
removedAttachments: z.array(z.string()).optional(),
|
|
38
41
|
maxGrade: z.number().optional(),
|
|
@@ -55,7 +58,7 @@ const submissionSchema = z.object({
|
|
|
55
58
|
classId: z.string(),
|
|
56
59
|
submissionId: z.string(),
|
|
57
60
|
submit: z.boolean().optional(),
|
|
58
|
-
newAttachments: z.array(
|
|
61
|
+
newAttachments: z.array(directFileSchema).optional(), // Use direct file schema
|
|
59
62
|
existingFileIds: z.array(z.string()).optional(),
|
|
60
63
|
removedAttachments: z.array(z.string()).optional(),
|
|
61
64
|
});
|
|
@@ -65,7 +68,7 @@ const updateSubmissionSchema = z.object({
|
|
|
65
68
|
submissionId: z.string(),
|
|
66
69
|
return: z.boolean().optional(),
|
|
67
70
|
gradeReceived: z.number().nullable().optional(),
|
|
68
|
-
newAttachments: z.array(
|
|
71
|
+
newAttachments: z.array(directFileSchema).optional(), // Use direct file schema
|
|
69
72
|
existingFileIds: z.array(z.string()).optional(),
|
|
70
73
|
removedAttachments: z.array(z.string()).optional(),
|
|
71
74
|
feedback: z.string().optional(),
|
|
@@ -76,6 +79,31 @@ const updateSubmissionSchema = z.object({
|
|
|
76
79
|
comments: z.string(),
|
|
77
80
|
})).optional(),
|
|
78
81
|
});
|
|
82
|
+
// New schemas for direct upload functionality
|
|
83
|
+
const getAssignmentUploadUrlsSchema = z.object({
|
|
84
|
+
assignmentId: z.string(),
|
|
85
|
+
classId: z.string(),
|
|
86
|
+
files: z.array(directFileSchema),
|
|
87
|
+
});
|
|
88
|
+
const getSubmissionUploadUrlsSchema = z.object({
|
|
89
|
+
submissionId: z.string(),
|
|
90
|
+
classId: z.string(),
|
|
91
|
+
files: z.array(directFileSchema),
|
|
92
|
+
});
|
|
93
|
+
const confirmAssignmentUploadSchema = z.object({
|
|
94
|
+
fileId: z.string(),
|
|
95
|
+
uploadSuccess: z.boolean(),
|
|
96
|
+
errorMessage: z.string().optional(),
|
|
97
|
+
});
|
|
98
|
+
const confirmSubmissionUploadSchema = z.object({
|
|
99
|
+
fileId: z.string(),
|
|
100
|
+
uploadSuccess: z.boolean(),
|
|
101
|
+
errorMessage: z.string().optional(),
|
|
102
|
+
});
|
|
103
|
+
const updateUploadProgressSchema = z.object({
|
|
104
|
+
fileId: z.string(),
|
|
105
|
+
progress: z.number().min(0).max(100),
|
|
106
|
+
});
|
|
79
107
|
export const assignmentRouter = createTRPCRouter({
|
|
80
108
|
order: protectedTeacherProcedure
|
|
81
109
|
.input(z.object({
|
|
@@ -98,7 +126,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
98
126
|
targetSectionId: z.string(),
|
|
99
127
|
}))
|
|
100
128
|
.mutation(async ({ ctx, input }) => {
|
|
101
|
-
const { id, targetSectionId
|
|
129
|
+
const { id, targetSectionId } = input;
|
|
102
130
|
const assignments = await prisma.assignment.findMany({
|
|
103
131
|
where: { sectionId: targetSectionId },
|
|
104
132
|
});
|
|
@@ -251,11 +279,13 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
251
279
|
}
|
|
252
280
|
});
|
|
253
281
|
await Promise.all(stack.map(({ where, data }) => prisma.assignment.update({ where, data })));
|
|
254
|
-
//
|
|
282
|
+
// NOTE: Files are now handled via direct upload endpoints
|
|
283
|
+
// The files field in the schema is for metadata only
|
|
284
|
+
// Actual file uploads should use getAssignmentUploadUrls endpoint
|
|
255
285
|
let uploadedFiles = [];
|
|
256
286
|
if (files && files.length > 0) {
|
|
257
|
-
//
|
|
258
|
-
uploadedFiles = await
|
|
287
|
+
// Create direct upload files instead of processing base64
|
|
288
|
+
uploadedFiles = await createDirectUploadFiles(files, ctx.user.id, undefined, assignment.id);
|
|
259
289
|
}
|
|
260
290
|
// Update assignment with new file attachments
|
|
261
291
|
if (uploadedFiles.length > 0) {
|
|
@@ -336,11 +366,11 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
336
366
|
message: "Assignment not found",
|
|
337
367
|
});
|
|
338
368
|
}
|
|
339
|
-
//
|
|
369
|
+
// NOTE: Files are now handled via direct upload endpoints
|
|
340
370
|
let uploadedFiles = [];
|
|
341
371
|
if (files && files.length > 0) {
|
|
342
|
-
//
|
|
343
|
-
uploadedFiles = await
|
|
372
|
+
// Create direct upload files instead of processing base64
|
|
373
|
+
uploadedFiles = await createDirectUploadFiles(files, ctx.user.id, undefined, input.id);
|
|
344
374
|
}
|
|
345
375
|
// Update assignment
|
|
346
376
|
const updatedAssignment = await prisma.assignment.update({
|
|
@@ -740,6 +770,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
740
770
|
select: {
|
|
741
771
|
id: true,
|
|
742
772
|
username: true,
|
|
773
|
+
profile: true,
|
|
743
774
|
},
|
|
744
775
|
},
|
|
745
776
|
assignment: {
|
|
@@ -874,7 +905,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
874
905
|
let uploadedFiles = [];
|
|
875
906
|
if (newAttachments && newAttachments.length > 0) {
|
|
876
907
|
// Store files in a class and assignment specific directory
|
|
877
|
-
uploadedFiles = await
|
|
908
|
+
uploadedFiles = await createDirectUploadFiles(newAttachments, ctx.user.id, undefined, undefined, submission.id);
|
|
878
909
|
}
|
|
879
910
|
// Update submission with new file attachments
|
|
880
911
|
if (uploadedFiles.length > 0) {
|
|
@@ -1139,7 +1170,7 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
1139
1170
|
let uploadedFiles = [];
|
|
1140
1171
|
if (newAttachments && newAttachments.length > 0) {
|
|
1141
1172
|
// Store files in a class and assignment specific directory
|
|
1142
|
-
uploadedFiles = await
|
|
1173
|
+
uploadedFiles = await createDirectUploadFiles(newAttachments, ctx.user.id, undefined, undefined, submission.id);
|
|
1143
1174
|
}
|
|
1144
1175
|
// Update submission with new file attachments
|
|
1145
1176
|
if (uploadedFiles.length > 0) {
|
|
@@ -1665,4 +1696,201 @@ export const assignmentRouter = createTRPCRouter({
|
|
|
1665
1696
|
});
|
|
1666
1697
|
return updatedAssignment;
|
|
1667
1698
|
}),
|
|
1699
|
+
// New direct upload endpoints
|
|
1700
|
+
getAssignmentUploadUrls: protectedTeacherProcedure
|
|
1701
|
+
.input(getAssignmentUploadUrlsSchema)
|
|
1702
|
+
.mutation(async ({ ctx, input }) => {
|
|
1703
|
+
const { assignmentId, classId, files } = input;
|
|
1704
|
+
if (!ctx.user) {
|
|
1705
|
+
throw new TRPCError({
|
|
1706
|
+
code: "UNAUTHORIZED",
|
|
1707
|
+
message: "You must be logged in to upload files",
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
// Verify user is a teacher of the class
|
|
1711
|
+
const classData = await prisma.class.findFirst({
|
|
1712
|
+
where: {
|
|
1713
|
+
id: classId,
|
|
1714
|
+
teachers: {
|
|
1715
|
+
some: {
|
|
1716
|
+
id: ctx.user.id,
|
|
1717
|
+
},
|
|
1718
|
+
},
|
|
1719
|
+
},
|
|
1720
|
+
});
|
|
1721
|
+
if (!classData) {
|
|
1722
|
+
throw new TRPCError({
|
|
1723
|
+
code: "NOT_FOUND",
|
|
1724
|
+
message: "Class not found or you are not a teacher",
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
// Verify assignment exists and belongs to the class
|
|
1728
|
+
const assignment = await prisma.assignment.findFirst({
|
|
1729
|
+
where: {
|
|
1730
|
+
id: assignmentId,
|
|
1731
|
+
classId: classId,
|
|
1732
|
+
},
|
|
1733
|
+
});
|
|
1734
|
+
if (!assignment) {
|
|
1735
|
+
throw new TRPCError({
|
|
1736
|
+
code: "NOT_FOUND",
|
|
1737
|
+
message: "Assignment not found",
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
// Create direct upload files
|
|
1741
|
+
const directUploadFiles = await createDirectUploadFiles(files, ctx.user.id, undefined, // No specific directory
|
|
1742
|
+
assignmentId);
|
|
1743
|
+
return {
|
|
1744
|
+
success: true,
|
|
1745
|
+
uploadFiles: directUploadFiles,
|
|
1746
|
+
};
|
|
1747
|
+
}),
|
|
1748
|
+
getSubmissionUploadUrls: protectedClassMemberProcedure
|
|
1749
|
+
.input(getSubmissionUploadUrlsSchema)
|
|
1750
|
+
.mutation(async ({ ctx, input }) => {
|
|
1751
|
+
const { submissionId, classId, files } = input;
|
|
1752
|
+
if (!ctx.user) {
|
|
1753
|
+
throw new TRPCError({
|
|
1754
|
+
code: "UNAUTHORIZED",
|
|
1755
|
+
message: "You must be logged in to upload files",
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
// Verify submission exists and user has access
|
|
1759
|
+
const submission = await prisma.submission.findFirst({
|
|
1760
|
+
where: {
|
|
1761
|
+
id: submissionId,
|
|
1762
|
+
assignment: {
|
|
1763
|
+
classId: classId,
|
|
1764
|
+
},
|
|
1765
|
+
},
|
|
1766
|
+
include: {
|
|
1767
|
+
assignment: {
|
|
1768
|
+
include: {
|
|
1769
|
+
class: {
|
|
1770
|
+
include: {
|
|
1771
|
+
students: true,
|
|
1772
|
+
teachers: true,
|
|
1773
|
+
},
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
},
|
|
1777
|
+
},
|
|
1778
|
+
});
|
|
1779
|
+
if (!submission) {
|
|
1780
|
+
throw new TRPCError({
|
|
1781
|
+
code: "NOT_FOUND",
|
|
1782
|
+
message: "Submission not found",
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
// Check if user is the student who owns the submission or a teacher of the class
|
|
1786
|
+
const isStudent = submission.studentId === ctx.user.id;
|
|
1787
|
+
const isTeacher = submission.assignment.class.teachers.some(teacher => teacher.id === ctx.user?.id);
|
|
1788
|
+
if (!isStudent && !isTeacher) {
|
|
1789
|
+
throw new TRPCError({
|
|
1790
|
+
code: "FORBIDDEN",
|
|
1791
|
+
message: "You don't have permission to upload files to this submission",
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
// Create direct upload files
|
|
1795
|
+
const directUploadFiles = await createDirectUploadFiles(files, ctx.user.id, undefined, // No specific directory
|
|
1796
|
+
undefined, // No assignment ID
|
|
1797
|
+
submissionId);
|
|
1798
|
+
return {
|
|
1799
|
+
success: true,
|
|
1800
|
+
uploadFiles: directUploadFiles,
|
|
1801
|
+
};
|
|
1802
|
+
}),
|
|
1803
|
+
confirmAssignmentUpload: protectedTeacherProcedure
|
|
1804
|
+
.input(confirmAssignmentUploadSchema)
|
|
1805
|
+
.mutation(async ({ ctx, input }) => {
|
|
1806
|
+
const { fileId, uploadSuccess, errorMessage } = input;
|
|
1807
|
+
if (!ctx.user) {
|
|
1808
|
+
throw new TRPCError({
|
|
1809
|
+
code: "UNAUTHORIZED",
|
|
1810
|
+
message: "You must be logged in",
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
// Verify file belongs to user and is an assignment file
|
|
1814
|
+
const file = await prisma.file.findFirst({
|
|
1815
|
+
where: {
|
|
1816
|
+
id: fileId,
|
|
1817
|
+
userId: ctx.user.id,
|
|
1818
|
+
assignment: {
|
|
1819
|
+
isNot: null,
|
|
1820
|
+
},
|
|
1821
|
+
},
|
|
1822
|
+
});
|
|
1823
|
+
if (!file) {
|
|
1824
|
+
throw new TRPCError({
|
|
1825
|
+
code: "NOT_FOUND",
|
|
1826
|
+
message: "File not found or you don't have permission",
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
await confirmDirectUpload(fileId, uploadSuccess, errorMessage);
|
|
1830
|
+
return {
|
|
1831
|
+
success: true,
|
|
1832
|
+
message: uploadSuccess ? "Upload confirmed successfully" : "Upload failed",
|
|
1833
|
+
};
|
|
1834
|
+
}),
|
|
1835
|
+
confirmSubmissionUpload: protectedClassMemberProcedure
|
|
1836
|
+
.input(confirmSubmissionUploadSchema)
|
|
1837
|
+
.mutation(async ({ ctx, input }) => {
|
|
1838
|
+
const { fileId, uploadSuccess, errorMessage } = input;
|
|
1839
|
+
if (!ctx.user) {
|
|
1840
|
+
throw new TRPCError({
|
|
1841
|
+
code: "UNAUTHORIZED",
|
|
1842
|
+
message: "You must be logged in",
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
// Verify file belongs to user and is a submission file
|
|
1846
|
+
const file = await prisma.file.findFirst({
|
|
1847
|
+
where: {
|
|
1848
|
+
id: fileId,
|
|
1849
|
+
userId: ctx.user.id,
|
|
1850
|
+
submission: {
|
|
1851
|
+
isNot: null,
|
|
1852
|
+
},
|
|
1853
|
+
},
|
|
1854
|
+
});
|
|
1855
|
+
if (!file) {
|
|
1856
|
+
throw new TRPCError({
|
|
1857
|
+
code: "NOT_FOUND",
|
|
1858
|
+
message: "File not found or you don't have permission",
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
await confirmDirectUpload(fileId, uploadSuccess, errorMessage);
|
|
1862
|
+
return {
|
|
1863
|
+
success: true,
|
|
1864
|
+
message: uploadSuccess ? "Upload confirmed successfully" : "Upload failed",
|
|
1865
|
+
};
|
|
1866
|
+
}),
|
|
1867
|
+
updateUploadProgress: protectedProcedure
|
|
1868
|
+
.input(updateUploadProgressSchema)
|
|
1869
|
+
.mutation(async ({ ctx, input }) => {
|
|
1870
|
+
const { fileId, progress } = input;
|
|
1871
|
+
if (!ctx.user) {
|
|
1872
|
+
throw new TRPCError({
|
|
1873
|
+
code: "UNAUTHORIZED",
|
|
1874
|
+
message: "You must be logged in",
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
// Verify file belongs to user
|
|
1878
|
+
const file = await prisma.file.findFirst({
|
|
1879
|
+
where: {
|
|
1880
|
+
id: fileId,
|
|
1881
|
+
userId: ctx.user.id,
|
|
1882
|
+
},
|
|
1883
|
+
});
|
|
1884
|
+
if (!file) {
|
|
1885
|
+
throw new TRPCError({
|
|
1886
|
+
code: "NOT_FOUND",
|
|
1887
|
+
message: "File not found or you don't have permission",
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
await updateUploadProgress(fileId, progress);
|
|
1891
|
+
return {
|
|
1892
|
+
success: true,
|
|
1893
|
+
progress,
|
|
1894
|
+
};
|
|
1895
|
+
}),
|
|
1668
1896
|
});
|
package/dist/routers/auth.js
CHANGED
|
@@ -113,7 +113,7 @@ export const authRouter = createTRPCRouter({
|
|
|
113
113
|
if (!user) {
|
|
114
114
|
throw new TRPCError({
|
|
115
115
|
code: "UNAUTHORIZED",
|
|
116
|
-
message: "
|
|
116
|
+
message: "user doesn't exists.",
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
if (!(await compare(password, user.password))) {
|
package/dist/routers/file.d.ts
CHANGED
|
@@ -49,6 +49,15 @@ export declare const fileRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
49
49
|
userId: string | null;
|
|
50
50
|
thumbnailId: string | null;
|
|
51
51
|
annotationId: string | null;
|
|
52
|
+
uploadStatus: import(".prisma/client").$Enums.UploadStatus;
|
|
53
|
+
uploadUrl: string | null;
|
|
54
|
+
uploadExpiresAt: Date | null;
|
|
55
|
+
uploadSessionId: string | null;
|
|
56
|
+
uploadProgress: number | null;
|
|
57
|
+
uploadError: string | null;
|
|
58
|
+
uploadRetryCount: number;
|
|
59
|
+
isOrphaned: boolean;
|
|
60
|
+
cleanupAt: Date | null;
|
|
52
61
|
classDraftId: string | null;
|
|
53
62
|
folderId: string | null;
|
|
54
63
|
conversationId: string | null;
|
|
@@ -81,6 +90,15 @@ export declare const fileRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
81
90
|
userId: string | null;
|
|
82
91
|
thumbnailId: string | null;
|
|
83
92
|
annotationId: string | null;
|
|
93
|
+
uploadStatus: import(".prisma/client").$Enums.UploadStatus;
|
|
94
|
+
uploadUrl: string | null;
|
|
95
|
+
uploadExpiresAt: Date | null;
|
|
96
|
+
uploadSessionId: string | null;
|
|
97
|
+
uploadProgress: number | null;
|
|
98
|
+
uploadError: string | null;
|
|
99
|
+
uploadRetryCount: number;
|
|
100
|
+
isOrphaned: boolean;
|
|
101
|
+
cleanupAt: Date | null;
|
|
84
102
|
classDraftId: string | null;
|
|
85
103
|
folderId: string | null;
|
|
86
104
|
conversationId: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/routers/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,UAAU
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/routers/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsVrB,CAAC"}
|
package/dist/routers/folder.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder.d.ts","sourceRoot":"","sources":["../../src/routers/folder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"folder.d.ts","sourceRoot":"","sources":["../../src/routers/folder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiCxB,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0vBvB,CAAC"}
|
package/dist/routers/folder.js
CHANGED
|
@@ -2,21 +2,24 @@ import { z } from "zod";
|
|
|
2
2
|
import { createTRPCRouter, protectedProcedure, protectedClassMemberProcedure, protectedTeacherProcedure } from "../trpc.js";
|
|
3
3
|
import { TRPCError } from "@trpc/server";
|
|
4
4
|
import { prisma } from "../lib/prisma.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type: z.string(),
|
|
9
|
-
size: z.number(),
|
|
10
|
-
data: z.string(), // base64 encoded file data
|
|
11
|
-
});
|
|
5
|
+
import { createDirectUploadFiles } from "../lib/fileUpload.js";
|
|
6
|
+
// DEPRECATED: This schema is no longer used - files are uploaded directly to GCS
|
|
7
|
+
// Use directFileSchema instead
|
|
12
8
|
const createFolderSchema = z.object({
|
|
13
9
|
name: z.string(),
|
|
14
10
|
parentFolderId: z.string().optional(),
|
|
15
11
|
color: z.string().optional(),
|
|
16
12
|
});
|
|
13
|
+
// New schema for direct file uploads (no base64 data)
|
|
14
|
+
const directFileSchema = z.object({
|
|
15
|
+
name: z.string(),
|
|
16
|
+
type: z.string(),
|
|
17
|
+
size: z.number(),
|
|
18
|
+
// No data field - for direct file uploads
|
|
19
|
+
});
|
|
17
20
|
const uploadFilesToFolderSchema = z.object({
|
|
18
21
|
folderId: z.string(),
|
|
19
|
-
files: z.array(
|
|
22
|
+
files: z.array(directFileSchema), // Use direct file schema
|
|
20
23
|
});
|
|
21
24
|
const getRootFolderSchema = z.object({
|
|
22
25
|
classId: z.string(),
|
|
@@ -431,7 +434,7 @@ export const folderRouter = createTRPCRouter({
|
|
|
431
434
|
});
|
|
432
435
|
}
|
|
433
436
|
// Upload files
|
|
434
|
-
const uploadedFiles = await
|
|
437
|
+
const uploadedFiles = await createDirectUploadFiles(files, ctx.user.id, folder.id);
|
|
435
438
|
// Create file records in database
|
|
436
439
|
// const fileRecords = await prisma.file.createMany({
|
|
437
440
|
// data: uploadedFiles.map(file => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labChat.d.ts","sourceRoot":"","sources":["../../src/routers/labChat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"labChat.d.ts","sourceRoot":"","sources":["../../src/routers/labChat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAkBxB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAonBxB,CAAC"}
|
package/dist/routers/labChat.js
CHANGED
|
@@ -6,7 +6,8 @@ import { TRPCError } from '@trpc/server';
|
|
|
6
6
|
import { inferenceClient, sendAIMessage } from '../utils/inference.js';
|
|
7
7
|
import { logger } from '../utils/logger.js';
|
|
8
8
|
import { isAIUser } from '../utils/aiUser.js';
|
|
9
|
-
|
|
9
|
+
// DEPRECATED: uploadFile removed - use direct upload instead
|
|
10
|
+
// import { uploadFile } from '../lib/googleCloudStorage.js';
|
|
10
11
|
import { createPdf } from "../lib/jsonConversion.js";
|
|
11
12
|
import { v4 as uuidv4 } from "uuid";
|
|
12
13
|
export const labChatRouter = createTRPCRouter({
|
|
@@ -833,7 +834,8 @@ WHEN CREATING COURSE MATERIALS (docs field):
|
|
|
833
834
|
.substring(0, 50);
|
|
834
835
|
const filename = `${sanitizedTitle}_${uuidv4().substring(0, 8)}.pdf`;
|
|
835
836
|
logger.info(`PDF ${i + 1} generated successfully`, { labChatId, title: doc.title });
|
|
836
|
-
|
|
837
|
+
// DEPRECATED: Base64 upload removed - use direct upload instead
|
|
838
|
+
// const gcpResult = await uploadFile(Buffer.from(pdfBytes).toString('base64'), `class/generated/${fullLabChat.classId}/${filename}`, 'application/pdf');
|
|
837
839
|
logger.info(`PDF ${i + 1} uploaded successfully`, { labChatId, filename });
|
|
838
840
|
const file = await prisma.file.create({
|
|
839
841
|
data: {
|
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "public"."UploadStatus" AS ENUM ('PENDING', 'UPLOADING', 'COMPLETED', 'FAILED', 'CANCELLED');
|
|
3
|
+
|
|
4
|
+
-- AlterTable
|
|
5
|
+
ALTER TABLE "public"."File" ADD COLUMN "cleanupAt" TIMESTAMP(3),
|
|
6
|
+
ADD COLUMN "conversationId" TEXT,
|
|
7
|
+
ADD COLUMN "isOrphaned" BOOLEAN NOT NULL DEFAULT false,
|
|
8
|
+
ADD COLUMN "messageId" TEXT,
|
|
9
|
+
ADD COLUMN "schoolDevelopementProgramId" TEXT,
|
|
10
|
+
ADD COLUMN "uploadError" TEXT,
|
|
11
|
+
ADD COLUMN "uploadExpiresAt" TIMESTAMP(3),
|
|
12
|
+
ADD COLUMN "uploadProgress" INTEGER,
|
|
13
|
+
ADD COLUMN "uploadRetryCount" INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
ADD COLUMN "uploadSessionId" TEXT,
|
|
15
|
+
ADD COLUMN "uploadStatus" "public"."UploadStatus" NOT NULL DEFAULT 'PENDING',
|
|
16
|
+
ADD COLUMN "uploadUrl" TEXT;
|
|
17
|
+
|
|
18
|
+
-- CreateTable
|
|
19
|
+
CREATE TABLE "public"."SchoolDevelopementProgram" (
|
|
20
|
+
"id" TEXT NOT NULL,
|
|
21
|
+
"name" TEXT NOT NULL,
|
|
22
|
+
"type" TEXT NOT NULL,
|
|
23
|
+
"address" TEXT NOT NULL,
|
|
24
|
+
"city" TEXT NOT NULL,
|
|
25
|
+
"country" TEXT NOT NULL,
|
|
26
|
+
"numberOfStudents" INTEGER NOT NULL,
|
|
27
|
+
"numberOfTeachers" INTEGER NOT NULL,
|
|
28
|
+
"website" TEXT,
|
|
29
|
+
"contactName" TEXT,
|
|
30
|
+
"contactRole" TEXT,
|
|
31
|
+
"contactEmail" TEXT,
|
|
32
|
+
"contactPhone" TEXT DEFAULT '',
|
|
33
|
+
"eligibilityInformation" TEXT,
|
|
34
|
+
"whyHelp" TEXT,
|
|
35
|
+
"additionalInformation" TEXT,
|
|
36
|
+
"submittedAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
|
37
|
+
"reviewedAt" TIMESTAMP(3),
|
|
38
|
+
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
|
39
|
+
|
|
40
|
+
CONSTRAINT "SchoolDevelopementProgram_pkey" PRIMARY KEY ("id")
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
-- CreateTable
|
|
44
|
+
CREATE TABLE "public"."EarlyAccessRequest" (
|
|
45
|
+
"id" TEXT NOT NULL,
|
|
46
|
+
"email" TEXT NOT NULL,
|
|
47
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
48
|
+
"institutionSize" TEXT NOT NULL,
|
|
49
|
+
|
|
50
|
+
CONSTRAINT "EarlyAccessRequest_pkey" PRIMARY KEY ("id")
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
-- AddForeignKey
|
|
54
|
+
ALTER TABLE "public"."File" ADD CONSTRAINT "File_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "public"."Message"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
55
|
+
|
|
56
|
+
-- AddForeignKey
|
|
57
|
+
ALTER TABLE "public"."File" ADD CONSTRAINT "File_schoolDevelopementProgramId_fkey" FOREIGN KEY ("schoolDevelopementProgramId") REFERENCES "public"."SchoolDevelopementProgram"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
package/prisma/schema.prisma
CHANGED
|
@@ -34,6 +34,14 @@ enum UserRole {
|
|
|
34
34
|
NONE
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
enum UploadStatus {
|
|
38
|
+
PENDING
|
|
39
|
+
UPLOADING
|
|
40
|
+
COMPLETED
|
|
41
|
+
FAILED
|
|
42
|
+
CANCELLED
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
model School {
|
|
38
46
|
id String @id @default(uuid())
|
|
39
47
|
name String
|
|
@@ -163,6 +171,17 @@ model File {
|
|
|
163
171
|
userId String?
|
|
164
172
|
uploadedAt DateTime? @default(now())
|
|
165
173
|
|
|
174
|
+
// Upload tracking fields
|
|
175
|
+
uploadStatus UploadStatus @default(PENDING)
|
|
176
|
+
uploadUrl String?
|
|
177
|
+
uploadExpiresAt DateTime?
|
|
178
|
+
uploadSessionId String?
|
|
179
|
+
uploadProgress Int?
|
|
180
|
+
uploadError String?
|
|
181
|
+
uploadRetryCount Int @default(0)
|
|
182
|
+
isOrphaned Boolean @default(false)
|
|
183
|
+
cleanupAt DateTime?
|
|
184
|
+
|
|
166
185
|
// Thumbnail relationship
|
|
167
186
|
thumbnail File? @relation("Thumbnail", fields: [thumbnailId], references: [id])
|
|
168
187
|
thumbnailId String? @unique
|