Skip to content

Commit 346c0b2

Browse files
committed
document listing read model naming
1 parent d5a64b9 commit 346c0b2

1 file changed

Lines changed: 56 additions & 0 deletions

File tree

docs/supabase-data-architecture.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,42 @@ The main public tables are split into two groups:
1515
- Source-of-truth tables that store real application records.
1616
- Read-model tables that expose deliberately shaped data to a specific audience.
1717

18+
## Naming
19+
20+
### Source tables
21+
22+
Source tables are the canonical records. They are the place writes happen and
23+
the place the application should treat as the source of truth.
24+
25+
For example, `public.listings.location` is the canonical listing location.
26+
Other tables may copy or reshape that location for safe reads, but they do not
27+
become the source of truth.
28+
29+
### Read models
30+
31+
A read model is a table shaped for a read use case rather than for canonical
32+
storage. Peels uses read models when different audiences need different columns
33+
from the same underlying records.
34+
35+
This avoids using base tables as both storage and public API. The base table can
36+
keep all fields needed by the product, while each read model exposes only the
37+
fields needed by its audience.
38+
39+
### `contact_cards`
40+
41+
`contact_cards` is Peels naming, not a Postgres or Supabase convention. It means
42+
"small safe display/contact summary".
43+
44+
The important part is the audience boundary:
45+
46+
- `public_listings` is for signed-out public browsing and SEO.
47+
- `listing_contact_cards` is for signed-in listing/contact/chat flows.
48+
- `profile_contact_cards` is for signed-in profile display in allowed
49+
relationships, especially chat participants.
50+
51+
If a future table needs a different audience or purpose, prefer a name that says
52+
that audience clearly rather than blindly reusing `contact_cards`.
53+
1854
### `private`
1955

2056
`private` is not an exposed Data API schema. It is for database implementation details such as trigger functions, security-definer helpers, and internal-only tables.
@@ -77,6 +113,26 @@ Residential listings keep their description, accepted items, and rejected items
77113

78114
Rows are maintained by database triggers from `public.listings`.
79115

116+
#### `owner_has_multiple_non_residential_listings`
117+
118+
This boolean means: the hidden owner of this listing has more than one visible
119+
business/community listing.
120+
121+
It exists for presentation copy. In chat, if a person reaches out to an owner
122+
who has multiple non-residential listings, the UI can say they reached out about
123+
a specific listing name rather than using more ambiguous generic copy.
124+
125+
The field is low sensitivity because it does not expose the owner id, owner
126+
name, email, profile, or the list of the owner's other listings. It reveals only
127+
that this listing's owner has multiple visible non-residential listings.
128+
129+
Current app code mainly needs this field through `listing_contact_cards` for
130+
authenticated chat flows. It remains on `public_listings` because it was already
131+
part of the public listing read shape and may be useful presentation metadata,
132+
but it is not essential to signed-out browsing today. If public minimisation
133+
becomes stricter later, this is a reasonable candidate to remove from
134+
`public_listings` while keeping it on `listing_contact_cards`.
135+
80136
### `public.listing_contact_cards`
81137

82138
Authenticated listing/contact read model for visible listings, own listings, and chat participant flows.

0 commit comments

Comments
 (0)