Here lays a Kotlin web project with Ktor framework, Postgres database, and JWT Authentication.
The project comprises the following ingredients:
- Ktor server includes JSON serializers, Authentication, and Testing
- Netty web server
- Postgres as database
- Exposed as ORM
- Hikari Connection Pool
- Logback for logging purposes
- JBCrypt for hashing passwords (No salting yet)
There is a simple implementation of DFS algorithm, but if you aim for a solid solution, better go for GUAVA Graph from Google.
Project is SQL DB agnostic. You are able to dynamically change Postgres to any other databases that Exposed JDBC supports by changing a couple of variables:
- the database driver version in
gradle.properties
- the database driver dependency in
build.gradle.kts
- the WEB_DB_URL variable in
.env
file
- deploy the docker compose with
docker compose up -d
command - sign up to
/signup
route providing a username and password(not hardened enough) - log in to with your username and password to get access token
/login
- send
POST
request with payload and token to/hirearchy
to create the hierarchy of the organization - send get request with token to
/hierarchy/{name}/supervisors
to fetch the supervisors of the current user
You need root access for docker
Go to the root directory of the project where docker-compose.yml
is and change the environment variables in
.env-example
with yours and rename the file to .env
then deploy the application with the following command:
docker-compose up -d
for shutting down the deployment run following command where the docker-compose.yml
file resides:
docker-compose down -v
We have REST API to post the JSON below. This JSON represents a Person -> Person relationship that looks like this:
{
"Pete": "Nick",
"Barbara": "Nick",
"Nick": "Sophie",
"Sophie": "Jonas"
}
In this case, Nick is a supervisor of Pete and Barbara, Sophie supervises Nick. The supervisor list is not always in order.
curl --request POST -sLv \
--url 'http://localhost:8080/hierarchy' \
--header "Content-Type: application/json" \
--header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwOi8vMC4wLjAuMDo4MDgwL2hpZXJhcmNoeSIsImlzcyI6Imh0dHA6Ly8wLjAuMC4wOjgwODAvIiwiZXhwIjoxNjUwNDkwNzUwLCJ1c2VybmFtZSI6ImphbmUifQ.Xfn4JEOHo-Px7vy0TVyo3malCFlj3eFvzAJejqlefPM" \
--data '{"Nick":"Barbara","Barbara":"Nick","Elias":"Levi"}'
The response to querying the endpoint where the root is at the top of the JSON nested dictionary. For instance, previous input would result in:
curl --request GET -sLv \
--url 'http://localhost:8080/hierarchy' \
--header "Content-Type: application/json" \
--header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwOi8vMC4wLjAuMDo4MDgwL2hpZXJhcmNoeSIsImlzcyI6Imh0dHA6Ly8wLjAuMC4wOjgwODAvIiwiZXhwIjoxNjUwNDkwNzUwLCJ1c2VybmFtZSI6ImphbmUifQ.Xfn4JEOHo-Px7vy0TVyo3malCFlj3eFvzAJejqlefPM"
the response will be:
{
"Jonas": {
"Sophie": {
"Nick": {
"Pete": {},
"Barbara": {}
}
}
}
}
Query for a specific Person it's the hierarchy:
curl --request GET -sLv \
--url 'http://localhost:8080/hierarchy/Nick/supervisors'\
--header "Content-Type: application/json" \
--header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwOi8vMC4wLjAuMDo4MDgwL2hpZXJhcmNoeSIsImlzcyI6Imh0dHA6Ly8wLjAuMC4wOjgwODAvIiwiZXhwIjoxNjUwNDkwNzUwLCJ1c2VybmFtZSI6ImphbmUifQ.Xfn4JEOHo-Px7vy0TVyo3malCFlj3eFvzAJejqlefPM"
the response of the query will be:
{
"Nick": {
"Sophie": {
"Jonas": {}
}
}
}
Sophie is the supervisor of the Nick, and Jonas is a supervisor of the supervisor of the Nick
Sign up to the system
curl --request POST -sL \
--url 'http://localhost:8080/signup'\
--header "Content-Type: application/json" \
--data '{"username":"jane","password":"doe"}'
login to the system
curl --request POST -sL \
--url 'http://localhost:8080/login'\
--header "Content-Type: application/json" \
--data '{"username":"jane","password":"doe"}'
The response will be the access token
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwOi8vMC4wLjAuMDo4MDgwL2hpZXJhcmNoeSIsImlzcyI6Imh0dHA6Ly8wLjAuMC4wOjgwODAvIiwiZXhwIjoxNjUwMTU3NjIxLCJ1c2VybmFtZSI6ImpvaG4ifQ.LSJUte7oy9Kv7qkozI3APBzPxHVZ56GID-n0lRIKvdY"
}
To run the tests locally, you should run docker compose with docker-compose-test.yml
file.
It will run the tests against the test database.
docker-compose --file docker-compose-test.yml up
After finishing the tests, you can clean test data nd shut the containers down with the following command:
docker-compose --file docker-compose-test.yml down -v
CI workflow prepares the database, runs the Gradle build with tests, and generates a good quality report to Codacy.
- Increase test coverage
- Add caching
- E2E testing
- OpenAPI specifications
- Change to GUAVA