import Colle from '#models/colle' import { ColleService } from '#services/colle_service' import { NotificationService } from '#services/notification_service' import { createColleValidator, createUpcomingCollesValidator } from '#validators/colle' import { inject } from '@adonisjs/core' import type { HttpContext } from '@adonisjs/core/http' import redis from '@adonisjs/redis/services/main' import { DateTime } from 'luxon' @inject() export default class CollesController { constructor( private service: ColleService, private notificationService: NotificationService ) {} async index({ request, response, auth }: HttpContext) { const { startDate: rawStartDate } = request.qs() // Validate startDate const startDate = rawStartDate ? DateTime.fromISO(rawStartDate, { zone: 'local' }) : null if (!rawStartDate || !startDate || !startDate.isValid) { return response.badRequest({ message: 'Invalid start date format' }) } const endDate = startDate.plus({ days: 6 }) // Sunday // Retrieve colles for the authenticated user const data = await this.service.getColles(auth.user!, startDate.toISO(), endDate.toISO()) return { success: true, data, } } async show({ params, response, auth }: HttpContext) { const colleId = parseInt(params.colleId) if (isNaN(colleId)) { return response.badRequest({ message: 'Invalid colle ID' }) } // Retrieve the colle by ID const colle = await Colle.query() .where('id', colleId) .whereHas('student', (query) => { query.where('className', auth.user!.className) }) .preload('student') .preload('examiner') .preload('subject') .preload('room') .preload('attachments') .first() if (!colle) { return response.notFound({ message: 'Colle not found' }) } return { success: true, data: { ...colle.serialize(), bjid: colle.bjid, bjsecret: colle.bjsecret, }, } } async refresh({ params, response, auth }: HttpContext) { const colleId = parseInt(params.colleId) if (isNaN(colleId)) { return response.badRequest({ message: 'Invalid colle ID' }) } // Retrieve the colle by ID const colle = await Colle.query() .where('id', colleId) .whereHas('student', (query) => { query.where('className', auth.user!.className) }) .first() if (!colle) { return response.notFound({ message: 'Colle not found' }) } // Post Redis message to refresh the colle await redis.publish( 'jobs_queue', JSON.stringify({ type: 0, // Refresh colle colle_id: colle.bjid, colle_secret: colle.bjsecret, class_name: auth.user!.className, }) ) return response.ok({ success: true, message: `Colle ${colleId} refresh request sent`, }) } async create({ request, response }: HttpContext) { const { colle: payload, className } = await request.validateUsing(createColleValidator) // TODO: Use Redis cache to avoid multiple queries // Retrieve or create the necessary entities (relations) const student = await this.service.getStudent(payload.student, className) const examiner = await this.service.getExaminer(payload.examiner) const subject = await this.service.getSubject(payload.subject) const room = await this.service.getRoom(payload.room) const date = DateTime.fromJSDate(payload.date) if (!date.isValid) { return response.badRequest({ message: 'Invalid date format' }) } // Prepare the content and comment for rendering if (payload.comment) { payload.comment = this.service.prepareHtmlForRendering(payload.comment) } if (payload.content) { payload.content = this.service.prepareHtmlForRendering(payload.content) } const colleData = { studentId: student.id, examinerId: examiner.id, subjectId: subject.id, roomId: room.id, bjsecret: payload.bjsecret, bjid: payload.bjid, grade: payload.grade, content: payload.content, comment: payload.comment, date, } // Check if the colle already exists const existing = await Colle.query() .where('studentId', student.id) .where('subjectId', subject.id) .where('date', date.toISO()) .first() // If it exists, update the existing colle if (existing) { // Merge the new data with the existing colle const beforeColle = existing.serialize() Object.assign(existing, colleData) // Handle attachments if any if (payload.attachments && payload.attachments.length > 0) { // Retrieve existing attachments const existingAttachments = await existing.related('attachments').query() // Remove attachments that are not in the new payload const existingAttachmentUrls = new Set(existingAttachments.map((a) => a.path)) for (const attachment of payload.attachments) { if (!existingAttachmentUrls.has(attachment.url)) { await existing.related('attachments').create({ name: attachment.name, path: attachment.url, }) } } // Remove attachments that are not in the new payload for (const attachment of existingAttachments) { if (!payload.attachments.some((a) => a.url === attachment.path)) { await attachment.delete() } } } const afterColle = existing.serialize() if (!beforeColle.grade && afterColle.grade) { await this.notificationService.sendNotification('GRADE_ADDED', existing) } else if (parseFloat(beforeColle.grade) !== afterColle.grade) { await this.notificationService.sendNotification( 'GRADE_UPDATED', existing, parseFloat(beforeColle.grade) ) } if (!deepEqual(beforeColle, afterColle)) { await this.notificationService.sendNotification('COLLE_UPDATED', existing) } return existing.save() } const colle = await Colle.create(colleData) // Handle attachments if any if (payload.attachments && payload.attachments.length > 0) { for (const attachment of payload.attachments) { await colle.related('attachments').create({ path: attachment.url, name: attachment.name, }) } } return colle } async createUpcoming({ request }: HttpContext) { const payload = await request.validateUsing(createUpcomingCollesValidator) // ONLY UPCOMING COLLES const now = DateTime.now() const colles = payload.colles.filter( (colle) => colle.date && DateTime.fromJSDate(colle.date) > now ) // Retrieve all upcoming colles for the class const upcomingColles = await Colle.query() .whereHas('student', (query) => { query.where('className', payload.className) }) .where('date', '>=', now.toISO()) .orderBy('date', 'asc') // Store the upcoming colles ids const upcomingCollesIds = new Set(upcomingColles.map((colle) => colle.id)) for await (const colle of colles) { // Find the updated data for the colle const student = await this.service.getStudent(colle.student, payload.className) const examiner = await this.service.getExaminer(colle.examiner) const subject = await this.service.getSubject(colle.subject) const room = await this.service.getRoom(colle.room) const date = DateTime.fromJSDate(colle.date) const oldColle = upcomingColles.find( (c) => c.studentId === student.id && c.subjectId === subject.id && c.date.toISO() === date.toISO() ) const updatedColle = { studentId: student.id, examinerId: examiner.id, subjectId: subject.id, roomId: room.id, bjsecret: colle.bjsecret, bjid: colle.bjid, grade: colle.grade, content: colle.content, comment: colle.comment, date, } // Create a new colle if it doesn't exist if (!oldColle) { const colle = await Colle.create(updatedColle) await this.notificationService.sendNotification('COLLE_ADDED', colle) continue } // Update the colle with the new data // and remove it from the list const beforeColle = oldColle.serialize() Object.assign(oldColle, updatedColle) await oldColle.save() const afterColle = oldColle.serialize() upcomingCollesIds.delete(oldColle.id) if (beforeColle.room !== afterColle.room) { await this.notificationService.sendNotification('ROOM_UPDATED', oldColle) } else if (!deepEqual(beforeColle, afterColle)) { await this.notificationService.sendNotification('COLLE_UPDATED', oldColle) } } // Delete the colles that were not updated const collesToDelete = await Promise.all( Array.from(upcomingCollesIds).map((id) => { return Colle.query() .where('id', id) .whereHas('student', (query) => { query.where('className', payload.className) }) .first() }) ) for (const colle of collesToDelete) { if (colle) { await this.notificationService.sendNotification('COLLE_REMOVED', colle) await colle.delete() } } console.log(`Deleted ${collesToDelete.length} upcoming colles that were not updated`) } } function deepEqual(a: any, b: any) { return JSON.stringify(a) === JSON.stringify(b) }