Booking systems with smart-lock integration
Church venue booking & smart-lock access
Built pro bono for a large church. Its volunteer teams book rooms online; once a booking is approved, the system sends a time-limited passcode to the door lock, revokes it on expiry, and traces every unlock back to the passcode used. Currently in beta.
- ~1,600Volunteer staff as users, ~200 regular (in beta)
- 1,364Server-side tests, plus 151 in the admin app
- 29Permission capabilities, combined with roles and team hierarchy
- 11Scheduled jobs driven by a single job table
- Member books a room
- Admin approves (auto-reject on timeout)
- System issues a time-limited lock passcode
- Member unlocks the door
- Unlock log is traced to the passcode
- Passcode is revoked on expiry
Background
The church needed its volunteer teams to book rooms online, admins to approve, doors to open according to bookings, and a record of who opened what. I designed and built all of it, pro bono.
What I did
- Member app: SMS sign-up with admin review, WeChat login; rooms with time-slot occupancy; team and personal bookings; three tiers of edits (content only, shorten, reschedule — rescheduling needs re-approval); door passcodes; repair requests and room borrowing.
- Admin console: dashboard, approvals and booking on behalf of members; open, restricted and dedicated room modes; lock monitoring (status, logs, alarms, revocation); idle detection, team tree, reports, audit log, settings; a capability grid for permissions.
Problems and solutions
- Three kinds of passcodes: time-limited codes are issued on approval with buffers on both ends and revoked on cancellation or expiry; one-time codes have a validity window, a concurrency cap and a cooldown; dedicated rooms get standing codes valid for 48 hours and rotated every 24.
- Tracing unlocks: each unlock log is matched to the passcode that was valid at that moment, excluding standing codes already replaced and codes revoked before the unlock.
- Alarms vs. real unlocks: alarm records don’t count as someone entering, wrong-code attempts don’t count as use, and a failed sync or offline lock never auto-releases a booking.
- Concurrency: SQLite transactions can’t prevent check-then-write races, so work is serialized per key (room, team, …) in-process, with a single allowed nesting order that rules out deadlocks by construction.
- Secrets: secrets are stored encrypted and never sent to the browser; lock passcodes use AES-256-CBC; production refuses to start with short keys.
- Trade-offs: PostgreSQL, Redis and ELK were evaluated and deliberately left out — a single SQLite instance with backups fits this scale, and the reasoning is documented.
Stack
React 19 · Vite · TypeScript · Tailwind · Zustand · Express · Prisma · SQLite · Tuya OpenAPI · WeChat · Aliyun SMS · Vitest · Docker · Caddy
Screenshots


