diff --git a/app/controllers/subjects_controller.ts b/app/controllers/subjects_controller.ts new file mode 100644 index 0000000..cc61a96 --- /dev/null +++ b/app/controllers/subjects_controller.ts @@ -0,0 +1,17 @@ +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, + } + } +} diff --git a/app/controllers/user_controller.ts b/app/controllers/user_controller.ts index d92b1da..244a17d 100644 --- a/app/controllers/user_controller.ts +++ b/app/controllers/user_controller.ts @@ -1,9 +1,12 @@ -import User from '#models/user' -import { createUserValidator } from '#validators/user' +import { SubjectService } from '#services/subject_service' +import { updateUserValidator } from '#validators/user' +import { inject } from '@adonisjs/core' 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 { @@ -12,17 +15,40 @@ export default class UserController { } } - // 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, - }) + // 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, + } } } diff --git a/app/models/user.ts b/app/models/user.ts index d6146da..403f8f7 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -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,6 +25,14 @@ export default class User extends BaseModel { @column({ serializeAs: null }) declare email: string + @column({ serializeAs: null }) + declare extras: Record + + @computed() + get preferences(): { name: string; emoji: string; color: string }[] { + return this.extras?.preferences || [] + } + @column.dateTime({ autoCreate: true }) declare createdAt: DateTime diff --git a/app/services/subject_service.ts b/app/services/subject_service.ts new file mode 100644 index 0000000..8b54320 --- /dev/null +++ b/app/services/subject_service.ts @@ -0,0 +1,18 @@ +import Colle from '#models/colle' +import Subject from '#models/subject' + +export class SubjectService { + async getAll(className: string): Promise { + 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) + } +} diff --git a/app/validators/user.ts b/app/validators/user.ts index 9504d9a..7b64edf 100644 --- a/app/validators/user.ts +++ b/app/validators/user.ts @@ -1,10 +1,13 @@ import vine from '@vinejs/vine' -export const createUserValidator = vine.compile( +export const updateUserValidator = vine.compile( vine.object({ - firstName: vine.string().minLength(2).maxLength(50), - lastName: vine.string().minLength(2).maxLength(50), - className: vine.string().minLength(2).maxLength(10), - avatar: vine.file(), + preferences: vine.array( + vine.object({ + name: vine.string(), + emoji: vine.string().maxLength(12), + color: vine.string().maxLength(12), + }) + ), }) ) diff --git a/database/migrations/1755608664070_create_user_extras_table.ts b/database/migrations/1755608664070_create_user_extras_table.ts new file mode 100644 index 0000000..44e9a6b --- /dev/null +++ b/database/migrations/1755608664070_create_user_extras_table.ts @@ -0,0 +1,18 @@ +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') + }) + } +} diff --git a/start/routes.ts b/start/routes.ts index 81e0e3f..ad2442b 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -8,12 +8,13 @@ */ 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' -transmit.registerRoutes() +// TODO: Magic link login +// transmit.registerRoutes() const AuthController = () => import('#controllers/auth_controller') @@ -29,8 +30,11 @@ 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()) // TEST ROUTE import redis from '@adonisjs/redis/services/main' @@ -38,7 +42,8 @@ import redis from '@adonisjs/redis/services/main' router.get('/', async () => { await redis.publish("jobs_queue", JSON.stringify({ type: 1, - date: "09/12/2024" + date: "20/09/2019", + class_name: "MPSI 2", })) return { message: 'Hello, world!' } }) @@ -51,8 +56,5 @@ router.group(() => { 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')