Initiële opzet: Fastify + static, Docker, Postgres, Forgejo Actions
Some checks are pending
dev - build & deploy naar test / build-and-deploy (push) Waiting to run
Some checks are pending
dev - build & deploy naar test / build-and-deploy (push) Waiting to run
This commit is contained in:
commit
516a048ce7
14 changed files with 6453 additions and 0 deletions
14
.dockerignore
Normal file
14
.dockerignore
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
compose*.yaml
|
||||
deploy
|
||||
.forgejo
|
||||
README.md
|
||||
*.md
|
||||
15
.env.example
Normal file
15
.env.example
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Kopieer naar .env voor lokaal draaien (nooit .env committen).
|
||||
# Op test/prod komen deze waarden uit een .env naast de deploy-compose op de VM.
|
||||
|
||||
# App
|
||||
PORT=3000
|
||||
APP_VERSION=dev
|
||||
|
||||
# PostgreSQL (moet matchen met de db-service in compose)
|
||||
POSTGRES_DB=teach
|
||||
POSTGRES_USER=teach
|
||||
POSTGRES_PASSWORD=change-me-locally
|
||||
|
||||
# Connectiestring die de app gebruikt.
|
||||
# Hostnaam 'db' verwijst naar de postgres-service binnen het compose-netwerk.
|
||||
DATABASE_URL=postgres://teach:change-me-locally@db:5432/teach
|
||||
62
.forgejo/workflows/dev.yaml
Normal file
62
.forgejo/workflows/dev.yaml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
name: dev - build & deploy naar test
|
||||
|
||||
# Elke push naar dev bouwt een image en zet die op de test-VM.
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
|
||||
concurrency:
|
||||
group: deploy-test
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest # pas aan naar het label van jouw Forgejo-runner
|
||||
env:
|
||||
# git.jouwdomein.nl - host van je Forgejo (= container registry host)
|
||||
REGISTRY: ${{ vars.REGISTRY }}
|
||||
IMAGE_NAME: ${{ github.repository }} # bv. ramon/teach
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Image-tags bepalen
|
||||
id: meta
|
||||
run: |
|
||||
IMAGE="${REGISTRY}/${IMAGE_NAME}"
|
||||
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
|
||||
echo "sha_tag=${IMAGE}:dev-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
|
||||
echo "dev_tag=${IMAGE}:dev" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Login bij Forgejo registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
- name: Build & push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
build-args: |
|
||||
APP_VERSION=dev-${{ github.sha }}
|
||||
tags: |
|
||||
${{ steps.meta.outputs.sha_tag }}
|
||||
${{ steps.meta.outputs.dev_tag }}
|
||||
|
||||
- name: Deploy naar test-VM
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ secrets.TEST_HOST }}
|
||||
username: ${{ secrets.TEST_USER }}
|
||||
key: ${{ secrets.TEST_SSH_KEY }}
|
||||
script: |
|
||||
set -e
|
||||
cd ${{ secrets.TEST_DEPLOY_PATH }}
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ vars.REGISTRY }} -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
# Zet de te draaien image-tag in de env van de VM
|
||||
sed -i "s|^IMAGE=.*|IMAGE=${{ steps.meta.outputs.dev_tag }}|" .env
|
||||
docker compose -f compose.deploy.yaml pull
|
||||
docker compose -f compose.deploy.yaml up -d
|
||||
docker image prune -f
|
||||
63
.forgejo/workflows/release.yaml
Normal file
63
.forgejo/workflows/release.yaml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
name: release - build & deploy naar productie
|
||||
|
||||
# Draait wanneer je in Forgejo een release publiceert.
|
||||
# De release-tag (bv. v1.2.0) wordt de image-tag op productie.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
concurrency:
|
||||
group: deploy-prod
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest # pas aan naar het label van jouw Forgejo-runner
|
||||
env:
|
||||
REGISTRY: ${{ vars.REGISTRY }}
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Image-tags bepalen
|
||||
id: meta
|
||||
run: |
|
||||
IMAGE="${REGISTRY}/${IMAGE_NAME}"
|
||||
VERSION="${GITHUB_REF_NAME}" # de release-/tagnaam, bv. v1.2.0
|
||||
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
|
||||
echo "version_tag=${IMAGE}:${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "latest_tag=${IMAGE}:latest" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Login bij Forgejo registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
- name: Build & push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
build-args: |
|
||||
APP_VERSION=${{ steps.meta.outputs.version }}
|
||||
tags: |
|
||||
${{ steps.meta.outputs.version_tag }}
|
||||
${{ steps.meta.outputs.latest_tag }}
|
||||
|
||||
- name: Deploy naar productie-VM
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ secrets.PROD_HOST }}
|
||||
username: ${{ secrets.PROD_USER }}
|
||||
key: ${{ secrets.PROD_SSH_KEY }}
|
||||
script: |
|
||||
set -e
|
||||
cd ${{ secrets.PROD_DEPLOY_PATH }}
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ vars.REGISTRY }} -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
sed -i "s|^IMAGE=.*|IMAGE=${{ steps.meta.outputs.version_tag }}|" .env
|
||||
docker compose -f compose.deploy.yaml pull
|
||||
docker compose -f compose.deploy.yaml up -d
|
||||
docker image prune -f
|
||||
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
node_modules/
|
||||
npm-debug.log*
|
||||
|
||||
# Lokale env-bestanden met secrets nooit committen
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Postgres data volume bij lokaal draaien
|
||||
pgdata/
|
||||
|
||||
# OS / editor
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.vscode/
|
||||
.idea/
|
||||
33
Dockerfile
Normal file
33
Dockerfile
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
# --- Dependencies (alleen productie) ----------------------------------------
|
||||
FROM node:22-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
# npm ci als er een lockfile is, anders install (eerste keer nog geen lock)
|
||||
RUN if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi
|
||||
|
||||
# --- Runtime ----------------------------------------------------------------
|
||||
FROM node:22-alpine AS runtime
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
|
||||
# Draai als non-root gebruiker
|
||||
RUN addgroup -S app && adduser -S app -G app
|
||||
|
||||
COPY --chown=app:app --from=deps /app/node_modules ./node_modules
|
||||
COPY --chown=app:app package.json ./
|
||||
COPY --chown=app:app src ./src
|
||||
COPY --chown=app:app public ./public
|
||||
|
||||
# Versie wordt tijdens de build meegegeven door CI
|
||||
ARG APP_VERSION=dev
|
||||
ENV APP_VERSION=$APP_VERSION
|
||||
|
||||
USER app
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD node -e "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
|
||||
CMD ["node", "src/server.js"]
|
||||
131
README.md
Normal file
131
README.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Teach
|
||||
|
||||
Digibord-webapp (Fastify + static frontend) met PostgreSQL, gecontaineriseerd en
|
||||
via Forgejo Actions automatisch uitgerold: `dev` → test-VM, release → productie-VM.
|
||||
|
||||
## Structuur
|
||||
|
||||
```
|
||||
teach/
|
||||
├─ public/index.html # de digibord-app (voorheen teach.html)
|
||||
├─ src/server.js # Fastify: serveert de app + /api + DB-pool
|
||||
├─ db/001_init.sql # initieel Postgres-schema
|
||||
├─ Dockerfile # productie-image (non-root, healthcheck)
|
||||
├─ compose.yaml # lokaal draaien (app + postgres)
|
||||
├─ deploy/compose.deploy.yaml # test/prod: pullt image uit de registry
|
||||
├─ .forgejo/workflows/
|
||||
│ ├─ dev.yaml # push naar dev → build + deploy test
|
||||
│ └─ release.yaml # release → build + deploy prod
|
||||
└─ .env.example
|
||||
```
|
||||
|
||||
## Lokaal draaien
|
||||
|
||||
```bash
|
||||
cp .env.example .env # pas POSTGRES_PASSWORD en DATABASE_URL aan
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
App op http://localhost:3000, healthcheck op `/healthz`, DB-check op `/readyz`.
|
||||
|
||||
## Architectuurkeuzes
|
||||
|
||||
- **Container i.p.v. losse static site**: omdat er een database bijkomt, is een
|
||||
backend nodig (browser praat niet rechtstreeks met Postgres). De Fastify-app
|
||||
serveert de HTML én biedt de `/api`, en praat met de DB.
|
||||
- **Forgejo container registry**: CI bouwt de image één keer en pusht die; beide
|
||||
VM's pullen exact dezelfde geteste image. Geen build op de productie-VM.
|
||||
- **Nginx blijft ervoor** als reverse proxy / TLS op elke VM; de app luistert
|
||||
alleen op `127.0.0.1:<APP_PORT>`.
|
||||
|
||||
## Eenmalige setup
|
||||
|
||||
### 1. Repo aanmaken in Forgejo
|
||||
Maak een leeg repo (bv. `ramon/teach`) en push (zie onderaan).
|
||||
|
||||
### 2. Registry / Actions variabelen en secrets
|
||||
Onder **Settings → Actions → Variables** van het repo (of org):
|
||||
|
||||
| Variable | Voorbeeld | Uitleg |
|
||||
|------------|--------------------------|---------------------------------|
|
||||
| `REGISTRY` | `git.familiebesselink.nl`| Host van je Forgejo = registry |
|
||||
|
||||
Onder **Settings → Actions → Secrets**:
|
||||
|
||||
| Secret | Uitleg |
|
||||
|--------------------|--------------------------------------------------------------|
|
||||
| `REGISTRY_USER` | Forgejo-gebruiker met package-write rechten |
|
||||
| `REGISTRY_TOKEN` | Token/wachtwoord voor die gebruiker (scope: packages) |
|
||||
| `TEST_HOST` | IP/hostname van de test-VM |
|
||||
| `TEST_USER` | SSH-gebruiker op de test-VM |
|
||||
| `TEST_SSH_KEY` | Private SSH-key (deploy key) voor de test-VM |
|
||||
| `TEST_DEPLOY_PATH` | Pad op de test-VM met `compose.deploy.yaml` + `.env` + `db/` |
|
||||
| `PROD_HOST` | IP/hostname van de productie-VM |
|
||||
| `PROD_USER` | SSH-gebruiker op de productie-VM |
|
||||
| `PROD_SSH_KEY` | Private SSH-key voor de productie-VM |
|
||||
| `PROD_DEPLOY_PATH` | Pad op de productie-VM met de deploy-bestanden |
|
||||
|
||||
> De runner moet Docker + buildx beschikbaar hebben en `actions/checkout`,
|
||||
> `docker/*` en `appleboy/ssh-action` kunnen ophalen (standaard van github.com;
|
||||
> instelbaar via de runner-config). Pas `runs-on` in de workflows aan naar het
|
||||
> label van jouw runner als dat niet `ubuntu-latest` is.
|
||||
|
||||
### 3. Deploy-map op elke VM
|
||||
Op zowel de test- als de productie-VM, in het pad dat je bij `*_DEPLOY_PATH`
|
||||
opgeeft:
|
||||
|
||||
```bash
|
||||
mkdir -p teach && cd teach
|
||||
# kopieer deze twee uit de repo:
|
||||
# deploy/compose.deploy.yaml -> compose.deploy.yaml
|
||||
# db/ -> db/
|
||||
# maak een .env aan:
|
||||
cat > .env <<'EOF'
|
||||
IMAGE=git.familiebesselink.nl/ramon/teach:dev # prod: laat CI dit op :vX.Y.Z zetten
|
||||
POSTGRES_DB=teach
|
||||
POSTGRES_USER=teach
|
||||
POSTGRES_PASSWORD=<sterk-wachtwoord>
|
||||
DATABASE_URL=postgres://teach:<sterk-wachtwoord>@db:5432/teach
|
||||
APP_PORT=3000
|
||||
EOF
|
||||
```
|
||||
|
||||
CI werkt bij elke deploy de `IMAGE=`-regel bij, pullt en herstart.
|
||||
|
||||
### 4. Nginx reverse proxy (per VM)
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name digibord.familiebesselink.nl; # test-VM eigen subdomein
|
||||
|
||||
# ssl_certificate ... (bestaande config)
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow / branching
|
||||
|
||||
- Werk op feature-branches, merge naar **`dev`** → automatisch naar test.
|
||||
- Tevreden? Maak een **release** (tag `vX.Y.Z`) → automatisch naar productie.
|
||||
|
||||
## Push naar Forgejo (eerste keer)
|
||||
|
||||
```bash
|
||||
git remote add origin https://git.familiebesselink.nl/ramon/teach.git
|
||||
git push -u origin main
|
||||
git push -u origin dev
|
||||
```
|
||||
|
||||
## Volgende stap: data uit localStorage naar de database
|
||||
|
||||
De frontend bewaart borden/gebruikers nu nog in `localStorage`. Om echt een
|
||||
gedeelde database te gebruiken, bouwen we `/api`-endpoints (boards, folders,
|
||||
users) in `src/server.js` en laten we de frontend die aanroepen i.p.v.
|
||||
`localStorage`. Het schema in `db/001_init.sql` is daarvoor het startpunt.
|
||||
41
compose.yaml
Normal file
41
compose.yaml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Lokaal ontwikkelen: bouwt de app zelf en start Postgres ernaast.
|
||||
# cp .env.example .env (en pas het wachtwoord aan)
|
||||
# docker compose up --build
|
||||
# App op http://localhost:3000
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
APP_VERSION: dev
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
PORT: 3000
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
APP_VERSION: dev
|
||||
ports:
|
||||
- "3000:3000"
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
# Init-scripts draaien alleen bij een lege database
|
||||
- ./db:/docker-entrypoint-initdb.d:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
34
db/001_init.sql
Normal file
34
db/001_init.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
-- Initieel schema voor teach.
|
||||
-- Dit bestand wordt bij een verse Postgres-container automatisch uitgevoerd
|
||||
-- (docker-entrypoint-initdb.d). Bij bestaande databases draai je migraties apart.
|
||||
--
|
||||
-- Onderstaande tabellen zijn een startpunt: ze weerspiegelen wat nu in
|
||||
-- localStorage zit (gebruikers en borden). Pas ze aan zodra de API-vorm vaststaat.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL DEFAULT 'teacher',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS folders (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
owner_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS boards (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
folder_id BIGINT REFERENCES folders(id) ON DELETE SET NULL,
|
||||
owner_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
-- De volledige bordinhoud (tekeningen, widgets, blokken) als JSON.
|
||||
content JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_boards_owner ON boards(owner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_boards_folder ON boards(folder_id);
|
||||
45
deploy/compose.deploy.yaml
Normal file
45
deploy/compose.deploy.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Deploy-compose voor test/prod. Draait op de VM.
|
||||
# De app-image wordt NIET gebouwd maar uit de Forgejo registry gepulld.
|
||||
# CI zet het juiste IMAGE (incl. tag) in een .env naast dit bestand en draait:
|
||||
# docker compose -f compose.deploy.yaml pull
|
||||
# docker compose -f compose.deploy.yaml up -d
|
||||
#
|
||||
# Verwachte env-variabelen op de VM (in ./.env):
|
||||
# IMAGE=git.jouwdomein.nl/ramon/teach:dev (of :vX.Y.Z op prod)
|
||||
# POSTGRES_DB / POSTGRES_USER / POSTGRES_PASSWORD
|
||||
# DATABASE_URL=postgres://<user>:<pw>@db:5432/<db>
|
||||
# APP_PORT=3000 (poort die nginx op deze VM naar de app proxyt)
|
||||
|
||||
services:
|
||||
app:
|
||||
image: ${IMAGE}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
PORT: 3000
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
APP_VERSION: ${IMAGE}
|
||||
ports:
|
||||
# Alleen op localhost van de VM; nginx zit ervoor als reverse proxy
|
||||
- "127.0.0.1:${APP_PORT:-3000}:3000"
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
- ./db:/docker-entrypoint-initdb.d:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
1187
package-lock.json
generated
Normal file
1187
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
19
package.json
Normal file
19
package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "teach",
|
||||
"version": "0.1.0",
|
||||
"description": "Teach digibord - Fastify server met static frontend en PostgreSQL",
|
||||
"type": "module",
|
||||
"main": "src/server.js",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"dev": "node --watch src/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/static": "^8.0.1",
|
||||
"fastify": "^5.1.0",
|
||||
"pg": "^8.13.1"
|
||||
}
|
||||
}
|
||||
4712
public/index.html
Normal file
4712
public/index.html
Normal file
File diff suppressed because it is too large
Load diff
81
src/server.js
Normal file
81
src/server.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import Fastify from 'fastify';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import pg from 'pg';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const PORT = Number(process.env.PORT ?? 3000);
|
||||
const HOST = process.env.HOST ?? '0.0.0.0';
|
||||
|
||||
const app = Fastify({
|
||||
logger: true,
|
||||
trustProxy: true, // achter nginx reverse proxy
|
||||
});
|
||||
|
||||
// --- Database pool -----------------------------------------------------------
|
||||
// Verbinding via DATABASE_URL, bv: postgres://teach:pw@db:5432/teach
|
||||
const pool = new pg.Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
max: Number(process.env.PG_POOL_MAX ?? 10),
|
||||
});
|
||||
|
||||
// Maak de pool bereikbaar in routes via app.pg
|
||||
app.decorate('pg', pool);
|
||||
|
||||
// --- Health checks (gebruikt door Docker + load balancer) --------------------
|
||||
app.get('/healthz', async () => ({ status: 'ok' }));
|
||||
|
||||
app.get('/readyz', async (req, reply) => {
|
||||
try {
|
||||
await pool.query('SELECT 1');
|
||||
return { status: 'ready' };
|
||||
} catch (err) {
|
||||
req.log.error({ err }, 'database not ready');
|
||||
reply.code(503);
|
||||
return { status: 'db_unavailable' };
|
||||
}
|
||||
});
|
||||
|
||||
// --- API ---------------------------------------------------------------------
|
||||
// Voorbeeld-endpoint. Hier komt straks de board/user-API die nu nog in
|
||||
// localStorage zit. Prefix /api houdt het netjes gescheiden van de static app.
|
||||
app.register(
|
||||
async (api) => {
|
||||
api.get('/version', async () => ({
|
||||
name: 'teach',
|
||||
version: process.env.APP_VERSION ?? 'dev',
|
||||
}));
|
||||
},
|
||||
{ prefix: '/api' },
|
||||
);
|
||||
|
||||
// --- Static frontend ---------------------------------------------------------
|
||||
// Serveert public/index.html (de digibord-app) op /
|
||||
app.register(fastifyStatic, {
|
||||
root: join(__dirname, '..', 'public'),
|
||||
index: ['index.html'],
|
||||
});
|
||||
|
||||
// --- Start -------------------------------------------------------------------
|
||||
const start = async () => {
|
||||
try {
|
||||
await app.listen({ port: PORT, host: HOST });
|
||||
} catch (err) {
|
||||
app.log.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Nette shutdown zodat Docker de container snel kan stoppen
|
||||
for (const signal of ['SIGINT', 'SIGTERM']) {
|
||||
process.on(signal, async () => {
|
||||
app.log.info(`${signal} ontvangen, afsluiten...`);
|
||||
await app.close();
|
||||
await pool.end();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
start();
|
||||
Loading…
Reference in a new issue