Compare commits
	
		
			No commits in common. "1082f29143cfe4fff62dbf4c220c904b4447e3b3" and "c724dac0860b7ccccc5082c04ba81bfd0a6bd8a8" have entirely different histories.
		
	
	
		
			1082f29143
			...
			c724dac086
		
	
		
					 6 changed files with 39 additions and 118 deletions
				
			
		|  | @ -9,7 +9,7 @@ import User from '#models/user' | |||
| 
 | ||||
| @inject() | ||||
| export default class AuthController { | ||||
|   constructor(private authService: AuthService) {} | ||||
|   constructor(private authService: AuthService) { } | ||||
| 
 | ||||
|   // POST /auth/request
 | ||||
|   async requestLogin({ request, response, captcha }: HttpContext) { | ||||
|  | @ -31,6 +31,7 @@ export default class AuthController { | |||
|     const expiresIn = '15 minutes' | ||||
|     const payload = await this.authService.generateCode(email, expiresIn) | ||||
| 
 | ||||
|     // Send email
 | ||||
|     await mail | ||||
|       .send((message) => { | ||||
|         message | ||||
|  | @ -61,6 +62,7 @@ export default class AuthController { | |||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     // Find user by id
 | ||||
|     const user = await User.findBy('email', email) | ||||
|     if (!user) { | ||||
|       // If the user does not exist, return a token for registration
 | ||||
|  | @ -73,86 +75,53 @@ export default class AuthController { | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Perform login
 | ||||
|     await auth.use('web').login(user, true) // true for remember me
 | ||||
|     return { | ||||
|       success: true, | ||||
|       user | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // GET /auth/autocomplete
 | ||||
|   async listNames({ request }: HttpContext) { | ||||
|     const { className } = request.qs() | ||||
|     if (!className) { | ||||
|       return { | ||||
|         success: false, | ||||
|         message: 'Veuillez spécifier une classe', | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return User.query() | ||||
|       .select('firstName', 'lastName') | ||||
|       .where('className', className) | ||||
|       .orderBy('lastName', 'asc') | ||||
|       .then((users) => { | ||||
|         return { | ||||
|           success: true, | ||||
|           data: users.map((user) => ({ | ||||
|             value: `${user.firstName}::${user.lastName}`, | ||||
|             label: user.fullName, | ||||
|           })), | ||||
|         } | ||||
|       }) | ||||
|   } | ||||
| 
 | ||||
|   // POST /auth/register
 | ||||
|   async register({ request, response, auth }: HttpContext) { | ||||
|     const { name, className, token } = await request.validateUsing(registerValidator) | ||||
|     const { firstName, lastName, className, token } = await request.validateUsing(registerValidator) | ||||
| 
 | ||||
|     // Validate token
 | ||||
|     const { success, email } = this.authService.validateToken(token) | ||||
|     const [firstName, lastName] = name.split('::') | ||||
|     if (!success || !email || !firstName || !lastName) { | ||||
|     if (!success || !email) { | ||||
|       return response.badRequest({ | ||||
|         success: false, | ||||
|         message: 'Votre lien de connexion est invalide ou a expiré.', | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     const user = await User.query() | ||||
|       .where('firstName', firstName) | ||||
|       .where('lastName', lastName) | ||||
|       .where('className', className) | ||||
|       .first() | ||||
|     if (!user) { | ||||
|       return response.badRequest({ | ||||
|         success: false, | ||||
|         message: 'Utilisateur non trouvé. Veuillez vérifier vos informations.', | ||||
|       }) | ||||
|     // 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, | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (user.email && user.email.toLowerCase() !== email.toLowerCase()) { | ||||
|       return response.badRequest({ | ||||
|         success: false, | ||||
|         message: | ||||
|           "L'email associé à votre compte ne correspond pas à celui utilisé pour la connexion.", | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     user.email = email.toLowerCase() // Update email if necessary
 | ||||
|     await user.save() | ||||
|     // 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, | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // POST /auth/logout
 | ||||
|   async logout({ auth }: HttpContext) { | ||||
|     await auth.use('web').logout() | ||||
|     return { | ||||
|       success: true, | ||||
|       user | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,17 +43,13 @@ export default class CollesController { | |||
|       .preload('subject') | ||||
|       .preload('room') | ||||
|       .first() | ||||
|     // TODO: Include BJID and BJSecret !
 | ||||
|     if (!colle) { | ||||
|       return response.notFound({ message: 'Colle not found' }) | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       success: true, | ||||
|       data: { | ||||
|         ...colle.serialize(), | ||||
|         bjid: colle.bjid, | ||||
|         bjsecret: colle.bjsecret, | ||||
|       }, | ||||
|       data: colle, | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -71,14 +67,6 @@ export default class CollesController { | |||
|       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, | ||||
|  |  | |||
|  | @ -11,10 +11,10 @@ export default class User extends BaseModel { | |||
|   @column() | ||||
|   declare className: string | ||||
| 
 | ||||
|   @column({ serializeAs: null}) | ||||
|   @column() | ||||
|   declare firstName: string | ||||
| 
 | ||||
|   @column({ serializeAs: null}) | ||||
|   @column() | ||||
|   declare lastName: string | ||||
| 
 | ||||
|   @computed() | ||||
|  | @ -22,7 +22,7 @@ export default class User extends BaseModel { | |||
|     return `${this.firstName} ${this.lastName}` | ||||
|   } | ||||
| 
 | ||||
|   @column({ serializeAs: null }) | ||||
|   @column() | ||||
|   declare email: string | ||||
| 
 | ||||
|   @column.dateTime({ autoCreate: true }) | ||||
|  |  | |||
|  | @ -92,45 +92,4 @@ export class ColleService { | |||
|       favoriteColles, | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Pre-process HTML content to render LaTeX
 | ||||
|   private removeTrailingLines(htmlString: string) { | ||||
|     return htmlString.replace(/(<br\s*\/?>\s*)+$/gi, '').trim() | ||||
|   } | ||||
| 
 | ||||
|   private extractLatexImages(html: string) { | ||||
|     const imgRegex = /<img[^>]+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, ' ') | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -19,9 +19,16 @@ export const verifyCodeValidator = 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({ | ||||
|     name: vine.string().minLength(2).maxLength(50).trim(), | ||||
|     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(), | ||||
|   }) | ||||
|  |  | |||
|  | @ -21,8 +21,6 @@ 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) | ||||
|   router.post('/auth/logout', [AuthController, 'logout']) | ||||
|   router.get('/auth/autocomplete', [AuthController, 'listNames']).use(throttle) | ||||
|   // TODO: Magic link login
 | ||||
|   // router.get('/auth/magic-link', 'AuthController.magicLink').use(throttle)
 | ||||
|   // router.get('/auth/listen', 'AuthController.listen')
 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue