From b281724a1a0901e6b8fc055dbc6836a226a20a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kiss=20D=C3=A1vid?= Date: Wed, 1 Apr 2026 12:13:38 +0200 Subject: [PATCH] room reservation model, controller, seeder and migration files added --- .../room_reservations_controller.ts | 128 ++++++++++++++++++ app/models/reservation.ts | 42 ++++++ app/models/room.ts | 27 ++++ app/validators/reservation.ts | 53 ++++++++ .../1775037701330_create_rooms_table.ts | 21 +++ ...1775037701943_create_reservations_table.ts | 26 ++++ database/seeders/room_seeder.ts | 33 +++++ 7 files changed, 330 insertions(+) create mode 100644 app/controllers/room_reservations_controller.ts create mode 100644 app/models/reservation.ts create mode 100644 app/models/room.ts create mode 100644 app/validators/reservation.ts create mode 100644 database/migrations/1775037701330_create_rooms_table.ts create mode 100644 database/migrations/1775037701943_create_reservations_table.ts create mode 100644 database/seeders/room_seeder.ts diff --git a/app/controllers/room_reservations_controller.ts b/app/controllers/room_reservations_controller.ts new file mode 100644 index 0000000..834f155 --- /dev/null +++ b/app/controllers/room_reservations_controller.ts @@ -0,0 +1,128 @@ +import type { HttpContext } from '@adonisjs/core/http' +import Room from '#models/room' +import Reservation from '#models/reservation' +import { + createReservationValidator, + updateReservationValidator, + createRoomValidator, + updateRoomValidator, +} from '#validators/reservation' +import logger from '@adonisjs/core/services/logger' + +export default class RoomReservationsController { + /** + * List all rooms. + */ + async listRooms() { + return Room.all() + } + + /** + * Create a new room (Admin only). + */ + async createRoom({ request, response }: HttpContext) { + try { + const data = await request.validateUsing(createRoomValidator) + const room = await Room.create(data) + return room + } catch (error) { + logger.error('Room creation failed: %s', error) + return response.internalServerError({ message: 'Room creation failed' }) + } + } + + /** + * Update a room (Admin only). + */ + async updateRoom({ request, response }: HttpContext) { + try { + const data = await request.validateUsing(updateRoomValidator) + const room = await Room.find(data.id) + if (!room) { + return response.notFound({ message: 'Room not found' }) + } + room.merge(data) + await room.save() + return room + } catch (error) { + logger.error('Room update failed: %s', error) + return response.internalServerError({ message: 'Room update failed' }) + } + } + + /** + * Delete a room (Admin only). + */ + async deleteRoom({ request, response }: HttpContext) { + const { id } = request.body() + try { + const room = await Room.find(id) + if (!room) { + return response.notFound({ message: 'Room not found' }) + } + await room.delete() + return response.ok({ message: 'Room deleted' }) + } catch (error) { + logger.error('Room deletion failed: %s', error) + return response.internalServerError({ message: 'Room deletion failed' }) + } + } + + /** + * List all reservations (Frontend/Admin). + */ + async index() { + return Reservation.query().preload('room').orderBy('startsAt', 'desc') + } + + /** + * Create a new reservation (Admin only). + */ + async store({ request, response }: HttpContext) { + try { + const data = await request.validateUsing(createReservationValidator) + const reservation = await Reservation.create(data) + return reservation + } catch (error) { + logger.error('Reservation creation failed: %s', error) + return response.internalServerError({ message: 'Reservation creation failed' }) + } + } + + /** + * Update a reservation (Admin only). + */ + async update({ request, response }: HttpContext) { + try { + const data = await request.validateUsing(updateReservationValidator) + const reservation = await Reservation.find(data.id) + if (!reservation) { + return response.notFound({ message: 'Reservation not found' }) + } + reservation.merge(data) + await reservation.save() + return reservation + } catch (error) { + logger.error('Reservation update failed: %s', error) + return response.internalServerError({ message: 'Reservation update failed' }) + } + } + + /** + * Delete a reservation (Admin only). + */ + async destroy({ request, response }: HttpContext) { + const { id } = request.body() + try { + const reservation = await Reservation.find(id) + if (!reservation) { + return response.notFound({ message: 'Reservation not found' }) + } + await reservation.delete() + return response.ok({ message: 'Reservation deleted' }) + } catch (error) { + logger.error('Reservation deletion failed: %s', error) + return response.internalServerError({ message: 'Reservation deletion failed' }) + } + } +} diff --git a/app/models/reservation.ts b/app/models/reservation.ts new file mode 100644 index 0000000..28ba912 --- /dev/null +++ b/app/models/reservation.ts @@ -0,0 +1,42 @@ +import { DateTime } from 'luxon' +import { BaseModel, column, belongsTo } from '@adonisjs/lucid/orm' +import type { BelongsTo } from '@adonisjs/lucid/types/relations' +import Room from '#models/room' + +export default class Reservation extends BaseModel { + @column({ isPrimary: true }) + declare id: number + + @column() + declare roomId: number + + @column() + declare customerName: string + + @column() + declare customerPhone: string + + @column() + declare guestsCount: number + + @column.dateTime() + declare startsAt: DateTime + + @column.dateTime() + declare endsAt: DateTime + + @column() + declare status: 'confirmed' | 'cancelled' + + @column() + declare notes: string | null + + @belongsTo(() => Room) + declare room: BelongsTo + + @column.dateTime({ autoCreate: true }) + declare createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updatedAt: DateTime +} diff --git a/app/models/room.ts b/app/models/room.ts new file mode 100644 index 0000000..be64caa --- /dev/null +++ b/app/models/room.ts @@ -0,0 +1,27 @@ +import { DateTime } from 'luxon' +import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm' +import type { HasMany } from '@adonisjs/lucid/types/relations' +import Reservation from '#models/reservation' + +export default class Room extends BaseModel { + @column({ isPrimary: true }) + declare id: number + + @column() + declare name: string + + @column() + declare capacity: number + + @column() + declare description: string | null + + @hasMany(() => Reservation) + declare reservations: HasMany + + @column.dateTime({ autoCreate: true }) + declare createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updatedAt: DateTime +} diff --git a/app/validators/reservation.ts b/app/validators/reservation.ts new file mode 100644 index 0000000..0392586 --- /dev/null +++ b/app/validators/reservation.ts @@ -0,0 +1,53 @@ +import vine from '@vinejs/vine' + +export const createReservationValidator = vine.compile( + vine.object({ + roomId: vine.number().exists(async (db, value) => { + const room = await db.from('rooms').where('id', value).first() + return !!room + }), + customerName: vine.string().trim(), + customerPhone: vine.string().trim(), + guestsCount: vine.number().positive(), + startsAt: vine.string().trim(), + endsAt: vine.string().trim(), + notes: vine.string().trim().optional(), + }) +) + +export const updateReservationValidator = vine.compile( + vine.object({ + id: vine.number(), + roomId: vine + .number() + .exists(async (db, value) => { + const room = await db.from('rooms').where('id', value).first() + return !!room + }) + .optional(), + customerName: vine.string().trim().optional(), + customerPhone: vine.string().trim().optional(), + guestsCount: vine.number().positive().optional(), + startsAt: vine.string().trim().optional(), + endsAt: vine.string().trim().optional(), + status: vine.enum(['confirmed', 'cancelled']).optional(), + notes: vine.string().trim().optional(), + }) +) + +export const createRoomValidator = vine.compile( + vine.object({ + name: vine.string().trim(), + capacity: vine.number().positive(), + description: vine.string().trim().optional(), + }) +) + +export const updateRoomValidator = vine.compile( + vine.object({ + id: vine.number(), + name: vine.string().trim().optional(), + capacity: vine.number().positive().optional(), + description: vine.string().trim().optional(), + }) +) diff --git a/database/migrations/1775037701330_create_rooms_table.ts b/database/migrations/1775037701330_create_rooms_table.ts new file mode 100644 index 0000000..ebcaeda --- /dev/null +++ b/database/migrations/1775037701330_create_rooms_table.ts @@ -0,0 +1,21 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'rooms' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + table.string('name').notNullable() + table.integer('capacity').notNullable() + table.string('description').nullable() + + table.timestamp('created_at') + table.timestamp('updated_at') + }) + } + + async down() { + this.schema.dropTable(this.tableName) + } +} diff --git a/database/migrations/1775037701943_create_reservations_table.ts b/database/migrations/1775037701943_create_reservations_table.ts new file mode 100644 index 0000000..0c29621 --- /dev/null +++ b/database/migrations/1775037701943_create_reservations_table.ts @@ -0,0 +1,26 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'reservations' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + table.integer('room_id').unsigned().references('id').inTable('rooms').onDelete('CASCADE') + table.string('customer_name').notNullable() + table.string('customer_phone').notNullable() + table.integer('guests_count').notNullable() + table.timestamp('starts_at').notNullable() + table.timestamp('ends_at').notNullable() + table.string('status').defaultTo('confirmed') // confirmed, cancelled + table.text('notes').nullable() + + table.timestamp('created_at') + table.timestamp('updated_at') + }) + } + + async down() { + this.schema.dropTable(this.tableName) + } +} diff --git a/database/seeders/room_seeder.ts b/database/seeders/room_seeder.ts new file mode 100644 index 0000000..c451561 --- /dev/null +++ b/database/seeders/room_seeder.ts @@ -0,0 +1,33 @@ +import { BaseSeeder } from '@adonisjs/lucid/seeders' +import Room from '#models/room' + +export default class extends BaseSeeder { + async run() { + await Room.updateOrCreate( + { name: 'Kisterem' }, + { + name: 'Kisterem', + capacity: 20, + description: 'Cozy small room for private gatherings.', + } + ) + + await Room.updateOrCreate( + { name: 'Nagyterem' }, + { + name: 'Nagyterem', + capacity: 50, + description: 'Large hall for big events and weddings.', + } + ) + + await Room.updateOrCreate( + { name: 'Terasz' }, + { + name: 'Terasz', + capacity: 30, + description: 'Outdoor terrace with a view.', + } + ) + } +}