Gambaran Arsitektur
Catera adalah aplikasi single-page React yang di-build statis (Vite) dan dideploy sebagai SPA di Vercel. Backend aktifnya adalah Supabase (Postgres + Auth + Row Level Security + RPC + Edge Function). Sebuah jalur kompatibilitas Convex masih dipertahankan di kode untuk rollback, tapi tidak lagi jadi backend produksi default.
Diagram sistem
Section titled “Diagram sistem”flowchart TB
UI["Browser: aplikasi React"]
subgraph Supabase ["Supabase (backend aktif)"]
Auth["Auth"]
DB[("Postgres + RLS + RPC")]
EF["Edge Functions"]
end
ConvexBackend[("Convex (jalur rollback)")]
UI -->|"VITE_BACKEND_PROVIDER=supabase"| Auth
UI --> DB
UI --> EF
EF --> DB
UI -.->|"rollback saja"| ConvexBackend
Enam edge function yang berjalan di Supabase: ai-chat,
ai-daily-summary, ai-demand-prediction, connection-test,
portal-order-detail, dan push-notifications. Detail tiap fungsi ada di
Referensi Edge Function.
Pemilihan backend (feature flag)
Section titled “Pemilihan backend (feature flag)”Pemilihan backend ditentukan satu env var di build time, bukan deteksi
runtime: VITE_BACKEND_PROVIDER=supabase mengaktifkan jalur Supabase;
nilai lain jatuh ke Convex. Logikanya terpusat di satu modul kecil supaya
tidak ada cabang provider yang tersebar di seluruh kode:
export const isSupabaseBackend = backendProvider === "supabase";
export function selectBackend<TSupabase>(supabase: TSupabase, convex: unknown): TSupabase { return (isSupabaseBackend ? supabase : convex) as TSupabase;}src/App.tsx memakai flag yang sama untuk memilih antara memberi rute
publik (portal, katalog publik, display, kiosk) akses langsung ke Supabase
tanpa provider React apa pun (“providerless”), atau membungkusnya dengan
ConvexReactProvider saat jalur rollback aktif.
Routing & lapisan akses
Section titled “Routing & lapisan akses”- Rute publik (tanpa login):
/cek-pesanan,/portal*,/display/*,/katalog-publik,/pesan-publik,/kiosk/*. Dirender di luar layout aplikasi, tanpa chrome internal (sidebar, bottom nav). - Semua rute lain dibungkus
<AppLayout>dan tiap elemen rute individual dibungkus<RouteGuard>. RouteGuard(src/components/RouteGuard.tsx) menolak akses untuk: belum login, profil masihpending/blocked, atau role tidak terdaftar untuk path tersebut — lalu mengarahkan ke/pending-approvalatau/forbidden.- Keputusan akses per-path dibaca dari peta statis
ROUTE_ACCESSdisrc/lib/role-access.ts, dicocokkan dengan role yang sudah dinormalisasi lewatnormalizeUserRole()(src/lib/roles.ts).
Detail model peran dan lapisan keamanan di sisi database dijelaskan di Model Keamanan.
Auth & profil pengguna
Section titled “Auth & profil pengguna”Supabase Auth (auth.users) adalah sumber identitas. Trigger
on_auth_user_created otomatis membuat baris public.profiles berstatus
pending setiap kali ada user Auth baru — aktivasi role tetap aksi manual
oleh pemilik/manager, bukan otomatis. Tabel profiles menyimpan role
(termasuk alias peran lama) dan status (pending/active/blocked), dan
menjadi rujukan semua fungsi RLS lain lewat auth.uid().
Sumber: src/App.tsx, src/lib/backend-provider.ts,
src/lib/role-access.ts, src/lib/roles.ts,
src/components/RouteGuard.tsx,
supabase/migrations/0002_identity_and_rls.sql.