This project includes backend for the Mild Blue's system for vaccination registration support. If you need more information, contact Lukas Forst.
- This system uses programming language Kotlin and Gradle as build system.
- HTTP web server - Ktor.
- Swagger UI - Ktor OpenAPI Generator.
- SQL Framework - Exposed.
- PostgreSQL as our database.
- We run everything in Docker - see Dockerfile.
- In production, we use Fargate and RDS.
- For development, we use Docker-Compose - our compose file .
- For running tests, we have another PostgreSQL instance in docker-compose.test.yml.
- We use Detekt to keep our code consistent.
- Most of the useful commands are in Makefile.
- For testing we use JUnit 5 and some other goodies from Kotlin test.
- We use standard implementation of authorization from Ktor.
- We use JWTs for request authentication, and we sign them with HMAC256.
- We use Role Based Authorization when we need to distinguish between roles - see RoleBasedAuthorization - based on this article with modifications for OpenAPI Generator.
- We use reCaptcha to verify the patients registrations.
- We have simple rate limiting built to the Ktor
- Limiter and the Ktor Feature. Other DDoS protection is implemented in our reverse proxy setup in the production environment.
- We're hashing passwords using Scrypt with the following implementation.
Disclaimer: We use bash friendly operating systems, thus we know the application and all scripts work on Linux and macOS. However, we believe that everything should work on Windows as well.
Note: If you use Windows, all commands that contain ./gradlew ...
must be changed to ./gradlew.bat ...
.
Note: If you are using Compose V2, all commands that contain docker-compose
must be changed to docker compose
.
Especailly in the Makefile.
In order to fully set up the project, one needs to have following software installed:
- JDK - we usually use
8
or11
to run our projects. (11
is recommended) - Docker - in order to run the database
- Docker-Compose - to start the database with correct configuration.
- (Optional) Make - to simply run
make
commands from Makefile. If you don't have this software installed, you can always copy-paste commands from the Makefile. - (Optional) NPM - if you want to run frontend as well. Not necessary if you want to just use backend.
There are basically two options how to run the server, bare-metal and docker. For development, it is usually better to run it on bare-metal. However, for both cases you need running database - we usually use the one in docker-compose.yml. The credentials for inspecting the database are in the .env.
Please note, that when you run the application for the first time it does not contain any data. This means that it does not have any user in the database and for that reason you won't be able to perform most of the actions. The first user must be created manually in the database - this is because we don't want to ship this dummy user to our production environment. There's an SQL insert in the Test data section that you should use to create this first admin user. To interact with the database using psql throught Docker run:
docker container exec -it covid-vaxx-db-1 psql -U mildblue -d covid-vaxx
You need to have database up & running. For that you should use included docker-compose.yml.
You need to be in the directory backend
in order for commands to work.
- To start the database run
make db
- ordocker-compose -f ../docker-compose.yml up -d db
if you don't have Make. - Now when the database is running, execute
./gradlew run
- The application is now up & running on localhost:8080 (open this address in the browser) -
if you installed the frontend as well, you should see the complete app (you should either set the working directory
to
backend
or set env variableFRONTEND_PATH
properly). If not, you can test all backend endpoints using Swagger UI that is running on localhost:8080/swagger-ui. - To stop the running database, run
make stop-db
ordocker-compose -f ../docker-compose.yml stop db
.
Again, the commands must be executed in the backend
directory.
- Run
make docker-run
ordocker-compose -f ../docker-compose.yml up
- To stop the application run
docker-compose -f ../docker-compose.yml stop
- When you edit some files (like code), and you want to run the application again execute
make docker-rerun
or:
docker-compose -f ../docker-compose.yml stop
docker-compose -f ../docker-compose.yml rm -f be
docker-compose -f ../docker-compose.yml up --build be
In order to run all tests, you need to have the test database up & running. To do so, execute make test-db
or
docker-compose -f docker-compose.test.yml up -d db
.
To run all tests execute make check
- this will start Detekt check and then all unit tests. It will also try to start
the test database. To manually run the tests execute ./gradlew check
.
There are multiple base classes for the tests - see TestBase.kt for more information.
- Classic unit testing without database and server - ValidationServiceTest.kt - it even has some nifty features like parameters testing and mocking.
- Simple integration tests that require database and starting the server ServiceRoutesTest.kt.
See EnvVariables where all necessary variables are described. The application uses development settings by default. So if no configuration is set, we assume the server is running in development mode on the developer's machine - see ConfigurationDependencyInjection.kt for default values.
Following data might be useful during testing.
{
"email": "vaxx@mild.blue",
"password": "bluemild",
"role": "ADMIN"
}
use following SQL insert to add the test user:
INSERT INTO users (first_name, last_name, email, password_hash, "role")
VALUES ('Mild', 'Blue', 'vaxx@mild.blue',
'$s0$e0801$asDyD5znh458o/+vCMIaLw==$zydsv6Cw2fKxkIGqFNFMDWQ47pKdHIInLURYOeVlYuA=', 'ADMIN');
INSERT INTO nurses (first_name, last_name, email)
VALUES ('John', 'Doe', 'john.doe@mild.blue'),
('Amanda', 'Smith', 'amanda.smith@mild.blue'),
('Alice', 'B', 'alice.b@mild.blue');
{
"answers": [
{
"questionId": "9a5587a1-dc43-49f3-9847-b736127c9e39",
"value": "true"
},
{
"questionId": "f74ebe1e-ef94-4af0-963d-97ffab086b6b",
"value": "true"
},
{
"questionId": "f68d221d-27a1-4c81-bf45-07b1f0290e15",
"value": "false"
},
{
"questionId": "f9c99047-0f44-4dfe-9964-71274a7af5e9",
"value": "false"
},
{
"questionId": "f5cf0689-a4d7-4c42-8107-6eaedca88a93",
"value": "true"
},
{
"questionId": "7b02b12a-abb4-45d3-8bf4-0b074e445f37",
"value": "false"
},
{
"questionId": "112f5fbd-cde2-4fe9-8cab-f5b4fff57296",
"value": "true"
},
{
"questionId": "f4ca8d25-faaa-4b2f-abc2-3d7a8702d4a3",
"value": "true"
}
],
"confirmation": {
"covid19VaccinationAgreement": true,
"healthStateDisclosureConfirmation": true,
"gdprAgreement": true
},
"district": "Praha 6",
"email": "bob@doe.com",
"firstName": "Bob",
"indication": "Teacher",
"insuranceCompany": "VZP",
"lastName": "Doe",
"personalNumber": "9109146255",
"phoneNumber": {
"countryCode": "CZ",
"number": "604987321"
},
"zipCode": 16001
}
So curl looks like this:
curl -X POST "http://localhost:8080/api/patient?captcha=123456" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"answers\":[{\"questionId\":\"9a5587a1-dc43-49f3-9847-b736127c9e39\",\"value\":\"true\"},{\"questionId\":\"f74ebe1e-ef94-4af0-963d-97ffab086b6b\",\"value\":\"true\"},{\"questionId\":\"f68d221d-27a1-4c81-bf45-07b1f0290e15\",\"value\":\"false\"},{\"questionId\":\"f9c99047-0f44-4dfe-9964-71274a7af5e9\",\"value\":\"false\"},{\"questionId\":\"f5cf0689-a4d7-4c42-8107-6eaedca88a93\",\"value\":\"true\"},{\"questionId\":\"7b02b12a-abb4-45d3-8bf4-0b074e445f37\",\"value\":\"false\"},{\"questionId\":\"112f5fbd-cde2-4fe9-8cab-f5b4fff57296\",\"value\":\"true\"},{\"questionId\":\"f4ca8d25-faaa-4b2f-abc2-3d7a8702d4a3\",\"value\":\"true\"}],\"confirmation\":{\"covid19VaccinationAgreement\":true,\"healthStateDisclosureConfirmation\":true,\"gdprAgreement\":true},\"district\":\"Praha 6\",\"email\":\"bob@doe.com\",\"firstName\":\"Bob\",\"indication\":\"Teacher\",\"insuranceCompany\":\"VZP\",\"lastName\":\"Doe\",\"personalNumber\":\"9109146255\",\"phoneNumber\":{\"countryCode\":\"CZ\",\"number\":\"604987321\"},\"zipCode\":16001}"
{
"address": "Markova 123",
"district": "Praha 7",
"email": "prh7@praha7.cz",
"notes": "Nic moc zvlastniho",
"phoneNumber": {
"countryCode": "CZ",
"number": "777123456"
},
"zipCode": 16001
}