diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml
new file mode 100644
index 0000000..cee1627
--- /dev/null
+++ b/.forgejo/workflows/deploy.yml
@@ -0,0 +1,31 @@
+name: Deploy to Pages
+
+on:
+ push:
+ branches: ['main']
+ pull_request:
+ branches: ['main']
+ workflow_dispatch:
+
+concurrency:
+ group: 'pages'
+ cancel-in-progress: false
+
+jobs:
+ deploy:
+ runs-on: docker
+
+ steps:
+ - 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
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+ run: |
+ apt update && apt install -y rsync
+ mkdir -p /pages
+ rsync -a --delete frontend/ /pages/fizika
diff --git a/.forgejo/workflows/docker-publish.yml b/.forgejo/workflows/docker-publish.yml
new file mode 100644
index 0000000..f13528a
--- /dev/null
+++ b/.forgejo/workflows/docker-publish.yml
@@ -0,0 +1,63 @@
+name: Build and Publish Docker Image
+
+on:
+ push:
+ branches: ['main']
+ tags: ['v*']
+ pull_request:
+ branches: ['main']
+ workflow_dispatch:
+
+env:
+ IMAGE_NAME: ${{ forgejo.repository }}/fizika-admin
+
+jobs:
+ build-and-push:
+ runs-on: ubuntu-docker
+
+ steps:
+ - name: Checkout repository
+ uses: https://code.forgejo.org/actions/checkout@v4
+
+ - name: Extract registry host
+ id: registry
+ run: echo "host=$(echo '${{ forgejo.server_url }}' | sed 's|https\?://||')" >> "$GITHUB_OUTPUT"
+
+ - name: Log into Forgejo registry
+ if: forgejo.event_name != 'pull_request'
+ run: echo "${{ secrets.FORGEJO_TOKEN }}" | docker login "${{ steps.registry.outputs.host }}" -u "${{ forgejo.actor }}" --password-stdin
+
+ - name: Build Docker image
+ run: |
+ IMAGE="${{ steps.registry.outputs.host }}/$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')"
+ SHA_SHORT="$(echo "${{ forgejo.sha }}" | cut -c1-12)"
+ TAG_ARGS="-t ${IMAGE}:sha-${SHA_SHORT}"
+
+ if [ "${{ forgejo.ref }}" = "refs/heads/main" ]; then
+ TAG_ARGS="${TAG_ARGS} -t ${IMAGE}:main -t ${IMAGE}:latest"
+ fi
+
+ if [ "${{ forgejo.ref_type }}" = "tag" ]; then
+ REF_NAME="${{ forgejo.ref_name }}"
+ TAG_ARGS="${TAG_ARGS} -t ${IMAGE}:${REF_NAME}"
+
+ if echo "$REF_NAME" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
+ VERSION="${REF_NAME#v}"
+ MAJOR_MINOR="$(echo "$VERSION" | cut -d. -f1,2)"
+ MAJOR="$(echo "$VERSION" | cut -d. -f1)"
+ TAG_ARGS="${TAG_ARGS} -t ${IMAGE}:${VERSION} -t ${IMAGE}:${MAJOR_MINOR} -t ${IMAGE}:${MAJOR}"
+ fi
+ fi
+
+ docker build \
+ --label "org.opencontainers.image.source=${{ forgejo.server_url }}/${{ forgejo.repository }}" \
+ --label "org.opencontainers.image.revision=${{ forgejo.sha }}" \
+ --label "org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
+ ${TAG_ARGS} \
+ ./backend
+
+ echo "IMAGE=${IMAGE}" >> "$GITHUB_ENV"
+
+ - name: Push Docker image
+ if: forgejo.event_name != 'pull_request'
+ run: docker push --all-tags "$IMAGE"
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 1230149..0000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-version: 2
-updates:
- - package-ecosystem: "github-actions"
- directory: "/"
- schedule:
- interval: "daily"
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
deleted file mode 100644
index f4851b4..0000000
--- a/.github/workflows/deploy.yaml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: Deploy to Pages
-
-on:
- push:
- branches: [main]
- workflow_dispatch:
-
-permissions:
- contents: read
- pages: write
- id-token: write
-
-# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
-# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
-concurrency:
- group: "pages"
- cancel-in-progress: false
-
-jobs:
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Setup Pages
- uses: actions/configure-pages@v5
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: "frontend"
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
deleted file mode 100644
index 351e43e..0000000
--- a/.github/workflows/docker-publish.yml
+++ /dev/null
@@ -1,86 +0,0 @@
-name: Build and Publish Docker Image
-
-on:
- push:
- branches: ["main"]
-
-env:
- REGISTRY: ghcr.io
- IMAGE_NAME: ${{ github.repository }}/fizika-admin
-
-jobs:
- build-and-push:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
- # This is used to complete the identity challenge
- # with sigstore/fulcio when running outside of PRs.
- id-token: write
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Setup Docker buildx
- uses: docker/setup-buildx-action@v3
-
- # Login against a Docker registry except on PR
- # https://github.com/docker/login-action
- - name: Log into registry ${{ env.REGISTRY }}
- if: github.event_name != 'pull_request'
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- # Extract metadata (tags, labels) for Docker
- # https://github.com/docker/metadata-action
- - name: Extract metadata
- id: meta
- uses: docker/metadata-action@v5
- with:
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- tags: |
- type=ref,event=branch
- type=ref,event=pr
- type=semver,pattern={{version}}
- type=semver,pattern={{major}}.{{minor}}
- type=semver,pattern={{major}}
- type=sha,prefix={{branch}}-
- # set latest tag for default branch
- type=raw,value=latest,enable={{is_default_branch}}
-
- # Build and push Docker image with Buildx (don't push on PR)
- # https://github.com/docker/build-push-action
- - name: Build and push Docker image
- id: build-and-push
- uses: docker/build-push-action@v5
- with:
- context: ./backend
- file: ./backend/Dockerfile
- platforms: linux/amd64,linux/arm64
- push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
- cache-from: type=gha
- cache-to: type=gha,mode=max
- # Security scanning
- sbom: true
- provenance: true
-
- # Sign the resulting Docker image digest.
- # This will only write to the public Rekor transparency log when the Docker
- # repository is public to avoid leaking data. If you would like to publish
- # transparency data even for private images, pass --force to cosign below.
- # https://github.com/sigstore/cosign
- - name: Sign the published Docker image
- if: ${{ github.ref_type == 'tag' }}
- env:
- # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
- TAGS: ${{ steps.meta.outputs.tags }}
- DIGEST: ${{ steps.build-and-push.outputs.digest }}
- # This step uses the identity token to provision an ephemeral certificate
- # against the sigstore community Fulcio instance.
- run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
diff --git a/README.md b/README.md
index 1bb4f69..909cef3 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1 @@
-# Fizika - Physics Quiz Application
-
-A comprehensive physics quiz application for Hungarian students preparing for their physics exams (érettségi). The application consists of a frontend quiz interface and an admin backend for content management.
-
-## 🚀 Features
-
-### Student Interface (Frontend)
-
-- Interactive physics quiz questions
-- Multiple choice questions with immediate feedback
-- Category-based filtering (dynamics, mechanics, fluids, etc.)
-- Search functionality by year, month, and question number
-- Responsive design for desktop and mobile
-- Progress tracking and scoring
-- Timer functionality
-
-### Admin Interface (Backend)
-
-- 📝 Full CRUD operations for quiz questions
-- 🖼️ Image management (upload, view, delete)
-- 📊 RESTful API for frontend integration
-- 🛡️ Basic security features (input validation)
-- 🐳 Docker containerization ready
+# Fizika
diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md
deleted file mode 100644
index 124ed9c..0000000
--- a/backend/CLAUDE.md
+++ /dev/null
@@ -1,134 +0,0 @@
-# CLAUDE.md
-
-This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
-
-## Common Development Commands
-
-### Running the Application
-```bash
-npm run dev # Development mode with nodemon hot reload
-npm start # Production mode
-```
-
-### Docker Commands
-```bash
-# Production deployment
-docker-compose up -d
-
-# Development with hot reload
-docker-compose --profile dev up fizika-admin-dev
-
-# Build image manually
-docker build -t fizika-admin ./backend
-```
-
-### Environment Setup
-```bash
-cp .env.example .env
-# Edit .env with your configuration
-```
-
-## Architecture Overview
-
-This is a single-file Node.js/Express backend (`server.js`) that serves as both:
-1. **API Server**: Provides REST endpoints for question and image management
-2. **Static File Server**: Serves the admin interface from `/public/` directory
-
-### Key Components
-
-**Data Storage**:
-- Questions stored in JSON file at `../frontend/fizika.json` (configurable via `DATA_PATH`)
-- Images stored in `../frontend/pics/` directory (configurable via `PICS_PATH`)
-
-**Admin Interface**:
-- Built-in web UI served from `/public/index.html`
-- JavaScript client in `/public/admin.js`
-- No authentication required (simplified for admin use)
-
-**API Structure**:
-- Public endpoints: `/api/fizika`, `/api/images`, `/api/pics/:filename`
-- Admin endpoints: `/api/admin/questions`, `/api/admin/images`
-- No JWT authentication implemented despite README documentation
-
-### Question Data Format
-```json
-{
- "id": 1,
- "source": "2016/m1/1",
- "description": "Question text...",
- "a": "Option A",
- "b": "Option B",
- "c": "Option C",
- "d": "Option D",
- "correct": 2,
- "type": "md",
- "image": "optional-image.jpg"
-}
-```
-
-### Complete Question Types (17 Categories)
-
-**IMPORTANT**: The README only mentions 3 types, but the frontend supports all 17:
-
-**Mechanics (Mechanika)**:
-- `mec` - Mechanika (general mechanics)
-- `mk` - Kinematika (kinematics)
-- `md` - Dinamika (dynamics)
-- `me` - Munka és energia (work and energy)
-- `mf` - Folyadékok és gázok mechanikája (fluid mechanics)
-- `mr` - Rezgések és hullámok (oscillations and waves)
-
-**Thermodynamics**:
-- `h` - Hőtan (thermodynamics)
-
-**Electricity**:
-- `ele` - Elektromosság (general electricity)
-- `es` - Elektrosztatika (electrostatics)
-- `ee` - Egyenáram (direct current)
-- `ev` - Váltakozó áram (alternating current)
-
-**Other Physics**:
-- `m` - Mágnesesség (magnetism)
-- `o` - Fénytan (optics)
-- `atm` - Atomfizika (general atomic physics)
-- `ah` - Atomhéj (electron shells)
-- `am` - Atommag (atomic nucleus)
-- `cs` - Égi mechanika, csillagászat (celestial mechanics, astronomy)
-- `v` - Vegyes (mixed/various)
-
-### Security Considerations
-- Helmet.js for security headers
-- CORS configuration via `FRONTEND_URL` environment variable
-- File upload restricted to images only (5MB limit)
-- Input validation minimal - add validation when modifying endpoints
-
-## File Structure
-```
-backend/
-├── server.js # Main application file
-├── public/
-│ ├── index.html # Admin interface HTML
-│ └── admin.js # Admin interface JavaScript
-├── package.json
-├── Dockerfile
-├── docker-compose.yml
-└── .env.example
-```
-
-## Important Notes
-
-- **Single File Architecture**: All server logic is in `server.js` - no separate route files or modules
-- **File-based Data**: Uses JSON file for persistence, not a database
-- **No Authentication**: Despite README documentation mentioning JWT, no auth is implemented
-- **Path Dependencies**: Assumes frontend directory structure (`../frontend/fizika.json`, `../frontend/pics/`)
-- **Admin UI Included**: Built-in web interface accessible at root path `/`
-- **Question Types**: Support all 17 physics categories listed above, not just the 3 in README
-
-## Making Changes
-
-When modifying the API:
-1. All changes go in `server.js`
-2. Test both API endpoints and admin UI functionality
-3. Ensure question type validation supports all 17 categories if adding validation
-4. Consider impact on file paths and data format
-5. Update environment variables in `.env.example` if needed
\ No newline at end of file
diff --git a/backend/Dockerfile b/backend/Dockerfile
index e267374..4df2b9e 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -17,7 +17,6 @@ ENV NODE_ENV=production
ENV PORT=3001
EXPOSE 3001
-# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3001/api/fizika', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"
diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml
deleted file mode 100644
index a72505e..0000000
--- a/backend/docker-compose.yml
+++ /dev/null
@@ -1,70 +0,0 @@
-version: '3.8'
-
-services:
- fizika-admin:
- build:
- context: ./backend
- dockerfile: Dockerfile
- ports:
- - "3001:3001"
- environment:
- - NODE_ENV=production
- - PORT=3001
- - FRONTEND_URL=${FRONTEND_URL:-*}
- - DATA_PATH=${DATA_PATH:-/usr/src/app/fizika.json}
- - PICS_PATH=${PICS_PATH:-/usr/src/app/pics}
- volumes:
- # Mount data files
- - ./fizika.json:/usr/src/app/fizika.json:ro
- - ./pics:/usr/src/app/pics
- # Optional: mount for development
- # - ./backend:/usr/src/app
- restart: unless-stopped
- healthcheck:
- test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/api/fizika', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"]
- interval: 30s
- timeout: 10s
- retries: 3
- start_period: 40s
-
- # Optional: Development service with hot reload
- fizika-admin-dev:
- build:
- context: ./backend
- dockerfile: Dockerfile
- target: dev-dependencies
- ports:
- - "3001:3001"
- environment:
- - NODE_ENV=development
- - PORT=3001
- - FRONTEND_URL=*
- - DATA_PATH=/usr/src/app/fizika.json
- - PICS_PATH=/usr/src/app/pics
- volumes:
- - ./backend:/usr/src/app
- - ./fizika.json:/usr/src/app/fizika.json
- - ./pics:/usr/src/app/pics
- - /usr/src/app/node_modules
- command: npm run dev
- profiles: ["dev"]
-
- # Optional: Nginx reverse proxy for production
- nginx:
- image: nginx:alpine
- ports:
- - "80:80"
- - "443:443"
- volumes:
- - ./nginx.conf:/etc/nginx/nginx.conf:ro
- - .:/usr/share/nginx/html:ro
- # SSL certificates (if using HTTPS)
- # - ./ssl:/etc/ssl/certs
- depends_on:
- - fizika-admin
- restart: unless-stopped
- profiles: ["nginx"]
-
-networks:
- default:
- name: fizika-network
\ No newline at end of file
diff --git a/backend/public/admin.js b/backend/public/admin.js
index 4739746..80b2086 100644
--- a/backend/public/admin.js
+++ b/backend/public/admin.js
@@ -1,5 +1,11 @@
const API_BASE = window.location.origin;
+window.plausible =
+ window.plausible ||
+ function () {
+ (window.plausible.q = window.plausible.q || []).push(arguments);
+ };
+
document.addEventListener("DOMContentLoaded", function () {
loadQuestions();
loadImages();
diff --git a/backend/public/index.html b/backend/public/index.html
index 068322b..8dd795a 100644
--- a/backend/public/index.html
+++ b/backend/public/index.html
@@ -10,13 +10,6 @@
data-api="https://stats.schmelczer.dev/status"
src="https://stats.schmelczer.dev/js/script.file-downloads.hash.outbound-links.js"
>
-