
The shape of the data, documented. Every table, column, enum, index and row-level-security policy below is transcribed straight from this site’s production migrations, not a sketch of how it might look. Read it as the contract the application is held to.
.jpg&w=3840&q=75)
The rules, stated once
Consistency is a security property. These six rules hold across the whole schema, so any table you open behaves the way the last one did.
Every table keys on a uuid_generate_v4() primary key. IDs are never guessable or enumerable, so a leaked URL reveals nothing about row counts or order.
Records carry a nullable deleted_at TIMESTAMPTZ. Deletion sets the timestamp; every public read filters deleted_at IS NULL. Nothing is ever physically removed, history stays recoverable and auditable.
Time is stored as TIMESTAMPTZ and defaulted with NOW() at the database, never as local strings. Africa/Johannesburg (UTC+2) is applied only at the display edge.
RLS is ENABLEd on all ten core tables. Public-facing reads are gated by explicit policies (active products, approved testimonials, published articles); private data (subscribers, quote leads) has no public policy at all and is reachable only via the service role.
Mutable tables (products, case_studies, articles) run a BEFORE UPDATE trigger calling update_updated_at(), so the column is always correct regardless of which code path wrote the row.
Prices and amounts are NUMERIC(10,2), never floating point, with a separate currency column. ZAR is the base; price_usd is nullable for the products that publish one.
.jpg&w=3840&q=75)
Controlled vocabularies
State lives in Postgres enums, not free text, so an order can only ever be one of four things, and an impossible status can’t be written at all.
product_status·products.status, drives the public RLS read (active/beta only) and the Coming-Soon UI state.
order_status·orders.status, set to paid only by the verified PayFast ITN handler.
quote_status·quote_requests.status, the lead lifecycle in the admin queue.
product_tier·products.tier, the three-tier pricing model.
billing_type·products.billing_type.
.jpg&w=3840&q=75)
This site’s database
Six of the ten core tables, in full. Each opens with its row-level-security posture in plain language, because on this platform, what a row is matters less than who is allowed to read it. (subscribers, quote_requests, products, orders and referrals are transcribed verbatim from migration 001.)
subscribersNewsletter and nurture-sequence membership for The Mirembe Brief. No public read policy, service-role only.
RLSRLS on. No public policy whatsoever, all access is via the service role or SECURITY DEFINER functions, so emails can never be read from the client.
| Column | Type | Notes |
|---|---|---|
idPK | UUID | uuid_generate_v4() |
emailUNIQUENOT NULLINDEX | TEXT | One row per address |
source | TEXT | Capture point: 'homepage', 'quote_form', 'build_shop'… |
sequenceNOT NULL | TEXT | Which nurture track; default 'mirembe_brief' |
sequence_stepINDEX | INTEGER | Position in the track; partial index where unsubscribed_at IS NULL |
languageNOT NULL | TEXT | Locale for sends; default 'en' |
confirmed_at | TIMESTAMPTZ | Double opt-in confirmation |
unsubscribed_at | TIMESTAMPTZ | Suppresses all further sends |
created_atNOT NULL | TIMESTAMPTZ | DEFAULT NOW() |
Indexes
quote_requestsInbound project briefs from /quote, the top of the consulting funnel.
RLSRLS on. Public INSERT only (lead capture); no public read. The admin queue reads via the service role.
| Column | Type | Notes |
|---|---|---|
idPK | UUID | uuid_generate_v4() |
nameNOT NULL | TEXT | — |
emailNOT NULLINDEX | TEXT | Indexed for lookup |
service_wingNOT NULL | TEXT | Which wing the brief is for |
project_typeNOT NULL | TEXT | — |
budget_range | TEXT | Self-reported band |
timeline | TEXT | Desired delivery window |
descriptionNOT NULL | TEXT | The brief itself |
statusNOT NULLINDEX | quote_status | Lifecycle enum; default 'pending' |
admin_notes | TEXT | Internal triage notes |
deleted_atsoft-delete | TIMESTAMPTZ | Soft delete |
Indexes
productsThe catalog, SaaS, digital products and courses across the ecosystem.
RLSRLS on. Public read is policy-gated to status IN ('active','beta') AND deleted_at IS NULL, so coming-soon and archived rows are invisible to the client.
| Column | Type | Notes |
|---|---|---|
idPK | UUID | uuid_generate_v4() |
slugUNIQUENOT NULLINDEX | TEXT | URL key |
nameNOT NULL | TEXT | — |
tierNOT NULL | product_tier | access | professional | enterprise |
price_zarNOT NULL | NUMERIC(10,2) | Base price |
price_usd | NUMERIC(10,2) | Optional published USD |
billing_typeNOT NULL | billing_type | Default 'monthly' |
statusNOT NULLINDEX | product_status | Drives the public read policy |
featuresJSONBNOT NULL | JSONB | Feature list; default '[]' |
updated_atNOT NULL | TIMESTAMPTZ | Maintained by trigger |
deleted_atsoft-delete | TIMESTAMPTZ | Soft delete |
Indexes
ordersPurchases and deposits. Written by the visitor, confirmed only by the payment webhook.
RLSRLS on. Users may read and insert only their own rows (auth.uid() = user_id). The paid status is set exclusively by the service-role PayFast ITN handler, never trusted from the client.
| Column | Type | Notes |
|---|---|---|
idPK | UUID | uuid_generate_v4() |
user_idFKNOT NULLINDEX | UUID | → auth.users(id); RLS boundary |
product_idFKNOT NULL | UUID | → products(id) |
amount_zarNOT NULL | NUMERIC(10,2) | — |
currencyNOT NULL | TEXT | Default 'ZAR' |
payment_providerNOT NULL | payment_provider | payfast (canonical) |
payment_reference | TEXT | Gateway reference |
statusNOT NULLINDEX | order_status | Default 'pending'; paid only via verified ITN |
referral_code | TEXT | Attribution to a referral |
discount_appliedNOT NULL | NUMERIC(10,2) | Referral discount; default 0 |
deleted_atsoft-delete | TIMESTAMPTZ | Soft delete |
Indexes
case_studiesPublished client work. The /case-studies index and detail pages, and the homepage proof strip, read from here.
RLSRLS on. Public read is gated to published_at IS NOT NULL AND published_at <= NOW() AND deleted_at IS NULL, so a draft, a future-dated piece, or a deleted one is never visible to the client.
| Column | Type | Notes |
|---|---|---|
idPK | UUID | uuid_generate_v4() |
slugUNIQUENOT NULLINDEX | TEXT | URL key |
titleNOT NULL | TEXT | — |
client_nameNOT NULL | TEXT | — |
excerptNOT NULL | TEXT | Card + meta summary |
body | TEXT | Full write-up |
service_wingNOT NULL | TEXT | Which wing delivered it |
resultsJSONBNOT NULL | JSONB | Metric→value pairs; default '{}' |
featuredNOT NULL | BOOLEAN | Homepage pull; default false |
published_atINDEX | TIMESTAMPTZ | Gates the public read policy |
updated_atNOT NULL | TIMESTAMPTZ | Maintained by trigger |
deleted_atsoft-delete | TIMESTAMPTZ | Soft delete |
Indexes
referralsThe public referral programme, R2,000 credit to the referrer, 5% off for the referee.
RLSRLS on. A user can read only their own referrals (auth.uid() = referrer_user_id). Codes are globally unique.
| Column | Type | Notes |
|---|---|---|
idPK | UUID | uuid_generate_v4() |
referrer_user_idFKNOT NULLINDEX | UUID | → auth.users(id); RLS boundary |
referee_emailNOT NULL | TEXT | Invited address |
codeUNIQUENOT NULLINDEX | TEXT | Shareable code |
credit_zarNOT NULL | NUMERIC(10,2) | Referrer credit; default 2000 |
discount_percentNOT NULL | NUMERIC(5,2) | Referee discount; default 5 |
redeemed_at | TIMESTAMPTZ | When the referee converted |
expires_atNOT NULL | TIMESTAMPTZ | DEFAULT NOW() + 90 days |
Indexes
Also in the schema: contact_messages, articles, service_bookings and the testimonials table (extended across later phases with status, video and source/consent governance columns), same conventions, same RLS discipline.

Across the ecosystem
Each product carries its own database with the same discipline. The figures below are drawn from each product’s build journals; open a product spec for its full architecture diagram and decisions.
149 tables · 391 RLS policies · 220 indexes · 88 migrations
86 tables · all under RLS · 147 policies · 43 migrations
1 Supabase table + ~15 localStorage stores
25 tables · 53 migrations · 8 stored RPCs
25 tables · RLS + policies on all 25 · 8 enums · 32 migrations
19 tables · PostGIS · 8 migrations · 13 RPCs
19 tables across 6 modular schema files · region-pinned jnb1
26 tables · 26 indexes · RLS on all 26 · 50 policies · trigger-driven
.jpg&w=3840&q=75)
Every client build ships with this same data dictionary, you own the schema, the migrations and the docs.