- Nighty night
Project is not ready. Work in progress!
It is an application for recording and monitoring a newborn's sleeping and eating patterns.
Users can register in the application and add entries as they see fit.
It is a backend written in Rust with the help of the Axum framework. It has a session service provided by Redis. The main database is in PostgreSQL.
Set up a redis and postgreSQL servers.
If installed locally:
sudo service redis-server start
sudo service postgresql start
Or run both commands at same time with:
sudo service redis-server start && sudo service postgresql start
Build an .env file, with name local.env
inside ./key
folder with these environments variables:
local.env
BRANCH=local
POSTGRES_PASSWORD=password
POSTGRES_USER=username
POSTGRES_DB=nighty_night_db
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
LOGGER_LEVEL=debug
ADDRESS=127.0.0.1
PORT=3000
REDIS_ADDRESS=127.0.0.1
REDIS_PORT=6379
SESSION_DURATION=600
Install libpq
sudo apt-get update
sudo apt-get install libpq-dev
Install diesel cli:
cargo install diesel_cli --no-default-features --features postgres
run migrations:
diesel migration run
Launch application
cargo build --release
exec ./target/release/nighty_night
Test Endpoint => http://127.0.0.0:3000/api/auth
Compiling rust in docker is a really slow process. There is no way around it. Although, final image only weights around 110 Mb, reaching that point the first time can take up to 1000s.
With cargo chef
speed is a lot better for incremental builds. As long as there are no changes in Cargo.toml
, dependencies are cached, thus speeding whole process.
docker build -t nighty_night -f ./docker/Dockerfile .
Optional, run container:
docker run --env-file .env -d -p 3000:3000 --name rs nighty_night
Create network with app and databases; initialize containers:
docker compose --env-file ./key/docker.env -f ./docker/compose.yaml up -d
Stop containers:
docker compose -f ./docker/compose.yaml stop
Delete containers:
docker compose -f ./docker/compose.yaml down
Delete dangling images and volumes:
docker image prune && docker volume prune
To remove all images which are not used by existing containers, use the -a
flag:
docker image prune -a
BRANCH=branch_name
POSTGRES_PASSWORD=password
POSTGRES_USER=user
POSTGRES_DB=app_db
POSTGRES_HOST=host.docker.internal
POSTGRES_PORT=8080
LOGGER_LEVEL=debug
<!-- Leave ADDRESS to 0.0.0.0 in docker -->
ADDRESS=0.0.0.0
PORT=3000
REDIS_ADDRESS=host.docker.internal
REDIS_PORT=8081
SESSION_DURATION=600
Modify ports accordingly. This is an example with default ports. Docker compose file runs on ports 8080 for postgreSQL and 8081 for redis.
-e = environment variable
-d = container runs as a background application
-p = maps container ports to host ports
--rm = will delete container after stopping the app
--name = image name
--env-file = path to .env file
Create secrets and config maps files. Examples below.
If running in local:
minikube start
Tell docker where to build the image:
eval $(minikube docker-env)
Create nighty night image inside kubernetes:
docker build -t nighty_night:latest -f docker/Dockerfile .
Load secrets and config map in kubernetes' cluster:
kubectl create -f kubernetes/local/secrets.yaml
kubectl apply -f kubernetes/local/configmap.yaml
Apply all deployments and services:
kubectl apply -f kubernetes/00-postgreSQL-configmap.yaml
kubectl apply -f kubernetes/01-postgreSQL-service.yaml
kubectl apply -f kubernetes/02-postgreSQL-pvc.yaml
kubectl apply -f kubernetes/03-postgreSQL-deployment.yaml
kubectl apply -f kubernetes/04-redis-deployment.yaml
kubectl apply -f kubernetes/05-redis-service.yaml
kubectl apply -f kubernetes/06-nighty-night-deployment.yaml
kubectl apply -f kubernetes/07-nighty-night-service.yaml
Port forward deployment pod to desired port:
kubectl port-forward nighty-night-deployment from:to
apiVersion: v1
kind: ConfigMap
metadata:
name: nighty-night-config-map
labels:
group: nighty.night.app
app: nighty-night
branch: minikube
data:
# APP
BRANCH: K8s
POSTGRES_DB: nighty_night_db
POSTGRES_HOST: psql-service.default.svc.cluster.local
POSTGRES_PORT: "5432"
LOGGER_LEVEL: debug
ADDRESS: 0.0.0.0
PORT: "3000"
REDIS_ADDRESS: redis-service.default.svc.cluster.local
REDIS_PORT: "6379"
SESSION_DURATION: "600"
Get the values for each secret with:
echo -n "value" | base64
apiVersion: v1
kind: Secret
metadata:
name: nighty-night-secrets
labels:
group: nighty.night.app
app: nighty-night-API
branch: minikube
type: Opaque
data:
POSTGRES_USER: ZGJh
POSTGRES_PASSWORD: MTIzNA==
Rol | Username | Password |
---|---|---|
Guest | guest | anonymous |
Admin | admin | admin |
Route | Method | Function | Parameters | Arguments |
---|---|---|---|---|
/register | post |
Create a new user | Body: Json | {username, password, email, name, surname} |
/user | post |
find user by username | Body: Json | {username} |
/session | get |
Get current user in session | ||
/session | post |
login user | Body: Json | {username, password} |
/session | delete |
logout user | ||
/profile | get |
Get user profile | ||
/profile | patch |
Update user profile | Body: Json | {name, surname, email, } |
/profile | delete |
Deactivate user |
Route | Method | Function | Parameters | Arguments |
---|---|---|---|---|
/ | get |
Get all babies for current user | ||
/ | post |
Add new baby | Body: Json | {name, birthdate} |
/:baby_id | patch |
Update baby info by id | Path: Uuid | Body: Json | {name, birthdate} |
/:baby_id | delete |
Delete baby and all records associated to it | Path: Uuid | |
/:baby_id/share?username=username | post |
Associate current baby to another username | Path: Uuid | username: String | |
/:baby_id/transfer | patch |
Transfer current baby to another username | Path: Uuid | Body: Json | {username} |
Route | Method | Function | Parameters | Arguments |
---|---|---|---|---|
/meals?all=true | get |
Get all meals associated to a baby | all: boolean | |
/meals?date=YYYY-mm-dd | get |
Get all meals in a given date | date: String | |
/meals?from=YYYY-mm-dd&to=YYYY-mm-dd | get |
Get all meals in a given range | {from: String | to: String} | |
/meals?last_days=X | get |
Get all meals from last X days, default to 7 | last_days: integer | |
/meals | post |
Add new meals to an associated baby | Body: Json | {date, quantity, elapsed} |
/meals/:record | patch |
Update a meal record with any new values | Path: Integer | Body: Json | {date, quantity, elapsed} |
/meals/:record | delete |
Delete entry X from DB | Path: Integer | |
/meals/:record | get |
Get an individual record | Path: Integer | |
/meals/summary?all=bool | get |
Get all summaries | all: Boolean | |
/meals/summary?date=YYYY-mm-dd | get |
Get a summary from one day's data | date: String | |
/meals/summary?date=today | get |
Get a summary from today's data | ||
/meals/summary?last_days=X | get |
Get a summary from last X days, default to 7 | days: Integer | |
/meals/summary?from=YYYY-mm-dd&to=YYYY-mm-dd | get |
Get a summary from date X up to date Y | {from: String | to: String} |
Route | Method | Function | Parameters | Arguments |
---|---|---|---|---|
/dreams?all=true | get |
Get all dreams associated to a baby | all: boolean | |
/dreams?date=YYYY-mm-dd | get |
Get all dreams in a given date | date: String | |
/dreams?from=YYYY-mm-dd&to=YYYY-mm-dd | get |
Get all dreams in a given range | {from: String | to: String} | |
/dreams?last_days=X | get |
Get all dreams from last X days, default to 7 | last_days: integer | |
/dreams | post |
Add new dreams to an associated baby | Body: Json | {from_date, to_date } |
/dreams/:record | patch |
Update a dream record with any new values | Path: Integer | Body: Json | {from_date, to_date } |
/dreams/:record | delete |
Delete entry X from DB | Path: Integer | |
/dreams/:record | get |
Get an individual record | Path: Integer | |
/dreams/summary?all=bool | get |
Get all summaries | all: Boolean | |
/dreams/summary?date=YYYY-mm-dd | get |
Get a summary from one day's data | date: String | |
/dreams/summary?date=today | get |
Get a summary from today's data | ||
/dreams/summary?days=X | get |
Get a summary from last X days, default to 7 | days: Integer | |
/dreams/summary?from=YYYY-mm-dd&to=YYYY-mm-dd | get |
Get a summary from date X up to date Y | {from: String | to: String} |
Route | Method | Function | Parameters | Arguments |
---|---|---|---|---|
/weights?all=true | get |
Get all weight measures associated to a baby | all: boolean | |
/weights?date=YYYY-mm-dd | get |
Get weight in a given date | date: String | |
/weights?from=YYYY-mm-dd&to=YYYY-mm-dd | get |
Get weights in a given range | {from: String | to: String} | |
/weights?last_days=X | get |
Get weights from last X days, default to 30 | last_days: Integer | |
/weights | post |
Add new weight measure to an associated baby | Body: Json | {date, value } |
/weights/:record | patch |
Update a measure with any new values | Path: Integer | Body: Json | {date, value } |
/weights/:record | delete |
Delete entry X from DB | Path: Integer | |
/weights/:record | get |
Get an individual record | Path: Integer |
Route | Method | Function | Parameters | Arguments |
---|---|---|---|---|
/user | get |
Get all users in db | ||
/user | patch |
Activate user by id | entry: Integer | |
/user | delete |
Delete all inactive users | ||
/user?entry=X | delete |
Delete a user by id | entry: Integer | |
/baby | get |
Get all babies in db | ||
/baby/baby_id?entry=X | get |
Get baby info by id | entry: Integer | |
/stats | get |
Get number of records & statistics | ||
/roles | get |
Get roles and associated statistics | ||
/roles | put |
Add role to user | Body: Json | {username, role} |
/roles | delete |
Delete role from user | Body: Json | {username, role} |
It's implemented on methods that requests several hundred records from database. It needs two (2) parameters, page
and per_page
. page
is page number requested, per_page
are records per page requested. page
is always requested and per_page
could be omitted. By default, page=1&per_page=100
.
Pagination is implemented by default in get
requests for:
/api/admin/user
/api/admin/baby
/api/baby/:baby_id/dreams
/api/baby/:baby_id/dreams/summary
/api/baby/:baby_id/meals
/api/baby/:baby_id/meals/summary
/api/baby/:baby_id/weights
Response is in json format. It always has data
field. It may contain an additional key page_info
when appropriate.
Simple response is like:
{
"message": {
"status": 201,
"title": "Created",
"detail": "New record added."
}
}
Objects contain info separated in two levels. Top level contains id
, attributes
and type
{
"data": {
"attributes": {
"date": "2023-07-04",
"elapsed": "00:00",
"quantity": 145,
"time": "08:25"
},
"id": 169,
"type": "meal"
}
}
Response, with pagination, is like:
{
"data": [
{
"attributes": {
"added_on": "2023-08-28T10:14:45.898688",
"belongs_to": 2,
"name": "BabyOne"
},
"id": 1,
"type": "Baby"
},
{
"attributes": {
"added_on": "2023-08-28T10:32:31.408920",
"belongs_to": 2,
"name": "BabyTwo"
},
"id": 2,
"type": "Baby"
}
],
"page_info": {
"current": 1,
"total_pages": 1
}
}
Error message:
{
"errors": {
"status": 404,
"title": "Not found",
"detail": "No user found."
}
}
Start with http request method
- get
- post
- put
- patch
- delete
If the function comes from the controller, should follow the pattern:
<http-request><controller-function-name><service>
get_user
>> get_user_service
post_new_baby
>> post_new_baby_service
Start with SQL action
- select
- insert
- delete
- update
Proposed layout.
- Implement tracing system.
- Update Cargo.toml and license.
- Authentication and session.
- Migrate database to PostgreSQL.
- Time and dates.
- Implement meals and dreams tables.
- Set up associations.
- User profile.
- Admin panel.
- Logout user.
- Add co-parenting.
- Update fields.
- Delete entries.
- Add entries by batch.
- Elapsed times.
- Recovery system.
- Docker.
- Kubernetes.
This layout is not set in stone. It can, and possibly will, change, neither they're in order.
Copyright 2023 Jaime Alvarez Fernandez