├── .gitattributes ├── src ├── main │ ├── java │ │ └── com │ │ │ └── soumyajit │ │ │ └── HotelBooking │ │ │ ├── entities │ │ │ ├── Enums │ │ │ │ ├── Roles.java │ │ │ │ ├── Gender.java │ │ │ │ ├── PaymentStatus.java │ │ │ │ └── BookingStatus.java │ │ │ ├── HotelContactInfo.java │ │ │ ├── Guest.java │ │ │ ├── HotelMinPrice.java │ │ │ ├── Room.java │ │ │ ├── Hotel.java │ │ │ ├── Inventory.java │ │ │ ├── User.java │ │ │ └── Booking.java │ │ │ ├── Exception │ │ │ ├── ResourceNotFound.java │ │ │ └── UnAuthorizedException.java │ │ │ ├── dtos │ │ │ ├── LoginResponseDTO.java │ │ │ ├── LoginDTOS.java │ │ │ ├── SignUpRequestDTOS.java │ │ │ ├── HotelPriceDTO.java │ │ │ ├── HotelReportsDTOS.java │ │ │ ├── UserProfileUpdateRequestDTOS.java │ │ │ ├── HotelSearchRequest.java │ │ │ ├── HotelInfoDTOS.java │ │ │ ├── UpdateInventoryRequestDTO.java │ │ │ ├── UserDTOS.java │ │ │ ├── BookingRequest.java │ │ │ ├── GuestDTOS.java │ │ │ ├── InventoryDTOS.java │ │ │ ├── RoomDTOS.java │ │ │ ├── HotelDTOS.java │ │ │ └── BookingDTOS.java │ │ │ ├── repository │ │ │ ├── GuestRepository.java │ │ │ ├── RoomRepository.java │ │ │ ├── UserRepository.java │ │ │ ├── HotelRepository.java │ │ │ ├── BookingRepository.java │ │ │ ├── HotelMinPriceRepository.java │ │ │ └── InventoryRepository.java │ │ │ ├── EmailService │ │ │ ├── EmailRequest.java │ │ │ ├── ResetPasswordRequest.java │ │ │ ├── EmailService.java │ │ │ ├── TokenService.java │ │ │ └── PasswordResetController.java │ │ │ ├── Strategy │ │ │ ├── PricingStrategy.java │ │ │ ├── BasePriceStrategy.java │ │ │ ├── SurgePricingStrategy.java │ │ │ ├── OccupancyPricingStrategy.java │ │ │ ├── HolidayPricingStrategy.java │ │ │ ├── UrgencyPricingStrategy.java │ │ │ └── PricingService.java │ │ │ ├── service │ │ │ ├── CheckOutService.java │ │ │ ├── UserService.java │ │ │ ├── RoomService.java │ │ │ ├── HotelService.java │ │ │ ├── InventoryService.java │ │ │ ├── BookingService.java │ │ │ ├── UserServiceImpl.java │ │ │ ├── CheckOutServiceImpl.java │ │ │ ├── PricingUpdateService.java │ │ │ ├── InventoryServiceImpl.java │ │ │ ├── RoomServiceImpl.java │ │ │ ├── HotelServiceImpl.java │ │ │ └── BookingServiceImpl.java │ │ │ ├── Security │ │ │ ├── OTPServiceAndValidation │ │ │ │ ├── OTPVerificationRequest.java │ │ │ │ ├── OTPDetails.java │ │ │ │ ├── OtpService.java │ │ │ │ └── OtpAuthController.java │ │ │ ├── Configuration │ │ │ │ └── PasswordConfig.java │ │ │ ├── JwtService.java │ │ │ ├── JwtAuthFilter.java │ │ │ ├── WebSecurityConfig.java │ │ │ └── AuthService.java │ │ │ ├── Advices │ │ │ ├── ApiError.java │ │ │ ├── ApiResponse.java │ │ │ ├── GlobalResponseHandler.java │ │ │ └── GlobalExceptionHandler.java │ │ │ ├── config │ │ │ ├── StripeConfig.java │ │ │ └── Configuration.java │ │ │ ├── util │ │ │ └── AppUtils.java │ │ │ ├── HotelBookingApplication.java │ │ │ └── controller │ │ │ ├── webhookController.java │ │ │ ├── HotelBrowseController.java │ │ │ ├── InventoryController.java │ │ │ ├── UserController.java │ │ │ ├── RoomAdminController.java │ │ │ ├── HotelBookingController.java │ │ │ ├── AuthController.java │ │ │ └── HotelController.java │ └── resources │ │ ├── application-prod.properties │ │ ├── application-dev.properties │ │ └── application.properties └── test │ └── java │ └── com │ └── soumyajit │ └── HotelBooking │ └── HotelBookingApplicationTests.java ├── Dockerfile ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── LICENSE ├── pom.xml ├── README.md ├── mvnw.cmd └── mvnw /.gitattributes: -------------------------------------------------------------------------------- 1 | /mvnw text eol=lf 2 | *.cmd text eol=crlf 3 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Enums/Roles.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities.Enums; 2 | 3 | public enum Roles { 4 | GUEST, 5 | HOTEL_MANAGER 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Enums/Gender.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities.Enums; 2 | 3 | public enum Gender { 4 | MALE, 5 | FEMALE, 6 | OTHERS 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Enums/PaymentStatus.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities.Enums; 2 | 3 | public enum PaymentStatus { 4 | PENDING, 5 | CONFIRMED, 6 | CANCELLED 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Exception/ResourceNotFound.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Exception; 2 | 3 | public class ResourceNotFound extends RuntimeException { 4 | public ResourceNotFound(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Exception/UnAuthorizedException.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Exception; 2 | 3 | public class UnAuthorizedException extends RuntimeException{ 4 | public UnAuthorizedException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Enums/BookingStatus.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities.Enums; 2 | 3 | public enum BookingStatus { 4 | RESERVED, 5 | GUESTS_ADDED, 6 | PAYMENT_PENDING, 7 | CONFIRMED, 8 | CANCELLED, 9 | EXPIRED 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/LoginResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @AllArgsConstructor 7 | @NoArgsConstructor 8 | @Getter 9 | @Setter 10 | public class LoginResponseDTO { 11 | private String accessToken; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/repository/GuestRepository.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.repository; 2 | 3 | import com.soumyajit.HotelBooking.entities.Guest; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface GuestRepository extends JpaRepository { 7 | } -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/EmailService/EmailRequest.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.EmailService; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Data 8 | @Getter 9 | @Setter 10 | public class EmailRequest { 11 | private String email; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Strategy/PricingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Strategy; 2 | 3 | import com.soumyajit.HotelBooking.entities.Inventory; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public interface PricingStrategy { 8 | BigDecimal calculatePrice(Inventory inventory); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/LoginDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Data 8 | @Getter 9 | @Setter 10 | public class LoginDTOS { 11 | private String email; 12 | private String password; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/CheckOutService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.entities.Booking; 4 | 5 | public interface CheckOutService { 6 | 7 | String getCheckOutSession(Booking booking , String successURL , String failureURL); 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/soumyajit/HotelBooking/HotelBookingApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class HotelBookingApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/SignUpRequestDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Data 8 | @Getter 9 | @Setter 10 | public class SignUpRequestDTOS { 11 | private String name; 12 | private String email; 13 | private String password; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/OTPServiceAndValidation/OTPVerificationRequest.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security.OTPServiceAndValidation; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class OTPVerificationRequest { 9 | 10 | private String email; 11 | private String otp; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Advices/ApiError.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Advices; 2 | 3 | import lombok.*; 4 | import org.springframework.http.HttpStatus; 5 | 6 | 7 | @Getter 8 | @Setter 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Builder 13 | public class ApiError { 14 | private HttpStatus status; 15 | private String message; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/repository/RoomRepository.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.repository; 2 | 3 | import com.soumyajit.HotelBooking.entities.Room; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface RoomRepository extends JpaRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.repository; 2 | 3 | import com.soumyajit.HotelBooking.entities.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface UserRepository extends JpaRepository { 9 | Optional findByEmail(String email); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/HotelPriceDTO.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.soumyajit.HotelBooking.entities.Hotel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class HotelPriceDTO { 12 | private Hotel hotel; 13 | private Double price; 14 | } 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3-eclipse-temurin-23-alpine 2 | 3 | WORKDIR /app 4 | COPY mvnw pom.xml ./ 5 | COPY .mvn .mvn 6 | 7 | RUN chmod +x mvnw && ./mvnw dependency:go-offline 8 | 9 | COPY src ./src 10 | 11 | CMD ["./mvnw", "spring-boot:run"] 12 | 13 | 14 | #COPY ./target/DockerWSpring-0.0.1-SNAPSHOT.jar . 15 | # 16 | #CMD ["java", "-jar", "DockerWSpring-0.0.1-SNAPSHOT.jar"] 17 | # 18 | #EXPOSE 8080 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/HotelReportsDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import lombok.*; 4 | 5 | import java.math.BigDecimal; 6 | 7 | @Getter 8 | @Setter 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class HotelReportsDTOS { 13 | private Long bookingCount; 14 | private BigDecimal totalRevenue; 15 | private BigDecimal avgRevenue; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/UserProfileUpdateRequestDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.soumyajit.HotelBooking.entities.Enums.Gender; 4 | import lombok.Data; 5 | 6 | import java.time.LocalDate; 7 | 8 | @Data 9 | public class UserProfileUpdateRequestDTOS { 10 | 11 | private String name; 12 | private LocalDate dateOfBirth; 13 | private Gender gender; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/EmailService/ResetPasswordRequest.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.EmailService; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @Getter 9 | @Setter 10 | @Data 11 | @RequiredArgsConstructor 12 | public class ResetPasswordRequest { 13 | private String code; 14 | private String newPassword; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/HotelSearchRequest.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDate; 6 | 7 | @Data 8 | public class HotelSearchRequest { 9 | private String city; 10 | private LocalDate startDate; 11 | private LocalDate endDate; 12 | private Integer roomsCount; 13 | 14 | private Integer page = 0; 15 | private Integer size=4; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/HotelInfoDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.soumyajit.HotelBooking.entities.Hotel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | public class HotelInfoDTOS { 13 | private HotelDTOS hotelInfo; 14 | private List roomInfo; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/OTPServiceAndValidation/OTPDetails.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security.OTPServiceAndValidation; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class OTPDetails { 13 | private String otp; 14 | private long timestamp; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/config/StripeConfig.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.config; 2 | 3 | import com.stripe.Stripe; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class StripeConfig { 9 | 10 | public StripeConfig(@Value("${stripe.secret.key}") String stripeSecretKey) { 11 | Stripe.apiKey = stripeSecretKey; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Strategy/BasePriceStrategy.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Strategy; 2 | 3 | import com.soumyajit.HotelBooking.entities.Inventory; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.math.BigDecimal; 7 | 8 | 9 | public class BasePriceStrategy implements PricingStrategy{ 10 | @Override 11 | public BigDecimal calculatePrice(Inventory inventory) { 12 | return inventory.getRoom().getBasePrice(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/UpdateInventoryRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import lombok.Data; 4 | import org.modelmapper.internal.bytebuddy.asm.Advice; 5 | 6 | import java.math.BigDecimal; 7 | import java.time.LocalDate; 8 | 9 | @Data 10 | public class UpdateInventoryRequestDTO { 11 | private LocalDate startDate; 12 | private LocalDate endDate; 13 | private BigDecimal surgeFactor; 14 | private Boolean closed; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/util/AppUtils.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.util; 2 | 3 | import com.soumyajit.HotelBooking.entities.User; 4 | import org.springframework.security.core.context.SecurityContextHolder; 5 | 6 | public class AppUtils { 7 | 8 | public static User getCurrentUser(){ 9 | return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); //this way we can get the authenticated user everyTime 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.dtos.UserDTOS; 4 | import com.soumyajit.HotelBooking.dtos.UserProfileUpdateRequestDTOS; 5 | import com.soumyajit.HotelBooking.entities.User; 6 | 7 | public interface UserService { 8 | 9 | User getUserById(Long id); 10 | 11 | void updateUserProfile(UserProfileUpdateRequestDTOS userProfileUpdateRequestDTOS); 12 | 13 | UserDTOS getMyProfile(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/UserDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.soumyajit.HotelBooking.entities.Enums.Gender; 4 | import lombok.Data; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.time.LocalDate; 9 | 10 | @Data 11 | @Getter 12 | @Setter 13 | public class UserDTOS { 14 | private Long id; 15 | private String email; 16 | private String name; 17 | private LocalDate dateOfBirth; 18 | private Gender gender; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/BookingRequest.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.Data; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.time.LocalDate; 9 | 10 | @Data 11 | @Getter 12 | @Setter 13 | public class BookingRequest { 14 | private Long hotelId; 15 | private Long roomId; 16 | private LocalDate checkInDate; 17 | private LocalDate checkOutDate; 18 | private Integer roomsCount; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/HotelBookingApplication.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @SpringBootApplication 8 | @EnableScheduling 9 | public class HotelBookingApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(HotelBookingApplication.class, args); 13 | 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/HotelContactInfo.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities; 2 | 3 | import jakarta.persistence.Embeddable; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.Table; 6 | import lombok.Data; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | @Getter 11 | @Setter 12 | @Data 13 | @Embeddable 14 | public class HotelContactInfo { 15 | private String address; 16 | private String phoneNumber; 17 | private String email; 18 | private String location; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/config/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.config; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @org.springframework.context.annotation.Configuration 9 | public class Configuration { 10 | @Bean 11 | ModelMapper getBean(){ 12 | return new ModelMapper(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/RoomService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.dtos.RoomDTOS; 4 | 5 | import java.util.List; 6 | 7 | public interface RoomService { 8 | RoomDTOS createNewRoom(Long hotelId , RoomDTOS roomDTOS); 9 | List getAllRoomsInAHotel(Long hotelId); 10 | RoomDTOS getRoomById(Long roomId); 11 | boolean deleteRoomById(Long roomId); 12 | 13 | RoomDTOS updateRoom(Long hotelId, Long roomId, RoomDTOS roomDTOS); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/repository/HotelRepository.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.repository; 2 | 3 | import com.soumyajit.HotelBooking.dtos.HotelInfoDTOS; 4 | import com.soumyajit.HotelBooking.entities.Hotel; 5 | import com.soumyajit.HotelBooking.entities.User; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | @Repository 12 | public interface HotelRepository extends JpaRepository { 13 | List findByOwner(User user); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/GuestDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.soumyajit.HotelBooking.entities.Booking; 4 | import com.soumyajit.HotelBooking.entities.Enums.Gender; 5 | import com.soumyajit.HotelBooking.entities.User; 6 | import jakarta.persistence.*; 7 | import lombok.Data; 8 | 9 | import java.util.Set; 10 | 11 | @Data 12 | public class GuestDTOS { 13 | private Long id; 14 | private User user; 15 | private String name; 16 | private Gender gender; 17 | private Integer age; 18 | private Set bookings; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/Configuration/PasswordConfig.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security.Configuration; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @Configuration 9 | public class PasswordConfig { 10 | 11 | @Bean 12 | PasswordEncoder passwordEncoder() { 13 | return new BCryptPasswordEncoder(); // BCrypt ensures strong password hashing 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Advices/ApiResponse.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Advices; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | @Data 8 | public class ApiResponse{ 9 | private LocalDateTime localDateTime; 10 | private T data; 11 | private ApiError apiError; 12 | 13 | 14 | public ApiResponse() { 15 | this.localDateTime = LocalDateTime.now(); 16 | } 17 | 18 | public ApiResponse(ApiError apiError) { 19 | this(); 20 | this.apiError = apiError; 21 | } 22 | 23 | public ApiResponse(T data) { 24 | this(); 25 | this.data = data; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/HotelService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.dtos.HotelDTOS; 4 | import com.soumyajit.HotelBooking.dtos.HotelInfoDTOS; 5 | 6 | import java.util.List; 7 | 8 | public interface HotelService { 9 | 10 | HotelDTOS createNewHotel(HotelDTOS hotelDTOS); 11 | HotelDTOS getHotelById(Long id); 12 | HotelDTOS updateHotelById(Long id , HotelDTOS hotelDTOS); 13 | Boolean deleteHotelById(Long hotelId); 14 | void activateHotel(Long hotelId); 15 | HotelInfoDTOS getHotelInfoById(Long hotelId); 16 | 17 | List getAllHotels(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Strategy/SurgePricingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Strategy; 2 | 3 | import com.soumyajit.HotelBooking.entities.Inventory; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.math.BigDecimal; 8 | 9 | @RequiredArgsConstructor 10 | public class SurgePricingStrategy implements PricingStrategy{ 11 | 12 | private final PricingStrategy wrapped; 13 | 14 | @Override 15 | public BigDecimal calculatePrice(Inventory inventory) { 16 | BigDecimal price = wrapped.calculatePrice(inventory); 17 | return price.multiply(inventory.getSurgeFactor()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/InventoryService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.dtos.*; 4 | import com.soumyajit.HotelBooking.entities.Room; 5 | import org.springframework.data.domain.Page; 6 | 7 | import java.util.List; 8 | 9 | public interface InventoryService { 10 | void initializeRoomForAYear(Room room); 11 | void deleteAllInventories(Room room); 12 | 13 | Page searchHotels(HotelSearchRequest hotelSearchRequest); 14 | 15 | List getAllInventoryByRoom(Long roomId); 16 | 17 | void updateInventory(Long roomId, UpdateInventoryRequestDTO updateInventoryRequestDTO); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Strategy/OccupancyPricingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Strategy; 2 | 3 | import com.soumyajit.HotelBooking.entities.Inventory; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.math.BigDecimal; 8 | 9 | @RequiredArgsConstructor 10 | public class OccupancyPricingStrategy implements PricingStrategy{ 11 | 12 | private final PricingStrategy wrapped; 13 | 14 | @Override 15 | public BigDecimal calculatePrice(Inventory inventory) { 16 | BigDecimal price = wrapped.calculatePrice(inventory); 17 | double occupancyRate = (double) inventory.getBookedCount() /inventory.getTotalCount(); 18 | if(occupancyRate>0.8){ 19 | price = price.multiply(BigDecimal.valueOf(1.2)); 20 | } 21 | return price; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Strategy/HolidayPricingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Strategy; 2 | 3 | import com.soumyajit.HotelBooking.entities.Inventory; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.math.BigDecimal; 8 | 9 | 10 | @RequiredArgsConstructor 11 | public class HolidayPricingStrategy implements PricingStrategy{ 12 | 13 | private final PricingStrategy wrapped; 14 | 15 | @Override 16 | public BigDecimal calculatePrice(Inventory inventory) { 17 | BigDecimal price = wrapped.calculatePrice(inventory); 18 | boolean isTodayHoliday = true ; //TODO : Create a Constant array or ArrayList to store Holidays 19 | if (isTodayHoliday){ 20 | price = price.multiply(BigDecimal.valueOf(1.25)); 21 | } 22 | return price; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Guest.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities; 2 | 3 | import com.soumyajit.HotelBooking.entities.Enums.Gender; 4 | import jakarta.persistence.*; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | @Getter 12 | @Setter 13 | @Entity 14 | public class Guest { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | 20 | @ManyToOne(fetch = FetchType.LAZY) 21 | @JoinColumn(name = "user_id") 22 | private User user; 23 | 24 | @Column(nullable = false) 25 | private String name; 26 | 27 | @Enumerated(EnumType.STRING) 28 | private Gender gender; 29 | 30 | private Integer age; 31 | 32 | @ManyToMany(mappedBy = "guest") 33 | private Set bookings; 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/InventoryDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.soumyajit.HotelBooking.entities.Hotel; 4 | import com.soumyajit.HotelBooking.entities.Room; 5 | import jakarta.persistence.*; 6 | import lombok.Data; 7 | import org.hibernate.annotations.CreationTimestamp; 8 | import org.hibernate.annotations.UpdateTimestamp; 9 | 10 | import java.math.BigDecimal; 11 | import java.time.LocalDate; 12 | import java.time.LocalDateTime; 13 | 14 | @Data 15 | public class InventoryDTOS { 16 | private Long id; 17 | private LocalDate date; 18 | private Integer bookedCount; 19 | private Integer reservedCount; 20 | private Integer totalCount; 21 | private BigDecimal surgeFactor; 22 | private BigDecimal price; 23 | private Boolean closed; 24 | private LocalDateTime createdAt; 25 | private LocalDateTime updatedAt; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/repository/BookingRepository.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.repository; 2 | 3 | import com.soumyajit.HotelBooking.entities.Booking; 4 | import com.soumyajit.HotelBooking.entities.Hotel; 5 | import com.soumyajit.HotelBooking.entities.User; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | @Repository 14 | public interface BookingRepository extends JpaRepository { 15 | 16 | Optional findByPaymentSessionId(String sessionId); 17 | 18 | List findByHotel(Hotel hotel); 19 | 20 | List findByHotelAndCreatedAtBetween(Hotel hotel 21 | , LocalDateTime startDate , LocalDateTime endDate); 22 | 23 | List findByUser(User user); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/RoomDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.soumyajit.HotelBooking.entities.Hotel; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.JoinColumn; 6 | import jakarta.persistence.ManyToOne; 7 | import lombok.Data; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | import org.hibernate.annotations.CreationTimestamp; 11 | import org.hibernate.annotations.UpdateTimestamp; 12 | 13 | import java.math.BigDecimal; 14 | import java.time.LocalDateTime; 15 | 16 | @Getter 17 | @Setter 18 | @Data 19 | public class RoomDTOS { 20 | private Long id; 21 | //private Hotel hotel; 22 | private String type; 23 | private BigDecimal basePrice; 24 | private String[] photos; 25 | private String[] amenities; 26 | //private LocalDateTime createdAt; 27 | // private LocalDateTime updatedAt; 28 | private Integer totalCount; 29 | private Integer capacity; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Strategy/UrgencyPricingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Strategy; 2 | 3 | import com.soumyajit.HotelBooking.entities.Inventory; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.math.BigDecimal; 8 | import java.time.LocalDate; 9 | import java.time.temporal.ChronoUnit; 10 | 11 | 12 | @RequiredArgsConstructor 13 | public class UrgencyPricingStrategy implements PricingStrategy { 14 | private final PricingStrategy wrapped; 15 | 16 | @Override 17 | public BigDecimal calculatePrice(Inventory inventory) { 18 | BigDecimal price = wrapped.calculatePrice(inventory); 19 | LocalDate today = LocalDate.now(); 20 | LocalDate inventoryDate = inventory.getDate(); 21 | if (!inventoryDate.isBefore(today) && inventoryDate.isBefore(today.plusDays(7))){ 22 | price = price.multiply(BigDecimal.valueOf(1.15)); 23 | } 24 | return price; 25 | } 26 | } 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/BookingService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.dtos.BookingDTOS; 4 | import com.soumyajit.HotelBooking.dtos.BookingRequest; 5 | import com.soumyajit.HotelBooking.dtos.GuestDTOS; 6 | import com.soumyajit.HotelBooking.dtos.HotelReportsDTOS; 7 | import com.stripe.model.Event; 8 | 9 | import java.time.LocalDate; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public interface BookingService { 14 | BookingDTOS initialiseBooking(BookingRequest bookingRequest); 15 | 16 | BookingDTOS addGuests(Long bookingId, List guestDTOS); 17 | 18 | 19 | String initiatePayment(Long bookingId); 20 | 21 | void capturePayment(Event event); 22 | 23 | void cancelPayment(Long bookingId); 24 | 25 | String getBookingStatus(Long bookingId); 26 | 27 | List getAllBookings(Long hotelId); 28 | 29 | HotelReportsDTOS getHotelReport(Long hotelId, LocalDate startDate, LocalDate endDate); 30 | 31 | List getAllMyBookings(); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/HotelDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.soumyajit.HotelBooking.entities.HotelContactInfo; 5 | import com.soumyajit.HotelBooking.entities.Room; 6 | import jakarta.persistence.Column; 7 | import jakarta.persistence.Embedded; 8 | import jakarta.persistence.FetchType; 9 | import jakarta.persistence.OneToMany; 10 | import lombok.Data; 11 | import lombok.Getter; 12 | import lombok.Setter; 13 | import org.hibernate.annotations.CreationTimestamp; 14 | import org.hibernate.annotations.UpdateTimestamp; 15 | 16 | import java.time.LocalDateTime; 17 | import java.util.List; 18 | 19 | @Data 20 | @Getter 21 | @Setter 22 | public class HotelDTOS { 23 | private Long id; 24 | private String name; 25 | private String city; 26 | private String[] photos; 27 | private String[] amenities; 28 | // private LocalDateTime createdAt; 29 | // private LocalDateTime updatedAt; 30 | private HotelContactInfo hotelContactInfo; 31 | //private List room; 32 | private Boolean isActive; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 [leo-soumyajit] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/dtos/BookingDTOS.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.dtos; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.soumyajit.HotelBooking.entities.*; 5 | import com.soumyajit.HotelBooking.entities.Enums.BookingStatus; 6 | import lombok.Data; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | import java.math.BigDecimal; 11 | import java.time.LocalDate; 12 | import java.time.LocalDateTime; 13 | import java.util.Set; 14 | 15 | @Data 16 | @Getter 17 | @Setter 18 | public class BookingDTOS { 19 | 20 | private Long id; 21 | 22 | // private Hotel hotel; 23 | // 24 | // private Room room; 25 | 26 | @JsonIgnore 27 | private User user; 28 | 29 | private LocalDateTime createdAt; 30 | 31 | private LocalDateTime updatedAt; 32 | 33 | private Integer roomsCount; 34 | 35 | private LocalDate checkedInDate; 36 | 37 | private LocalDate checkedOutDate; 38 | 39 | private BookingStatus bookingStatus; 40 | 41 | private Set guest; 42 | 43 | private BigDecimal amount; 44 | 45 | 46 | 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/EmailService/EmailService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.EmailService; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.mail.SimpleMailMessage; 5 | import org.springframework.mail.javamail.JavaMailSender; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class EmailService { 10 | @Autowired 11 | private JavaMailSender mailSender; 12 | 13 | public void sendPasswordResetEmail(String to, String code) { 14 | SimpleMailMessage message = new SimpleMailMessage(); 15 | message.setTo(to); 16 | message.setSubject("Password Reset Request"); 17 | message.setText("To reset your password, please use the following code: " + code+"\nNote : This code is valid for only 30 minutes "); 18 | mailSender.send(message); 19 | } 20 | 21 | public void sendOtpEmail(String to, String otp) { 22 | SimpleMailMessage message = new SimpleMailMessage(); 23 | message.setTo(to); 24 | message.setSubject("Your OTP Code"); 25 | message.setText("Your OTP code is: " + otp); 26 | mailSender.send(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Strategy/PricingService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Strategy; 2 | 3 | import com.soumyajit.HotelBooking.entities.Inventory; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.List; 8 | 9 | @Service 10 | public class PricingService { 11 | public BigDecimal calculateDynamicPricing(Inventory inventory){ 12 | PricingStrategy pricingStrategy = new BasePriceStrategy(); 13 | 14 | //apply the additional strategies 15 | pricingStrategy = new SurgePricingStrategy(pricingStrategy); 16 | pricingStrategy = new OccupancyPricingStrategy(pricingStrategy); 17 | pricingStrategy = new UrgencyPricingStrategy(pricingStrategy); 18 | pricingStrategy = new HolidayPricingStrategy(pricingStrategy); 19 | return pricingStrategy.calculatePrice(inventory); 20 | } 21 | 22 | //return the sum of price of this inventory list 23 | public BigDecimal calculateTotalPrice(List inventoryList){ 24 | return inventoryList.stream() 25 | .map(this::calculateDynamicPricing) 26 | .reduce(BigDecimal.ZERO,BigDecimal::add); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/HotelMinPrice.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import lombok.Setter; 8 | import org.hibernate.annotations.CreationTimestamp; 9 | import org.hibernate.annotations.UpdateTimestamp; 10 | 11 | import java.math.BigDecimal; 12 | import java.time.LocalDate; 13 | import java.time.LocalDateTime; 14 | 15 | @Entity 16 | @Getter 17 | @Setter 18 | @NoArgsConstructor 19 | public class HotelMinPrice { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Long id; 23 | 24 | 25 | 26 | @ManyToOne(fetch = FetchType.LAZY) 27 | @JoinColumn(name = "hotel_id",nullable = false) 28 | private Hotel hotel; 29 | 30 | @CreationTimestamp 31 | private LocalDateTime createdAt; 32 | 33 | @UpdateTimestamp 34 | private LocalDateTime updatedAt; 35 | 36 | @Column(nullable = false) 37 | private LocalDate date; 38 | 39 | @Column(nullable = false,precision = 10,scale = 2 ) 40 | private BigDecimal price; //cheapest room price on a particular day 41 | 42 | public HotelMinPrice(Hotel hotel, LocalDate date) { 43 | this.hotel = hotel; 44 | this.date = date; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/controller/webhookController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.controller; 2 | 3 | import com.soumyajit.HotelBooking.service.BookingService; 4 | import com.stripe.exception.SignatureVerificationException; 5 | import com.stripe.model.Event; 6 | import com.stripe.net.Webhook; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | @RestController 13 | @RequestMapping("/webhook") 14 | @RequiredArgsConstructor 15 | public class webhookController { 16 | 17 | //webhook controller 18 | 19 | private final BookingService bookingService; 20 | 21 | @Value("${stripe.webhook.secret}") 22 | private String endpointSecret; 23 | 24 | @PostMapping("/payment") 25 | public ResponseEntity capturePayments(@RequestBody String payload, @RequestHeader("Stripe-Signature") String sigHeader) { 26 | try { 27 | Event event = Webhook.constructEvent(payload, sigHeader, endpointSecret); 28 | bookingService.capturePayment(event); 29 | return ResponseEntity.noContent().build(); 30 | } catch (SignatureVerificationException e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/controller/HotelBrowseController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.controller; 2 | 3 | import com.soumyajit.HotelBooking.dtos.HotelDTOS; 4 | import com.soumyajit.HotelBooking.dtos.HotelInfoDTOS; 5 | import com.soumyajit.HotelBooking.dtos.HotelPriceDTO; 6 | import com.soumyajit.HotelBooking.dtos.HotelSearchRequest; 7 | import com.soumyajit.HotelBooking.service.HotelService; 8 | import com.soumyajit.HotelBooking.service.InventoryService; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.data.domain.Page; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | @RestController 15 | @RequestMapping("/hotels") 16 | @RequiredArgsConstructor 17 | public class HotelBrowseController { 18 | private final InventoryService inventoryService; 19 | private final HotelService hotelService; 20 | 21 | @GetMapping(value = "/search") 22 | public ResponseEntity> searchHotels(@RequestBody HotelSearchRequest hotelSearchRequest){ 23 | var page = inventoryService.searchHotels(hotelSearchRequest); 24 | return ResponseEntity.ok(page); 25 | } 26 | @GetMapping("/{hotelId}/info") 27 | public ResponseEntity getHotelInfo(@PathVariable Long hotelId){ 28 | return ResponseEntity.ok(hotelService.getHotelInfoById(hotelId)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/controller/InventoryController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.controller; 2 | 3 | import com.soumyajit.HotelBooking.dtos.InventoryDTOS; 4 | import com.soumyajit.HotelBooking.dtos.UpdateInventoryRequestDTO; 5 | import com.soumyajit.HotelBooking.entities.Inventory; 6 | import com.soumyajit.HotelBooking.service.InventoryService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequiredArgsConstructor 15 | @RequestMapping("/admin/inventory") 16 | public class InventoryController { 17 | private final InventoryService inventoryService; 18 | 19 | @GetMapping("/rooms/{roomId}") 20 | public ResponseEntity> getAllInventoryByRoom(@PathVariable Long roomId){ 21 | return ResponseEntity.ok(inventoryService.getAllInventoryByRoom(roomId)); 22 | } 23 | 24 | @PatchMapping("/rooms/{roomId}") 25 | public ResponseEntity updateInventory(@PathVariable Long roomId, 26 | @RequestBody UpdateInventoryRequestDTO updateInventoryRequestDTO){ 27 | inventoryService.updateInventory(roomId,updateInventoryRequestDTO); 28 | return ResponseEntity.noContent().build(); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Room.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.hibernate.annotations.CreationTimestamp; 8 | import org.hibernate.annotations.UpdateTimestamp; 9 | 10 | import java.math.BigDecimal; 11 | import java.time.LocalDateTime; 12 | import java.util.Scanner; 13 | @Entity 14 | @Getter 15 | @Setter 16 | public class Room { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | 21 | @ManyToOne(fetch = FetchType.LAZY) 22 | @JoinColumn(name = "hotel_id",nullable = false) 23 | 24 | private Hotel hotel; 25 | 26 | @Column(nullable = false) 27 | private String type; 28 | 29 | @Column(nullable = false,precision = 10,scale = 2) 30 | private BigDecimal basePrice; 31 | 32 | @Column(columnDefinition = "TEXT[]") 33 | private String[] photos; 34 | 35 | @Column(columnDefinition = "TEXT[]") 36 | private String[] amenities; 37 | 38 | @CreationTimestamp 39 | @Column(updatable = false) 40 | private LocalDateTime createdAt; 41 | 42 | @UpdateTimestamp 43 | private LocalDateTime updatedAt; 44 | 45 | @Column(nullable = false) 46 | private Integer totalCount; 47 | 48 | @Column(nullable = false) 49 | private Integer capacity; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Hotel.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | import lombok.*; 6 | import org.hibernate.annotations.CreationTimestamp; 7 | import org.hibernate.annotations.UpdateTimestamp; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | 12 | @Entity 13 | @Getter 14 | @Setter 15 | @Data 16 | @Table(name = "hotel") 17 | public class Hotel { 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Long id; 21 | @Column(nullable = false) 22 | 23 | private String name; 24 | 25 | private String city; 26 | 27 | @Column(columnDefinition = "TEXT[]") 28 | private String[] photos; 29 | 30 | @Column(columnDefinition = "TEXT[]") 31 | private String[] amenities; 32 | 33 | @CreationTimestamp 34 | @Column(updatable = false) 35 | private LocalDateTime createdAt; 36 | 37 | @UpdateTimestamp 38 | private LocalDateTime updatedAt; 39 | 40 | @Embedded 41 | private HotelContactInfo hotelContactInfo; 42 | 43 | @OneToMany(mappedBy = "hotel",fetch = FetchType.LAZY) 44 | //@ElementCollection(fetch = FetchType.LAZY) 45 | @JsonIgnore 46 | private List room; 47 | 48 | @Column(nullable = false) 49 | private Boolean isActive; 50 | 51 | @ManyToOne(optional = false,fetch = FetchType.EAGER) 52 | private User owner; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.controller; 2 | 3 | import com.soumyajit.HotelBooking.dtos.BookingDTOS; 4 | import com.soumyajit.HotelBooking.dtos.UserDTOS; 5 | import com.soumyajit.HotelBooking.dtos.UserProfileUpdateRequestDTOS; 6 | import com.soumyajit.HotelBooking.service.BookingService; 7 | import com.soumyajit.HotelBooking.service.UserService; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.util.List; 13 | 14 | @RequiredArgsConstructor 15 | @RestController 16 | @RequestMapping("/users") 17 | public class UserController { 18 | 19 | private final UserService userService; 20 | private final BookingService bookingService; 21 | 22 | @PatchMapping("/profile") 23 | public ResponseEntity updateUserProfile( 24 | @RequestBody UserProfileUpdateRequestDTOS userProfileUpdateRequestDTOS){ 25 | userService.updateUserProfile(userProfileUpdateRequestDTOS); 26 | return ResponseEntity.noContent().build(); 27 | } 28 | 29 | @GetMapping("/profile") 30 | public ResponseEntity getMyProfile(){ 31 | return ResponseEntity.ok(userService.getMyProfile()); 32 | } 33 | 34 | @GetMapping("/myBookings") 35 | public ResponseEntity> getAllMyBookings(){ 36 | return ResponseEntity.ok(bookingService.getAllMyBookings()); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=HotelBooking 2 | #DB Config 3 | #spring.jpa.hibernate.ddl-auto=update 4 | #spring.jpa.show-sql=true 5 | #spring.jpa.properties.hibernate.format_sql=true 6 | #spring.datasource.url=jdbc:postgresql://localhost:8080/HotelBookingDataBase?useSSl=false 7 | #spring.datasource.username=postgres 8 | #spring.datasource.password=721154 9 | 10 | 11 | # JPA and Hibernate settings 12 | spring.jpa.hibernate.ddl-auto=update 13 | spring.jpa.show-sql=true 14 | spring.jpa.properties.hibernate.format_sql=true 15 | 16 | # PostgreSQL settings (use env vars) 17 | spring.datasource.url=${DB_URL} 18 | spring.datasource.username=${DB_USERNAME} 19 | spring.datasource.password=${DB_PASSWORD} 20 | 21 | 22 | server.servlet.context-path=/api/v1 23 | 24 | jwt.secretKey=hsdsd5vs4d5s5vsdds6vad5bs6bwglkavad68b8s9d7bkjbsdk56aafopq0as989as 25 | 26 | deploy.env = development 27 | 28 | 29 | #email sending 30 | spring.mail.host=smtp.gmail.com 31 | spring.mail.port=587 32 | spring.mail.username=hotelbookingapp2025@gmail.com 33 | spring.mail.password=mydd pttq sgql nwhx 34 | spring.mail.properties.mail.smtp.auth=true 35 | spring.mail.properties.mail.smtp.starttls.enable=true 36 | 37 | #set frontend url 38 | frontend.url = http://localhost:8000 39 | 40 | #stripe config 41 | stripe.secret.key=sk_test_51QrgToKTjoJsKFTkEcwrQG14dr5QbDNsv5UTRYGlIdtgJ0zhA0Oh3Vi2AaONg4Yh0CzKswCbPRDTVF4mkNPFUf1w00GdiYbtJJ 42 | stripe.webhook.secret=whsec_21b353d57e1df07378d58646ebc3c0fdbe5cd31fbc6be4d8e92004ee0c2e46a7 43 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Advices/GlobalResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Advices; 2 | 3 | import org.springframework.core.MethodParameter; 4 | import org.springframework.http.MediaType; 5 | import org.springframework.http.converter.HttpMessageConverter; 6 | import org.springframework.http.server.ServerHttpRequest; 7 | import org.springframework.http.server.ServerHttpResponse; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; 10 | 11 | import java.util.List; 12 | 13 | @RestControllerAdvice 14 | public class GlobalResponseHandler implements ResponseBodyAdvice { 15 | 16 | @Override 17 | public boolean supports(MethodParameter returnType, Class> converterType) { 18 | return true; 19 | } 20 | 21 | @Override 22 | public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { 23 | List allowedRoutes = List.of("/v3/api-docs", "/actuator"); 24 | 25 | boolean isAllowed = allowedRoutes 26 | .stream() 27 | .anyMatch(route -> request.getURI().getPath().contains(route)); 28 | 29 | if(body instanceof ApiResponse || isAllowed) { 30 | return body; 31 | } 32 | 33 | return new ApiResponse<>(body); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/repository/HotelMinPriceRepository.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.repository; 2 | 3 | import com.soumyajit.HotelBooking.dtos.HotelPriceDTO; 4 | import com.soumyajit.HotelBooking.entities.Hotel; 5 | import com.soumyajit.HotelBooking.entities.HotelMinPrice; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.data.jpa.repository.JpaRepository; 9 | import org.springframework.data.jpa.repository.Query; 10 | import org.springframework.data.repository.query.Param; 11 | 12 | import java.time.LocalDate; 13 | import java.util.Optional; 14 | 15 | public interface HotelMinPriceRepository extends JpaRepository { 16 | 17 | 18 | 19 | @Query(""" 20 | SELECT new com.soumyajit.HotelBooking.dtos.HotelPriceDTO(i.hotel , AVG(i.price)) 21 | FROM HotelMinPrice i 22 | WHERE i.hotel.city = :city 23 | AND i.date BETWEEN :startDate AND :endDate 24 | AND i.hotel.isActive = true 25 | GROUP BY i.hotel 26 | """) 27 | Page findHotelsWithAvailableInventory 28 | ( 29 | @Param("city") String city, 30 | @Param("startDate") LocalDate startDate, 31 | @Param("endDate") LocalDate endDate, 32 | @Param("roomsCount") Integer roomsCount, 33 | @Param("dateCount") Long dateCount, 34 | Pageable pageable 35 | ); 36 | 37 | Optional findByHotelAndDate(Hotel hotel, LocalDate date); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=HotelBooking 2 | #DB Config 3 | #spring.jpa.hibernate.ddl-auto=update 4 | #spring.jpa.show-sql=true 5 | #spring.jpa.properties.hibernate.format_sql=true 6 | #spring.datasource.url=jdbc:postgresql://localhost:8080/HotelBookingDataBase?useSSl=false 7 | #spring.datasource.username=postgres 8 | #spring.datasource.password=721154 9 | 10 | # JPA and Hibernate settings 11 | spring.jpa.hibernate.ddl-auto=update 12 | spring.jpa.show-sql=true 13 | spring.jpa.properties.hibernate.format_sql=true 14 | 15 | # PostgreSQL settings for Render 16 | spring.datasource.url=jdbc:postgresql://dpg-d1eqoegdl3ps73c0gd10-a.oregon-postgres.render.com:5432/hotelbooking_eeco 17 | spring.datasource.username=hotelbooking_eeco_user 18 | spring.datasource.password=ghokexeymKQfExu2Mhiak8AeL9tGgqmy 19 | 20 | 21 | 22 | 23 | server.servlet.context-path=/api/v1 24 | 25 | jwt.secretKey=hsdsd5vs4d5s5vsdds6vad5bs6bwglkavad68b8s9d7bkjbsdk56aafopq0as989as 26 | 27 | deploy.env = development 28 | 29 | 30 | #email sending 31 | spring.mail.host=smtp.gmail.com 32 | spring.mail.port=587 33 | spring.mail.username=hotelbookingapp2025@gmail.com 34 | spring.mail.password=mydd pttq sgql nwhx 35 | spring.mail.properties.mail.smtp.auth=true 36 | spring.mail.properties.mail.smtp.starttls.enable=true 37 | 38 | #set frontend url 39 | frontend.url = http://localhost:8000 40 | 41 | #stripe config 42 | stripe.secret.key=sk_test_51QrgToKTjoJsKFTkEcwrQG14dr5QbDNsv5UTRYGlIdtgJ0zhA0Oh3Vi2AaONg4Yh0CzKswCbPRDTVF4mkNPFUf1w00GdiYbtJJ 43 | stripe.webhook.secret=whsec_21b353d57e1df07378d58646ebc3c0fdbe5cd31fbc6be4d8e92004ee0c2e46a7 44 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=HotelBooking 2 | #DB Config 3 | #spring.jpa.hibernate.ddl-auto=update 4 | #spring.jpa.show-sql=true 5 | #spring.jpa.properties.hibernate.format_sql=true 6 | #spring.datasource.url=jdbc:postgresql://localhost:8080/HotelBookingDataBase?useSSl=false 7 | #spring.datasource.username=postgres 8 | #spring.datasource.password=721154 9 | 10 | 11 | # JPA and Hibernate settings 12 | spring.jpa.hibernate.ddl-auto=update 13 | spring.jpa.show-sql=true 14 | spring.jpa.properties.hibernate.format_sql=true 15 | 16 | # PostgreSQL settings for Render 17 | spring.datasource.url=jdbc:postgresql://dpg-d1eqoegdl3ps73c0gd10-a.oregon-postgres.render.com:5432/hotelbooking_eeco 18 | spring.datasource.username=hotelbooking_eeco_user 19 | spring.datasource.password=ghokexeymKQfExu2Mhiak8AeL9tGgqmy 20 | 21 | 22 | server.port=8000 23 | 24 | server.servlet.context-path=/api/v1 25 | 26 | jwt.secretKey=hsdsd5vs4d5s5vsdds6vad5bs6bwglkavad68b8s9d7bkjbsdk56aafopq0as989as 27 | 28 | deploy.env = development 29 | 30 | 31 | #email sending 32 | spring.mail.host=smtp.gmail.com 33 | spring.mail.port=587 34 | spring.mail.username=hotelbookingapp2025@gmail.com 35 | spring.mail.password=mydd pttq sgql nwhx 36 | spring.mail.properties.mail.smtp.auth=true 37 | spring.mail.properties.mail.smtp.starttls.enable=true 38 | 39 | #set frontend url 40 | frontend.url = http://localhost:8000 41 | 42 | #stripe config 43 | stripe.secret.key=sk_test_51QrgToKTjoJsKFTkEcwrQG14dr5QbDNsv5UTRYGlIdtgJ0zhA0Oh3Vi2AaONg4Yh0CzKswCbPRDTVF4mkNPFUf1w00GdiYbtJJ 44 | stripe.webhook.secret=whsec_21b353d57e1df07378d58646ebc3c0fdbe5cd31fbc6be4d8e92004ee0c2e46a7 45 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/controller/RoomAdminController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.controller; 2 | 3 | import com.soumyajit.HotelBooking.dtos.RoomDTOS; 4 | import com.soumyajit.HotelBooking.service.RoomService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import java.util.List; 11 | 12 | @RestController 13 | @RequiredArgsConstructor 14 | @RequestMapping("/admin/hotels/{hotelId}/rooms") 15 | public class RoomAdminController { 16 | private final RoomService roomService; 17 | 18 | @PostMapping 19 | public ResponseEntity createNewRoom( 20 | @RequestBody RoomDTOS roomDTOS , @PathVariable Long hotelId){ 21 | return new ResponseEntity<>(roomService.createNewRoom(hotelId,roomDTOS), HttpStatus.CREATED); 22 | } 23 | 24 | @GetMapping 25 | public ResponseEntity> getAllRooms(@PathVariable Long hotelId){ 26 | return ResponseEntity.ok(roomService.getAllRoomsInAHotel(hotelId)); 27 | } 28 | 29 | @GetMapping("/{roomId}") 30 | public ResponseEntity getRoomById(@PathVariable Long roomId,@PathVariable Long hotelId){ 31 | return ResponseEntity.ok(roomService.getRoomById(roomId)); 32 | } 33 | @DeleteMapping("/{roomId}") 34 | public ResponseEntity deleteRoomById(@PathVariable Long roomId,@PathVariable Long hotelId){ 35 | return ResponseEntity.ok(roomService.deleteRoomById(roomId)); 36 | } 37 | 38 | @PutMapping("/{roomId}") 39 | public ResponseEntity updateRoom(@PathVariable Long roomId,@PathVariable Long hotelId, 40 | @RequestBody RoomDTOS roomDTOS){ 41 | return ResponseEntity.ok(roomService.updateRoom(hotelId,roomId,roomDTOS)); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/JwtService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security; 2 | 3 | import com.soumyajit.HotelBooking.entities.User; 4 | import io.jsonwebtoken.Claims; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Service; 9 | 10 | import javax.crypto.SecretKey; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.Date; 13 | 14 | @Service 15 | public class JwtService { 16 | @Value("${jwt.secretKey}") 17 | private String secretKey; 18 | 19 | public SecretKey getSecretKey(){ 20 | return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); 21 | } 22 | //access token 23 | public String generateAccessToken(User userEntities){ 24 | return Jwts.builder() 25 | .setSubject(userEntities.getId().toString()) 26 | .claim("email",userEntities.getEmail()) 27 | .claim("roles",userEntities.getRoles().toString()) 28 | .setIssuedAt(new Date()) 29 | .setExpiration(new Date(System.currentTimeMillis()+1000*60*10)) //10 min 30 | .signWith(getSecretKey()) 31 | .compact(); 32 | } 33 | 34 | //refresh Token 35 | public String generateRefreshToken(User userEntities){ 36 | return Jwts.builder() 37 | .setSubject(userEntities.getId().toString()) 38 | .setIssuedAt(new Date()) 39 | .setExpiration(new Date(System.currentTimeMillis()+ 1000L *60*60*24*30*6)) //6 months 40 | .signWith(getSecretKey()) 41 | .compact(); 42 | } 43 | 44 | public Long getUserIdFromToken(String token){ 45 | Claims claims = Jwts.parserBuilder() 46 | .setSigningKey(getSecretKey()) 47 | .build() 48 | .parseClaimsJws(token) 49 | .getBody(); 50 | return Long.valueOf(claims.getSubject()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Inventory.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.*; 5 | import org.hibernate.annotations.CreationTimestamp; 6 | import org.hibernate.annotations.UpdateTimestamp; 7 | 8 | import java.math.BigDecimal; 9 | import java.time.LocalDate; 10 | import java.time.LocalDateTime; 11 | 12 | @Entity 13 | @Getter 14 | @Setter 15 | @Table( 16 | uniqueConstraints = @UniqueConstraint( 17 | name = "unique_hotel_room_date", 18 | columnNames = {"hotel_id", "room_id", "date"} 19 | )) 20 | @Builder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | public class Inventory { 24 | 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | private Long id; 28 | 29 | @ManyToOne(fetch = FetchType.EAGER) 30 | @JoinColumn(name = "hotel_id",nullable = false) 31 | private Hotel hotel; 32 | 33 | @ManyToOne(fetch = FetchType.LAZY) 34 | @JoinColumn(name = "room_id",nullable = false) 35 | private Room room; 36 | 37 | @Column(updatable = false) 38 | private LocalDate date; 39 | 40 | @Column(nullable = false , columnDefinition = "INTEGER DEFAULT 0") 41 | private Integer bookedCount; 42 | 43 | @Column(nullable = false , columnDefinition = "INTEGER DEFAULT 0") 44 | private Integer reservedCount; 45 | 46 | @Column(nullable = false ) 47 | private Integer totalCount; 48 | 49 | @Column(nullable = false,precision = 5,scale = 2) 50 | private BigDecimal surgeFactor; 51 | 52 | @Column(nullable = false,precision = 10,scale = 2) 53 | private BigDecimal price; 54 | 55 | @Column(nullable = false) 56 | private String city; 57 | 58 | @Column(nullable = false) 59 | private Boolean closed; 60 | 61 | @CreationTimestamp 62 | @Column(updatable = false) 63 | private LocalDateTime createdAt; 64 | 65 | @UpdateTimestamp 66 | private LocalDateTime updatedAt; 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/User.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.soumyajit.HotelBooking.entities.Enums.Gender; 5 | import com.soumyajit.HotelBooking.entities.Enums.Roles; 6 | import jakarta.persistence.*; 7 | import lombok.Data; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | import org.springframework.security.core.GrantedAuthority; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | 14 | import java.time.LocalDate; 15 | import java.util.Collection; 16 | import java.util.Set; 17 | import java.util.stream.Collectors; 18 | 19 | @Entity 20 | @Getter 21 | @Setter 22 | @Table(name = "app_user") 23 | @Data 24 | public class User implements UserDetails { 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | private Long id; 28 | 29 | @ElementCollection(fetch = FetchType.EAGER) // to save the roles in a separate db table 30 | @Enumerated(EnumType.STRING) 31 | private Set roles; 32 | 33 | @Column(nullable = false) 34 | private String name; 35 | 36 | private LocalDate dateOfBirth; 37 | 38 | @Enumerated(EnumType.STRING) 39 | private Gender gender; 40 | 41 | @Column(nullable = false,unique = true) 42 | private String email; 43 | 44 | @JsonIgnore 45 | @Column(nullable = false) 46 | private String password; 47 | 48 | @OneToMany(mappedBy = "user" , fetch = FetchType.EAGER) 49 | @JsonIgnore() 50 | private Set guests; 51 | 52 | @Override 53 | public Collection getAuthorities() { 54 | return roles.stream() 55 | .map(role -> new SimpleGrantedAuthority("ROLE_"+role.name())) 56 | .collect(Collectors.toSet()); 57 | } 58 | 59 | @Override 60 | public String getUsername() { //email 61 | return email; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/EmailService/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.EmailService; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.security.SecureRandom; 9 | 10 | @Service 11 | public class TokenService { 12 | private final Map codeStore = new HashMap<>(); 13 | private final long codeExpirationTime = 30 * 60 * 1000; // 30 minutes 14 | private final Map codeExpirationStore = new HashMap<>(); 15 | 16 | private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 17 | private static final SecureRandom RANDOM = new SecureRandom(); 18 | private static final int TOKEN_LENGTH = 8; // Length of the token 19 | 20 | public String createResetCode(String email) { 21 | String code = generateRandomToken(); 22 | codeStore.put(code, email); 23 | codeExpirationStore.put(code, System.currentTimeMillis() + codeExpirationTime); 24 | return code; 25 | } 26 | 27 | public Optional getEmailByCode(String code) { 28 | Long expirationTime = codeExpirationStore.get(code); 29 | if (expirationTime != null && expirationTime > System.currentTimeMillis()) { 30 | return Optional.ofNullable(codeStore.get(code)); 31 | } else { 32 | codeStore.remove(code); 33 | codeExpirationStore.remove(code); 34 | return Optional.empty(); 35 | } 36 | } 37 | 38 | public void invalidateCode(String code) { 39 | codeStore.remove(code); 40 | codeExpirationStore.remove(code); 41 | } 42 | 43 | private String generateRandomToken() { 44 | StringBuilder token = new StringBuilder(TOKEN_LENGTH); 45 | for (int i = 0; i < TOKEN_LENGTH; i++) { 46 | token.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length()))); 47 | } 48 | return token.toString(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/controller/HotelBookingController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.controller; 2 | 3 | import com.soumyajit.HotelBooking.dtos.BookingDTOS; 4 | import com.soumyajit.HotelBooking.dtos.BookingRequest; 5 | import com.soumyajit.HotelBooking.dtos.GuestDTOS; 6 | import com.soumyajit.HotelBooking.service.BookingService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | @RestController 15 | @RequestMapping("/bookings") 16 | @RequiredArgsConstructor 17 | public class HotelBookingController { 18 | 19 | private final BookingService bookingService; 20 | 21 | @PostMapping("/init") 22 | public ResponseEntity initialiseBooking(@RequestBody BookingRequest bookingRequest){ 23 | return ResponseEntity.ok(bookingService.initialiseBooking(bookingRequest)); 24 | } 25 | 26 | @PostMapping("/{bookingId}/addGuests") 27 | public ResponseEntity addGuests(@PathVariable Long bookingId , 28 | @RequestBody List guestDTOSList){ 29 | return ResponseEntity.ok(bookingService.addGuests(bookingId,guestDTOSList)); 30 | } 31 | 32 | @PostMapping("/{bookingId}/payments") 33 | public ResponseEntity> initiatePayment(@PathVariable Long bookingId){//return the session URL 34 | String sessionURL = bookingService.initiatePayment(bookingId); 35 | return ResponseEntity.ok(Map.of("sessionURL",sessionURL)); 36 | } 37 | 38 | @PostMapping("/{bookingId}/cancel") 39 | public ResponseEntity cancelPayment(@PathVariable Long bookingId){ 40 | bookingService.cancelPayment(bookingId); 41 | return ResponseEntity.noContent().build(); 42 | } 43 | 44 | @PostMapping("/{bookingId}/status") 45 | public ResponseEntity> getBookingStatus(@PathVariable Long bookingId){ 46 | return ResponseEntity.ok(Map.of("status",bookingService.getBookingStatus(bookingId))); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/entities/Booking.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.soumyajit.HotelBooking.entities.Enums.BookingStatus; 5 | import jakarta.persistence.*; 6 | import lombok.*; 7 | import org.hibernate.annotations.CreationTimestamp; 8 | import org.hibernate.annotations.UpdateTimestamp; 9 | 10 | import java.math.BigDecimal; 11 | import java.time.LocalDate; 12 | import java.time.LocalDateTime; 13 | import java.util.Set; 14 | 15 | @Getter 16 | @Setter 17 | @Entity 18 | @Builder 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | public class Booking { 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | 26 | @JsonIgnore 27 | @ManyToOne(fetch = FetchType.LAZY) 28 | @JoinColumn(name = "hotel_id") 29 | private Hotel hotel; 30 | 31 | @JsonIgnore 32 | @ManyToOne(fetch = FetchType.LAZY) 33 | @JoinColumn(name = "room_id") 34 | private Room room; 35 | 36 | @ManyToOne(fetch = FetchType.EAGER) 37 | @JoinColumn(name = "user_id",nullable = false) 38 | private User user; 39 | 40 | @CreationTimestamp 41 | @Column(updatable = false) 42 | private LocalDateTime createdAt; 43 | 44 | @UpdateTimestamp 45 | private LocalDateTime updatedAt; 46 | 47 | @Column(nullable = false) 48 | private Integer roomsCount; 49 | 50 | @Column(nullable = false) 51 | private LocalDate checkedInDate; 52 | 53 | @Column(nullable = false) 54 | private LocalDate checkedOutDate; 55 | 56 | @Column(nullable = false,precision = 10,scale = 2) 57 | private BigDecimal amount; 58 | 59 | @Enumerated(EnumType.STRING) 60 | private BookingStatus bookingStatus; 61 | 62 | @JsonIgnore 63 | @ManyToMany(fetch = FetchType.LAZY) 64 | @JoinTable(name = "booking_guest", 65 | joinColumns = @JoinColumn(name = "booking_id") 66 | ,inverseJoinColumns = @JoinColumn(name = "guest_id")) 67 | private Set guest; 68 | 69 | @Column(name = "transaction_Id",unique = true) 70 | private String paymentSessionId; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/JwtAuthFilter.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security; 2 | 3 | import com.soumyajit.HotelBooking.entities.User; 4 | import com.soumyajit.HotelBooking.service.UserService; 5 | import io.jsonwebtoken.JwtException; 6 | import jakarta.servlet.FilterChain; 7 | import jakarta.servlet.ServletException; 8 | import jakarta.servlet.http.HttpServletRequest; 9 | import jakarta.servlet.http.HttpServletResponse; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Qualifier; 13 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 14 | import org.springframework.security.core.context.SecurityContextHolder; 15 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 16 | import org.springframework.stereotype.Component; 17 | import org.springframework.web.filter.OncePerRequestFilter; 18 | import org.springframework.web.servlet.HandlerExceptionResolver; 19 | import com.soumyajit.HotelBooking.service.UserService; 20 | import java.io.IOException; 21 | 22 | @Component 23 | @RequiredArgsConstructor 24 | public class JwtAuthFilter extends OncePerRequestFilter { 25 | private final JwtService jwtService; 26 | private final com.soumyajit.HotelBooking.service.UserService userService; 27 | 28 | 29 | @Autowired 30 | @Qualifier("handlerExceptionResolver") 31 | private HandlerExceptionResolver handlerExceptionResolver; 32 | 33 | @Override 34 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 35 | try { 36 | final String requestTokenHeader = request.getHeader("Authorization"); 37 | if (requestTokenHeader == null || !requestTokenHeader.startsWith("Bearer")) { 38 | filterChain.doFilter(request, response); 39 | return; 40 | } 41 | String token = requestTokenHeader.split("Bearer")[1]; 42 | Long userId = jwtService.getUserIdFromToken(token); 43 | 44 | if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { 45 | User userEntities = userService.getUserById(userId); 46 | 47 | UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = 48 | new UsernamePasswordAuthenticationToken(userEntities, null, userEntities.getAuthorities()); 49 | 50 | usernamePasswordAuthenticationToken.setDetails( 51 | new WebAuthenticationDetailsSource().buildDetails(request) 52 | ); 53 | 54 | SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); 55 | } 56 | filterChain.doFilter(request, response); 57 | }catch (JwtException ex){ 58 | handlerExceptionResolver.resolveException(request,response,null,ex); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.controller; 2 | 3 | import com.soumyajit.HotelBooking.Security.AuthService; 4 | import com.soumyajit.HotelBooking.dtos.LoginDTOS; 5 | import com.soumyajit.HotelBooking.dtos.LoginResponseDTO; 6 | import com.soumyajit.HotelBooking.dtos.SignUpRequestDTOS; 7 | import com.soumyajit.HotelBooking.dtos.UserDTOS; 8 | import jakarta.servlet.http.Cookie; 9 | import jakarta.servlet.http.HttpServletRequest; 10 | import jakarta.servlet.http.HttpServletResponse; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.security.authentication.AuthenticationServiceException; 16 | import org.springframework.web.bind.annotation.PostMapping; 17 | import org.springframework.web.bind.annotation.RequestBody; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | import java.util.Arrays; 22 | 23 | @RestController 24 | @RequestMapping("/auth") 25 | @RequiredArgsConstructor 26 | public class AuthController { 27 | 28 | @Value("${deploy.env}") 29 | private String deployEnv; 30 | private final AuthService authService; 31 | 32 | 33 | @PostMapping("/signup") 34 | public ResponseEntity signUp(@RequestBody SignUpRequestDTOS signUpRequestDTOS){ 35 | return new ResponseEntity<>(authService.signUp(signUpRequestDTOS), HttpStatus.CREATED); 36 | } 37 | 38 | 39 | @PostMapping("/login") 40 | public ResponseEntity login(@RequestBody LoginDTOS loginDTOS , 41 | HttpServletRequest request , 42 | HttpServletResponse response){ 43 | String[] tokens = authService.login(loginDTOS); //get the tokens 44 | Cookie cookie = new Cookie("refreshToken",tokens[1]); // add the refreshToken inside the cookie only 45 | cookie.setHttpOnly(true); 46 | 47 | cookie.setSecure("production".equals(deployEnv)); // when its on production mode it is true else development mode its false 48 | response.addCookie(cookie); 49 | return ResponseEntity.ok(new LoginResponseDTO(tokens[0])); // return the access token which is String[0] 50 | } 51 | 52 | 53 | @PostMapping("/refresh") 54 | public ResponseEntity refreshToken(HttpServletRequest request){ 55 | String refreshToken = Arrays.stream(request.getCookies()) 56 | .filter(cookie -> "refreshToken".equals(cookie.getName())) 57 | .findFirst() 58 | .map(Cookie::getValue) 59 | .orElseThrow(()->new AuthenticationServiceException("refreshToken Not Found inside the Cookies")); 60 | 61 | String newAccessToken = authService.refreshToken(refreshToken); 62 | return ResponseEntity.ok(new LoginResponseDTO(newAccessToken)); 63 | } 64 | 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/OTPServiceAndValidation/OtpService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security.OTPServiceAndValidation; 2 | 3 | import jakarta.mail.internet.MimeMessage; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.mail.javamail.JavaMailSender; 6 | import org.springframework.mail.javamail.MimeMessageHelper; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.mail.MessagingException; 10 | import javax.mail.internet.InternetAddress; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | 15 | @Service 16 | public class OtpService { 17 | 18 | @Autowired 19 | private JavaMailSender emailSender; 20 | 21 | private final Map otpStorage = new HashMap<>(); 22 | private final Map otpVerified = new HashMap<>(); 23 | 24 | public String generateOTP() { 25 | int otp = (int)(Math.random() * 9000) + 1000; 26 | return String.valueOf(otp); 27 | } 28 | 29 | public void sendOTPEmail(String email, String otp) { 30 | try { 31 | // Validate email address format 32 | InternetAddress emailAddr = new InternetAddress(email); 33 | emailAddr.validate(); 34 | 35 | MimeMessage message = emailSender.createMimeMessage(); 36 | MimeMessageHelper helper = new MimeMessageHelper(message, true); 37 | 38 | helper.setFrom("hotelbookingapp2025@gmail.com"); 39 | helper.setTo(email); 40 | helper.setSubject("Your OTP Code"); 41 | helper.setText("Your OTP code is: " + otp+" \nNote : This OTP is valid for only 10 minutes "); 42 | 43 | emailSender.send(message); 44 | System.out.println("OTP sent successfully.Check your gmail "); 45 | } catch (MessagingException e) { 46 | System.err.println("Failed to send OTP: " + e.getMessage()); 47 | e.printStackTrace(); 48 | } catch (jakarta.mail.MessagingException e) { 49 | System.err.println("Invalid email address: " + e.getMessage()); 50 | } 51 | } 52 | 53 | public void saveOTP(String email, String otp) { 54 | otpStorage.put(email, new OTPDetails(otp, System.currentTimeMillis())); 55 | otpVerified.put(email, false); // Set OTP verification status to false 56 | } 57 | 58 | public OTPDetails getOTPDetails(String email) { 59 | return otpStorage.get(email); 60 | } 61 | 62 | public void deleteOTP(String email) { 63 | otpStorage.remove(email); 64 | // Do not remove verification status here to retain it for signup 65 | } 66 | 67 | public void clearOTPVerified(String email) { 68 | otpVerified.remove(email); // Clear OTP verification status 69 | } 70 | 71 | public boolean isOTPExpired(long timestamp) { 72 | long currentTime = System.currentTimeMillis(); 73 | return (currentTime - timestamp) > (10 * 60 * 1000); // 10 minutes in milliseconds 74 | } 75 | 76 | public void markOTPVerified(String email) { 77 | otpVerified.put(email, true); // Mark OTP as verified 78 | } 79 | 80 | public boolean isOTPVerified(String email) { 81 | return otpVerified.getOrDefault(email, false); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.Exception.ResourceNotFound; 4 | import com.soumyajit.HotelBooking.dtos.UserDTOS; 5 | import com.soumyajit.HotelBooking.dtos.UserProfileUpdateRequestDTOS; 6 | import com.soumyajit.HotelBooking.entities.User; 7 | import com.soumyajit.HotelBooking.repository.UserRepository; 8 | import lombok.Getter; 9 | import lombok.RequiredArgsConstructor; 10 | import lombok.Setter; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.modelmapper.ModelMapper; 13 | import org.springframework.security.core.AuthenticationException; 14 | import org.springframework.security.core.userdetails.UserDetails; 15 | import org.springframework.security.core.userdetails.UserDetailsService; 16 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 17 | import org.springframework.security.crypto.password.PasswordEncoder; 18 | import org.springframework.stereotype.Service; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Optional; 22 | 23 | import static com.soumyajit.HotelBooking.util.AppUtils.getCurrentUser; 24 | 25 | @Service 26 | @RequiredArgsConstructor 27 | @Getter 28 | @Setter 29 | @Slf4j 30 | public class UserServiceImpl implements UserService, UserDetailsService { 31 | 32 | private final UserRepository userRepository; 33 | private final PasswordEncoder passwordEncoder; 34 | private final ModelMapper modelMapper; 35 | 36 | @Override 37 | public User getUserById(Long id) { 38 | return userRepository.findById(id) 39 | .orElseThrow(()-> 40 | new ResourceNotFound("User not found with id :"+id) 41 | ); 42 | } 43 | 44 | @Override 45 | public void updateUserProfile(UserProfileUpdateRequestDTOS userProfileUpdateRequestDTOS) { 46 | User user = getCurrentUser(); 47 | if(userProfileUpdateRequestDTOS.getDateOfBirth() != null){ 48 | user.setDateOfBirth(userProfileUpdateRequestDTOS.getDateOfBirth()); 49 | } 50 | if(userProfileUpdateRequestDTOS.getGender() != null){ 51 | user.setGender(userProfileUpdateRequestDTOS.getGender()); 52 | } 53 | if(userProfileUpdateRequestDTOS.getName() != null){ 54 | user.setName(userProfileUpdateRequestDTOS.getName()); 55 | } 56 | userRepository.save(user); 57 | } 58 | 59 | @Override 60 | public UserDTOS getMyProfile() { 61 | log.info("Getting my profile"); 62 | User user = getCurrentUser(); 63 | return modelMapper.map(user, UserDTOS.class); 64 | } 65 | 66 | 67 | @Override 68 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 69 | User user= userRepository.findByEmail(username) 70 | .orElseThrow(()->new UsernameNotFoundException("Resource isn't matched") { 71 | }); 72 | return user; 73 | } 74 | 75 | 76 | public Optional findByEmail(String email) { 77 | return userRepository.findByEmail(email); 78 | } 79 | 80 | public void updateUserPassword(User user, String newPassword) { 81 | user.setPassword(passwordEncoder.encode(newPassword)); 82 | userRepository.save(user); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/EmailService/PasswordResetController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.EmailService; 2 | 3 | import com.soumyajit.HotelBooking.Advices.ApiError; 4 | import com.soumyajit.HotelBooking.Advices.ApiResponse; 5 | import com.soumyajit.HotelBooking.entities.User; 6 | import com.soumyajit.HotelBooking.service.UserServiceImpl; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.Optional; 17 | @RestController 18 | @RequestMapping("/password-reset") 19 | public class PasswordResetController { 20 | @Autowired 21 | private UserServiceImpl userService; 22 | @Autowired 23 | private EmailService emailService; 24 | @Autowired 25 | private TokenService tokenService; 26 | @Autowired 27 | PasswordEncoder passwordEncoder; 28 | 29 | @PostMapping("/request") 30 | public ResponseEntity> requestPasswordReset(@RequestParam String email) { 31 | Optional user = userService.findByEmail(email); 32 | ApiResponse response = new ApiResponse<>(); 33 | if (user.isPresent()) { 34 | String token = tokenService.createResetCode(email); 35 | emailService.sendPasswordResetEmail(email, token); 36 | response.setData("Password reset email sent"); 37 | return ResponseEntity.ok(response); 38 | } else { 39 | ApiError apiError = new ApiError(HttpStatus.NOT_FOUND, "User not found"); 40 | response.setApiError(apiError); 41 | return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); 42 | } 43 | } 44 | 45 | @PostMapping("/reset") 46 | public ResponseEntity> resetPassword(@RequestParam String token, @RequestParam String newPassword) { 47 | Optional email = tokenService.getEmailByCode(token); 48 | ApiResponse response = new ApiResponse<>(); 49 | if (email.isPresent()) { 50 | Optional user = userService.findByEmail(email.get()); 51 | if (user.isPresent()) { 52 | userService.updateUserPassword(user.get(), newPassword); // Password encoded in service 53 | tokenService.invalidateCode(token); 54 | response.setData("Password reset successful"); 55 | return ResponseEntity.ok(response); 56 | } else { 57 | ApiError apiError = new ApiError(HttpStatus.NOT_FOUND, "User not found"); 58 | response.setApiError(apiError); 59 | return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); 60 | } 61 | } else { 62 | ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, "Invalid token"); 63 | response.setApiError(apiError); 64 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); 65 | } 66 | } 67 | 68 | 69 | 70 | } -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security; 2 | 3 | import jakarta.servlet.ServletException; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.security.access.AccessDeniedException; 12 | import org.springframework.security.authentication.AuthenticationManager; 13 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 14 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 15 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 16 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 17 | import org.springframework.security.config.http.SessionCreationPolicy; 18 | import org.springframework.security.web.SecurityFilterChain; 19 | import org.springframework.security.web.access.AccessDeniedHandler; 20 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 21 | import org.springframework.web.servlet.HandlerExceptionResolver; 22 | 23 | import java.io.IOException; 24 | 25 | @Configuration 26 | @EnableWebSecurity 27 | @RequiredArgsConstructor 28 | public class WebSecurityConfig { 29 | 30 | private final JwtAuthFilter jwtAuthFilter; 31 | 32 | @Autowired 33 | @Qualifier("handlerExceptionResolver") 34 | private HandlerExceptionResolver handlerExceptionResolver; // handle exceptions 35 | 36 | 37 | @Bean 38 | public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { 39 | 40 | httpSecurity 41 | .csrf(AbstractHttpConfigurer::disable) 42 | .sessionManagement(sessionConfig->sessionConfig.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 43 | .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) 44 | .authorizeHttpRequests(auth->auth 45 | .requestMatchers("/admin/**").hasRole("HOTEL_MANAGER") 46 | .requestMatchers("/bookings/**").authenticated() 47 | .requestMatchers("/users/**").authenticated() 48 | .anyRequest().permitAll() 49 | ) 50 | .exceptionHandling(exceptionHandlingConfig -> 51 | exceptionHandlingConfig.accessDeniedHandler(accessDeniedHandler())); //handle AccessDenied Exceptions 52 | 53 | return httpSecurity.build(); 54 | } 55 | 56 | 57 | @Bean //Authentication Manager bean use for Login 58 | public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { 59 | return authenticationConfiguration.getAuthenticationManager(); 60 | } 61 | 62 | 63 | @Bean 64 | public AccessDeniedHandler accessDeniedHandler(){ 65 | return (request, response, accessDeniedException) -> { 66 | handlerExceptionResolver.resolveException(request,response,null,accessDeniedException); 67 | }; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/controller/HotelController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.controller; 2 | 3 | import com.soumyajit.HotelBooking.dtos.BookingDTOS; 4 | import com.soumyajit.HotelBooking.dtos.HotelDTOS; 5 | import com.soumyajit.HotelBooking.dtos.HotelReportsDTOS; 6 | import com.soumyajit.HotelBooking.service.BookingService; 7 | import com.soumyajit.HotelBooking.service.HotelService; 8 | import lombok.AllArgsConstructor; 9 | import lombok.NoArgsConstructor; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.bind.annotation.*; 15 | import org.apache.logging.log4j.LogManager; 16 | import org.apache.logging.log4j.Logger; 17 | import lombok.extern.slf4j.Slf4j; 18 | 19 | import java.time.LocalDate; 20 | import java.util.List; 21 | 22 | @RestController 23 | @RequiredArgsConstructor 24 | @RequestMapping("/admin/hotels") 25 | @Slf4j 26 | public class HotelController { 27 | private final HotelService hotelService; 28 | private final BookingService bookingService; 29 | 30 | @PostMapping() 31 | public ResponseEntity createNewHotel(@RequestBody HotelDTOS hotelDTOS){ 32 | log.info("Attempting to create new Hotel with this name : {}",hotelDTOS.getName()); 33 | return new ResponseEntity<> 34 | (hotelService.createNewHotel(hotelDTOS), HttpStatus.CREATED); 35 | } 36 | @GetMapping("/{hotelId}") 37 | public ResponseEntity getHotelById(@PathVariable Long hotelId){ 38 | return ResponseEntity.ok(hotelService.getHotelById(hotelId)); 39 | } 40 | 41 | @PutMapping("/{hotelId}") 42 | public ResponseEntity updateHotelById(@PathVariable Long hotelId , @RequestBody HotelDTOS hotelDTOS){ 43 | return ResponseEntity.ok(hotelService.updateHotelById(hotelId,hotelDTOS)); 44 | } 45 | 46 | @DeleteMapping("/{hotelId}") 47 | public ResponseEntity deleteHotelById(@PathVariable Long hotelId){ 48 | return ResponseEntity.ok(hotelService.deleteHotelById(hotelId)); 49 | } 50 | 51 | @PatchMapping("/{hotelId}") 52 | public ResponseEntity activateHotel(@PathVariable Long hotelId){ // just for updating isActive not for all the fields 53 | hotelService.activateHotel(hotelId); 54 | return ResponseEntity.ok().build(); 55 | } 56 | 57 | @GetMapping 58 | public ResponseEntity> getAllHotels(){ 59 | return ResponseEntity.ok(hotelService.getAllHotels()); 60 | } 61 | 62 | @GetMapping("/{hotelId}/bookings") 63 | public ResponseEntity> getAllBookings(@PathVariable Long hotelId){ 64 | return ResponseEntity.ok(bookingService.getAllBookings(hotelId)); 65 | } 66 | 67 | @GetMapping("/{hotelId}/reports") 68 | public ResponseEntity getHotelReport(@PathVariable Long hotelId , 69 | @RequestParam(required = false)LocalDate startDate , 70 | @RequestParam(required = false)LocalDate endDate){ 71 | 72 | if(startDate==null)startDate=LocalDate.now().minusMonths(1); 73 | if(endDate==null)endDate=LocalDate.now(); 74 | 75 | return ResponseEntity.ok(bookingService.getHotelReport(hotelId,startDate,endDate)); 76 | } 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Advices/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Advices; 2 | 3 | import io.jsonwebtoken.JwtException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.access.AccessDeniedException; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | import org.springframework.web.client.HttpServerErrorException; 11 | 12 | import javax.security.sasl.AuthenticationException; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | @RestControllerAdvice 17 | public class GlobalExceptionHandler { 18 | 19 | 20 | 21 | @ExceptionHandler(com.soumyajit.HotelBooking.Exception.ResourceNotFound.class) 22 | public ResponseEntity> noEmployeeFound(com.soumyajit.HotelBooking.Exception.ResourceNotFound exception){ 23 | ApiError apiError = ApiError.builder().status(HttpStatus.NOT_FOUND).message(exception.getMessage()).build(); 24 | return buildErrorResponseEntity(apiError); 25 | } 26 | 27 | private ResponseEntity> buildErrorResponseEntity(ApiError apiError) { 28 | return new ResponseEntity<>(new ApiResponse<>(apiError),apiError.getStatus()); 29 | } 30 | 31 | 32 | 33 | 34 | @ExceptionHandler(JwtException.class) 35 | public ResponseEntity> HandleJwtException(JwtException exception){ 36 | ApiError error = ApiError.builder() 37 | .status(HttpStatus.UNAUTHORIZED) 38 | .message(exception.getMessage()) 39 | .build(); 40 | return buildErrorResponseEntity(error); 41 | } 42 | 43 | @ExceptionHandler(AuthenticationException.class) 44 | public ResponseEntity> HandleAuthenticationException(AuthenticationException exception){ 45 | ApiError error = ApiError.builder() 46 | .status(HttpStatus.UNAUTHORIZED) 47 | .message(exception.getMessage()) 48 | .build(); 49 | return buildErrorResponseEntity(error); 50 | } 51 | 52 | @ExceptionHandler(org.springframework.security.access.AccessDeniedException.class) 53 | public ResponseEntity> HandleAccessDeniedException(AccessDeniedException exception){ 54 | ApiError error = ApiError.builder() 55 | .status(HttpStatus.FORBIDDEN) 56 | .message(exception.getMessage()) 57 | .build(); 58 | return buildErrorResponseEntity(error); 59 | } 60 | 61 | 62 | @ExceptionHandler(MethodArgumentNotValidException.class) 63 | public ResponseEntity> invalidInputs(MethodArgumentNotValidException exception){ 64 | List errors = exception.getBindingResult() 65 | .getAllErrors() 66 | .stream().map(objectError -> objectError.getDefaultMessage()) 67 | .collect(Collectors.toList()); 68 | 69 | ApiError apiError = ApiError 70 | .builder().status(HttpStatus.BAD_REQUEST) 71 | .message(errors.toString()).build(); 72 | return buildErrorResponseEntity(apiError); 73 | } 74 | 75 | @ExceptionHandler(RuntimeException.class) 76 | public ResponseEntity> InternalServererror(RuntimeException e){ 77 | ApiError apiError = ApiError 78 | .builder() 79 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 80 | .message(e.getMessage()) 81 | .build(); 82 | return buildErrorResponseEntity(apiError); 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/OTPServiceAndValidation/OtpAuthController.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security.OTPServiceAndValidation; 2 | 3 | import com.soumyajit.HotelBooking.Advices.ApiError; 4 | import com.soumyajit.HotelBooking.Advices.ApiResponse; 5 | import com.soumyajit.HotelBooking.repository.UserRepository; 6 | import lombok.RequiredArgsConstructor; 7 | import org.modelmapper.ModelMapper; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.Map; 17 | 18 | @RestController() 19 | @RequestMapping("/otp") 20 | @RequiredArgsConstructor 21 | public class OtpAuthController { 22 | 23 | private final com.soumyajit.HotelBooking.Security.OTPServiceAndValidation.OtpService otpService; 24 | 25 | @Autowired 26 | private UserRepository userRepository; 27 | 28 | @Autowired 29 | private ModelMapper modelMapper; 30 | 31 | @PostMapping("/send-otp") 32 | public ResponseEntity> sendOTP(@RequestBody Map request) { 33 | try { 34 | String email = request.get("email").trim(); // Extract and trim the email address 35 | String otp = otpService.generateOTP(); 36 | otpService.saveOTP(email, otp); 37 | otpService.sendOTPEmail(email, otp); // This will use your existing email sending logic 38 | return ResponseEntity.ok(new ApiResponse<>("OTP sent successfully. Check your gmail")); 39 | } catch (Exception e) { 40 | ApiError apiError = ApiError.builder() 41 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 42 | .message("Failed to send OTP") 43 | .build(); 44 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 45 | .body(new ApiResponse<>(apiError)); 46 | } 47 | } 48 | 49 | @PostMapping("/verify-otp") 50 | public ResponseEntity> verifyOTP(@RequestBody OTPVerificationRequest request) { 51 | try { 52 | OTPDetails otpDetails = otpService.getOTPDetails(request.getEmail()); 53 | if (otpDetails == null || !otpDetails.getOtp().equals(request.getOtp())) { 54 | ApiError apiError = ApiError.builder() 55 | .status(HttpStatus.BAD_REQUEST) 56 | .message("Invalid OTP") 57 | .build(); 58 | return ResponseEntity.status(HttpStatus.BAD_REQUEST) 59 | .body(new ApiResponse<>(apiError)); 60 | } 61 | if (otpService.isOTPExpired(otpDetails.getTimestamp())) { 62 | ApiError apiError = ApiError.builder() 63 | .status(HttpStatus.BAD_REQUEST) 64 | .message("OTP has expired") 65 | .build(); 66 | return ResponseEntity.status(HttpStatus.BAD_REQUEST) 67 | .body(new ApiResponse<>(apiError)); 68 | } 69 | otpService.markOTPVerified(request.getEmail()); 70 | return ResponseEntity.ok(new ApiResponse<>("OTP verified successfully. Now you can signup your email")); 71 | } catch (Exception e) { 72 | ApiError apiError = ApiError.builder() 73 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 74 | .message("Failed to verify OTP") 75 | .build(); 76 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 77 | .body(new ApiResponse<>(apiError)); 78 | } 79 | } 80 | 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/CheckOutServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.entities.Booking; 4 | import com.soumyajit.HotelBooking.entities.User; 5 | import com.soumyajit.HotelBooking.repository.BookingRepository; 6 | import com.stripe.exception.StripeException; 7 | import com.stripe.model.Customer; 8 | import com.stripe.model.checkout.Session; 9 | import com.stripe.param.CustomerCreateParams; 10 | import com.stripe.param.checkout.SessionCreateParams; 11 | import lombok.RequiredArgsConstructor; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.security.core.context.SecurityContextHolder; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.math.BigDecimal; 17 | 18 | @Service 19 | @RequiredArgsConstructor 20 | @Slf4j 21 | public class CheckOutServiceImpl implements CheckOutService{ 22 | 23 | private final BookingRepository bookingRepository; 24 | 25 | 26 | @Override 27 | public String getCheckOutSession(Booking booking, String successURL, String failureURL) { 28 | log.info("Creating session for booking with ID {} ",booking.getId()); 29 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 30 | 31 | CustomerCreateParams customerCreateParams = CustomerCreateParams.builder() //get customer details 32 | .setEmail(user.getEmail()) 33 | .setName(user.getName()) 34 | .build(); 35 | 36 | try { 37 | Customer customer = Customer.create(customerCreateParams); 38 | 39 | SessionCreateParams sessionParams = SessionCreateParams.builder() 40 | .setMode(SessionCreateParams.Mode.PAYMENT) 41 | .setBillingAddressCollection(SessionCreateParams.BillingAddressCollection.REQUIRED) 42 | .setCustomer(customer.getId()) //pass customer details 43 | .setSuccessUrl(successURL) 44 | .setCancelUrl(failureURL) 45 | .addLineItem( 46 | SessionCreateParams.LineItem.builder() 47 | .setQuantity(1L) 48 | .setPriceData( 49 | SessionCreateParams.LineItem.PriceData.builder() 50 | .setCurrency("inr") //set currency to inr 51 | .setUnitAmount(booking.getAmount().multiply(BigDecimal.valueOf(100)).longValue()) //set amount to paisa or rupees 52 | .setProductData( 53 | SessionCreateParams.LineItem.PriceData.ProductData.builder() //set Hotel details(Product) 54 | .setName(booking.getHotel().getName() +" : "+ booking.getRoom().getType()) //hotel name and Type 55 | .setDescription("Booking Id : "+booking.getId()) 56 | .build() 57 | ) 58 | .build() 59 | ) 60 | .build() 61 | ) 62 | .build(); 63 | 64 | Session session = Session.create(sessionParams); 65 | booking.setPaymentSessionId(session.getId()); 66 | bookingRepository.save(booking); //save the booking 67 | 68 | log.info("Session created successfully for booking with ID {} ",booking.getId()); 69 | return session.getUrl(); //using this url user can proceed to Stripe checkout page 70 | 71 | } catch (StripeException e) { 72 | throw new RuntimeException(e); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/PricingUpdateService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | 4 | import com.soumyajit.HotelBooking.Strategy.PricingService; 5 | import com.soumyajit.HotelBooking.entities.Hotel; 6 | import com.soumyajit.HotelBooking.entities.HotelMinPrice; 7 | import com.soumyajit.HotelBooking.entities.Inventory; 8 | import com.soumyajit.HotelBooking.repository.HotelMinPriceRepository; 9 | import com.soumyajit.HotelBooking.repository.HotelRepository; 10 | import com.soumyajit.HotelBooking.repository.InventoryRepository; 11 | import lombok.RequiredArgsConstructor; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.PageRequest; 15 | import org.springframework.scheduling.annotation.Scheduled; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import java.math.BigDecimal; 20 | import java.time.LocalDate; 21 | import java.util.ArrayList; 22 | import java.util.Comparator; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.stream.Collectors; 26 | 27 | @Service 28 | @RequiredArgsConstructor 29 | @Slf4j 30 | @Transactional 31 | public class PricingUpdateService { 32 | 33 | // Scheduler to update the inventory and HotelMinPrice tables every hour 34 | 35 | private final HotelRepository hotelRepository; 36 | private final InventoryRepository inventoryRepository; 37 | private final HotelMinPriceRepository hotelMinPriceRepository; 38 | private final PricingService pricingService; 39 | 40 | //@Scheduled(cron = "*/5 * * * * *") 41 | @Scheduled(cron = "0 0 * * * *") 42 | public void updatePrices() { 43 | int page = 0; 44 | int batchSize = 100; 45 | 46 | while(true) { 47 | Page hotelPage = hotelRepository.findAll(PageRequest.of(page, batchSize)); 48 | if(hotelPage.isEmpty()) { 49 | break; 50 | } 51 | hotelPage.getContent().forEach(this::updateHotelPrices); 52 | 53 | page++; 54 | } 55 | } 56 | 57 | private void updateHotelPrices(Hotel hotel) { 58 | log.info("Updating hotel prices for hotel ID: {}", hotel.getId()); 59 | LocalDate startDate = LocalDate.now(); 60 | LocalDate endDate = LocalDate.now().plusYears(1); 61 | 62 | List inventoryList = inventoryRepository.findByHotelAndDateBetween(hotel, startDate, endDate); 63 | 64 | updateInventoryPrices(inventoryList); 65 | 66 | updateHotelMinPrice(hotel, inventoryList, startDate, endDate); 67 | } 68 | 69 | private void updateHotelMinPrice(Hotel hotel, List inventoryList, LocalDate startDate, LocalDate endDate) { 70 | // Compute minimum price per day for the hotel 71 | Map dailyMinPrices = inventoryList.stream() 72 | .collect(Collectors.groupingBy( 73 | Inventory::getDate, 74 | Collectors.mapping(Inventory::getPrice, Collectors.minBy(Comparator.naturalOrder())) 75 | )) 76 | .entrySet().stream() 77 | .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().orElse(BigDecimal.ZERO))); 78 | 79 | // Prepare HotelPrice entities in bulk 80 | List hotelPrices = new ArrayList<>(); 81 | dailyMinPrices.forEach((date, price) -> { 82 | HotelMinPrice hotelPrice = hotelMinPriceRepository.findByHotelAndDate(hotel, date) 83 | .orElse(new HotelMinPrice(hotel, date)); 84 | hotelPrice.setPrice(price); 85 | hotelPrices.add(hotelPrice); 86 | }); 87 | 88 | // Save all HotelPrice entities in bulk 89 | hotelMinPriceRepository.saveAll(hotelPrices); 90 | } 91 | 92 | private void updateInventoryPrices(List inventoryList) { 93 | inventoryList.forEach(inventory -> { 94 | BigDecimal dynamicPrice = pricingService.calculateDynamicPricing(inventory); 95 | inventory.setPrice(dynamicPrice); 96 | }); 97 | inventoryRepository.saveAll(inventoryList); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/Security/AuthService.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.Security; 2 | 3 | import com.soumyajit.HotelBooking.EmailService.EmailService; 4 | import com.soumyajit.HotelBooking.Exception.ResourceNotFound; 5 | import com.soumyajit.HotelBooking.dtos.LoginDTOS; 6 | import com.soumyajit.HotelBooking.dtos.SignUpRequestDTOS; 7 | import com.soumyajit.HotelBooking.dtos.UserDTOS; 8 | import com.soumyajit.HotelBooking.entities.Enums.Roles; 9 | import com.soumyajit.HotelBooking.entities.User; 10 | import com.soumyajit.HotelBooking.repository.UserRepository; 11 | import lombok.RequiredArgsConstructor; 12 | import org.modelmapper.ModelMapper; 13 | import org.springframework.security.authentication.AuthenticationManager; 14 | import org.springframework.security.authentication.BadCredentialsException; 15 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 16 | import org.springframework.security.core.Authentication; 17 | import org.springframework.security.crypto.password.PasswordEncoder; 18 | import org.springframework.stereotype.Service; 19 | 20 | import java.util.Optional; 21 | import java.util.Set; 22 | 23 | @Service 24 | @RequiredArgsConstructor 25 | public class AuthService { 26 | private final AuthenticationManager authenticationManager; 27 | private final JwtService jwtService; 28 | private final PasswordEncoder passwordEncoder; 29 | private final ModelMapper modelMapper; 30 | private final UserRepository userRepository; 31 | private final EmailService emailService; 32 | private final com.soumyajit.HotelBooking.Security.OTPServiceAndValidation.OtpService otpService; 33 | 34 | 35 | 36 | //signup function with otp validation 37 | 38 | public UserDTOS signUp(SignUpRequestDTOS signUpRequestDTOS) { 39 | Optional user = userRepository.findByEmail(signUpRequestDTOS.getEmail()); 40 | if (user.isPresent()) { 41 | throw new BadCredentialsException("User with this Email is already present"); 42 | } 43 | 44 | // Check if OTP was verified 45 | if (!otpService.isOTPVerified(signUpRequestDTOS.getEmail())) { 46 | throw new BadCredentialsException("OTP not verified"); 47 | } 48 | 49 | User newUser = modelMapper.map(signUpRequestDTOS, User.class); 50 | newUser.setRoles(Set.of(Roles.GUEST)); // by default all users are guests 51 | newUser.setPassword(passwordEncoder.encode(signUpRequestDTOS.getPassword())); // bcrypt the password 52 | User savedUser = userRepository.save(newUser); // save the user 53 | 54 | // Clear OTP verification status after successful signup 55 | otpService.clearOTPVerified(signUpRequestDTOS.getEmail()); 56 | 57 | return modelMapper.map(savedUser, UserDTOS.class); 58 | } 59 | 60 | 61 | 62 | public String[] login(LoginDTOS loginDTOS){ 63 | Authentication authentication = authenticationManager.authenticate( 64 | new UsernamePasswordAuthenticationToken(loginDTOS.getEmail(),loginDTOS.getPassword()) 65 | ); 66 | User user = (User) authentication.getPrincipal(); 67 | String[] arr = new String[2]; 68 | String accessToken = jwtService.generateAccessToken(user); 69 | String refreshToken = jwtService.generateRefreshToken(user); 70 | arr[0] = accessToken; //1st one is accessToken 71 | arr[1] = refreshToken; // 2nd one is refreshToken 72 | return arr; 73 | 74 | } 75 | public String refreshToken(String refreshToken) { //refreshToken method 76 | Long uerId = jwtService.getUserIdFromToken(refreshToken); //refresh token is valid 77 | User user = userRepository.findById(uerId) 78 | .orElseThrow(()-> 79 | new ResourceNotFound("User not found with id : "+uerId)); 80 | return jwtService.generateAccessToken(user); 81 | 82 | 83 | } 84 | 85 | 86 | 87 | 88 | 89 | // public UserDTOS signUp(SignUpRequestDTOS signUpRequestDTOS){ // signUp method for user normal no otp validation 90 | // Optional user = userRepository.findByEmail(signUpRequestDTOS.getEmail()); 91 | // if(user.isPresent()){ 92 | // throw new BadCredentialsException("User with this Email is already present"); 93 | // } 94 | // User newUser = modelMapper.map(signUpRequestDTOS,User.class); 95 | // newUser.setRoles(Set.of(Roles.GUEST)); // by default all user are guests 96 | // newUser.setPassword(passwordEncoder.encode(signUpRequestDTOS.getPassword())); // bcrypt the pass 97 | // User savedUser = userRepository.save(newUser); // save the user 98 | // return modelMapper.map(savedUser, UserDTOS.class); 99 | // } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.1 9 | 10 | 11 | com.soumyajit 12 | HotelBooking 13 | 0.0.1-SNAPSHOT 14 | HotelBooking 15 | HotelBooking Backend project for Spring Boot 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 23 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-data-jpa 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | 43 | org.modelmapper 44 | modelmapper 45 | 3.2.2 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-devtools 51 | runtime 52 | true 53 | 54 | 55 | org.postgresql 56 | postgresql 57 | runtime 58 | 59 | 60 | 61 | org.apache.logging.log4j 62 | log4j-slf4j-impl 63 | 2.23.1 64 | test 65 | 66 | 67 | 68 | org.slf4j 69 | slf4j-api 70 | ${slf4j.version} 71 | 72 | 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-security 78 | 79 | 80 | 81 | 82 | 83 | io.jsonwebtoken 84 | jjwt-api 85 | 0.11.5 86 | 87 | 88 | io.jsonwebtoken 89 | jjwt-impl 90 | 0.11.5 91 | 92 | 93 | io.jsonwebtoken 94 | jjwt-jackson 95 | 0.11.5 96 | 97 | 98 | 99 | org.springdoc 100 | springdoc-openapi-starter-webmvc-ui 101 | 2.7.0 102 | 103 | 104 | 105 | 106 | org.springframework.boot 107 | spring-boot-starter-mail 108 | 109 | 110 | javax.mail 111 | javax.mail-api 112 | 1.6.2 113 | 114 | 115 | com.sun.mail 116 | javax.mail 117 | 1.6.2 118 | 119 | 120 | 121 | 122 | com.stripe 123 | stripe-java 124 | 28.3.0 125 | 126 | 127 | 128 | 129 | 130 | org.projectlombok 131 | lombok 132 | true 133 | 134 | 135 | org.springframework.boot 136 | spring-boot-starter-test 137 | test 138 | 139 | 140 | 141 | 142 | 143 | 144 | org.apache.maven.plugins 145 | maven-compiler-plugin 146 | 147 | 148 | 149 | org.projectlombok 150 | lombok 151 | 152 | 153 | 23 154 | 23 155 | --enable-preview 156 | 157 | 158 | 159 | org.springframework.boot 160 | spring-boot-maven-plugin 161 | 162 | 163 | 164 | org.projectlombok 165 | lombok 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 🏨 Hotel Booking & Management Backend System 2 | 3 | A scalable and secure backend solution for hotel booking and management, built using **Spring Boot**, integrated with **Stripe** for payments, and containerized with **Docker** for easy deployment. The application is hosted on **Render** and supports robust admin and user functionalities. 4 | 5 | 🔗 **Live API:** [https://hotelbooking-service.onrender.com/api/v1/](https://hotelbooking-service.onrender.com/api/v1/) 6 | 📦 **Docker Image:** [`soumyajit2005/hotelbooking-service:v0.0.1`](https://hub.docker.com/r/soumyajit2005/hotelbooking-service) 7 | 8 | --- 9 | 10 |

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

19 | 20 | 21 | 22 | --- 23 | 24 | ## 🚀 Features 25 | 26 | - 🔐 **User Authentication & OTP Email Verification** 27 | - 🛏️ **Hotel Room & Inventory Management** 28 | - 📅 **Booking System**: Create, update, cancel bookings 29 | - 📈 **Dynamic Pricing Strategy** 30 | - 💰 **Minimum Price Display Per Hotel** 31 | - 👥 **Guest Management** for group travel 32 | - 💳 **Stripe Payment Integration** 33 | - 👤 **Role-Based Access Control (RBAC)** 34 | - 🖥️ **Admin Panel Functions** 35 | - 📊 **Report Generation** 36 | 37 | --- 38 | 39 | ## 🛠️ Tech Stack 40 | 41 | | Layer | Technology | 42 | |-------------------|-------------------------------------------| 43 | | **Backend** | Spring Boot, Spring MVC, Spring Security | 44 | | **Database** | PostgreSQL | 45 | | **ORM** | Hibernate (JPA) | 46 | | **Authentication** | JWT, Email OTP | 47 | | **Payment** | Stripe API | 48 | | **Deployment** | Docker, Render | 49 | | **API Docs** | Postman / Swagger | 50 | 51 | 52 | ![Java](https://img.shields.io/badge/Java-ED8B00?style=for-the-badge&logo=openjdk&logoColor=white) 53 | ![Spring Boot](https://img.shields.io/badge/Spring_Boot-6DB33F?style=for-the-badge&logo=spring-boot&logoColor=white) 54 | ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?style=for-the-badge&logo=postgresql&logoColor=white) 55 | ![Docker](https://img.shields.io/badge/Docker-4169E1?style=for-the-badge&logo=docker&logoColor=white) 56 | ![Maven](https://img.shields.io/badge/Maven-C71A36?style=for-the-badge&logo=apachemaven&logoColor=white) 57 | ![JWT](https://img.shields.io/badge/JWT-black?style=for-the-badge&logo=JSON%20web%20tokens) 58 | ![Swagger](https://img.shields.io/badge/Swagger-85EA2D?style=for-the-badge&logo=swagger&logoColor=black) 59 | ![Postman](https://img.shields.io/badge/Postman-FF6C37?style=for-the-badge&logo=postman&logoColor=white) 60 | ![Stripe](https://img.shields.io/badge/Stripe-635BFF?style=for-the-badge&logo=stripe&logoColor=white) 61 | ![IntelliJ IDEA](https://img.shields.io/badge/IntelliJ_IDEA-000000?style=for-the-badge&logo=intellij-idea&logoColor=white) 62 | 63 | --- 64 | 65 | ## 📁 Project Structure 66 | 67 | ```bash 68 | src/ 69 | ├── main/ 70 | │ ├── java/com/soumyajit/HotelBooking/ 71 | │ │ ├── Advices/ 72 | │ │ ├── config/ 73 | │ │ ├── controller/ 74 | │ │ ├── dtos/ 75 | │ │ ├── EmailService/ 76 | │ │ ├── entities/ 77 | │ │ ├── Exception/ 78 | │ │ ├── repository/ 79 | │ │ ├── Security/ 80 | │ │ ├── service/ 81 | │ │ ├── Strategy/ 82 | │ │ └── util/ 83 | │ │ └── HotelBookingApplication.java 84 | │ └── resources/ 85 | ├── test/ 86 | ├── Dockerfile 87 | └── target/ 88 | ``` 89 | 90 | 91 | --- 92 | 93 | ## 🐳 Docker & 🔧 Deployment Instructions 94 | pull the public image from Docker Hub: 95 | ```bash 96 | docker pull soumyajit2005/hotelbooking-service:v0.0.1 97 | ``` 98 | ```bash 99 | docker run -p 8000:8000 soumyajit2005/hotelbooking-service:v0.0.1 100 | ``` 101 | 102 | 🌐 Deployment 103 | This backend is hosted live on Render using Docker. 104 | 105 | 📍 API Base URL: 106 | ```bash 107 | https://hotelbooking-service.onrender.com/api/v1/ 108 | ``` 109 | 110 | 🐦 Try Endpoints in Postman: 111 | 🔗 with https://hotelbooking-service.onrender.com/api/v1/ 112 | > 🔗 View the full API reference in [Postman Collection](https://www.postman.com/newsly-0222/workspace/hotel-booking-backend/collection/39002667-020dd3ae-aa23-4a38-93f2-aba4eebb3e93?action=share&source=copy-link&creator=39002667) 113 | 114 | --- 115 | 116 | ## ⚙️ Setup & Run Locally 117 | 118 | ### 📦 Clone the Repository 119 | 120 | ```bash 121 | git clone https://github.com/leo-soumyajit/HotelBooking-Backend.git 122 | cd HotelBooking-Backend 123 | ``` 124 | 🛠 Configure Database Connection 125 | Edit the application.properties file: 126 | ```bash 127 | spring.datasource.url=jdbc:postgresql://localhost:5432/ 128 | spring.datasource.username=your_db_username 129 | spring.datasource.password=your_db_password 130 | ``` 131 | 132 | ▶ Run the Application 133 | ```bash 134 | ./mvnw spring-boot:run 135 | ``` 136 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/InventoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.Exception.ResourceNotFound; 4 | import com.soumyajit.HotelBooking.Exception.UnAuthorizedException; 5 | import com.soumyajit.HotelBooking.dtos.HotelPriceDTO; 6 | import com.soumyajit.HotelBooking.dtos.HotelSearchRequest; 7 | import com.soumyajit.HotelBooking.dtos.InventoryDTOS; 8 | import com.soumyajit.HotelBooking.dtos.UpdateInventoryRequestDTO; 9 | import com.soumyajit.HotelBooking.entities.Inventory; 10 | import com.soumyajit.HotelBooking.entities.Room; 11 | import com.soumyajit.HotelBooking.entities.User; 12 | import com.soumyajit.HotelBooking.repository.HotelMinPriceRepository; 13 | import com.soumyajit.HotelBooking.repository.InventoryRepository; 14 | import com.soumyajit.HotelBooking.repository.RoomRepository; 15 | import lombok.RequiredArgsConstructor; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.modelmapper.ModelMapper; 18 | import org.springframework.data.domain.Page; 19 | import org.springframework.data.domain.PageRequest; 20 | import org.springframework.data.domain.Pageable; 21 | import org.springframework.stereotype.Service; 22 | import org.springframework.transaction.annotation.Transactional; 23 | 24 | import java.math.BigDecimal; 25 | import java.time.LocalDate; 26 | import java.time.temporal.ChronoUnit; 27 | import java.util.List; 28 | import java.util.stream.Collectors; 29 | 30 | import static com.soumyajit.HotelBooking.util.AppUtils.getCurrentUser; 31 | 32 | @Service 33 | @RequiredArgsConstructor 34 | @Slf4j 35 | public class InventoryServiceImpl implements InventoryService{ 36 | private final RoomRepository roomRepository; 37 | private final ModelMapper modelMapper; 38 | private final InventoryRepository inventoryRepository; 39 | private final HotelMinPriceRepository hotelMinPriceRepository; 40 | 41 | @Override 42 | public void initializeRoomForAYear(Room room) { 43 | LocalDate today = LocalDate.now(); 44 | LocalDate endDate = today.plusYears(1); 45 | for (; !today.isAfter(endDate); today=today.plusDays(1)) { 46 | Inventory inventory = Inventory.builder() 47 | .hotel(room.getHotel()) 48 | .room(room) 49 | .bookedCount(0) 50 | .city(room.getHotel().getCity()) 51 | .date(today) 52 | .price(room.getBasePrice()) 53 | .surgeFactor(BigDecimal.ONE) 54 | .totalCount(room.getTotalCount()) 55 | .closed(false) 56 | .reservedCount(0) 57 | .build(); 58 | inventoryRepository.save(inventory); 59 | } 60 | } 61 | 62 | @Override 63 | public void deleteAllInventories(Room room) { 64 | log.info("Deleting inventories of the room with id {} ",room.getId()); 65 | LocalDate today = LocalDate.now(); 66 | inventoryRepository.deleteByRoom(room); 67 | } 68 | 69 | @Override 70 | public Page searchHotels(HotelSearchRequest hotelSearchRequest) { 71 | log.info("Searching Hotels for {} city,from {} to {}", 72 | hotelSearchRequest.getCity(),hotelSearchRequest.getStartDate(),hotelSearchRequest.getEndDate()); 73 | 74 | Pageable pageable = PageRequest 75 | .of(hotelSearchRequest.getPage() , hotelSearchRequest.getSize()); 76 | long dateCount = ChronoUnit.DAYS 77 | .between(hotelSearchRequest.getStartDate(),hotelSearchRequest.getEndDate())+1; 78 | log.info("Getting info by page form"); 79 | Page hotelPage = hotelMinPriceRepository 80 | .findHotelsWithAvailableInventory(hotelSearchRequest.getCity() 81 | , hotelSearchRequest.getStartDate(),hotelSearchRequest.getEndDate(), 82 | hotelSearchRequest.getRoomsCount(),dateCount,pageable 83 | ); 84 | return hotelPage; 85 | } 86 | 87 | @Override 88 | public List getAllInventoryByRoom(Long roomId) { 89 | log.info("Getting all Inventory of a room with roomId : {}",roomId); 90 | Room room = roomRepository.findById(roomId) 91 | .orElseThrow(()->new ResourceNotFound("Room with this Id : "+roomId+"Not found")); 92 | 93 | User user = getCurrentUser(); 94 | if (!user.getId().equals(room.getHotel().getOwner().getId())){ 95 | throw new UnAuthorizedException("This hotel is not belongs to this user : "+user.getUsername()); 96 | } 97 | List inventoryList = inventoryRepository.findByRoomOrderByDate(room); 98 | return inventoryList.stream() 99 | .map(inventory -> modelMapper.map(inventory,InventoryDTOS.class)) 100 | .collect(Collectors.toList()); 101 | 102 | } 103 | 104 | 105 | @Transactional 106 | @Override 107 | public void updateInventory(Long roomId, UpdateInventoryRequestDTO updateInventoryRequestDTO) { 108 | log.info("Updating Inventory of a room with roomId in a Given DateRange: {}",roomId); 109 | Room room = roomRepository.findById(roomId) 110 | .orElseThrow(()->new ResourceNotFound("Room with this Id : "+roomId+"Not found")); 111 | 112 | User user = getCurrentUser(); 113 | if (!user.getId().equals(room.getHotel().getOwner().getId())){ 114 | throw new UnAuthorizedException("This hotel is not belongs to this user : "+user.getUsername()); 115 | } 116 | 117 | inventoryRepository.getInventoryAndLockBeforeUpdate(roomId,updateInventoryRequestDTO.getStartDate(), 118 | updateInventoryRequestDTO.getEndDate()); 119 | 120 | 121 | inventoryRepository.updateInventory(roomId,updateInventoryRequestDTO.getStartDate(), 122 | updateInventoryRequestDTO.getEndDate(),updateInventoryRequestDTO.getSurgeFactor(), 123 | updateInventoryRequestDTO.getClosed()); 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/RoomServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.Exception.ResourceNotFound; 4 | import com.soumyajit.HotelBooking.Exception.UnAuthorizedException; 5 | import com.soumyajit.HotelBooking.dtos.HotelDTOS; 6 | import com.soumyajit.HotelBooking.dtos.RoomDTOS; 7 | import com.soumyajit.HotelBooking.entities.Hotel; 8 | import com.soumyajit.HotelBooking.entities.Room; 9 | import com.soumyajit.HotelBooking.entities.User; 10 | import com.soumyajit.HotelBooking.repository.HotelRepository; 11 | import com.soumyajit.HotelBooking.repository.RoomRepository; 12 | import lombok.RequiredArgsConstructor; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.modelmapper.ModelMapper; 15 | import org.springframework.security.core.context.SecurityContextHolder; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | import static com.soumyajit.HotelBooking.util.AppUtils.getCurrentUser; 23 | 24 | @Service 25 | @RequiredArgsConstructor 26 | @Slf4j 27 | public class RoomServiceImpl implements RoomService{ 28 | 29 | private final ModelMapper modelMapper; 30 | private final RoomRepository roomRepository; 31 | private final HotelRepository hotelRepository; 32 | private final InventoryService inventoryService; 33 | 34 | 35 | @Override 36 | @org.springframework.transaction.annotation.Transactional 37 | public RoomDTOS createNewRoom(Long hotelId , RoomDTOS roomDTOS) { 38 | log.info("Creating new room in Hotel with Id {}",hotelId); 39 | Hotel hotel = hotelRepository.findById(hotelId) 40 | .orElseThrow(()->new ResourceNotFound("Hotel not found with ID :"+hotelId)); 41 | 42 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 43 | if(!user.equals(hotel.getOwner())){ //if the Admin isn't won the hotel, if we don't put this any admin can access this and can create Room this 44 | throw new UnAuthorizedException("This user doesn't won this hotel with id: "+hotelId); 45 | } 46 | 47 | Room room = modelMapper.map(roomDTOS,Room.class); 48 | room.setHotel(hotel); 49 | room = roomRepository.save(room); 50 | if(hotel.getIsActive()){ //initialize room when hotel getActivate 51 | inventoryService.initializeRoomForAYear(room); 52 | } 53 | 54 | return modelMapper.map(room,RoomDTOS.class); 55 | } 56 | 57 | @Override 58 | public List getAllRoomsInAHotel(Long hotelId) { 59 | log.info("Getting all rooms in Hotel with Id {}",hotelId); 60 | Hotel hotel = hotelRepository.findById(hotelId) 61 | .orElseThrow(()->new ResourceNotFound("Hotel not found with ID :"+hotelId)); 62 | 63 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 64 | if(!user.equals(hotel.getOwner())){ //if the Admin isn't won the hotel, if we don't put this any admin can access this 65 | throw new UnAuthorizedException("This user doesn't won this hotel with id: "+hotelId); 66 | } 67 | 68 | return hotel.getRoom().stream().map((elements)-> 69 | modelMapper.map(elements,RoomDTOS.class)) 70 | .collect(Collectors.toList()); 71 | } 72 | 73 | @Override 74 | public RoomDTOS getRoomById(Long roomId) { 75 | log.info("Getting the room with Id {}",roomId); 76 | Room room = roomRepository 77 | .findById(roomId) 78 | .orElseThrow(()->new ResourceNotFound("Room not found with ID :"+roomId)); 79 | return modelMapper.map(room,RoomDTOS.class); 80 | } 81 | 82 | 83 | @Override 84 | @Transactional 85 | public boolean deleteRoomById(Long roomId) { 86 | log.info("Deleting the room with Id {}",roomId); 87 | Room room = roomRepository.findById(roomId) 88 | .orElseThrow(()->new ResourceNotFound("Room not found with ID :"+roomId)); 89 | 90 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 91 | if(!user.equals(room.getHotel().getOwner())){ //if the Admin isn't won the hotel, if we don't put this any admin can access this and can delete this 92 | throw new UnAuthorizedException("This user doesn't won this room with id: "+roomId); 93 | } 94 | 95 | inventoryService.deleteAllInventories(room); 96 | roomRepository.deleteById(roomId); 97 | return true; 98 | } 99 | 100 | @Override 101 | public RoomDTOS updateRoom(Long hotelId, Long roomId, RoomDTOS roomDTOS) { 102 | log.info("Updating room with this : {}",roomId); 103 | Hotel hotel = hotelRepository.findById(hotelId) 104 | .orElseThrow(()->new ResourceNotFound("Hotel with this hotelId not found"+hotelId)); 105 | 106 | User user = getCurrentUser(); 107 | if(!user.getId().equals(hotel.getOwner().getId())){ //if the Admin isn't won the hotel if we don't put this any admin can access this and can update this 108 | throw new UnAuthorizedException("This user doesn't won this hotel with id: "+hotelId); 109 | } 110 | Room room = roomRepository.findById(roomId) 111 | .orElseThrow(()->new ResourceNotFound("Room with this roomId notfound : "+roomId)); 112 | 113 | modelMapper.map(roomDTOS,room); 114 | room.setId(roomId); 115 | 116 | //TODO : if price or inventory updated , update the inventory for room as well 117 | 118 | room=roomRepository.save(room); 119 | return modelMapper.map(room, RoomDTOS.class); 120 | } 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | boolean isHotelExists(Long hotelId){ 136 | boolean isExists = hotelRepository.existsById(hotelId); 137 | if(isExists){ 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | 144 | boolean isExists(Long roomId){ 145 | boolean isExists = roomRepository.existsById(roomId); 146 | if(!isExists){ 147 | return false; 148 | } 149 | return true; 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/repository/InventoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.repository; 2 | 3 | import com.soumyajit.HotelBooking.entities.Hotel; 4 | import com.soumyajit.HotelBooking.entities.Inventory; 5 | import com.soumyajit.HotelBooking.entities.Room; 6 | import jakarta.persistence.LockModeType; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.data.jpa.repository.JpaRepository; 10 | import org.springframework.data.jpa.repository.Lock; 11 | import org.springframework.data.jpa.repository.Modifying; 12 | import org.springframework.data.jpa.repository.Query; 13 | import org.springframework.data.repository.query.Param; 14 | import org.springframework.stereotype.Repository; 15 | 16 | import java.math.BigDecimal; 17 | import java.time.LocalDate; 18 | import java.util.List; 19 | 20 | @Repository 21 | public interface InventoryRepository extends JpaRepository { 22 | 23 | void deleteByRoom( Room room); 24 | 25 | @Query(""" 26 | SELECT DISTINCT i.hotel 27 | FROM Inventory i 28 | WHERE i.city = :city 29 | AND i.date BETWEEN :startDate AND :endDate 30 | AND i.closed = false 31 | AND (i.totalCount - i.bookedCount - i.reservedCount) >= :roomsCount 32 | GROUP BY i.hotel,i.room 33 | HAVING COUNT(i.date) = :dateCount 34 | """) 35 | Page findHotelsWithAvailableInventory 36 | ( 37 | @Param("city") String city, 38 | @Param("startDate")LocalDate startDate, 39 | @Param("endDate") LocalDate endDate, 40 | @Param("roomsCount") Integer roomsCount, 41 | @Param("dateCount") Long dateCount, 42 | Pageable pageable 43 | ); 44 | 45 | 46 | @Query(""" 47 | SELECT i\s 48 | FROM Inventory i 49 | WHERE i.room.id = :roomId 50 | AND i.date BETWEEN :startDate AND :endDate 51 | AND i.closed = false 52 | AND (i.totalCount - i.bookedCount - i.reservedCount) >= :roomsCount 53 | \s""") 54 | @Lock(LockModeType.PESSIMISTIC_WRITE) 55 | List findAndLockAvailableInventory( 56 | @Param("roomId") Long roomId, 57 | @Param("startDate")LocalDate startDate, 58 | @Param("endDate") LocalDate endDate, 59 | @Param("roomsCount") Integer roomsCount 60 | ); 61 | 62 | 63 | List findByHotelAndDateBetween(Hotel hotel, LocalDate startDate, LocalDate endDate); 64 | 65 | List findByRoomOrderByDate(Room room); 66 | 67 | 68 | 69 | @Modifying 70 | @Query(""" 71 | UPDATE Inventory i 72 | SET i.reservedCount = i.reservedCount + :numberOfRooms 73 | WHERE i.room.id = :roomId 74 | AND i.date BETWEEN :startDate AND :endDate 75 | AND (i.totalCount - i.bookedCount - i.reservedCount) >= :numberOfRooms 76 | AND i.closed = false 77 | """) 78 | void initBooking(@Param("roomId") Long roomId, 79 | @Param("startDate") LocalDate startDate, 80 | @Param("endDate") LocalDate endDate, 81 | @Param("numberOfRooms") int numberOfRooms); 82 | 83 | 84 | 85 | 86 | @Query(""" 87 | SELECT i 88 | FROM Inventory i 89 | WHERE i.room.id = :roomId 90 | AND i.date BETWEEN :startDate AND :endDate 91 | AND (i.totalCount - i.bookedCount) >= :numberOfRooms 92 | AND i.closed = false 93 | """) 94 | @Lock(LockModeType.PESSIMISTIC_WRITE) 95 | List findAndLockReservedInventory(@Param("roomId") Long roomId, 96 | @Param("startDate") LocalDate startDate, 97 | @Param("endDate") LocalDate endDate, 98 | @Param("numberOfRooms") int numberOfRooms); 99 | 100 | 101 | @Modifying //modifying means updating query others are select query 102 | @Query(""" 103 | UPDATE Inventory i 104 | SET i.reservedCount = i.reservedCount - :numberOfRooms, 105 | i.bookedCount = i.bookedCount + :numberOfRooms 106 | WHERE i.room.id = :roomId 107 | AND i.date BETWEEN :startDate AND :endDate 108 | AND (i.totalCount - i.bookedCount) >= :numberOfRooms 109 | AND i.reservedCount >= :numberOfRooms 110 | AND i.closed = false 111 | """) 112 | void confirmBooking(@Param("roomId") Long roomId, //this is confirmed booking query 113 | @Param("startDate") LocalDate startDate, 114 | @Param("endDate") LocalDate endDate, 115 | @Param("numberOfRooms") int numberOfRooms); 116 | 117 | 118 | 119 | 120 | @Modifying 121 | @Query(""" 122 | UPDATE Inventory i 123 | SET i.bookedCount = i.bookedCount - :numberOfRooms 124 | WHERE i.room.id = :roomId 125 | AND i.date BETWEEN :startDate AND :endDate 126 | AND (i.totalCount - i.bookedCount) >= :numberOfRooms 127 | AND i.closed = false 128 | """) 129 | void cancelBooking(@Param("roomId") Long roomId, 130 | @Param("startDate") LocalDate startDate, 131 | @Param("endDate") LocalDate endDate, 132 | @Param("numberOfRooms") int numberOfRooms); 133 | 134 | 135 | 136 | 137 | @Query(""" 138 | SELECT i 139 | FROM Inventory i 140 | WHERE i.room.id = :roomId 141 | AND i.date BETWEEN :startDate AND :endDate 142 | """) 143 | @Lock(LockModeType.PESSIMISTIC_WRITE) 144 | List getInventoryAndLockBeforeUpdate(@Param("roomId") Long roomId, 145 | @Param("startDate") LocalDate startDate, 146 | @Param("endDate") LocalDate endDate); 147 | 148 | 149 | 150 | @Modifying 151 | @Query(""" 152 | UPDATE Inventory i 153 | SET i.surgeFactor = :surgeFactor, 154 | i.closed = :closed 155 | WHERE i.room.id = :roomId 156 | AND i.date BETWEEN :startDate AND :endDate 157 | """) 158 | void updateInventory(@Param("roomId") Long roomId, 159 | @Param("startDate") LocalDate startDate, 160 | @Param("endDate") LocalDate endDate, 161 | @Param("surgeFactor") BigDecimal surgeFactor, 162 | @Param("closed") boolean closed); 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/HotelServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.Exception.ResourceNotFound; 4 | import com.soumyajit.HotelBooking.Exception.UnAuthorizedException; 5 | import com.soumyajit.HotelBooking.dtos.HotelDTOS; 6 | import com.soumyajit.HotelBooking.dtos.HotelInfoDTOS; 7 | import com.soumyajit.HotelBooking.dtos.RoomDTOS; 8 | import com.soumyajit.HotelBooking.entities.Hotel; 9 | import com.soumyajit.HotelBooking.entities.Room; 10 | import com.soumyajit.HotelBooking.entities.User; 11 | import com.soumyajit.HotelBooking.repository.HotelRepository; 12 | import com.soumyajit.HotelBooking.repository.RoomRepository; 13 | import lombok.RequiredArgsConstructor; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.modelmapper.ModelMapper; 16 | import org.springframework.security.core.context.SecurityContextHolder; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.transaction.annotation.Transactional; 19 | 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | import static com.soumyajit.HotelBooking.util.AppUtils.getCurrentUser; 24 | 25 | @Service 26 | @RequiredArgsConstructor 27 | @Slf4j 28 | public class HotelServiceImpl implements HotelService { 29 | 30 | private final HotelRepository hotelRepository; 31 | private final ModelMapper modelMapper; 32 | private final InventoryService inventoryService; 33 | private final RoomRepository roomRepository; 34 | 35 | @Override 36 | public HotelDTOS createNewHotel(HotelDTOS hotelDTOS) { 37 | log.info("Creating new Hotel with name:{}" ,hotelDTOS.getName()); 38 | Hotel hotel = modelMapper.map(hotelDTOS,Hotel.class); 39 | hotel.setIsActive(false); 40 | 41 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 42 | hotel.setOwner(user); 43 | 44 | Hotel savedHotel = hotelRepository.save(hotel); 45 | log.info("Created a new Hotel with this : {}",hotelDTOS.getId()); 46 | return modelMapper.map(savedHotel,HotelDTOS.class); 47 | } 48 | 49 | @Override 50 | public HotelDTOS getHotelById(Long id) { 51 | log.info("Getting a new Hotel with this : {}",id); 52 | Hotel hotel = hotelRepository 53 | .findById(id) 54 | .orElseThrow(()->new ResourceNotFound("Hotel not found with ID :"+id)); 55 | 56 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 57 | 58 | if(!user.equals(hotel.getOwner())){ //if the Admin isn't won the hotel if we don't put this any admin can access this 59 | throw new UnAuthorizedException("This user doesn't won this hotel with id: "+id); 60 | } 61 | 62 | return modelMapper.map(hotel,HotelDTOS.class); 63 | } 64 | 65 | private Boolean isExists(Long hotelId){ 66 | boolean isExists = hotelRepository.existsById(hotelId); 67 | if(!isExists){ 68 | return false; 69 | }else { 70 | return true; 71 | } 72 | } 73 | 74 | 75 | @Override 76 | public HotelDTOS updateHotelById(Long id, HotelDTOS hotelDTOS) { 77 | log.info("Updating Hotel with this : {}",id); 78 | if(!isExists(id)){ 79 | throw new ResourceNotFound("Hotel not found with ID :"+id); 80 | } 81 | 82 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 83 | 84 | Hotel hotel = modelMapper.map(hotelDTOS, Hotel.class); 85 | 86 | if(!user.equals(hotel.getOwner())){ //if the Admin isn't won the hotel if we don't put this any admin can access this and can update this 87 | throw new UnAuthorizedException("This user doesn't won this hotel with id: "+id); 88 | } 89 | hotel.setId(id); 90 | Hotel savedHotel = hotelRepository.save(hotel); 91 | return modelMapper.map(savedHotel, HotelDTOS.class); 92 | } 93 | 94 | @Override 95 | @org.springframework.transaction.annotation.Transactional 96 | public Boolean deleteHotelById(Long hotelId) { 97 | log.info("Deleting Hotel with this : {}",hotelId); 98 | Hotel hotel = hotelRepository.findById(hotelId) 99 | .orElseThrow(()->new ResourceNotFound("Hotel not found with ID :"+hotelId)); 100 | 101 | 102 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 103 | if(!user.equals(hotel.getOwner())){ //if the Admin isn't won the hotel, if we don't put this any admin can access this and can delete this 104 | throw new UnAuthorizedException("This user doesn't won this hotel with id: "+hotelId); 105 | } 106 | 107 | for(Room room : hotel.getRoom()){ 108 | inventoryService.deleteAllInventories(room); 109 | roomRepository.deleteById(room.getId()); 110 | } 111 | hotelRepository.deleteById(hotelId); 112 | return true; 113 | } 114 | 115 | @Override 116 | @Transactional 117 | public void activateHotel(Long hotelId) { 118 | log.info("Activating the hotel with ID: {}", hotelId); 119 | Hotel hotel = hotelRepository 120 | .findById(hotelId) 121 | .orElseThrow(() -> new ResourceNotFound("Hotel not found with ID: "+hotelId)); 122 | 123 | User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 124 | if(!user.equals(hotel.getOwner())){ //if the Admin isn't won the hotel, if we don't put this any admin can access this and can activate this 125 | throw new UnAuthorizedException("This user doesn't won this hotel with id: "+hotelId); 126 | } 127 | 128 | hotel.setIsActive(true); 129 | 130 | if(hotel.getIsActive()){ 131 | for(Room room: hotel.getRoom()) { 132 | inventoryService.initializeRoomForAYear(room); 133 | } 134 | } 135 | // assuming only do it once 136 | 137 | } 138 | 139 | @Override 140 | public HotelInfoDTOS getHotelInfoById(Long hotelId) { //public route 141 | log.info("Getting the hotel information with ID: {}", hotelId); 142 | Hotel hotel = hotelRepository 143 | .findById(hotelId) 144 | .orElseThrow(() -> new ResourceNotFound("Hotel not found with ID: "+hotelId)); 145 | 146 | List roomDTOS = hotel.getRoom().stream().map(room -> 147 | modelMapper.map(room, RoomDTOS.class)) 148 | .toList(); 149 | return new HotelInfoDTOS(modelMapper.map(hotel, HotelDTOS.class),roomDTOS); 150 | } 151 | 152 | @Override 153 | public List getAllHotels() { 154 | User user = getCurrentUser(); 155 | log.info("Getting all hotels for the admin user with Id {}: ",user.getId()); 156 | List hotel = hotelRepository.findByOwner(user); 157 | return hotel.stream() 158 | .map(hotel1 -> modelMapper.map(hotel1,HotelDTOS.class)) 159 | .collect(Collectors.toList()); 160 | 161 | 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /src/main/java/com/soumyajit/HotelBooking/service/BookingServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.soumyajit.HotelBooking.service; 2 | 3 | import com.soumyajit.HotelBooking.Exception.ResourceNotFound; 4 | import com.soumyajit.HotelBooking.Exception.UnAuthorizedException; 5 | import com.soumyajit.HotelBooking.Strategy.PricingService; 6 | import com.soumyajit.HotelBooking.dtos.BookingDTOS; 7 | import com.soumyajit.HotelBooking.dtos.BookingRequest; 8 | import com.soumyajit.HotelBooking.dtos.GuestDTOS; 9 | import com.soumyajit.HotelBooking.dtos.HotelReportsDTOS; 10 | import com.soumyajit.HotelBooking.entities.*; 11 | import com.soumyajit.HotelBooking.entities.Enums.BookingStatus; 12 | import com.soumyajit.HotelBooking.repository.*; 13 | import com.stripe.exception.StripeException; 14 | import com.stripe.model.Event; 15 | import com.stripe.model.Refund; 16 | import com.stripe.model.TODO; 17 | import com.stripe.model.checkout.Session; 18 | import com.stripe.param.RefundCreateParams; 19 | import lombok.RequiredArgsConstructor; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.modelmapper.ModelMapper; 22 | import org.springframework.beans.factory.annotation.Value; 23 | import org.springframework.security.core.context.SecurityContextHolder; 24 | import org.springframework.stereotype.Service; 25 | import org.springframework.transaction.annotation.Transactional; 26 | 27 | import java.math.BigDecimal; 28 | import java.math.RoundingMode; 29 | import java.time.LocalDate; 30 | import java.time.LocalDateTime; 31 | import java.time.LocalTime; 32 | import java.time.temporal.ChronoUnit; 33 | import java.util.List; 34 | import java.util.stream.Collectors; 35 | 36 | import static com.soumyajit.HotelBooking.util.AppUtils.getCurrentUser; 37 | 38 | 39 | @Service 40 | @RequiredArgsConstructor 41 | @Slf4j 42 | public class BookingServiceImpl implements BookingService{ 43 | 44 | private final GuestRepository guestRepository; 45 | private final BookingRepository bookingRepository; 46 | private final HotelRepository hotelRepository; 47 | private final RoomRepository roomRepository; 48 | private final InventoryRepository inventoryRepository; 49 | private final ModelMapper modelMapper; 50 | private final CheckOutService checkOutService; 51 | private final PricingService pricingService; 52 | 53 | @Value("${frontend.url}") 54 | private String frontendUrl; 55 | 56 | @Override 57 | @Transactional 58 | public BookingDTOS initialiseBooking(BookingRequest bookingRequest) { 59 | 60 | log.info("Initialising booking for hotel : {} , room: {}, date {}-{}", 61 | bookingRequest.getHotelId(),bookingRequest.getRoomId() 62 | ,bookingRequest.getCheckInDate(),bookingRequest.getCheckOutDate()); 63 | 64 | Hotel hotel = hotelRepository.findById(bookingRequest.getHotelId()) 65 | .orElseThrow(()->new ResourceNotFound("Hotel not found with id "+bookingRequest.getHotelId())); 66 | Room room = roomRepository.findById(bookingRequest.getRoomId()) 67 | .orElseThrow(()->new ResourceNotFound("Room not found with id "+bookingRequest.getRoomId())); 68 | 69 | List inventoryList = inventoryRepository.findAndLockAvailableInventory( 70 | room.getId(),bookingRequest.getCheckInDate(), 71 | bookingRequest.getCheckOutDate(),bookingRequest.getRoomsCount() 72 | ); 73 | long daysCount = ChronoUnit.DAYS.between(bookingRequest.getCheckInDate(),bookingRequest.getCheckOutDate())+1; 74 | if(inventoryList.size() != daysCount){ 75 | throw new IllegalStateException("Rooms are not available for anymore"); 76 | } 77 | 78 | // Reverse the room / update the booked count of inventories 79 | 80 | inventoryRepository.initBooking(room.getId(), 81 | bookingRequest.getCheckInDate(), 82 | bookingRequest.getCheckOutDate(), 83 | bookingRequest.getRoomsCount()); 84 | 85 | //create the Booking 86 | 87 | BigDecimal priceForOneRoom = pricingService.calculateTotalPrice(inventoryList); 88 | BigDecimal totalPrice = priceForOneRoom.multiply(BigDecimal.valueOf(bookingRequest.getRoomsCount())); 89 | 90 | Booking booking = Booking.builder() 91 | .bookingStatus(BookingStatus.RESERVED) 92 | .checkedInDate(bookingRequest.getCheckInDate()) 93 | .checkedOutDate(bookingRequest.getCheckOutDate()) 94 | .roomsCount(bookingRequest.getRoomsCount()) 95 | .hotel(hotel) 96 | .room(room) 97 | .user(getCurrentUser()) 98 | .amount(totalPrice) 99 | //.guest() //TODO Guests will be added later 100 | .build(); 101 | 102 | booking = bookingRepository.save(booking); 103 | return modelMapper.map(booking, BookingDTOS.class); 104 | } 105 | 106 | @Override 107 | @Transactional 108 | public BookingDTOS addGuests(Long bookingId, List guestDTOSList) { 109 | 110 | log.info("Adding Guests for Booking with Id {} ",bookingId); 111 | 112 | Booking booking = bookingRepository.findById(bookingId) 113 | .orElseThrow(()->new ResourceNotFound("Booking not found with id "+bookingId)); 114 | 115 | 116 | User user = getCurrentUser(); 117 | if (!user.getId().equals(booking.getUser().getId())) { 118 | throw new UnAuthorizedException("Booking does not belong to this user with id: "+user.getId()); 119 | } 120 | 121 | 122 | if(hasBookingExpired(booking)){ 123 | throw new IllegalStateException("Booking has already expired"); 124 | } 125 | 126 | 127 | if(booking.getBookingStatus() != BookingStatus.RESERVED){ 128 | throw new IllegalStateException("Booking is not under reserved state , cannot add guests"); 129 | } 130 | 131 | for(GuestDTOS guestDTOS : guestDTOSList){ 132 | Guest guest = modelMapper.map(guestDTOS, Guest.class); 133 | guest.setUser(getCurrentUser()); //set the Authenticated User 134 | guest = guestRepository.save(guest); 135 | booking.getGuest().add(guest); 136 | } 137 | 138 | booking.setBookingStatus(BookingStatus.GUESTS_ADDED); 139 | return modelMapper.map(bookingRepository.save(booking), BookingDTOS.class); 140 | } 141 | 142 | @Override 143 | @Transactional 144 | public String initiatePayment(Long bookingId) { 145 | log.info("Initialise Payments for Booking with Id {} ",bookingId); 146 | Booking booking = bookingRepository.findById(bookingId) 147 | .orElseThrow(()->new ResourceNotFound("Booking not found with id "+bookingId)); 148 | 149 | User user = getCurrentUser(); 150 | if (user.getId().equals(booking.getUser().getId())) { 151 | if(hasBookingExpired(booking)){ //check the booking hasn't expired 152 | throw new IllegalStateException("Booking has already expired"); 153 | } 154 | 155 | //success url and failureUrl are comes from frontend part 156 | String sessionURL = checkOutService.getCheckOutSession(booking, 157 | frontendUrl+"/payments/successURL", 158 | frontendUrl+"/payments/failureURL"); 159 | 160 | booking.setBookingStatus(BookingStatus.PAYMENT_PENDING); 161 | bookingRepository.save(booking); 162 | 163 | return sessionURL; 164 | } 165 | 166 | throw new UnAuthorizedException("Booking does not belong to this user with id: "+user.getId()); 167 | } 168 | 169 | @Override 170 | @Transactional 171 | public void capturePayment(Event event) { 172 | if ("checkout.session.completed".equals(event.getType())) { 173 | Session session = (Session) event.getDataObjectDeserializer().getObject().orElse(null); 174 | if (session == null) return; 175 | 176 | String sessionId = session.getId(); 177 | 178 | Booking booking = 179 | bookingRepository.findByPaymentSessionId(sessionId).orElseThrow(() -> 180 | new ResourceNotFound("Booking not found for session ID: "+sessionId)); //find the session id 181 | 182 | booking.setBookingStatus(BookingStatus.CONFIRMED); //now booking is confirmed 183 | bookingRepository.save(booking); //save the booking 184 | 185 | inventoryRepository.findAndLockReservedInventory(booking.getRoom().getId(), booking.getCheckedInDate(), 186 | booking.getCheckedOutDate(), booking.getRoomsCount()); 187 | 188 | inventoryRepository.confirmBooking(booking.getRoom().getId(), booking.getCheckedInDate(), 189 | booking.getCheckedOutDate(), booking.getRoomsCount()); 190 | 191 | log.info("Successfully confirmed the booking for Booking ID: {}", booking.getId()); 192 | } else { 193 | log.warn("Unhandled event type: {}", event.getType()); 194 | } 195 | } 196 | 197 | 198 | @Override //cancel payment 199 | @Transactional 200 | public void cancelPayment(Long bookingId) { 201 | Booking booking = bookingRepository.findById(bookingId).orElseThrow( 202 | () -> new ResourceNotFound("Booking not found with id: "+bookingId) 203 | ); 204 | 205 | User user = getCurrentUser(); 206 | if (!user.getId().equals(booking.getUser().getId())) { 207 | throw new UnAuthorizedException("Booking does not belong to this user with id: "+user.getId()); 208 | } 209 | 210 | if(booking.getBookingStatus() != BookingStatus.CONFIRMED) { 211 | throw new IllegalStateException("Only confirmed bookings can be cancelled"); 212 | } 213 | 214 | booking.setBookingStatus(BookingStatus.CANCELLED); 215 | bookingRepository.save(booking); 216 | 217 | inventoryRepository.findAndLockReservedInventory(booking.getRoom().getId(), booking.getCheckedInDate(), 218 | booking.getCheckedOutDate(), booking.getRoomsCount()); 219 | 220 | inventoryRepository.cancelBooking(booking.getRoom().getId(), booking.getCheckedInDate(), 221 | booking.getCheckedOutDate(), booking.getRoomsCount()); 222 | 223 | // handle the refund 224 | 225 | try { 226 | Session session = Session.retrieve(booking.getPaymentSessionId()); //get session Id 227 | RefundCreateParams refundParams = RefundCreateParams.builder() //for refund 228 | .setPaymentIntent(session.getPaymentIntent()) 229 | .build(); 230 | 231 | Refund.create(refundParams); //create Refund 232 | } catch (StripeException e) { 233 | throw new RuntimeException(e); 234 | } 235 | } 236 | 237 | //get booking status 238 | @Override 239 | public String getBookingStatus(Long bookingId) { 240 | Booking booking = bookingRepository.findById(bookingId).orElseThrow( 241 | () -> new ResourceNotFound("Booking not found with id: "+bookingId) 242 | ); 243 | 244 | User user = getCurrentUser(); 245 | if (!user.getId().equals(booking.getUser().getId())) { 246 | throw new UnAuthorizedException("Booking does not belong to this user with id: "+user.getId()); 247 | } 248 | return booking.getBookingStatus().name(); 249 | } 250 | 251 | @Override 252 | @Transactional 253 | public List getAllBookings(Long hotelId) { 254 | Hotel hotel = hotelRepository.findById(hotelId) 255 | .orElseThrow(()->new ResourceNotFound("Hotel not found with id "+hotelId)); 256 | 257 | log.info("Getting all bookings of the Hotel with Id: {}",hotelId); 258 | 259 | User user = getCurrentUser(); 260 | if (!user.getId().equals(hotel.getOwner().getId())){ 261 | throw new UnAuthorizedException("Hotel does not belong to this user with id: "+hotelId); 262 | } 263 | 264 | log.info("User is owns this hotel"); 265 | 266 | List bookings = bookingRepository.findByHotel(hotel); 267 | 268 | return bookings.stream() 269 | .map(booking -> 270 | modelMapper.map(booking,BookingDTOS.class)) 271 | .collect(Collectors.toList()); 272 | 273 | } 274 | 275 | @Override 276 | public HotelReportsDTOS getHotelReport(Long hotelId, LocalDate startDate, LocalDate endDate) { 277 | Hotel hotel = hotelRepository.findById(hotelId) 278 | .orElseThrow(()->new ResourceNotFound("Hotel not found with id "+hotelId)); 279 | 280 | log.info("Getting all Reports of the Hotel with Id: {}",hotelId); 281 | 282 | User user = getCurrentUser(); 283 | if (!user.getId().equals(hotel.getOwner().getId())){ 284 | throw new UnAuthorizedException("Hotel does not belong to this user with id: "+hotelId); 285 | } 286 | 287 | LocalDateTime startingTime = startDate.atStartOfDay(); 288 | LocalDateTime endingTime = endDate.atTime(LocalTime.MAX); 289 | 290 | List bookings = bookingRepository.findByHotelAndCreatedAtBetween(hotel,startingTime,endingTime); 291 | 292 | Long totalConfirmedBooking = bookings 293 | .stream() 294 | .filter(booking -> booking.getBookingStatus()==BookingStatus.CONFIRMED) 295 | .count(); 296 | 297 | BigDecimal totalRevenueConfirmedBooking = bookings.stream() 298 | .filter(booking -> booking.getBookingStatus()==BookingStatus.CONFIRMED) 299 | .map(Booking::getAmount) 300 | .reduce(BigDecimal.ZERO,BigDecimal::add); 301 | 302 | BigDecimal avgRevenue = totalConfirmedBooking==0 ? BigDecimal.ZERO: 303 | totalRevenueConfirmedBooking 304 | .divide(BigDecimal.valueOf(totalConfirmedBooking), RoundingMode.HALF_UP); 305 | 306 | return new HotelReportsDTOS(totalConfirmedBooking,totalRevenueConfirmedBooking,avgRevenue); 307 | } 308 | 309 | @Override 310 | public List getAllMyBookings() { 311 | User user = getCurrentUser(); 312 | List bookings = bookingRepository.findByUser(user); 313 | return bookings.stream() 314 | .map(booking -> modelMapper.map(booking,BookingDTOS.class)) 315 | .collect(Collectors.toList()); 316 | } 317 | 318 | 319 | private boolean hasBookingExpired(Booking booking){ 320 | return booking.getCreatedAt().plusMinutes(10).isBefore(LocalDateTime.now()); 321 | } 322 | 323 | 324 | //get Authenticated User 325 | 326 | 327 | } 328 | --------------------------------------------------------------------------------