@studious-lms/server 1.7.0 → 1.9.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/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +3 -3
- package/dist/middleware/security.js.map +1 -1
- 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 +4 -2
- package/dist/models/agenda.d.ts.map +1 -1
- package/dist/models/announcement.d.ts +4 -3
- 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 +40 -33
- 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 +34 -6
- 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 +153 -0
- package/dist/models/cloudStorage.d.ts.map +1 -0
- package/dist/models/cloudStorage.js +295 -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 +10 -4
- 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 +52 -36
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/file.js +4 -42
- 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 +11 -9
- 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 +2 -1
- 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 +6704 -5222
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +8 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/agenda.d.ts +4 -2
- 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 +49 -40
- 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 +92 -19
- 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 +4 -3
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +10 -8
- 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 +163 -186
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +102 -88
- package/dist/routers/folder.js.map +1 -1
- package/dist/routers/labChat.d.ts +13 -4
- 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 +10 -1
- package/dist/routers/newtonChat.d.ts.map +1 -1
- package/dist/routers/permissions.d.ts +120 -0
- package/dist/routers/permissions.d.ts.map +1 -0
- package/dist/routers/permissions.js +50 -0
- package/dist/routers/permissions.js.map +1 -0
- 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 +4 -2
- package/dist/services/agenda.d.ts.map +1 -1
- package/dist/services/assignment.d.ts +49 -40
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/class.d.ts +92 -19
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +92 -5
- 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 +4 -3
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/file.d.ts +105 -3
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/file.js +167 -41
- package/dist/services/file.js.map +1 -1
- package/dist/services/folder.d.ts +220 -149
- package/dist/services/folder.d.ts.map +1 -1
- package/dist/services/folder.js +326 -196
- package/dist/services/folder.js.map +1 -1
- package/dist/services/labChat.d.ts +13 -4
- 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/permissions.d.ts +188 -0
- package/dist/services/permissions.d.ts.map +1 -0
- package/dist/services/permissions.js +661 -0
- package/dist/services/permissions.js.map +1 -0
- 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/migrations/20260510170000_add_folder_permissions/migration.sql +93 -0
- package/prisma/schema.prisma +193 -35
- package/src/middleware/security.ts +1 -1
- 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 +327 -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 +2 -40
- 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 +6 -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 +127 -118
- package/src/routers/permissions.ts +71 -0
- package/src/routers/school.ts +83 -0
- package/src/routers/tag.ts +78 -0
- package/src/services/class.ts +109 -4
- package/src/services/department.ts +478 -0
- package/src/services/file.ts +172 -43
- package/src/services/folder.ts +399 -237
- package/src/services/labChat.ts +2 -1
- package/src/services/message.ts +5 -21
- package/src/services/newtonChat.ts +2 -1
- package/src/services/permissions.ts +778 -0
- package/src/services/school.ts +369 -57
- package/src/services/tag.ts +229 -0
- package/src/utils/aiUser.ts +0 -1
- package/tests/unit/services/permissions.test.ts +463 -0
package/src/models/comment.ts
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Department model — department CRUD, members, and template artefacts.
|
|
3
|
+
*
|
|
4
|
+
* Departments scope rubric/boundary templates and tags inside an institution.
|
|
5
|
+
* They are an admin-managed taxonomy; teachers/students can be members of
|
|
6
|
+
* many departments.
|
|
7
|
+
*/
|
|
8
|
+
import { prisma } from "../lib/prisma.js";
|
|
9
|
+
|
|
10
|
+
const departmentSelect = {
|
|
11
|
+
id: true,
|
|
12
|
+
schoolId: true,
|
|
13
|
+
name: true,
|
|
14
|
+
description: true,
|
|
15
|
+
color: true,
|
|
16
|
+
createdAt: true,
|
|
17
|
+
updatedAt: true,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
const memberSelect = {
|
|
21
|
+
id: true,
|
|
22
|
+
username: true,
|
|
23
|
+
email: true,
|
|
24
|
+
profile: {
|
|
25
|
+
select: {
|
|
26
|
+
displayName: true,
|
|
27
|
+
profilePicture: true,
|
|
28
|
+
profilePictureThumbnail: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export function listDepartmentsForSchool(schoolId: string) {
|
|
34
|
+
return prisma.department.findMany({
|
|
35
|
+
where: { schoolId },
|
|
36
|
+
orderBy: { name: "asc" },
|
|
37
|
+
select: {
|
|
38
|
+
...departmentSelect,
|
|
39
|
+
_count: {
|
|
40
|
+
select: {
|
|
41
|
+
members: true,
|
|
42
|
+
classes: true,
|
|
43
|
+
tags: true,
|
|
44
|
+
rubricTemplates: true,
|
|
45
|
+
boundaryTemplates: true,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function listDepartmentsForUser(userId: string, schoolId: string) {
|
|
53
|
+
return prisma.department.findMany({
|
|
54
|
+
where: {
|
|
55
|
+
schoolId,
|
|
56
|
+
members: { some: { id: userId } },
|
|
57
|
+
},
|
|
58
|
+
orderBy: { name: "asc" },
|
|
59
|
+
select: departmentSelect,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getDepartment(departmentId: string) {
|
|
64
|
+
return prisma.department.findUnique({
|
|
65
|
+
where: { id: departmentId },
|
|
66
|
+
select: {
|
|
67
|
+
...departmentSelect,
|
|
68
|
+
members: { select: memberSelect },
|
|
69
|
+
tags: {
|
|
70
|
+
select: { id: true, name: true, color: true },
|
|
71
|
+
},
|
|
72
|
+
rubricTemplates: {
|
|
73
|
+
select: { id: true, name: true, structured: true },
|
|
74
|
+
},
|
|
75
|
+
boundaryTemplates: {
|
|
76
|
+
select: { id: true, name: true, structured: true },
|
|
77
|
+
},
|
|
78
|
+
classes: {
|
|
79
|
+
select: {
|
|
80
|
+
id: true,
|
|
81
|
+
name: true,
|
|
82
|
+
subject: true,
|
|
83
|
+
section: true,
|
|
84
|
+
color: true,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createDepartment(data: {
|
|
92
|
+
schoolId: string;
|
|
93
|
+
name: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
color?: string;
|
|
96
|
+
}) {
|
|
97
|
+
return prisma.department.create({
|
|
98
|
+
data: {
|
|
99
|
+
schoolId: data.schoolId,
|
|
100
|
+
name: data.name,
|
|
101
|
+
description: data.description,
|
|
102
|
+
color: data.color,
|
|
103
|
+
},
|
|
104
|
+
select: departmentSelect,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function updateDepartment(
|
|
109
|
+
departmentId: string,
|
|
110
|
+
data: { name?: string; description?: string | null; color?: string }
|
|
111
|
+
) {
|
|
112
|
+
return prisma.department.update({
|
|
113
|
+
where: { id: departmentId },
|
|
114
|
+
data,
|
|
115
|
+
select: departmentSelect,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function deleteDepartment(departmentId: string) {
|
|
120
|
+
return prisma.department.delete({
|
|
121
|
+
where: { id: departmentId },
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function addDepartmentMembers(
|
|
126
|
+
departmentId: string,
|
|
127
|
+
userIds: string[]
|
|
128
|
+
) {
|
|
129
|
+
return prisma.department.update({
|
|
130
|
+
where: { id: departmentId },
|
|
131
|
+
data: {
|
|
132
|
+
members: { connect: userIds.map((id) => ({ id })) },
|
|
133
|
+
},
|
|
134
|
+
select: { id: true },
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function removeDepartmentMember(
|
|
139
|
+
departmentId: string,
|
|
140
|
+
userId: string
|
|
141
|
+
) {
|
|
142
|
+
return prisma.department.update({
|
|
143
|
+
where: { id: departmentId },
|
|
144
|
+
data: {
|
|
145
|
+
members: { disconnect: { id: userId } },
|
|
146
|
+
},
|
|
147
|
+
select: { id: true },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function findDepartmentMembership(
|
|
152
|
+
departmentId: string,
|
|
153
|
+
userId: string
|
|
154
|
+
) {
|
|
155
|
+
return prisma.department.findFirst({
|
|
156
|
+
where: { id: departmentId, members: { some: { id: userId } } },
|
|
157
|
+
select: { id: true },
|
|
158
|
+
});
|
|
159
|
+
}
|
package/src/models/event.ts
CHANGED
|
@@ -11,7 +11,6 @@ const eventGetInclude = {
|
|
|
11
11
|
school: {
|
|
12
12
|
include: {
|
|
13
13
|
users: {
|
|
14
|
-
where: { role: "ADMIN" as const },
|
|
15
14
|
select: { id: true },
|
|
16
15
|
},
|
|
17
16
|
memberships: {
|
|
@@ -134,7 +133,6 @@ export function findEventWithClass(eventId: string) {
|
|
|
134
133
|
school: {
|
|
135
134
|
include: {
|
|
136
135
|
users: {
|
|
137
|
-
where: { role: "ADMIN" as const },
|
|
138
136
|
select: { id: true },
|
|
139
137
|
},
|
|
140
138
|
memberships: {
|
|
@@ -163,7 +161,6 @@ export function findAssignmentWithClass(assignmentId: string) {
|
|
|
163
161
|
school: {
|
|
164
162
|
include: {
|
|
165
163
|
users: {
|
|
166
|
-
where: { role: "ADMIN" as const },
|
|
167
164
|
select: { id: true },
|
|
168
165
|
},
|
|
169
166
|
memberships: {
|
package/src/models/file.ts
CHANGED
|
@@ -8,10 +8,6 @@ const classFileAccessInclude = {
|
|
|
8
8
|
teachers: true,
|
|
9
9
|
school: {
|
|
10
10
|
include: {
|
|
11
|
-
users: {
|
|
12
|
-
where: { role: "ADMIN" as const },
|
|
13
|
-
select: { id: true },
|
|
14
|
-
},
|
|
15
11
|
memberships: {
|
|
16
12
|
where: {
|
|
17
13
|
role: "ADMIN" as const,
|
|
@@ -41,24 +37,7 @@ export function findFileById(fileId: string) {
|
|
|
41
37
|
assignment: {
|
|
42
38
|
include: {
|
|
43
39
|
class: {
|
|
44
|
-
include:
|
|
45
|
-
teachers: true,
|
|
46
|
-
school: {
|
|
47
|
-
include: {
|
|
48
|
-
users: {
|
|
49
|
-
where: { role: "ADMIN" as const },
|
|
50
|
-
select: { id: true },
|
|
51
|
-
},
|
|
52
|
-
memberships: {
|
|
53
|
-
where: {
|
|
54
|
-
role: "ADMIN" as const,
|
|
55
|
-
status: "ACTIVE" as const,
|
|
56
|
-
},
|
|
57
|
-
select: { userId: true },
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
},
|
|
40
|
+
include: classFileAccessInclude,
|
|
62
41
|
},
|
|
63
42
|
},
|
|
64
43
|
},
|
|
@@ -70,24 +49,7 @@ export function findFileById(fileId: string) {
|
|
|
70
49
|
assignment: {
|
|
71
50
|
include: {
|
|
72
51
|
class: {
|
|
73
|
-
include:
|
|
74
|
-
teachers: true,
|
|
75
|
-
school: {
|
|
76
|
-
include: {
|
|
77
|
-
users: {
|
|
78
|
-
where: { role: "ADMIN" as const },
|
|
79
|
-
select: { id: true },
|
|
80
|
-
},
|
|
81
|
-
memberships: {
|
|
82
|
-
where: {
|
|
83
|
-
role: "ADMIN" as const,
|
|
84
|
-
status: "ACTIVE" as const,
|
|
85
|
-
},
|
|
86
|
-
select: { userId: true },
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
},
|
|
52
|
+
include: classFileAccessInclude,
|
|
91
53
|
},
|
|
92
54
|
},
|
|
93
55
|
},
|
package/src/models/message.ts
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Message model – messages, attachments, mentions, unread counts.
|
|
3
3
|
*/
|
|
4
|
+
import type { Prisma } from "@prisma/client";
|
|
4
5
|
import { prisma } from "../lib/prisma.js";
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Canonical shape for File rows surfaced as message attachments.
|
|
9
|
+
* Use this everywhere a message (list, send, update, suggestion broadcast)
|
|
10
|
+
* exposes attached files so the wire shape stays in sync.
|
|
11
|
+
*/
|
|
12
|
+
export const messageFileSelect = {
|
|
13
|
+
id: true,
|
|
14
|
+
name: true,
|
|
15
|
+
type: true,
|
|
16
|
+
size: true,
|
|
17
|
+
path: true,
|
|
18
|
+
uploadedAt: true,
|
|
19
|
+
uploadStatus: true,
|
|
20
|
+
thumbnailId: true,
|
|
21
|
+
thumbnail: { select: { id: true, path: true } },
|
|
22
|
+
} as const satisfies Prisma.FileSelect;
|
|
23
|
+
|
|
6
24
|
const messageInclude = {
|
|
7
25
|
attachments: {
|
|
8
|
-
select:
|
|
9
|
-
id: true,
|
|
10
|
-
name: true,
|
|
11
|
-
type: true,
|
|
12
|
-
},
|
|
26
|
+
select: messageFileSelect,
|
|
13
27
|
},
|
|
14
28
|
sender: {
|
|
15
29
|
select: {
|
package/src/models/school.ts
CHANGED
|
@@ -52,8 +52,10 @@ export function listUserSchoolMemberships(userId: string) {
|
|
|
52
52
|
select: {
|
|
53
53
|
id: true,
|
|
54
54
|
name: true,
|
|
55
|
+
description: true,
|
|
55
56
|
subdomain: true,
|
|
56
57
|
logoId: true,
|
|
58
|
+
logo: { select: { id: true, path: true, type: true } },
|
|
57
59
|
createdAt: true,
|
|
58
60
|
updatedAt: true,
|
|
59
61
|
},
|
|
@@ -96,14 +98,38 @@ export function getSchoolById(schoolId: string) {
|
|
|
96
98
|
select: {
|
|
97
99
|
id: true,
|
|
98
100
|
name: true,
|
|
101
|
+
description: true,
|
|
99
102
|
subdomain: true,
|
|
100
103
|
logoId: true,
|
|
104
|
+
logo: { select: { id: true, name: true, type: true, path: true } },
|
|
101
105
|
createdAt: true,
|
|
102
106
|
updatedAt: true,
|
|
103
107
|
},
|
|
104
108
|
});
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
export function updateSchoolRecord(
|
|
112
|
+
schoolId: string,
|
|
113
|
+
data: {
|
|
114
|
+
name?: string;
|
|
115
|
+
description?: string | null;
|
|
116
|
+
subdomain?: string | null;
|
|
117
|
+
logoId?: string | null;
|
|
118
|
+
}
|
|
119
|
+
) {
|
|
120
|
+
return prisma.school.update({
|
|
121
|
+
where: { id: schoolId },
|
|
122
|
+
data,
|
|
123
|
+
select: {
|
|
124
|
+
id: true,
|
|
125
|
+
name: true,
|
|
126
|
+
description: true,
|
|
127
|
+
subdomain: true,
|
|
128
|
+
logoId: true,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
107
133
|
export function getSchoolOverview(schoolId: string) {
|
|
108
134
|
return prisma.school.findUnique({
|
|
109
135
|
where: { id: schoolId },
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tag model — institution-scoped tags applicable to users, classes, rubrics,
|
|
3
|
+
* and grading boundaries. Optionally associated with a department.
|
|
4
|
+
*/
|
|
5
|
+
import { prisma } from "../lib/prisma.js";
|
|
6
|
+
|
|
7
|
+
const tagSelect = {
|
|
8
|
+
id: true,
|
|
9
|
+
schoolId: true,
|
|
10
|
+
departmentId: true,
|
|
11
|
+
name: true,
|
|
12
|
+
color: true,
|
|
13
|
+
createdAt: true,
|
|
14
|
+
updatedAt: true,
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export type TagTarget = "USER" | "CLASS" | "RUBRIC" | "BOUNDARY";
|
|
18
|
+
|
|
19
|
+
export function listTagsForSchool(
|
|
20
|
+
schoolId: string,
|
|
21
|
+
options?: { departmentId?: string | null }
|
|
22
|
+
) {
|
|
23
|
+
return prisma.tag.findMany({
|
|
24
|
+
where: {
|
|
25
|
+
schoolId,
|
|
26
|
+
...(options?.departmentId === null
|
|
27
|
+
? { departmentId: null }
|
|
28
|
+
: options?.departmentId
|
|
29
|
+
? { departmentId: options.departmentId }
|
|
30
|
+
: {}),
|
|
31
|
+
},
|
|
32
|
+
orderBy: [{ name: "asc" }],
|
|
33
|
+
select: tagSelect,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getTag(tagId: string) {
|
|
38
|
+
return prisma.tag.findUnique({
|
|
39
|
+
where: { id: tagId },
|
|
40
|
+
select: tagSelect,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createTag(data: {
|
|
45
|
+
schoolId: string;
|
|
46
|
+
departmentId?: string | null;
|
|
47
|
+
name: string;
|
|
48
|
+
color?: string;
|
|
49
|
+
}) {
|
|
50
|
+
return prisma.tag.create({
|
|
51
|
+
data: {
|
|
52
|
+
schoolId: data.schoolId,
|
|
53
|
+
departmentId: data.departmentId ?? null,
|
|
54
|
+
name: data.name,
|
|
55
|
+
color: data.color ?? "#3B82F6",
|
|
56
|
+
},
|
|
57
|
+
select: tagSelect,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function updateTag(
|
|
62
|
+
tagId: string,
|
|
63
|
+
data: { name?: string; color?: string; departmentId?: string | null }
|
|
64
|
+
) {
|
|
65
|
+
return prisma.tag.update({
|
|
66
|
+
where: { id: tagId },
|
|
67
|
+
data,
|
|
68
|
+
select: tagSelect,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function deleteTag(tagId: string) {
|
|
73
|
+
return prisma.tag.delete({ where: { id: tagId } });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const targetRelation: Record<TagTarget, "users" | "classes" | "rubrics" | "boundaries"> = {
|
|
77
|
+
USER: "users",
|
|
78
|
+
CLASS: "classes",
|
|
79
|
+
RUBRIC: "rubrics",
|
|
80
|
+
BOUNDARY: "boundaries",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export function attachTagToTarget(params: {
|
|
84
|
+
tagId: string;
|
|
85
|
+
target: TagTarget;
|
|
86
|
+
targetId: string;
|
|
87
|
+
}) {
|
|
88
|
+
const relation = targetRelation[params.target];
|
|
89
|
+
return prisma.tag.update({
|
|
90
|
+
where: { id: params.tagId },
|
|
91
|
+
data: {
|
|
92
|
+
[relation]: { connect: { id: params.targetId } },
|
|
93
|
+
},
|
|
94
|
+
select: { id: true },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function detachTagFromTarget(params: {
|
|
99
|
+
tagId: string;
|
|
100
|
+
target: TagTarget;
|
|
101
|
+
targetId: string;
|
|
102
|
+
}) {
|
|
103
|
+
const relation = targetRelation[params.target];
|
|
104
|
+
return prisma.tag.update({
|
|
105
|
+
where: { id: params.tagId },
|
|
106
|
+
data: {
|
|
107
|
+
[relation]: { disconnect: { id: params.targetId } },
|
|
108
|
+
},
|
|
109
|
+
select: { id: true },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function listTagsAttachedTo(target: TagTarget, targetId: string) {
|
|
114
|
+
switch (target) {
|
|
115
|
+
case "USER":
|
|
116
|
+
return prisma.tag.findMany({
|
|
117
|
+
where: { users: { some: { id: targetId } } },
|
|
118
|
+
select: tagSelect,
|
|
119
|
+
orderBy: { name: "asc" },
|
|
120
|
+
});
|
|
121
|
+
case "CLASS":
|
|
122
|
+
return prisma.tag.findMany({
|
|
123
|
+
where: { classes: { some: { id: targetId } } },
|
|
124
|
+
select: tagSelect,
|
|
125
|
+
orderBy: { name: "asc" },
|
|
126
|
+
});
|
|
127
|
+
case "RUBRIC":
|
|
128
|
+
return prisma.tag.findMany({
|
|
129
|
+
where: { rubrics: { some: { id: targetId } } },
|
|
130
|
+
select: tagSelect,
|
|
131
|
+
orderBy: { name: "asc" },
|
|
132
|
+
});
|
|
133
|
+
case "BOUNDARY":
|
|
134
|
+
return prisma.tag.findMany({
|
|
135
|
+
where: { boundaries: { some: { id: targetId } } },
|
|
136
|
+
select: tagSelect,
|
|
137
|
+
orderBy: { name: "asc" },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/routers/_app.ts
CHANGED
|
@@ -23,6 +23,9 @@ import { commentRouter } from "./comment.js";
|
|
|
23
23
|
import { studentProgressRouter } from "./studentProgress.js";
|
|
24
24
|
import { searchRouter } from "./search.js";
|
|
25
25
|
import { schoolRouter } from "./school.js";
|
|
26
|
+
import { departmentRouter } from "./department.js";
|
|
27
|
+
import { tagRouter } from "./tag.js";
|
|
28
|
+
import { permissionsRouter } from "./permissions.js";
|
|
26
29
|
|
|
27
30
|
export const appRouter = createTRPCRouter({
|
|
28
31
|
class: classRouter,
|
|
@@ -47,6 +50,9 @@ export const appRouter = createTRPCRouter({
|
|
|
47
50
|
studentProgress: studentProgressRouter,
|
|
48
51
|
search: searchRouter,
|
|
49
52
|
school: schoolRouter,
|
|
53
|
+
department: departmentRouter,
|
|
54
|
+
tag: tagRouter,
|
|
55
|
+
permissions: permissionsRouter,
|
|
50
56
|
});
|
|
51
57
|
|
|
52
58
|
// Export type router type definition
|
package/src/routers/class.ts
CHANGED
|
@@ -58,6 +58,8 @@ export const classRouter = createTRPCRouter({
|
|
|
58
58
|
name: z.string().optional(),
|
|
59
59
|
section: z.string().optional(),
|
|
60
60
|
subject: z.string().optional(),
|
|
61
|
+
color: z.string().optional(),
|
|
62
|
+
departmentId: z.string().nullable().optional(),
|
|
61
63
|
})
|
|
62
64
|
)
|
|
63
65
|
.mutation(({ input }) => {
|
|
@@ -75,6 +77,7 @@ export const classRouter = createTRPCRouter({
|
|
|
75
77
|
subject: z.string(),
|
|
76
78
|
color: z.string().optional(),
|
|
77
79
|
schoolId: z.string().optional(),
|
|
80
|
+
departmentId: z.string().optional(),
|
|
78
81
|
})
|
|
79
82
|
)
|
|
80
83
|
.mutation(({ ctx, input }) =>
|