Compare commits

...

5 commits

Author SHA1 Message Date
Nathan Lamy
15a435e5a3 feat: query past year colles 2026-02-24 23:36:44 +01:00
Nathan Lamy
ede2eba681 feat: add merge user 2026-02-24 23:28:26 +01:00
Nathan Lamy
1682132e1c fix: notifications spam 2026-02-24 23:16:26 +01:00
Nathan Lamy
02ce8bd5b4 fix: mutable reference issue 2026-02-24 23:07:40 +01:00
Nathan Lamy
b57d12a400 fix: wrong start date 2026-02-24 23:06:49 +01:00
9 changed files with 105 additions and 19 deletions

View file

@ -25,7 +25,7 @@ export default class CollesController {
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())
const data = await this.service.getColles(auth.user!, startDate, endDate)
return {
success: true,
data,
@ -187,9 +187,9 @@ export default class CollesController {
parseFloat(beforeColle.grade)
)
}
// if (!deepEqual(beforeColle, afterColle)) {
// await this.notificationService.sendNotification('COLLE_UPDATED', existing)
// }
if (hasChanged(beforeColle, afterColle)) {
await this.notificationService.sendNotification('COLLE_UPDATED', existing)
}
return existing.save()
}
const colle = await Colle.create(colleData)
@ -269,9 +269,9 @@ export default class CollesController {
const afterColle = oldColle.serialize()
upcomingCollesIds.delete(oldColle.id)
if (beforeColle.room !== afterColle.room) {
if (beforeColle.roomId !== afterColle.roomId) {
await this.notificationService.sendNotification('ROOM_UPDATED', oldColle)
} else if (!deepEqual(beforeColle, afterColle)) {
} else if (hasChanged(beforeColle, afterColle)) {
await this.notificationService.sendNotification('COLLE_UPDATED', oldColle)
}
}
@ -298,6 +298,16 @@ export default class CollesController {
}
}
function deepEqual(a: any, b: any) {
return JSON.stringify(a) === JSON.stringify(b)
}
const propsToCheck = [
'examinerId',
'bjsecret',
'bjid',
'grade',
'content',
'comment',
'roomId',
'date',
'subjectId',
]
const hasChanged = (beforeColle: any, afterColle: any) =>
propsToCheck.some((prop) => beforeColle[prop] !== afterColle[prop])

View file

@ -1,7 +1,8 @@
import { SubjectService } from '#services/subject_service'
import { updateUserValidator } from '#validators/user'
import { updateUserValidator, matchStudentValidator, mergeStudentValidator } from '#validators/user'
import { inject } from '@adonisjs/core'
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
@inject()
export default class UserController {
@ -54,4 +55,32 @@ export default class UserController {
data: user,
}
}
// GET /match-student
async matchStudent({ request, response }: HttpContext) {
const { firstName, lastName, classId } = await request.validateUsing(matchStudentValidator)
const user = await User.query()
.where('firstName', firstName)
.andWhere('lastName', lastName)
.andWhere('className', classId)
.first()
if (!user) {
return response.notFound({ message: 'User not found' })
}
return { success: true, data: user }
}
// POST /merge-student
async mergeStudent({ request, auth }: HttpContext) {
const user = auth.user!
const { pastStudentId } = await request.validateUsing(mergeStudentValidator)
user.pastStudentId = pastStudentId
await user.save()
return { success: true, data: user }
}
}

View file

@ -8,6 +8,9 @@ export default class User extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare pastStudentId: number | null
@column()
declare className: string

View file

@ -4,6 +4,7 @@ import Room from '#models/room'
import Subject from '#models/subject'
import User from '#models/user'
import redis from '@adonisjs/redis/services/main'
import { DateTime } from 'luxon'
export class ColleService {
async getHealthyUntil(className: string) {
@ -69,15 +70,15 @@ export class ColleService {
return room
}
async getColles(student: User, startDate: string, endDate: string) {
async getColles(student: User, startDate: DateTime, endDate: DateTime) {
const classColles = await Colle.query()
.preload('student')
.preload('examiner')
.preload('subject')
.preload('room')
.preload('attachments')
.where('date', '>=', startDate)
.where('date', '<=', endDate)
.where('date', '>=', startDate.toISO()!)
.where('date', '<=', endDate.toISO()!)
.whereHas('student', (query) => {
query.where('className', student.className)
})
@ -85,17 +86,26 @@ export class ColleService {
.whereNotNull('grade')
.orderBy('date', 'asc')
const studentColles = await Colle.query()
const studentCollesQuery = Colle.query()
.preload('student')
.preload('examiner')
.preload('subject')
.preload('room')
.preload('attachments')
.where('date', '>=', startDate)
.where('date', '<=', endDate)
.where('studentId', student.id)
.where('date', '>=', startDate.toISO()!)
.where('date', '<=', endDate.toISO()!)
.orderBy('date', 'asc')
if (student.pastStudentId) {
studentCollesQuery.where((query) => {
query.where('studentId', student.id).orWhere('studentId', student.pastStudentId!)
})
} else {
studentCollesQuery.where('studentId', student.id)
}
const studentColles = await studentCollesQuery
// TODO: Favorite colles
const favoriteColles = [] as Colle[]

View file

@ -112,7 +112,7 @@ export class GradeService {
while (periodStartDate < startDate.plus({ months })) {
const periodEndDate = periodStartDate.endOf('month')
const periodColles = this.getPeriodColles(colles, startDate, periodEndDate)
const periodColles = this.getPeriodColles(colles, periodStartDate, periodEndDate)
const periodAverage = this.calculateAverage(periodColles)
const subjectAverages = await this.getSubjectAverages(

View file

@ -49,7 +49,7 @@ export class NotificationService {
if (notificationId === 'ROOM_UPDATED') {
await colle.load('room')
}
const payload = Object.assign(DEFAULT_NOTIFICATION, NOTIFICATIONS[notificationId](colle, args))
const payload = Object.assign({}, DEFAULT_NOTIFICATION, NOTIFICATIONS[notificationId](colle, args))
const subscriptions = await Subscription.query()
.where('enabled', true)

View file

@ -11,3 +11,17 @@ export const updateUserValidator = vine.compile(
),
})
)
export const matchStudentValidator = vine.compile(
vine.object({
firstName: vine.string(),
lastName: vine.string(),
classId: vine.string(),
})
)
export const mergeStudentValidator = vine.compile(
vine.object({
pastStudentId: vine.number(),
})
)

View file

@ -0,0 +1,18 @@
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
protected tableName = 'add_past_student_id_to_users'
async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.timestamp('created_at')
table.timestamp('updated_at')
})
}
async down() {
this.schema.dropTable(this.tableName)
}
}

View file

@ -39,6 +39,8 @@ router
const UserController = () => import('#controllers/user_controller')
router.get('/users/@me', [UserController, 'me']).use(middleware.auth())
router.post('/users/@me', [UserController, 'update']).use(middleware.auth())
router.get('/match-student', [UserController, 'matchStudent']).use(middleware.auth())
router.post('/merge-student', [UserController, 'mergeStudent']).use(middleware.auth())
const SubjectsController = () => import('#controllers/subjects_controller')
router.get('/subjects', [SubjectsController, 'index']).use(middleware.auth())