Skip to content
Snippets Groups Projects
user avatar
CHRIS-HP\chrii authored
90bba38a
History

StayFinder

1. Introduction

StayFinder is the final project for the courses IDATA2301 Web Technologies and IDATA2306 Application Development at the Norwegian University of Science and Technology (NTNU) in Ålesund.

This project is a hotel price aggregator web application — similar to Trivago or Kayak — that allows users to search for hotels based on their preferences and compare prices across multiple booking sites to find the best deals.

Because this site is meant to be a price aggregator, we have a fake booking-system with no payment processing, meant to simulate the user being sent to a 3rd party booking site.

Please note that since this is a school project, most of the content is fictional. The application does not provide real services or provide accurate information.

This README file pertains to the backend development (IDATA2306). Some content may overlap with the README of the frontend repository, where relevant.

2. Repository Structure

The repository is structured as follows:

Stayfinder/ 
├── logs/ # Debug and Hibernate logs
│ ├── debug.log 
│ └── hibernate.log 
├── poastman/ # Postman test collection
│ ├── Full Test Suite.postman_collection.json # Postman collection
│ └── local.postman_environment.json # Environment for tests
├── src/ 
│ ├── main/ 
│ │ ├── java/ 
│ │ │ └── edu.ntnu.group8.stayfinder/ 
│ │ │ ├── composite/ # Composite keys
│ │ │ │ └── RoomWebsitePricingId 
│ │ │ ├── controller/ # REST API controllers
│ │ │ │ ├── AmenityController 
│ │ │ │ └── ... 
│ │ │ ├── dto/ # Data Transfer Objects
│ │ │ │ ├── AddFavoriteDTO 
│ │ │ │ └── ... 
│ │ │ ├── entity/ # JPA entities
│ │ │ │ ├── Entity 
│ │ │ │ ├── Hotel 
│ │ │ │ └── ... 
│ │ │ ├── repository/  # JPA repositories
│ │ │ │ ├──AmenityRepository
│ │ │ │ └── ... 
│ │ │ ├── security/ # Security & Authentication
│ │ │ │ ├── SecurityConfig 
│ │ │ │ └── ...
│ │ │ ├── service/  # Service classes
│ │ │ │ ├── AmenityService 
│ │ │ │ └── ...
│ │ │ └── StayFinderApplication 
│ │ └── resources/ 
│ │ ├── application.properties # Spring Boot configuration
│ │ └── logback-spring.xml  # Logging configuration
│ └── test/ 
├── .gitignore 
├── photo-handling.md 
├── README.md 
├── spring-report.md 
├── StayFinder.postman_collection.json # Postman collection
└── pom.xml

3. Deployed Project Architecture

Our project follows a server-side monolithic architecture with REST API communication, where the frontend, backend, and database are all hosted on the same server (provided by the school). The backend application (built with Java and Spring Boot) is cloned and run directly on the server, exposing a REST API. The frontend (HTML, CSS, and JavaScript) is cloned into a separate directory and served via NGINX. The frontend communicates with the backend through HTTP requests to this REST API, and the backend in turn interacts with a locally hosted MySQL database.

This setup reflects a variation of a classic three-tier architecture, with all tiers deployed on a single node. To streamline deployment, we've configured automatic pulling from both the frontend and backend Git repositories on the server.

The architecture can be visualized as follows:

              +----------------------+
              |     NGINX Server     |
              |   (serving frontend) |
              +----------+-----------+
                         |
            +------------v-------------+
            |      Frontend (static)   |
            | 	      HTML/CSS/JS      |
            +------------+-------------+
                         |
                         v
            +------------+-------------+
            |      Backend (Java)      |
            |  REST API (Spring Boot)  |
            +------------+-------------+
                         |
                         v
              +----------+-----------+
              |      MySQL Database  |
              +----------------------+

Diagram generated by ChatGPT based on our description of the architecture.

The flow of data is as follows: img.png

Image from Lecture 11 PowerPoint titled "Full-stack monolith" by Di Wu.

4. Security with HTTPS

Our backend is secured with HTTPS via an NGINX reverse proxy. The Spring Boot application itself listens internally on port 8080, while NGINX handles all HTTPS traffic on port 443. This means that all external communication — including API calls from the frontend — is encrypted using HTTPS.

We have not explicitly configured HTTPS within the Spring Boot application (e.g. via application.properties), because the backend is not exposed directly to the internet. Instead, it is protected behind NGINX, which terminates the TLS connection and forwards requests securely to the backend.

This separation of concerns — where NGINX handles TLS and the application focuses on business logic — is a widely-used and recommended approach in production environments.

The diagram below illustrates this setup:

   [ User's Browser ]
           |
           |   HTTPS (port 443)
           v
     [ NGINX Reverse Proxy ]
           |
           |   HTTP (port 8080)
           v
     [ Spring Boot Backend ]
              |
              |   JDBC (MySQL)
              v
      [ MySQL Database ]

Diagram generated by ChatGPT based on the text above (written by us).

5. Technologies Used

  • Java: The primary programming language used for the backend.
  • Spring Boot: A framework for building Java applications, used to create the REST API.
  • Spring Data JPA: A part of the Spring framework that simplifies database access and management.
  • MySQL: The relational database management system used to store application data.

6. Database

The database is hosted on the server and is managed using MySQL. We have entities for hotels, users, bookings, and amenities, among others, representing tables in the database.

Below is a diagram of the database schema, showing the relationships between the entities (generated by DataGrip): img.png

7. How to Run the Project Locally

7.1 Prerequisites

  • You must be connected to the NTNU network (directly or via VPN).
  • You need access to a properly configured .env file.
  • You need backend access credentials and database configuration (only provided to project developers).

If you have the backend set up, ensure:

  • The .env file in the backend project includes:

    DB_USERNAME
    DB_PASSWORD
    JWT_SECRET
  • CORS is configured to allow requests from the frontend (add the following to the securityFilterChain method in SecurityConfigurer class):

    config.addAllowedOrigin("http://localhost:63342"); // Adjust for your local frontend port

7.2 Running the Backend

  1. Clone the backend repository.
  2. Open the project in your IDE (e.g., IntelliJ or VSCode).
  3. Navigate to the StayFinderApplication class and run it.

7.3 Important Notes

  • All debug or lower logs will appear in logs/debug.log.
  • Hibernate logs will appear in logs/hibernate.log.

8. Postman Tests

This project has been tested using Postman.

8.1 Importing the Postman Collection & Environment

  1. Make sure you have the Postman version with the "Scripts" tab containing "Pre-request" and "Post-response" rather than the "Tests" tab.
  2. Importing the Postman collection:
    • Open Postman and go to the "Collections" tab.
    • Click on "Import" and select the Full Test Suite.postman_collection.json file from the project directory.
  3. Importing the environment:
    • Go to the "Environments" tab in Postman.
    • Click on "Import" and select the local.postman_environment.json file from the project directory.
  4. Set the environment:
    • In the top right corner of Postman, select the imported environment from the dropdown menu.

8.2 Running Tests

  • Open the imported collection "Full Test Suite" in Postman.
  • Right-click on the collection and select "Run".

8.3 Notes

  • The tests are organized into folders based on the functionality they test. You can click on each folder for a description of each test within with expected results.
  • If you wish to test the backend running locally, you may change the environment variable {{BASE_URL}} to http://localhost:8080 in the Postman environment settings.

9. Highlighted Features (Extras)

This section highlights some key features that we believe set our project apart (extras). While there are many features implemented throughout the application, we want to explicitly mention the ones that we think make our project unique or interesting, and which could count as an extra outside the requirements.

9.1 Hotel Search & Filtering (Backend)

The backend exposes a single endpoint for hotel search and filtering:

POST /api/hotels/search/filter

This endpoint accepts a JSON body matching the HotelFilterRequest DTO, which contains all possible filtering and sorting parameters, including:

  • Location (hotel name, city, or country)
  • Check-in and check-out dates
  • Number of guests and rooms
  • Selected amenities
  • Price range (min and max)
  • Sorting preference (e.g., price ascending/descending, star rating)

Note: A POST request is used instead of GET to accommodate complex and potentially lengthy parameter structures. This is a common and accepted practice when working with structured request bodies. (For more information, check out this article: https://www.linkedin.com/pulse/why-we-use-post-instead-get-fetch-data-modern-web-deepika-naik-8mo2c/)

9.1.1 Processing Flow

  1. Request Handling
    The controller receives the HotelFilterRequest object from the frontend and passes it to the service layer.

  2. Validation
    The service layer validates date inputs:

    • Dates must not be in the past.
    • Check-out must be after check-in.
  3. Database Query Execution
    The service delegates the request to the HotelRepository, which uses a dynamic @Query to perform all filtering directly at the database level. This ensures efficiency and avoids unnecessary in-memory filtering in Java.

  4. Result Mapping
    The raw query result is a list of custom objects returned from the database, each representing a hotel with relevant fields (e.g., id, name, location, etc.). These are mapped to DTOs in the service layer and returned to the frontend.

9.1.2 Notes

  • All filtering happens in the database, not in Java code. This makes the process more scalable and performant.
  • The system returns only hotels that match all selected criteria, including matching all selected amenities.
  • The endpoint supports flexible, multi-parameter filtering in a single request, making it simple for the frontend to retrieve up-to-date results based on user preferences.
  • A fallback "Get All Hotels" endpoint is used in case filtering should fail.

9.2 Popular Hotels

The "Popular Hotels" section displays the top 10 most-clicked hotels across all users. This data is updated dynamically based on user interactions.

How it works:

  • When a user clicks on a hotel card, the frontend sends a POST request to the backend containing the user_id and hotel_id. If the user is not logged in, the user_id is set to null, to keep accurate statistics.
  • This request is handled by the backend, which records the click in the viewed_hotels table, along with the current timestamp (NOW()).
  • The frontend fetches the top 10 most-clicked hotels from the backend and displays them in the "Popular Hotels" section on the front page, sorted by the number of clicks in descending order.

9.3 Recently Viewed Hotels

The "Recently Viewed Hotels" section shows the most recent hotels clicked by each individual user. Each user only sees their own recently viewed hotels. If a user is not logged in, they're prompted to log into start tracking their viewed hotels.

How it works:

  • When a user clicks a hotel, the frontend sends a POST request to the backend with the user_id and hotel_id, same as in the "Popular Hotels" section.
  • The backend records the click in the viewed_hotels table, including the timestamp (NOW()).
  • The frontend fetches a list of hotels viewed by the current user by sending the user_id in the GET request, along with an optional parameter for the number of hotels to return (default is 10). The hotels are then returned in order from most to least recently clicked.
  • This list is displayed under the "Recently Viewed Hotels" section, ensuring that each user sees only the hotels they have recently interacted with.

9.4 Admin Page

We've implemented a feature which lets an admin hide and show hotels. This is the only admin requirement, however we've also decided to extend admin functionality to include a full admin page where the admin can manage amenities (and in the future, all other aspects of the website).

The admin can currently:

  • Add Amenity: The admin can add new amenities to the database.
  • Edit Amenity: The admin can edit existing amenities (name and description).
  • Delete Amenity: The admin can delete amenities from the database.
  • Hide/Show Hotels: The admin can hide hotels from the public view, making them invisible to users. They can also choose to make them visible again.

We've also readied buttons for adding, editing, and deleting hotels, seeing all users, managing rooms, etc., but these features are not yet fully implemented, and are meant to showcase what we would include on a real admin page.

9.5 User Profile

The user profile page allows users to view and edit their personal information, including first name, last name, email, and password.

The user can also choose to delete their account. Deleting their account will set their user_id to null in the viewed_hotels table, and remove all their other data, including bookings from the database.

10. Image Handling

Refer to the photo-handling.md file for details on how we handle images in the project.

11. Further Work

11.1 Filtering Query

The current filtering logic in the HotelRepository is implemented using a single, complex @Query. While it functions correctly, the query is long, difficult to maintain, and not easily extensible. In the future, it would be beneficial to refactor this into smaller, reusable components—either by using the Criteria API, QueryDSL, Spring Data Specifications, or something similar. These approaches can help make the logic more modular, readable, and easier to modify as new filtering features are added.

11.2 Additional Postman Tests

While we have a comprehensive set of Postman tests, there are still some areas that could benefit from further testing. For example, we could benefit from more negative tests to ensure that the system behaves as expected when invalid data is provided. Additionally, we could add tests for edge cases, such as filtering with no results or testing the limits of the database (e.g., maximum number of hotels, maximum number of amenities). These tests would help ensure that the system is robust and can handle a variety of scenarios without errors.

11.3 Decouple "Recently Viewed Hotels" and "Popular Hotels"

It would be ideal to decouple the "Recently Viewed Hotels" and "Popular Hotels" features, making them independent of each other. Currently, both features rely on the same viewed_hotels table, which can lead to confusion and potential data integrity issues. However, we figured that for a project of this size, it would be acceptable to keep them together, as they are both based on the same concept of hotel views.