Daily Taqwa
Tamper-resistant prayer attendance system for ~2,000 OIKN government employees at Masjid Negara IKN

- OIKN employees served
- ~2,000
- check-ins in five minutes, designed for
- ~500
- daily prayers tracked per employee
- 5
- leaderboards, including dawn prayer and directorate
- 4
On this page
The problem
Otorita IKN wanted to encourage its roughly 2,000 civil servants to pray at Masjid Negara IKN, and to know whether they did. Paper sign-in sheets could be filled in afterwards. A simple digital check-in could be faked from outside the mosque. HR leadership also wanted to see attendance trends across directorates, beyond a list of who showed up.
The timing made it harder than a normal attendance system. Prayer windows are short, and check-ins pile up in the few minutes around each call to prayer. The location check also had to hold up against someone who was trying to fake it.
What I built
Only the mobile app can check in
A browser can't prove its GPS reading came from the phone's own hardware. Free fake-GPS apps and browser tools beat a web-only geofence, however careful the server is. So I split the system. The native mobile app is the only client allowed to submit a check-in. It captures a liveness check and reads GPS with the phone's own fake-location detection. The web portal shows prayer times, rankings, profiles, release notes and the admin panel. Both talk to one backend, which holds every rule.
The server owns the clock
The server calculates each prayer window, caches it, and checks it again at the moment a check-in arrives. The phone never sends a time that counts. IKN runs on Central Indonesia Time, eight hours ahead of UTC. A dawn check-in at 5:00 local time falls on the previous day in UTC. I made every date in the database use local time, so dawn and night prayers never land on the wrong day.
One check-in, one step, no duplicates
A check-in does everything in one step: it uses up a one-time code, checks the prayer window, the geofence, the travel speed since the last check-in and the liveness capture, then saves the record. If any part fails, nothing is saved. The database refuses a second record for the same person, day and prayer, so two taps at once can't create two entries. Daily, monthly and directorate totals update in that same step, so they never drift from the records.
One login for web and mobile
Both apps sign in through OIKN's existing single sign-on. The web uses the standard browser redirect. The mobile app signs in directly, then trades that login for its own session with the backend. I map each employee's ID number, work unit and gender from the login to their profile. Some HR systems send ID numbers in odd formats, and contract and military staff use non-standard ones, so I accept those too.
Rankings, history and tools HR asked for
There are four leaderboards: monthly by gender, yearly, dawn prayer only, and by directorate. Your own rank stays pinned at the bottom if you're outside the top ten. A monthly grid colours all five prayers for each day. Launch brought needs the spec didn't have. The login sends no gender, so people can correct gender and directorate on their profile, and the next login keeps their fix. HR can upload the old paper records from a web page, and admins can see web and mobile logins separately to track app adoption.
Result
Daily Taqwa is in production at OIKN. The mobile app is live and is the only channel for check-ins. The web portal runs rankings and administration on the same backend. It serves about 2,000 employees and is designed for about 500 check-ins in a five-minute midday window. I built it between April and June 2026. The scope grew past the original spec: single sign-on replaced local accounts, and the HR tools above came from launch feedback.
Under the hoodTechnical detail for engineers
- Prayer windows are computed with the Python
praytimeslibrary and cached in Redis. Aget_wita_date()helper converts every attendance date to WITA (UTC+8) before it hits a date-keyed constraint. - The check-in service runs in one
@transaction.atomicblock withSELECT FOR UPDATEon the user row: nonce consumption, window validation, Haversine geofence with a configurable radius, velocity check, liveness sanity, photo save and aggregate updates. UNIQUE(user_id, date, prayer)is the hard guard against races. The service catchesIntegrityErrorand returns "already checked in". Nonces are single-use with a two-minute TTL.- Web SSO is Keycloak PKCE via
mozilla-django-oidc. Mobile uses Direct Access Grant: it gets a Keycloakid_tokenand exchanges it at a backend endpoint for SimpleJWT tokens. Claims (NIP, unit_kerja, gender) map to the user model with fuzzy eselon matching. NIPs arriving as floats are coerced to strings. - A
profile_self_editedflag stops the OIDC sync from overwriting a user's corrected directorate on the next login.last_login_webandlast_login_mobilereplace Django's singlelast_login. - Face matching with
dlibwas descoped to liveness-only. The server checks frames are present and not identical;face-api.jsruns the liveness challenge on the client. - Photos are stripped of EXIF with Pillow and served only through an authenticated endpoint. Presigned MinIO URLs pointed at an internal Docker hostname, so the endpoint streams the bytes.
import_backfill_csvimports the paper records idempotently with--appendand merges SSO accounts by NIP. It is also wrapped in an admin upload page.- The admin panel has a Leaflet geofence editor, per-prayer window settings, bulk Excel user import, an attendance log with photo preview, and an audit log.
- About 15 models, about 30 API endpoints, 23 frontend page files, about 18,800 lines of Python and TypeScript, 85 commits.
Screenshots
Hiring, or building something similar?
I'm open to full-time roles and select freelance work.