Compare commits

..

No commits in common. "06fc137057faaba37cc81675187130de181860b1" and "1082f29143cfe4fff62dbf4c220c904b4447e3b3" have entirely different histories.

11 changed files with 49 additions and 225 deletions

View file

@ -3,7 +3,6 @@ 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 redis from '@adonisjs/redis/services/main'
import { DateTime } from 'luxon'
@inject()
@ -43,7 +42,6 @@ export default class CollesController {
.preload('examiner')
.preload('subject')
.preload('room')
.preload('attachments')
.first()
if (!colle) {
return response.notFound({ message: 'Colle not found' })
@ -59,39 +57,6 @@ export default class CollesController {
}
}
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)
@ -128,54 +93,19 @@ export default class CollesController {
}
// Check if the colle already exists
const existing = await Colle.query()
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 (existing) {
// Merge the new data with the existing colle
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()
}
}
}
return existing.save()
if (colle) {
Object.assign(colle, colleData)
return colle.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
// Create the colle
return Colle.create(colleData)
}
async createUpcoming({ request }: HttpContext) {

View file

@ -1,17 +0,0 @@
import type { HttpContext } from '@adonisjs/core/http'
import { inject } from '@adonisjs/core'
import { SubjectService } from '#services/subject_service'
@inject()
export default class SubjectsController {
constructor(private subjectService: SubjectService) {}
// GET /subjects
async index({ auth }: HttpContext) {
const data = await this.subjectService.getAll(auth.user!.className)
return {
success: true,
data,
}
}
}

View file

@ -1,57 +1,28 @@
import { SubjectService } from '#services/subject_service'
import { updateUserValidator } from '#validators/user'
import { inject } from '@adonisjs/core'
import User from '#models/user'
import { createUserValidator } from '#validators/user'
import type { HttpContext } from '@adonisjs/core/http'
import { cuid } from '@adonisjs/core/helpers'
@inject()
export default class UserController {
constructor(private subjectService: SubjectService) {}
// GET /users/@me
async me({ auth }: HttpContext) {
return {
success: true,
data: {
...auth.user?.serialize(),
email: auth.user!.email || '',
},
data: auth.user,
}
}
// POST /users/@me
// Update user preferences (for subjects)
async update({ request, response, auth }: HttpContext) {
const user = auth.user!
const { preferences: data } = await request.validateUsing(updateUserValidator)
const preferences = user.extras?.preferences || []
// Validate subject names
const validSubjects = await this.subjectService.getAll(user.className)
for (const { name, emoji, color } of data) {
if (!validSubjects.includes(name)) {
return response.badRequest({
message: `Invalid subject name: ${name}`,
})
}
const existing = preferences.find((p: any) => p.name === name)
if (existing) {
// Update
existing.emoji = emoji
existing.color = color
} else {
// Create new preference
preferences.push({ name, emoji, color })
}
}
user.extras = {
...user.extras,
preferences,
}
await user.save()
return {
success: true,
data: user,
}
// POST /users
async create({ request }: HttpContext) {
const payload = await request.validateUsing(createUserValidator)
// Save avatar
const avatar = `avatars/${cuid()}.${payload.avatar.extname}`
await payload.avatar.moveToDisk(avatar)
// const avatar = await drive.use().getSignedUrl(key)
return User.create({
...payload,
// TODO: No avatar for now!!
// avatar,
})
}
}

View file

@ -4,9 +4,6 @@ export default class ColleAttachment extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare colleId: number
@column()
declare name: string

View file

@ -11,10 +11,10 @@ export default class User extends BaseModel {
@column()
declare className: string
@column({ serializeAs: null })
@column({ serializeAs: null})
declare firstName: string
@column({ serializeAs: null })
@column({ serializeAs: null})
declare lastName: string
@computed()
@ -25,14 +25,6 @@ export default class User extends BaseModel {
@column({ serializeAs: null })
declare email: string
@column({ serializeAs: null })
declare extras: Record<string, any>
@computed()
get preferences(): { name: string; emoji: string; color: string }[] {
return this.extras?.preferences || []
}
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime

View file

@ -3,19 +3,8 @@ import Examiner from '#models/examiner'
import Room from '#models/room'
import Subject from '#models/subject'
import User from '#models/user'
import redis from '@adonisjs/redis/services/main'
export class ColleService {
async getHealthyUntil(className: string) {
const healtyUntil = await redis.get(`healthy_until_${className}`)
return new Date(healtyUntil || '')
}
async getLastSync(className: string) {
const lastSync = await redis.get(`last_sync_${className}`)
return new Date(lastSync || '')
}
async getStudent(studentName: string, className: string) {
// Find or create a student by name
const { firstName, lastName } = this.splitNames(studentName)
@ -75,7 +64,6 @@ export class ColleService {
.preload('examiner')
.preload('subject')
.preload('room')
.preload('attachments')
.where('date', '>=', startDate)
.where('date', '<=', endDate)
.whereHas('student', (query) => {
@ -86,11 +74,10 @@ export class ColleService {
.orderBy('date', 'asc')
const studentColles = await Colle.query()
.preload('student')
.preload('examiner')
.preload('subject')
.preload('room')
.preload('attachments')
.preload('student')
.where('date', '>=', startDate)
.where('date', '<=', endDate)
.where('studentId', student.id)
@ -103,8 +90,6 @@ export class ColleService {
classColles,
studentColles,
favoriteColles,
healthyUntil: await this.getHealthyUntil(student.className),
lastSync: await this.getLastSync(student.className),
}
}

View file

@ -1,18 +0,0 @@
import Colle from '#models/colle'
import Subject from '#models/subject'
export class SubjectService {
async getAll(className: string): Promise<string[]> {
const subjectsIds = (
await Colle.query()
.distinct('subjectId')
.select('subjectId')
.whereHas('student', (query) => {
query.where('className', className)
})
).map((colle) => colle.subjectId)
const subjects = await Subject.query().whereIn('id', subjectsIds).select('name')
return subjects.map((subject) => subject.name)
}
}

View file

@ -11,12 +11,7 @@ const colle = vine.object({
date: vine.date({ formats: ['iso8601'] }),
bjsecret: vine.string().optional(),
bjid: vine.string().optional(),
attachments: vine.array(
vine.object({
url: vine.string(),
name: vine.string().maxLength(255),
})
).optional(),
// TODO: Add attachments validation
})
const className = vine.string()

View file

@ -1,13 +1,10 @@
import vine from '@vinejs/vine'
export const updateUserValidator = vine.compile(
export const createUserValidator = vine.compile(
vine.object({
preferences: vine.array(
vine.object({
name: vine.string(),
emoji: vine.string().maxLength(12),
color: vine.string().maxLength(12),
})
),
firstName: vine.string().minLength(2).maxLength(50),
lastName: vine.string().minLength(2).maxLength(50),
className: vine.string().minLength(2).maxLength(10),
avatar: vine.file(),
})
)

View file

@ -1,18 +0,0 @@
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
protected tableName = 'users'
async up() {
this.schema.alterTable(this.tableName, (table) => {
// Adding a JSON column for extras (can hold flexible data)
table.jsonb('extras').nullable().after('updated_at')
})
}
async down() {
this.schema.alterTable(this.tableName, (table) => {
table.dropColumn('extras')
})
}
}

View file

@ -8,13 +8,12 @@
*/
import router from '@adonisjs/core/services/router'
// import transmit from '@adonisjs/transmit/services/main'
import transmit from '@adonisjs/transmit/services/main'
import { authThrottle } from './limiters.js'
import { throttle } from './limiter.js'
import { middleware } from './kernel.js'
// TODO: Magic link login
// transmit.registerRoutes()
transmit.registerRoutes()
const AuthController = () => import('#controllers/auth_controller')
@ -30,19 +29,30 @@ router.group(() => {
})
const UserController = () => import('#controllers/user_controller')
router.get('/users/@me', [UserController, 'me']).use(middleware.auth())
router.post('/users/@me', [UserController, 'update']).use(middleware.auth())
const SubjectsController = () => import('#controllers/subjects_controller')
router.get('/subjects', [SubjectsController, 'index']).use(middleware.auth())
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: 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.post('/:colleId/refresh', [CollesController, 'refresh']).use(middleware.auth())
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')