diff --git a/app/src/main/java/fr/parisnanterre/greentrip/backend/controller/HotelController.java b/app/src/main/java/fr/parisnanterre/greentrip/backend/controller/HotelController.java new file mode 100644 index 0000000..2d94054 --- /dev/null +++ b/app/src/main/java/fr/parisnanterre/greentrip/backend/controller/HotelController.java @@ -0,0 +1,52 @@ +package fr.parisnanterre.greentrip.backend.controller; + +import fr.parisnanterre.greentrip.backend.entity.Hotel; +import fr.parisnanterre.greentrip.backend.entity.HotelReview; +import fr.parisnanterre.greentrip.backend.service.HotelService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/hotels") +@CrossOrigin(origins = "*") +public class HotelController { + + @Autowired + private HotelService hotelService; + + @GetMapping("/favorites/{userId}") + public ResponseEntity> getUserFavorites(@PathVariable Long userId) { + return ResponseEntity.ok(hotelService.getUserFavorites(userId)); + } + + @PostMapping("/favorites/{userId}/{hotelId}") + public ResponseEntity addToFavorites( + @PathVariable Long userId, + @PathVariable Long hotelId) { + hotelService.addToFavorites(userId, hotelId); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/favorites/{userId}/{hotelId}") + public ResponseEntity removeFromFavorites( + @PathVariable Long userId, + @PathVariable Long hotelId) { + hotelService.removeFromFavorites(userId, hotelId); + return ResponseEntity.ok().build(); + } + + @PostMapping("/{hotelId}/reviews") + public ResponseEntity addReview( + @PathVariable Long hotelId, + @RequestBody HotelReview review) { + return ResponseEntity.ok(hotelService.addReview(hotelId, review)); + } + + @GetMapping("/{hotelId}/reviews") + public ResponseEntity> getHotelReviews(@PathVariable Long hotelId) { + return ResponseEntity.ok(hotelService.getHotelReviews(hotelId)); + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/Hotel.java b/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/Hotel.java new file mode 100644 index 0000000..fa86d98 --- /dev/null +++ b/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/Hotel.java @@ -0,0 +1,29 @@ +package fr.parisnanterre.greentrip.backend.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.List; + +@Entity +@Data +public class Hotel { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // Référence à l'API externe + private String externalId; + private String externalSource; // ex: "booking", "hotels.com", etc. + + // Informations de base + private String name; + private String city; + private String country; + + // Relations + @ManyToMany(mappedBy = "favoriteHotels") + private List favoritedBy; + + @OneToMany(mappedBy = "hotel", cascade = CascadeType.ALL) + private List reviews; +} \ No newline at end of file diff --git a/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/HotelReview.java b/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/HotelReview.java new file mode 100644 index 0000000..5e56067 --- /dev/null +++ b/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/HotelReview.java @@ -0,0 +1,45 @@ +package fr.parisnanterre.greentrip.backend.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Entity +@Data +@Table(name = "hotel_review") +public class HotelReview { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hotel_id", nullable = false) + private Hotel hotel; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(nullable = false) + private String comment; + + @Column(nullable = false) + private Integer ecoRating; // Note sur 10 pour l'aspect écologique + + @Column(nullable = false) + private LocalDateTime createdAt; + + @Column(nullable = false) + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/User.java b/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/User.java index 6260d6e..f1b9d2d 100644 --- a/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/User.java +++ b/app/src/main/java/fr/parisnanterre/greentrip/backend/entity/User.java @@ -9,6 +9,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -31,6 +32,14 @@ public class User implements UserDetails { @Enumerated(EnumType.STRING) private Role role; + @ManyToMany + @JoinTable( + name = "user_favorite_hotels", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "hotel_id") + ) + private List favoriteHotels = new ArrayList<>(); + public User(Long id, String firstName, String lastName, String email, String password, Role role) { this.id = id; this.firstName = firstName; diff --git a/app/src/main/java/fr/parisnanterre/greentrip/backend/repository/HotelRepository.java b/app/src/main/java/fr/parisnanterre/greentrip/backend/repository/HotelRepository.java new file mode 100644 index 0000000..68d4c50 --- /dev/null +++ b/app/src/main/java/fr/parisnanterre/greentrip/backend/repository/HotelRepository.java @@ -0,0 +1,19 @@ +package fr.parisnanterre.greentrip.backend.repository; + +import fr.parisnanterre.greentrip.backend.entity.Hotel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface HotelRepository extends JpaRepository { + + Optional findByExternalIdAndExternalSource(String externalId, String externalSource); + + @Query("SELECT h FROM Hotel h JOIN h.favoritedBy u WHERE u.id = :userId") + List findFavoritesByUserId(@Param("userId") Long userId); +} \ No newline at end of file diff --git a/app/src/main/java/fr/parisnanterre/greentrip/backend/service/HotelService.java b/app/src/main/java/fr/parisnanterre/greentrip/backend/service/HotelService.java new file mode 100644 index 0000000..0c3867a --- /dev/null +++ b/app/src/main/java/fr/parisnanterre/greentrip/backend/service/HotelService.java @@ -0,0 +1,76 @@ +package fr.parisnanterre.greentrip.backend.service; + +import fr.parisnanterre.greentrip.backend.entity.Hotel; +import fr.parisnanterre.greentrip.backend.entity.HotelReview; +import fr.parisnanterre.greentrip.backend.entity.User; +import fr.parisnanterre.greentrip.backend.repository.HotelRepository; +import fr.parisnanterre.greentrip.backend.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class HotelService { + + @Autowired + private HotelRepository hotelRepository; + + @Autowired + private UserRepository userRepository; + + public List getUserFavorites(Long userId) { + return hotelRepository.findFavoritesByUserId(userId); + } + + public void addToFavorites(Long userId, Long hotelId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("User not found")); + + Hotel hotel = hotelRepository.findById(hotelId) + .orElseThrow(() -> new RuntimeException("Hotel not found")); + + if (user.getFavoriteHotels() == null) { + user.setFavoriteHotels(new ArrayList<>()); + } + + user.getFavoriteHotels().add(hotel); + userRepository.save(user); + } + + public void removeFromFavorites(Long userId, Long hotelId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("User not found")); + + Hotel hotel = hotelRepository.findById(hotelId) + .orElseThrow(() -> new RuntimeException("Hotel not found")); + + if (user.getFavoriteHotels() != null) { + user.getFavoriteHotels().remove(hotel); + userRepository.save(user); + } + } + + public HotelReview addReview(Long hotelId, HotelReview review) { + Hotel hotel = hotelRepository.findById(hotelId) + .orElseThrow(() -> new RuntimeException("Hotel not found")); + + if (hotel.getReviews() == null) { + hotel.setReviews(new ArrayList<>()); + } + + review.setHotel(hotel); + hotel.getReviews().add(review); + hotelRepository.save(hotel); + + return review; + } + + public List getHotelReviews(Long hotelId) { + Hotel hotel = hotelRepository.findById(hotelId) + .orElseThrow(() -> new RuntimeException("Hotel not found")); + + return hotel.getReviews() != null ? hotel.getReviews() : new ArrayList<>(); + } +} \ No newline at end of file diff --git a/app/src/test/java/fr/parisnanterre/greentrip/backend/controller/HotelControllerTest.java b/app/src/test/java/fr/parisnanterre/greentrip/backend/controller/HotelControllerTest.java new file mode 100644 index 0000000..5280654 --- /dev/null +++ b/app/src/test/java/fr/parisnanterre/greentrip/backend/controller/HotelControllerTest.java @@ -0,0 +1,118 @@ +package fr.parisnanterre.greentrip.backend.controller; + +import fr.parisnanterre.greentrip.backend.entity.Hotel; +import fr.parisnanterre.greentrip.backend.entity.HotelReview; +import fr.parisnanterre.greentrip.backend.service.HotelService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class HotelControllerTest { + + @Mock + private HotelService hotelService; + + @InjectMocks + private HotelController hotelController; + + private Hotel hotel; + private HotelReview review; + private List hotels; + private List reviews; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + + // Création d'un hôtel de test + hotel = new Hotel(); + hotel.setId(1L); + hotel.setName("Test Hotel"); + hotel.setCity("Paris"); + hotel.setCountry("France"); + + // Création d'un avis de test + review = new HotelReview(); + review.setId(1L); + review.setComment("Great hotel!"); + review.setEcoRating(8); + + // Création des listes de test + hotels = new ArrayList<>(); + hotels.add(hotel); + + reviews = new ArrayList<>(); + reviews.add(review); + } + + @Test + void getUserFavorites_ShouldReturnFavorites() { + // Arrange + when(hotelService.getUserFavorites(1L)).thenReturn(hotels); + + // Act + ResponseEntity> response = hotelController.getUserFavorites(1L); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + assertEquals(hotels, response.getBody()); + verify(hotelService).getUserFavorites(1L); + } + + @Test + void addToFavorites_ShouldAddHotelToFavorites() { + // Act + ResponseEntity response = hotelController.addToFavorites(1L, 1L); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + verify(hotelService).addToFavorites(1L, 1L); + } + + @Test + void removeFromFavorites_ShouldRemoveHotelFromFavorites() { + // Act + ResponseEntity response = hotelController.removeFromFavorites(1L, 1L); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + verify(hotelService).removeFromFavorites(1L, 1L); + } + + @Test + void addReview_ShouldAddReviewToHotel() { + // Arrange + when(hotelService.addReview(1L, review)).thenReturn(review); + + // Act + ResponseEntity response = hotelController.addReview(1L, review); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + assertEquals(review, response.getBody()); + verify(hotelService).addReview(1L, review); + } + + @Test + void getHotelReviews_ShouldReturnReviews() { + // Arrange + when(hotelService.getHotelReviews(1L)).thenReturn(reviews); + + // Act + ResponseEntity> response = hotelController.getHotelReviews(1L); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + assertEquals(reviews, response.getBody()); + verify(hotelService).getHotelReviews(1L); + } +} \ No newline at end of file diff --git a/app/src/test/java/fr/parisnanterre/greentrip/backend/service/HotelServiceTest.java b/app/src/test/java/fr/parisnanterre/greentrip/backend/service/HotelServiceTest.java new file mode 100644 index 0000000..3c82e89 --- /dev/null +++ b/app/src/test/java/fr/parisnanterre/greentrip/backend/service/HotelServiceTest.java @@ -0,0 +1,151 @@ +package fr.parisnanterre.greentrip.backend.service; + +import fr.parisnanterre.greentrip.backend.entity.Hotel; +import fr.parisnanterre.greentrip.backend.entity.HotelReview; +import fr.parisnanterre.greentrip.backend.entity.User; +import fr.parisnanterre.greentrip.backend.repository.HotelRepository; +import fr.parisnanterre.greentrip.backend.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class HotelServiceTest { + + @Mock + private HotelRepository hotelRepository; + + @Mock + private UserRepository userRepository; + + @InjectMocks + private HotelService hotelService; + + private Hotel hotel; + private User user; + private HotelReview review; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + + // Création d'un hôtel de test + hotel = new Hotel(); + hotel.setId(1L); + hotel.setName("Test Hotel"); + hotel.setCity("Paris"); + hotel.setCountry("France"); + hotel.setExternalId("123"); + hotel.setExternalSource("booking"); + + // Création d'un utilisateur de test + user = new User(); + user.setId(1L); + user.setFavoriteHotels(new ArrayList<>()); + + // Création d'un avis de test + review = new HotelReview(); + review.setId(1L); + review.setComment("Great hotel!"); + review.setEcoRating(8); + } + + @Test + void getUserFavorites_ShouldReturnFavorites() { + // Arrange + List expectedFavorites = List.of(hotel); + when(hotelRepository.findFavoritesByUserId(1L)).thenReturn(expectedFavorites); + + // Act + List result = hotelService.getUserFavorites(1L); + + // Assert + assertEquals(expectedFavorites, result); + verify(hotelRepository).findFavoritesByUserId(1L); + } + + @Test + void addToFavorites_ShouldAddHotelToFavorites() { + // Arrange + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(hotelRepository.findById(1L)).thenReturn(Optional.of(hotel)); + + // Act + hotelService.addToFavorites(1L, 1L); + + // Assert + assertTrue(user.getFavoriteHotels().contains(hotel)); + verify(userRepository).save(user); + } + + @Test + void removeFromFavorites_ShouldRemoveHotelFromFavorites() { + // Arrange + user.getFavoriteHotels().add(hotel); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(hotelRepository.findById(1L)).thenReturn(Optional.of(hotel)); + + // Act + hotelService.removeFromFavorites(1L, 1L); + + // Assert + assertFalse(user.getFavoriteHotels().contains(hotel)); + verify(userRepository).save(user); + } + + @Test + void addReview_ShouldAddReviewToHotel() { + // Arrange + when(hotelRepository.findById(1L)).thenReturn(Optional.of(hotel)); + when(hotelRepository.save(any(Hotel.class))).thenReturn(hotel); + + // Act + HotelReview result = hotelService.addReview(1L, review); + + // Assert + assertNotNull(result); + verify(hotelRepository).save(hotel); + } + + @Test + void getHotelReviews_ShouldReturnReviews() { + // Arrange + List expectedReviews = List.of(review); + hotel.setReviews(expectedReviews); + when(hotelRepository.findById(1L)).thenReturn(Optional.of(hotel)); + + // Act + List result = hotelService.getHotelReviews(1L); + + // Assert + assertEquals(expectedReviews, result); + } + + @Test + void addToFavorites_ShouldThrowException_WhenUserNotFound() { + // Arrange + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(RuntimeException.class, () -> hotelService.addToFavorites(1L, 1L)); + } + + @Test + void addToFavorites_ShouldThrowException_WhenHotelNotFound() { + // Arrange + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(hotelRepository.findById(1L)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(RuntimeException.class, () -> hotelService.addToFavorites(1L, 1L)); + } +} \ No newline at end of file