@studious-lms/server 1.26.0 → 1.28.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/.env.example +3 -1
- package/.github/workflows/npm-publish.yml +1 -0
- package/AGENTS.md +84 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -4
- package/dist/index.js.map +1 -1
- package/dist/instrument.js +9 -4
- package/dist/instrument.js.map +1 -1
- package/dist/lib/fileUpload.d.ts +15 -0
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +43 -2
- package/dist/lib/fileUpload.js.map +1 -1
- package/dist/models/cloudStorage.d.ts +11 -0
- package/dist/models/cloudStorage.d.ts.map +1 -1
- package/dist/models/cloudStorage.js +18 -2
- package/dist/models/cloudStorage.js.map +1 -1
- package/dist/models/conversation.d.ts +1 -0
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +40 -0
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/labChat.js +32 -2
- package/dist/models/labChat.js.map +1 -1
- package/dist/models/message.d.ts +3 -0
- package/dist/models/message.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts +2 -0
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +60 -16
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +170 -2
- package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
- package/dist/pipelines/aiLabChatContract.js +30 -2
- package/dist/pipelines/aiLabChatContract.js.map +1 -1
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
- package/dist/pipelines/aiNewtonChat.js +30 -5
- package/dist/pipelines/aiNewtonChat.js.map +1 -1
- package/dist/pipelines/generateVisualization.d.ts +33 -0
- package/dist/pipelines/generateVisualization.d.ts.map +1 -0
- package/dist/pipelines/generateVisualization.js +79 -0
- package/dist/pipelines/generateVisualization.js.map +1 -0
- package/dist/pipelines/labChatPrompt.d.ts +1 -1
- package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
- package/dist/pipelines/labChatPrompt.js +13 -14
- package/dist/pipelines/labChatPrompt.js.map +1 -1
- package/dist/routers/_app.d.ts +1118 -70
- 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/admin.d.ts +297 -1
- package/dist/routers/admin.d.ts.map +1 -1
- package/dist/routers/admin.js +43 -2
- package/dist/routers/admin.js.map +1 -1
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +16 -3
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/conversation.d.ts +1 -0
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/labChat.d.ts +10 -0
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +6 -3
- package/dist/routers/labChat.js.map +1 -1
- package/dist/routers/message.d.ts +37 -4
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +24 -7
- package/dist/routers/message.js.map +1 -1
- package/dist/routers/module.d.ts +1 -1
- package/dist/routers/parent.d.ts +18 -0
- package/dist/routers/parent.d.ts.map +1 -1
- package/dist/routers/parent.js +10 -3
- package/dist/routers/parent.js.map +1 -1
- package/dist/routers/studentProgress.d.ts +44 -0
- package/dist/routers/studentProgress.d.ts.map +1 -1
- package/dist/routers/studentProgress.js +12 -3
- package/dist/routers/studentProgress.js.map +1 -1
- package/dist/routers/visualization.d.ts +124 -0
- package/dist/routers/visualization.d.ts.map +1 -0
- package/dist/routers/visualization.js +41 -0
- package/dist/routers/visualization.js.map +1 -0
- package/dist/routes/aiStream.js +32 -2
- package/dist/routes/aiStream.js.map +1 -1
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/seedDatabase.js +107 -3
- package/dist/seedDatabase.js.map +1 -1
- package/dist/services/adminClass.d.ts +153 -0
- package/dist/services/adminClass.d.ts.map +1 -0
- package/dist/services/adminClass.js +196 -0
- package/dist/services/adminClass.js.map +1 -0
- package/dist/services/adminInstitution.d.ts +2 -0
- package/dist/services/adminInstitution.d.ts.map +1 -1
- package/dist/services/adminInstitution.js +11 -4
- package/dist/services/adminInstitution.js.map +1 -1
- package/dist/services/adminMetrics.d.ts +8 -0
- package/dist/services/adminMetrics.d.ts.map +1 -1
- package/dist/services/adminMetrics.js +30 -3
- package/dist/services/adminMetrics.js.map +1 -1
- package/dist/services/adminSpam.d.ts +76 -0
- package/dist/services/adminSpam.d.ts.map +1 -0
- package/dist/services/adminSpam.js +163 -0
- package/dist/services/adminSpam.js.map +1 -0
- package/dist/services/adminSql.d.ts +36 -0
- package/dist/services/adminSql.d.ts.map +1 -0
- package/dist/services/adminSql.js +172 -0
- package/dist/services/adminSql.js.map +1 -0
- package/dist/services/adminTelemetry.d.ts +56 -0
- package/dist/services/adminTelemetry.d.ts.map +1 -0
- package/dist/services/adminTelemetry.js +147 -0
- package/dist/services/adminTelemetry.js.map +1 -0
- package/dist/services/adminUser.d.ts +45 -1
- package/dist/services/adminUser.d.ts.map +1 -1
- package/dist/services/adminUser.js +53 -13
- package/dist/services/adminUser.js.map +1 -1
- package/dist/services/aiChatStream.d.ts +15 -0
- package/dist/services/aiChatStream.d.ts.map +1 -1
- package/dist/services/aiChatStream.js +144 -74
- package/dist/services/aiChatStream.js.map +1 -1
- package/dist/services/aiGeneration.d.ts +4 -0
- package/dist/services/aiGeneration.d.ts.map +1 -0
- package/dist/services/aiGeneration.js +88 -0
- package/dist/services/aiGeneration.js.map +1 -0
- package/dist/services/aiProposalTools.d.ts +4 -1
- package/dist/services/aiProposalTools.d.ts.map +1 -1
- package/dist/services/aiProposalTools.js +128 -3
- package/dist/services/aiProposalTools.js.map +1 -1
- package/dist/services/aiProposals.d.ts +70 -0
- package/dist/services/aiProposals.d.ts.map +1 -0
- package/dist/services/aiProposals.js +363 -0
- package/dist/services/aiProposals.js.map +1 -0
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +10 -4
- package/dist/services/auth.js.map +1 -1
- package/dist/services/conversation.d.ts +1 -0
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/fees.d.ts.map +1 -1
- package/dist/services/fees.js +7 -3
- package/dist/services/fees.js.map +1 -1
- package/dist/services/labChat.d.ts +10 -0
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +52 -3
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/message.d.ts +24 -8
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +170 -117
- package/dist/services/message.js.map +1 -1
- package/dist/services/module.d.ts.map +1 -1
- package/dist/services/module.js +23 -4
- package/dist/services/module.js.map +1 -1
- package/dist/services/parentPortal.d.ts +14 -0
- package/dist/services/parentPortal.d.ts.map +1 -1
- package/dist/services/parentPortal.js +72 -2
- package/dist/services/parentPortal.js.map +1 -1
- package/dist/services/studentProgress.d.ts +50 -0
- package/dist/services/studentProgress.d.ts.map +1 -1
- package/dist/services/studentProgress.js +107 -2
- package/dist/services/studentProgress.js.map +1 -1
- package/dist/services/visualization.d.ts +72 -0
- package/dist/services/visualization.d.ts.map +1 -0
- package/dist/services/visualization.js +122 -0
- package/dist/services/visualization.js.map +1 -0
- package/dist/utils/inference.d.ts +10 -0
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +77 -2
- package/dist/utils/inference.js.map +1 -1
- package/dist/utils/sentryTrpc.d.ts +3 -0
- package/dist/utils/sentryTrpc.d.ts.map +1 -0
- package/dist/utils/sentryTrpc.js +26 -0
- package/dist/utils/sentryTrpc.js.map +1 -0
- package/dist/workers/processors/createRunJobWorker.js +17 -16
- package/dist/workers/processors/createRunJobWorker.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260811130000_add_ai_message_feedback/migration.sql +7 -0
- package/prisma/migrations/20261025120000_add_visualizations/migration.sql +27 -0
- package/prisma/schema.prisma +32 -0
- package/src/index.ts +30 -4
- package/src/instrument.ts +8 -2
- package/src/lib/fileUpload.ts +49 -0
- package/src/models/cloudStorage.ts +19 -0
- package/src/models/labChat.ts +34 -0
- package/src/pipelines/aiLabChat.ts +79 -18
- package/src/pipelines/aiLabChatContract.ts +35 -0
- package/src/pipelines/aiNewtonChat.ts +34 -2
- package/src/pipelines/generateVisualization.ts +107 -0
- package/src/pipelines/labChatPrompt.ts +11 -12
- package/src/routers/_app.ts +2 -0
- package/src/routers/admin.ts +72 -0
- package/src/routers/class.ts +14 -1
- package/src/routers/labChat.ts +5 -0
- package/src/routers/message.ts +34 -5
- package/src/routers/parent.ts +11 -0
- package/src/routers/studentProgress.ts +15 -0
- package/src/routers/visualization.ts +60 -0
- package/src/routes/aiStream.ts +32 -0
- package/src/seedDatabase.ts +107 -2
- package/src/services/adminClass.ts +222 -0
- package/src/services/adminInstitution.ts +9 -2
- package/src/services/adminMetrics.ts +35 -0
- package/src/services/adminSpam.ts +224 -0
- package/src/services/adminSql.ts +214 -0
- package/src/services/adminTelemetry.ts +194 -0
- package/src/services/adminUser.ts +66 -11
- package/src/services/aiChatStream.ts +83 -7
- package/src/services/aiGeneration.ts +92 -0
- package/src/services/aiProposalTools.ts +166 -1
- package/src/services/aiProposals.ts +428 -0
- package/src/services/auth.ts +8 -2
- package/src/services/fees.ts +5 -1
- package/src/services/labChat.ts +65 -0
- package/src/services/message.ts +205 -142
- package/src/services/module.ts +29 -2
- package/src/services/parentPortal.ts +85 -0
- package/src/services/studentProgress.ts +135 -0
- package/src/services/visualization.ts +147 -0
- package/src/utils/inference.ts +85 -0
- package/src/utils/sentryTrpc.ts +25 -0
- package/src/workers/processors/createRunJobWorker.ts +17 -17
- package/tests/unit/lib/fileUploadSession.test.ts +67 -0
- package/tests/unit/services/adminClass.test.ts +102 -0
- package/tests/unit/services/adminSpam.test.ts +108 -0
- package/tests/unit/services/adminSql.test.ts +56 -0
- package/tests/unit/services/adminTelemetry.test.ts +47 -0
- package/tests/unit/services/adminUser.test.ts +77 -0
- package/tests/unit/services/visualization.test.ts +130 -0
- package/tests/unit/utils/adminPanelAccess.test.ts +26 -0
- package/tests/unit/utils/sentryTrpc.test.ts +15 -0
package/dist/services/message.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
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]="
|
|
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]="ebbba0b2-a07e-5511-b849-8de0f6dc0ed4")}catch(e){}}();
|
|
3
3
|
import { TRPCError } from "@trpc/server";
|
|
4
|
+
import { JobStatus } from "@prisma/client";
|
|
5
|
+
import { isAIUser } from "../utils/aiUser.js";
|
|
6
|
+
import { enqueueRunJob } from "../workers/queues.js";
|
|
4
7
|
import { prisma } from "../lib/prisma.js";
|
|
5
8
|
import { chatChannel, pusher } from "../lib/pusher.js";
|
|
6
9
|
import { confirmDirectUpload, createDirectUploadFiles, } from "../lib/fileUpload.js";
|
|
@@ -107,6 +110,7 @@ export async function listMessages(userId, input) {
|
|
|
107
110
|
createdAt: message.createdAt,
|
|
108
111
|
sender: message.sender,
|
|
109
112
|
status: message.status,
|
|
113
|
+
aiFeedback: message.aiFeedback,
|
|
110
114
|
attachments: message.attachments,
|
|
111
115
|
meta: message.meta,
|
|
112
116
|
mentions: message.mentions.map((m) => ({ user: m.user })),
|
|
@@ -361,121 +365,6 @@ export async function deleteMessage(userId, messageId) {
|
|
|
361
365
|
}
|
|
362
366
|
return { success: true, messageId };
|
|
363
367
|
}
|
|
364
|
-
const CREATED_INDICES_KEY = {
|
|
365
|
-
assignment: "assignments",
|
|
366
|
-
worksheet: "worksheets",
|
|
367
|
-
section: "sections",
|
|
368
|
-
};
|
|
369
|
-
const MARK_SUGGESTION_MAX_RETRIES = 5;
|
|
370
|
-
/** Mark an AI-suggested item as created. Stores in message meta for fast reads. */
|
|
371
|
-
export async function markSuggestionCreated(userId, input) {
|
|
372
|
-
const { messageId, type, index } = input;
|
|
373
|
-
const existingMessage = await findMessageByIdMinimal(messageId);
|
|
374
|
-
if (!existingMessage) {
|
|
375
|
-
throw new TRPCError({
|
|
376
|
-
code: "NOT_FOUND",
|
|
377
|
-
message: "Message not found",
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
const membership = await findConversationMembership(existingMessage.conversationId, userId);
|
|
381
|
-
if (!membership) {
|
|
382
|
-
throw new TRPCError({
|
|
383
|
-
code: "FORBIDDEN",
|
|
384
|
-
message: "Not a member of this conversation",
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
const conversationId = existingMessage.conversationId;
|
|
388
|
-
let lastError = null;
|
|
389
|
-
for (let attempt = 0; attempt < MARK_SUGGESTION_MAX_RETRIES; attempt++) {
|
|
390
|
-
const msg = await prisma.message.findUnique({
|
|
391
|
-
where: { id: messageId },
|
|
392
|
-
select: { meta: true },
|
|
393
|
-
});
|
|
394
|
-
if (!msg) {
|
|
395
|
-
throw new TRPCError({
|
|
396
|
-
code: "NOT_FOUND",
|
|
397
|
-
message: "Message not found",
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
const currentMeta = msg.meta ?? {};
|
|
401
|
-
const createdIndices = currentMeta.createdIndices ?? {};
|
|
402
|
-
const typeIndices = createdIndices[CREATED_INDICES_KEY[type]] ?? [];
|
|
403
|
-
if (typeIndices.includes(index))
|
|
404
|
-
return { success: true };
|
|
405
|
-
const newTypeIndices = [...typeIndices, index].sort((a, b) => a - b);
|
|
406
|
-
const newMeta = {
|
|
407
|
-
...currentMeta,
|
|
408
|
-
createdIndices: {
|
|
409
|
-
...createdIndices,
|
|
410
|
-
[CREATED_INDICES_KEY[type]]: newTypeIndices,
|
|
411
|
-
},
|
|
412
|
-
};
|
|
413
|
-
const newMetaJson = JSON.stringify(newMeta);
|
|
414
|
-
const currentMetaJson = msg.meta === null ? null : JSON.stringify(msg.meta);
|
|
415
|
-
const updatedCount = currentMetaJson === null
|
|
416
|
-
? await prisma.$executeRaw `
|
|
417
|
-
UPDATE "Message" SET meta = ${newMetaJson}::jsonb
|
|
418
|
-
WHERE id = ${messageId} AND meta IS NULL
|
|
419
|
-
`
|
|
420
|
-
: await prisma.$executeRaw `
|
|
421
|
-
UPDATE "Message" SET meta = ${newMetaJson}::jsonb
|
|
422
|
-
WHERE id = ${messageId} AND meta = ${currentMetaJson}::jsonb
|
|
423
|
-
`;
|
|
424
|
-
if (Number(updatedCount) > 0) {
|
|
425
|
-
const updated = await prisma.message.findUnique({
|
|
426
|
-
where: { id: messageId },
|
|
427
|
-
include: {
|
|
428
|
-
sender: {
|
|
429
|
-
select: {
|
|
430
|
-
id: true,
|
|
431
|
-
username: true,
|
|
432
|
-
profile: {
|
|
433
|
-
select: { displayName: true, profilePicture: true },
|
|
434
|
-
},
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
attachments: { select: messageFileSelect },
|
|
438
|
-
mentions: { select: { userId: true } },
|
|
439
|
-
},
|
|
440
|
-
});
|
|
441
|
-
if (updated) {
|
|
442
|
-
const mentionedUserIds = updated.mentions.map((m) => m.userId);
|
|
443
|
-
try {
|
|
444
|
-
await pusher.trigger(chatChannel(conversationId), "message-updated", {
|
|
445
|
-
id: updated.id,
|
|
446
|
-
content: updated.content,
|
|
447
|
-
senderId: updated.senderId,
|
|
448
|
-
conversationId: updated.conversationId,
|
|
449
|
-
status: updated.status,
|
|
450
|
-
createdAt: updated.createdAt,
|
|
451
|
-
sender: updated.sender,
|
|
452
|
-
attachments: updated.attachments ?? [],
|
|
453
|
-
meta: newMeta,
|
|
454
|
-
mentionedUserIds,
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
catch (error) {
|
|
458
|
-
logger.error("Failed to broadcast suggestion status via Pusher", {
|
|
459
|
-
error,
|
|
460
|
-
messageId,
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return { success: true };
|
|
465
|
-
}
|
|
466
|
-
lastError = new Error("Concurrent update conflict");
|
|
467
|
-
}
|
|
468
|
-
logger.error("markSuggestionCreated failed after retries", {
|
|
469
|
-
messageId,
|
|
470
|
-
type,
|
|
471
|
-
index,
|
|
472
|
-
error: lastError,
|
|
473
|
-
});
|
|
474
|
-
throw new TRPCError({
|
|
475
|
-
code: "CONFLICT",
|
|
476
|
-
message: "Failed to update suggestion status after retries due to concurrent updates",
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
368
|
export async function markAsRead(userId, conversationId) {
|
|
480
369
|
const membership = await findConversationMembership(conversationId, userId);
|
|
481
370
|
if (!membership) {
|
|
@@ -545,5 +434,169 @@ export async function getUnreadCount(userId, conversationId) {
|
|
|
545
434
|
const unreadMentionCount = await countUnreadMentions(conversationId, userId, mentionCutoffTime ?? undefined);
|
|
546
435
|
return { unreadCount, unreadMentionCount };
|
|
547
436
|
}
|
|
437
|
+
/**
|
|
438
|
+
* Regenerate an AI response in a lab or Newton chat.
|
|
439
|
+
*
|
|
440
|
+
* The existing AI bubble is reused (not deleted): the row flips back to
|
|
441
|
+
* PENDING with cleared content/meta/feedback so the message id stays stable
|
|
442
|
+
* for the client, then the matching pipeline refills it. Only the latest
|
|
443
|
+
* message in the conversation can be regenerated — the pipelines build
|
|
444
|
+
* their context from the messages preceding the placeholder.
|
|
445
|
+
*/
|
|
446
|
+
export async function regenerateAIResponse(userId, messageId) {
|
|
447
|
+
const message = await prisma.message.findUnique({
|
|
448
|
+
where: { id: messageId },
|
|
449
|
+
select: {
|
|
450
|
+
id: true,
|
|
451
|
+
senderId: true,
|
|
452
|
+
conversationId: true,
|
|
453
|
+
status: true,
|
|
454
|
+
conversation: {
|
|
455
|
+
select: {
|
|
456
|
+
labChat: { select: { id: true, classId: true } },
|
|
457
|
+
newtonChat: { select: { id: true } },
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
if (!message) {
|
|
463
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Message not found" });
|
|
464
|
+
}
|
|
465
|
+
const membership = await findConversationMembership(message.conversationId, userId);
|
|
466
|
+
if (!membership) {
|
|
467
|
+
throw new TRPCError({
|
|
468
|
+
code: "FORBIDDEN",
|
|
469
|
+
message: "Not a member of this conversation",
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
if (!isAIUser(message.senderId)) {
|
|
473
|
+
throw new TRPCError({
|
|
474
|
+
code: "BAD_REQUEST",
|
|
475
|
+
message: "Only AI messages can be regenerated",
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
const { labChat, newtonChat } = message.conversation;
|
|
479
|
+
if (!labChat && !newtonChat) {
|
|
480
|
+
throw new TRPCError({
|
|
481
|
+
code: "BAD_REQUEST",
|
|
482
|
+
message: "This conversation has no AI pipeline to regenerate with",
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (message.status === JobStatus.PENDING) {
|
|
486
|
+
throw new TRPCError({
|
|
487
|
+
code: "BAD_REQUEST",
|
|
488
|
+
message: "This response is still being generated",
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
const latestMessage = await prisma.message.findFirst({
|
|
492
|
+
where: { conversationId: message.conversationId },
|
|
493
|
+
orderBy: { createdAt: "desc" },
|
|
494
|
+
select: { id: true },
|
|
495
|
+
});
|
|
496
|
+
if (latestMessage?.id !== message.id) {
|
|
497
|
+
throw new TRPCError({
|
|
498
|
+
code: "BAD_REQUEST",
|
|
499
|
+
message: "Only the latest AI response can be regenerated",
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
const reusedAiMessage = await prisma.message.update({
|
|
503
|
+
where: { id: message.id },
|
|
504
|
+
data: {
|
|
505
|
+
content: "",
|
|
506
|
+
status: JobStatus.PENDING,
|
|
507
|
+
meta: {},
|
|
508
|
+
aiFeedback: null,
|
|
509
|
+
},
|
|
510
|
+
select: {
|
|
511
|
+
id: true,
|
|
512
|
+
content: true,
|
|
513
|
+
senderId: true,
|
|
514
|
+
conversationId: true,
|
|
515
|
+
status: true,
|
|
516
|
+
createdAt: true,
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
// Tell the chat UI the existing AI bubble is regenerating — content
|
|
520
|
+
// cleared, status flipped back to PENDING, same id.
|
|
521
|
+
try {
|
|
522
|
+
await pusher.trigger(chatChannel(message.conversationId), "message-updated", {
|
|
523
|
+
id: reusedAiMessage.id,
|
|
524
|
+
content: reusedAiMessage.content,
|
|
525
|
+
senderId: reusedAiMessage.senderId,
|
|
526
|
+
conversationId: reusedAiMessage.conversationId,
|
|
527
|
+
status: reusedAiMessage.status,
|
|
528
|
+
createdAt: reusedAiMessage.createdAt,
|
|
529
|
+
sender: {
|
|
530
|
+
id: reusedAiMessage.senderId,
|
|
531
|
+
username: "Newton_AI",
|
|
532
|
+
profile: { displayName: "Newton AI", profilePicture: null },
|
|
533
|
+
},
|
|
534
|
+
mentionedUserIds: [],
|
|
535
|
+
meta: {},
|
|
536
|
+
attachments: [],
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
logger.error("Failed to broadcast AI regenerate reset", {
|
|
541
|
+
error,
|
|
542
|
+
messageId,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
if (labChat) {
|
|
547
|
+
await enqueueRunJob("AI_LAB_CHAT", { conversationId: message.conversationId, aiMessageId: reusedAiMessage.id }, { classId: labChat.classId, userId });
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
await enqueueRunJob("AI_NEWTON_CHAT", { conversationId: message.conversationId, aiMessageId: reusedAiMessage.id }, { userId });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
logger.error("Failed to enqueue AI regeneration", {
|
|
555
|
+
error,
|
|
556
|
+
messageId,
|
|
557
|
+
conversationId: message.conversationId,
|
|
558
|
+
});
|
|
559
|
+
throw new TRPCError({
|
|
560
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
561
|
+
message: "Failed to start regeneration",
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
return { success: true, messageId: reusedAiMessage.id };
|
|
565
|
+
}
|
|
566
|
+
/** Record (or clear) thumbs up/down feedback on an AI message. */
|
|
567
|
+
export async function setAIMessageFeedback(userId, input) {
|
|
568
|
+
const message = await prisma.message.findUnique({
|
|
569
|
+
where: { id: input.messageId },
|
|
570
|
+
select: { id: true, senderId: true, conversationId: true, status: true },
|
|
571
|
+
});
|
|
572
|
+
if (!message) {
|
|
573
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Message not found" });
|
|
574
|
+
}
|
|
575
|
+
const membership = await findConversationMembership(message.conversationId, userId);
|
|
576
|
+
if (!membership) {
|
|
577
|
+
throw new TRPCError({
|
|
578
|
+
code: "FORBIDDEN",
|
|
579
|
+
message: "Not a member of this conversation",
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
if (!isAIUser(message.senderId)) {
|
|
583
|
+
throw new TRPCError({
|
|
584
|
+
code: "BAD_REQUEST",
|
|
585
|
+
message: "Feedback can only be left on AI messages",
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (message.status !== JobStatus.COMPLETED) {
|
|
589
|
+
throw new TRPCError({
|
|
590
|
+
code: "BAD_REQUEST",
|
|
591
|
+
message: "Feedback can only be left on completed AI responses",
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
const updated = await prisma.message.update({
|
|
595
|
+
where: { id: message.id },
|
|
596
|
+
data: { aiFeedback: input.feedback },
|
|
597
|
+
select: { id: true, aiFeedback: true },
|
|
598
|
+
});
|
|
599
|
+
return { success: true, messageId: updated.id, feedback: updated.aiFeedback };
|
|
600
|
+
}
|
|
548
601
|
//# sourceMappingURL=message.js.map
|
|
549
|
-
//# debugId=
|
|
602
|
+
//# debugId=ebbba0b2-a07e-5511-b849-8de0f6dc0ed4
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","sources":["services/message.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Message service – send, update, delete messages; mark as read; track mentions.\n * Broadcasts real-time updates via Pusher.\n */\nimport type { AppTransactionClient } from \"../lib/prisma.js\";\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { chatChannel, pusher } from \"../lib/pusher.js\";\nimport {\n confirmDirectUpload,\n createDirectUploadFiles,\n} from \"../lib/fileUpload.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n findConversationMembership,\n findMessages,\n findMentionedMemberships,\n findMessageById,\n findMessageByIdMinimal,\n countUnreadMessages,\n countUnreadMentions,\n messageFileSelect,\n} from \"../models/message.js\";\n\nexport function ensureMessageHasContentOrAttachments(\n content: string,\n fileIds: string[] = []\n) {\n if (!content.trim() && fileIds.length === 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Message must include content or attachments\",\n });\n }\n}\n\nexport async function claimMessageAttachments(\n tx: AppTransactionClient,\n userId: string,\n conversationId: string,\n messageId: string,\n fileIds: string[] = []\n) {\n const uniqueFileIds = [...new Set(fileIds)];\n\n if (uniqueFileIds.length === 0) {\n return;\n }\n\n const { count } = await tx.file.updateMany({\n where: {\n id: { in: uniqueFileIds },\n userId,\n conversationId,\n uploadStatus: \"COMPLETED\",\n messageId: null,\n },\n data: {\n messageId,\n },\n });\n\n if (count !== uniqueFileIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some attachments are missing, unfinished, or unavailable\",\n });\n }\n}\n\nexport async function getMessageUploadUrls(\n userId: string,\n input: {\n conversationId: string;\n files: { name: string; type: string; size: number }[];\n }\n) {\n const membership = await findConversationMembership(input.conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n return createDirectUploadFiles(\n input.files,\n userId,\n undefined,\n undefined,\n undefined,\n undefined,\n input.conversationId\n );\n}\n\nexport async function confirmMessageUpload(\n userId: string,\n input: {\n conversationId: string;\n fileId: string;\n uploadSuccess: boolean;\n errorMessage?: string;\n }\n) {\n const membership = await findConversationMembership(input.conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const file = await prisma.file.findFirst({\n where: {\n id: input.fileId,\n userId,\n conversationId: input.conversationId,\n messageId: null,\n },\n select: { id: true },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found or you do not have permission\",\n });\n }\n\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n\n return {\n success: true,\n message: input.uploadSuccess\n ? \"Upload confirmed successfully\"\n : \"Upload failed\",\n };\n}\n\n/** List messages in a conversation with cursor-based pagination. */\nexport async function listMessages(\n userId: string,\n input: {\n conversationId: string;\n cursor?: string;\n limit: number;\n }\n) {\n const { conversationId, cursor, limit } = input;\n\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const messages = await findMessages(conversationId, {\n cursor: cursor ? new Date(cursor) : undefined,\n limit,\n });\n\n let nextCursor: string | undefined;\n if (messages.length > limit) {\n const nextItem = messages.pop();\n nextCursor = nextItem!.createdAt.toISOString();\n }\n\n return {\n messages: messages.reverse().map((message) => ({\n id: message.id,\n content: message.content,\n senderId: message.senderId,\n conversationId: message.conversationId,\n createdAt: message.createdAt,\n sender: message.sender,\n status: message.status,\n attachments: message.attachments,\n meta: message.meta as Record<string, unknown>,\n mentions: message.mentions.map((m) => ({ user: m.user })),\n mentionsMe: message.mentions.some((m) => m.userId === userId),\n })),\n nextCursor,\n };\n}\n\n/** Send a message. Validates membership, creates message, broadcasts via Pusher. */\nexport async function sendMessage(\n userId: string,\n input: {\n conversationId: string;\n content: string;\n mentionedUserIds?: string[];\n fileIds?: string[];\n }\n) {\n const {\n conversationId,\n content,\n mentionedUserIds = [],\n fileIds = [],\n } = input;\n\n ensureMessageHasContentOrAttachments(content, fileIds);\n const normalizedContent = content.trim();\n\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findMentionedMemberships(\n conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n const result = await prisma.$transaction(async (tx) => {\n const message = await tx.message.create({\n data: {\n content: normalizedContent,\n senderId: userId,\n conversationId,\n },\n });\n\n await claimMessageAttachments(\n tx,\n userId,\n conversationId,\n message.id,\n fileIds\n );\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId: message.id,\n userId: mentionedUserId,\n })),\n });\n }\n\n await tx.conversationMember.updateMany({\n where: {\n conversationId,\n hiddenAt: { not: null },\n },\n data: { hiddenAt: null },\n });\n\n await tx.conversation.update({\n where: { id: conversationId },\n data: { updatedAt: new Date() },\n });\n\n return tx.message.findUniqueOrThrow({\n where: { id: message.id },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: messageFileSelect,\n },\n },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"new-message\",\n {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n status: result.status,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n meta: (result.meta as Record<string, unknown>) ?? {},\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message via Pusher\", {\n error,\n conversationId,\n messageId: result.id,\n });\n }\n\n return {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n status: result.status,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n mentionedUserIds,\n };\n}\n\nexport async function updateMessage(\n userId: string,\n input: {\n messageId: string;\n content: string;\n mentionedUserIds?: string[];\n }\n) {\n const { messageId, content, mentionedUserIds = [] } = input;\n\n const existingMessage = await findMessageById(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n if (existingMessage.senderId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not the sender of this message\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findMentionedMemberships(\n existingMessage.conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n const updatedMessage = await prisma.$transaction(async (tx) => {\n const message = await tx.message.update({\n where: { id: messageId },\n data: { content },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: messageFileSelect,\n },\n },\n });\n\n await tx.mention.deleteMany({\n where: { messageId },\n });\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId,\n userId: mentionedUserId,\n })),\n });\n }\n\n return message;\n });\n\n try {\n await pusher.trigger(\n chatChannel(existingMessage.conversationId),\n \"message-updated\",\n {\n id: updatedMessage.id,\n content: updatedMessage.content,\n senderId: updatedMessage.senderId,\n conversationId: updatedMessage.conversationId,\n status: updatedMessage.status,\n createdAt: updatedMessage.createdAt,\n sender: updatedMessage.sender,\n attachments: updatedMessage.attachments ?? [],\n meta: (updatedMessage.meta as Record<string, unknown>) ?? {},\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message update via Pusher\", {\n error,\n conversationId: existingMessage.conversationId,\n messageId,\n });\n }\n\n return {\n id: updatedMessage.id,\n content: updatedMessage.content,\n senderId: updatedMessage.senderId,\n conversationId: updatedMessage.conversationId,\n status: updatedMessage.status,\n createdAt: updatedMessage.createdAt,\n sender: updatedMessage.sender,\n mentionedUserIds,\n };\n}\n\nexport async function deleteMessage(userId: string, messageId: string) {\n const existingMessage = await findMessageByIdMinimal(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n if (existingMessage.senderId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not the sender of this message\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.$transaction(async (tx) => {\n await tx.mention.deleteMany({\n where: { messageId },\n });\n await tx.message.delete({\n where: { id: messageId },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(existingMessage.conversationId),\n \"message-deleted\",\n {\n messageId,\n conversationId: existingMessage.conversationId,\n senderId: existingMessage.senderId,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message deletion via Pusher\", {\n error,\n conversationId: existingMessage.conversationId,\n messageId,\n });\n }\n\n return { success: true, messageId };\n}\n\nconst CREATED_INDICES_KEY: Record<\"assignment\" | \"worksheet\" | \"section\", string> = {\n assignment: \"assignments\",\n worksheet: \"worksheets\",\n section: \"sections\",\n};\n\nconst MARK_SUGGESTION_MAX_RETRIES = 5;\n\n/** Mark an AI-suggested item as created. Stores in message meta for fast reads. */\nexport async function markSuggestionCreated(\n userId: string,\n input: {\n messageId: string;\n type: \"assignment\" | \"worksheet\" | \"section\";\n index: number;\n }\n) {\n const { messageId, type, index } = input;\n\n const existingMessage = await findMessageByIdMinimal(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const conversationId = existingMessage.conversationId;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < MARK_SUGGESTION_MAX_RETRIES; attempt++) {\n const msg = await prisma.message.findUnique({\n where: { id: messageId },\n select: { meta: true },\n });\n if (!msg) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n const currentMeta = (msg.meta as Record<string, unknown>) ?? {};\n const createdIndices = (currentMeta.createdIndices as Record<string, number[]>) ?? {};\n const typeIndices = createdIndices[CREATED_INDICES_KEY[type]] ?? [];\n if (typeIndices.includes(index)) return { success: true };\n\n const newTypeIndices = [...typeIndices, index].sort((a, b) => a - b);\n const newMeta = {\n ...currentMeta,\n createdIndices: {\n ...createdIndices,\n [CREATED_INDICES_KEY[type]]: newTypeIndices,\n },\n };\n\n const newMetaJson = JSON.stringify(newMeta);\n const currentMetaJson = msg.meta === null ? null : JSON.stringify(msg.meta);\n\n const updatedCount =\n currentMetaJson === null\n ? await prisma.$executeRaw`\n UPDATE \"Message\" SET meta = ${newMetaJson}::jsonb\n WHERE id = ${messageId} AND meta IS NULL\n `\n : await prisma.$executeRaw`\n UPDATE \"Message\" SET meta = ${newMetaJson}::jsonb\n WHERE id = ${messageId} AND meta = ${currentMetaJson}::jsonb\n `;\n\n if (Number(updatedCount) > 0) {\n const updated = await prisma.message.findUnique({\n where: { id: messageId },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: { displayName: true, profilePicture: true },\n },\n },\n },\n attachments: { select: messageFileSelect },\n mentions: { select: { userId: true } },\n },\n });\n\n if (updated) {\n const mentionedUserIds = updated.mentions.map((m) => m.userId);\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"message-updated\",\n {\n id: updated.id,\n content: updated.content,\n senderId: updated.senderId,\n conversationId: updated.conversationId,\n status: updated.status,\n createdAt: updated.createdAt,\n sender: updated.sender,\n attachments: updated.attachments ?? [],\n meta: newMeta,\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast suggestion status via Pusher\", {\n error,\n messageId,\n });\n }\n }\n return { success: true };\n }\n\n lastError = new Error(\"Concurrent update conflict\");\n }\n\n logger.error(\"markSuggestionCreated failed after retries\", {\n messageId,\n type,\n index,\n error: lastError,\n });\n throw new TRPCError({\n code: \"CONFLICT\",\n message: \"Failed to update suggestion status after retries due to concurrent updates\",\n });\n}\n\nexport async function markAsRead(userId: string, conversationId: string) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.conversationMember.update({\n where: { id: membership.id },\n data: { lastViewedAt: new Date() },\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"conversation-viewed\",\n {\n userId,\n viewedAt: new Date(),\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast conversation-viewed via Pusher\", {\n error,\n conversationId,\n });\n }\n\n return { success: true };\n}\n\nexport async function markMentionsAsRead(\n userId: string,\n conversationId: string\n) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.conversationMember.update({\n where: { id: membership.id },\n data: { lastViewedMentionAt: new Date() },\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"mentions-viewed\",\n {\n userId,\n viewedAt: new Date(),\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast mentions-viewed via Pusher\", {\n error,\n conversationId,\n });\n }\n\n return { success: true };\n}\n\nexport async function getUnreadCount(\n userId: string,\n conversationId: string\n) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const unreadCount = await countUnreadMessages(\n conversationId,\n userId,\n membership.lastViewedAt ?? undefined\n );\n\n const mentionCutoffTime =\n membership.lastViewedMentionAt && membership.lastViewedAt\n ? membership.lastViewedMentionAt > membership.lastViewedAt\n ? membership.lastViewedMentionAt\n : membership.lastViewedAt\n : membership.lastViewedMentionAt ?? membership.lastViewedAt;\n\n const unreadMentionCount = await countUnreadMentions(\n conversationId,\n userId,\n mentionCutoffTime ?? undefined\n );\n\n return { unreadCount, unreadMentionCount };\n}\n"],"names":[],"mappings":"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","debug_id":"fbce11f4-499e-5d7a-90a5-2b01d5c0082f"}
|
|
1
|
+
{"version":3,"file":"message.js","sources":["services/message.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Message service – send, update, delete messages; mark as read; track mentions.\n * Broadcasts real-time updates via Pusher.\n */\nimport type { AppTransactionClient } from \"../lib/prisma.js\";\nimport { TRPCError } from \"@trpc/server\";\nimport { JobStatus, type AiMessageFeedback } from \"@prisma/client\";\nimport { isAIUser } from \"../utils/aiUser.js\";\nimport { enqueueRunJob } from \"../workers/queues.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport { chatChannel, pusher } from \"../lib/pusher.js\";\nimport {\n confirmDirectUpload,\n createDirectUploadFiles,\n} from \"../lib/fileUpload.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n findConversationMembership,\n findMessages,\n findMentionedMemberships,\n findMessageById,\n findMessageByIdMinimal,\n countUnreadMessages,\n countUnreadMentions,\n messageFileSelect,\n} from \"../models/message.js\";\n\nexport function ensureMessageHasContentOrAttachments(\n content: string,\n fileIds: string[] = []\n) {\n if (!content.trim() && fileIds.length === 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Message must include content or attachments\",\n });\n }\n}\n\nexport async function claimMessageAttachments(\n tx: AppTransactionClient,\n userId: string,\n conversationId: string,\n messageId: string,\n fileIds: string[] = []\n) {\n const uniqueFileIds = [...new Set(fileIds)];\n\n if (uniqueFileIds.length === 0) {\n return;\n }\n\n const { count } = await tx.file.updateMany({\n where: {\n id: { in: uniqueFileIds },\n userId,\n conversationId,\n uploadStatus: \"COMPLETED\",\n messageId: null,\n },\n data: {\n messageId,\n },\n });\n\n if (count !== uniqueFileIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some attachments are missing, unfinished, or unavailable\",\n });\n }\n}\n\nexport async function getMessageUploadUrls(\n userId: string,\n input: {\n conversationId: string;\n files: { name: string; type: string; size: number }[];\n }\n) {\n const membership = await findConversationMembership(input.conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n return createDirectUploadFiles(\n input.files,\n userId,\n undefined,\n undefined,\n undefined,\n undefined,\n input.conversationId\n );\n}\n\nexport async function confirmMessageUpload(\n userId: string,\n input: {\n conversationId: string;\n fileId: string;\n uploadSuccess: boolean;\n errorMessage?: string;\n }\n) {\n const membership = await findConversationMembership(input.conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const file = await prisma.file.findFirst({\n where: {\n id: input.fileId,\n userId,\n conversationId: input.conversationId,\n messageId: null,\n },\n select: { id: true },\n });\n\n if (!file) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"File not found or you do not have permission\",\n });\n }\n\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n\n return {\n success: true,\n message: input.uploadSuccess\n ? \"Upload confirmed successfully\"\n : \"Upload failed\",\n };\n}\n\n/** List messages in a conversation with cursor-based pagination. */\nexport async function listMessages(\n userId: string,\n input: {\n conversationId: string;\n cursor?: string;\n limit: number;\n }\n) {\n const { conversationId, cursor, limit } = input;\n\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const messages = await findMessages(conversationId, {\n cursor: cursor ? new Date(cursor) : undefined,\n limit,\n });\n\n let nextCursor: string | undefined;\n if (messages.length > limit) {\n const nextItem = messages.pop();\n nextCursor = nextItem!.createdAt.toISOString();\n }\n\n return {\n messages: messages.reverse().map((message) => ({\n id: message.id,\n content: message.content,\n senderId: message.senderId,\n conversationId: message.conversationId,\n createdAt: message.createdAt,\n sender: message.sender,\n status: message.status,\n aiFeedback: message.aiFeedback,\n attachments: message.attachments,\n meta: message.meta as Record<string, unknown>,\n mentions: message.mentions.map((m) => ({ user: m.user })),\n mentionsMe: message.mentions.some((m) => m.userId === userId),\n })),\n nextCursor,\n };\n}\n\n/** Send a message. Validates membership, creates message, broadcasts via Pusher. */\nexport async function sendMessage(\n userId: string,\n input: {\n conversationId: string;\n content: string;\n mentionedUserIds?: string[];\n fileIds?: string[];\n }\n) {\n const {\n conversationId,\n content,\n mentionedUserIds = [],\n fileIds = [],\n } = input;\n\n ensureMessageHasContentOrAttachments(content, fileIds);\n const normalizedContent = content.trim();\n\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findMentionedMemberships(\n conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n const result = await prisma.$transaction(async (tx) => {\n const message = await tx.message.create({\n data: {\n content: normalizedContent,\n senderId: userId,\n conversationId,\n },\n });\n\n await claimMessageAttachments(\n tx,\n userId,\n conversationId,\n message.id,\n fileIds\n );\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId: message.id,\n userId: mentionedUserId,\n })),\n });\n }\n\n await tx.conversationMember.updateMany({\n where: {\n conversationId,\n hiddenAt: { not: null },\n },\n data: { hiddenAt: null },\n });\n\n await tx.conversation.update({\n where: { id: conversationId },\n data: { updatedAt: new Date() },\n });\n\n return tx.message.findUniqueOrThrow({\n where: { id: message.id },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: messageFileSelect,\n },\n },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"new-message\",\n {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n status: result.status,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n meta: (result.meta as Record<string, unknown>) ?? {},\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message via Pusher\", {\n error,\n conversationId,\n messageId: result.id,\n });\n }\n\n return {\n id: result.id,\n content: result.content,\n senderId: result.senderId,\n conversationId: result.conversationId,\n status: result.status,\n createdAt: result.createdAt,\n sender: result.sender,\n attachments: result.attachments ?? [],\n mentionedUserIds,\n };\n}\n\nexport async function updateMessage(\n userId: string,\n input: {\n messageId: string;\n content: string;\n mentionedUserIds?: string[];\n }\n) {\n const { messageId, content, mentionedUserIds = [] } = input;\n\n const existingMessage = await findMessageById(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n if (existingMessage.senderId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not the sender of this message\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (mentionedUserIds.length > 0) {\n const mentionedMemberships = await findMentionedMemberships(\n existingMessage.conversationId,\n mentionedUserIds\n );\n if (mentionedMemberships.length !== mentionedUserIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Some mentioned users are not members of this conversation\",\n });\n }\n }\n\n const updatedMessage = await prisma.$transaction(async (tx) => {\n const message = await tx.message.update({\n where: { id: messageId },\n data: { content },\n include: {\n sender: {\n select: {\n id: true,\n username: true,\n profile: {\n select: {\n displayName: true,\n profilePicture: true,\n },\n },\n },\n },\n attachments: {\n select: messageFileSelect,\n },\n },\n });\n\n await tx.mention.deleteMany({\n where: { messageId },\n });\n\n if (mentionedUserIds.length > 0) {\n await tx.mention.createMany({\n data: mentionedUserIds.map((mentionedUserId) => ({\n messageId,\n userId: mentionedUserId,\n })),\n });\n }\n\n return message;\n });\n\n try {\n await pusher.trigger(\n chatChannel(existingMessage.conversationId),\n \"message-updated\",\n {\n id: updatedMessage.id,\n content: updatedMessage.content,\n senderId: updatedMessage.senderId,\n conversationId: updatedMessage.conversationId,\n status: updatedMessage.status,\n createdAt: updatedMessage.createdAt,\n sender: updatedMessage.sender,\n attachments: updatedMessage.attachments ?? [],\n meta: (updatedMessage.meta as Record<string, unknown>) ?? {},\n mentionedUserIds,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message update via Pusher\", {\n error,\n conversationId: existingMessage.conversationId,\n messageId,\n });\n }\n\n return {\n id: updatedMessage.id,\n content: updatedMessage.content,\n senderId: updatedMessage.senderId,\n conversationId: updatedMessage.conversationId,\n status: updatedMessage.status,\n createdAt: updatedMessage.createdAt,\n sender: updatedMessage.sender,\n mentionedUserIds,\n };\n}\n\nexport async function deleteMessage(userId: string, messageId: string) {\n const existingMessage = await findMessageByIdMinimal(messageId);\n if (!existingMessage) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: \"Message not found\",\n });\n }\n\n if (existingMessage.senderId !== userId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not the sender of this message\",\n });\n }\n\n const membership = await findConversationMembership(\n existingMessage.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.$transaction(async (tx) => {\n await tx.mention.deleteMany({\n where: { messageId },\n });\n await tx.message.delete({\n where: { id: messageId },\n });\n });\n\n try {\n await pusher.trigger(\n chatChannel(existingMessage.conversationId),\n \"message-deleted\",\n {\n messageId,\n conversationId: existingMessage.conversationId,\n senderId: existingMessage.senderId,\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast message deletion via Pusher\", {\n error,\n conversationId: existingMessage.conversationId,\n messageId,\n });\n }\n\n return { success: true, messageId };\n}\n\n\nexport async function markAsRead(userId: string, conversationId: string) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.conversationMember.update({\n where: { id: membership.id },\n data: { lastViewedAt: new Date() },\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"conversation-viewed\",\n {\n userId,\n viewedAt: new Date(),\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast conversation-viewed via Pusher\", {\n error,\n conversationId,\n });\n }\n\n return { success: true };\n}\n\nexport async function markMentionsAsRead(\n userId: string,\n conversationId: string\n) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n await prisma.conversationMember.update({\n where: { id: membership.id },\n data: { lastViewedMentionAt: new Date() },\n });\n\n try {\n await pusher.trigger(\n chatChannel(conversationId),\n \"mentions-viewed\",\n {\n userId,\n viewedAt: new Date(),\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast mentions-viewed via Pusher\", {\n error,\n conversationId,\n });\n }\n\n return { success: true };\n}\n\nexport async function getUnreadCount(\n userId: string,\n conversationId: string\n) {\n const membership = await findConversationMembership(conversationId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n const unreadCount = await countUnreadMessages(\n conversationId,\n userId,\n membership.lastViewedAt ?? undefined\n );\n\n const mentionCutoffTime =\n membership.lastViewedMentionAt && membership.lastViewedAt\n ? membership.lastViewedMentionAt > membership.lastViewedAt\n ? membership.lastViewedMentionAt\n : membership.lastViewedAt\n : membership.lastViewedMentionAt ?? membership.lastViewedAt;\n\n const unreadMentionCount = await countUnreadMentions(\n conversationId,\n userId,\n mentionCutoffTime ?? undefined\n );\n\n return { unreadCount, unreadMentionCount };\n}\n\n/**\n * Regenerate an AI response in a lab or Newton chat.\n *\n * The existing AI bubble is reused (not deleted): the row flips back to\n * PENDING with cleared content/meta/feedback so the message id stays stable\n * for the client, then the matching pipeline refills it. Only the latest\n * message in the conversation can be regenerated — the pipelines build\n * their context from the messages preceding the placeholder.\n */\nexport async function regenerateAIResponse(userId: string, messageId: string) {\n const message = await prisma.message.findUnique({\n where: { id: messageId },\n select: {\n id: true,\n senderId: true,\n conversationId: true,\n status: true,\n conversation: {\n select: {\n labChat: { select: { id: true, classId: true } },\n newtonChat: { select: { id: true } },\n },\n },\n },\n });\n if (!message) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Message not found\" });\n }\n\n const membership = await findConversationMembership(\n message.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (!isAIUser(message.senderId)) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Only AI messages can be regenerated\",\n });\n }\n\n const { labChat, newtonChat } = message.conversation;\n if (!labChat && !newtonChat) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"This conversation has no AI pipeline to regenerate with\",\n });\n }\n\n if (message.status === JobStatus.PENDING) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"This response is still being generated\",\n });\n }\n\n const latestMessage = await prisma.message.findFirst({\n where: { conversationId: message.conversationId },\n orderBy: { createdAt: \"desc\" },\n select: { id: true },\n });\n if (latestMessage?.id !== message.id) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Only the latest AI response can be regenerated\",\n });\n }\n\n const reusedAiMessage = await prisma.message.update({\n where: { id: message.id },\n data: {\n content: \"\",\n status: JobStatus.PENDING,\n meta: {},\n aiFeedback: null,\n },\n select: {\n id: true,\n content: true,\n senderId: true,\n conversationId: true,\n status: true,\n createdAt: true,\n },\n });\n\n // Tell the chat UI the existing AI bubble is regenerating — content\n // cleared, status flipped back to PENDING, same id.\n try {\n await pusher.trigger(\n chatChannel(message.conversationId),\n \"message-updated\",\n {\n id: reusedAiMessage.id,\n content: reusedAiMessage.content,\n senderId: reusedAiMessage.senderId,\n conversationId: reusedAiMessage.conversationId,\n status: reusedAiMessage.status,\n createdAt: reusedAiMessage.createdAt,\n sender: {\n id: reusedAiMessage.senderId,\n username: \"Newton_AI\",\n profile: { displayName: \"Newton AI\", profilePicture: null },\n },\n mentionedUserIds: [],\n meta: {},\n attachments: [],\n }\n );\n } catch (error) {\n logger.error(\"Failed to broadcast AI regenerate reset\", {\n error,\n messageId,\n });\n }\n\n try {\n if (labChat) {\n await enqueueRunJob(\n \"AI_LAB_CHAT\",\n { conversationId: message.conversationId, aiMessageId: reusedAiMessage.id },\n { classId: labChat.classId, userId }\n );\n } else {\n await enqueueRunJob(\n \"AI_NEWTON_CHAT\",\n { conversationId: message.conversationId, aiMessageId: reusedAiMessage.id },\n { userId }\n );\n }\n } catch (error) {\n logger.error(\"Failed to enqueue AI regeneration\", {\n error,\n messageId,\n conversationId: message.conversationId,\n });\n throw new TRPCError({\n code: \"INTERNAL_SERVER_ERROR\",\n message: \"Failed to start regeneration\",\n });\n }\n\n return { success: true, messageId: reusedAiMessage.id };\n}\n\n/** Record (or clear) thumbs up/down feedback on an AI message. */\nexport async function setAIMessageFeedback(\n userId: string,\n input: {\n messageId: string;\n feedback: AiMessageFeedback | null;\n }\n) {\n const message = await prisma.message.findUnique({\n where: { id: input.messageId },\n select: { id: true, senderId: true, conversationId: true, status: true },\n });\n if (!message) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Message not found\" });\n }\n\n const membership = await findConversationMembership(\n message.conversationId,\n userId\n );\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Not a member of this conversation\",\n });\n }\n\n if (!isAIUser(message.senderId)) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Feedback can only be left on AI messages\",\n });\n }\n\n if (message.status !== JobStatus.COMPLETED) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Feedback can only be left on completed AI responses\",\n });\n }\n\n const updated = await prisma.message.update({\n where: { id: message.id },\n data: { aiFeedback: input.feedback },\n select: { id: true, aiFeedback: true },\n });\n\n return { success: true, messageId: updated.id, feedback: updated.aiFeedback };\n}\n"],"names":[],"mappings":"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","debug_id":"ebbba0b2-a07e-5511-b849-8de0f6dc0ed4"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"/","sources":["services/module.ts"],"names":[],"mappings":"AASA,OAAO,EACL,oBAAoB,IAAI,cAAc,EACtC,yBAAyB,EAG1B,MAAM,gBAAgB,CAAC;AA4BxB,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;MAGtE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;GAkB/F;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDF;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;;GASvC;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;MAuBjD;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"/","sources":["services/module.ts"],"names":[],"mappings":"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"}
|
package/dist/services/module.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* sequential progression.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
!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]="
|
|
10
|
+
!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]="69f3db7b-9e94-5f6e-9e8b-f196705cf116")}catch(e){}}();
|
|
11
11
|
import { TRPCError } from "@trpc/server";
|
|
12
12
|
import { CourseModuleItemType as ModuleItemType, ModuleItemRequirementType, ModuleItemProgressStatus, } from "@prisma/client";
|
|
13
13
|
import { prisma } from "../lib/prisma.js";
|
|
@@ -134,6 +134,7 @@ const CONTENT_TYPES = [
|
|
|
134
134
|
ModuleItemType.FILE,
|
|
135
135
|
ModuleItemType.ANNOUNCEMENT,
|
|
136
136
|
ModuleItemType.DISCUSSION,
|
|
137
|
+
ModuleItemType.VISUALIZATION,
|
|
137
138
|
];
|
|
138
139
|
const SUBMITTABLE_TYPES = [
|
|
139
140
|
ModuleItemType.ASSIGNMENT,
|
|
@@ -173,7 +174,9 @@ async function validateItemInput(classId, input) {
|
|
|
173
174
|
? await prisma.announcement.findFirst({ where: { id: itemId, classId } })
|
|
174
175
|
: itemType === ModuleItemType.DISCUSSION
|
|
175
176
|
? await prisma.discussion.findFirst({ where: { id: itemId, classId } })
|
|
176
|
-
:
|
|
177
|
+
: itemType === ModuleItemType.VISUALIZATION
|
|
178
|
+
? await prisma.visualization.findFirst({ where: { id: itemId, classId } })
|
|
179
|
+
: await prisma.file.findUnique({ where: { id: itemId } });
|
|
177
180
|
if (!exists) {
|
|
178
181
|
throw new TRPCError({
|
|
179
182
|
code: "NOT_FOUND",
|
|
@@ -309,10 +312,26 @@ function isModuleComplete(module, completedItemIds) {
|
|
|
309
312
|
* completion/accessibility and per-module progress percentages.
|
|
310
313
|
*/
|
|
311
314
|
export async function getStudentModulesState(classId, studentId) {
|
|
312
|
-
const [
|
|
315
|
+
const [allModules, progress] = await Promise.all([
|
|
313
316
|
findModulesByClassId(classId, true),
|
|
314
317
|
findStudentProgressForClass(classId, studentId),
|
|
315
318
|
]);
|
|
319
|
+
// Hide visualisations the teacher hasn't published yet.
|
|
320
|
+
const visualizationIds = allModules
|
|
321
|
+
.flatMap((m) => m.items)
|
|
322
|
+
.filter((i) => i.itemType === ModuleItemType.VISUALIZATION && i.itemId)
|
|
323
|
+
.map((i) => i.itemId);
|
|
324
|
+
const publishedVisualizationIds = new Set(visualizationIds.length
|
|
325
|
+
? (await prisma.visualization.findMany({
|
|
326
|
+
where: { id: { in: visualizationIds }, classId, published: true },
|
|
327
|
+
select: { id: true },
|
|
328
|
+
})).map((v) => v.id)
|
|
329
|
+
: []);
|
|
330
|
+
const modules = allModules.map((m) => ({
|
|
331
|
+
...m,
|
|
332
|
+
items: m.items.filter((i) => i.itemType !== ModuleItemType.VISUALIZATION ||
|
|
333
|
+
(!!i.itemId && publishedVisualizationIds.has(i.itemId))),
|
|
334
|
+
}));
|
|
316
335
|
const completedItemIds = new Set(progress
|
|
317
336
|
.filter((p) => p.status === ModuleItemProgressStatus.COMPLETE)
|
|
318
337
|
.map((p) => p.moduleItemId));
|
|
@@ -515,4 +534,4 @@ export async function syncWorksheetModuleProgress(studentId, worksheetId) {
|
|
|
515
534
|
}
|
|
516
535
|
}
|
|
517
536
|
//# sourceMappingURL=module.js.map
|
|
518
|
-
//# debugId=
|
|
537
|
+
//# debugId=69f3db7b-9e94-5f6e-9e8b-f196705cf116
|