From c724dac0860b7ccccc5082c04ba81bfd0a6bd8a8 Mon Sep 17 00:00:00 2001 From: Nathan Lamy Date: Wed, 30 Jul 2025 12:56:52 +0200 Subject: [PATCH] feat: add colles routes --- app/controllers/auth_controller.ts | 109 ++++++++---- app/controllers/colles_controller.ts | 168 ++++++++++++++++++ app/controllers/user_controller.ts | 3 +- app/exceptions/handler.ts | 2 +- app/models/colle.ts | 41 +++-- app/models/user.ts | 10 +- app/services/auth_service.ts | 95 ++++------ app/services/colle_service.ts | 95 ++++++++++ app/validators/auth.ts | 40 +++-- app/validators/colle.ts | 30 ++++ config/cors.ts | 2 +- .../1747564644339_create_users_table.ts | 1 - docker-compose.yml | 15 ++ package.json | 1 - pnpm-lock.yaml | 9 - resources/views/mails/auth-fallback.edge | 11 +- resources/views/mails/auth.edge | 14 +- start/limiter.ts | 2 +- start/routes.ts | 24 ++- 19 files changed, 502 insertions(+), 170 deletions(-) create mode 100644 app/controllers/colles_controller.ts create mode 100644 app/services/colle_service.ts create mode 100644 app/validators/colle.ts diff --git a/app/controllers/auth_controller.ts b/app/controllers/auth_controller.ts index be22204..3be99a6 100644 --- a/app/controllers/auth_controller.ts +++ b/app/controllers/auth_controller.ts @@ -1,5 +1,5 @@ import type { HttpContext } from '@adonisjs/core/http' -import { requestLoginValidator, verifyCodeValidator } from '#validators/auth' +import { registerValidator, requestLoginValidator, verifyCodeValidator } from '#validators/auth' import mail from '@adonisjs/mail/services/main' import { AuthService } from '#services/auth_service' import { inject } from '@adonisjs/core' @@ -7,10 +7,9 @@ import app from '@adonisjs/core/services/app' import env from '#start/env' import User from '#models/user' -// TODO: When login, set user.email to the email used to request login (lowercase) @inject() export default class AuthController { - constructor(private authService: AuthService) {} + constructor(private authService: AuthService) { } // POST /auth/request async requestLogin({ request, response, captcha }: HttpContext) { @@ -19,25 +18,18 @@ export default class AuthController { const validateResult = await (captcha.use('turnstile') as any).validate() if (!validateResult.success) { return response.badRequest({ - message: 'Captcha validation failed', + success: false, + message: 'Veuillez valider le captcha', error: validateResult.errorCodes, }) } } - // Find user by email const { email } = await request.validateUsing(requestLoginValidator) - const user = await this.authService.findUser(email) - if (!user) { - return response.notFound({ - success: false, - message: 'Utilisateur non trouvé', - }) - } // Generate token const expiresIn = '15 minutes' - const payload = await this.authService.generateToken(email, user.id, expiresIn) + const payload = await this.authService.generateCode(email, expiresIn) // Send email await mail @@ -55,9 +47,6 @@ export default class AuthController { return { success: true, - data: { - token: payload.token, - }, } } @@ -65,8 +54,8 @@ export default class AuthController { async verifyCode({ request, response, auth }: HttpContext) { // Validate code const { code } = await request.validateUsing(verifyCodeValidator) - const { success, userId, email } = await this.authService.validateCode(code) - if (!success || !userId || isNaN(userId)) { + const { success, email } = await this.authService.validateCode(code) + if (!success) { return response.badRequest({ success: false, message: 'Code de vérification invalide', @@ -74,32 +63,78 @@ export default class AuthController { } // Find user by id - const user = await User.findBy('id', userId) + const user = await User.findBy('email', email) if (!user) { - return response.notFound({ - success: false, - message: 'Utilisateur non trouvé', - }) + // If the user does not exist, return a token for registration + const expiresIn = '1 hour' + const { token, email: userEmail } = this.authService.generateToken(email, expiresIn) + return { + token, + email: userEmail, + success: true, + } } - // Set user email to the email used to request login (lowercase) - user.email = email.toLowerCase() - await user.save() - // Perform login await auth.use('web').login(user, true) // true for remember me - return user + return { + success: true, + user + } } - magicLink({}: HttpContext) { - // Validate signed url (adonis) - // + login current device - // + SSE to notify other devices (and login) + // POST /auth/register + async register({ request, response, auth }: HttpContext) { + const { firstName, lastName, className, token } = await request.validateUsing(registerValidator) + + // Validate token + const { success, email } = this.authService.validateToken(token) + if (!success || !email) { + return response.badRequest({ + success: false, + message: 'Votre lien de connexion est invalide ou a expiré.', + }) + } + + // Check if user already exists + const existingUser = await User.findBy('email', email) + if (existingUser) { + // If user already exists, perform login + await auth.use('web').login(existingUser, true) // true for remember me + return { + success: true, + user: existingUser, + } + } + + // TODO: Check if className is allowed (else redirect for account giving) + + // TODO: Rewrite user creation (NEVER CREATE USER - use string similarity) + // Create new user + const user = await User.create({ + firstName, + lastName, + className, + email + }) + // Perform login + await auth.use('web').login(user, true) // true for remember me + return { + success: true, + user + } } - listen({}: HttpContext) { - // Listen for SSE events - // Need an AUTH token to connect - // AUTH token sent to client in requestLogin - } + // TODO: Magic link login + // magicLink({ }: HttpContext) { + // // Validate signed url (adonis) + // // + login current device + // // + SSE to notify other devices (and login) + // } + + // listen({ }: HttpContext) { + // // Listen for SSE events + // // Need an AUTH token to connect + // // AUTH token sent to client in requestLogin + // } } diff --git a/app/controllers/colles_controller.ts b/app/controllers/colles_controller.ts new file mode 100644 index 0000000..8eba953 --- /dev/null +++ b/app/controllers/colles_controller.ts @@ -0,0 +1,168 @@ +import Colle from '#models/colle' +import { ColleService } from '#services/colle_service' +import { createColleValidator, createUpcomingCollesValidator } from '#validators/colle' +import { inject } from '@adonisjs/core' +import type { HttpContext } from '@adonisjs/core/http' +import { DateTime } from 'luxon' + +@inject() +export default class CollesController { + constructor(private service: ColleService) {} + + 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') + .first() + // TODO: Include BJID and BJSecret ! + if (!colle) { + return response.notFound({ message: 'Colle not found' }) + } + return { + success: true, + data: colle, + } + } + + 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' }) + } + + 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 colle = await Colle.query() + .where('studentId', student.id) + .where('subjectId', subject.id) + .where('date', date.toISO()) + .first() + + // If it exists, update the existing colle + if (colle) { + Object.assign(colle, colleData) + return colle.save() + } + // Create the colle + return Colle.create(colleData) + } + + 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) { + await Colle.create(updatedColle) + continue + } + + // Update the colle with the new data + // and remove it from the list + Object.assign(oldColle, updatedColle) + await oldColle.save() + upcomingCollesIds.delete(oldColle.id) + } + + // Delete the colles that were not updated + const deleted = await Colle.query() + .whereHas('student', (query) => { + query.where('className', payload.className) + }) + .whereIn('id', Array.from(upcomingCollesIds)) + .delete() + + console.log(`Deleted ${deleted} upcoming colles that were not updated`) + } +} diff --git a/app/controllers/user_controller.ts b/app/controllers/user_controller.ts index fa6f448..d92b1da 100644 --- a/app/controllers/user_controller.ts +++ b/app/controllers/user_controller.ts @@ -21,7 +21,8 @@ export default class UserController { // const avatar = await drive.use().getSignedUrl(key) return User.create({ ...payload, - avatar, + // TODO: No avatar for now!! + // avatar, }) } } diff --git a/app/exceptions/handler.ts b/app/exceptions/handler.ts index 724380d..e1d2e42 100644 --- a/app/exceptions/handler.ts +++ b/app/exceptions/handler.ts @@ -20,7 +20,7 @@ export default class HttpExceptionHandler extends ExceptionHandler { */ async handle(error: any, ctx: HttpContext) { const statusCode = error.status || error.statusCode || 500 - const message = error.message || 'Internal Server Error' + const message = error.messages || error.message || 'Internal Server Error' const stack = this.debug ? error.stack : undefined const response = { status: statusCode, diff --git a/app/models/colle.ts b/app/models/colle.ts index 5085cc1..46fe14d 100644 --- a/app/models/colle.ts +++ b/app/models/colle.ts @@ -1,7 +1,7 @@ import { DateTime } from 'luxon' -import { BaseModel, column, hasMany, hasOne } from '@adonisjs/lucid/orm' +import { BaseModel, belongsTo, column, hasMany } from '@adonisjs/lucid/orm' import User from './user.js' -import type { HasMany, HasOne } from '@adonisjs/lucid/types/relations' +import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations' import Subject from './subject.js' import Room from './room.js' import ColleAttachment from './colle_attachment.js' @@ -11,25 +11,42 @@ export default class Colle extends BaseModel { @column({ isPrimary: true }) declare id: number - @hasOne(() => User) - declare student: HasOne + @belongsTo(() => User, { foreignKey: 'studentId' }) + declare student: BelongsTo - @hasOne(() => Examiner) - declare examiner: HasOne + @column({ serializeAs: null }) + declare studentId: number + + @belongsTo(() => Examiner) + declare examiner: BelongsTo + + @column({ serializeAs: null }) + declare examinerId: number + + // @computed() + // get examinerName(): string { + // return this.examiner?.name + // } // Bjcolle data - @column() + @column({ serializeAs: null }) declare bjsecret: string - @column() + @column({ serializeAs: null }) declare bjid: string // Colle data - @hasOne(() => Subject) - declare subject: HasOne + @belongsTo(() => Subject) + declare subject: BelongsTo - @hasOne(() => Room) - declare room: HasOne + @column({ serializeAs: null }) + declare subjectId: number + + @belongsTo(() => Room) + declare room: BelongsTo + + @column({ serializeAs: null }) + declare roomId: number @column() declare grade: number diff --git a/app/models/user.ts b/app/models/user.ts index d2283d3..f397fe0 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -1,5 +1,5 @@ import { DateTime } from 'luxon' -import { BaseModel, column } from '@adonisjs/lucid/orm' +import { BaseModel, column, computed } from '@adonisjs/lucid/orm' import { DbRememberMeTokensProvider } from '@adonisjs/auth/session' export default class User extends BaseModel { @@ -17,11 +17,13 @@ export default class User extends BaseModel { @column() declare lastName: string - @column() - declare email: string + @computed() + get fullName() { + return `${this.firstName} ${this.lastName}` + } @column() - declare avatar: string + declare email: string @column.dateTime({ autoCreate: true }) declare createdAt: DateTime diff --git a/app/services/auth_service.ts b/app/services/auth_service.ts index da544b4..7bd43b4 100644 --- a/app/services/auth_service.ts +++ b/app/services/auth_service.ts @@ -1,17 +1,13 @@ import encryption from '@adonisjs/core/services/encryption' import cache from '@adonisjs/cache/services/main' import env from '#start/env' -import User from '#models/user' -import { CmpStrAsync } from 'cmpstr' - -const cmp = CmpStrAsync.create().setMetric('levenshtein').setFlags('i') export class AuthService { - async generateToken(email: string, userId: number, expiresIn: string) { - // Generate magic link token - const identifier = `${email}:${userId}` - const token = encryption.encrypt(identifier, expiresIn) - const magicLink = env.get('PUBLIC_AUTH_URL') + '/success?signature=' + token + async generateCode(email: string, expiresIn: string) { + // TODO: Generate magic link token + // const identifier = email + // const token = encryption.encrypt(identifier, expiresIn) + // const magicLink = env.get('PUBLIC_AUTH_URL') + '/success?signature=' + token // Generate code const formattedOTP = Math.floor(Math.random() * 1000000) @@ -19,7 +15,7 @@ export class AuthService { .padStart(6, '0') await cache.set({ key: 'auth:otp:' + formattedOTP, - value: identifier, + value: email, ttl: expiresIn, }) @@ -27,81 +23,52 @@ export class AuthService { return { emailTitle, formattedOTP, - magicLink, + // magicLink, expiresIn, email, - token, - } - } - - parseIdentifier(id: string) { - const [email, userId] = id.split(':') - return { - email: email.toLowerCase(), - userId: parseInt(userId, 10), + // token, } } async validateCode(code: string) { // Validate code const key = 'auth:otp:' + code - const id = await cache.get({ key }) - if (!id) return { success: false, userId: null, email: null } + const email = await cache.get({ key }) + if (!email) return { success: false, email: null } // Delete code from cache await cache.delete({ key }) return { + email, success: true, - ...this.parseIdentifier(id) } } - parseNameFromEmail(email: string): Promise { - // Parse name from email - return new Promise((resolve, reject) => { - try { - const [firstName, lastName] = email.split('@')[0].split('.') - resolve(`${firstName} ${lastName}`.toLowerCase()) - } catch (error) { - reject(new Error('Invalid email format')) - } - }) + generateToken(email: string, expiresIn: string) { + // Generate token + const identifier = email.toLowerCase() + const token = encryption.encrypt(identifier, expiresIn) + + return { + token, + expiresIn, + email: identifier, + } } - findUserByEmail(email: string) { - return User.query().where('email', email).first() - } + validateToken(token: string) { + // Decrypt token + const decrypted = encryption.decrypt(token) + if (!decrypted) return { success: false, email: null } - async findUser(email: string) { - // Try to find user by email - let user: User | null | undefined = await this.findUserByEmail(email) - if (user) return user + // Validate email format + const email = (decrypted as string).toLowerCase() + if (!email.includes('@')) return { success: false, email: null } - // If not found, try to parse name from email and find user by name - const name = await this.parseNameFromEmail(email) - const users = await User.query() - // Select all users with no email - .whereNull('email') - const names = users.map((user) => `${user.firstName} ${user.lastName}`.toLowerCase()) - - // Search for similar names - const data = await cmp.searchAsync(name, names) - if (data.length === 0) { - console.warn(`No user found for email "${email}" or name "${name}"`) - return null + return { + success: true, + email, } - const source = data[0] - const { match: similarity } = await cmp.testAsync(source, name) - - console.log(similarity) - // If similarity is high enough, return the user - if (similarity > 0.8) { - user = users.find((u) => `${u.firstName} ${u.lastName}`.toLowerCase() === source) - return user - } - console.warn( - `No user found for email "${email}" or name "${name}". Similarity: ${similarity} with source "${source}"` - ) } } diff --git a/app/services/colle_service.ts b/app/services/colle_service.ts new file mode 100644 index 0000000..ee20a23 --- /dev/null +++ b/app/services/colle_service.ts @@ -0,0 +1,95 @@ +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' + +export class ColleService { + 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, + } + } +} diff --git a/app/validators/auth.ts b/app/validators/auth.ts index 6c19250..47235e9 100644 --- a/app/validators/auth.ts +++ b/app/validators/auth.ts @@ -9,7 +9,7 @@ export const requestLoginValidator = vine.compile( }) .normalizeEmail({ all_lowercase: true, - }), + }).trim(), }) ) @@ -19,20 +19,36 @@ export const verifyCodeValidator = vine.compile( }) ) -export const magicLinkValidator = vine.compile( +function toTitleCase(value: string) { + return value.replace(/\w\S*/g, (txt) => { + return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase() + }) +} + +export const registerValidator = vine.compile( vine.object({ + firstName: vine.string().minLength(2).maxLength(50).trim().transform(toTitleCase), + lastName: vine.string().minLength(2).maxLength(50).trim().toUpperCase(), + className: vine.string().minLength(2).maxLength(50), token: vine.string(), }) ) -export const listenValidator = vine.compile( - vine.object({ - token: vine.string().uuid(), - }) -) +// TODO: Magic link login +// export const magicLinkValidator = vine.compile( +// vine.object({ +// token: vine.string(), +// }) +// ) -export const exchangeTokenValidator = vine.compile( - vine.object({ - token: vine.string().uuid(), - }) -) +// export const listenValidator = vine.compile( +// vine.object({ +// token: vine.string().uuid(), +// }) +// ) + +// export const exchangeTokenValidator = vine.compile( +// vine.object({ +// token: vine.string().uuid(), +// }) +// ) diff --git a/app/validators/colle.ts b/app/validators/colle.ts new file mode 100644 index 0000000..2a077fc --- /dev/null +++ b/app/validators/colle.ts @@ -0,0 +1,30 @@ +import vine from '@vinejs/vine' + +const colle = vine.object({ + student: vine.string(), + examiner: vine.string(), + subject: vine.string(), + room: vine.string(), + grade: vine.number().min(-1).max(20).optional(), + content: vine.string().optional(), + comment: vine.string().optional(), + date: vine.date({ formats: ['iso8601'] }), + bjsecret: vine.string().optional(), + bjid: vine.string().optional(), + // TODO: Add attachments validation +}) + +const className = vine.string() + +export const createColleValidator = vine.compile(vine.object({ + colle, + className, +})) + + +export const createUpcomingCollesValidator = vine.compile( + vine.object({ + colles: vine.array(colle), + className, + }), +) diff --git a/config/cors.ts b/config/cors.ts index 6366820..e4d8183 100644 --- a/config/cors.ts +++ b/config/cors.ts @@ -9,7 +9,7 @@ import { defineConfig } from '@adonisjs/cors' const corsConfig = defineConfig({ enabled: true, // TODO: Only same domain - origin: true, + origin: '*', methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], headers: true, exposeHeaders: [], diff --git a/database/migrations/1747564644339_create_users_table.ts b/database/migrations/1747564644339_create_users_table.ts index 5681f53..ca31df9 100644 --- a/database/migrations/1747564644339_create_users_table.ts +++ b/database/migrations/1747564644339_create_users_table.ts @@ -10,7 +10,6 @@ export default class extends BaseSchema { table.string('first_name', 50).notNullable() table.string('last_name', 50).notNullable() table.string('email', 254).unique().nullable() - table.string('avatar', 254).notNullable() table.timestamp('created_at').notNullable() table.timestamp('updated_at').nullable() }) diff --git a/docker-compose.yml b/docker-compose.yml index fc08f2e..ec3e83b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,3 +18,18 @@ services: - ./data:/var/lib/postgresql/data ports: - "127.0.0.1:5432:5432" + + pgadmin: + image: dpage/pgadmin4 + container_name: pgadmin4_container + restart: unless-stopped + ports: + - "8888:80" + environment: + PGADMIN_DEFAULT_EMAIL: nathan@lamy-charrier.fr + PGADMIN_DEFAULT_PASSWORD: securepass + volumes: + - pgadmin-data:/var/lib/pgadmin + +volumes: + pgadmin-data: \ No newline at end of file diff --git a/package.json b/package.json index d10b030..4800aa5 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "@adonisjs/transmit": "^2.0.2", "@vinejs/vine": "^3.0.1", "adonis-captcha-guard": "^1.0.1", - "cmpstr": "^3.0.1", "edge.js": "^6.2.1", "luxon": "^3.6.1", "pg": "^8.16.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e606e5..42ed410 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,9 +47,6 @@ importers: adonis-captcha-guard: specifier: ^1.0.1 version: 1.0.1(@adonisjs/core@6.19.0(@adonisjs/assembler@7.8.2(typescript@5.8.3))(@vinejs/vine@3.0.1)(edge.js@6.2.1)) - cmpstr: - specifier: ^3.0.1 - version: 3.0.1 edge.js: specifier: ^6.2.1 version: 6.2.1 @@ -1114,10 +1111,6 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} - cmpstr@3.0.1: - resolution: {integrity: sha512-0gj7U07fS8XaAx4IY854d6xpttqxVKUtaeI/giOguqDrXD9OQaBlW321NwhoE+WCF7rv+Pj55YL5kEW3jVuxEA==} - engines: {node: '>=18.0.0'} - code-block-writer@13.0.3: resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} @@ -3858,8 +3851,6 @@ snapshots: cluster-key-slot@1.1.2: {} - cmpstr@3.0.1: {} - code-block-writer@13.0.3: {} color-convert@2.0.1: diff --git a/resources/views/mails/auth-fallback.edge b/resources/views/mails/auth-fallback.edge index 555ca5a..7de8837 100644 --- a/resources/views/mails/auth-fallback.edge +++ b/resources/views/mails/auth-fallback.edge @@ -2,18 +2,15 @@ Bonjour, Nous avons reçu une demande de connexion à votre compte Khollisé. -Cliquez sur ce lien pour vous connecter : -{{ magicLink }} - -Vous pouvez également entrer ce code de vérification à 6 chiffres : +Utilisez ce code de vérification à 6 chiffres pour vous connecter : {{ formattedOTP }} -Ce lien et ce code expireront dans {{ expiresIn }} et ne peuvent être utilisés qu'une seule fois. +Ce code expirera dans {{ expiresIn }} et ne peut être utilisé qu'une seule fois. -Si vous n'avez pas demandé cette connexion, vous pouvez ignorer cet e-mail. +Si vous n'avez pas demandé cette connexion, vous pouvez ignorer cet email. --- -Cet e-mail a été envoyé à {{ email }} +Cet email a été envoyé à {{ email }} © {{ new Date().getFullYear() }} Khollisé. Tous droits réservés. diff --git a/resources/views/mails/auth.edge b/resources/views/mails/auth.edge index 7b60ad5..f727e70 100644 --- a/resources/views/mails/auth.edge +++ b/resources/views/mails/auth.edge @@ -11,17 +11,7 @@

Bonjour,

- Nous avons reçu une demande de connexion à votre compte Khollisé. Utilisez le bouton ci-dessous pour vous connecter instantanément : -

- - - -
- -

- Vous pouvez également utiliser le code de vérification à 6 chiffres ci-dessous : + Nous avons reçu une demande de connexion à votre compte Khollisé. Utilisez le code de vérification ci-dessous pour vous connecter :

@@ -31,7 +21,7 @@

- Le lien et le code ci-dessus expireront dans {{ expiresIn }} et ne peuvent être utilisés qu'une seule fois. + Le code ci-dessus expirera dans {{ expiresIn }} et ne peut être utilisé qu'une seule fois.

diff --git a/start/limiter.ts b/start/limiter.ts index d1a5aac..bc84b7d 100644 --- a/start/limiter.ts +++ b/start/limiter.ts @@ -13,4 +13,4 @@ import limiter from '@adonisjs/limiter/services/main' export const throttle = limiter.define('global', () => { return limiter.allowRequests(10).every('1 minute') -}) \ No newline at end of file +}) diff --git a/start/routes.ts b/start/routes.ts index 44a7cd5..3f26a15 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -11,7 +11,6 @@ import router from '@adonisjs/core/services/router' import transmit from '@adonisjs/transmit/services/main' import { authThrottle } from './limiters.js' import { throttle } from './limiter.js' -import app from '@adonisjs/core/services/app' import { middleware } from './kernel.js' transmit.registerRoutes() @@ -21,26 +20,37 @@ const AuthController = () => import('#controllers/auth_controller') router.group(() => { router.post('/auth/request', [AuthController, 'requestLogin']).use(authThrottle) router.post('/auth/verify', [AuthController, 'verifyCode']).use(throttle) + router.post('/auth/register', [AuthController, 'register']).use(throttle) + // TODO: Magic link login // router.get('/auth/magic-link', 'AuthController.magicLink').use(throttle) // router.get('/auth/listen', 'AuthController.listen') }) const UserController = () => import('#controllers/user_controller') -if (app.inDev) { - router.post('users', [UserController, 'create']) -} router.get('/users/@me', [UserController, 'me']).use(middleware.auth()) - - // TEST ROUTE import redis from '@adonisjs/redis/services/main' router.get('/', async () => { await redis.publish("jobs_queue", JSON.stringify({ - type: 2 + type: 1, + date: "09/12/2024" })) return { message: 'Hello, world!' } }) // END TEST ROUTE + +const CollesController = () => import('#controllers/colles_controller') +router.group(() => { + // TODO: PRIVATE ROUTES + router.post('/', [CollesController, 'create']) + router.post('/upcoming', [CollesController, 'createUpcoming']) + router.get('/', [CollesController, 'index']).use(middleware.auth()) + router.get('/:colleId', [CollesController, 'show']).use(middleware.auth()) + // router.get('/colles/:id', 'CollesController.show') + // router.put('/colles/:id', 'CollesController.update') + // router.delete('/colles/:id', 'CollesController.delete') +} +).prefix('/colles')