@studious-lms/server 1.6.0 → 1.8.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/models/access.d.ts +21 -66
- package/dist/models/access.d.ts.map +1 -1
- package/dist/models/access.js +10 -26
- package/dist/models/access.js.map +1 -1
- package/dist/models/agenda.d.ts +2 -0
- package/dist/models/agenda.d.ts.map +1 -1
- package/dist/models/announcement.d.ts +2 -1
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/announcement.js +2 -3
- package/dist/models/announcement.js.map +1 -1
- package/dist/models/assignment.d.ts +7 -0
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/auth.d.ts +2 -4
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +2 -3
- package/dist/models/auth.js.map +1 -1
- package/dist/models/class.d.ts +32 -4
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +15 -2
- package/dist/models/class.js.map +1 -1
- package/dist/models/cloudStorage.d.ts +140 -0
- package/dist/models/cloudStorage.d.ts.map +1 -0
- package/dist/models/cloudStorage.js +175 -0
- package/dist/models/cloudStorage.js.map +1 -0
- package/dist/models/comment.d.ts +0 -24
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/comment.js +2 -6
- package/dist/models/comment.js.map +1 -1
- package/dist/models/conversation.d.ts +38 -22
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/conversation.js +50 -2
- package/dist/models/conversation.js.map +1 -1
- package/dist/models/department.d.ts +154 -0
- package/dist/models/department.d.ts.map +1 -0
- package/dist/models/department.js +135 -0
- package/dist/models/department.js.map +1 -0
- package/dist/models/event.d.ts +7 -1
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/event.js +2 -5
- package/dist/models/event.js.map +1 -1
- package/dist/models/file.d.ts +25 -10
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/file.js +2 -5
- package/dist/models/file.js.map +1 -1
- package/dist/models/folder.d.ts +47 -0
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +5 -3
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/message.d.ts +46 -12
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/message.js +19 -10
- package/dist/models/message.js.map +1 -1
- package/dist/models/newtonChat.d.ts +2 -1
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/school.d.ts +45 -12
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +19 -2
- package/dist/models/school.js.map +1 -1
- package/dist/models/section.d.ts +1 -0
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/tag.d.ts +117 -0
- package/dist/models/tag.d.ts.map +1 -0
- package/dist/models/tag.js +113 -0
- package/dist/models/tag.js.map +1 -0
- package/dist/models/worksheet.d.ts +3 -0
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +8 -8
- package/dist/routers/_app.d.ts +3556 -2298
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +6 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/agenda.d.ts +2 -0
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +3 -5
- package/dist/routers/announcement.js.map +1 -1
- package/dist/routers/assignment.d.ts +18 -9
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +2 -3
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/class.d.ts +87 -14
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +5 -2
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/conversation.d.ts +20 -5
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/conversation.js +6 -3
- package/dist/routers/conversation.js.map +1 -1
- package/dist/routers/department.d.ts +293 -0
- package/dist/routers/department.d.ts.map +1 -0
- package/dist/routers/department.js +93 -0
- package/dist/routers/department.js.map +1 -0
- package/dist/routers/event.d.ts +2 -1
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +8 -6
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +26 -17
- package/dist/routers/file.js.map +1 -1
- package/dist/routers/folder.d.ts +139 -186
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +82 -90
- package/dist/routers/folder.js.map +1 -1
- package/dist/routers/labChat.d.ts +11 -2
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/message.d.ts +20 -2
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/newtonChat.d.ts +9 -0
- package/dist/routers/newtonChat.d.ts.map +1 -1
- package/dist/routers/school.d.ts +160 -17
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +46 -3
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/tag.d.ts +119 -0
- package/dist/routers/tag.d.ts.map +1 -0
- package/dist/routers/tag.js +52 -0
- package/dist/routers/tag.js.map +1 -0
- package/dist/routers/worksheet.d.ts +1 -0
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/seedDatabase.d.ts +0 -1
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/services/agenda.d.ts +2 -0
- package/dist/services/agenda.d.ts.map +1 -1
- package/dist/services/assignment.d.ts +18 -9
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +4 -4
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/class.d.ts +87 -14
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +91 -4
- package/dist/services/class.js.map +1 -1
- package/dist/services/conversation.d.ts +21 -5
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/conversation.js +22 -3
- package/dist/services/conversation.js.map +1 -1
- package/dist/services/department.d.ts +200 -0
- package/dist/services/department.d.ts.map +1 -0
- package/dist/services/department.js +368 -0
- package/dist/services/department.js.map +1 -0
- package/dist/services/event.d.ts +2 -1
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/file.d.ts +85 -0
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/file.js +92 -2
- package/dist/services/file.js.map +1 -1
- package/dist/services/folder.d.ts +125 -149
- package/dist/services/folder.d.ts.map +1 -1
- package/dist/services/folder.js +258 -199
- package/dist/services/folder.js.map +1 -1
- package/dist/services/labChat.d.ts +11 -2
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +8 -7
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/message.d.ts +20 -2
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +7 -23
- package/dist/services/message.js.map +1 -1
- package/dist/services/newtonChat.d.ts +9 -0
- package/dist/services/newtonChat.d.ts.map +1 -1
- package/dist/services/newtonChat.js +7 -6
- package/dist/services/newtonChat.js.map +1 -1
- package/dist/services/school.d.ts +142 -15
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +308 -54
- package/dist/services/school.js.map +1 -1
- package/dist/services/tag.d.ts +71 -0
- package/dist/services/tag.d.ts.map +1 -0
- package/dist/services/tag.js +178 -0
- package/dist/services/tag.js.map +1 -0
- package/dist/services/worksheet.d.ts +1 -0
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/utils/aiUser.d.ts.map +1 -1
- package/dist/utils/aiUser.js +2 -3
- package/dist/utils/aiUser.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +184 -0
- package/prisma/schema.prisma +119 -35
- package/src/models/access.ts +8 -24
- package/src/models/announcement.ts +0 -1
- package/src/models/auth.ts +0 -1
- package/src/models/class.ts +13 -0
- package/src/models/cloudStorage.ts +194 -0
- package/src/models/comment.ts +0 -4
- package/src/models/conversation.ts +54 -0
- package/src/models/department.ts +159 -0
- package/src/models/event.ts +0 -3
- package/src/models/file.ts +0 -3
- package/src/models/message.ts +19 -5
- package/src/models/school.ts +26 -0
- package/src/models/tag.ts +140 -0
- package/src/routers/_app.ts +4 -0
- package/src/routers/announcement.ts +1 -3
- package/src/routers/assignment.ts +0 -1
- package/src/routers/class.ts +3 -0
- package/src/routers/conversation.ts +7 -0
- package/src/routers/department.ts +174 -0
- package/src/routers/file.ts +29 -26
- package/src/routers/folder.ts +100 -121
- package/src/routers/school.ts +83 -0
- package/src/routers/tag.ts +78 -0
- package/src/services/assignment.ts +2 -3
- package/src/services/class.ts +108 -3
- package/src/services/conversation.ts +30 -0
- package/src/services/department.ts +478 -0
- package/src/services/file.ts +100 -0
- package/src/services/folder.ts +313 -240
- package/src/services/labChat.ts +7 -6
- package/src/services/message.ts +5 -21
- package/src/services/newtonChat.ts +5 -5
- package/src/services/school.ts +369 -57
- package/src/services/tag.ts +229 -0
- package/src/utils/aiUser.ts +0 -1
- package/tests/integration/routers/conversation.test.ts +121 -2
package/dist/services/folder.js
CHANGED
|
@@ -1,248 +1,307 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Folder service
|
|
2
|
+
* Folder service — single source of truth for folders across every scope.
|
|
3
|
+
*
|
|
4
|
+
* A `Folder` can live inside one of four scopes (see `models/cloudStorage.ts`):
|
|
5
|
+
* class files, school storage, school documents, or a user's personal storage.
|
|
6
|
+
* Permissions and ownership are derived from the scope by walking up the tree
|
|
7
|
+
* to the root, so callers can either pass a `scope` (when there is no folder
|
|
8
|
+
* id yet, e.g. browsing the root) or just a `folderId` (everything else).
|
|
9
|
+
*
|
|
10
|
+
* The class files page and the global cloud-storage view both consume these
|
|
11
|
+
* endpoints — no duplicate routers.
|
|
3
12
|
*/
|
|
4
13
|
|
|
5
|
-
!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]="
|
|
14
|
+
!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]="1e1a55f0-d115-5d93-9636-a89ae577bf45")}catch(e){}}();
|
|
6
15
|
import { TRPCError } from "@trpc/server";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
import { prisma } from "../lib/prisma.js";
|
|
17
|
+
import { createSubFolder, deleteFolderById, findFolderById, findFolderWithContents, findOrCreateScopeRoot, listFolderBreadcrumbs, renameFolder, resolveFolderScope, } from "../models/cloudStorage.js";
|
|
18
|
+
import { createDirectUploadFiles, confirmDirectUpload, } from "../lib/fileUpload.js";
|
|
19
|
+
async function requireScopeAccess(userId, scope, level) {
|
|
20
|
+
switch (scope.kind) {
|
|
21
|
+
case "CLASS": {
|
|
22
|
+
const classData = await prisma.class.findFirst({
|
|
23
|
+
where: { id: scope.classId },
|
|
24
|
+
select: {
|
|
25
|
+
id: true,
|
|
26
|
+
schoolId: true,
|
|
27
|
+
teachers: { where: { id: userId }, select: { id: true } },
|
|
28
|
+
students: { where: { id: userId }, select: { id: true } },
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
if (!classData) {
|
|
32
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Class not found" });
|
|
33
|
+
}
|
|
34
|
+
const isTeacher = classData.teachers.length > 0;
|
|
35
|
+
const isStudent = classData.students.length > 0;
|
|
36
|
+
const isSchoolAdmin = classData.schoolId
|
|
37
|
+
? Boolean(await prisma.schoolMembership.findFirst({
|
|
38
|
+
where: {
|
|
39
|
+
schoolId: classData.schoolId,
|
|
40
|
+
userId,
|
|
41
|
+
role: "ADMIN",
|
|
42
|
+
status: "ACTIVE",
|
|
43
|
+
},
|
|
44
|
+
select: { id: true },
|
|
45
|
+
}))
|
|
46
|
+
: false;
|
|
47
|
+
if (!isTeacher && !isStudent && !isSchoolAdmin) {
|
|
48
|
+
throw new TRPCError({
|
|
49
|
+
code: "FORBIDDEN",
|
|
50
|
+
message: "You don't have access to this class",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const canWrite = isTeacher || isSchoolAdmin;
|
|
54
|
+
if (level === "WRITE" && !canWrite) {
|
|
55
|
+
throw new TRPCError({
|
|
56
|
+
code: "FORBIDDEN",
|
|
57
|
+
message: "Only class teachers can modify class files",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return { scope, canWrite };
|
|
61
|
+
}
|
|
62
|
+
case "SCHOOL_STORAGE": {
|
|
63
|
+
const membership = await prisma.schoolMembership.findFirst({
|
|
64
|
+
where: { schoolId: scope.schoolId, userId, status: "ACTIVE" },
|
|
65
|
+
select: { role: true },
|
|
29
66
|
});
|
|
67
|
+
if (!membership) {
|
|
68
|
+
throw new TRPCError({
|
|
69
|
+
code: "FORBIDDEN",
|
|
70
|
+
message: "You aren't a member of this institution",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const canWrite = membership.role === "ADMIN" || membership.role === "TEACHER";
|
|
74
|
+
if (level === "WRITE" && !canWrite) {
|
|
75
|
+
throw new TRPCError({
|
|
76
|
+
code: "FORBIDDEN",
|
|
77
|
+
message: "Only teachers and admins can modify institution storage",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return { scope, canWrite };
|
|
81
|
+
}
|
|
82
|
+
case "PERSONAL": {
|
|
83
|
+
if (scope.userId !== userId) {
|
|
84
|
+
throw new TRPCError({
|
|
85
|
+
code: "FORBIDDEN",
|
|
86
|
+
message: "You can only access your own personal storage",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return { scope, canWrite: true };
|
|
30
90
|
}
|
|
31
91
|
}
|
|
32
|
-
return createFolder({
|
|
33
|
-
name: input.name,
|
|
34
|
-
parentFolderId,
|
|
35
|
-
color: input.color,
|
|
36
|
-
});
|
|
37
92
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
throw new TRPCError({
|
|
43
|
-
code: "NOT_FOUND",
|
|
44
|
-
message: "Folder not found",
|
|
45
|
-
});
|
|
93
|
+
export async function requireFolderAccess(userId, folderId, level) {
|
|
94
|
+
const scope = await resolveFolderScope(folderId);
|
|
95
|
+
if (!scope) {
|
|
96
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Folder not found" });
|
|
46
97
|
}
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
export async function getChildFolders(classId) {
|
|
50
|
-
const root = await findOrCreateRootFolder(classId);
|
|
51
|
-
return findChildFolders(root.id);
|
|
98
|
+
return requireScopeAccess(userId, scope, level);
|
|
52
99
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
100
|
+
function scopesEqual(a, b) {
|
|
101
|
+
if (a.kind !== b.kind)
|
|
102
|
+
return false;
|
|
103
|
+
switch (a.kind) {
|
|
104
|
+
case "CLASS":
|
|
105
|
+
return a.classId === b.classId;
|
|
106
|
+
case "SCHOOL_STORAGE":
|
|
107
|
+
return a.schoolId === b.schoolId;
|
|
108
|
+
case "PERSONAL":
|
|
109
|
+
return a.userId === b.userId;
|
|
64
110
|
}
|
|
65
|
-
return rootWithContents;
|
|
66
111
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
112
|
+
/** Browse a folder. Pass `folderId` to drill into a specific folder, or a `scope` to land on its root. */
|
|
113
|
+
export async function browseFolder(userId, input) {
|
|
114
|
+
let scope;
|
|
115
|
+
let folderId;
|
|
116
|
+
if ("folderId" in input) {
|
|
117
|
+
folderId = input.folderId;
|
|
118
|
+
const resolved = await resolveFolderScope(input.folderId);
|
|
119
|
+
if (!resolved) {
|
|
120
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Folder not found" });
|
|
121
|
+
}
|
|
122
|
+
scope = resolved;
|
|
74
123
|
}
|
|
75
|
-
|
|
124
|
+
else {
|
|
125
|
+
scope = input.scope;
|
|
126
|
+
}
|
|
127
|
+
const access = await requireScopeAccess(userId, scope, "READ");
|
|
128
|
+
const targetId = folderId ?? (await findOrCreateScopeRoot(scope)).id;
|
|
129
|
+
const [folder, breadcrumbs] = await Promise.all([
|
|
130
|
+
findFolderWithContents(targetId),
|
|
131
|
+
listFolderBreadcrumbs(targetId),
|
|
132
|
+
]);
|
|
76
133
|
if (!folder) {
|
|
77
|
-
throw new TRPCError({
|
|
78
|
-
code: "NOT_FOUND",
|
|
79
|
-
message: "Folder not found",
|
|
80
|
-
});
|
|
134
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Folder not found" });
|
|
81
135
|
}
|
|
82
|
-
const uploadedFiles = await createDirectUploadFiles(files, userId, folderId);
|
|
83
136
|
return {
|
|
84
|
-
|
|
85
|
-
|
|
137
|
+
folder,
|
|
138
|
+
breadcrumbs,
|
|
139
|
+
canWrite: access.canWrite,
|
|
140
|
+
scope,
|
|
86
141
|
};
|
|
87
142
|
}
|
|
88
|
-
export async function
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
143
|
+
export async function getRootFolder(userId, scope) {
|
|
144
|
+
const result = await browseFolder(userId, { scope });
|
|
145
|
+
return result.folder;
|
|
146
|
+
}
|
|
147
|
+
export async function getFolder(userId, folderId) {
|
|
148
|
+
const result = await browseFolder(userId, { folderId });
|
|
149
|
+
return result.folder;
|
|
150
|
+
}
|
|
151
|
+
export async function getFolderBreadcrumbs(userId, folderId) {
|
|
152
|
+
await requireFolderAccess(userId, folderId, "READ");
|
|
153
|
+
return listFolderBreadcrumbs(folderId);
|
|
154
|
+
}
|
|
155
|
+
export async function getFolderParents(userId, folderId) {
|
|
156
|
+
// Permission: user can read this folder.
|
|
157
|
+
await requireFolderAccess(userId, folderId, "READ");
|
|
158
|
+
const breadcrumbs = await listFolderBreadcrumbs(folderId);
|
|
159
|
+
// Existing API returned full folder records — re-fetch them.
|
|
160
|
+
return Promise.all(breadcrumbs.map((b) => findFolderById(b.id)));
|
|
161
|
+
}
|
|
162
|
+
export async function createFolderRecord(userId, input) {
|
|
163
|
+
let parentId;
|
|
164
|
+
if (input.parentFolderId) {
|
|
165
|
+
parentId = input.parentFolderId;
|
|
166
|
+
await requireFolderAccess(userId, parentId, "WRITE");
|
|
102
167
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
break;
|
|
110
|
-
if (current.classId === classId) {
|
|
111
|
-
belongsToClass = true;
|
|
112
|
-
break;
|
|
168
|
+
else {
|
|
169
|
+
if (!input.scope) {
|
|
170
|
+
throw new TRPCError({
|
|
171
|
+
code: "BAD_REQUEST",
|
|
172
|
+
message: "scope is required when parentFolderId is omitted",
|
|
173
|
+
});
|
|
113
174
|
}
|
|
114
|
-
|
|
175
|
+
await requireScopeAccess(userId, input.scope, "WRITE");
|
|
176
|
+
parentId = (await findOrCreateScopeRoot(input.scope)).id;
|
|
115
177
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
await deleteFolderById(folderId);
|
|
123
|
-
return {
|
|
124
|
-
success: true,
|
|
125
|
-
deletedFiles: folder._count.files,
|
|
126
|
-
deletedFolders: folder._count.childFolders + 1,
|
|
127
|
-
};
|
|
178
|
+
return createSubFolder({
|
|
179
|
+
name: input.name.trim(),
|
|
180
|
+
parentFolderId: parentId,
|
|
181
|
+
color: input.color,
|
|
182
|
+
});
|
|
128
183
|
}
|
|
129
|
-
export async function
|
|
130
|
-
const
|
|
131
|
-
if (!classData) {
|
|
132
|
-
throw new TRPCError({
|
|
133
|
-
code: "FORBIDDEN",
|
|
134
|
-
message: "You must be a teacher of this class to move folders",
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
const folder = await findFolderById(folderId);
|
|
184
|
+
export async function updateFolderRecord(userId, input) {
|
|
185
|
+
const folder = await findFolderById(input.folderId);
|
|
138
186
|
if (!folder) {
|
|
139
|
-
throw new TRPCError({
|
|
140
|
-
code: "NOT_FOUND",
|
|
141
|
-
message: "Folder not found",
|
|
142
|
-
});
|
|
187
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Folder not found" });
|
|
143
188
|
}
|
|
144
189
|
if (!folder.parentFolderId) {
|
|
145
190
|
throw new TRPCError({
|
|
146
191
|
code: "BAD_REQUEST",
|
|
147
|
-
message: "Cannot
|
|
192
|
+
message: "Cannot rename a root folder",
|
|
148
193
|
});
|
|
149
194
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (!targetParent) {
|
|
153
|
-
throw new TRPCError({
|
|
154
|
-
code: "NOT_FOUND",
|
|
155
|
-
message: "Target parent folder not found",
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
if (targetParentFolderId === folderId) {
|
|
159
|
-
throw new TRPCError({
|
|
160
|
-
code: "BAD_REQUEST",
|
|
161
|
-
message: "Cannot move a folder into itself",
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
let currentParent = targetParent;
|
|
165
|
-
while (currentParent?.parentFolderId) {
|
|
166
|
-
if (currentParent.parentFolderId === folderId) {
|
|
167
|
-
throw new TRPCError({
|
|
168
|
-
code: "BAD_REQUEST",
|
|
169
|
-
message: "Cannot move a folder into its descendant",
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
currentParent = await findFolderByIdForTraversal(currentParent.parentFolderId);
|
|
173
|
-
if (!currentParent)
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return updateFolderParent(folderId, targetParentFolderId);
|
|
195
|
+
await requireFolderAccess(userId, input.folderId, "WRITE");
|
|
196
|
+
return renameFolder(input.folderId, input.name?.trim() ?? folder.name, input.color);
|
|
178
197
|
}
|
|
179
|
-
export async function
|
|
198
|
+
export async function deleteFolder(userId, folderId) {
|
|
180
199
|
const folder = await findFolderById(folderId);
|
|
181
200
|
if (!folder) {
|
|
182
|
-
throw new TRPCError({
|
|
183
|
-
code: "NOT_FOUND",
|
|
184
|
-
message: "Folder not found",
|
|
185
|
-
});
|
|
201
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Folder not found" });
|
|
186
202
|
}
|
|
187
|
-
if (!
|
|
203
|
+
if (!folder.parentFolderId) {
|
|
188
204
|
throw new TRPCError({
|
|
189
205
|
code: "BAD_REQUEST",
|
|
190
|
-
message: "
|
|
206
|
+
message: "Cannot delete a root folder",
|
|
191
207
|
});
|
|
192
208
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
export async function getFolderParents(folderId) {
|
|
199
|
-
const parents = [];
|
|
200
|
-
let currentId = folderId;
|
|
201
|
-
while (currentId) {
|
|
202
|
-
const folder = await findFolderByIdForTraversal(currentId);
|
|
203
|
-
if (!folder)
|
|
204
|
-
break;
|
|
205
|
-
parents.push(folder);
|
|
206
|
-
currentId = folder.parentFolderId;
|
|
207
|
-
}
|
|
208
|
-
return parents;
|
|
209
|
+
await requireFolderAccess(userId, folderId, "WRITE");
|
|
210
|
+
await deleteFolderById(folderId);
|
|
211
|
+
return { success: true };
|
|
209
212
|
}
|
|
210
|
-
export async function
|
|
211
|
-
|
|
212
|
-
if (!classData) {
|
|
213
|
+
export async function moveFolder(userId, input) {
|
|
214
|
+
if (input.folderId === input.targetParentFolderId) {
|
|
213
215
|
throw new TRPCError({
|
|
214
|
-
code: "
|
|
215
|
-
message: "
|
|
216
|
+
code: "BAD_REQUEST",
|
|
217
|
+
message: "Cannot move a folder into itself",
|
|
216
218
|
});
|
|
217
219
|
}
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
+
const [sourceScope, targetScope] = await Promise.all([
|
|
221
|
+
resolveFolderScope(input.folderId),
|
|
222
|
+
resolveFolderScope(input.targetParentFolderId),
|
|
223
|
+
]);
|
|
224
|
+
if (!sourceScope || !targetScope) {
|
|
225
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Folder not found" });
|
|
226
|
+
}
|
|
227
|
+
if (!scopesEqual(sourceScope, targetScope)) {
|
|
220
228
|
throw new TRPCError({
|
|
221
|
-
code: "
|
|
222
|
-
message: "
|
|
229
|
+
code: "BAD_REQUEST",
|
|
230
|
+
message: "Cannot move folders between scopes",
|
|
223
231
|
});
|
|
224
232
|
}
|
|
225
|
-
|
|
226
|
-
let
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
break;
|
|
233
|
+
await requireScopeAccess(userId, sourceScope, "WRITE");
|
|
234
|
+
// Don't let a folder land under one of its own descendants.
|
|
235
|
+
let cursor = input.targetParentFolderId;
|
|
236
|
+
while (cursor) {
|
|
237
|
+
if (cursor === input.folderId) {
|
|
238
|
+
throw new TRPCError({
|
|
239
|
+
code: "BAD_REQUEST",
|
|
240
|
+
message: "Cannot move a folder into its own descendant",
|
|
241
|
+
});
|
|
237
242
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
code: "FORBIDDEN",
|
|
242
|
-
message: "Folder does not belong to this class",
|
|
243
|
+
const node = await prisma.folder.findUnique({
|
|
244
|
+
where: { id: cursor },
|
|
245
|
+
select: { parentFolderId: true },
|
|
243
246
|
});
|
|
247
|
+
cursor = node?.parentFolderId ?? null;
|
|
244
248
|
}
|
|
245
|
-
|
|
249
|
+
await prisma.folder.update({
|
|
250
|
+
where: { id: input.folderId },
|
|
251
|
+
data: { parentFolderId: input.targetParentFolderId },
|
|
252
|
+
});
|
|
253
|
+
return { success: true };
|
|
254
|
+
}
|
|
255
|
+
export async function getFolderUploadUrls(userId, input) {
|
|
256
|
+
await requireFolderAccess(userId, input.folderId, "WRITE");
|
|
257
|
+
return createDirectUploadFiles(input.files, userId, input.folderId);
|
|
258
|
+
}
|
|
259
|
+
export async function confirmFolderUpload(_userId, input) {
|
|
260
|
+
// Permission was already checked at upload-url issuance — the file record
|
|
261
|
+
// exists with PENDING status, and the user holds the session id needed to
|
|
262
|
+
// PUT bytes. Confirming flips status, so we just trust the call.
|
|
263
|
+
await confirmDirectUpload(input.fileId, input.uploadSuccess, input.errorMessage);
|
|
264
|
+
return { success: true };
|
|
265
|
+
}
|
|
266
|
+
/** What scopes can this user pick from in cloud-storage UIs? */
|
|
267
|
+
export async function listUserScopes(userId) {
|
|
268
|
+
const memberships = await prisma.schoolMembership.findMany({
|
|
269
|
+
where: { userId, status: "ACTIVE" },
|
|
270
|
+
select: {
|
|
271
|
+
role: true,
|
|
272
|
+
school: {
|
|
273
|
+
select: { id: true, name: true, logoId: true },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
orderBy: { createdAt: "asc" },
|
|
277
|
+
});
|
|
278
|
+
const classes = await prisma.class.findMany({
|
|
279
|
+
where: {
|
|
280
|
+
OR: [
|
|
281
|
+
{ teachers: { some: { id: userId } } },
|
|
282
|
+
{ students: { some: { id: userId } } },
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
select: {
|
|
286
|
+
id: true,
|
|
287
|
+
name: true,
|
|
288
|
+
subject: true,
|
|
289
|
+
section: true,
|
|
290
|
+
color: true,
|
|
291
|
+
schoolId: true,
|
|
292
|
+
},
|
|
293
|
+
orderBy: { name: "asc" },
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
personal: { kind: "PERSONAL", userId },
|
|
297
|
+
schools: memberships.map((m) => ({
|
|
298
|
+
id: m.school.id,
|
|
299
|
+
name: m.school.name,
|
|
300
|
+
logoId: m.school.logoId,
|
|
301
|
+
role: m.role,
|
|
302
|
+
})),
|
|
303
|
+
classes,
|
|
304
|
+
};
|
|
246
305
|
}
|
|
247
306
|
//# sourceMappingURL=folder.js.map
|
|
248
|
-
//# debugId=
|
|
307
|
+
//# debugId=1e1a55f0-d115-5d93-9636-a89ae577bf45
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder.js","sources":["services/folder.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Folder service – CRUD for class folders, file uploads, and folder hierarchy.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport {\n findClassWithTeacher,\n findFolderById,\n findOrCreateRootFolder,\n createFolder,\n findFolderWithContents,\n findChildFolders,\n findRootFolderWithContents,\n findFolderByIdWithCount,\n deleteFolderById,\n updateFolderParent,\n updateFolder,\n findFolderByIdForTraversal,\n} from \"../models/folder.js\";\nimport { createDirectUploadFiles } from \"../lib/fileUpload.js\";\n\n/** Create a folder in a class. Teacher-only. */\nexport async function createFolderRecord(\n userId: string,\n input: {\n classId: string;\n name: string;\n parentFolderId?: string | null;\n color?: string;\n }\n) {\n const classData = await findClassWithTeacher(input.classId, userId);\n if (!classData) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Class not found or you are not a teacher\",\n });\n }\n\n let parentFolderId = input.parentFolderId;\n if (!parentFolderId) {\n const root = await findOrCreateRootFolder(input.classId);\n parentFolderId = root.id;\n } else {\n const parentFolder = await findFolderById(parentFolderId);\n if (!parentFolder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Parent folder not found\",\n });\n }\n }\n\n return createFolder({\n name: input.name,\n parentFolderId,\n color: input.color,\n });\n}\n\n/** Get folder with its contents (subfolders and files). */\nexport async function getFolder(folderId: string) {\n const folder = await findFolderWithContents(folderId);\n if (!folder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Folder not found\",\n });\n }\n return folder;\n}\n\nexport async function getChildFolders(classId: string) {\n const root = await findOrCreateRootFolder(classId);\n return findChildFolders(root.id);\n}\n\nexport async function getFolderChildren(folderId: string, classId: string) {\n return findChildFolders(folderId);\n}\n\nexport async function getRootFolder(classId: string) {\n const root = await findOrCreateRootFolder(classId);\n const rootWithContents = await findRootFolderWithContents(classId);\n if (!rootWithContents) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Root folder not found\",\n });\n }\n return rootWithContents;\n}\n\nexport async function uploadFilesToFolder(\n userId: string,\n classId: string,\n folderId: string,\n files: { name: string; type: string; size: number }[]\n) {\n const classData = await findClassWithTeacher(classId, userId);\n if (!classData) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Class not found or you are not a teacher\",\n });\n }\n\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Folder not found\",\n });\n }\n\n const uploadedFiles = await createDirectUploadFiles(files, userId, folderId);\n return {\n success: true,\n uploadedCount: uploadedFiles.length,\n };\n}\n\nexport async function deleteFolderRecord(\n userId: string,\n classId: string,\n folderId: string\n) {\n const classData = await findClassWithTeacher(classId, userId);\n if (!classData) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Class not found or you are not a teacher\",\n });\n }\n\n const folder = await findFolderByIdWithCount(folderId);\n if (!folder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Folder not found\",\n });\n }\n\n // Verify folder belongs to class (traverse to root)\n let currentId: string | null = folderId;\n let belongsToClass = false;\n while (currentId) {\n const current = await findFolderByIdForTraversal(currentId);\n if (!current) break;\n if (current.classId === classId) {\n belongsToClass = true;\n break;\n }\n currentId = current.parentFolderId;\n }\n if (!belongsToClass) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Folder does not belong to this class\",\n });\n }\n\n await deleteFolderById(folderId);\n return {\n success: true,\n deletedFiles: folder._count.files,\n deletedFolders: folder._count.childFolders + 1,\n };\n}\n\nexport async function moveFolder(\n userId: string,\n folderId: string,\n targetParentFolderId: string,\n classId: string\n) {\n const classData = await findClassWithTeacher(classId, userId);\n if (!classData) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You must be a teacher of this class to move folders\",\n });\n }\n\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Folder not found\",\n });\n }\n\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move the root folder\",\n });\n }\n\n if (targetParentFolderId) {\n const targetParent = await findFolderByIdForTraversal(targetParentFolderId);\n if (!targetParent) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Target parent folder not found\",\n });\n }\n\n if (targetParentFolderId === folderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into itself\",\n });\n }\n\n let currentParent: typeof targetParent | null = targetParent;\n while (currentParent?.parentFolderId) {\n if (currentParent.parentFolderId === folderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into its descendant\",\n });\n }\n currentParent = await findFolderByIdForTraversal(currentParent.parentFolderId);\n if (!currentParent) break;\n }\n }\n\n return updateFolderParent(folderId, targetParentFolderId);\n}\n\nexport async function updateFolderRecord(\n folderId: string,\n name: string,\n color: string | undefined,\n classId: string\n) {\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Folder not found\",\n });\n }\n\n if (!name.trim()) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Folder name cannot be empty\",\n });\n }\n\n return updateFolder(folderId, {\n name: name.trim(),\n ...(color && { color }),\n });\n}\n\nexport async function getFolderParents(folderId: string) {\n const parents: Awaited<ReturnType<typeof findFolderByIdForTraversal>>[] = [];\n let currentId: string | null = folderId;\n\n while (currentId) {\n const folder = await findFolderByIdForTraversal(currentId);\n if (!folder) break;\n parents.push(folder);\n currentId = folder.parentFolderId;\n }\n\n return parents;\n}\n\nexport async function getFolderUploadUrls(\n userId: string,\n classId: string,\n folderId: string,\n files: { name: string; type: string; size: number }[]\n) {\n const classData = await findClassWithTeacher(classId, userId);\n if (!classData) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Class not found or you are not a teacher\",\n });\n }\n\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Folder not found\",\n });\n }\n\n let currentFolder: typeof folder | null = folder;\n let belongsToClass = false;\n while (currentFolder) {\n if (currentFolder.classId === classId) {\n belongsToClass = true;\n break;\n }\n if (currentFolder.parentFolderId) {\n currentFolder = await findFolderByIdForTraversal(currentFolder.parentFolderId);\n } else {\n break;\n }\n }\n\n if (!belongsToClass) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Folder does not belong to this class\",\n });\n }\n\n return createDirectUploadFiles(files, userId, folderId);\n}\n"],"names":[],"mappings":"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","debug_id":"4875a835-3cd4-5fcf-8e19-868d405dfe96"}
|
|
1
|
+
{"version":3,"file":"folder.js","sources":["services/folder.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Folder service — single source of truth for folders across every scope.\n *\n * A `Folder` can live inside one of four scopes (see `models/cloudStorage.ts`):\n * class files, school storage, school documents, or a user's personal storage.\n * Permissions and ownership are derived from the scope by walking up the tree\n * to the root, so callers can either pass a `scope` (when there is no folder\n * id yet, e.g. browsing the root) or just a `folderId` (everything else).\n *\n * The class files page and the global cloud-storage view both consume these\n * endpoints — no duplicate routers.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n CloudScope,\n createSubFolder,\n deleteFolderById,\n findFolderById,\n findFolderWithContents,\n findOrCreateScopeRoot,\n listFolderBreadcrumbs,\n renameFolder,\n resolveFolderScope,\n} from \"../models/cloudStorage.js\";\nimport {\n createDirectUploadFiles,\n type DirectFileData,\n confirmDirectUpload,\n} from \"../lib/fileUpload.js\";\n\ntype AccessLevel = \"READ\" | \"WRITE\";\n\ninterface AccessResult {\n scope: CloudScope;\n canWrite: boolean;\n}\n\nasync function requireScopeAccess(\n userId: string,\n scope: CloudScope,\n level: AccessLevel\n): Promise<AccessResult> {\n switch (scope.kind) {\n case \"CLASS\": {\n const classData = await prisma.class.findFirst({\n where: { id: scope.classId },\n select: {\n id: true,\n schoolId: true,\n teachers: { where: { id: userId }, select: { id: true } },\n students: { where: { id: userId }, select: { id: true } },\n },\n });\n if (!classData) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n const isTeacher = classData.teachers.length > 0;\n const isStudent = classData.students.length > 0;\n const isSchoolAdmin = classData.schoolId\n ? Boolean(\n await prisma.schoolMembership.findFirst({\n where: {\n schoolId: classData.schoolId,\n userId,\n role: \"ADMIN\",\n status: \"ACTIVE\",\n },\n select: { id: true },\n })\n )\n : false;\n if (!isTeacher && !isStudent && !isSchoolAdmin) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You don't have access to this class\",\n });\n }\n const canWrite = isTeacher || isSchoolAdmin;\n if (level === \"WRITE\" && !canWrite) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only class teachers can modify class files\",\n });\n }\n return { scope, canWrite };\n }\n case \"SCHOOL_STORAGE\": {\n const membership = await prisma.schoolMembership.findFirst({\n where: { schoolId: scope.schoolId, userId, status: \"ACTIVE\" },\n select: { role: true },\n });\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You aren't a member of this institution\",\n });\n }\n const canWrite =\n membership.role === \"ADMIN\" || membership.role === \"TEACHER\";\n if (level === \"WRITE\" && !canWrite) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only teachers and admins can modify institution storage\",\n });\n }\n return { scope, canWrite };\n }\n case \"PERSONAL\": {\n if (scope.userId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You can only access your own personal storage\",\n });\n }\n return { scope, canWrite: true };\n }\n }\n}\n\nexport async function requireFolderAccess(\n userId: string,\n folderId: string,\n level: AccessLevel\n) {\n const scope = await resolveFolderScope(folderId);\n if (!scope) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n return requireScopeAccess(userId, scope, level);\n}\n\nfunction scopesEqual(a: CloudScope, b: CloudScope): boolean {\n if (a.kind !== b.kind) return false;\n switch (a.kind) {\n case \"CLASS\":\n return a.classId === (b as typeof a).classId;\n case \"SCHOOL_STORAGE\":\n return a.schoolId === (b as typeof a).schoolId;\n case \"PERSONAL\":\n return a.userId === (b as typeof a).userId;\n }\n}\n\n/** Browse a folder. Pass `folderId` to drill into a specific folder, or a `scope` to land on its root. */\nexport async function browseFolder(\n userId: string,\n input: { folderId: string } | { scope: CloudScope }\n) {\n let scope: CloudScope;\n let folderId: string | undefined;\n\n if (\"folderId\" in input) {\n folderId = input.folderId;\n const resolved = await resolveFolderScope(input.folderId);\n if (!resolved) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n scope = resolved;\n } else {\n scope = input.scope;\n }\n\n const access = await requireScopeAccess(userId, scope, \"READ\");\n const targetId = folderId ?? (await findOrCreateScopeRoot(scope)).id;\n\n const [folder, breadcrumbs] = await Promise.all([\n findFolderWithContents(targetId),\n listFolderBreadcrumbs(targetId),\n ]);\n\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n\n return {\n folder,\n breadcrumbs,\n canWrite: access.canWrite,\n scope,\n };\n}\n\nexport async function getRootFolder(userId: string, scope: CloudScope) {\n const result = await browseFolder(userId, { scope });\n return result.folder;\n}\n\nexport async function getFolder(userId: string, folderId: string) {\n const result = await browseFolder(userId, { folderId });\n return result.folder;\n}\n\nexport async function getFolderBreadcrumbs(userId: string, folderId: string) {\n await requireFolderAccess(userId, folderId, \"READ\");\n return listFolderBreadcrumbs(folderId);\n}\n\nexport async function getFolderParents(userId: string, folderId: string) {\n // Permission: user can read this folder.\n await requireFolderAccess(userId, folderId, \"READ\");\n const breadcrumbs = await listFolderBreadcrumbs(folderId);\n // Existing API returned full folder records — re-fetch them.\n return Promise.all(\n breadcrumbs.map((b) => findFolderById(b.id))\n );\n}\n\nexport async function createFolderRecord(\n userId: string,\n input: {\n name: string;\n parentFolderId?: string | null;\n color?: string;\n scope?: CloudScope;\n }\n) {\n let parentId: string;\n if (input.parentFolderId) {\n parentId = input.parentFolderId;\n await requireFolderAccess(userId, parentId, \"WRITE\");\n } else {\n if (!input.scope) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"scope is required when parentFolderId is omitted\",\n });\n }\n await requireScopeAccess(userId, input.scope, \"WRITE\");\n parentId = (await findOrCreateScopeRoot(input.scope)).id;\n }\n return createSubFolder({\n name: input.name.trim(),\n parentFolderId: parentId,\n color: input.color,\n });\n}\n\nexport async function updateFolderRecord(\n userId: string,\n input: { folderId: string; name?: string; color?: string }\n) {\n const folder = await findFolderById(input.folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot rename a root folder\",\n });\n }\n await requireFolderAccess(userId, input.folderId, \"WRITE\");\n return renameFolder(\n input.folderId,\n input.name?.trim() ?? folder.name,\n input.color\n );\n}\n\nexport async function deleteFolder(userId: string, folderId: string) {\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot delete a root folder\",\n });\n }\n await requireFolderAccess(userId, folderId, \"WRITE\");\n await deleteFolderById(folderId);\n return { success: true };\n}\n\nexport async function moveFolder(\n userId: string,\n input: { folderId: string; targetParentFolderId: string }\n) {\n if (input.folderId === input.targetParentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into itself\",\n });\n }\n const [sourceScope, targetScope] = await Promise.all([\n resolveFolderScope(input.folderId),\n resolveFolderScope(input.targetParentFolderId),\n ]);\n if (!sourceScope || !targetScope) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!scopesEqual(sourceScope, targetScope)) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move folders between scopes\",\n });\n }\n await requireScopeAccess(userId, sourceScope, \"WRITE\");\n\n // Don't let a folder land under one of its own descendants.\n let cursor: string | null = input.targetParentFolderId;\n while (cursor) {\n if (cursor === input.folderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into its own descendant\",\n });\n }\n const node = await prisma.folder.findUnique({\n where: { id: cursor },\n select: { parentFolderId: true },\n });\n cursor = node?.parentFolderId ?? null;\n }\n\n await prisma.folder.update({\n where: { id: input.folderId },\n data: { parentFolderId: input.targetParentFolderId },\n });\n return { success: true };\n}\n\nexport async function getFolderUploadUrls(\n userId: string,\n input: { folderId: string; files: DirectFileData[] }\n) {\n await requireFolderAccess(userId, input.folderId, \"WRITE\");\n return createDirectUploadFiles(input.files, userId, input.folderId);\n}\n\nexport async function confirmFolderUpload(\n _userId: string,\n input: { fileId: string; uploadSuccess: boolean; errorMessage?: string }\n) {\n // Permission was already checked at upload-url issuance — the file record\n // exists with PENDING status, and the user holds the session id needed to\n // PUT bytes. Confirming flips status, so we just trust the call.\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n return { success: true };\n}\n\n/** What scopes can this user pick from in cloud-storage UIs? */\nexport async function listUserScopes(userId: string) {\n const memberships = await prisma.schoolMembership.findMany({\n where: { userId, status: \"ACTIVE\" },\n select: {\n role: true,\n school: {\n select: { id: true, name: true, logoId: true },\n },\n },\n orderBy: { createdAt: \"asc\" },\n });\n\n const classes = await prisma.class.findMany({\n where: {\n OR: [\n { teachers: { some: { id: userId } } },\n { students: { some: { id: userId } } },\n ],\n },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n schoolId: true,\n },\n orderBy: { name: \"asc\" },\n });\n\n return {\n personal: { kind: \"PERSONAL\" as const, userId },\n schools: memberships.map((m) => ({\n id: m.school.id,\n name: m.school.name,\n logoId: m.school.logoId,\n role: m.role,\n })),\n classes,\n };\n}\n"],"names":[],"mappings":"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","debug_id":"1e1a55f0-d115-5d93-9636-a89ae577bf45"}
|
|
@@ -22,8 +22,8 @@ export declare function createLabChat(userId: string, input: {
|
|
|
22
22
|
};
|
|
23
23
|
} & {
|
|
24
24
|
id: string;
|
|
25
|
-
role: import(".prisma/client").$Enums.ConversationRole;
|
|
26
25
|
userId: string;
|
|
26
|
+
role: import(".prisma/client").$Enums.ConversationRole;
|
|
27
27
|
conversationId: string;
|
|
28
28
|
joinedAt: Date;
|
|
29
29
|
hiddenAt: Date | null;
|
|
@@ -75,8 +75,8 @@ export declare function getLabChat(userId: string, labChatId: string): Promise<{
|
|
|
75
75
|
};
|
|
76
76
|
} & {
|
|
77
77
|
id: string;
|
|
78
|
-
role: import(".prisma/client").$Enums.ConversationRole;
|
|
79
78
|
userId: string;
|
|
79
|
+
role: import(".prisma/client").$Enums.ConversationRole;
|
|
80
80
|
conversationId: string;
|
|
81
81
|
joinedAt: Date;
|
|
82
82
|
hiddenAt: Date | null;
|
|
@@ -163,9 +163,18 @@ export declare function postToLabChat(userId: string, input: {
|
|
|
163
163
|
} | null;
|
|
164
164
|
};
|
|
165
165
|
attachments: {
|
|
166
|
+
path: string;
|
|
166
167
|
type: string;
|
|
167
168
|
name: string;
|
|
168
169
|
id: string;
|
|
170
|
+
size: number | null;
|
|
171
|
+
uploadedAt: Date | null;
|
|
172
|
+
uploadStatus: import(".prisma/client").$Enums.UploadStatus;
|
|
173
|
+
thumbnailId: string | null;
|
|
174
|
+
thumbnail: {
|
|
175
|
+
path: string;
|
|
176
|
+
id: string;
|
|
177
|
+
} | null;
|
|
169
178
|
}[];
|
|
170
179
|
mentionedUserIds: string[];
|
|
171
180
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labChat.d.ts","sourceRoot":"/","sources":["services/labChat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"labChat.d.ts","sourceRoot":"/","sources":["services/labChat.ts"],"names":[],"mappings":"AA6BA,sFAAsF;AACtF,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiI3D;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCjE;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAqBjE;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+JF;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;GAiDpE;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;GAiGxE"}
|