api/app/controllers/colles_controller.ts
2025-08-21 10:49:56 +02:00

292 lines
9.4 KiB
TypeScript

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)
}