@studious-lms/server 1.4.4 → 1.4.5
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/index.js +3 -17
- package/dist/index.js.map +1 -1
- package/dist/models/conversation.d.ts +22 -0
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/conversation.js +13 -2
- package/dist/models/conversation.js.map +1 -1
- package/dist/models/labChat.d.ts +3 -0
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/message.d.ts +2 -0
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/newtonChat.d.ts +1 -0
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/routers/_app.d.ts +110 -0
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +4 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/conversation.d.ts +14 -0
- 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/labChat.d.ts +2 -0
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/search.d.ts +41 -0
- package/dist/routers/search.d.ts.map +1 -0
- package/dist/routers/search.js +15 -0
- package/dist/routers/search.js.map +1 -0
- package/dist/services/conversation.d.ts +8 -0
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/conversation.js +24 -6
- package/dist/services/conversation.js.map +1 -1
- package/dist/services/labChat.d.ts +2 -0
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +9 -2
- package/dist/services/message.js.map +1 -1
- package/dist/services/search.d.ts +21 -0
- package/dist/services/search.d.ts.map +1 -0
- package/dist/services/search.js +352 -0
- package/dist/services/search.js.map +1 -0
- package/package.json +1 -1
- package/prisma/migrations/20260417083000_add_conversation_member_hidden_at/migration.sql +2 -0
- package/prisma/schema.prisma +1 -0
- package/src/index.ts +1 -17
- package/src/models/conversation.ts +12 -0
- package/src/routers/_app.ts +2 -0
- package/src/routers/conversation.ts +7 -0
- package/src/routers/search.ts +14 -0
- package/src/services/conversation.ts +27 -3
- package/src/services/message.ts +8 -0
- package/src/services/search.ts +397 -0
- package/tests/routers/conversation.test.ts +114 -2
- package/tests/routers/search.test.ts +109 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
|
|
2
|
+
!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]="9858c6c3-883b-538a-9404-7b225e24d88e")}catch(e){}}();
|
|
3
|
+
import { prisma } from "../lib/prisma.js";
|
|
4
|
+
const SEARCH_LIMIT = 12;
|
|
5
|
+
const contains = (query) => ({
|
|
6
|
+
contains: query,
|
|
7
|
+
mode: "insensitive",
|
|
8
|
+
});
|
|
9
|
+
const includesToken = (query, ...tokens) => tokens.some((token) => token.includes(query.toLowerCase()));
|
|
10
|
+
const excerpt = (value, query) => {
|
|
11
|
+
if (!value)
|
|
12
|
+
return null;
|
|
13
|
+
const trimmed = value.replace(/\s+/g, " ").trim();
|
|
14
|
+
if (!trimmed)
|
|
15
|
+
return null;
|
|
16
|
+
if (!query)
|
|
17
|
+
return trimmed.slice(0, 140);
|
|
18
|
+
const index = trimmed.toLowerCase().indexOf(query.toLowerCase());
|
|
19
|
+
if (index < 0)
|
|
20
|
+
return trimmed.slice(0, 140);
|
|
21
|
+
const start = Math.max(0, index - 48);
|
|
22
|
+
const end = Math.min(trimmed.length, index + query.length + 92);
|
|
23
|
+
return `${start > 0 ? "..." : ""}${trimmed.slice(start, end)}${end < trimmed.length ? "..." : ""}`;
|
|
24
|
+
};
|
|
25
|
+
const trimResults = (results, limit) => results
|
|
26
|
+
.sort((a, b) => {
|
|
27
|
+
const priority = {
|
|
28
|
+
class: 0,
|
|
29
|
+
assignment: 1,
|
|
30
|
+
event: 2,
|
|
31
|
+
syllabus: 3,
|
|
32
|
+
worksheet: 4,
|
|
33
|
+
file: 5,
|
|
34
|
+
announcement: 6,
|
|
35
|
+
section: 7,
|
|
36
|
+
};
|
|
37
|
+
const priorityDiff = priority[a.type] - priority[b.type];
|
|
38
|
+
if (priorityDiff !== 0)
|
|
39
|
+
return priorityDiff;
|
|
40
|
+
return (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0);
|
|
41
|
+
})
|
|
42
|
+
.slice(0, limit);
|
|
43
|
+
export async function searchGlobalClasses(userId, input) {
|
|
44
|
+
const rawQuery = input.query?.trim() ?? "";
|
|
45
|
+
const query = rawQuery.slice(0, 80);
|
|
46
|
+
const limit = Math.min(Math.max(input.limit ?? SEARCH_LIMIT, 1), 30);
|
|
47
|
+
const memberships = await prisma.class.findMany({
|
|
48
|
+
where: {
|
|
49
|
+
OR: [
|
|
50
|
+
{ teachers: { some: { id: userId } } },
|
|
51
|
+
{ students: { some: { id: userId } } },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
select: {
|
|
55
|
+
id: true,
|
|
56
|
+
name: true,
|
|
57
|
+
section: true,
|
|
58
|
+
subject: true,
|
|
59
|
+
syllabus: true,
|
|
60
|
+
teachers: { where: { id: userId }, select: { id: true } },
|
|
61
|
+
students: { where: { id: userId }, select: { id: true } },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const allClassIds = memberships.map((cls) => cls.id);
|
|
65
|
+
if (allClassIds.length === 0) {
|
|
66
|
+
return { query, results: [] };
|
|
67
|
+
}
|
|
68
|
+
const teacherClassIds = memberships
|
|
69
|
+
.filter((cls) => cls.teachers.length > 0)
|
|
70
|
+
.map((cls) => cls.id);
|
|
71
|
+
const studentClassIds = memberships
|
|
72
|
+
.filter((cls) => cls.students.length > 0 && cls.teachers.length === 0)
|
|
73
|
+
.map((cls) => cls.id);
|
|
74
|
+
const classById = new Map(memberships.map((cls) => [cls.id, cls]));
|
|
75
|
+
const queryFilters = query
|
|
76
|
+
? {
|
|
77
|
+
class: [
|
|
78
|
+
{ name: contains(query) },
|
|
79
|
+
{ section: contains(query) },
|
|
80
|
+
{ subject: contains(query) },
|
|
81
|
+
],
|
|
82
|
+
assignment: [
|
|
83
|
+
{ title: contains(query) },
|
|
84
|
+
{ instructions: contains(query) },
|
|
85
|
+
],
|
|
86
|
+
event: [
|
|
87
|
+
{ name: contains(query) },
|
|
88
|
+
{ location: contains(query) },
|
|
89
|
+
{ remarks: contains(query) },
|
|
90
|
+
],
|
|
91
|
+
worksheet: [{ name: contains(query) }],
|
|
92
|
+
file: [{ name: contains(query) }, { type: contains(query) }],
|
|
93
|
+
announcement: [{ remarks: contains(query) }],
|
|
94
|
+
section: [{ name: contains(query) }],
|
|
95
|
+
}
|
|
96
|
+
: null;
|
|
97
|
+
const assignmentAccess = [
|
|
98
|
+
...(teacherClassIds.length > 0
|
|
99
|
+
? [{ classId: { in: teacherClassIds } }]
|
|
100
|
+
: []),
|
|
101
|
+
...(studentClassIds.length > 0
|
|
102
|
+
? [
|
|
103
|
+
{
|
|
104
|
+
classId: { in: studentClassIds },
|
|
105
|
+
OR: [
|
|
106
|
+
{ assignedTo: { some: { id: userId } } },
|
|
107
|
+
{ assignedTo: { none: {} } },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
: []),
|
|
112
|
+
];
|
|
113
|
+
const [classes, assignments, events, worksheets, files, announcements, sections,] = await Promise.all([
|
|
114
|
+
prisma.class.findMany({
|
|
115
|
+
where: {
|
|
116
|
+
id: { in: allClassIds },
|
|
117
|
+
...(queryFilters ? { OR: queryFilters.class } : {}),
|
|
118
|
+
},
|
|
119
|
+
select: { id: true, name: true, section: true, subject: true },
|
|
120
|
+
take: limit,
|
|
121
|
+
}),
|
|
122
|
+
prisma.assignment.findMany({
|
|
123
|
+
where: {
|
|
124
|
+
template: false,
|
|
125
|
+
AND: [
|
|
126
|
+
...(queryFilters ? [{ OR: queryFilters.assignment }] : []),
|
|
127
|
+
...(assignmentAccess.length > 0 ? [{ OR: assignmentAccess }] : []),
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
select: {
|
|
131
|
+
id: true,
|
|
132
|
+
title: true,
|
|
133
|
+
instructions: true,
|
|
134
|
+
dueDate: true,
|
|
135
|
+
modifiedAt: true,
|
|
136
|
+
classId: true,
|
|
137
|
+
class: { select: { name: true } },
|
|
138
|
+
},
|
|
139
|
+
orderBy: { dueDate: "asc" },
|
|
140
|
+
take: limit,
|
|
141
|
+
}),
|
|
142
|
+
prisma.event.findMany({
|
|
143
|
+
where: {
|
|
144
|
+
classId: { in: allClassIds },
|
|
145
|
+
...(queryFilters ? { OR: queryFilters.event } : {}),
|
|
146
|
+
},
|
|
147
|
+
select: {
|
|
148
|
+
id: true,
|
|
149
|
+
name: true,
|
|
150
|
+
location: true,
|
|
151
|
+
remarks: true,
|
|
152
|
+
startTime: true,
|
|
153
|
+
classId: true,
|
|
154
|
+
class: { select: { name: true } },
|
|
155
|
+
},
|
|
156
|
+
orderBy: { startTime: "asc" },
|
|
157
|
+
take: limit,
|
|
158
|
+
}),
|
|
159
|
+
prisma.worksheet.findMany({
|
|
160
|
+
where: {
|
|
161
|
+
classId: { in: allClassIds },
|
|
162
|
+
...(queryFilters && !includesToken(query, "worksheet", "worksheets")
|
|
163
|
+
? { OR: queryFilters.worksheet }
|
|
164
|
+
: {}),
|
|
165
|
+
},
|
|
166
|
+
select: {
|
|
167
|
+
id: true,
|
|
168
|
+
name: true,
|
|
169
|
+
updatedAt: true,
|
|
170
|
+
classId: true,
|
|
171
|
+
class: { select: { name: true } },
|
|
172
|
+
},
|
|
173
|
+
orderBy: { updatedAt: "desc" },
|
|
174
|
+
take: limit,
|
|
175
|
+
}),
|
|
176
|
+
prisma.file.findMany({
|
|
177
|
+
where: {
|
|
178
|
+
...(queryFilters ? { OR: queryFilters.file } : {}),
|
|
179
|
+
AND: [
|
|
180
|
+
{
|
|
181
|
+
OR: [
|
|
182
|
+
{ folder: { classId: { in: allClassIds } } },
|
|
183
|
+
{ classDraftId: { in: teacherClassIds } },
|
|
184
|
+
{
|
|
185
|
+
assignment: {
|
|
186
|
+
OR: assignmentAccess,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{ announcement: { classId: { in: allClassIds } } },
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
select: {
|
|
195
|
+
id: true,
|
|
196
|
+
name: true,
|
|
197
|
+
type: true,
|
|
198
|
+
uploadedAt: true,
|
|
199
|
+
folder: { select: { classId: true, class: { select: { name: true } } } },
|
|
200
|
+
classDraft: { select: { id: true, name: true } },
|
|
201
|
+
assignment: { select: { classId: true, class: { select: { name: true } } } },
|
|
202
|
+
announcement: { select: { classId: true, class: { select: { name: true } } } },
|
|
203
|
+
},
|
|
204
|
+
orderBy: { uploadedAt: "desc" },
|
|
205
|
+
take: limit,
|
|
206
|
+
}),
|
|
207
|
+
prisma.announcement.findMany({
|
|
208
|
+
where: {
|
|
209
|
+
classId: { in: allClassIds },
|
|
210
|
+
...(queryFilters ? { OR: queryFilters.announcement } : {}),
|
|
211
|
+
},
|
|
212
|
+
select: {
|
|
213
|
+
id: true,
|
|
214
|
+
remarks: true,
|
|
215
|
+
createdAt: true,
|
|
216
|
+
classId: true,
|
|
217
|
+
class: { select: { name: true } },
|
|
218
|
+
},
|
|
219
|
+
orderBy: { createdAt: "desc" },
|
|
220
|
+
take: limit,
|
|
221
|
+
}),
|
|
222
|
+
prisma.section.findMany({
|
|
223
|
+
where: {
|
|
224
|
+
classId: { in: allClassIds },
|
|
225
|
+
...(queryFilters && !includesToken(query, "section", "sections", "assignment", "assignments")
|
|
226
|
+
? { OR: queryFilters.section }
|
|
227
|
+
: {}),
|
|
228
|
+
},
|
|
229
|
+
select: {
|
|
230
|
+
id: true,
|
|
231
|
+
name: true,
|
|
232
|
+
classId: true,
|
|
233
|
+
class: { select: { name: true } },
|
|
234
|
+
},
|
|
235
|
+
take: limit,
|
|
236
|
+
}),
|
|
237
|
+
]);
|
|
238
|
+
const results = [
|
|
239
|
+
...classes.map((cls) => ({
|
|
240
|
+
id: cls.id,
|
|
241
|
+
type: "class",
|
|
242
|
+
title: cls.name,
|
|
243
|
+
subtitle: [cls.subject, cls.section].filter(Boolean).join(" · "),
|
|
244
|
+
classId: cls.id,
|
|
245
|
+
className: cls.name,
|
|
246
|
+
href: `/class/${cls.id}`,
|
|
247
|
+
})),
|
|
248
|
+
...assignments.map((assignment) => ({
|
|
249
|
+
id: assignment.id,
|
|
250
|
+
type: "assignment",
|
|
251
|
+
title: assignment.title,
|
|
252
|
+
subtitle: `${assignment.class.name} · Assignment`,
|
|
253
|
+
classId: assignment.classId,
|
|
254
|
+
className: assignment.class.name,
|
|
255
|
+
href: `/class/${assignment.classId}/assignment/${assignment.id}`,
|
|
256
|
+
matchedText: excerpt(assignment.instructions, query),
|
|
257
|
+
date: assignment.dueDate ?? assignment.modifiedAt,
|
|
258
|
+
})),
|
|
259
|
+
...events.map((event) => ({
|
|
260
|
+
id: event.id,
|
|
261
|
+
type: "event",
|
|
262
|
+
title: event.name || "Untitled event",
|
|
263
|
+
subtitle: `${event.class?.name ?? "Class"} · Event`,
|
|
264
|
+
classId: event.classId,
|
|
265
|
+
className: event.class?.name ?? "Class",
|
|
266
|
+
href: `/class/${event.classId}/attendance/${event.id}`,
|
|
267
|
+
matchedText: excerpt(event.location || event.remarks, query),
|
|
268
|
+
date: event.startTime,
|
|
269
|
+
})),
|
|
270
|
+
...memberships
|
|
271
|
+
.filter((cls) => {
|
|
272
|
+
if (!cls.syllabus)
|
|
273
|
+
return false;
|
|
274
|
+
if (!query)
|
|
275
|
+
return true;
|
|
276
|
+
return (includesToken(query, "syllabus") ||
|
|
277
|
+
cls.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
278
|
+
cls.subject.toLowerCase().includes(query.toLowerCase()) ||
|
|
279
|
+
cls.syllabus.toLowerCase().includes(query.toLowerCase()));
|
|
280
|
+
})
|
|
281
|
+
.slice(0, limit)
|
|
282
|
+
.map((cls) => ({
|
|
283
|
+
id: `${cls.id}:syllabus`,
|
|
284
|
+
type: "syllabus",
|
|
285
|
+
title: `${cls.name} syllabus`,
|
|
286
|
+
subtitle: `${cls.subject} · Syllabus`,
|
|
287
|
+
classId: cls.id,
|
|
288
|
+
className: cls.name,
|
|
289
|
+
href: `/class/${cls.id}/syllabus`,
|
|
290
|
+
matchedText: excerpt(cls.syllabus, query),
|
|
291
|
+
})),
|
|
292
|
+
...worksheets.map((worksheet) => ({
|
|
293
|
+
id: worksheet.id,
|
|
294
|
+
type: "worksheet",
|
|
295
|
+
title: worksheet.name,
|
|
296
|
+
subtitle: `${worksheet.class?.name ?? "Class"} · Worksheet`,
|
|
297
|
+
classId: worksheet.classId,
|
|
298
|
+
className: worksheet.class?.name ?? "Class",
|
|
299
|
+
href: `/class/${worksheet.classId}/worksheets`,
|
|
300
|
+
date: worksheet.updatedAt,
|
|
301
|
+
})),
|
|
302
|
+
...files.flatMap((file) => {
|
|
303
|
+
const classId = file.folder?.classId ??
|
|
304
|
+
file.classDraft?.id ??
|
|
305
|
+
file.assignment?.classId ??
|
|
306
|
+
file.announcement?.classId;
|
|
307
|
+
const className = file.folder?.class?.name ??
|
|
308
|
+
file.classDraft?.name ??
|
|
309
|
+
file.assignment?.class?.name ??
|
|
310
|
+
file.announcement?.class?.name ??
|
|
311
|
+
(classId ? classById.get(classId)?.name : undefined);
|
|
312
|
+
if (!classId || !className)
|
|
313
|
+
return [];
|
|
314
|
+
return [
|
|
315
|
+
{
|
|
316
|
+
id: file.id,
|
|
317
|
+
type: "file",
|
|
318
|
+
title: file.name,
|
|
319
|
+
subtitle: `${className} · File`,
|
|
320
|
+
classId,
|
|
321
|
+
className,
|
|
322
|
+
href: `/class/${classId}/files`,
|
|
323
|
+
matchedText: file.type,
|
|
324
|
+
date: file.uploadedAt,
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
}),
|
|
328
|
+
...announcements.map((announcement) => ({
|
|
329
|
+
id: announcement.id,
|
|
330
|
+
type: "announcement",
|
|
331
|
+
title: excerpt(announcement.remarks, query) ?? "Announcement",
|
|
332
|
+
subtitle: `${announcement.class.name} · Announcement`,
|
|
333
|
+
classId: announcement.classId,
|
|
334
|
+
className: announcement.class.name,
|
|
335
|
+
href: `/class/${announcement.classId}`,
|
|
336
|
+
matchedText: excerpt(announcement.remarks, query),
|
|
337
|
+
date: announcement.createdAt,
|
|
338
|
+
})),
|
|
339
|
+
...sections.map((section) => ({
|
|
340
|
+
id: section.id,
|
|
341
|
+
type: "section",
|
|
342
|
+
title: section.name,
|
|
343
|
+
subtitle: `${section.class.name} · Section`,
|
|
344
|
+
classId: section.classId,
|
|
345
|
+
className: section.class.name,
|
|
346
|
+
href: `/class/${section.classId}/assignments`,
|
|
347
|
+
})),
|
|
348
|
+
];
|
|
349
|
+
return { query, results: trimResults(results, limit) };
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=search.js.map
|
|
352
|
+
//# debugId=9858c6c3-883b-538a-9404-7b225e24d88e
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sources":["services/search.ts"],"sourceRoot":"/","sourcesContent":["import { prisma } from \"../lib/prisma.js\";\n\ntype SearchScope =\n | \"class\"\n | \"assignment\"\n | \"event\"\n | \"syllabus\"\n | \"worksheet\"\n | \"file\"\n | \"announcement\"\n | \"section\";\n\ntype SearchResult = {\n id: string;\n type: SearchScope;\n title: string;\n subtitle: string;\n classId: string;\n className: string;\n href: string;\n matchedText?: string | null;\n date?: Date | null;\n};\n\nconst SEARCH_LIMIT = 12;\n\nconst contains = (query: string) => ({\n contains: query,\n mode: \"insensitive\" as const,\n});\n\nconst includesToken = (query: string, ...tokens: string[]) =>\n tokens.some((token) => token.includes(query.toLowerCase()));\n\nconst excerpt = (value: string | null | undefined, query: string) => {\n if (!value) return null;\n const trimmed = value.replace(/\\s+/g, \" \").trim();\n if (!trimmed) return null;\n if (!query) return trimmed.slice(0, 140);\n\n const index = trimmed.toLowerCase().indexOf(query.toLowerCase());\n if (index < 0) return trimmed.slice(0, 140);\n\n const start = Math.max(0, index - 48);\n const end = Math.min(trimmed.length, index + query.length + 92);\n return `${start > 0 ? \"...\" : \"\"}${trimmed.slice(start, end)}${end < trimmed.length ? \"...\" : \"\"}`;\n};\n\nconst trimResults = (results: SearchResult[], limit: number) =>\n results\n .sort((a, b) => {\n const priority: Record<SearchScope, number> = {\n class: 0,\n assignment: 1,\n event: 2,\n syllabus: 3,\n worksheet: 4,\n file: 5,\n announcement: 6,\n section: 7,\n };\n const priorityDiff = priority[a.type] - priority[b.type];\n if (priorityDiff !== 0) return priorityDiff;\n return (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0);\n })\n .slice(0, limit);\n\nexport async function searchGlobalClasses(\n userId: string,\n input: { query?: string; limit?: number }\n) {\n const rawQuery = input.query?.trim() ?? \"\";\n const query = rawQuery.slice(0, 80);\n const limit = Math.min(Math.max(input.limit ?? SEARCH_LIMIT, 1), 30);\n\n const memberships = 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 section: true,\n subject: true,\n syllabus: true,\n teachers: { where: { id: userId }, select: { id: true } },\n students: { where: { id: userId }, select: { id: true } },\n },\n });\n\n const allClassIds = memberships.map((cls) => cls.id);\n if (allClassIds.length === 0) {\n return { query, results: [] as SearchResult[] };\n }\n\n const teacherClassIds = memberships\n .filter((cls) => cls.teachers.length > 0)\n .map((cls) => cls.id);\n const studentClassIds = memberships\n .filter((cls) => cls.students.length > 0 && cls.teachers.length === 0)\n .map((cls) => cls.id);\n const classById = new Map(memberships.map((cls) => [cls.id, cls]));\n\n const queryFilters = query\n ? {\n class: [\n { name: contains(query) },\n { section: contains(query) },\n { subject: contains(query) },\n ],\n assignment: [\n { title: contains(query) },\n { instructions: contains(query) },\n ],\n event: [\n { name: contains(query) },\n { location: contains(query) },\n { remarks: contains(query) },\n ],\n worksheet: [{ name: contains(query) }],\n file: [{ name: contains(query) }, { type: contains(query) }],\n announcement: [{ remarks: contains(query) }],\n section: [{ name: contains(query) }],\n }\n : null;\n\n const assignmentAccess = [\n ...(teacherClassIds.length > 0\n ? [{ classId: { in: teacherClassIds } }]\n : []),\n ...(studentClassIds.length > 0\n ? [\n {\n classId: { in: studentClassIds },\n OR: [\n { assignedTo: { some: { id: userId } } },\n { assignedTo: { none: {} } },\n ],\n },\n ]\n : []),\n ];\n\n const [\n classes,\n assignments,\n events,\n worksheets,\n files,\n announcements,\n sections,\n ] = await Promise.all([\n prisma.class.findMany({\n where: {\n id: { in: allClassIds },\n ...(queryFilters ? { OR: queryFilters.class } : {}),\n },\n select: { id: true, name: true, section: true, subject: true },\n take: limit,\n }),\n prisma.assignment.findMany({\n where: {\n template: false,\n AND: [\n ...(queryFilters ? [{ OR: queryFilters.assignment }] : []),\n ...(assignmentAccess.length > 0 ? [{ OR: assignmentAccess }] : []),\n ],\n },\n select: {\n id: true,\n title: true,\n instructions: true,\n dueDate: true,\n modifiedAt: true,\n classId: true,\n class: { select: { name: true } },\n },\n orderBy: { dueDate: \"asc\" },\n take: limit,\n }),\n prisma.event.findMany({\n where: {\n classId: { in: allClassIds },\n ...(queryFilters ? { OR: queryFilters.event } : {}),\n },\n select: {\n id: true,\n name: true,\n location: true,\n remarks: true,\n startTime: true,\n classId: true,\n class: { select: { name: true } },\n },\n orderBy: { startTime: \"asc\" },\n take: limit,\n }),\n prisma.worksheet.findMany({\n where: {\n classId: { in: allClassIds },\n ...(queryFilters && !includesToken(query, \"worksheet\", \"worksheets\")\n ? { OR: queryFilters.worksheet }\n : {}),\n },\n select: {\n id: true,\n name: true,\n updatedAt: true,\n classId: true,\n class: { select: { name: true } },\n },\n orderBy: { updatedAt: \"desc\" },\n take: limit,\n }),\n prisma.file.findMany({\n where: {\n ...(queryFilters ? { OR: queryFilters.file } : {}),\n AND: [\n {\n OR: [\n { folder: { classId: { in: allClassIds } } },\n { classDraftId: { in: teacherClassIds } },\n {\n assignment: {\n OR: assignmentAccess,\n },\n },\n { announcement: { classId: { in: allClassIds } } },\n ],\n },\n ],\n },\n select: {\n id: true,\n name: true,\n type: true,\n uploadedAt: true,\n folder: { select: { classId: true, class: { select: { name: true } } } },\n classDraft: { select: { id: true, name: true } },\n assignment: { select: { classId: true, class: { select: { name: true } } } },\n announcement: { select: { classId: true, class: { select: { name: true } } } },\n },\n orderBy: { uploadedAt: \"desc\" },\n take: limit,\n }),\n prisma.announcement.findMany({\n where: {\n classId: { in: allClassIds },\n ...(queryFilters ? { OR: queryFilters.announcement } : {}),\n },\n select: {\n id: true,\n remarks: true,\n createdAt: true,\n classId: true,\n class: { select: { name: true } },\n },\n orderBy: { createdAt: \"desc\" },\n take: limit,\n }),\n prisma.section.findMany({\n where: {\n classId: { in: allClassIds },\n ...(queryFilters && !includesToken(query, \"section\", \"sections\", \"assignment\", \"assignments\")\n ? { OR: queryFilters.section }\n : {}),\n },\n select: {\n id: true,\n name: true,\n classId: true,\n class: { select: { name: true } },\n },\n take: limit,\n }),\n ]);\n\n const results: SearchResult[] = [\n ...classes.map((cls) => ({\n id: cls.id,\n type: \"class\" as const,\n title: cls.name,\n subtitle: [cls.subject, cls.section].filter(Boolean).join(\" · \"),\n classId: cls.id,\n className: cls.name,\n href: `/class/${cls.id}`,\n })),\n ...assignments.map((assignment) => ({\n id: assignment.id,\n type: \"assignment\" as const,\n title: assignment.title,\n subtitle: `${assignment.class.name} · Assignment`,\n classId: assignment.classId,\n className: assignment.class.name,\n href: `/class/${assignment.classId}/assignment/${assignment.id}`,\n matchedText: excerpt(assignment.instructions, query),\n date: assignment.dueDate ?? assignment.modifiedAt,\n })),\n ...events.map((event) => ({\n id: event.id,\n type: \"event\" as const,\n title: event.name || \"Untitled event\",\n subtitle: `${event.class?.name ?? \"Class\"} · Event`,\n classId: event.classId!,\n className: event.class?.name ?? \"Class\",\n href: `/class/${event.classId}/attendance/${event.id}`,\n matchedText: excerpt(event.location || event.remarks, query),\n date: event.startTime,\n })),\n ...memberships\n .filter((cls) => {\n if (!cls.syllabus) return false;\n if (!query) return true;\n return (\n includesToken(query, \"syllabus\") ||\n cls.name.toLowerCase().includes(query.toLowerCase()) ||\n cls.subject.toLowerCase().includes(query.toLowerCase()) ||\n cls.syllabus.toLowerCase().includes(query.toLowerCase())\n );\n })\n .slice(0, limit)\n .map((cls) => ({\n id: `${cls.id}:syllabus`,\n type: \"syllabus\" as const,\n title: `${cls.name} syllabus`,\n subtitle: `${cls.subject} · Syllabus`,\n classId: cls.id,\n className: cls.name,\n href: `/class/${cls.id}/syllabus`,\n matchedText: excerpt(cls.syllabus, query),\n })),\n ...worksheets.map((worksheet) => ({\n id: worksheet.id,\n type: \"worksheet\" as const,\n title: worksheet.name,\n subtitle: `${worksheet.class?.name ?? \"Class\"} · Worksheet`,\n classId: worksheet.classId,\n className: worksheet.class?.name ?? \"Class\",\n href: `/class/${worksheet.classId}/worksheets`,\n date: worksheet.updatedAt,\n })),\n ...files.flatMap((file) => {\n const classId =\n file.folder?.classId ??\n file.classDraft?.id ??\n file.assignment?.classId ??\n file.announcement?.classId;\n const className =\n file.folder?.class?.name ??\n file.classDraft?.name ??\n file.assignment?.class?.name ??\n file.announcement?.class?.name ??\n (classId ? classById.get(classId)?.name : undefined);\n\n if (!classId || !className) return [];\n\n return [\n {\n id: file.id,\n type: \"file\" as const,\n title: file.name,\n subtitle: `${className} · File`,\n classId,\n className,\n href: `/class/${classId}/files`,\n matchedText: file.type,\n date: file.uploadedAt,\n },\n ];\n }),\n ...announcements.map((announcement) => ({\n id: announcement.id,\n type: \"announcement\" as const,\n title: excerpt(announcement.remarks, query) ?? \"Announcement\",\n subtitle: `${announcement.class.name} · Announcement`,\n classId: announcement.classId,\n className: announcement.class.name,\n href: `/class/${announcement.classId}`,\n matchedText: excerpt(announcement.remarks, query),\n date: announcement.createdAt,\n })),\n ...sections.map((section) => ({\n id: section.id,\n type: \"section\" as const,\n title: section.name,\n subtitle: `${section.class.name} · Section`,\n classId: section.classId,\n className: section.class.name,\n href: `/class/${section.classId}/assignments`,\n })),\n ];\n\n return { query, results: trimResults(results, limit) };\n}\n"],"names":[],"mappings":"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","debug_id":"9858c6c3-883b-538a-9404-7b225e24d88e"}
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -512,6 +512,7 @@ model ConversationMember {
|
|
|
512
512
|
conversationId String
|
|
513
513
|
role ConversationRole @default(MEMBER)
|
|
514
514
|
joinedAt DateTime @default(now())
|
|
515
|
+
hiddenAt DateTime? // When user last hid this conversation from their list
|
|
515
516
|
lastViewedAt DateTime? // When user last viewed this conversation
|
|
516
517
|
lastViewedMentionAt DateTime? // When user last viewed mentions in this conversation
|
|
517
518
|
|
package/src/index.ts
CHANGED
|
@@ -520,22 +520,6 @@ async function handleFileUpload(req: Request, res: Response) {
|
|
|
520
520
|
}
|
|
521
521
|
|
|
522
522
|
res.header('Access-Control-Allow-Credentials', 'true');
|
|
523
|
-
|
|
524
|
-
const user = await getAuthenticatedRequestUser(req);
|
|
525
|
-
if (!user) {
|
|
526
|
-
return res.status(401).json({ error: 'Authentication required' });
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const uploadSessionId = getUploadSessionId(req);
|
|
530
|
-
if (!uploadSessionId) {
|
|
531
|
-
return res.status(401).json({ error: 'Upload session is required' });
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
await validateDirectUploadIntent({
|
|
535
|
-
filePath,
|
|
536
|
-
uploadSessionId,
|
|
537
|
-
userId: user.id,
|
|
538
|
-
});
|
|
539
523
|
|
|
540
524
|
const contentType = req.headers['content-type'] || 'application/octet-stream';
|
|
541
525
|
const file = bucket.file(filePath);
|
|
@@ -547,7 +531,7 @@ async function handleFileUpload(req: Request, res: Response) {
|
|
|
547
531
|
});
|
|
548
532
|
|
|
549
533
|
writeStream.on('error', (error) => {
|
|
550
|
-
logger.error('Error uploading file:', {error, filePath
|
|
534
|
+
logger.error('Error uploading file:', {error, filePath});
|
|
551
535
|
if (!res.headersSent) {
|
|
552
536
|
res.status(500).json({ error: 'Error uploading file' });
|
|
553
537
|
}
|
|
@@ -190,6 +190,18 @@ export function createConversationMember(data: {
|
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/** Hide a conversation for a single member until new activity occurs. */
|
|
194
|
+
export function hideConversationMember(userId: string, conversationId: string) {
|
|
195
|
+
return prisma.conversationMember.update({
|
|
196
|
+
where: {
|
|
197
|
+
userId_conversationId: { userId, conversationId },
|
|
198
|
+
},
|
|
199
|
+
data: {
|
|
200
|
+
hiddenAt: new Date(),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
193
205
|
/** Remove a member from a conversation. */
|
|
194
206
|
export function deleteConversationMember(userId: string, conversationId: string) {
|
|
195
207
|
return prisma.conversationMember.delete({
|
package/src/routers/_app.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { marketingRouter } from "./marketing.js";
|
|
|
21
21
|
import { worksheetRouter } from "./worksheet.js";
|
|
22
22
|
import { commentRouter } from "./comment.js";
|
|
23
23
|
import { studentProgressRouter } from "./studentProgress.js";
|
|
24
|
+
import { searchRouter } from "./search.js";
|
|
24
25
|
|
|
25
26
|
export const appRouter = createTRPCRouter({
|
|
26
27
|
class: classRouter,
|
|
@@ -43,6 +44,7 @@ export const appRouter = createTRPCRouter({
|
|
|
43
44
|
worksheet: worksheetRouter,
|
|
44
45
|
comment: commentRouter,
|
|
45
46
|
studentProgress: studentProgressRouter,
|
|
47
|
+
search: searchRouter,
|
|
46
48
|
});
|
|
47
49
|
|
|
48
50
|
// Export type router type definition
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createConversationRecord,
|
|
6
6
|
getConversation,
|
|
7
7
|
addMemberToConversation,
|
|
8
|
+
hideConversation,
|
|
8
9
|
removeMemberFromConversation,
|
|
9
10
|
} from "../services/conversation.js";
|
|
10
11
|
|
|
@@ -36,6 +37,12 @@ export const conversationRouter = createTRPCRouter({
|
|
|
36
37
|
getConversation(ctx.user!.id, input.conversationId)
|
|
37
38
|
),
|
|
38
39
|
|
|
40
|
+
hide: protectedProcedure
|
|
41
|
+
.input(z.object({ conversationId: z.string() }))
|
|
42
|
+
.mutation(({ ctx, input }) =>
|
|
43
|
+
hideConversation(ctx.user!.id, input.conversationId)
|
|
44
|
+
),
|
|
45
|
+
|
|
39
46
|
addMember: protectedProcedure
|
|
40
47
|
.input(
|
|
41
48
|
z.object({
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createTRPCRouter, protectedProcedure } from "../trpc.js";
|
|
3
|
+
import { searchGlobalClasses } from "../services/search.js";
|
|
4
|
+
|
|
5
|
+
export const searchRouter = createTRPCRouter({
|
|
6
|
+
global: protectedProcedure
|
|
7
|
+
.input(
|
|
8
|
+
z.object({
|
|
9
|
+
query: z.string().max(80).optional(),
|
|
10
|
+
limit: z.number().int().min(1).max(30).optional(),
|
|
11
|
+
})
|
|
12
|
+
)
|
|
13
|
+
.query(({ ctx, input }) => searchGlobalClasses(ctx.user!.id, input)),
|
|
14
|
+
});
|
|
@@ -13,15 +13,25 @@ import {
|
|
|
13
13
|
createConversation,
|
|
14
14
|
findConversationByIdAndMember,
|
|
15
15
|
createConversationMember,
|
|
16
|
+
hideConversationMember,
|
|
16
17
|
deleteConversationMember,
|
|
17
18
|
} from "../models/conversation.js";
|
|
18
19
|
|
|
19
20
|
/** List user's conversations with unread and mention counts. */
|
|
20
21
|
export async function listConversations(userId: string) {
|
|
21
22
|
const conversations = await findConversationsByUserId(userId);
|
|
23
|
+
const visibleConversations = conversations.filter((conversation) => {
|
|
24
|
+
const userMembership = conversation.members.find((member) => member.userId === userId);
|
|
25
|
+
|
|
26
|
+
if (!userMembership?.hiddenAt) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return conversation.updatedAt > userMembership.hiddenAt;
|
|
31
|
+
});
|
|
22
32
|
|
|
23
33
|
const conversationsWithUnread = await Promise.all(
|
|
24
|
-
|
|
34
|
+
visibleConversations.map(async (conversation) => {
|
|
25
35
|
const userMembership = conversation.members.find(
|
|
26
36
|
(m) => m.userId === userId
|
|
27
37
|
);
|
|
@@ -65,6 +75,20 @@ export async function listConversations(userId: string) {
|
|
|
65
75
|
return conversationsWithUnread;
|
|
66
76
|
}
|
|
67
77
|
|
|
78
|
+
export async function hideConversation(userId: string, conversationId: string) {
|
|
79
|
+
const conversation = await findConversationByIdAndMember(conversationId, userId);
|
|
80
|
+
if (!conversation) {
|
|
81
|
+
throw new TRPCError({
|
|
82
|
+
code: "FORBIDDEN",
|
|
83
|
+
message: "Not a member of this conversation",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await hideConversationMember(userId, conversationId);
|
|
88
|
+
|
|
89
|
+
return { success: true };
|
|
90
|
+
}
|
|
91
|
+
|
|
68
92
|
/** Create a DM or group conversation with the given members. */
|
|
69
93
|
export async function createConversationRecord(
|
|
70
94
|
userId: string,
|
|
@@ -87,11 +111,11 @@ export async function createConversationRecord(
|
|
|
87
111
|
}
|
|
88
112
|
|
|
89
113
|
if (type === "DM") {
|
|
90
|
-
const targetUser = await
|
|
114
|
+
const [targetUser] = await findUsersByIdsOrUsernames([memberIds[0]]);
|
|
91
115
|
if (!targetUser) {
|
|
92
116
|
throw new TRPCError({
|
|
93
117
|
code: "BAD_REQUEST",
|
|
94
|
-
message: `User
|
|
118
|
+
message: `User not found`,
|
|
95
119
|
});
|
|
96
120
|
}
|
|
97
121
|
|
package/src/services/message.ts
CHANGED
|
@@ -259,6 +259,14 @@ export async function sendMessage(
|
|
|
259
259
|
});
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
await tx.conversationMember.updateMany({
|
|
263
|
+
where: {
|
|
264
|
+
conversationId,
|
|
265
|
+
hiddenAt: { not: null },
|
|
266
|
+
},
|
|
267
|
+
data: { hiddenAt: null },
|
|
268
|
+
});
|
|
269
|
+
|
|
262
270
|
await tx.conversation.update({
|
|
263
271
|
where: { id: conversationId },
|
|
264
272
|
data: { updatedAt: new Date() },
|