@studious-lms/server 1.7.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 +22 -22
- 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 +3200 -1972
- 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 +5 -5
- 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/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 +5 -5
- 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 +4 -3
- 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 +4 -3
- 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/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/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/class.ts +108 -3
- 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 +2 -1
- package/src/services/message.ts +5 -21
- package/src/services/newtonChat.ts +2 -1
- package/src/services/school.ts +369 -57
- package/src/services/tag.ts +229 -0
- package/src/utils/aiUser.ts +0 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tag service — institution-scoped tag CRUD and assignment to taggable
|
|
3
|
+
* targets (users, classes, rubrics, boundaries).
|
|
4
|
+
*/
|
|
5
|
+
import { TRPCError } from "@trpc/server";
|
|
6
|
+
import { prisma } from "../lib/prisma.js";
|
|
7
|
+
import {
|
|
8
|
+
attachTagToTarget,
|
|
9
|
+
createTag,
|
|
10
|
+
deleteTag,
|
|
11
|
+
detachTagFromTarget,
|
|
12
|
+
getTag,
|
|
13
|
+
listTagsAttachedTo,
|
|
14
|
+
listTagsForSchool,
|
|
15
|
+
type TagTarget,
|
|
16
|
+
updateTag,
|
|
17
|
+
} from "../models/tag.js";
|
|
18
|
+
import {
|
|
19
|
+
findActiveSchoolAdminMembership,
|
|
20
|
+
findSchoolMembership,
|
|
21
|
+
} from "../models/school.js";
|
|
22
|
+
|
|
23
|
+
async function requireAdmin(schoolId: string, userId: string) {
|
|
24
|
+
const membership = await findActiveSchoolAdminMembership(schoolId, userId);
|
|
25
|
+
if (!membership) {
|
|
26
|
+
throw new TRPCError({
|
|
27
|
+
code: "FORBIDDEN",
|
|
28
|
+
message: "Only school admins can manage tags",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function requireMember(schoolId: string, userId: string) {
|
|
34
|
+
const membership = await findSchoolMembership(schoolId, userId);
|
|
35
|
+
if (!membership || membership.status !== "ACTIVE") {
|
|
36
|
+
throw new TRPCError({
|
|
37
|
+
code: "FORBIDDEN",
|
|
38
|
+
message: "You aren't a member of this institution",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return membership;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function listTags(
|
|
45
|
+
userId: string,
|
|
46
|
+
input: { schoolId: string; departmentId?: string | null }
|
|
47
|
+
) {
|
|
48
|
+
await requireMember(input.schoolId, userId);
|
|
49
|
+
return listTagsForSchool(input.schoolId, {
|
|
50
|
+
departmentId: input.departmentId,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function createTagRecord(
|
|
55
|
+
userId: string,
|
|
56
|
+
input: {
|
|
57
|
+
schoolId: string;
|
|
58
|
+
departmentId?: string | null;
|
|
59
|
+
name: string;
|
|
60
|
+
color?: string;
|
|
61
|
+
}
|
|
62
|
+
) {
|
|
63
|
+
await requireAdmin(input.schoolId, userId);
|
|
64
|
+
if (input.departmentId) {
|
|
65
|
+
const dept = await prisma.department.findUnique({
|
|
66
|
+
where: { id: input.departmentId },
|
|
67
|
+
select: { schoolId: true },
|
|
68
|
+
});
|
|
69
|
+
if (!dept || dept.schoolId !== input.schoolId) {
|
|
70
|
+
throw new TRPCError({
|
|
71
|
+
code: "BAD_REQUEST",
|
|
72
|
+
message: "Department does not belong to this institution",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return createTag(input);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function updateTagRecord(
|
|
80
|
+
userId: string,
|
|
81
|
+
input: {
|
|
82
|
+
tagId: string;
|
|
83
|
+
name?: string;
|
|
84
|
+
color?: string;
|
|
85
|
+
departmentId?: string | null;
|
|
86
|
+
}
|
|
87
|
+
) {
|
|
88
|
+
const tag = await getTag(input.tagId);
|
|
89
|
+
if (!tag) {
|
|
90
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
|
|
91
|
+
}
|
|
92
|
+
await requireAdmin(tag.schoolId, userId);
|
|
93
|
+
if (input.departmentId) {
|
|
94
|
+
const dept = await prisma.department.findUnique({
|
|
95
|
+
where: { id: input.departmentId },
|
|
96
|
+
select: { schoolId: true },
|
|
97
|
+
});
|
|
98
|
+
if (!dept || dept.schoolId !== tag.schoolId) {
|
|
99
|
+
throw new TRPCError({
|
|
100
|
+
code: "BAD_REQUEST",
|
|
101
|
+
message: "Department does not belong to this institution",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return updateTag(input.tagId, {
|
|
106
|
+
name: input.name,
|
|
107
|
+
color: input.color,
|
|
108
|
+
departmentId: input.departmentId,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function deleteTagRecord(userId: string, tagId: string) {
|
|
113
|
+
const tag = await getTag(tagId);
|
|
114
|
+
if (!tag) {
|
|
115
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
|
|
116
|
+
}
|
|
117
|
+
await requireAdmin(tag.schoolId, userId);
|
|
118
|
+
await deleteTag(tagId);
|
|
119
|
+
return { success: true };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function ensureTargetInScope(
|
|
123
|
+
schoolId: string,
|
|
124
|
+
target: TagTarget,
|
|
125
|
+
targetId: string
|
|
126
|
+
) {
|
|
127
|
+
switch (target) {
|
|
128
|
+
case "USER": {
|
|
129
|
+
const inSchool = await prisma.schoolMembership.findFirst({
|
|
130
|
+
where: { schoolId, userId: targetId, status: "ACTIVE" },
|
|
131
|
+
select: { id: true },
|
|
132
|
+
});
|
|
133
|
+
if (!inSchool) {
|
|
134
|
+
throw new TRPCError({
|
|
135
|
+
code: "BAD_REQUEST",
|
|
136
|
+
message: "User is not in this institution",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
case "CLASS": {
|
|
142
|
+
const inSchool = await prisma.class.findFirst({
|
|
143
|
+
where: { id: targetId, schoolId },
|
|
144
|
+
select: { id: true },
|
|
145
|
+
});
|
|
146
|
+
if (!inSchool) {
|
|
147
|
+
throw new TRPCError({
|
|
148
|
+
code: "BAD_REQUEST",
|
|
149
|
+
message: "Class is not in this institution",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
case "RUBRIC": {
|
|
155
|
+
const inSchool = await prisma.markScheme.findFirst({
|
|
156
|
+
where: {
|
|
157
|
+
id: targetId,
|
|
158
|
+
OR: [
|
|
159
|
+
{ class: { schoolId } },
|
|
160
|
+
{ department: { schoolId } },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
select: { id: true },
|
|
164
|
+
});
|
|
165
|
+
if (!inSchool) {
|
|
166
|
+
throw new TRPCError({
|
|
167
|
+
code: "BAD_REQUEST",
|
|
168
|
+
message: "Rubric is not in this institution",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
case "BOUNDARY": {
|
|
174
|
+
const inSchool = await prisma.gradingBoundary.findFirst({
|
|
175
|
+
where: {
|
|
176
|
+
id: targetId,
|
|
177
|
+
OR: [
|
|
178
|
+
{ class: { schoolId } },
|
|
179
|
+
{ department: { schoolId } },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
select: { id: true },
|
|
183
|
+
});
|
|
184
|
+
if (!inSchool) {
|
|
185
|
+
throw new TRPCError({
|
|
186
|
+
code: "BAD_REQUEST",
|
|
187
|
+
message: "Boundary is not in this institution",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function attachTag(
|
|
196
|
+
userId: string,
|
|
197
|
+
input: { tagId: string; target: TagTarget; targetId: string }
|
|
198
|
+
) {
|
|
199
|
+
const tag = await getTag(input.tagId);
|
|
200
|
+
if (!tag) {
|
|
201
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
|
|
202
|
+
}
|
|
203
|
+
await requireMember(tag.schoolId, userId);
|
|
204
|
+
await ensureTargetInScope(tag.schoolId, input.target, input.targetId);
|
|
205
|
+
return attachTagToTarget(input);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function detachTag(
|
|
209
|
+
userId: string,
|
|
210
|
+
input: { tagId: string; target: TagTarget; targetId: string }
|
|
211
|
+
) {
|
|
212
|
+
const tag = await getTag(input.tagId);
|
|
213
|
+
if (!tag) {
|
|
214
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Tag not found" });
|
|
215
|
+
}
|
|
216
|
+
await requireMember(tag.schoolId, userId);
|
|
217
|
+
return detachTagFromTarget(input);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function getAttachedTags(
|
|
221
|
+
userId: string,
|
|
222
|
+
input: { target: TagTarget; targetId: string }
|
|
223
|
+
) {
|
|
224
|
+
const tags = await listTagsAttachedTo(input.target, input.targetId);
|
|
225
|
+
if (tags[0]) {
|
|
226
|
+
await requireMember(tags[0].schoolId, userId);
|
|
227
|
+
}
|
|
228
|
+
return tags;
|
|
229
|
+
}
|
package/src/utils/aiUser.ts
CHANGED
|
@@ -26,7 +26,6 @@ export async function ensureAIUserExists(): Promise<void> {
|
|
|
26
26
|
email: 'ai@studious-lms.com',
|
|
27
27
|
password: 'ai-system-user', // Not used for login
|
|
28
28
|
verified: true,
|
|
29
|
-
role: 'NONE', // Special role for AI
|
|
30
29
|
profile: {
|
|
31
30
|
create: {
|
|
32
31
|
displayName: 'AI Assistant',
|