diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/BookingController.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/BookingController.java index 5677526ffade16bc0298de537cdf9f42f79c8213..b3b78e3543d2dd533c1423d27c5e2e291552a729 100644 --- a/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/BookingController.java +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/BookingController.java @@ -2,19 +2,30 @@ package com.uva.roomBooking.Controllers; import com.uva.roomBooking.Models.Booking; +import com.uva.roomBooking.Models.Room; +import com.uva.roomBooking.Models.User; import com.uva.roomBooking.Repositories.BookingRepository; +import com.uva.roomBooking.Repositories.RoomRepository; +import com.uva.roomBooking.Repositories.UserRepository; + import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/bookings") +@CrossOrigin(origins = "http://localhost:4200") public class BookingController { private final BookingRepository bookingRepository; + private final UserRepository userRepository; + private final RoomRepository roomRepository; - public BookingController(BookingRepository bookingRepository) { + public BookingController(BookingRepository bookingRepository, UserRepository userRepository, + RoomRepository roomRepository) { this.bookingRepository = bookingRepository; + this.userRepository = userRepository; + this.roomRepository = roomRepository; } @GetMapping @@ -24,6 +35,10 @@ public class BookingController { @PostMapping public Booking createBooking(@RequestBody Booking booking) { + User user = userRepository.findById(booking.getUserId().getId()).orElseThrow(); + Room room = roomRepository.findById(booking.getRoomId().getId()).orElseThrow(); + booking.setUserId(user); + booking.setRoomId(room); return bookingRepository.save(booking); } diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/HotelController.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/HotelController.java index a4dae001f04d3eb9af97d609275634f86e4f31e6..4739c7343d804c472f691636481e7dd3f7a208d4 100644 --- a/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/HotelController.java +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Controllers/HotelController.java @@ -3,90 +3,98 @@ package com.uva.roomBooking.Controllers; import java.util.List; import java.time.LocalDate; +import com.uva.roomBooking.Exceptions.HotelNotFoundException; +import com.uva.roomBooking.Exceptions.InvalidDateRangeException; import com.uva.roomBooking.Models.Hotel; import com.uva.roomBooking.Models.Room; import com.uva.roomBooking.Repositories.HotelRepository; import com.uva.roomBooking.Repositories.RoomRepository; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; - -// TODO incluir excepciones y procesado de respuestas con entidad y código de estado - @RestController @RequestMapping("hotels") @CrossOrigin(origins = "*") public class HotelController { - private final HotelRepository hotelRepository; - private final RoomRepository roomRepository; - - public HotelController(HotelRepository hotelRepository, RoomRepository roomRepository) { - this.hotelRepository = hotelRepository; - this.roomRepository = roomRepository; - } - - @GetMapping - public List<Hotel> getAllHotels() { - return hotelRepository.findAll(); - } - - @PostMapping - public Hotel addHotel(@RequestBody Hotel hotel) { - // TODO comprobar y asegurar que se agregan todas sus habitaciones también - return hotelRepository.save(hotel); - } - - @GetMapping("/{id}") - public Hotel getHotelById(@PathVariable int id) { - return hotelRepository.findById(id).orElse(null); - } - - @DeleteMapping("/{id}") - public Hotel deleteHotel(@PathVariable Integer id) { - Hotel target; - if ((target = hotelRepository.findById(id).orElse(null)) != null) { - // TODO asegurarse de que el borrado es en CASCADA -> Hotel y habitaciones - hotelRepository.deleteById(id); + private final HotelRepository hotelRepository; + private final RoomRepository roomRepository; + + public HotelController(HotelRepository hotelRepository, RoomRepository roomRepository) { + this.hotelRepository = hotelRepository; + this.roomRepository = roomRepository; + } + + // Obtener todos los hoteles + @GetMapping + public List<Hotel> getAllHotels() { + return hotelRepository.findAll(); + } + + // Añadir un hotel con sus habitaciones + @PostMapping + public ResponseEntity<Hotel> addHotel(@RequestBody Hotel hotel) { + Hotel savedHotel = hotelRepository.save(hotel); + return new ResponseEntity<>(savedHotel, HttpStatus.CREATED); + } + + // Obtener un hotel por su ID + @GetMapping("/{id}") + public Hotel getHotelById(@PathVariable int id) { + return hotelRepository.findById(id) + .orElseThrow(() -> new HotelNotFoundException(id)); + } + + // Borrar un hotel junto con sus habitaciones (borrado en cascada) + @DeleteMapping("/{id}") + public ResponseEntity<Void> deleteHotel(@PathVariable Integer id) { + Hotel target = hotelRepository.findById(id) + .orElseThrow(() -> new HotelNotFoundException(id)); + hotelRepository.delete(target); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + // Obtener habitaciones de un hotel según disponibilidad y fechas + @GetMapping("/{hotelId}/rooms") + public ResponseEntity<List<Room>> getRoomsFromHotel( + @PathVariable int hotelId, + @RequestParam(required = false) LocalDate date1, + @RequestParam(required = false) LocalDate date2) { + + List<Room> rooms; + if (date1 != null && date2 != null) { + if (!date1.isBefore(date2)) { + throw new InvalidDateRangeException("La fecha de inicio debe ser anterior a la fecha de fin"); + } + rooms = roomRepository.findAvailableRoomsByHotelAndDates(hotelId, date1, date2); + } else { + rooms = roomRepository.findAllByHotelId(hotelId); + } + return new ResponseEntity<>(rooms, HttpStatus.OK); + } + + // Actualizar disponibilidad de una habitación específica en un hotel + @PatchMapping("/{hotelId}/rooms/{roomId}") + public ResponseEntity<Room> updateRoomAvailability( + @PathVariable int hotelId, + @PathVariable int roomId, + @RequestBody boolean available) { + + Room targetRoom = roomRepository.findByIdAndHotelId(roomId, hotelId) + .orElseThrow(() -> new IllegalArgumentException("Habitación no encontrada")); + + targetRoom.setAvailable(available); + roomRepository.save(targetRoom); + + return new ResponseEntity<>(targetRoom, HttpStatus.OK); + } + + // Obtener los detalles de una habitación específica en un hotel + @GetMapping("/{hotelId}/rooms/{roomId}") + public Room getRoomByIdFromHotel( + @PathVariable int hotelId, @PathVariable int roomId) { + return roomRepository.findByIdAndHotelId(roomId, hotelId) + .orElseThrow(() -> new HotelNotFoundException(hotelId)); } - return target; - } - - // TODO completar controllers de rooms - @GetMapping("/{id}/rooms") - // Dejo un esqueleto, a lo mejor sería mejor otra forma de plantear el manejo y - // recogida de query params - public List<Room> getRoomsFromHotel(@PathVariable int hotelId, @RequestParam LocalDate date1, - @RequestParam LocalDate date2) { - throw new IllegalStateException("Not implemented yet"); - } - - @PatchMapping("/{id}/rooms") - public List<Room> updateRoomAvailabilityFromHotel(@PathVariable int hotelId) { - throw new IllegalStateException("Not implemented yet"); - } - - @GetMapping("/{hotelId}/rooms/{roomId}") - public List<Room> getRoomByIdFromHotel(@PathVariable int hotelId, @PathVariable int roomId) { - throw new IllegalStateException("Not implemented yet"); - } - - // Te dejo esto de la otra clase comentado por si te sirve algo - - // @GetMapping - // public List<Room> getAllRooms() { - // return roomRepository.findAll(); - // } - - // @PostMapping - // public Room addRoom(@RequestBody Room room) { - // return roomRepository.save(room); - // } - - // @DeleteMapping("/{id}") - // public void deleteRoom(@PathVariable Integer id) { - // roomRepository.deleteById(id); - // } - -} \ No newline at end of file +} diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/GlobalExceptionHandler.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/GlobalExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..03b21e27ffa7e4e921cc9801187911d9f1be6a52 --- /dev/null +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/GlobalExceptionHandler.java @@ -0,0 +1,50 @@ +package com.uva.roomBooking.Exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(HotelNotFoundException.class) + public ResponseEntity<Map<String, Object>> handleHotelNotFound(HotelNotFoundException ex) { + Map<String, Object> body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("message", ex.getMessage()); + + return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(InvalidRequestException.class) + public ResponseEntity<Map<String, Object>> handleInvalidRequest(InvalidRequestException ex) { + Map<String, Object> body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("message", ex.getMessage()); + + return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(InvalidDateRangeException.class) + public ResponseEntity<Map<String, Object>> handleInvalidDateRange(InvalidDateRangeException ex) { + Map<String, Object> body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("message", ex.getMessage()); + + return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity<Map<String, Object>> handleGeneralException(Exception ex) { + Map<String, Object> body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("message", "An unexpected error occurred: " + ex.getMessage()); + + return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/HotelNotFoundException.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/HotelNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..3d47f5efad71b6fbf3b3f5912493c9c0af1ca412 --- /dev/null +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/HotelNotFoundException.java @@ -0,0 +1,11 @@ +package com.uva.roomBooking.Exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) // Devuelve un 404 cuando se lanza la excepción +public class HotelNotFoundException extends RuntimeException { + public HotelNotFoundException(int id) { + super("Hotel not found with id: " + id); + } +} diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/InvalidDateRangeException.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/InvalidDateRangeException.java new file mode 100644 index 0000000000000000000000000000000000000000..17a8420453ef402411b61b965d001560a4dd51ce --- /dev/null +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/InvalidDateRangeException.java @@ -0,0 +1,8 @@ +package com.uva.roomBooking.Exceptions; + +public class InvalidDateRangeException extends RuntimeException { + public InvalidDateRangeException(String message) { + super(message); + } +} + diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/InvalidRequestException.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/InvalidRequestException.java new file mode 100644 index 0000000000000000000000000000000000000000..a8433b6f620da742dab87dae96a2e0f0193709ff --- /dev/null +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Exceptions/InvalidRequestException.java @@ -0,0 +1,11 @@ +package com.uva.roomBooking.Exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class InvalidRequestException extends RuntimeException { + public InvalidRequestException(String message) { + super(message); + } +} diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Booking.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Booking.java index 573bde806cf28ffba375f41331b8f32bdc8d9715..c2b36c948f72392174dcf1ebdcb7c4face3303ef 100644 --- a/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Booking.java +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Booking.java @@ -2,8 +2,6 @@ package com.uva.roomBooking.Models; import java.util.Date; -import com.fasterxml.jackson.annotation.JsonIgnore; - import jakarta.persistence.Basic; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -24,21 +22,25 @@ public class Booking { @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) private int id; + // TODO revisar si lo de cascade es estrictamente necesario @JoinColumn(name = "user_id", referencedColumnName = "id") - @ManyToOne(optional = false, fetch = FetchType.LAZY) + @ManyToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.MERGE) private User userId; @JoinColumn(name = "room_id", referencedColumnName = "id") - @ManyToOne(optional = false, fetch = FetchType.LAZY) - private Room roomID; + @ManyToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.MERGE) + private Room roomId; @Column(name = "start_date", nullable = false) - private Date startDate; + private LocalDate startDate; @Column(name = "end_date", nullable = false) - private Date endDate; + private LocalDate endDate; + + public Booking() { + } - public Booking(int id, User userId, Room roomID, Date startDate, Date endDate) { + public Booking(int id, User userId, Room roomID, LocalDate startDate, LocalDate endDate) { this.id = id; this.userId = userId; - this.roomID = roomID; + this.roomId = roomID; this.startDate = startDate; this.endDate = endDate; } @@ -51,35 +53,36 @@ public class Booking { return this.id; } - public void setUser(User userId) { + public void setUserId(User userId) { this.userId = userId; } - public User getUser() { + public User getUserId() { return this.userId; } - public void setRoom(Room roomID) { - this.roomID = roomID; + public void setRoomId(Room roomID) { + this.roomId = roomID; } - public Room getRoom() { - return this.roomID; + public Room getRoomId() { + return this.roomId; } - public void setStartDate(Date startDate) { + public void setStartDate(LocalDate startDate) { this.startDate = startDate; } - public Date getStartDate() { + public LocalDate getStartDate() { return this.startDate; } - public void setEndDate(Date endDate) { + public void setEndDate(LocalDate endDate) { this.endDate = endDate; } - public Date getEndDate() { + public LocalDate getEndDate() { return this.endDate; } + } diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Hotel.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Hotel.java index 29e4e94ed076c53eed8628856cc93af80a36090a..5a23005de99c022b5fff9570e2c515fcfc75e07e 100644 --- a/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Hotel.java +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Hotel.java @@ -35,7 +35,7 @@ public class Hotel { @OneToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.EAGER) private Address address; - @OneToMany(mappedBy = "hotelId", fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @OneToMany(mappedBy = "hotel", fetch = FetchType.EAGER, cascade = CascadeType.ALL) private List<Room> rooms; public Hotel() { @@ -78,7 +78,7 @@ public class Hotel { public void setRooms(List<Room> rooms) { this.rooms = rooms; - rooms.forEach(room -> room.setHotelId(this)); + rooms.forEach(room -> room.setHotel(this)); } } diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Room.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Room.java index b03b0b6469ae0ea504bb4efa2e457bc6890d6990..7919e3643add4b380472b348e7100fb8e6742975 100644 --- a/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Room.java +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Models/Room.java @@ -32,7 +32,7 @@ public class Room { @ManyToOne @JoinColumn(name = "hotel_id", referencedColumnName = "id") @JsonIgnore - private Hotel hotelId; + private Hotel hotel; @Column(name = "room_number", nullable = false) private int roomNumber; @Column(name = "type", nullable = false) @@ -40,7 +40,7 @@ public class Room { @Column(name = "available", nullable = false) private boolean available; @JsonIgnore - @OneToMany(mappedBy = "roomID", fetch = FetchType.EAGER, cascade = CascadeType.MERGE) + @OneToMany(mappedBy = "roomId", fetch = FetchType.EAGER, cascade = CascadeType.MERGE) private List<Booking> bookings; public Room() { @@ -48,7 +48,7 @@ public class Room { public Room(int id, Hotel hotelId, int roomNumber, Tipo type, boolean available, List<Booking> bookings) { this.id = id; - this.hotelId = hotelId; + this.hotel = hotelId; this.roomNumber = roomNumber; this.type = type; this.available = available; @@ -63,12 +63,12 @@ public class Room { return this.id; } - public void setHotelId(Hotel hotelId) { - this.hotelId = hotelId; + public void setHotel(Hotel hotelId) { + this.hotel = hotelId; } - public Hotel getHotelId() { - return this.hotelId; + public Hotel getHotel() { + return this.hotel; } public void setRoomNumber(int roomNumber) { @@ -91,7 +91,7 @@ public class Room { this.available = available; } - public boolean getAvailable() { + public boolean isAvailable() { return this.available; } diff --git a/java/roomBooking/src/main/java/com/uva/roomBooking/Repositories/RoomRepository.java b/java/roomBooking/src/main/java/com/uva/roomBooking/Repositories/RoomRepository.java index 2ab8752620e9a982ded2b26a02f70a4463b48b0b..71b439ff3db90f48caa5ed5fbea6df89d44bd02e 100644 --- a/java/roomBooking/src/main/java/com/uva/roomBooking/Repositories/RoomRepository.java +++ b/java/roomBooking/src/main/java/com/uva/roomBooking/Repositories/RoomRepository.java @@ -1,8 +1,32 @@ -// RoomRepository.java package com.uva.roomBooking.Repositories; import com.uva.roomBooking.Models.Room; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; public interface RoomRepository extends JpaRepository<Room, Integer> { + + Optional<Room> findByIdAndHotelId(int id, int hotelId); + + // Encontrar todas las habitaciones de un hotel + List<Room> findAllByHotelId(int hotelId); + + // Encontrar habitaciones disponibles de un hotel en un rango de fechas + // TODO revisar los límites en las fechas + @Query(""" + SELECT r FROM Room r + WHERE r.hotel.id = ?1 + AND r.available = true + AND NOT EXISTS ( + SELECT b FROM Booking b + WHERE b.roomId.id = r.id + AND (b.startDate < ?3 AND b.endDate > ?2) + ) + """) + List<Room> findAvailableRoomsByHotelAndDates( + int hotelId, LocalDate startDate, LocalDate endDate); } diff --git a/java/roomBooking/src/main/resources/application.properties b/java/roomBooking/src/main/resources/application.properties index 4331f85f147bc141240304d915c1820829a7768f..e111b6dcf5ce04ef32bd59b8d7c15b7fc35dc407 100644 --- a/java/roomBooking/src/main/resources/application.properties +++ b/java/roomBooking/src/main/resources/application.properties @@ -4,4 +4,7 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/RoomsBooking?createDatabaseIfNotExist=true spring.datasource.username=user spring.datasource.password=password -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver \ No newline at end of file +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +# Usar esto para alternar entre las exposición del room repository ya que no es necesario su uso pero por defecto, al no cubrir su ruta, se expone +spring.data.rest.base-path=false \ No newline at end of file