diff --git a/db/migrations/20251030154438_reservation_team_nullable.ts b/db/migrations/20251030154438_reservation_team_nullable.ts new file mode 100644 index 00000000..9aa4ffc9 --- /dev/null +++ b/db/migrations/20251030154438_reservation_team_nullable.ts @@ -0,0 +1,16 @@ +import type { Knex } from "knex"; + + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable("reservations", (table) => { + table.uuid("team_id").nullable().alter(); + }); +} + + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable("reservations", (table) => { + table.uuid("team_id").notNullable().alter(); + }); +} + diff --git a/src/modules/reservation/reservation.controller.ts b/src/modules/reservation/reservation.controller.ts index a837c5fc..403cd628 100644 --- a/src/modules/reservation/reservation.controller.ts +++ b/src/modules/reservation/reservation.controller.ts @@ -28,9 +28,10 @@ class CreateReservationEntity { @Type(() => Number) locationId: number; - @ApiProperty() + @ApiProperty({ required: false }) + @IsOptional() @IsString() - teamId: string; + teamId?: string; @ApiProperty() @IsNumber() diff --git a/src/modules/reservation/reservation.service.ts b/src/modules/reservation/reservation.service.ts index 04a0e5b6..57b85eed 100644 --- a/src/modules/reservation/reservation.service.ts +++ b/src/modules/reservation/reservation.service.ts @@ -23,7 +23,7 @@ export interface UpdateReservationDto { export interface CreateReservationDto { locationId: number; - teamId: string; + teamId?: string; // Make teamId optional since it can be null for organizer reservations startTime: number; endTime: number; hackathonId: string; @@ -77,7 +77,7 @@ export class ReservationService { const reservation = this.reservationRepo .createOne({ locationId: data.locationId, - teamId: data.teamId, // Organizer reservations are not team-specific + teamId: data.teamId && data.teamId.trim() !== '' ? data.teamId : null, startTime: data.startTime, endTime: data.endTime, hackathonId: data.hackathonId, @@ -195,9 +195,13 @@ export class ReservationService { // 1. Validate basic constraints (hackathon bounds, location exists, duration) await this.validateBasicConstraints(data); - // 2. Validate team constraints - await this.validateTeamConstraints(data.teamId, userId, data.hackathonId); - console.log("validated team constraints"); + // 2. Validate team constraints only if teamId is provided + if (data.teamId && data.teamId.trim() !== '') { + await this.validateTeamConstraints(data.teamId, userId, data.hackathonId); + console.log("validated team constraints"); + } else { + console.log("skipping team validation - no teamId provided"); + } // 3. Check for conflicts (blackouts, capacity, team double booking) await this.checkConflicts(data); @@ -310,26 +314,31 @@ export class ReservationService { // If capacity is 0, skip capacity check (unlimited) // 2. Check team double booking (team can't have multiple reservations) - const teamConflicts = await Reservation.query() - .where("teamId", data.teamId) - .where("reservationType", ReservationType.PARTICIPANT) - .where("hackathonId", data.hackathonId) - .where(function (this) { - this.where(function (this) { - this.where("startTime", "<", data.endTime).where( - "endTime", - ">", - data.startTime, - ); + // Only check team conflicts if teamId is provided + if (data.teamId && data.teamId.trim() !== '') { + const teamConflicts = await Reservation.query() + .where("teamId", data.teamId) + .where("reservationType", ReservationType.PARTICIPANT) + .where("hackathonId", data.hackathonId) + .where(function (this) { + this.where(function (this) { + this.where("startTime", "<", data.endTime).where( + "endTime", + ">", + data.startTime, + ); + }); }); - }); - console.log("teamConflicts:", teamConflicts); + console.log("teamConflicts:", teamConflicts); - if (teamConflicts.length > 0) { - throw new BadRequestException( - "Team already has a reservation during this time", - ); + if (teamConflicts.length > 0) { + throw new BadRequestException( + "Team already has a reservation during this time", + ); + } + } else { + console.log("skipping team conflict check - no teamId provided"); } } diff --git a/yarn.lock b/yarn.lock index a6ff0100..daf88765 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2050,13 +2050,13 @@ "@types/send" "*" "@types/express@*", "@types/express@^5.0.0": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.4.tgz#975e7fc1097066a83b992fd2bb8a4819622e8bae" - integrity sha512-g64dbryHk7loCIrsa0R3shBnEu5p6LPJ09bu9NG58+jz+cRUjFrc3Bz0kNQ7j9bXeCsrRDvNET1G54P/GJkAyA== + version "5.0.5" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.5.tgz#3ba069177caa34ab96585ca23b3984d752300cdc" + integrity sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^5.0.0" - "@types/serve-static" "*" + "@types/serve-static" "^1" "@types/express@^4.17.20": version "4.17.23" @@ -2157,12 +2157,12 @@ dependencies: "@types/express" "*" -"@types/node@*", "@types/node@^22.10.5", "@types/node@^22.8.7": - version "22.18.11" - resolved "https://registry.npmjs.org/@types/node/-/node-22.18.11.tgz" - integrity sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ== +"@types/node@*": + version "24.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.9.1.tgz#b7360b3c789089e57e192695a855aa4f6981a53c" + integrity sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg== dependencies: - undici-types "~6.21.0" + undici-types "~7.16.0" "@types/node@>=10.0.0": version "24.2.0" @@ -2178,6 +2178,13 @@ dependencies: undici-types "~7.14.0" +"@types/node@^22.10.5", "@types/node@^22.8.7": + version "22.18.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.13.tgz#a037c4f474b860be660e05dbe92a9ef945472e28" + integrity sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A== + dependencies: + undici-types "~6.21.0" + "@types/passport-jwt@^4.0.1": version "4.0.1" resolved "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz" @@ -2236,7 +2243,7 @@ "@types/mime" "^1" "@types/node" "*" -"@types/serve-static@*": +"@types/serve-static@*", "@types/serve-static@^1": version "1.15.10" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.10.tgz#768169145a778f8f5dfcb6360aead414a3994fee" integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== @@ -2879,9 +2886,9 @@ asynckit@^0.4.0: integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== axios@^1.12.0, axios@^1.7.9: - version "1.13.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.0.tgz#ead6f495f41f9c8869dcf7b0f24f5a4ab89707f0" - integrity sha512-zt40Pz4zcRXra9CVV31KeyofwiNvAbJ5B6YPz9pMJ+yOSLikvPT4Yi5LjfgjRa9CawVYBaD1JQzIVcIvBejKeA== + version "1.13.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.1.tgz#45b62dc8fe04e0e92274e08b98e910ba3d7963a7" + integrity sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.4" @@ -3515,9 +3522,9 @@ css-what@^6.1.0: integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== csvtojson@^2.0.10: - version "2.0.13" - resolved "https://registry.yarnpkg.com/csvtojson/-/csvtojson-2.0.13.tgz#48f431a9320f04d821770c17b206dfb2093a78ec" - integrity sha512-1C7bojbYRmQPLfc6yE5zeMbp+1w8dj3jNrz51S3mLYgQOau2ZtNu/fc67uV8UvyAfuLU+xydDvHG/+APwCJUAA== + version "2.0.14" + resolved "https://registry.yarnpkg.com/csvtojson/-/csvtojson-2.0.14.tgz#89b46c302bb1aae1f2f7a9d8a5a3d7a6c301750b" + integrity sha512-F7NNvhhDyob7OsuEGRsH0FM1aqLs/WYITyza3l+hTEEmOK9sGPBlYQZwlVG0ezCojXYpE17lhS5qL6BCOZSPyA== dependencies: lodash "^4.17.21" @@ -7925,6 +7932,11 @@ undici-types@~7.14.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.14.0.tgz#4c037b32ca4d7d62fae042174604341588bc0840" integrity sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA== +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + universalify@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"