import Colle from '#models/colle' import Examiner from '#models/examiner' import Room from '#models/room' import Subject from '#models/subject' import User from '#models/user' import redis from '@adonisjs/redis/services/main' export class ColleService { async getHealthyUntil(className: string) { const healtyUntil = await redis.get(`healthy_until_${className}`) return new Date(healtyUntil || '') } async getLastSync(className: string) { const lastSync = await redis.get(`last_sync_${className}`) return new Date(lastSync || '') } async getStudent(studentName: string, className: string) { // Find or create a student by name const { firstName, lastName } = this.splitNames(studentName) const student = await User.query() .where('first_name', firstName) .where('last_name', lastName) .where('class_name', className) .first() if (!student) { return User.create({ firstName, lastName, className }) } return student } splitNames(fullName: string): { firstName: string; lastName: string } { const words = fullName.trim().split(/\s+/) let lastNameParts: string[] = [] let i = 0 // Collect all uppercase words at the start while (i < words.length && words[i] === words[i].toUpperCase()) { lastNameParts.push(words[i]) i++ } const lastName = lastNameParts.join(' ') const firstName = words.slice(i).join(' ') return { firstName, lastName } } async getExaminer(examinerName: string) { // Find or create an examiner by name const examiner = await Examiner.query().where('name', examinerName).first() if (!examiner) return Examiner.create({ name: examinerName }) return examiner } async getSubject(subjectName: string) { // Find or create a subject by name const subject = await Subject.query().where('name', subjectName).first() if (!subject) return Subject.create({ name: subjectName }) return subject } async getRoom(roomName: string) { // Find or create a room by name const room = await Room.query().where('name', roomName).first() if (!room) return Room.create({ name: roomName }) return room } async getColles(student: User, startDate: string, endDate: string) { const classColles = await Colle.query() .preload('student') .preload('examiner') .preload('subject') .preload('room') .where('date', '>=', startDate) .where('date', '<=', endDate) .whereHas('student', (query) => { query.where('className', student.className) }) // Filter only colles that have been graded .whereNotNull('grade') .orderBy('date', 'asc') const studentColles = await Colle.query() .preload('examiner') .preload('subject') .preload('room') .preload('student') .where('date', '>=', startDate) .where('date', '<=', endDate) .where('studentId', student.id) .orderBy('date', 'asc') // TODO: Favorite colles const favoriteColles = [] as Colle[] return { classColles, studentColles, favoriteColles, healthyUntil: await this.getHealthyUntil(student.className), lastSync: await this.getLastSync(student.className), } } // Pre-process HTML content to render LaTeX private removeTrailingLines(htmlString: string) { return htmlString.replace(/(\s*)+$/gi, '').trim() } private extractLatexImages(html: string) { const imgRegex = /]+src="(https:\/\/latex\.codecogs\.com\/gif\.latex\?(=?.*?))"[^>]*>/g let parts = [] let latexMatches: string[] = [] let lastIndex = 0 html.replace(imgRegex, (match, _, latex, index) => { parts.push(html.slice(lastIndex, index)) // Add HTML before image latexMatches.push(decodeURIComponent(latex)) // Extract and decode LaTeX lastIndex = index + match.length return '' }) parts.push(html.slice(lastIndex)) // Add remaining HTML after last image return { parts, latexMatches } } prepareHtmlForRendering(html: string) { const strippedHtml = this.removeTrailingLines(html) const { parts, latexMatches } = this.extractLatexImages(strippedHtml) const outputHtml = parts .map((part, i) => { if (!latexMatches[i]) { return part } return `${part}$$${latexMatches[i]}$$` }) .join('') // Remove all "\," from string const regex = /\\,/g return outputHtml.replace(regex, ' ') } }