chore: update dependencies and enhance document schema
- Updated package.json and package-lock.json to include the new dependency 'quill-to-pdf' for improved document export functionality. - Modified DocumentSchema to introduce a new 'DocumentExportObject' type, facilitating document export operations. - Cleaned up commented-out code in document.service.ts and minio.service.ts for better readability and maintainability. - Adjusted quiz schema to expose user input and questions as JSON types, enhancing data flexibility. - Updated workshop subscription logic to maintain accurate participant counts upon new subscriptions. These changes improve the overall functionality and maintainability of the project, ensuring better document handling and schema consistency.
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -67,6 +67,7 @@
|
|||||||
"openai": "^4.76.0",
|
"openai": "^4.76.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
|
"quill-to-pdf": "^1.0.7",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
@@ -15230,6 +15231,21 @@
|
|||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/quill-to-pdf": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/quill-to-pdf/-/quill-to-pdf-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-aHB4m7r9Vb0da2JldFSMGge2Sa8oUYFBMxXl0Ne/p9O33Xav+jkqKsE6yYLfw+3BLtKwqJyEUv9bzYvmvU746w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"quilljs-parser": "^1.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/quilljs-parser": {
|
||||||
|
"version": "1.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/quilljs-parser/-/quilljs-parser-1.0.14.tgz",
|
||||||
|
"integrity": "sha512-tycXmRQRcAp3Mq4vb+8Je+u8s3zoBEtwJmc3dZn4N6rFDYdBl1LlkzXJMBCvnpapHyejoOIViYq558MF2Ca7hg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/randombytes": {
|
"node_modules/randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -89,6 +89,7 @@
|
|||||||
"openai": "^4.76.0",
|
"openai": "^4.76.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
|
"quill-to-pdf": "^1.0.7",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
@@ -139,13 +140,19 @@
|
|||||||
"@css-inline/css-inline-linux-x64-musl": "^0.14.3"
|
"@css-inline/css-inline-linux-x64-musl": "^0.14.3"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": ["js", "json", "ts"],
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"testRegex": ".*\\.spec\\.ts$",
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
},
|
},
|
||||||
"collectCoverageFrom": ["**/*.(t|j)s"],
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -100,6 +100,23 @@ export class DocumentSchema extends PothosSchema {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PothosRef()
|
||||||
|
DocumentExportObject() {
|
||||||
|
return this.builder.simpleObject('DocumentExportObject', {
|
||||||
|
fields: (t) => ({
|
||||||
|
documentId: t.string(),
|
||||||
|
pageIndex: t.int(),
|
||||||
|
type: t.field({
|
||||||
|
type: this.builder.enumType('DocumentExportType', {
|
||||||
|
values: ['PDF', 'DOCX'] as const,
|
||||||
|
}),
|
||||||
|
nullable: false,
|
||||||
|
}),
|
||||||
|
fileUrl: t.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@PothosRef()
|
@PothosRef()
|
||||||
documentDeltaInput() {
|
documentDeltaInput() {
|
||||||
return this.builder.inputType('DocumentDeltaInput', {
|
return this.builder.inputType('DocumentDeltaInput', {
|
||||||
@@ -179,6 +196,45 @@ export class DocumentSchema extends PothosSchema {
|
|||||||
return document
|
return document
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// exportDocument: t.field({
|
||||||
|
// type: this.DocumentExportObject(),
|
||||||
|
// args: {
|
||||||
|
// documentId: t.arg({ type: 'String', required: true }),
|
||||||
|
// },
|
||||||
|
// resolve: async (_query, args, ctx: SchemaContext) => {
|
||||||
|
// if (ctx.isSubscription) {
|
||||||
|
// throw new Error('Not allowed')
|
||||||
|
// }
|
||||||
|
// if (!args.documentId) {
|
||||||
|
// throw new Error('Document id not found')
|
||||||
|
// }
|
||||||
|
// const document = await this.prisma.document.findUnique({
|
||||||
|
// where: { id: args.documentId },
|
||||||
|
// include: {
|
||||||
|
// collaborators: true,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// if (!document) {
|
||||||
|
// throw new Error('Document not found')
|
||||||
|
// }
|
||||||
|
// // check if user is owner or collaborator
|
||||||
|
// if (
|
||||||
|
// document.ownerId !== ctx.http?.me?.id &&
|
||||||
|
// !document.collaborators.some((c) => c.userId === ctx.http?.me?.id && c.readable)
|
||||||
|
// ) {
|
||||||
|
// throw new Error('User is not owner or collaborator of document')
|
||||||
|
// }
|
||||||
|
// // export document
|
||||||
|
// const fileUrl = await this.minio.exportDocument(args.documentId)
|
||||||
|
// return {
|
||||||
|
// documentId: args.documentId,
|
||||||
|
// pageIndex: 0,
|
||||||
|
// type: args.type,
|
||||||
|
// fileUrl: '',
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
eventDocumentClientRequestSync: t.field({
|
eventDocumentClientRequestSync: t.field({
|
||||||
type: this.documentDelta(),
|
type: this.documentDelta(),
|
||||||
args: {
|
args: {
|
||||||
@@ -547,7 +603,7 @@ export class DocumentSchema extends PothosSchema {
|
|||||||
// 0.5% chance to request sync
|
// 0.5% chance to request sync
|
||||||
if (random <= 0.005) {
|
if (random <= 0.005) {
|
||||||
// check grammar too
|
// check grammar too
|
||||||
this.documentService.checkGrammarForPage(payload.documentId, payload.pageIndex)
|
// this.documentService.checkGrammarForPage(payload.documentId, payload.pageIndex)
|
||||||
Logger.log('request sync', 'request sync')
|
Logger.log('request sync', 'request sync')
|
||||||
payload.requestSync = true
|
payload.requestSync = true
|
||||||
return payload
|
return payload
|
||||||
|
|||||||
@@ -98,50 +98,51 @@ export class DocumentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check grammar for a page by parallely send each sentence to OpenAI service as text and get the result, after that return the result as delta and publish the result to the document
|
// check grammar for a page by parallely send each sentence to OpenAI service as text and get the result, after that return the result as delta and publish the result to the document
|
||||||
async checkGrammarForPage(documentId: string, pageId: number): Promise<void> {
|
// async checkGrammarForPage(documentId: string, pageId: number): Promise<void> {
|
||||||
const sentenceDelta = await this.pageToSentence(documentId, pageId)
|
// const sentenceDelta = await this.pageToSentence(documentId, pageId)
|
||||||
|
|
||||||
// Extract the entire page content as a single text
|
// // Extract the entire page content as a single text
|
||||||
const pageText = sentenceDelta.ops
|
// const pageText = sentenceDelta.ops
|
||||||
.filter((op) => typeof op.insert === 'string')
|
// .filter((op) => typeof op.insert === 'string')
|
||||||
.map((op) => op.insert as string)
|
// .map((op) => op.insert as string)
|
||||||
.join(' ')
|
// .join(' ')
|
||||||
|
|
||||||
// Create a unique cache key for the entire page
|
// // Create a unique cache key for the entire page
|
||||||
const cacheKey = `grammar_check:${documentId}:${pageId}`
|
// const cacheKey = `grammar_check:${documentId}:${pageId}`
|
||||||
|
|
||||||
// Try to get cached result first
|
// // Try to get cached result first
|
||||||
const cachedResult = await this.redis.get(cacheKey)
|
// const cachedResult = await this.redis.get(cacheKey)
|
||||||
|
|
||||||
if (cachedResult) {
|
// if (cachedResult) {
|
||||||
Logger.log('Cached grammar check result exists', 'Grammar Check')
|
// Logger.log('Cached grammar check result exists', 'Grammar Check')
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Process the entire page text
|
// // Process the entire page text
|
||||||
const grammarCheckResult = await this.openai.processText(pageText, 0, PromptType.CHECK_GRAMMAR)
|
// const grammarCheckResult = await this.openai.processText(pageText, 0, PromptType.CHECK_GRAMMAR)
|
||||||
|
|
||||||
if (!grammarCheckResult.result) {
|
// if (!grammarCheckResult.result) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Cache the result
|
// // Cache the result
|
||||||
await this.redis.setPermanent(cacheKey, JSON.stringify(grammarCheckResult))
|
// await this.redis.setPermanent(cacheKey, JSON.stringify(grammarCheckResult))
|
||||||
|
|
||||||
// Calculate diff between original page and corrected page
|
// // Calculate diff between original page and corrected page
|
||||||
const diff = await this.diff(pageText, grammarCheckResult.result)
|
// const diff = await this.diff(pageText, grammarCheckResult.result)
|
||||||
|
|
||||||
// Build payload
|
// // Build payload
|
||||||
|
|
||||||
// Publish the result to the subscriber
|
|
||||||
this.pubSub.publish(`${DocumentEvent.AI_SUGGESTION}.${documentId}.${pageId}`, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
async diff(original: string, corrected: string): Promise<Delta> {
|
// // Publish the result to the subscriber
|
||||||
const originalDelta = new Delta().insert(original)
|
// this.pubSub.publish(`${DocumentEvent.AI_SUGGESTION}.${documentId}.${pageId}`, payload)
|
||||||
const correctedDelta = new Delta().insert(corrected)
|
// }
|
||||||
const diff = originalDelta.diff(correctedDelta)
|
|
||||||
Logger.log(diff, 'diff')
|
// async diff(original: string, corrected: string): Promise<Delta> {
|
||||||
return diff
|
// const originalDelta = new Delta().insert(original)
|
||||||
}
|
// const correctedDelta = new Delta().insert(corrected)
|
||||||
|
// const diff = originalDelta.diff(correctedDelta)
|
||||||
|
// Logger.log(diff, 'diff')
|
||||||
|
// return diff
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { FileUpload } from 'graphql-upload/processRequest.mjs'
|
|||||||
import { BucketItem, Client } from 'minio'
|
import { BucketItem, Client } from 'minio'
|
||||||
import { MINIO_CONNECTION } from 'nestjs-minio'
|
import { MINIO_CONNECTION } from 'nestjs-minio'
|
||||||
import Delta from 'quill-delta'
|
import Delta from 'quill-delta'
|
||||||
|
// import { pdfExporter } from 'quill-to-pdf'
|
||||||
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
|
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -33,7 +34,7 @@ export class MinioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async upsertFile(name: string, file: FileUpload, category: string) {
|
async upsertFile(name: string, file: FileUpload, category: string) {
|
||||||
const { mimetype, createReadStream, filename: actualFileName } = await file
|
const { mimetype, createReadStream, filename: _actualFileName } = await file
|
||||||
|
|
||||||
const fileBuffer = createReadStream()
|
const fileBuffer = createReadStream()
|
||||||
|
|
||||||
@@ -150,15 +151,67 @@ export class MinioService {
|
|||||||
return JSON.parse(buffer.toString()) as Delta
|
return JSON.parse(buffer.toString()) as Delta
|
||||||
}
|
}
|
||||||
// export document to docx format by get all pages and convert to docx
|
// export document to docx format by get all pages and convert to docx
|
||||||
async exportDocument(id: string) {
|
// async exportDocument(id: string) {
|
||||||
// get all pages
|
// try {
|
||||||
const _pages = await this.minioClient.listObjects(
|
// // List all page objects for the document
|
||||||
this.configService.get('BUCKET_NAME') ?? 'epess',
|
// const stream = this.minioClient.listObjects(
|
||||||
`documents/${id}`,
|
// this.configService.get('BUCKET_NAME') ?? 'epess',
|
||||||
)
|
// `documents/${id}/`,
|
||||||
// convert to docx
|
// true,
|
||||||
// const docx = await this.convertToDocx(pages)
|
// )
|
||||||
}
|
|
||||||
|
// // Collect page items
|
||||||
|
// const pageItems: BucketItem[] = await new Promise((resolve, reject) => {
|
||||||
|
// const items: BucketItem[] = []
|
||||||
|
// stream.on('data', (item) => items.push(item))
|
||||||
|
// stream.on('end', () => resolve(items))
|
||||||
|
// stream.on('error', (err) => reject(err))
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // Sort pages numerically (assuming page names are numeric)
|
||||||
|
// const sortedPageItems = pageItems
|
||||||
|
// .filter((item) => item.name && /\/\d+$/.test(item.name))
|
||||||
|
// .sort((a, b) => {
|
||||||
|
// const pageA = parseInt(a.name?.split('/').pop() ?? '0')
|
||||||
|
// const pageB = parseInt(b.name?.split('/').pop() ?? '0')
|
||||||
|
// return pageA - pageB
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // Fetch and parse page deltas in order
|
||||||
|
// const pages = await Promise.all(
|
||||||
|
// sortedPageItems.map(async (page) => {
|
||||||
|
// const delta = await this.minioClient.getObject(
|
||||||
|
// this.configService.get('BUCKET_NAME') ?? 'epess',
|
||||||
|
// page.name ?? '',
|
||||||
|
// )
|
||||||
|
// const buffer = await delta.read()
|
||||||
|
// return JSON.parse(buffer.toString()) as Delta
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
|
||||||
|
// // Convert to PDF
|
||||||
|
// const pdf = await pdfExporter.generatePdf(pages)
|
||||||
|
// // convert blob to
|
||||||
|
// const pdfBuffer = await pdf.arrayBuffer().then((buffer) => Buffer.from(buffer))
|
||||||
|
|
||||||
|
// // Upload PDF to Minio
|
||||||
|
// await this.minioClient.putObject(
|
||||||
|
// this.configService.get('BUCKET_NAME') ?? 'epess',
|
||||||
|
// `documents/${id}/export.pdf`,
|
||||||
|
// pdfBuffer,
|
||||||
|
// undefined,
|
||||||
|
// {
|
||||||
|
// 'Content-Type': 'application/pdf',
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
|
||||||
|
// // Get and return document URL
|
||||||
|
// return await this.getFileUrl(`documents/${id}/export.pdf`, 'documents')
|
||||||
|
// } catch (error) {
|
||||||
|
// Logger.error(`Failed to export document ${id}: ${error}`, 'MinioService')
|
||||||
|
// throw error
|
||||||
|
// }
|
||||||
|
// }
|
||||||
fileName() {
|
fileName() {
|
||||||
// generate a unique file name using uuid
|
// generate a unique file name using uuid
|
||||||
return uuidv4()
|
return uuidv4()
|
||||||
|
|||||||
@@ -85,8 +85,12 @@ export class QuizSchema extends PothosSchema {
|
|||||||
numberOfQuestions: t.exposeInt('numberOfQuestions'),
|
numberOfQuestions: t.exposeInt('numberOfQuestions'),
|
||||||
correctPoints: t.exposeInt('correctPoints'),
|
correctPoints: t.exposeInt('correctPoints'),
|
||||||
totalPoints: t.exposeInt('totalPoints'),
|
totalPoints: t.exposeInt('totalPoints'),
|
||||||
userInput: t.exposeStringList('userInput'),
|
userInput: t.expose('userInput', {
|
||||||
questions: t.exposeStringList('questions'),
|
type: 'Json',
|
||||||
|
}),
|
||||||
|
questions: t.expose('questions', {
|
||||||
|
type: 'Json',
|
||||||
|
}),
|
||||||
createdAt: t.expose('createdAt', {
|
createdAt: t.expose('createdAt', {
|
||||||
type: 'DateTime',
|
type: 'DateTime',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common'
|
import { Inject, Injectable, Logger } from '@nestjs/common'
|
||||||
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
|
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
|
||||||
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
|
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
|
||||||
import { Builder } from '../Graphql/graphql.builder'
|
import { Builder } from '../Graphql/graphql.builder'
|
||||||
@@ -126,12 +126,23 @@ export class WorkshopSubscriptionSchema extends PothosSchema {
|
|||||||
throw new Error('User already subscribed to workshop')
|
throw new Error('User already subscribed to workshop')
|
||||||
}
|
}
|
||||||
// create the workshop subscription
|
// create the workshop subscription
|
||||||
return await this.prisma.workshopSubscription.create({
|
const result = await this.prisma.workshopSubscription.create({
|
||||||
data: {
|
data: {
|
||||||
userId,
|
userId,
|
||||||
workshopId: args.workshopId,
|
workshopId: args.workshopId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
// update participant count by querying the workshop subscription
|
||||||
|
const participantCount = await this.prisma.workshopSubscription.count({
|
||||||
|
where: { workshopId: args.workshopId },
|
||||||
|
})
|
||||||
|
await this.prisma.workshop.update({
|
||||||
|
where: { id: args.workshopId },
|
||||||
|
data: {
|
||||||
|
registeredParticipants: participantCount,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return result
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user