@studious-lms/server 1.14.0 → 1.16.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.
Files changed (129) hide show
  1. package/dist/lib/inferDocumentFormat.d.ts +10 -0
  2. package/dist/lib/inferDocumentFormat.d.ts.map +1 -0
  3. package/dist/lib/inferDocumentFormat.js +48 -0
  4. package/dist/lib/inferDocumentFormat.js.map +1 -0
  5. package/dist/models/announcement.d.ts +45 -45
  6. package/dist/models/assignment.d.ts +75 -75
  7. package/dist/models/class.d.ts +13 -13
  8. package/dist/models/comment.d.ts +6 -6
  9. package/dist/models/conversation.d.ts +23 -23
  10. package/dist/models/event.d.ts +18 -18
  11. package/dist/models/file.d.ts +9 -9
  12. package/dist/models/labChat.d.ts +4 -4
  13. package/dist/models/message.d.ts +13 -13
  14. package/dist/models/newtonChat.d.ts +3 -3
  15. package/dist/models/notification.d.ts +4 -4
  16. package/dist/models/worksheet.d.ts +121 -121
  17. package/dist/pipelines/aiLabChat.d.ts +7 -1
  18. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  19. package/dist/pipelines/aiLabChat.js +145 -67
  20. package/dist/pipelines/aiLabChat.js.map +1 -1
  21. package/dist/pipelines/aiLabChatContract.d.ts +34 -195
  22. package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
  23. package/dist/pipelines/aiLabChatContract.js +9 -19
  24. package/dist/pipelines/aiLabChatContract.js.map +1 -1
  25. package/dist/pipelines/gradeWorksheet.d.ts +6 -6
  26. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  27. package/dist/pipelines/gradeWorksheet.js +75 -36
  28. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  29. package/dist/pipelines/labChatPrompt.d.ts +8 -3
  30. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  31. package/dist/pipelines/labChatPrompt.js +49 -14
  32. package/dist/pipelines/labChatPrompt.js.map +1 -1
  33. package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
  34. package/dist/routers/_app.d.ts +524 -516
  35. package/dist/routers/_app.d.ts.map +1 -1
  36. package/dist/routers/announcement.d.ts +40 -40
  37. package/dist/routers/assignment.d.ts +121 -121
  38. package/dist/routers/class.d.ts +8 -8
  39. package/dist/routers/comment.d.ts +5 -5
  40. package/dist/routers/conversation.d.ts +6 -6
  41. package/dist/routers/discussion.d.ts +3 -3
  42. package/dist/routers/embedding.d.ts +6 -2
  43. package/dist/routers/embedding.d.ts.map +1 -1
  44. package/dist/routers/embedding.js +44 -36
  45. package/dist/routers/embedding.js.map +1 -1
  46. package/dist/routers/event.d.ts +18 -18
  47. package/dist/routers/file.d.ts +2 -2
  48. package/dist/routers/labChat.d.ts +3 -3
  49. package/dist/routers/notifications.d.ts +4 -4
  50. package/dist/routers/search.d.ts +2 -2
  51. package/dist/routers/worksheet.d.ts +44 -44
  52. package/dist/routes/aiStream.d.ts.map +1 -1
  53. package/dist/routes/aiStream.js +41 -11
  54. package/dist/routes/aiStream.js.map +1 -1
  55. package/dist/seedDatabase.d.ts +1 -1
  56. package/dist/services/aiChatStream.d.ts +18 -0
  57. package/dist/services/aiChatStream.d.ts.map +1 -1
  58. package/dist/services/aiChatStream.js +81 -11
  59. package/dist/services/aiChatStream.js.map +1 -1
  60. package/dist/services/aiModifierTools.d.ts +0 -16
  61. package/dist/services/aiModifierTools.d.ts.map +1 -1
  62. package/dist/services/aiModifierTools.js +112 -3
  63. package/dist/services/aiModifierTools.js.map +1 -1
  64. package/dist/services/aiProposalTools.d.ts.map +1 -1
  65. package/dist/services/aiProposalTools.js +25 -25
  66. package/dist/services/aiProposalTools.js.map +1 -1
  67. package/dist/services/announcement.d.ts +34 -34
  68. package/dist/services/assignment.d.ts +111 -111
  69. package/dist/services/assignment.d.ts.map +1 -1
  70. package/dist/services/assignment.js +4 -2
  71. package/dist/services/assignment.js.map +1 -1
  72. package/dist/services/class.d.ts +6 -6
  73. package/dist/services/class.d.ts.map +1 -1
  74. package/dist/services/class.js +7 -3
  75. package/dist/services/class.js.map +1 -1
  76. package/dist/services/comment.d.ts +5 -5
  77. package/dist/services/conversation.d.ts +6 -6
  78. package/dist/services/discussion.d.ts.map +1 -1
  79. package/dist/services/discussion.js +9 -2
  80. package/dist/services/discussion.js.map +1 -1
  81. package/dist/services/embedding.d.ts +19 -1
  82. package/dist/services/embedding.d.ts.map +1 -1
  83. package/dist/services/embedding.js +154 -8
  84. package/dist/services/embedding.js.map +1 -1
  85. package/dist/services/event.d.ts +18 -18
  86. package/dist/services/file.d.ts +4 -4
  87. package/dist/services/folder.d.ts.map +1 -1
  88. package/dist/services/folder.js +32 -2
  89. package/dist/services/folder.js.map +1 -1
  90. package/dist/services/labChat.d.ts +3 -3
  91. package/dist/services/notification.d.ts +4 -4
  92. package/dist/services/openaiDocumentGen.d.ts +25 -0
  93. package/dist/services/openaiDocumentGen.d.ts.map +1 -0
  94. package/dist/services/openaiDocumentGen.js +195 -0
  95. package/dist/services/openaiDocumentGen.js.map +1 -0
  96. package/dist/services/search.d.ts +1 -1
  97. package/dist/services/search.d.ts.map +1 -1
  98. package/dist/services/search.js +27 -2
  99. package/dist/services/search.js.map +1 -1
  100. package/dist/services/worksheet.d.ts +66 -49
  101. package/dist/services/worksheet.d.ts.map +1 -1
  102. package/dist/services/worksheet.js +48 -3
  103. package/dist/services/worksheet.js.map +1 -1
  104. package/dist/workers/processors/embedContent.d.ts.map +1 -1
  105. package/dist/workers/processors/embedContent.js +16 -25
  106. package/dist/workers/processors/embedContent.js.map +1 -1
  107. package/package.json +1 -1
  108. package/prisma/migrations/20260610140000_expand_embedding_source_types/migration.sql +4 -0
  109. package/prisma/schema.prisma +3 -0
  110. package/src/lib/inferDocumentFormat.ts +58 -0
  111. package/src/pipelines/aiLabChat.ts +185 -74
  112. package/src/pipelines/aiLabChatContract.ts +7 -17
  113. package/src/pipelines/gradeWorksheet.ts +95 -38
  114. package/src/pipelines/labChatPrompt.ts +61 -14
  115. package/src/routers/embedding.ts +44 -39
  116. package/src/routes/aiStream.ts +47 -9
  117. package/src/services/aiChatStream.ts +103 -8
  118. package/src/services/aiModifierTools.ts +154 -1
  119. package/src/services/aiProposalTools.ts +26 -25
  120. package/src/services/assignment.ts +3 -0
  121. package/src/services/class.ts +7 -1
  122. package/src/services/discussion.ts +13 -0
  123. package/src/services/embedding.ts +167 -6
  124. package/src/services/folder.ts +31 -0
  125. package/src/services/openaiDocumentGen.ts +237 -0
  126. package/src/services/search.ts +25 -1
  127. package/src/services/worksheet.ts +68 -1
  128. package/src/workers/processors/embedContent.ts +18 -22
  129. package/tests/unit/lib/inferDocumentFormat.test.ts +33 -0
@@ -1 +1 @@
1
- {"version":3,"file":"embedding.js","sources":["services/embedding.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Embedding service – pgvector-backed RAG for class materials.\n * Handles text chunking, embedding generation, storage, and semantic search.\n */\nimport { prisma } from '../lib/prisma.js';\nimport { openAIClient } from '../utils/inference.js';\nimport { logger } from '../utils/logger.js';\nimport { enqueueRunJob } from '../workers/queues.js';\nimport type { EmbeddingSourceType } from '@prisma/client';\n\nconst EMBEDDING_MODEL = 'text-embedding-3-small';\nconst EMBEDDING_DIMENSIONS = 1536;\nconst CHUNK_SIZE = 512; // tokens (~2048 chars)\nconst CHUNK_OVERLAP = 100; // tokens (~400 chars)\nconst CHARS_PER_TOKEN = 4;\n\nexport interface EmbeddingSearchResult {\n id: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n content: string;\n metadata: Record<string, unknown>;\n similarity: number;\n}\n\n/**\n * Split text into overlapping chunks for embedding.\n */\nexport function chunkText(text: string, chunkSize = CHUNK_SIZE, overlap = CHUNK_OVERLAP): string[] {\n const maxChars = chunkSize * CHARS_PER_TOKEN;\n const overlapChars = overlap * CHARS_PER_TOKEN;\n\n if (text.length <= maxChars) {\n return [text];\n }\n\n const chunks: string[] = [];\n let start = 0;\n\n while (start < text.length) {\n const end = Math.min(start + maxChars, text.length);\n chunks.push(text.slice(start, end));\n\n if (end >= text.length) break;\n start = end - overlapChars;\n }\n\n return chunks;\n}\n\n/**\n * Generate embedding vector for a text string.\n */\nexport async function generateEmbedding(text: string): Promise<number[]> {\n const response = await openAIClient.embeddings.create({\n model: EMBEDDING_MODEL,\n input: text,\n dimensions: EMBEDDING_DIMENSIONS,\n });\n\n return response.data[0].embedding;\n}\n\n/**\n * Generate embeddings for multiple texts in a single batch.\n */\nexport async function generateEmbeddings(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n\n const response = await openAIClient.embeddings.create({\n model: EMBEDDING_MODEL,\n input: texts,\n dimensions: EMBEDDING_DIMENSIONS,\n });\n\n return response.data\n .sort((a, b) => a.index - b.index)\n .map((item) => item.embedding);\n}\n\n/**\n * Store embeddings for a source document. Replaces any existing embeddings for\n * the same sourceType + sourceId combination.\n */\nexport async function indexContent(params: {\n classId: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n text: string;\n metadata?: Record<string, unknown>;\n}): Promise<number> {\n const { classId, sourceType, sourceId, text, metadata = {} } = params;\n\n // Remove existing embeddings for this source\n await prisma.embedding.deleteMany({\n where: { classId, sourceType, sourceId },\n });\n\n const chunks = chunkText(text);\n if (chunks.length === 0) return 0;\n\n const embeddings = await generateEmbeddings(chunks);\n\n // Insert using raw SQL for pgvector support\n for (let i = 0; i < chunks.length; i++) {\n const vectorStr = `[${embeddings[i].join(',')}]`;\n const chunkMetadata = { ...metadata, chunkIndex: i, totalChunks: chunks.length };\n\n await prisma.$executeRaw`\n INSERT INTO \"Embedding\" (id, \"classId\", \"sourceType\", \"sourceId\", content, vector, metadata, \"createdAt\")\n VALUES (\n gen_random_uuid(),\n ${classId},\n ${sourceType}::\"EmbeddingSourceType\",\n ${sourceId},\n ${chunks[i]},\n ${vectorStr}::vector,\n ${JSON.stringify(chunkMetadata)}::jsonb,\n NOW()\n )\n `;\n }\n\n logger.info('Content indexed', { classId, sourceType, sourceId, chunks: chunks.length });\n return chunks.length;\n}\n\n/**\n * Semantic search over class embeddings using cosine similarity.\n */\nexport async function searchEmbeddings(params: {\n classId: string;\n query: string;\n limit?: number;\n sourceTypes?: EmbeddingSourceType[];\n}): Promise<EmbeddingSearchResult[]> {\n const { classId, query, limit = 5, sourceTypes } = params;\n\n const queryEmbedding = await generateEmbedding(query);\n const vectorStr = `[${queryEmbedding.join(',')}]`;\n\n let results: EmbeddingSearchResult[];\n\n if (sourceTypes && sourceTypes.length > 0) {\n results = await prisma.$queryRaw<EmbeddingSearchResult[]>`\n SELECT\n id,\n \"sourceType\",\n \"sourceId\",\n content,\n metadata,\n 1 - (vector <=> ${vectorStr}::vector) as similarity\n FROM \"Embedding\"\n WHERE \"classId\" = ${classId}\n AND \"sourceType\" = ANY(${sourceTypes}::\"EmbeddingSourceType\"[])\n ORDER BY vector <=> ${vectorStr}::vector\n LIMIT ${limit}\n `;\n } else {\n results = await prisma.$queryRaw<EmbeddingSearchResult[]>`\n SELECT\n id,\n \"sourceType\",\n \"sourceId\",\n content,\n metadata,\n 1 - (vector <=> ${vectorStr}::vector) as similarity\n FROM \"Embedding\"\n WHERE \"classId\" = ${classId}\n ORDER BY vector <=> ${vectorStr}::vector\n LIMIT ${limit}\n `;\n }\n\n return results;\n}\n\n/**\n * Remove all embeddings for a given source.\n */\nexport async function removeEmbeddings(params: {\n classId: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n}): Promise<void> {\n await prisma.embedding.deleteMany({\n where: {\n classId: params.classId,\n sourceType: params.sourceType,\n sourceId: params.sourceId,\n },\n });\n}\n\n/**\n * Index an assignment (title + instructions) into the vector store.\n */\nexport async function indexAssignment(classId: string, assignmentId: string): Promise<void> {\n const assignment = await prisma.assignment.findUnique({\n where: { id: assignmentId },\n select: { title: true, instructions: true, type: true },\n });\n\n if (!assignment) return;\n\n const text = [\n `Title: ${assignment.title}`,\n `Type: ${assignment.type}`,\n assignment.instructions ? `Instructions: ${assignment.instructions}` : '',\n ].filter(Boolean).join('\\n');\n\n await indexContent({\n classId,\n sourceType: 'ASSIGNMENT',\n sourceId: assignmentId,\n text,\n metadata: { title: assignment.title, type: assignment.type },\n });\n}\n\n/**\n * Index a worksheet (questions + answers) into the vector store.\n */\nexport async function indexWorksheet(classId: string, worksheetId: string): Promise<void> {\n const worksheet = await prisma.worksheet.findUnique({\n where: { id: worksheetId },\n include: { questions: true },\n });\n\n if (!worksheet) return;\n\n const questionTexts = worksheet.questions.map((q, i) =>\n `Question ${i + 1} (${q.type}): ${q.question}\\nAnswer: ${q.answer}`\n );\n\n const text = [\n `Worksheet: ${worksheet.name}`,\n ...questionTexts,\n ].join('\\n\\n');\n\n await indexContent({\n classId,\n sourceType: 'WORKSHEET',\n sourceId: worksheetId,\n text,\n metadata: { name: worksheet.name, questionCount: worksheet.questions.length },\n });\n}\n\n/**\n * Index an announcement into the vector store.\n */\nexport async function indexAnnouncement(classId: string, announcementId: string): Promise<void> {\n const announcement = await prisma.announcement.findUnique({\n where: { id: announcementId },\n select: { remarks: true, teacherId: true },\n });\n\n if (!announcement || !announcement.remarks) return;\n\n const text = `Announcement: ${announcement.remarks}`;\n\n await indexContent({\n classId,\n sourceType: 'ANNOUNCEMENT',\n sourceId: announcementId,\n text,\n metadata: { teacherId: announcement.teacherId },\n });\n}\n\n/**\n * Fire-and-forget: enqueue a background job to embed content.\n * Safe to call from service functions after a create/update.\n */\nexport function enqueueEmbedding(\n classId: string,\n sourceType: EmbeddingSourceType,\n sourceId: string,\n): void {\n enqueueRunJob('EMBED_CONTENT', { classId, sourceType: sourceType as string, sourceId }, { classId })\n .catch((err) => {\n logger.error('Failed to enqueue embedding job', { err, classId, sourceType, sourceId });\n });\n}\n"],"names":[],"mappings":"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","debug_id":"684f6339-11e3-5a32-a920-de2f3c9797e6"}
1
+ {"version":3,"file":"embedding.js","sources":["services/embedding.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Embedding service – pgvector-backed RAG for class materials.\n * Handles text chunking, embedding generation, storage, and semantic search.\n */\nimport { prisma } from '../lib/prisma.js';\nimport { openAIClient } from '../utils/inference.js';\nimport { logger } from '../utils/logger.js';\nimport { enqueueRunJob } from '../workers/queues.js';\nimport type { EmbeddingSourceType } from '@prisma/client';\n\nconst EMBEDDING_MODEL = 'text-embedding-3-small';\nconst EMBEDDING_DIMENSIONS = 1536;\nconst CHUNK_SIZE = 512; // tokens (~2048 chars)\nconst CHUNK_OVERLAP = 100; // tokens (~400 chars)\nconst CHARS_PER_TOKEN = 4;\n\nexport interface EmbeddingSearchResult {\n id: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n content: string;\n metadata: Record<string, unknown>;\n similarity: number;\n}\n\n/**\n * Split text into overlapping chunks for embedding.\n */\nexport function chunkText(text: string, chunkSize = CHUNK_SIZE, overlap = CHUNK_OVERLAP): string[] {\n const maxChars = chunkSize * CHARS_PER_TOKEN;\n const overlapChars = overlap * CHARS_PER_TOKEN;\n\n if (text.length <= maxChars) {\n return [text];\n }\n\n const chunks: string[] = [];\n let start = 0;\n\n while (start < text.length) {\n const end = Math.min(start + maxChars, text.length);\n chunks.push(text.slice(start, end));\n\n if (end >= text.length) break;\n start = end - overlapChars;\n }\n\n return chunks;\n}\n\n/**\n * Generate embedding vector for a text string.\n */\nexport async function generateEmbedding(text: string): Promise<number[]> {\n const response = await openAIClient.embeddings.create({\n model: EMBEDDING_MODEL,\n input: text,\n dimensions: EMBEDDING_DIMENSIONS,\n });\n\n return response.data[0].embedding;\n}\n\n/**\n * Generate embeddings for multiple texts in a single batch.\n */\nexport async function generateEmbeddings(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n\n const response = await openAIClient.embeddings.create({\n model: EMBEDDING_MODEL,\n input: texts,\n dimensions: EMBEDDING_DIMENSIONS,\n });\n\n return response.data\n .sort((a, b) => a.index - b.index)\n .map((item) => item.embedding);\n}\n\n/**\n * Store embeddings for a source document. Replaces any existing embeddings for\n * the same sourceType + sourceId combination.\n */\nexport async function indexContent(params: {\n classId: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n text: string;\n metadata?: Record<string, unknown>;\n}): Promise<number> {\n const { classId, sourceType, sourceId, text, metadata = {} } = params;\n\n // Remove existing embeddings for this source\n await prisma.embedding.deleteMany({\n where: { classId, sourceType, sourceId },\n });\n\n const chunks = chunkText(text);\n if (chunks.length === 0) return 0;\n\n const embeddings = await generateEmbeddings(chunks);\n\n // Insert using raw SQL for pgvector support\n for (let i = 0; i < chunks.length; i++) {\n const vectorStr = `[${embeddings[i].join(',')}]`;\n const chunkMetadata = { ...metadata, chunkIndex: i, totalChunks: chunks.length };\n\n await prisma.$executeRaw`\n INSERT INTO \"Embedding\" (id, \"classId\", \"sourceType\", \"sourceId\", content, vector, metadata, \"createdAt\")\n VALUES (\n gen_random_uuid(),\n ${classId},\n ${sourceType}::\"EmbeddingSourceType\",\n ${sourceId},\n ${chunks[i]},\n ${vectorStr}::vector,\n ${JSON.stringify(chunkMetadata)}::jsonb,\n NOW()\n )\n `;\n }\n\n logger.info('Content indexed', { classId, sourceType, sourceId, chunks: chunks.length });\n return chunks.length;\n}\n\n/**\n * Semantic search over class embeddings using cosine similarity.\n */\nexport async function searchEmbeddings(params: {\n classId: string;\n query: string;\n limit?: number;\n sourceTypes?: EmbeddingSourceType[];\n}): Promise<EmbeddingSearchResult[]> {\n const { classId, query, limit = 5, sourceTypes } = params;\n\n const queryEmbedding = await generateEmbedding(query);\n const vectorStr = `[${queryEmbedding.join(',')}]`;\n\n let results: EmbeddingSearchResult[];\n\n if (sourceTypes && sourceTypes.length > 0) {\n results = await prisma.$queryRaw<EmbeddingSearchResult[]>`\n SELECT\n id,\n \"sourceType\",\n \"sourceId\",\n content,\n metadata,\n 1 - (vector <=> ${vectorStr}::vector) as similarity\n FROM \"Embedding\"\n WHERE \"classId\" = ${classId}\n AND \"sourceType\" = ANY(${sourceTypes}::\"EmbeddingSourceType\"[])\n ORDER BY vector <=> ${vectorStr}::vector\n LIMIT ${limit}\n `;\n } else {\n results = await prisma.$queryRaw<EmbeddingSearchResult[]>`\n SELECT\n id,\n \"sourceType\",\n \"sourceId\",\n content,\n metadata,\n 1 - (vector <=> ${vectorStr}::vector) as similarity\n FROM \"Embedding\"\n WHERE \"classId\" = ${classId}\n ORDER BY vector <=> ${vectorStr}::vector\n LIMIT ${limit}\n `;\n }\n\n return results;\n}\n\n/**\n * Remove all embeddings for a given source.\n */\nexport async function removeEmbeddings(params: {\n classId: string;\n sourceType: EmbeddingSourceType;\n sourceId: string;\n}): Promise<void> {\n await prisma.embedding.deleteMany({\n where: {\n classId: params.classId,\n sourceType: params.sourceType,\n sourceId: params.sourceId,\n },\n });\n}\n\n/**\n * Index an assignment (title + instructions + attached file names) into the vector store.\n */\nexport async function indexAssignment(classId: string, assignmentId: string): Promise<void> {\n const assignment = await prisma.assignment.findUnique({\n where: { id: assignmentId },\n select: {\n title: true,\n instructions: true,\n type: true,\n attachments: { select: { name: true, type: true } },\n },\n });\n\n if (!assignment) return;\n\n const parts = [\n `Title: ${assignment.title}`,\n `Type: ${assignment.type}`,\n assignment.instructions ? `Instructions: ${assignment.instructions}` : '',\n ];\n\n if (assignment.attachments.length > 0) {\n const fileList = assignment.attachments.map((f) => `${f.name} (${f.type})`).join(', ');\n parts.push(`Attached files: ${fileList}`);\n }\n\n await indexContent({\n classId,\n sourceType: 'ASSIGNMENT',\n sourceId: assignmentId,\n text: parts.filter(Boolean).join('\\n'),\n metadata: { title: assignment.title, type: assignment.type, fileCount: assignment.attachments.length },\n });\n}\n\n/**\n * Index a worksheet (questions + answers) into the vector store.\n */\nexport async function indexWorksheet(classId: string, worksheetId: string): Promise<void> {\n const worksheet = await prisma.worksheet.findUnique({\n where: { id: worksheetId },\n include: { questions: true },\n });\n\n if (!worksheet) return;\n\n const questionTexts = worksheet.questions.map((q, i) =>\n `Question ${i + 1} (${q.type}): ${q.question}\\nAnswer: ${q.answer}`\n );\n\n const text = [\n `Worksheet: ${worksheet.name}`,\n ...questionTexts,\n ].join('\\n\\n');\n\n await indexContent({\n classId,\n sourceType: 'WORKSHEET',\n sourceId: worksheetId,\n text,\n metadata: { name: worksheet.name, questionCount: worksheet.questions.length },\n });\n}\n\n/**\n * Index an announcement into the vector store.\n */\nexport async function indexAnnouncement(classId: string, announcementId: string): Promise<void> {\n const announcement = await prisma.announcement.findUnique({\n where: { id: announcementId },\n select: { remarks: true, teacherId: true },\n });\n\n if (!announcement || !announcement.remarks) return;\n\n const text = `Announcement: ${announcement.remarks}`;\n\n await indexContent({\n classId,\n sourceType: 'ANNOUNCEMENT',\n sourceId: announcementId,\n text,\n metadata: { teacherId: announcement.teacherId },\n });\n}\n\n/**\n * Index a discussion (title + body + replies) into the vector store.\n */\nexport async function indexDiscussion(classId: string, discussionId: string): Promise<void> {\n const discussion = await prisma.discussion.findUnique({\n where: { id: discussionId },\n select: {\n title: true,\n body: true,\n type: true,\n author: { select: { username: true, profile: { select: { displayName: true } } } },\n replies: {\n select: { body: true, author: { select: { username: true, profile: { select: { displayName: true } } } } },\n orderBy: { createdAt: 'asc' },\n take: 20,\n },\n },\n });\n\n if (!discussion) return;\n\n const authorName = discussion.author.profile?.displayName || discussion.author.username;\n const parts = [\n `Discussion: ${discussion.title}`,\n `Type: ${discussion.type}`,\n `Author: ${authorName}`,\n `Body: ${discussion.body}`,\n ];\n\n if (discussion.replies.length > 0) {\n parts.push('Replies:');\n for (const reply of discussion.replies) {\n const replyAuthor = reply.author.profile?.displayName || reply.author.username;\n parts.push(` ${replyAuthor}: ${reply.body.slice(0, 300)}`);\n }\n }\n\n await indexContent({\n classId,\n sourceType: 'DISCUSSION',\n sourceId: discussionId,\n text: parts.join('\\n'),\n metadata: { title: discussion.title, type: discussion.type, replyCount: discussion.replies.length },\n });\n}\n\n/**\n * Index a submission (grade + feedback + file attachments + extended response) into the vector store.\n */\nexport async function indexSubmission(classId: string, submissionId: string): Promise<void> {\n const submission = await prisma.submission.findUnique({\n where: { id: submissionId },\n select: {\n gradeReceived: true,\n teacherComments: true,\n extendedResponse: true,\n returned: true,\n student: { select: { username: true, profile: { select: { displayName: true } } } },\n assignment: { select: { title: true, maxGrade: true, type: true } },\n attachments: { select: { name: true, type: true } },\n annotations: { select: { name: true, type: true } },\n },\n });\n\n if (!submission) return;\n\n const studentName = submission.student.profile?.displayName || submission.student.username;\n const parts = [\n `Submission for: ${submission.assignment.title}`,\n `Student: ${studentName}`,\n `Assignment type: ${submission.assignment.type}`,\n ];\n\n if (submission.gradeReceived != null) {\n parts.push(`Grade: ${submission.gradeReceived}/${submission.assignment.maxGrade ?? '?'}`);\n }\n if (submission.teacherComments) {\n parts.push(`Teacher feedback: ${submission.teacherComments}`);\n }\n if (submission.extendedResponse) {\n parts.push(`Student response: ${submission.extendedResponse.slice(0, 500)}`);\n }\n if (submission.attachments.length > 0) {\n parts.push(`Submitted files: ${submission.attachments.map((f) => f.name).join(', ')}`);\n }\n if (submission.annotations.length > 0) {\n parts.push(`Teacher annotations: ${submission.annotations.map((f) => f.name).join(', ')}`);\n }\n\n await indexContent({\n classId,\n sourceType: 'SUBMISSION',\n sourceId: submissionId,\n text: parts.join('\\n'),\n metadata: { studentName, assignmentTitle: submission.assignment.title, grade: submission.gradeReceived },\n });\n}\n\n/**\n * Index a class syllabus into the vector store.\n * Uses classId as both classId and sourceId since syllabus is a field on the class.\n */\nexport async function indexSyllabus(classId: string): Promise<void> {\n const cls = await prisma.class.findUnique({\n where: { id: classId },\n select: { name: true, subject: true, syllabus: true },\n });\n\n if (!cls?.syllabus) return;\n\n const text = [\n `Syllabus for ${cls.name} (${cls.subject})`,\n cls.syllabus,\n ].join('\\n\\n');\n\n await indexContent({\n classId,\n sourceType: 'SYLLABUS',\n sourceId: classId,\n text,\n metadata: { className: cls.name, subject: cls.subject },\n });\n}\n\n/**\n * Index a file (name + type + path) into the vector store.\n * For text-based content, PDF extraction would be added here in the future.\n */\nexport async function indexFile(classId: string, fileId: string): Promise<void> {\n const file = await prisma.file.findUnique({\n where: { id: fileId },\n select: { name: true, path: true, type: true, size: true },\n });\n\n if (!file) return;\n\n const text = [\n `File: ${file.name}`,\n `Type: ${file.type}`,\n `Path: ${file.path}`,\n ].join('\\n');\n\n await indexContent({\n classId,\n sourceType: 'FILE',\n sourceId: fileId,\n text,\n metadata: { name: file.name, type: file.type, size: file.size },\n });\n}\n\n/**\n * Fire-and-forget: enqueue a background job to embed content.\n * Safe to call from service functions after a create/update.\n */\nexport function enqueueEmbedding(\n classId: string,\n sourceType: EmbeddingSourceType,\n sourceId: string,\n): void {\n enqueueRunJob('EMBED_CONTENT', { classId, sourceType: sourceType as string, sourceId }, { classId })\n .catch((err) => {\n logger.error('Failed to enqueue embedding job', { err, classId, sourceType, sourceId });\n });\n}\n"],"names":[],"mappings":"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","debug_id":"81da6aeb-b86a-5af7-8dc6-34ecc6523f0d"}
@@ -52,6 +52,11 @@ export declare function getEvent(userId: string, id: string): Promise<{
52
52
  title: string;
53
53
  dueDate: Date;
54
54
  maxGrade: number | null;
55
+ attachments: {
56
+ type: string;
57
+ id: string;
58
+ name: string;
59
+ }[];
55
60
  instructions: string;
56
61
  graded: boolean;
57
62
  weight: number;
@@ -59,11 +64,6 @@ export declare function getEvent(userId: string, id: string): Promise<{
59
64
  id: string;
60
65
  username: string;
61
66
  };
62
- attachments: {
63
- type: string;
64
- id: string;
65
- name: string;
66
- }[];
67
67
  }[];
68
68
  } & {
69
69
  id: string;
@@ -133,15 +133,15 @@ export declare function attachAssignment(userId: string, eventId: string, assign
133
133
  id: string;
134
134
  name: string;
135
135
  } | null;
136
- teacher: {
137
- id: string;
138
- username: string;
139
- };
140
136
  attachments: {
141
137
  type: string;
142
138
  id: string;
143
139
  name: string;
144
140
  }[];
141
+ teacher: {
142
+ id: string;
143
+ username: string;
144
+ };
145
145
  } & {
146
146
  type: import(".prisma/client").$Enums.AssignmentType;
147
147
  id: string;
@@ -175,15 +175,15 @@ export declare function detachAssignment(userId: string, eventId: string, assign
175
175
  id: string;
176
176
  name: string;
177
177
  } | null;
178
- teacher: {
179
- id: string;
180
- username: string;
181
- };
182
178
  attachments: {
183
179
  type: string;
184
180
  id: string;
185
181
  name: string;
186
182
  }[];
183
+ teacher: {
184
+ id: string;
185
+ username: string;
186
+ };
187
187
  } & {
188
188
  type: import(".prisma/client").$Enums.AssignmentType;
189
189
  id: string;
@@ -222,6 +222,11 @@ export declare function getAvailableAssignments(userId: string, eventId: string)
222
222
  title: string;
223
223
  dueDate: Date;
224
224
  maxGrade: number | null;
225
+ attachments: {
226
+ type: string;
227
+ id: string;
228
+ name: string;
229
+ }[];
225
230
  instructions: string;
226
231
  graded: boolean;
227
232
  weight: number;
@@ -229,11 +234,6 @@ export declare function getAvailableAssignments(userId: string, eventId: string)
229
234
  id: string;
230
235
  username: string;
231
236
  };
232
- attachments: {
233
- type: string;
234
- id: string;
235
- name: string;
236
- }[];
237
237
  }[];
238
238
  }>;
239
239
  //# sourceMappingURL=event.d.ts.map
@@ -36,6 +36,7 @@ export declare function moveFile(fileId: string, targetFolderId: string): Promis
36
36
  submissionId: string | null;
37
37
  thumbnailId: string | null;
38
38
  annotationId: string | null;
39
+ conversationId: string | null;
39
40
  uploadStatus: import(".prisma/client").$Enums.UploadStatus;
40
41
  uploadUrl: string | null;
41
42
  uploadExpiresAt: Date | null;
@@ -47,7 +48,6 @@ export declare function moveFile(fileId: string, targetFolderId: string): Promis
47
48
  cleanupAt: Date | null;
48
49
  classDraftId: string | null;
49
50
  folderId: string | null;
50
- conversationId: string | null;
51
51
  messageId: string | null;
52
52
  announcementId: string | null;
53
53
  discussionId: string | null;
@@ -71,6 +71,7 @@ export declare function renameFile(fileId: string, newName: string): Promise<{
71
71
  submissionId: string | null;
72
72
  thumbnailId: string | null;
73
73
  annotationId: string | null;
74
+ conversationId: string | null;
74
75
  uploadStatus: import(".prisma/client").$Enums.UploadStatus;
75
76
  uploadUrl: string | null;
76
77
  uploadExpiresAt: Date | null;
@@ -82,7 +83,6 @@ export declare function renameFile(fileId: string, newName: string): Promise<{
82
83
  cleanupAt: Date | null;
83
84
  classDraftId: string | null;
84
85
  folderId: string | null;
85
- conversationId: string | null;
86
86
  messageId: string | null;
87
87
  announcementId: string | null;
88
88
  discussionId: string | null;
@@ -116,6 +116,7 @@ export declare function moveFileScoped(userId: string, input: {
116
116
  submissionId: string | null;
117
117
  thumbnailId: string | null;
118
118
  annotationId: string | null;
119
+ conversationId: string | null;
119
120
  uploadStatus: import(".prisma/client").$Enums.UploadStatus;
120
121
  uploadUrl: string | null;
121
122
  uploadExpiresAt: Date | null;
@@ -127,7 +128,6 @@ export declare function moveFileScoped(userId: string, input: {
127
128
  cleanupAt: Date | null;
128
129
  classDraftId: string | null;
129
130
  folderId: string | null;
130
- conversationId: string | null;
131
131
  messageId: string | null;
132
132
  announcementId: string | null;
133
133
  discussionId: string | null;
@@ -155,6 +155,7 @@ export declare function renameFileScoped(userId: string, input: {
155
155
  submissionId: string | null;
156
156
  thumbnailId: string | null;
157
157
  annotationId: string | null;
158
+ conversationId: string | null;
158
159
  uploadStatus: import(".prisma/client").$Enums.UploadStatus;
159
160
  uploadUrl: string | null;
160
161
  uploadExpiresAt: Date | null;
@@ -166,7 +167,6 @@ export declare function renameFileScoped(userId: string, input: {
166
167
  cleanupAt: Date | null;
167
168
  classDraftId: string | null;
168
169
  folderId: string | null;
169
- conversationId: string | null;
170
170
  messageId: string | null;
171
171
  announcementId: string | null;
172
172
  discussionId: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"folder.d.ts","sourceRoot":"/","sources":["services/folder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"folder.d.ts","sourceRoot":"/","sources":["services/folder.ts"],"names":[],"mappings":"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"}
@@ -11,11 +11,12 @@
11
11
  * endpoints — no duplicate routers.
12
12
  */
13
13
 
14
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="338c145a-d8eb-5931-9a42-2a9c7c9acc94")}catch(e){}}();
14
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0e6d72f8-42d4-50f6-907d-bbab13f2bf3f")}catch(e){}}();
15
15
  import { TRPCError } from "@trpc/server";
16
16
  import { prisma } from "../lib/prisma.js";
17
17
  import { createSubFolder, deleteFolderById, findFolderById, findFolderWithContents, findOrCreateScopeRoot, listFolderBreadcrumbs, renameFolder, resolveFolderScope, } from "../models/cloudStorage.js";
18
18
  import { createDirectUploadFiles, confirmDirectUpload, } from "../lib/fileUpload.js";
19
+ import { enqueueEmbedding } from "./embedding.js";
19
20
  import { getScopeDefault, listSharedWithMe, requireFolderAccess as requireFolderAccessResolved, } from "./permissions.js";
20
21
  /**
21
22
  * Scope-default-only check: used when there is no folder id yet (browsing or
@@ -323,8 +324,37 @@ export async function confirmFolderUpload(_userId, input) {
323
324
  // exists with PENDING status, and the user holds the session id needed to
324
325
  // PUT bytes. Confirming flips status, so we just trust the call.
325
326
  await confirmDirectUpload(input.fileId, input.uploadSuccess, input.errorMessage);
327
+ // Index file for RAG search if it belongs to a class folder
328
+ if (input.uploadSuccess) {
329
+ const file = await prisma.file.findUnique({
330
+ where: { id: input.fileId },
331
+ select: { folderId: true },
332
+ });
333
+ if (file?.folderId) {
334
+ const classId = await resolveFileClassId(file.folderId);
335
+ if (classId) {
336
+ enqueueEmbedding(classId, 'FILE', input.fileId);
337
+ }
338
+ }
339
+ }
326
340
  return { success: true };
327
341
  }
342
+ /** Walk up folder tree to find classId, if any. */
343
+ async function resolveFileClassId(folderId) {
344
+ let currentId = folderId;
345
+ while (currentId) {
346
+ const folder = await prisma.folder.findUnique({
347
+ where: { id: currentId },
348
+ select: { classId: true, parentFolderId: true },
349
+ });
350
+ if (!folder)
351
+ return null;
352
+ if (folder.classId)
353
+ return folder.classId;
354
+ currentId = folder.parentFolderId;
355
+ }
356
+ return null;
357
+ }
328
358
  /** What scopes can this user pick from in cloud-storage UIs? */
329
359
  export async function listUserScopes(userId) {
330
360
  const memberships = await prisma.schoolMembership.findMany({
@@ -375,4 +405,4 @@ export async function listUserScopes(userId) {
375
405
  };
376
406
  }
377
407
  //# sourceMappingURL=folder.js.map
378
- //# debugId=338c145a-d8eb-5931-9a42-2a9c7c9acc94
408
+ //# debugId=0e6d72f8-42d4-50f6-907d-bbab13f2bf3f
@@ -1 +1 @@
1
- {"version":3,"file":"folder.js","sources":["services/folder.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Folder service — single source of truth for folders across every scope.\n *\n * A `Folder` can live inside one of four scopes (see `models/cloudStorage.ts`):\n * class files, school storage, school documents, or a user's personal storage.\n * Permissions and ownership are derived from the scope by walking up the tree\n * to the root, so callers can either pass a `scope` (when there is no folder\n * id yet, e.g. browsing the root) or just a `folderId` (everything else).\n *\n * The class files page and the global cloud-storage view both consume these\n * endpoints — no duplicate routers.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n CloudScope,\n createSubFolder,\n deleteFolderById,\n findFolderById,\n findFolderWithContents,\n findOrCreateScopeRoot,\n listFolderBreadcrumbs,\n renameFolder,\n resolveFolderScope,\n} from \"../models/cloudStorage.js\";\nimport {\n createDirectUploadFiles,\n type DirectFileData,\n confirmDirectUpload,\n} from \"../lib/fileUpload.js\";\nimport {\n type AccessLevel,\n getScopeDefault,\n listSharedWithMe,\n requireFolderAccess as requireFolderAccessResolved,\n} from \"./permissions.js\";\n\ninterface AccessResult {\n scope: CloudScope;\n canWrite: boolean;\n /**\n * True when the viewer's access comes through an explicit grant rather than\n * scope membership. Used by `browseFolder` to surface a \"Shared with me\"\n * heading instead of the underlying scope's title — the grantee shouldn't\n * see the folder labelled with the owner's scope.\n */\n viaGrant: boolean;\n}\n\n/**\n * Scope-default-only check: used when there is no folder id yet (browsing or\n * creating at the scope root). Does *not* consult explicit grants — those only\n * apply to specific folders. Explicit-grant access to sub-folders surfaces via\n * the \"Shared with me\" virtual scope, not by being a scope member.\n */\nasync function requireScopeAccess(\n userId: string,\n scope: CloudScope,\n level: AccessLevel\n): Promise<AccessResult> {\n if (scope.kind === \"CLASS\") {\n const classExists = await prisma.class.findFirst({\n where: { id: scope.classId },\n select: { id: true },\n });\n if (!classExists) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n }\n const granted = await getScopeDefault(userId, scope);\n if (!granted) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n scope.kind === \"CLASS\"\n ? \"You don't have access to this class\"\n : scope.kind === \"SCHOOL_STORAGE\"\n ? \"You aren't a member of this institution\"\n : \"You can only access your own personal storage\",\n });\n }\n if (level === \"WRITE\" && granted !== \"WRITE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n scope.kind === \"CLASS\"\n ? \"Only class teachers can modify class files\"\n : \"Only teachers and admins can modify institution storage\",\n });\n }\n // Scope-default-only path; access cannot be via a grant here.\n return { scope, canWrite: granted === \"WRITE\", viaGrant: false };\n}\n\n/**\n * Folder-level access check. Delegates to the central resolver which consults\n * explicit grants in addition to the scope default. Returns the scope and a\n * canWrite flag indicating whether the user has WRITE here (used by the UI to\n * gate the \"New folder\" / \"Upload\" buttons during a READ-level browse).\n *\n * Pass `mutatesSelf: true` for actions that target the folder itself (rename,\n * delete, move-this-folder). Drive-style: explicit grants don't authorize\n * those — only scope-default WRITE does. Mutations of children should NOT\n * pass this flag.\n */\nexport async function requireFolderAccess(\n userId: string,\n folderId: string,\n level: AccessLevel,\n opts: { mutatesSelf?: boolean } = {}\n): Promise<AccessResult> {\n const result = await requireFolderAccessResolved(userId, folderId, level, opts);\n if (!result.scope) {\n // resolveFolderScope returns null only for missing folders; the resolver\n // would have already thrown NOT_FOUND in that case.\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n let canWrite = result.scopeDefault === \"WRITE\";\n if (!canWrite && level !== \"WRITE\") {\n // We came in with READ; check whether the user *also* has WRITE so the UI\n // can show the right affordances. Cheaper to do this only when needed.\n try {\n await requireFolderAccessResolved(userId, folderId, \"WRITE\");\n canWrite = true;\n } catch {\n canWrite = false;\n }\n } else if (level === \"WRITE\") {\n canWrite = true;\n }\n return { scope: result.scope, canWrite, viaGrant: result.viaGrant };\n}\n\nfunction scopesEqual(a: CloudScope, b: CloudScope): boolean {\n if (a.kind !== b.kind) return false;\n switch (a.kind) {\n case \"CLASS\":\n return a.classId === (b as typeof a).classId;\n case \"SCHOOL_STORAGE\":\n return a.schoolId === (b as typeof a).schoolId;\n case \"PERSONAL\":\n return a.userId === (b as typeof a).userId;\n }\n}\n\n/** Browse a folder. Pass `folderId` to drill into a specific folder, or a `scope` to land on its root. */\nexport async function browseFolder(\n userId: string,\n input: { folderId: string } | { scope: CloudScope }\n) {\n let scope: CloudScope;\n let folderId: string | undefined;\n let canWrite: boolean;\n let viaGrant: boolean;\n\n if (\"folderId\" in input) {\n folderId = input.folderId;\n // `requireFolderAccess` consults explicit grants in addition to the scope\n // default, so users can drill into folders shared with them via \"Shared\n // with me\" without being scope members.\n const access = await requireFolderAccess(userId, folderId, \"READ\");\n scope = access.scope;\n canWrite = access.canWrite;\n viaGrant = access.viaGrant;\n } else {\n scope = input.scope;\n const access = await requireScopeAccess(userId, scope, \"READ\");\n canWrite = access.canWrite;\n viaGrant = false;\n }\n\n const targetId = folderId ?? (await findOrCreateScopeRoot(scope)).id;\n\n const [folder, breadcrumbs] = await Promise.all([\n findFolderWithContents(targetId),\n listFolderBreadcrumbs(targetId, userId),\n ]);\n\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n\n return {\n folder,\n breadcrumbs,\n canWrite,\n scope,\n /**\n * When the viewer's access comes through an explicit grant (not scope\n * membership), the UI should label the root as \"Shared with me\" rather\n * than the underlying scope's title. The grantee may not even be a\n * member of that scope — showing \"My Files\" or \"Class Files\" would be\n * misleading. Null when the viewer is a regular scope member.\n */\n displayRoot: viaGrant\n ? { kind: \"SHARED_WITH_ME\" as const, name: \"Shared with me\" }\n : null,\n };\n}\n\n/**\n * Drive-style \"Shared with me\" — surfaces every top-most folder/file the user\n * has an explicit grant on (filtering out anything already covered by their\n * scope defaults). Shaped like a folder so the existing folder-browser UI can\n * render it without special cases.\n *\n * The pseudo-folder has a synthetic id (`shared-with-me`) and the foreign-key\n * columns set to null — the UI should never try to mutate it directly. The\n * union shape matches `browseFolder`'s return type so the same client query\n * can render either.\n */\nexport async function browseSharedWithMe(userId: string) {\n const { folders } = await listSharedWithMe(userId);\n return {\n folder: {\n id: \"shared-with-me\",\n name: \"Shared with me\",\n color: null,\n classId: null,\n schoolStorageId: null,\n personalOwnerId: null,\n scope: \"GENERIC\" as const,\n parentFolderId: null,\n parentFolder: null,\n childFolders: folders.map((f) => ({\n id: f.id,\n name: f.name,\n color: f.color,\n _count: { files: 0, childFolders: 0 },\n })),\n // No file-level grants in v1 — files always live under a granted folder.\n files: [] as {\n id: string;\n name: string;\n type: string;\n size: number | null;\n path: string;\n uploadedAt: Date | null;\n user: { id: string; username: string } | null;\n }[],\n },\n breadcrumbs: [{ id: \"shared-with-me\", name: \"Shared with me\" }],\n canWrite: false,\n scope: { kind: \"PERSONAL\" as const, userId } satisfies CloudScope,\n /** Same `displayRoot` shape as `browseFolder` so the client union is uniform. */\n displayRoot: {\n kind: \"SHARED_WITH_ME\" as const,\n name: \"Shared with me\",\n },\n };\n}\n\nexport async function getRootFolder(userId: string, scope: CloudScope) {\n const result = await browseFolder(userId, { scope });\n return result.folder;\n}\n\nexport async function getFolder(userId: string, folderId: string) {\n const result = await browseFolder(userId, { folderId });\n return result.folder;\n}\n\nexport async function getFolderBreadcrumbs(userId: string, folderId: string) {\n await requireFolderAccess(userId, folderId, \"READ\");\n return listFolderBreadcrumbs(folderId);\n}\n\nexport async function getFolderParents(userId: string, folderId: string) {\n // Permission: user can read this folder.\n await requireFolderAccess(userId, folderId, \"READ\");\n const breadcrumbs = await listFolderBreadcrumbs(folderId);\n // Existing API returned full folder records — re-fetch them.\n return Promise.all(\n breadcrumbs.map((b) => findFolderById(b.id))\n );\n}\n\nexport async function createFolderRecord(\n userId: string,\n input: {\n name: string;\n parentFolderId?: string | null;\n color?: string;\n scope?: CloudScope;\n }\n) {\n let parentId: string;\n if (input.parentFolderId) {\n parentId = input.parentFolderId;\n await requireFolderAccess(userId, parentId, \"WRITE\");\n } else {\n if (!input.scope) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"scope is required when parentFolderId is omitted\",\n });\n }\n await requireScopeAccess(userId, input.scope, \"WRITE\");\n parentId = (await findOrCreateScopeRoot(input.scope)).id;\n }\n return createSubFolder({\n name: input.name.trim(),\n parentFolderId: parentId,\n color: input.color,\n });\n}\n\nexport async function updateFolderRecord(\n userId: string,\n input: { folderId: string; name?: string; color?: string }\n) {\n const folder = await findFolderById(input.folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot rename a root folder\",\n });\n }\n // Rename targets the folder itself — Drive-style guard reserves this for\n // scope-default WRITE (explicit grants don't authorize renaming the share).\n await requireFolderAccess(userId, input.folderId, \"WRITE\", {\n mutatesSelf: true,\n });\n return renameFolder(\n input.folderId,\n input.name?.trim() ?? folder.name,\n input.color\n );\n}\n\nexport async function deleteFolder(userId: string, folderId: string) {\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot delete a root folder\",\n });\n }\n // Delete targets the folder itself — same Drive-style guard as rename.\n await requireFolderAccess(userId, folderId, \"WRITE\", { mutatesSelf: true });\n await deleteFolderById(folderId);\n return { success: true };\n}\n\nexport async function moveFolder(\n userId: string,\n input: { folderId: string; targetParentFolderId: string }\n) {\n if (input.folderId === input.targetParentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into itself\",\n });\n }\n const [sourceScope, targetScope] = await Promise.all([\n resolveFolderScope(input.folderId),\n resolveFolderScope(input.targetParentFolderId),\n ]);\n if (!sourceScope || !targetScope) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!scopesEqual(sourceScope, targetScope)) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move folders between scopes\",\n });\n }\n // Moving the folder targets it (and the new parent's contents). Source-side\n // is mutates-self; target-side is regular WRITE on the new parent.\n await requireFolderAccess(userId, input.folderId, \"WRITE\", {\n mutatesSelf: true,\n });\n await requireFolderAccess(userId, input.targetParentFolderId, \"WRITE\");\n\n // Don't let a folder land under one of its own descendants.\n let cursor: string | null = input.targetParentFolderId;\n while (cursor) {\n if (cursor === input.folderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into its own descendant\",\n });\n }\n const node = await prisma.folder.findUnique({\n where: { id: cursor },\n select: { parentFolderId: true },\n });\n cursor = node?.parentFolderId ?? null;\n }\n\n await prisma.folder.update({\n where: { id: input.folderId },\n data: { parentFolderId: input.targetParentFolderId },\n });\n return { success: true };\n}\n\nexport async function getFolderUploadUrls(\n userId: string,\n input: { folderId: string; files: DirectFileData[] }\n) {\n await requireFolderAccess(userId, input.folderId, \"WRITE\");\n return createDirectUploadFiles(input.files, userId, input.folderId);\n}\n\nexport async function confirmFolderUpload(\n _userId: string,\n input: { fileId: string; uploadSuccess: boolean; errorMessage?: string }\n) {\n // Permission was already checked at upload-url issuance — the file record\n // exists with PENDING status, and the user holds the session id needed to\n // PUT bytes. Confirming flips status, so we just trust the call.\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n return { success: true };\n}\n\n/** What scopes can this user pick from in cloud-storage UIs? */\nexport async function listUserScopes(userId: string) {\n const memberships = await prisma.schoolMembership.findMany({\n where: { userId, status: \"ACTIVE\" },\n select: {\n role: true,\n school: {\n select: { id: true, name: true, logoId: true },\n },\n },\n orderBy: { createdAt: \"asc\" },\n });\n\n const classes = await prisma.class.findMany({\n where: {\n OR: [\n { teachers: { some: { id: userId } } },\n { students: { some: { id: userId } } },\n ],\n },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n schoolId: true,\n },\n orderBy: { name: \"asc\" },\n });\n\n // Light counts for the \"Shared with me\" bucket so the UI can hide the entry\n // when there's nothing to show. Cheap to compute — just counts top-most\n // grants, not folder/file metadata.\n const sharedWithMe = await listSharedWithMe(userId);\n\n return {\n personal: { kind: \"PERSONAL\" as const, userId },\n schools: memberships.map((m) => ({\n id: m.school.id,\n name: m.school.name,\n logoId: m.school.logoId,\n role: m.role,\n })),\n classes,\n sharedWithMe: {\n kind: \"SHARED_WITH_ME\" as const,\n folderCount: sharedWithMe.folders.length,\n fileCount: sharedWithMe.files.length,\n },\n };\n}\n"],"names":[],"mappings":"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","debug_id":"338c145a-d8eb-5931-9a42-2a9c7c9acc94"}
1
+ {"version":3,"file":"folder.js","sources":["services/folder.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Folder service — single source of truth for folders across every scope.\n *\n * A `Folder` can live inside one of four scopes (see `models/cloudStorage.ts`):\n * class files, school storage, school documents, or a user's personal storage.\n * Permissions and ownership are derived from the scope by walking up the tree\n * to the root, so callers can either pass a `scope` (when there is no folder\n * id yet, e.g. browsing the root) or just a `folderId` (everything else).\n *\n * The class files page and the global cloud-storage view both consume these\n * endpoints — no duplicate routers.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n CloudScope,\n createSubFolder,\n deleteFolderById,\n findFolderById,\n findFolderWithContents,\n findOrCreateScopeRoot,\n listFolderBreadcrumbs,\n renameFolder,\n resolveFolderScope,\n} from \"../models/cloudStorage.js\";\nimport {\n createDirectUploadFiles,\n type DirectFileData,\n confirmDirectUpload,\n} from \"../lib/fileUpload.js\";\nimport { enqueueEmbedding } from \"./embedding.js\";\nimport {\n type AccessLevel,\n getScopeDefault,\n listSharedWithMe,\n requireFolderAccess as requireFolderAccessResolved,\n} from \"./permissions.js\";\n\ninterface AccessResult {\n scope: CloudScope;\n canWrite: boolean;\n /**\n * True when the viewer's access comes through an explicit grant rather than\n * scope membership. Used by `browseFolder` to surface a \"Shared with me\"\n * heading instead of the underlying scope's title — the grantee shouldn't\n * see the folder labelled with the owner's scope.\n */\n viaGrant: boolean;\n}\n\n/**\n * Scope-default-only check: used when there is no folder id yet (browsing or\n * creating at the scope root). Does *not* consult explicit grants — those only\n * apply to specific folders. Explicit-grant access to sub-folders surfaces via\n * the \"Shared with me\" virtual scope, not by being a scope member.\n */\nasync function requireScopeAccess(\n userId: string,\n scope: CloudScope,\n level: AccessLevel\n): Promise<AccessResult> {\n if (scope.kind === \"CLASS\") {\n const classExists = await prisma.class.findFirst({\n where: { id: scope.classId },\n select: { id: true },\n });\n if (!classExists) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Class not found\" });\n }\n }\n const granted = await getScopeDefault(userId, scope);\n if (!granted) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n scope.kind === \"CLASS\"\n ? \"You don't have access to this class\"\n : scope.kind === \"SCHOOL_STORAGE\"\n ? \"You aren't a member of this institution\"\n : \"You can only access your own personal storage\",\n });\n }\n if (level === \"WRITE\" && granted !== \"WRITE\") {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n scope.kind === \"CLASS\"\n ? \"Only class teachers can modify class files\"\n : \"Only teachers and admins can modify institution storage\",\n });\n }\n // Scope-default-only path; access cannot be via a grant here.\n return { scope, canWrite: granted === \"WRITE\", viaGrant: false };\n}\n\n/**\n * Folder-level access check. Delegates to the central resolver which consults\n * explicit grants in addition to the scope default. Returns the scope and a\n * canWrite flag indicating whether the user has WRITE here (used by the UI to\n * gate the \"New folder\" / \"Upload\" buttons during a READ-level browse).\n *\n * Pass `mutatesSelf: true` for actions that target the folder itself (rename,\n * delete, move-this-folder). Drive-style: explicit grants don't authorize\n * those — only scope-default WRITE does. Mutations of children should NOT\n * pass this flag.\n */\nexport async function requireFolderAccess(\n userId: string,\n folderId: string,\n level: AccessLevel,\n opts: { mutatesSelf?: boolean } = {}\n): Promise<AccessResult> {\n const result = await requireFolderAccessResolved(userId, folderId, level, opts);\n if (!result.scope) {\n // resolveFolderScope returns null only for missing folders; the resolver\n // would have already thrown NOT_FOUND in that case.\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n let canWrite = result.scopeDefault === \"WRITE\";\n if (!canWrite && level !== \"WRITE\") {\n // We came in with READ; check whether the user *also* has WRITE so the UI\n // can show the right affordances. Cheaper to do this only when needed.\n try {\n await requireFolderAccessResolved(userId, folderId, \"WRITE\");\n canWrite = true;\n } catch {\n canWrite = false;\n }\n } else if (level === \"WRITE\") {\n canWrite = true;\n }\n return { scope: result.scope, canWrite, viaGrant: result.viaGrant };\n}\n\nfunction scopesEqual(a: CloudScope, b: CloudScope): boolean {\n if (a.kind !== b.kind) return false;\n switch (a.kind) {\n case \"CLASS\":\n return a.classId === (b as typeof a).classId;\n case \"SCHOOL_STORAGE\":\n return a.schoolId === (b as typeof a).schoolId;\n case \"PERSONAL\":\n return a.userId === (b as typeof a).userId;\n }\n}\n\n/** Browse a folder. Pass `folderId` to drill into a specific folder, or a `scope` to land on its root. */\nexport async function browseFolder(\n userId: string,\n input: { folderId: string } | { scope: CloudScope }\n) {\n let scope: CloudScope;\n let folderId: string | undefined;\n let canWrite: boolean;\n let viaGrant: boolean;\n\n if (\"folderId\" in input) {\n folderId = input.folderId;\n // `requireFolderAccess` consults explicit grants in addition to the scope\n // default, so users can drill into folders shared with them via \"Shared\n // with me\" without being scope members.\n const access = await requireFolderAccess(userId, folderId, \"READ\");\n scope = access.scope;\n canWrite = access.canWrite;\n viaGrant = access.viaGrant;\n } else {\n scope = input.scope;\n const access = await requireScopeAccess(userId, scope, \"READ\");\n canWrite = access.canWrite;\n viaGrant = false;\n }\n\n const targetId = folderId ?? (await findOrCreateScopeRoot(scope)).id;\n\n const [folder, breadcrumbs] = await Promise.all([\n findFolderWithContents(targetId),\n listFolderBreadcrumbs(targetId, userId),\n ]);\n\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n\n return {\n folder,\n breadcrumbs,\n canWrite,\n scope,\n /**\n * When the viewer's access comes through an explicit grant (not scope\n * membership), the UI should label the root as \"Shared with me\" rather\n * than the underlying scope's title. The grantee may not even be a\n * member of that scope — showing \"My Files\" or \"Class Files\" would be\n * misleading. Null when the viewer is a regular scope member.\n */\n displayRoot: viaGrant\n ? { kind: \"SHARED_WITH_ME\" as const, name: \"Shared with me\" }\n : null,\n };\n}\n\n/**\n * Drive-style \"Shared with me\" — surfaces every top-most folder/file the user\n * has an explicit grant on (filtering out anything already covered by their\n * scope defaults). Shaped like a folder so the existing folder-browser UI can\n * render it without special cases.\n *\n * The pseudo-folder has a synthetic id (`shared-with-me`) and the foreign-key\n * columns set to null — the UI should never try to mutate it directly. The\n * union shape matches `browseFolder`'s return type so the same client query\n * can render either.\n */\nexport async function browseSharedWithMe(userId: string) {\n const { folders } = await listSharedWithMe(userId);\n return {\n folder: {\n id: \"shared-with-me\",\n name: \"Shared with me\",\n color: null,\n classId: null,\n schoolStorageId: null,\n personalOwnerId: null,\n scope: \"GENERIC\" as const,\n parentFolderId: null,\n parentFolder: null,\n childFolders: folders.map((f) => ({\n id: f.id,\n name: f.name,\n color: f.color,\n _count: { files: 0, childFolders: 0 },\n })),\n // No file-level grants in v1 — files always live under a granted folder.\n files: [] as {\n id: string;\n name: string;\n type: string;\n size: number | null;\n path: string;\n uploadedAt: Date | null;\n user: { id: string; username: string } | null;\n }[],\n },\n breadcrumbs: [{ id: \"shared-with-me\", name: \"Shared with me\" }],\n canWrite: false,\n scope: { kind: \"PERSONAL\" as const, userId } satisfies CloudScope,\n /** Same `displayRoot` shape as `browseFolder` so the client union is uniform. */\n displayRoot: {\n kind: \"SHARED_WITH_ME\" as const,\n name: \"Shared with me\",\n },\n };\n}\n\nexport async function getRootFolder(userId: string, scope: CloudScope) {\n const result = await browseFolder(userId, { scope });\n return result.folder;\n}\n\nexport async function getFolder(userId: string, folderId: string) {\n const result = await browseFolder(userId, { folderId });\n return result.folder;\n}\n\nexport async function getFolderBreadcrumbs(userId: string, folderId: string) {\n await requireFolderAccess(userId, folderId, \"READ\");\n return listFolderBreadcrumbs(folderId);\n}\n\nexport async function getFolderParents(userId: string, folderId: string) {\n // Permission: user can read this folder.\n await requireFolderAccess(userId, folderId, \"READ\");\n const breadcrumbs = await listFolderBreadcrumbs(folderId);\n // Existing API returned full folder records — re-fetch them.\n return Promise.all(\n breadcrumbs.map((b) => findFolderById(b.id))\n );\n}\n\nexport async function createFolderRecord(\n userId: string,\n input: {\n name: string;\n parentFolderId?: string | null;\n color?: string;\n scope?: CloudScope;\n }\n) {\n let parentId: string;\n if (input.parentFolderId) {\n parentId = input.parentFolderId;\n await requireFolderAccess(userId, parentId, \"WRITE\");\n } else {\n if (!input.scope) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"scope is required when parentFolderId is omitted\",\n });\n }\n await requireScopeAccess(userId, input.scope, \"WRITE\");\n parentId = (await findOrCreateScopeRoot(input.scope)).id;\n }\n return createSubFolder({\n name: input.name.trim(),\n parentFolderId: parentId,\n color: input.color,\n });\n}\n\nexport async function updateFolderRecord(\n userId: string,\n input: { folderId: string; name?: string; color?: string }\n) {\n const folder = await findFolderById(input.folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot rename a root folder\",\n });\n }\n // Rename targets the folder itself — Drive-style guard reserves this for\n // scope-default WRITE (explicit grants don't authorize renaming the share).\n await requireFolderAccess(userId, input.folderId, \"WRITE\", {\n mutatesSelf: true,\n });\n return renameFolder(\n input.folderId,\n input.name?.trim() ?? folder.name,\n input.color\n );\n}\n\nexport async function deleteFolder(userId: string, folderId: string) {\n const folder = await findFolderById(folderId);\n if (!folder) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!folder.parentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot delete a root folder\",\n });\n }\n // Delete targets the folder itself — same Drive-style guard as rename.\n await requireFolderAccess(userId, folderId, \"WRITE\", { mutatesSelf: true });\n await deleteFolderById(folderId);\n return { success: true };\n}\n\nexport async function moveFolder(\n userId: string,\n input: { folderId: string; targetParentFolderId: string }\n) {\n if (input.folderId === input.targetParentFolderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into itself\",\n });\n }\n const [sourceScope, targetScope] = await Promise.all([\n resolveFolderScope(input.folderId),\n resolveFolderScope(input.targetParentFolderId),\n ]);\n if (!sourceScope || !targetScope) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Folder not found\" });\n }\n if (!scopesEqual(sourceScope, targetScope)) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move folders between scopes\",\n });\n }\n // Moving the folder targets it (and the new parent's contents). Source-side\n // is mutates-self; target-side is regular WRITE on the new parent.\n await requireFolderAccess(userId, input.folderId, \"WRITE\", {\n mutatesSelf: true,\n });\n await requireFolderAccess(userId, input.targetParentFolderId, \"WRITE\");\n\n // Don't let a folder land under one of its own descendants.\n let cursor: string | null = input.targetParentFolderId;\n while (cursor) {\n if (cursor === input.folderId) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot move a folder into its own descendant\",\n });\n }\n const node = await prisma.folder.findUnique({\n where: { id: cursor },\n select: { parentFolderId: true },\n });\n cursor = node?.parentFolderId ?? null;\n }\n\n await prisma.folder.update({\n where: { id: input.folderId },\n data: { parentFolderId: input.targetParentFolderId },\n });\n return { success: true };\n}\n\nexport async function getFolderUploadUrls(\n userId: string,\n input: { folderId: string; files: DirectFileData[] }\n) {\n await requireFolderAccess(userId, input.folderId, \"WRITE\");\n return createDirectUploadFiles(input.files, userId, input.folderId);\n}\n\nexport async function confirmFolderUpload(\n _userId: string,\n input: { fileId: string; uploadSuccess: boolean; errorMessage?: string }\n) {\n // Permission was already checked at upload-url issuance — the file record\n // exists with PENDING status, and the user holds the session id needed to\n // PUT bytes. Confirming flips status, so we just trust the call.\n await confirmDirectUpload(\n input.fileId,\n input.uploadSuccess,\n input.errorMessage\n );\n\n // Index file for RAG search if it belongs to a class folder\n if (input.uploadSuccess) {\n const file = await prisma.file.findUnique({\n where: { id: input.fileId },\n select: { folderId: true },\n });\n if (file?.folderId) {\n const classId = await resolveFileClassId(file.folderId);\n if (classId) {\n enqueueEmbedding(classId, 'FILE', input.fileId);\n }\n }\n }\n\n return { success: true };\n}\n\n/** Walk up folder tree to find classId, if any. */\nasync function resolveFileClassId(folderId: string): Promise<string | null> {\n let currentId: string | null = folderId;\n while (currentId) {\n const folder = await prisma.folder.findUnique({\n where: { id: currentId },\n select: { classId: true, parentFolderId: true },\n });\n if (!folder) return null;\n if (folder.classId) return folder.classId;\n currentId = folder.parentFolderId;\n }\n return null;\n}\n\n/** What scopes can this user pick from in cloud-storage UIs? */\nexport async function listUserScopes(userId: string) {\n const memberships = await prisma.schoolMembership.findMany({\n where: { userId, status: \"ACTIVE\" },\n select: {\n role: true,\n school: {\n select: { id: true, name: true, logoId: true },\n },\n },\n orderBy: { createdAt: \"asc\" },\n });\n\n const classes = await prisma.class.findMany({\n where: {\n OR: [\n { teachers: { some: { id: userId } } },\n { students: { some: { id: userId } } },\n ],\n },\n select: {\n id: true,\n name: true,\n subject: true,\n section: true,\n color: true,\n schoolId: true,\n },\n orderBy: { name: \"asc\" },\n });\n\n // Light counts for the \"Shared with me\" bucket so the UI can hide the entry\n // when there's nothing to show. Cheap to compute — just counts top-most\n // grants, not folder/file metadata.\n const sharedWithMe = await listSharedWithMe(userId);\n\n return {\n personal: { kind: \"PERSONAL\" as const, userId },\n schools: memberships.map((m) => ({\n id: m.school.id,\n name: m.school.name,\n logoId: m.school.logoId,\n role: m.role,\n })),\n classes,\n sharedWithMe: {\n kind: \"SHARED_WITH_ME\" as const,\n folderCount: sharedWithMe.folders.length,\n fileCount: sharedWithMe.files.length,\n },\n };\n}\n"],"names":[],"mappings":"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","debug_id":"0e6d72f8-42d4-50f6-907d-bbab13f2bf3f"}
@@ -23,8 +23,8 @@ export declare function createLabChat(userId: string, input: {
23
23
  } & {
24
24
  id: string;
25
25
  userId: string;
26
- conversationId: string;
27
26
  role: import(".prisma/client").$Enums.ConversationRole;
27
+ conversationId: string;
28
28
  joinedAt: Date;
29
29
  hiddenAt: Date | null;
30
30
  lastViewedAt: Date | null;
@@ -76,8 +76,8 @@ export declare function getLabChat(userId: string, labChatId: string): Promise<{
76
76
  } & {
77
77
  id: string;
78
78
  userId: string;
79
- conversationId: string;
80
79
  role: import(".prisma/client").$Enums.ConversationRole;
80
+ conversationId: string;
81
81
  joinedAt: Date;
82
82
  hiddenAt: Date | null;
83
83
  lastViewedAt: Date | null;
@@ -136,9 +136,9 @@ export declare function listLabChats(userId: string, classId: string): Promise<{
136
136
  id: string;
137
137
  createdAt: Date;
138
138
  content: string;
139
+ senderId: string;
139
140
  conversationId: string;
140
141
  runJobId: string | null;
141
- senderId: string;
142
142
  };
143
143
  messageCount: number;
144
144
  }[]>;
@@ -11,9 +11,9 @@ export declare function listNotifications(userId: string): Promise<({
11
11
  createdAt: Date;
12
12
  title: string;
13
13
  content: string;
14
+ senderId: string | null;
14
15
  read: boolean;
15
16
  receiverId: string;
16
- senderId: string | null;
17
17
  })[]>;
18
18
  /** Get a single notification by ID for its receiver. */
19
19
  export declare function getNotification(userId: string, id: string): Promise<{
@@ -28,9 +28,9 @@ export declare function getNotification(userId: string, id: string): Promise<{
28
28
  createdAt: Date;
29
29
  title: string;
30
30
  content: string;
31
+ senderId: string | null;
31
32
  read: boolean;
32
33
  receiverId: string;
33
- senderId: string | null;
34
34
  }>;
35
35
  /** Send a notification to one user. */
36
36
  export declare function sendNotification(input: {
@@ -42,9 +42,9 @@ export declare function sendNotification(input: {
42
42
  createdAt: Date;
43
43
  title: string;
44
44
  content: string;
45
+ senderId: string | null;
45
46
  read: boolean;
46
47
  receiverId: string;
47
- senderId: string | null;
48
48
  }>;
49
49
  /** Send the same notification to multiple users. */
50
50
  export declare function sendToMultiple(input: {
@@ -65,8 +65,8 @@ export declare function markAsRead(userId: string, id: string): Promise<{
65
65
  createdAt: Date;
66
66
  title: string;
67
67
  content: string;
68
+ senderId: string | null;
68
69
  read: boolean;
69
70
  receiverId: string;
70
- senderId: string | null;
71
71
  }>;
72
72
  //# sourceMappingURL=notification.d.ts.map
@@ -0,0 +1,25 @@
1
+ import type { Response } from 'openai/resources/responses/responses.js';
2
+ export declare const SUPPORTED_DOCUMENT_FORMATS: readonly ["pdf", "docx", "doc", "xlsx", "pptx", "csv", "md", "html", "txt", "json", "xml", "tex", "png", "jpeg", "jpg", "gif", "zip", "tar", "py", "js", "ts", "css", "sh"];
3
+ export type DocumentFormat = (typeof SUPPORTED_DOCUMENT_FORMATS)[number];
4
+ export interface GenerateDocumentInput {
5
+ title: string;
6
+ content: string;
7
+ format?: DocumentFormat | string;
8
+ }
9
+ export interface GeneratedDocumentResult {
10
+ bytes: Buffer;
11
+ filename: string;
12
+ mimeType: string;
13
+ format: DocumentFormat;
14
+ }
15
+ export interface ContainerGeneratedFile {
16
+ containerId: string;
17
+ fileId: string;
18
+ filename: string;
19
+ }
20
+ export declare function normalizeDocumentFormat(format?: string | null): DocumentFormat;
21
+ export declare function mimeTypeForFormat(format: DocumentFormat): string;
22
+ export declare function extractContainerGeneratedFiles(response: Response, extension: DocumentFormat): ContainerGeneratedFile[];
23
+ /** Generate a file via OpenAI Code Interpreter. */
24
+ export declare function generateDocumentWithOpenAI(input: GenerateDocumentInput): Promise<GeneratedDocumentResult | null>;
25
+ //# sourceMappingURL=openaiDocumentGen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openaiDocumentGen.d.ts","sourceRoot":"/","sources":["services/openaiDocumentGen.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yCAAyC,CAAC;AAKxE,eAAO,MAAM,0BAA0B,6KAI7B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAsDzE,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAM9E;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEhE;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,cAAc,GACxB,sBAAsB,EAAE,CAyB1B;AAwCD,mDAAmD;AACnD,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAgEzC"}