AN-006 · Software · Finished
Started Jul 2026
海那边: a bilingual tutoring portal
1.0Abstract
A Next.js portal I built and deployed for a Chinese-language tutor and the parents of her students, live at hainabian.org. Parents can see how many lessons they have left, get bank-transfer invoices by email and book through a Google appointment schedule, in English or Chinese. Lesson credits are kept in an immutable ledger with database-level guards.
At a glance
| Parameter | Value |
|---|---|
| Status | Finished. Live at hainabian.org |
| When and where | 2026, for a Chinese-language tutor and the parents of her students |
| My part | Built and deployed it |
| Built with | Next.js and TypeScript; PostgreSQL with Drizzle; Better Auth; Docker Compose behind a Cloudflare Tunnel |
| Code | github.com/zhaojinchu/HaiNaBian |
| Live | hainabian.org |
| Lesson credits | One immutable ledger entry per lesson; voids add a restoring entry; database-level guards reject edits, deletions and negative balances |
| Money | AED stored as integer fils; invoices generated, saved and emailed for bank transfer; payment status set by hand |
| Booking | A Google Appointment Schedule embedded read-only, so Google owns availability and conflicts |
The problem
A tutor and the parents of her students need three answers: how many lessons are left, who has paid, and when the next slot is. The portal gives each approved parent one page with those answers and gives the teacher one admin page to manage all of it. It intentionally does not integrate a bank or the Google Calendar API.
Design decisions
- Credits are a ledger, not a counter. Recording a lesson consumes one immutable entry per learner; voiding it adds a restoring entry. Migrations add database-level guards so a balance can never go negative and a ledger row can never be edited or deleted, even by a bug.
- Payment and usage are independent. A package can be used before it is paid for; the teacher marks payment when the transfer lands. That matches how the business actually runs.
- Google owns booking. The public appointment schedule is embedded; nothing is copied into Postgres, so there is no second source of truth to drift.
- Boring, safe production. A separate Compose stack with strict environment validation, health checks that know about the database, and a Cloudflare Tunnel instead of open ports.