Skip to content

Commit 505ce2c

Browse files
authored
Merge pull request #707 from devsoc-unsw/CHAOS-688-docker-localhosting-setup
Add Docker setup (db, FE and BE) for local development
2 parents fe838cc + 57807e3 commit 505ce2c

File tree

10 files changed

+287
-31
lines changed

10 files changed

+287
-31
lines changed

backend/Dockerfile.dev

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
FROM rust:1.92.0
2+
3+
ENV PATH="/usr/local/cargo/bin:${PATH}"
4+
5+
RUN apt-get update && apt-get install -y \
6+
libssl-dev \
7+
pkg-config \
8+
&& rm -rf /var/lib/apt/lists/*
9+
10+
RUN cargo install cargo-watch
11+
RUN cargo install sqlx-cli --no-default-features --features postgres
12+
13+
WORKDIR /app/backend/server
14+
15+
# Cache Rust dependencies at image build time for faster first run.
16+
# The actual source code is bind-mounted by docker-compose for live reload.
17+
COPY server/Cargo.toml server/Cargo.lock ./
18+
RUN mkdir -p src && \
19+
printf 'fn main() {}\n' > src/main.rs && \
20+
cargo build && \
21+
rm -rf src
22+
23+
EXPOSE 8080
24+

backend/README2.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# CHAOS Backend
2+
3+
CHAOS' backend is implemented in Rust and for data persistence, we use PostgreSQL.
4+
5+
## Table of Contents
6+
7+
- [Dev Setup](#dev-setup)
8+
- [Code Structure](#code-structure)
9+
- [Tech Stack](#tech-stack)
10+
11+
12+
## Dev Setup
13+
14+
### Backend Development
15+
To run the backend in a dev/testing environment:
16+
0. Contact your director before you do the set up.
17+
18+
1. Install `docker-compose` (see [official installation guide](https://docs.docker.com/compose/install/)).
19+
20+
2. Navigate to the directory this file is in (`backend`) in your terminal (not `backend/server`).
21+
22+
3. Possibly terminate any running instances of postgres, as the dockerized postgres we will spawn uses the same default port, so the two might interefere with each other.
23+
24+
4. If you are using WSL/Linux, install the OpenSSL development package with `sudo apt install libssl-dev`.
25+
26+
5. Run `./setup-dev-env.sh` (you might have to make it executable before with `chmod +x setup-dev-env.sh`), which should check if you have the correct docker setup and run the db-instance and do db setup and migrations. Then, it will also start your BE and FE container, so you could work on the project ASAP.
27+
28+
6 (DB Seeding IMPORTANT). To set up your first data for development, please get in `./setup-admin-db-dev.sh` and change GMAIL to your personal gmail. What it does is basically setup your personal email as a SUPERUSER ROLE, giving you access to all pages (no restriction).
29+
After that, run `./setup-admin-db-dev.sh` (you might have to make it executable before with `chmod +x setup-admin-db-dev.sh`).
30+
31+
7. To run the FE and BE, you can either run it locally or using the docker:
32+
- Local:
33+
at `backend/server`: do `cargo run build` to run the backend server.
34+
at `frontend-nextjs`: do `bun run dev` to run the frontend.
35+
- Docker:
36+
at the root `chaos/` folder,
37+
do `docker compose -f docker-compose.local.yml up -d frontend backend`
38+
do `docker compose -f docker-compose.local.yml up -d` to run all 3 (db, FE and BE)
39+
or
40+
If you want to run those seperately:
41+
do `docker compose -f docker-compose.local.yml up -d backend`
42+
do `docker compose -f docker-compose.local.yml up -d frontend`
43+
44+
7 (Optional). If you want to clear the database, an empty database to restart maybe, run `./reset-db-dev.sh` (you might have to make it executable before with `chmod +x ./reset-db-dev.sh`).
45+
46+
47+
### Authentication
48+
Some routes are only accessible by Users/Admins/SuperAdmins. To login your browser with a respective User/Admin/SuperAdmin cookie, seed your database as above (step 6), and then call one of the following routes in your browser:
49+
- **Normal User:** `/api/v1/dev/user_login`
50+
- **Organisation Admin User:** `/api/v1/dev/org_admin_login`
51+
- **Super Admin User:** `/api/v1/dev/super_admin_login`
52+
53+
## Code Structure
54+
55+
### Handler
56+
The handler module takes care of request handling. It implements the framework or library we are using, invokes the
57+
service functions and responds via HTTP with their return values.
58+
59+
### Middleware
60+
The middleware module contains middlewares, functions that run before or after the function handlers. A common use case
61+
is authorization, where middleware is used to find the userId from the user's token.
62+
63+
### Models
64+
Models are Rust structs that represent the data. There must be a struct for each table in the database, as well as a
65+
struct to describe the fully joined data. E.g. A campaign struct with a array of questions, even though questions are
66+
stored as rows in a separate table. These models implement all functions that conduct business logic on the respective
67+
entity, and also interact with the database. This separation from request handling makes it easy to swap out any new
68+
form of requests, but reuse the same logic functions.
69+
70+
### Service
71+
The service module contains all helper functions. For example, functions for determining a user's authorization to
72+
mutate an object are defined here.
73+
74+
#### Request Path
75+
Request -> Middleware (optional) -> Handler -> Service -> Middleware (Optional) -> Response
76+
77+
78+
## Tech Stack
79+
80+
### Web Server
81+
- [Axum](https://github.com/tokio-rs/axum)
82+
83+
### Persistence
84+
- [SQLx](https://github.com/launchbadge/sqlx) - Queries and Migrations
85+
- PostgreSQL
86+
87+
### AuthN
88+
- OAuth 2 (Google)
89+
90+
### AuthZ
91+
- JWT
92+
93+
### Storage
94+
- Object storage

backend/reset-db-dev.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/sh
2+
3+
# THIS SCRIPT ASSUMES THAT THE DB CONTAINER IS RUNNING.
4+
5+
# Drop the database, create it, and run the migrations.
6+
echo "Dropping the database..."
7+
sqlx db drop -f || exit 1
8+
echo "Creating the database..."
9+
sqlx db create || exit 1
10+
echo "Running the migrations..."
11+
sqlx migrate run || exit 1
12+
13+
echo "\nDatabase reset successfully!\n"

backend/setup-admin-db-dev.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/sh
2+
3+
# THIS SCRIPT ASSUMES THAT THE DB CONTAINER IS RUNNING.
4+
5+
# Please run ./setup-dev-env.sh first. Before running this script.
6+
this_script_dir="$(dirname "$(realpath "$0")")"
7+
repo_root="$(realpath "$this_script_dir/..")"
8+
working_dir="$repo_root/backend/database-seeding"
9+
cd "$working_dir"
10+
echo "Working directory: $working_dir"
11+
12+
# IMPORTANT: Replace YOUR_GMAIL with your account gmail.
13+
# Run the database seeding script to set up the admin account.
14+
# Like e.g. cargo run -- --email peter@gmail.com. Replace YOUR_GMAIL with your account gmail.
15+
cargo run -- --email "vietthanh01202@gmail.com"
16+
17+
echo "\nAdmin account set up successfully!\n"

backend/setup-dev-env.sh

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
tmp_env_file="$(mktemp)"
1010
trap 'rm -rf "$tmp_env_file"' EXIT INT TERM
1111
cat << 'EOF' > "$tmp_env_file"
12-
DATABASE_URL="postgres://user:password@localhost:5432/chaos"
12+
DATABASE_URL="postgres://postgres:password@localhost:5432/chaos"
1313
JWT_SECRET="test_secret"
1414
GOOGLE_CLIENT_ID="test"
1515
GOOGLE_CLIENT_SECRET="test"
@@ -26,13 +26,23 @@ SMTP_HOST="smtp.example.com"
2626
EOF
2727

2828
# Check the user has all required tools installed.
29-
for cmd in "which cargo" "which docker && docker info" "which docker-compose || docker compose"; do
29+
for cmd in "which cargo" "which docker && docker info"; do
3030
if ! eval "$cmd" 1>/dev/null 2>&1; then
3131
echo "The command '$cmd' failed, indicating you might not have that tool installed." >&2
3232
exit 1
3333
fi
3434
done
3535

36+
# Check if docker is available on the machine. Please install docker if don't have it.
37+
if docker compose version >/dev/null 2>&1; then
38+
compose_cmd="docker compose"
39+
elif which docker-compose >/dev/null 2>&1; then
40+
compose_cmd="docker-compose"
41+
else
42+
echo "Neither 'docker compose' nor 'docker-compose' is available. Please install Docker and try again." >&2
43+
exit 1
44+
fi
45+
3646
# Create .env file.
3747
env_file=.env
3848

@@ -64,22 +74,38 @@ fi
6474

6575
# Run postgres database in the background.
6676
this_script_dir="$(dirname "$(realpath "$0")")"
67-
docker_compose_file_name="setup-test-database.yml"
68-
docker_compose_file_path="$this_script_dir/$docker_compose_file_name"
77+
repo_root="$(realpath "$this_script_dir/..")"
78+
docker_compose_file_path="$repo_root/docker-compose.local.yml"
79+
80+
echo 'Starting up postgres db container in docker-compose.local.yml'
81+
# Run the db container only using docker compose.
82+
$compose_cmd -f "$docker_compose_file_path" up --detach db || exit 1
6983

70-
echo 'Starting up postgres database in docker'
71-
docker-compose -f "$docker_compose_file_path" up --detach || exit 1
84+
echo "Waiting for db container to be ready"
85+
# Wait for 30 seconds for the db container to be ready.
86+
max_attempts=30
87+
attempt=1
7288

73-
# Ensure the docker container gets killed once this script exits.
74-
trap 'echo "shutting down $docker_compose_file_path" && docker-compose -f "$docker_compose_file_path" down' EXIT INT TERM
89+
while [ "$attempt" -le "$max_attempts" ]; do
90+
if PGPASSWORD=password psql -h localhost -p 5432 -U postgres -d postgres -c "SELECT 1" >/dev/null 2>&1; then
91+
break
92+
fi
93+
echo "Database not ready yet (attempt $attempt/$max_attempts), retrying..."
94+
sleep 2
95+
attempt=$((attempt + 1))
96+
done
7597

76-
# Wait for the database to be ready.
77-
echo "Waiting for database to be ready"
78-
sleep 3
98+
if [ "$attempt" -gt "$max_attempts" ]; then
99+
echo "Database did not become ready in time." >&2
100+
exit 1
101+
fi
79102

80103
# Setup sqlx.
81104
echo "Setting up sqlx"
82105
sqlx database create || exit 1
83106
sqlx migrate run || exit 1
84107

108+
echo "\nDev environment setup complete! Only the db container is running. You can start the BE and FE containers now.\n"
109+
echo "Don't forget to run ./setup-admin-db-dev.sh to set up the admin account. \n"
110+
85111
"$SHELL"

backend/setup-test-database.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

docker-compose.local.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
services:
2+
# db
3+
db:
4+
image: postgres:17
5+
restart: unless-stopped
6+
environment:
7+
POSTGRES_USER: ${POSTGRES_USER:-postgres}
8+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
9+
POSTGRES_DB: ${POSTGRES_DB:-chaos}
10+
ports:
11+
- "${POSTGRES_PORT:-5432}:5432"
12+
volumes:
13+
- chaos_postgres-data:/var/lib/postgresql/data
14+
healthcheck:
15+
test:
16+
[
17+
"CMD-SHELL",
18+
"pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-chaos}",
19+
]
20+
interval: 10s
21+
timeout: 5s
22+
retries: 10
23+
24+
# server
25+
backend:
26+
build:
27+
context: ./backend
28+
dockerfile: Dockerfile.dev
29+
depends_on:
30+
db:
31+
condition: service_healthy
32+
env_file:
33+
- ./backend/.env
34+
environment:
35+
DATABASE_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-chaos}
36+
ports:
37+
- "${BACKEND_PORT:-8080}:8080"
38+
volumes:
39+
- ./backend:/app/backend:cached
40+
- cargo-registry:/usr/local/cargo/registry
41+
- cargo-git:/usr/local/cargo/git
42+
- backend-target:/app/backend/target
43+
working_dir: /app/backend/server
44+
command: sh -c "cd /app/backend && /usr/local/cargo/bin/sqlx migrate run && cd server && cargo watch -x run"
45+
46+
# next.js-frontend
47+
frontend:
48+
build:
49+
context: ./frontend-nextjs
50+
dockerfile: Dockerfile.dev
51+
depends_on:
52+
- backend
53+
env_file:
54+
- ./frontend-nextjs/.env
55+
environment:
56+
NEXT_API_BASE_URL: http://backend:8080
57+
NEXT_PUBLIC_API_BASE_URL: http://localhost:${BACKEND_PORT:-8080}
58+
NEXT_PUBLIC_APP_URL: http://localhost:${FRONTEND_PORT:-3000}/
59+
ports:
60+
- "${FRONTEND_PORT:-3000}:3000"
61+
volumes:
62+
- ./frontend-nextjs:/app:cached
63+
- frontend-node-modules:/app/node_modules
64+
- frontend-next-cache:/app/.next
65+
# Save time rebuilding the frontend if the node_modules directory is already present.
66+
command:
67+
sh -lc "if [ ! -d node_modules ] || [ -z \"$(ls -A node_modules 2>/dev/null)\" ];
68+
then bun install; fi;
69+
bun run dev -- -H 0.0.0.0 -p 3000"
70+
71+
volumes:
72+
chaos_postgres-data:
73+
external: true
74+
cargo-registry:
75+
cargo-git:
76+
backend-target:
77+
frontend-node-modules:
78+
frontend-next-cache:
79+

frontend-nextjs/Dockerfile.dev

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM oven/bun:latest
2+
3+
WORKDIR /app
4+
5+
# Pre-install deps for faster first run; source will be bind-mounted in compose.
6+
COPY package.json bun.lock ./
7+
RUN bun install
8+
9+
EXPOSE 3000
10+

frontend-nextjs/src/app/[lang]/login/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,12 @@ export default async function Login({
77
}) {
88
const to = (await searchParams).to ?? "/dashboard";
99

10-
redirect(`${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/google?to=${encodeURIComponent(to)}`)
10+
const raw = process.env.NEXT_PUBLIC_API_BASE_URL;
11+
let apiBase =
12+
raw && String(raw).startsWith("http") ? raw : "http://localhost:8080/";
13+
14+
apiBase = apiBase.endsWith("/") ? apiBase : `${apiBase}/`; // add a trailing slash if not present
15+
16+
redirect(`${apiBase}auth/google?to=${encodeURIComponent(to)}`);
17+
1118
}

frontend-nextjs/src/lib/api.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "https://chaos-api.devsoc.app";
2-
31
const isServer = typeof window === "undefined";
2+
const API_BASE_URL = isServer
3+
? process.env.NEXT_API_BASE_URL ||
4+
process.env.NEXT_PUBLIC_API_BASE_URL ||
5+
"http://backend:8080"
6+
: process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8080";
47

58
export class ApiError extends Error {
69
status: number;

0 commit comments

Comments
 (0)