This repository provides a news website server written in Haskell.
- Most functional requirements are described here.
We provide user authorization via JWT.
In a database, we store user passwords encrypted via password.
- PostgreSQL.
- esqueleto.
esqueleto
prevents SQL injections.
Haskell modules from back/src are logically grouped into the following units.
Persist
- provides operations with a database.Service
- provides services on top ofPersist
.API
- describes API.Controller
- provides operations forAPI
endpoints on top ofService
.Server
- bindsAPI
endpoints andController
operations to produce a server.
-
Nix prerequisites
See these for additional info:
- codium-generic - info just about
VSCodium
and extensions. - codium-haskell - an advanced version of this flake.
- flake.nix - extensively commented code.
- Haskell
- Troubleshooting
- Prerequisites
- codium-generic - info just about
-
Install Nix - see how.
-
Install the following programs.
pulumi
microk8s
-
Start a
devShell
.nix develop
-
(Optionally) Write
settings.json
and startVSCodium
.nix run .#writeSettings nix run .#codium .
-
(Optionally) Open a
.hs
file and hover over a function. Shortly, Haskell Language Server should start giving the type info. -
See the following sections. They assume
dev
environment.- There are configurations for the
prod
environment.
- There are configurations for the
Run a database, a server, and tests in a Kubernetes cluster.
-
Create a cluster.
# prepare the environment source microk8s.sh # start a microk8s vm microk8s start
-
The pods configuration is in Pulumi.dev.yaml.
-
Prepare
pulumi
.# go to pulumi directory cd pulumi # install node packages npm i # select dev stack pulumi stack select dev
-
Create pods in the cluster.
# create all resources pulumi up # select `yes` # check that all pods are ready kubectl get po -n breaking-news-dev
-
Follow the steps from Cluster until pod creation.
-
Run a database in the cluster.
# create postgres-related resources pulumi up -t **postgres* # select `yes` kubectl get po -n breaking-news-dev
-
The app configuration is in the local directory. See back.dev.yaml.
-
Run the server using
cabal-install
. The server will connect to the database in the cluster.cabal run back:exe:back
Build Docker images, push to Docker Hub and pin image digests for app and test.
-
Create
secrets.env
file fromsecrets.example.env
. -
source
environment files.-
Manually
source secrets.env source .env
-
Via
direnv
direnv allow
-
-
Push images to Docker Hub.
nix run .#backPushToDockerHub nix run .#testPushToDockerHub
-
Write image digests.
nix run .#writeDigests
-
The server IP address (let's call it
<IP>
) islocalhost
. -
Get the port (let's call it
<port>
) of the server.- For the server in the cluster, see
dev:back.service.nodePort
in Pulumi.dev.yaml. - For the server run via
cabal-install
, seeweb.port
in back.dev.yaml.
- For the server in the cluster, see
-
Expose the server to the world via
localtunnel
.lt -l <IP> -p <port> -s <subdomain> # or, in case of a cluster lt -l localhost -p 30003 -s breaking-news-fun
-
Copy the address (let's call it
<address>
) obtained fromlocaltunnel
.
View the API, make requests to it.
- Access the Swagger UI at
<address>/api1/index.html
.
View the API, make requests to it.
-
Import the API into
Postman
as aPostman Collection
. -
In that collection, set the variable
baseUrl
to<address>
.- Click on the collection ->
Variables
->Current Value
. - Save.
- Click on the collection ->
-
Send a request to
/api1/user/register
.- Set the values in the request
Body
. - You'll get a refresh token (
refreshToken
) and an access token (accessToken
).
- Set the values in the request
-
Send a request to
/api1/user/rotate-refresh-token
.- Copy
refreshToken
toAuthorization
->BearerToken
->Token
. - You'll get a renewed pair of a refresh token and an access token.
- Copy
-
Send the request again with the old
refreshToken
.- You'll get a message that the session has a newer token.
- This is due to refresh token reuse detection (see Authorization).
-
Send a request to
/api1/user/login
.- Copy the values from the request to
/api1/user/register
intoBody
. - You'll get a renewed pair of a refresh token and an access token.
- Copy the values from the request to
-
Send a request to
api1/news/create
.- Copy the last
accessToken
to your Collection ->Variables
->{{bearerToken}}
. - Edit the value in the request
Body
. - You'll get nothing in the response body.
- Copy the last
-
Send a request to
api1/news/get
.- Remove all query parameters.
- You'll get a list of news in the response body.
-
Check the database.
# connect to the database psql postgresql://admin:admin_password@localhost:30002/postgresdb # check records select * from users;
-
The cluster provides pod monitoring via loki-stack.
-
Monitoring is configured in Pulumi.dev.yaml at
dev:monitoring
. -
Grafana
should be available athttp://localhost:30005
.- Username:
admin
- Password:
admin_password
- Username:
-
In
Explore
->Log browser
, type{app="back-deployment"}
Manage resources in a running cluster via pulumi
(see Cluster).
-
Go to
pulumi
.cd pulumi
-
Get urns.
pulumi stack --show-urns
-
Try deleting a resource.
pulumi state delete <urn>
-
If you delete a resource via
kubectl
, refreshpulumi
. Usually,pulumi
will notice the changes.# delete some pod kubectl delete po ... # after deleting resources, run pulumi refresh # update resources pulumi up
-
Destroy all resources.
pulumi destroy
- package.yaml - used by
hpack
to generate a.cabal
- .markdownlint.jsonc - for
markdownlint
from the extensiondavidanson.vscode-markdownlint
- .ghcid - for ghcid
- .envrc - for direnv
- fourmolu.yaml - for fourmolu
- ci.yaml - a generated
GitHub Actions
workflow. See workflows. Generate a workflow vianix run .#writeWorkflows
. hie.yaml
- not present, but can be generated via implicit-hie (available on devshell) to verify theHaskell Language Server
setup.
- charts/loki-stack
- It's possible to overwrite chart values
- Find
grafana
in requirements.yaml - Find its
grafana
chart values - Edit
loki-stack
values forgrafana
- Find
- It's possible to overwrite chart values
- Grafana, Loki, Promtail - YT
DeriveAnyClass
sometimes leads to infinite loops - see ToHttpApiData- Running server in a
VM
- kustomization.yaml
- configmap
- web app
- Understanding Kubernetes Objects
- Debugging Templates (helm)
helm lint --strict
- toYaml correct indentation
- Persistent Volumes
- PVC is bound to a PV at runtime automatically
- There's no connection between PVCs and PVs in a manifest
- advanced helm templating
- Using production data in staging
- API versioning
- via url part (easy to version the whole API)
- PostgreSQL Initialization scripts