Aplikasi kehadiran KTM ambil peti undi (SPR 2026)

Sistem untuk admin DUN merekod waktu KTM (Ketua Tempat Mengundi) hadir
mengambil peti undi. Data KTM dibaca daripada DB sppm (spr2026) melalui
connection kedua — hanya penempatan berjawatan KTM yang dipaparkan.

- Dashboard: senarai KTM ikut pusat mengundi (asc) & saluran (asc),
  status BELUM HADIR (merah) / HADIR (hijau) + tarikh & masa
- Carian nama/no. KP, tapisan pusat mengundi & status
- Tanda hadir / batal dengan kawalan DUN (admin terhad ke DUN sendiri)
- Eksport Excel (worksheet Hadir & Belum Hadir) guna SimpleXlsx
- Peranan: superadmin (urus pengguna, semua DUN) & admin DUN
- Docker: nginx + php-fpm + queue + scheduler + webhook deploy,
  port 127.0.0.1:8009, TZ Asia/Kuala_Lumpur di semua container
- Zon waktu aplikasi: Asia/Kuala_Lumpur (APP_TIMEZONE)

Berasaskan scaffold spr2026_taklimat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Saufi
2026-07-11 02:40:00 +08:00
commit dfd22ff2af
95 changed files with 14681 additions and 0 deletions

93
docker-compose.yml Normal file
View File

@@ -0,0 +1,93 @@
# Docker stack for "Kehadiran KTM SPR" (Laravel 13 / PHP 8.4)
#
# Topology:
# web (nginx) -> exposes 127.0.0.1:8009, host nginx reverse-proxies the
# domain http://ktmspr.apps.mbip.my to it.
# app (fpm) -> PHP 8.4-FPM, runs migrations on boot.
# queue -> database queue worker.
# scheduler -> Laravel scheduler.
#
# Databases (sppm_ktm + the existing sppm DB) live on the HOST MySQL and
# are reached via host.docker.internal (see extra_hosts below + DOCKER.md).
x-app-build: &app-build
context: .
dockerfile: docker/Dockerfile
target: app
x-app-common: &app-common
build: *app-build
image: ktmspr-app:latest
restart: unless-stopped
env_file:
- .env.docker
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- app_storage:/var/www/html/storage
services:
app:
<<: *app-common
environment:
RUN_MIGRATIONS: "true"
TZ: Asia/Kuala_Lumpur
APP_TIMEZONE: Asia/Kuala_Lumpur
web:
build:
context: .
dockerfile: docker/Dockerfile
target: web
image: ktmspr-web:latest
restart: unless-stopped
environment:
TZ: Asia/Kuala_Lumpur
depends_on:
- app
ports:
# Bind to loopback only; the host nginx terminates the public domain.
- "127.0.0.1:8009:80"
queue:
<<: *app-common
environment:
RUN_MIGRATIONS: "false"
TZ: Asia/Kuala_Lumpur
APP_TIMEZONE: Asia/Kuala_Lumpur
command: php artisan queue:work --sleep=3 --tries=3 --max-time=3600
depends_on:
- app
scheduler:
<<: *app-common
environment:
RUN_MIGRATIONS: "false"
TZ: Asia/Kuala_Lumpur
APP_TIMEZONE: Asia/Kuala_Lumpur
command: php artisan schedule:work
depends_on:
- app
# Webhook deploy (GitHub push -> git pull + rebuild). Dengar 127.0.0.1:9002;
# host nginx reverse-proxy /hooks/ ke sini. WEBHOOK_SECRET dari .env.docker.
webhook:
build:
context: ./docker/webhook
image: ktmspr-webhook:latest
restart: unless-stopped
env_file:
- .env.docker
ports:
- "127.0.0.1:9002:9000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./docker/webhook/hooks.json:/etc/webhook/hooks.json:ro
- ./deploy.sh:/deploy.sh:ro
- .:/srv/spr2026_ktm
# Kunci SSH host utk `git pull` repo peribadi (abaikan jika repo awam/HTTPS).
- /root/.ssh:/root/.ssh:ro
command: -hooks=/etc/webhook/hooks.json -hotreload -verbose
volumes:
app_storage: