mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-07-05 07:00:14 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1d1eb3a24 | |||
| 5ea7a636b8 |
Executable
+48
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
setopt ERR_EXIT PIPE_FAIL NOUNSET
|
||||||
|
|
||||||
|
# Collect staged PHP and Twig files
|
||||||
|
STAGED_PHP=()
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -n "$file" ]] && STAGED_PHP+=("$file")
|
||||||
|
done < <(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.php$' || true)
|
||||||
|
|
||||||
|
STAGED_TWIG=()
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -n "$file" ]] && STAGED_TWIG+=("$file")
|
||||||
|
done < <(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.twig$' || true)
|
||||||
|
|
||||||
|
if [[ ${#STAGED_PHP[@]} -eq 0 && ${#STAGED_TWIG[@]} -eq 0 ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use exec if the service is up, otherwise spin up a one-off container
|
||||||
|
if docker compose exec -T php true 2>/dev/null; then
|
||||||
|
DOCKER_CMD=(docker compose exec -T php)
|
||||||
|
else
|
||||||
|
echo "PHP service not running — using docker compose run..."
|
||||||
|
DOCKER_CMD=(docker compose run --rm php)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#STAGED_PHP[@]} -gt 0 ]]; then
|
||||||
|
echo "PHP (${#STAGED_PHP[@]} file(s)): Rector → CS-Fixer → PHPStan"
|
||||||
|
|
||||||
|
echo " → Rector"
|
||||||
|
"${DOCKER_CMD[@]}" vendor/bin/rector process "${STAGED_PHP[@]}"
|
||||||
|
git add "${STAGED_PHP[@]}"
|
||||||
|
|
||||||
|
echo " → PHP-CS-Fixer"
|
||||||
|
"${DOCKER_CMD[@]}" vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php "${STAGED_PHP[@]}"
|
||||||
|
git add "${STAGED_PHP[@]}"
|
||||||
|
|
||||||
|
echo " → PHPStan"
|
||||||
|
"${DOCKER_CMD[@]}" vendor/bin/phpstan analyse "${STAGED_PHP[@]}" --no-progress
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#STAGED_TWIG[@]} -gt 0 ]]; then
|
||||||
|
echo "Twig (${#STAGED_TWIG[@]} file(s)): Twig-CS-Fixer"
|
||||||
|
|
||||||
|
echo " → Twig-CS-Fixer"
|
||||||
|
"${DOCKER_CMD[@]}" vendor/bin/twig-cs-fixer fix "${STAGED_TWIG[@]}"
|
||||||
|
git add "${STAGED_TWIG[@]}"
|
||||||
|
fi
|
||||||
+46
-12
@@ -21,6 +21,7 @@ jobs:
|
|||||||
name: Build Dev Image
|
name: Build Dev Image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
@@ -97,8 +98,20 @@ jobs:
|
|||||||
- name: Assert all checks passed
|
- name: Assert all checks passed
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
outcomes="${{ steps.twig_lint.outcome }} ${{ steps.cs.outcome }} ${{ steps.twig_cs.outcome }} ${{ steps.phpstan.outcome }} ${{ steps.rector.outcome }}"
|
failed=0
|
||||||
if echo "$outcomes" | grep -q "failure"; then exit 1; fi
|
check() {
|
||||||
|
local name="$1" outcome="$2"
|
||||||
|
if [[ "$outcome" == "failure" ]]; then
|
||||||
|
echo "::error::$name failed"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
check "Twig Lint" "${{ steps.twig_lint.outcome }}"
|
||||||
|
check "Coding Style" "${{ steps.cs.outcome }}"
|
||||||
|
check "Twig Coding Style" "${{ steps.twig_cs.outcome }}"
|
||||||
|
check "PHPStan" "${{ steps.phpstan.outcome }}"
|
||||||
|
check "Rector" "${{ steps.rector.outcome }}"
|
||||||
|
exit $failed
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Tests
|
name: Tests
|
||||||
@@ -148,23 +161,44 @@ jobs:
|
|||||||
verify-prior-run:
|
verify-prior-run:
|
||||||
name: Verify Prior CI Run
|
name: Verify Prior CI Run
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
timeout-minutes: 20
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
steps:
|
steps:
|
||||||
- name: Check for successful CI run on this commit
|
- name: Wait for and verify successful CI run on this commit
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
count=$(gh api \
|
max_attempts=30
|
||||||
"repos/${{ github.repository }}/actions/workflows/ci.yml/runs?head_sha=${{ github.sha }}&status=success&per_page=5" \
|
attempt=0
|
||||||
--jq "[.workflow_runs[] | select(.id != ${{ github.run_id }})] | length")
|
while [[ $attempt -lt $max_attempts ]]; do
|
||||||
if [[ "$count" -eq 0 ]]; then
|
attempt=$((attempt + 1))
|
||||||
echo "::error::No prior successful CI run found for ${{ github.sha }}. Only tag commits that have passed CI on main."
|
|
||||||
exit 1
|
success_count=$(gh api \
|
||||||
fi
|
"repos/${{ github.repository }}/actions/workflows/ci.yml/runs?head_sha=${{ github.sha }}&status=success&per_page=5" \
|
||||||
echo "Found $count prior successful CI run(s) for this commit."
|
--jq "[.workflow_runs[] | select(.id != ${{ github.run_id }})] | length")
|
||||||
|
|
||||||
|
if [[ "$success_count" -gt 0 ]]; then
|
||||||
|
echo "Found $success_count prior successful CI run(s) for ${{ github.sha }}."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
in_progress_count=$(gh api \
|
||||||
|
"repos/${{ github.repository }}/actions/workflows/ci.yml/runs?head_sha=${{ github.sha }}&per_page=10" \
|
||||||
|
--jq "[.workflow_runs[] | select(.id != ${{ github.run_id }}) | select(.status == \"in_progress\" or .status == \"queued\" or .status == \"waiting\" or .status == \"requested\" or .status == \"pending\")] | length")
|
||||||
|
|
||||||
|
if [[ "$in_progress_count" -gt 0 ]]; then
|
||||||
|
echo "CI still in progress (attempt $attempt/$max_attempts), waiting 30s..."
|
||||||
|
sleep 30
|
||||||
|
else
|
||||||
|
echo "::error::No prior successful CI run found for ${{ github.sha }}. Only tag commits that have passed CI on main."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "::error::Timed out waiting for CI run to complete for ${{ github.sha }}."
|
||||||
|
exit 1
|
||||||
|
|
||||||
build-deploy:
|
build-deploy:
|
||||||
name: Build and Deploy
|
name: Build and Deploy
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ reload-tests:
|
|||||||
@docker compose exec php bin/console --env=test doctrine:migrations:migrate -n
|
@docker compose exec php bin/console --env=test doctrine:migrations:migrate -n
|
||||||
@docker compose exec php bin/console --env=test doctrine:fixtures:load -n --group=test
|
@docker compose exec php bin/console --env=test doctrine:fixtures:load -n --group=test
|
||||||
|
|
||||||
|
install-hooks:
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
chmod +x .githooks/pre-commit
|
||||||
|
@echo "Pre-commit hook installed."
|
||||||
|
|
||||||
trust-cert:
|
trust-cert:
|
||||||
sudo security add-trusted-cer -d \
|
sudo security add-trusted-cer -d \
|
||||||
-r trustRoot \
|
-r trustRoot \
|
||||||
|
|||||||
@@ -1,39 +1,162 @@
|
|||||||
# Tijd voor de test
|
# Tijd voor de test
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
PHP/Symfony application for WIDM-style quiz management.
|
||||||
|
Built with FrankenPHP, PostgreSQL, and Docker.
|
||||||
|
|
||||||
|
> **Disclaimer:** This is an unofficial, non-commercial, open-source fan
|
||||||
|
> project. It is not affiliated with, endorsed by, or associated with
|
||||||
|
> *Wie is de Mol?* (produced by IDTV, broadcast by AVROTROS/NPO) or
|
||||||
|
> *De Mol* (produced by Woestijnvis, broadcast by Play/De Vijver Media).
|
||||||
|
> *Wie is de Mol?* and *De Mol* are trademarks of their respective rights
|
||||||
|
> holders. No copyright infringement is intended.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Maken van de test
|
- Docker
|
||||||
|
- [Just](https://just.systems) (`brew install just`)
|
||||||
|
|
||||||
- WIDM-tests met een variabel aantal vragen.
|
## Local development
|
||||||
- Vragen in een vaste volgorde zijn samen één test (een vraag kan niet bij
|
|
||||||
meerdere tests horen).
|
|
||||||
- Vragen hebben 2 of meer antwoordmogelijkheden. Slechts één antwoord is correct.
|
|
||||||
- Meerdere test samen vormen een seizoen.
|
|
||||||
- Een seizoen heeft één of geen actieve tests, als er een test actief is kan
|
|
||||||
uitsluitend die test gemaakt worden.
|
|
||||||
- Kandidaten kunnen een test maximaal 1 keer invullen.
|
|
||||||
- Vanaf het moment dat de kandidaat op start klikt na het intypen van hun naam
|
|
||||||
gaat de tijd lopen. Deze stopt na het aanklikken van een antwoord op de laatste
|
|
||||||
vraag van de test.
|
|
||||||
- Achtergrondmuziek
|
|
||||||
|
|
||||||
### Schermen kijken
|
```bash
|
||||||
|
just up # Start PHP + PostgreSQL containers
|
||||||
|
just migrate # Run pending database migrations
|
||||||
|
just fixtures # Load dev fixtures (truncates first)
|
||||||
|
```
|
||||||
|
|
||||||
- Nadat een speler een test heeft gemaakt (of vooraf als de namen vooraf
|
The app is available at **https://localhost** (self-signed cert — run
|
||||||
ingevoerd zijn) kunnen jokers toegekend worden aan de test van kandidaat. Een
|
`just trust-cert` on macOS to trust it).
|
||||||
positief getal om antwoorden goed te rekenen, een negatief getal om
|
|
||||||
antwoorden fout te rekenen.
|
|
||||||
- Vooraf kan gekozen worden hoe veel afvallers er zijn.
|
|
||||||
- Bij het kijken naam rode en groene schermen wordt een naam ingevoerd. Er
|
|
||||||
wordt een rood of groen scherm getoond.
|
|
||||||
- Spelers kunnen geforceerd op groen of rood gezet worden, deze worden dan niet
|
|
||||||
meegenomen in de berekening van de slechtste speler.
|
|
||||||
|
|
||||||
### Statistieken
|
### Useful commands
|
||||||
|
|
||||||
TBD
|
```bash
|
||||||
|
just shell # Shell inside the running PHP container
|
||||||
|
just shell-run # Shell in a fresh one-off container
|
||||||
|
just stop # Stop containers (keep volumes)
|
||||||
|
just down # Stop and remove containers
|
||||||
|
just clean # Nuclear: remove containers + volumes + generated files
|
||||||
|
just exec <cmd> # Run any command inside the PHP container
|
||||||
|
```
|
||||||
|
|
||||||
## Nice to haves
|
### Environment
|
||||||
|
|
||||||
- Optie voor antwoord geven in twee klikken (selecteren en volgende).
|
Copy `.env` and override locally via `.env.local` (not committed):
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------------|-------------------------------------|
|
||||||
|
| `APP_SECRET` | Symfony app secret |
|
||||||
|
| `DATABASE_URL` | PostgreSQL DSN (auto-set in Docker) |
|
||||||
|
| `SENTRY_DSN` | Sentry error tracking |
|
||||||
|
| `DEFAULT_URI` | Base URL for CLI-generated links |
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just test # Full PHPUnit suite
|
||||||
|
just test tests/Path/To/TestFile.php # Single file
|
||||||
|
just test --coverage-html var/coverage # HTML coverage report
|
||||||
|
just reload-tests # Drop/recreate test DB + migrate + test fixtures
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests use a separate database configured via `.env.test`. The DAMA
|
||||||
|
Doctrine bundle wraps each test in a transaction that is rolled back after.
|
||||||
|
`just reload-tests` loads the `--group=test` fixtures; `just fixtures`
|
||||||
|
loads the dev group and is unrelated to the test database.
|
||||||
|
|
||||||
|
## Code quality
|
||||||
|
|
||||||
|
All checks run in CI and must pass before merging.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just fix-cs # Auto-fix PHP-CS-Fixer + Twig-CS-Fixer
|
||||||
|
just phpstan # PHPStan static analysis (level 8)
|
||||||
|
just rector # Apply Rector modernizations
|
||||||
|
just rector --dry-run # Preview Rector changes without applying
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just migrate # Run pending migrations
|
||||||
|
just fixtures # Load dev fixtures
|
||||||
|
bin/console make:migration # Generate a new migration (inside container)
|
||||||
|
```
|
||||||
|
|
||||||
|
Migrations live in `migrations/` (namespace `DoctrineMigrations`). Test
|
||||||
|
fixtures are in `src/DataFixtures/` loaded with `--group=test`.
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just translations # Extract/update nl translation strings into translations/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Create a branch from `main` — use a prefix like `feat/`, `fix/`,
|
||||||
|
or `docs/`.
|
||||||
|
2. Open a pull request; CI must pass before merging.
|
||||||
|
3. Install the pre-commit hook (see below) to catch issues before pushing.
|
||||||
|
|
||||||
|
### Pre-commit hook
|
||||||
|
|
||||||
|
A pre-commit hook lives in `.githooks/pre-commit`. Install it once after cloning:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just install-hooks
|
||||||
|
```
|
||||||
|
|
||||||
|
On every commit it runs automatically, **only on staged files**:
|
||||||
|
|
||||||
|
| Staged file type | Tools run |
|
||||||
|
|-------------------------|------------------------------------------------------------------------------|
|
||||||
|
| `.php` | Rector → PHP-CS-Fixer (auto-fix + re-stage), then PHPStan (blocks on errors) |
|
||||||
|
| `.twig` | Twig-CS-Fixer (auto-fix + re-stage) |
|
||||||
|
| Other (docs, config, …) | Nothing — commit proceeds immediately |
|
||||||
|
|
||||||
|
If the PHP container is not running, the hook falls back to
|
||||||
|
`docker compose run --rm` so checks still execute. PHPUnit is not
|
||||||
|
run in the hook; CI covers that.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Docker images are published to `ghcr.io/marijndoeve/tijdvoordetest`
|
||||||
|
for each tagged release.
|
||||||
|
|
||||||
|
### First-time setup
|
||||||
|
|
||||||
|
1. Copy `compose.yaml` and `compose.prod.yaml` to your server.
|
||||||
|
2. Create a `.env.prod.local` file with the required variables (see below).
|
||||||
|
3. Start the stack — migrations run automatically on container start:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
IMAGE_TAG=latest docker compose -f compose.yaml -f compose.prod.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating to a new version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
IMAGE_TAG=<tag> docker compose -f compose.yaml -f compose.prod.yaml pull
|
||||||
|
IMAGE_TAG=<tag> docker compose -f compose.yaml -f compose.prod.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Required environment variables
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------------------------|---------------------------------------------|
|
||||||
|
| `IMAGE_TAG` | Image tag to run (e.g. `1.2.3` or `latest`) |
|
||||||
|
| `APP_SECRET` | Random secret string for Symfony |
|
||||||
|
| `CADDY_MERCURE_JWT_SECRET` | JWT secret for the Mercure hub |
|
||||||
|
| `POSTGRES_PASSWORD` | PostgreSQL password |
|
||||||
|
| `MAILER_DSN` | Mailer transport DSN |
|
||||||
|
| `MAILER_SENDER` | From address for emails |
|
||||||
|
| `SENTRY_DSN` | Sentry project DSN (optional) |
|
||||||
|
|
||||||
|
The `compose.prod.yaml` configures Traefik labels for TLS termination at
|
||||||
|
`tijdvoordetest.nl`. Adjust the `traefik` labels in that file if you're
|
||||||
|
hosting on a different domain or using a different reverse proxy.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|||||||
Reference in New Issue
Block a user