import Meal from '#models/meal' import MealRegistration from '#models/meal_registration' import { credentialsValidator } from '#validators/auth' import { createMealValidator } from '#validators/meal' import type { HttpContext } from '@adonisjs/core/http' import redis from '@adonisjs/redis/services/main' import { DateTime } from 'luxon' export default class MealsController { // POST /meals public async create({ request }: HttpContext) { const data = await request.validateUsing(createMealValidator) if ('meals' in data) { // Remove all already submittable meals await Meal.query().where('submittable', true).update({ submittable: false }) // Set new meals as submittable for (const mealData of data.meals) { const meal = await Meal.query() .where('date', mealData.date) .where('type', mealData.meal_type === 'lunch' ? 0 : 1) .first() if (meal) { meal.submittable = true await meal.save() } else { console.warn('Meal to set as submittable not found:', mealData) } } return { message: 'Meals updated successfully' } } const { date, type, courses } = data const mealType = type === 'Déjeuner' ? 0 : 1 // Check if a meal with the same date and type already exists const existingMeal = await Meal.query() .where({ date, type: mealType }) .preload('courses') .first() if (existingMeal) { // Check if the existing meal has the same courses const existingCourseNames = existingMeal.courses.map((course) => course.description) const newCourseNames = courses.map((course) => course.description) if ( existingCourseNames.length === newCourseNames.length && existingCourseNames.every((name) => newCourseNames.includes(name)) ) { return existingMeal } // If not, delete the existing meal (and its courses, thanks to cascade delete) await existingMeal.delete() } // Create the new meal const meal = await Meal.create({ date, type: mealType, }) await meal.related('courses').createMany(courses) return meal } // GET /meals public async index({ auth }: HttpContext) { const meals = await Meal.query().orderBy('date', 'asc').preload('courses') const data = meals.map(async (meal) => { if (meal.submittable) { const isRegistered = await MealRegistration.query() .where('meal_id', meal.id) .where('user_id', auth.user!.id) .first() // Remove temporary registrations that are older than 5 minutes if (isRegistered?.temporary) { const oneHourAgo = DateTime.now().minus({ minutes: 5 }) if (isRegistered.createdAt < oneHourAgo) { await isRegistered.delete() return meal.serialize() } } return { ...meal.serialize(), isRegistered: !!isRegistered, } } else { return meal.serialize() } }) return Promise.all(data) } // POST /meals/:id // TODO: Unregister from meal public async registerForMeal({ params, auth, response, request }: HttpContext) { const { username, password } = await request.validateUsing(credentialsValidator) const meal = await Meal.find(params.id) if (!meal) { return response.notFound({ message: 'Meal not found' }) } if (!meal.submittable) { return response.badRequest({ message: 'Meal is not submittable' }) } // Create a bullshit registration // (to confirm to the user that the registration is in progress...) // that will eventually be deleted if the registration fails (worker callback) await MealRegistration.firstOrCreate({ mealId: meal.id, userId: auth.user!.id, temporary: true, }) // Register the user for the meal await redis.publish( 'jobs_queue', JSON.stringify({ type: 4, // Submit meals meal: { date: DateTime.fromJSDate(meal.date).toISODate(), meal_type: meal.type === 0 ? 'lunch' : 'dinner', }, class_name: auth.user!.className, user_id: auth.user!.id, bj_username: username, bj_password: password, }) ) return { success: true, message: 'Registration in progress' } } }