A Spring Boot service that stores Locations and Transportations and provides REST APIs to:
- Manage Locations (create / update / delete / list)
- Manage Transportations (create / update / delete / list)
- Search valid Routes between an origin and destination with the rule set:
- max 3 legs
- exactly 1 flight
- max 1 pre-flight transfer (non-flight before the flight)
- max 1 post-flight transfer (non-flight after the flight)
- Filter routes by date using
Operating Days(bonus complexity)
All responses are wrapped in a consistent CustomResponse<T> envelope, and paging is returned as CustomPage<T> (or CustomPagingResponse<T> depending on your naming).
A Location represents a stop such as an airport or a point in a city.
namecountrycitycode(unique, used for search; e.g.,IST,LHR,WEMBLEY)
A Transportation represents a directed connection:
origin(Location)destination(Location)type(e.g.,BUS,SUBWAY,UBER,FLIGHT)operatingDays(optional bonus):[1..7]means Monday..Sunday- empty / null ⇒ operates every day
A Route is a sequence of 1..3 legs that follows the validity rules and date constraints.
Each leg contains:
transportationIdtypeoriginCodedestinationCode
A sequence of connected transportations is a valid route if:
- ✅ It contains at most 3 legs
- ✅ It contains exactly 1 flight
- ✅ It contains at most 1 pre-flight transfer (non-flight before the flight)
- ✅ It contains at most 1 post-flight transfer (non-flight after the flight)
Valid examples:
- ✅
UBER → FLIGHT → BUS - ✅
FLIGHT → BUS - ✅
UBER → FLIGHT
Invalid examples:
- ❌
UBER → BUS → FLIGHT(multiple pre-flight transfers) - ❌
UBER → BUS(no flight) - ❌
UBER → FLIGHT → FLIGHT(multiple flights) - ❌
FLIGHT → SUBWAY → UBER(multiple post-flight transfers)
Each transportation can define operatingDays:
[1, 3, 5, 6]⇒ Monday, Wednesday, Friday, Saturday[]ornull⇒ every day
When the route search endpoint receives a date, the service:
- converts date to day-of-week (1..7)
- excludes any route that contains a leg not active on that day
Example:
Route:
-
BUS (TAKSIM → IST)days[2, 3, 5] -
FLIGHT (IST → LHR)days[2, 3, 7] -
UBER (LHR → WEMBLEY)days[3, 5] -
2025-03-12(Wednesday = 3) ⇒ ✅ route returned -
2025-03-11(Tuesday = 2) ⇒ ❌ route excluded (post-transfer not active)
- Client sends
CreateLocationRequestcontaining:name,country,city,code
- Service validates request and persists to DB.
- Returns:
200 OKwithCustomResponse<LocationResponse>
- Client sends paging request (
CustomPagingRequest) - Returns:
200 OKwithCustomResponse<CustomPage<LocationResponse>>
- Client sends
CreateTransportationRequestcontaining:originCode,destinationCode,type- optional
operatingDays
- Service resolves origin/destination by code and persists to DB.
- Returns:
200 OKwithCustomResponse<TransportationResponse>
- Client sends paging request (
CustomPagingRequest) - Returns:
200 OKwithCustomResponse<CustomPage<TransportationResponse>>
- Client sends
FindRoutesPagedRequest:filter.origin(code)filter.destination(code)filter.date(optional, ISOYYYY-MM-DD)- plus paging: page number, page size
- Service:
- resolves origin/destination locations
- enumerates valid route patterns:
FLIGHTPRE + FLIGHTFLIGHT + POSTPRE + FLIGHT + POST
- enforces date filter via
operatingDayson each leg
- Returns:
200 OKwithCustomResponse<CustomPage<RouteResponse>>
400 Bad Request— Validation errors (@Valid) such as invalid codes, invalid date format, etc.404 Not Found— Location not found by code, transportation not found, etc.409 Conflict— Unique constraint violations (e.g., duplicate location code)500 Internal Server Error— DB access failures or unexpected errors
Custom Exceptions (examples)
NotFoundException→404 NOT_FOUNDAlreadyExistsException(orConflictException) →409 CONFLICTValidationException→400 BAD_REQUEST
| Method | URL | Description | Request Body | Headers | Response | Status Codes |
|---|---|---|---|---|---|---|
| POST | /api/locations |
Create a location. | CreateLocationRequest |
Content-Type: application/json |
CustomResponse<LocationResponse> |
200, 400, 409, 500 |
| GET | /api/locations/{id} |
Get location by id. | - | - | CustomResponse<LocationResponse> |
200, 404, 500 |
| PUT | /api/locations/{id} |
Update location by id. | UpdateLocationRequest |
Content-Type: application/json |
CustomResponse<LocationResponse> |
200, 400, 404, 409, 500 |
| DELETE | /api/locations/{id} |
Delete location by id. | - | - | CustomResponse<Void> |
200, 404, 500 |
| POST | /api/locations/list |
List locations (paged). | CustomPagingRequest |
Content-Type: application/json |
CustomResponse<CustomPage<LocationResponse>> |
200, 400, 500 |
| POST | /api/transportations |
Create a transportation. | CreateTransportationRequest |
Content-Type: application/json |
CustomResponse<TransportationResponse> |
200, 400, 404, 500 |
| GET | /api/transportations/{id} |
Get transportation by id. | - | - | CustomResponse<TransportationResponse> |
200, 404, 500 |
| PUT | /api/transportations/{id} |
Update transportation by id. | UpdateTransportationRequest |
Content-Type: application/json |
CustomResponse<TransportationResponse> |
200, 400, 404, 500 |
| DELETE | /api/transportations/{id} |
Delete transportation by id. | - | - | CustomResponse<Void> |
200, 404, 500 |
| POST | /api/transportations/list |
List transportations (paged). | CustomPagingRequest |
Content-Type: application/json |
CustomResponse<CustomPage<TransportationResponse>> |
200, 400, 500 |
| POST | /api/routes/search |
Search valid routes (paged) with optional date filtering. | FindRoutesPagedRequest |
Content-Type: application/json |
CustomResponse<CustomPage<RouteResponse>> |
200, 400, 404, 500 |
- Java 25
- Spring Boot 3.0
- Restful API
- Open Api (Swagger)
- Maven
- Junit5
- Mockito
- Integration Tests
- TestContainer
- Mapstruct
- Docker
- Docker Compose
- CI/CD (Github Actions)
- Postman
- Postgres
- Kubernetes
- JaCoCo (Test Report)
- AOP
- Prometheus
- Grafana
- SonarQube
- React
- Material UI
- Nginx
Import postman collection under postman_collection folder
$ cd aviation-routes-backend
AVIATION_ROUTES_DB_IP=localhost
AVIATION_ROUTES_DB_PORT=5432
POSTGRES_USER={your-postgres-user}
POSTGRES_PASSWORD={your-postgres-password}
$ cd aviation-ui
VITE_API_BASE_URL=http://localhost:3141
http://localhost:3141/swagger-ui/index.html
After the command named mvn clean install completes, the JaCoCo report will be available at:
target/site/jacoco/index.html
Navigate to the target/site/jacoco/ directory.
Open the index.html file in your browser to view the detailed coverage report.
To build and run the application with Maven, please follow the directions shown below;
$ git clone https://github.com/Rapter1990/aviation-routes.git
$ cd aviation-routes-backend
$ mvn clean install
$ mvn spring-boot:run
$ cd aviation-ui
$ npm install --force
$ npm run dev The application can be built and run by the Docker engine. The Dockerfile has multistage build, so you do not need to build and run separately.
Please follow directions shown below in order to build and run the application with Docker Compose file;
$ cd aviation-routes
$ docker-compose up -d If you change anything in the project and run it on Docker, you can also use this command shown below
$ cd aviation-routes
$ docker-compose up -d --buildTo monitor the application, you can use the following tools:
-
Prometheus:
Open in your browser at http://localhost:9090
Prometheus collects and stores application metrics. -
Grafana:
Open in your browser at http://localhost:3000
Grafana provides a dashboard for visualizing the metrics.
Default credentials:- Username:
admin - Password:
admin
- Username:
Define prometheus data source url, use this link shown below
http://prometheus:9090
To run the application, please follow the directions shown below;
- Start Minikube
$ minikube start- Open Minikube Dashboard
$ minikube dashboard- To deploy the application on Kubernetes, apply the Kubernetes configuration file underneath k8s folder
$ kubectl apply -f k8s- To learn url to be used for App through Minikube
minikube service aviation-routes-backend --urlIt will print something like:
http://127.0.0.1:xxxxxThen call:
http://127.0.0.1:xxxxx/api/routes/searchhttps://hub.docker.com/repository/docker/noyandocker/aviation-routes-backend/general
- Go to
localhost:9000for Docker and Go there throughminikube service sonarqubefor Kubernetes - Enter username and password as
admin - Change password
- Click
Create Local Project - Choose the baseline for this code for the project as
Use the global setting - Click
Locallyin Analyze Method - Define Token
- Click
Continue - Copy
sonar.host.urlandsonar.token(sonar.login) in thepropertiespart inpom.xml - Run
mvn sonar:sonarto show code analysis
Click here to show the screenshots of project
Figure 1
Figure 2
Figure 3
Figure 4
Figure 5
Figure 6
Figure 7
Figure 8
Figure 9
Figure 10
Figure 11
Figure 12
Figure 13
Figure 14
Figure 15
Figure 16
Figure 17
Figure 18
Figure 19
Figure 20
Figure 21
Figure 22
Figure 23
Figure 24
Figure 25
Figure 26
Figure 27
Figure 28
Figure 29
Figure 30
Figure 31
Figure 32
Figure 33
Figure 34
