It is built on top of Scala Play framework, however it relies heavily on akka for the actor system.
The whole stack is more or less fully asynchronous and mostly functional. Slick is used as the database layer. Guice is used for dependency injection. This makes it easy to write good tests and mock out specific modules. Akka provides rather good testing tools that is invaluable when testing the actor system.
docker compose up --build then seed the dev user:
./scripts/seed-dev-user.shThis creates an alice/password user with a default list. See below for manual user creation.
docker compose up --build will use Dockerfile.dev for local dev using docker.
sbt testfor running the testssbt runfor running in dev mode (remember to add a user first, see below)sbt runProdfor running the app locally in production mode, however do not forget to export the required ENV variablesDB_PASSWORDandCRYPTO_SECRET.
First, ensure evolutions have been applied by hitting any endpoint (e.g., curl http://localhost:9000/api/login).
Then start the console:
docker compose exec app sbt -Dconfig.file=conf/application.dev.conf consoleEnter :paste mode and load the application:
import play.api._
import play.api.inject.guice._
val app = new GuiceApplicationBuilder()
.in(Mode.Dev)
.build()
Play.start(app)
val injector = app.injectorTo create a user:
import scala.concurrent.Await
import scala.concurrent.duration._
val userService = injector.instanceOf[services.UserService]
val uuid = Await.result(userService.add("alice", "password"), 10.seconds)To create a list for the user:
val listService = injector.instanceOf[services.ItemListService]
val listUuid = Await.result(listService.add("my list", None, uuid), 10.seconds)- Substitute appropriate values into
.env- see sample.env for an example - Run
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
For updating only frontend:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up --no-deps --build -d frontend
docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm -i prod-console console
Then run console.
The backend consists of three routes: /api/login, /api/ws and /api/lists/batch. The /api/login route accepts a POST
with keys username and password. If the correct credentials are given a JSON response with a payload containing
the JWT token is returned.
The route /api/ws accepts WebSocket connections and requires that the client provides a valid JWT token (as required from /api/login). Each WebSocket connection is maintained by an akka actor.
A custom protocol is used. It is based on the idea of the client sending Actions and the server responding with
Responses and relaying the Actions to other clients. More details can be found in
Message.scala.
Endpoint /api/lists/batch is used to batch add items to the user's primary list.
curl -X POST http://localhost:9000/api/lists/batch \
-H "Authorization: Bearer $(uv run generate_jwt.py)" \
-H "content-type: application/json" \
-d '[ "mjölk", "mjöl", "socker"]' -i- use generate_jwt.py to generate a JWT - make sure the
subclaim points to a valid username in the database - curl
POST /api/lists/batchto batch add ingredients like the recipe db does - the listan service will then batch add the ingredients to the first list owned by the user defined in the
subclaim of the JWT