Category: Software
Subcategory: Java Backend
Difficulty: Medium
This challenge simulates a real estate auction system for CaixaBank. The backend is built in Java and offers several functionalities, including:
- User authentication and registration using JWT.
- Extraction (scraping) of property data directly from Idealista.
- An automatic mortgage application system based on user financial data.
- An auction system for properties that uses RabbitMQ to manage concurrent bids.
The goal is to evaluate your skills in secure API development, external data extraction, business rule implementation, and asynchronous message processing using queues.
In addition to the README, you must read and follow this file: RealEstate_App_info
Basic structure of the repository to be taken as a reference, but may vary depending on the proposed solution.
caixabank-backend-java-realestate
βββ src
β βββmain
β βββ java
β β βββ com
β β βββ round3
β β βββ realestate
β β βββ config # Spring Security, RabbitMQ, and other configurations
β β βββ controller # REST API endpoints
β β βββ entity # JPA entities
β β βββ messaging # Message publishing/consuming classes (BidMessage, BidPublisher, etc.)
β β βββ payload # Request and response payload classes
β β βββ repository # Spring Data repositories
β β βββ security # JWT and UserDetails implementations
β βββ resources
β βββ application.properties # Application configuration
β βββ ...
βββ Dockerfile # Docker build instructions
βββ docker-compose.yml
βββ README.md
βββ ...- Task 1: Dockerfile & Health Check
- Task 2: Login and register with JWT
- Task 3: Obtaining property data directly from Idealista
- Task 4: Automatic mortgage application system
- Task 5: Bidding system for a property
Please read the entire README carefully, as well as the detailed guides to fully understand the requirements of the challenge.
This task is only intended to ensure that the correction is being executed correctly.
A health check endpoint is provided and a test is passed to validate that the API is reachable, which ensures that the container with the app has been deployed correctly and the connection to the database has been made.
Before doing the first push, you should make sure that the app works correctly, as all other tasks will be tested by attacking the endpoint generated by this container on port 3000. You can do this by executing:
docker-compose up -dObjective:
Implement secure user registration and login endpoints using JSON Web Tokens (JWT) for authentication.
Detailed Requirements:
-
Registration (
POST /api/auth/register):- Accept user details such as username, email, and password.
- Validate that the email is unique.
- Encrypt the password (e.g., using BCrypt) before storing it in the database.
- Set new users to
unemployedby default. - Return a success message once the user is registered.
-
Login (
POST /api/auth/login):- Accept user credentials (username/email and password).
- Validate the credentials against the stored data.
- If authentication is successful, generate a JWT that includes at least the user ID as the subject.
- The JWT should have an expiration time defined in the application properties.
- Return the JWT in the response. For example:
{ "success": true, "token": "<JWT_TOKEN>" }.
-
Session check (
GET /api/user/me):- Returns the user id, username and email.
- Used to verify that the tokens are working correctly.
{ "id": 1, "username": "nuwe", "email": "[email protected]" } -
Security Considerations:
- Ensure that the JWT is used to protect all endpoints that require authentication.
- Implement a JWT filter that validates incoming tokens and loads user details accordingly.
Objective:
Implement a web scraping endpoint that extracts property details from Idealista based on a provided URL.
Detailed Requirements:
-
Endpoint (
POST /api/scrape):- Accept a JSON payload with:
"url": The URL of the property on Idealista."store": A Boolean flag indicating whether the scraped property should be saved in the database.
- Accept a JSON payload with:
-
Data Extraction:
- Use a library like Jsoup to connect to the provided URL.
- Extract the following details:
- Property Type: Extracted from the title as the first word (e.g., "Chalet", "Apartamento"). Stored as "name" in the database.
- Full Title: The complete title of the property as it appears on the page.
- Location: The propertyβs location (could be a street name, neighborhood, or city).
- Price: The price, ensuring it is converted to a standard numerical format(Decimal).
- Size: The area of the property (in mΒ²).
- Rooms: The number of rooms, extracted from text like "4 hab.".
- If the data for any of these fields is not available, handle it gracefully (e.g., set to an empty string or a default value).
-
Storage Behavior:
- If
"store"istrue, create a new property record in the database with the extracted data. - Set the propertyβs
availabilityfield to"Available"by default.
- If
-
Response:
- Return a JSON object that includes the extracted data and a flag indicating whether the property was saved.
Objective:
Implement a mortgage application system that evaluates if an authenticated user qualifies for a mortgage based on their financial information.
Detailed Requirements:
-
Employment Data:
- Create an endpoint (
POST /api/employment) where authenticated users can submit or update their employment data, including:- Contract Type: Values like "indefinite", "temporary", or
NULL(default at registration). - Salary: The gross annual salary.
- Contract Type: Values like "indefinite", "temporary", or
- On submission, automatically calculate the net monthly salary using Spanish tax brackets:
- 0 β 12,450β¬: 19% retention
- 12,450 β 20,199β¬: 24% retention
- 20,200 β 35,199β¬: 30% retention
- 35,200 β 59,999β¬: 37% retention
- 60,000 β 299,999β¬: 45% retention
- Above 299,999β¬: 50% retention (new bracket as per requirement)
- Based on the presence of employment data, update the userβs employment status (e.g., from "unemployed" to "employed").
- Create an endpoint (
-
Mortgage Request:
- Implement an endpoint (
POST /api/mortgage) that allows an authenticated user to request a mortgage. - The system should:
- Validate whether the user has a job. If not, returns an error.
- Retrieve the selected propertyβs price from the database.
- Calculate the total property cost as the property price plus 15% extras (to cover VAT and additional fees).
- Use the userβs net monthly salary (calculated previously) and their contract type to set an allowed payment threshold (30% for indefinite contracts; 15% for temporary contracts).
- Calculate the monthly payment using a fixed annual interest rate of 2% and the provided term (minimum 15 years). Use the standard amortization formula:
- Payment = P * r / (1 - (1 + r)^(-n))
- Where P = total cost, r = monthly interest rate, n = total number of months.
- Determine whether the calculated monthly payment is within the allowed percentage of the userβs net monthly salary.
- If the monthly payment exceeds the threshold, reject the application with an appropriate error message; otherwise, approve it.
- If approved, save the mortgage details in the database and associate the mortgage with the user and the property.
- Implement an endpoint (
-
User dashboard:
- Implement an endpoint (
GET /api/user/dashboard) that allows an authenticated user to request personal data. - It must show employment data and mortgages.
- Implement an endpoint (
-
Response:
- Return a JSON response indicating whether the mortgage was approved, the calculated monthly payment, and (if approved) the mortgage ID.
Objective:
Implement an auction system where properties can be bid upon using RabbitMQ to manage concurrent bids.
Detailed Requirements:
-
Auction Creation:
- Implement an endpoint (
POST /api/auction/create) that creates a new auction for a property. - The auction must include:
- Property reference (must exist in the database).
- Start and end times (ISO 8601 formatted).
- Starting price.
- Minimum bid increment.
- Initial current highest bid set to the starting price.
- Implement an endpoint (
-
Placing Bids:
- Implement an endpoint (
POST /api/auction/{auctionId}/bid) where authenticated users can place bids. - When a bid is placed:
- Verify that the auction exists and is still open.
- Create a bid message with the auction ID, user ID, bid amount, and timestamp.
- Publish the bid message to a RabbitMQ queue.
- The bid message is then asynchronously processed by a consumer that updates the auction's current highest bid if applicable.
- Implement an endpoint (
-
Auction Details:
- Implement an endpoint (
GET /api/auction/{auctionId}) to retrieve auction details, including all submitted bids.
- Implement an endpoint (
-
Closing Auctions:
- Implement an endpoint (
PATCH /api/auction/{auctionId}/close) that:- Closes the auction by setting its status to "closed".
- Processes all bids to determine the winning bid (the highest bid).
- Updates the propertyβs availability to "Unavailable".
- Returns a JSON response containing the winning bid amount and the winning user's ID.
- Implement an endpoint (
Concurrency & RabbitMQ:
-
The bidding system uses RabbitMQ to handle high volumes of bids concurrently.
-
Bids are enqueued and then processed asynchronously by a message listener, ensuring that race conditions are minimized.
-
You may implement a delay in the consumer (for testing purposes) to observe how messages accumulate in the queue.
-
Names for RabbitMQ configuration:
bid.queuebid.exchangebid.routingkey
| Endpoint | Method | Auth Required | Possible Status Codes | Response Example |
|---|---|---|---|---|
/api/health |
GET | No | 200 OK | "API is working" |
/api/auth/register |
POST | No | 200 OK, 400 Bad Request | { "success": true, "message": "User successfully registered" } |
/api/auth/login |
POST | No | 200 OK, 401 Unauthorized | { "accessToken": "eyJhbGciOiJIUzUxMiJ9...", "tokenType": "Bearer" } |
/api/user/me |
GET | Yes | 200 OK, 401 Unauthorized | { "id": 1, "username": "nuwe", "email": "[email protected]" } |
/api/user/dashboard |
GET | Yes | 200 OK, 401 Unauthorized | { "employmentData": { ... }, "mortgages": [ ... ] } |
/api/scrape |
POST | No | 200 OK, 500 Internal Server Error | { "data": { "type": "Casa", "fullTitle": "Casa o chalet independiente en venta en Las Rozas", "location": "Campoo-Los Valles, Cantabria", "price": "680.000", "size": "361 mΒ²", "rooms": "9 hab.", "url": "https://www.idealista.com/inmueble/104498284/" }, "saved": true } (or "saved": false if not stored) |
/api/employment |
POST | Yes | 200 OK, 400 Bad Request | { "employmentData": { "id": 1, "contract": "indefinite", "salary": 1000000, "netMonthly": 43674.7, "employmentStatus": "employed" }, "message": "Employment data updated successfully" } |
/api/employment |
PATCH | Yes | 200 OK, 400 Bad Request | { "employmentData": { "id": 2, "contract": "temporary", "salary": 50000, "netMonthly": 2983.09, "employmentStatus": "employed" }, "message": "Employment data updated successfully.", "success": true } |
/api/mortgage |
POST | Yes | 200 OK, 400 Bad Request | { "approved": true, "mortgageId": 1, "netMonthly": 43674.7, "monthlyPayment": 3956.01, "allowedPercentage": "30.0%", "message": "Mortgage approved.", "numberOfMonths": 240 } (Alternatively, if rejected, return a JSON with "approved": false and an appropriate error message.) |
/api/auction/create |
POST | No | 200 OK, 400 Bad Request | { "success": true, "auctionId": 1, "message": "Auction created successfully." } |
/api/auction/{auctionId} |
GET | No | 200 OK, 400 Bad Request | { "auction": { "id": 1, "property": { "id": 1, "name": "Casa", "location": "Campoo-Los Valles, Cantabria", "price": 680000, "size": "361 mΒ²", "rooms": "9 hab.", "availability": "Unavailable" }, "startTime": "2025-03-07T10:00:00", "endTime": "2025-03-07T11:00:00", "status": "open", "minIncrement": 1000, "startingPrice": 680000, "currentHighestBid": 680000, "bids": [ ... ] } } |
/api/auction/{auctionId}/bid |
POST | Yes | 200 OK, 400 Bad Request, 401 Unauthorized | { "success": true, "message": "Bid submitted successfully." } (Errors: e.g., bid too low, auction closed, or authentication errors.) |
/api/auction/{auctionId}/close |
PATCH | No | 200 OK, 400 Bad Request | { "success": true, "message": "Auction closed successfully.", "winningBid": 510000, "winningUserId": 1 } |
The application.properties file contains the configuration necessary for the correct functioning of the application.
The backend tests will simulate the interaction of a user directly with the API running in a container and exposed on port 3000
- Solve the proposed tasks.
- Continuously push the changes you have made.
- Wait for the results.
- Click submit challenge when you have reached your maximum score.
The final score will be given according to whether or not the objectives have been met.
In this case, the challenge will be evaluated on 1450 (1050 for backend tasks and 400 for code quality) points which are distributed as follows:
- Task 1: 50 points
- Task 2: 200 points
- Task 3: 250 points
- Task 4: 250 points
- Task 5: 300 points
- Code quality: 400 points
Q1: Can I change anything in the app?
A1: Yes, as it is a hackathon and the application is dockerised, you are free to modify anything within the project, except for the functions that are already predefined and files like: docker-compose.yml and application.properties.
If you modify application.properties, be sure to return it to its original state before pushing the project.
The Dockerfile is given configured to be fully functional.
Q2: Can I add resources that are not in pom.xml?
A2: Yes, you can add new resources if necessary, but keep in mind that everything needed to develop it has already been added.
Q3: Is it completely necessary to do the Dockerfile configuration first?
A3: Yes. To ensure the integrity of the correction, a Dockerised environment is the safest way to go. The Dockerfile is given configured to be fully functional so it is only necessary to push the code and check that the score appears in the first task.
Q4: I scored 0 for task 1, what's wrong?
A4: There is probably a misconfiguration in one of the following files:
Dockerfileapplication.propertiesIt is also possible that your code does not compile and the app container does not deploy and the API connection fails.
Q5: What is the latest version of README?
A5: The most recent version will always be the one that appears on the platform. In case there is something to correct in the readme, you can see the updated version on the NUWE website.