Set up CI

This commit is contained in:
Andras Schmelczer 2026-06-06 19:36:15 +01:00
parent 5dcf27bb9f
commit 52eb07a993
12 changed files with 223 additions and 8218 deletions

View file

@ -2,13 +2,13 @@ name: Deploy to Pages
on: on:
push: push:
branches: ['main'] branches: ["main"]
pull_request: pull_request:
branches: ['main'] branches: ["main"]
workflow_dispatch: workflow_dispatch:
concurrency: concurrency:
group: 'pages' group: "pages"
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
@ -18,11 +18,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Validate static frontend
run: |
test -f frontend/index.html
test -f frontend/fizika.json
- name: Copy frontend to host pages mount - name: Copy frontend to host pages mount
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: http://forgejo:3000/andras/ci-actions/deploy-pages@main uses: http://forgejo:3000/andras/ci-actions/deploy-pages@main

View file

@ -2,10 +2,9 @@ name: Build and Publish Docker Image
on: on:
push: push:
branches: ['main'] branches: ["main"]
tags: ['v*']
pull_request: pull_request:
branches: ['main'] branches: ["main"]
workflow_dispatch: workflow_dispatch:
jobs: jobs:

View file

@ -0,0 +1,30 @@
name: Test
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_dispatch:
jobs:
test:
runs-on: docker
steps:
- name: Checkout repository
uses: https://code.forgejo.org/actions/checkout@v4
- name: Install root tooling
run: npm ci
- name: Check formatting
run: npm run format:check
- name: Install backend dependencies
run: npm ci
working-directory: backend
- name: Run backend tests
run: npm test
working-directory: backend

10
.prettierignore Normal file
View file

@ -0,0 +1,10 @@
node_modules
backend/node_modules
# Lockfiles are managed by npm
package-lock.json
backend/package-lock.json
# Vendored / generated assets
frontend/js/jquery.min.js
*.min.js

6
.prettierrc.json Normal file
View file

@ -0,0 +1,6 @@
{
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"singleQuote": false
}

105
README.md
View file

@ -1 +1,106 @@
# Fizika # Fizika
Practice web app for the Hungarian **emelt szintű fizika** (advanced-level
physics) written school-leaving exam. Students generate quizzes from a bank of
past exam questions — by topic, by exam paper, or as a random "érettségi"
set — answer them, and review their past results (stored locally in the
browser).
Live at <https://fizika.schmelczer.dev>.
## Architecture
```
frontend/ Static site (HTML + CSS + jQuery, no build step).
Deployed to Pages. Fetches questions from the backend API.
backend/ Express server. Serves the admin panel and a small REST API for
editing questions and images. Reads/writes a single JSON file plus
an images directory.
```
The **backend is the single source of truth** for question data. The frontend
always fetches it from `GET /api/fizika` (see `frontend/js/load.js`); the
question JSON is no longer shipped with the static site.
### Access control
Authentication is handled at the reverse proxy (nginx + basic auth), not in the
app. The proxy exposes only two endpoints publicly; everything else (the admin
panel and all `/api/admin/*` routes) requires a login:
| Path | Access | Purpose |
| --------------- | ------- | -------------------------------- |
| `/api/fizika` | public | question data for the public app |
| `/api/pics/*` | public | question images |
| everything else | private | admin panel + edit API |
The app trusts the proxy, so **do not expose the backend port directly** without
adding authentication in front of it.
## API
| Method | Route | Description |
| -------- | ----------------------------- | --------------------- |
| `GET` | `/api/fizika` | All questions |
| `GET` | `/api/images` | List image filenames |
| `GET` | `/api/pics/:file` | Serve an image |
| `GET` | `/api/admin/questions` | All questions (admin) |
| `POST` | `/api/admin/questions` | Create a question |
| `PUT` | `/api/admin/questions/:id` | Update a question |
| `DELETE` | `/api/admin/questions/:id` | Delete a question |
| `POST` | `/api/admin/images/upload` | Upload an image |
| `DELETE` | `/api/admin/images/:filename` | Delete an image |
## Configuration
The backend is configured via environment variables:
| Variable | Default | Description |
| -------------- | ------------------------- | --------------------------------- |
| `PORT` | `3001` | Port to listen on |
| `DATA_PATH` | `../frontend/fizika.json` | Path to the questions JSON file |
| `PICS_PATH` | `../frontend/pics` | Directory holding question images |
| `FRONTEND_URL` | `*` | Allowed CORS origin |
In production `DATA_PATH` and `PICS_PATH` point at mounted volumes that hold the
canonical data. The defaults are dev conveniences only — set both explicitly to
a JSON file and a directory that exist before starting.
## Development
```sh
cd backend
npm install
DATA_PATH=/path/to/fizika.json PICS_PATH=/path/to/pics npm run dev
```
Then open `frontend/index.html` (or serve `frontend/` statically). On
`localhost` the frontend talks to the backend on port 3001 automatically.
## Tests
Backend tests use Node's built-in test runner (no extra dependencies):
```sh
cd backend
npm test
```
## Formatting
[Prettier](https://prettier.io) formats the whole repo. Run from the repo root:
```sh
npm install # once, to install Prettier
npm run format # write
npm run format:check # verify (also run in CI)
```
## CI / Deployment
Forgejo Actions workflows in `.forgejo/workflows/`:
- `test.yml` — formatting check + backend tests on every push/PR.
- `docker-publish.yml` — builds and publishes the backend image from `backend/`.
- `deploy.yml` — deploys `frontend/` to Pages.

View file

@ -1,3 +1,4 @@
node_modules node_modules
Dockerfile Dockerfile
.dockerignore .dockerignore
*.test.js

View file

@ -5,7 +5,8 @@
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js",
"dev": "nodemon server.js" "dev": "nodemon server.js",
"test": "node --test"
}, },
"dependencies": { "dependencies": {
"express": "^5.2.1", "express": "^5.2.1",

View file

@ -1,19 +1,19 @@
{ {
"name": "Fizika", "name": "Fizika",
"icons": [ "icons": [
{ {
"src": "favicons/android-chrome-192x192.png", "src": "favicons/android-chrome-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "favicons/android-chrome-384x384.png", "src": "favicons/android-chrome-384x384.png",
"sizes": "384x384", "sizes": "384x384",
"type": "image/png" "type": "image/png"
} }
], ],
"theme_color": "#ffffff", "theme_color": "#ffffff",
"background_color": "#ffffff", "background_color": "#ffffff",
"display": "standalone", "display": "standalone",
"orientation": "portrait" "orientation": "portrait"
} }

File diff suppressed because it is too large Load diff

31
package-lock.json generated Normal file
View file

@ -0,0 +1,31 @@
{
"name": "fizika",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fizika",
"version": "1.0.0",
"devDependencies": {
"prettier": "^3.4.2"
}
},
"node_modules/prettier": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
}
}
}

13
package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "fizika",
"version": "1.0.0",
"description": "Fizika emelt szintű érettségi gyakorló alkalmazás (repo tooling).",
"private": true,
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"devDependencies": {
"prettier": "^3.4.2"
}
}