Skip to content

Commit 356b369

Browse files
authored
docs(self-hosting): the app calls functions now, not tables (#1000)
* docs(self-hosting): the app calls functions now, not tables Checked against origin/release/2.2.0 and against sql/schema.sql in the backend repo. Most of the page holds: both env var names, `just build`, the five selectable food sources, the eight translation locales and the machine- translation disclosure all match the code exactly. The query mechanism does not. - **"The app reads exactly two relations" is no longer how it works.** There is not a single `.from(` left in sp_food_data_source.dart -- every read goes through one of five Postgres functions over RPC. That was a privacy change (#882): a PostgREST GET puts the search term in the URL where the API gateway logs it, an RPC carries it in the body. Two of the five, added for #864, read food_portion and food_portion_translation, so the app's required surface is two relations plus five functions over four tables. - **The text-search configurations moved.** `english` and `simple` now live inside the functions, next to the indexes that have to agree with them, rather than being passed by the app. - **Troubleshooting missed the likeliest failure and misattributed another.** A database built from a pre-RPC schema has no search functions, or has them without the execute grants -- schema.sql revokes from public and grants to anon/authenticated explicitly -- so a correctly populated database returns nothing. That is now the first item, with a pointer to sql/migrations/ for self-hosters upgrading in place. Access to food_summary was also attributed to RLS; a materialized view has no RLS, and schema.sql says so, restricting it with a grant instead. Refs #991 * docs(self-hosting): a missing index is slow, not wrong Copilot: indexes do not affect query correctness, so listing them among the causes of 'no results' pointed a self-hoster at the wrong thing. A sequential scan returns the same rows; what a missing index costs is time, and on a large table a timeout.
1 parent 8a47711 commit 356b369

1 file changed

Lines changed: 21 additions & 4 deletions

File tree

docs/supabase-self-hosting.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,19 @@ Users pick which sources they want to search in **Settings → Food databases**.
2424

2525
## What the app reads
2626

27-
The app reads exactly two relations from the backend schema; everything else in the backend repo (the per-source raw tables, the nutrient mapping, the import staging) is invisible to it. All names come from `SPConst` (`lib/features/add_meal/data/dto/sp/sp_const.dart`); the query code lives in `lib/features/add_meal/data/data_sources/sp_food_data_source.dart`.
27+
**The app does not query tables directly.** Every read goes through a Postgres function, called over RPC — there is not a single `.from(` left in `sp_food_data_source.dart`. That changed for privacy (#882): a PostgREST `GET` puts the search term in the URL, where the API gateway logs it, while an RPC carries it in the body.
28+
29+
Five functions are the whole of the app's required surface, all named in `SPConst` (`lib/features/add_meal/data/dto/sp/sp_const.dart`):
30+
31+
| Function | What it is for |
32+
|---|---|
33+
| `search_food_summary` | English name search |
34+
| `search_food_translation` | Localised name search |
35+
| `food_summary_by_ids` | Hydrate results, and re-read a saved food |
36+
| `portion_labels_by_food_ids` | Verified portion label in the reader's language (#864) |
37+
| `portions_by_food_ids` | Every usable portion, for the unit dropdown (#864) |
38+
39+
Behind them sit the two relations described below plus `food_portion` and `food_portion_translation`. Everything else in the backend repo — the per-source raw tables, the nutrient mapping, the import staging — stays invisible to the app. The calling code lives in `sp_food_data_source.dart`.
2840

2941
### `food_summary` — one flat row per food
3042

@@ -35,13 +47,13 @@ A materialized view with everything the app needs to render and log a food:
3547
- **Default serving**`serving_quantity`, `serving_unit`, `serving_size`, `serving_gram_weight`.
3648
- **Nutrients** — 24 canonical per-100g nutrient columns mirroring the app's `MealNutrimentsDBO` (energy, macros, extended lipids, minerals, vitamins).
3749

38-
English names in `food_summary.name` are searched with Postgres full-text search using the `english` configuration.
50+
English names in `food_summary.name` are searched with Postgres full-text search using the `english` configuration. That configuration now lives inside `search_food_summary` rather than in the app, next to the index that has to agree with it (`sql/schema.sql`, section 6b).
3951

4052
### `food_translation` — per-locale food names
4153

4254
One row per (food, locale) pair: `food_id`, `locale`, `description`, `source`. The app both **searches** this table for non-English locales and uses it to **label** foods in the UI. The `source` column records how the translation was produced — `native` (the original database carries the name, e.g. BLS German), `community`, `verified`, or `machine`. Machine translations (DeepL/LLM, produced by the backend repo's `translate_all.py`) are shown with a small disclosure hint in the app; human-sourced ones are not.
4355

44-
Supported locales are mapped in `SPConst.translationLocaleOf` — currently `de`, `pl`, `zh`, `cs`, `it`, `sk`, `tr`, and `uk`, with English reading `food_summary.name` directly. Translation search uses the `simple` text-search configuration, since the table holds many languages.
56+
Supported locales are mapped in `SPConst.translationLocaleOf` — currently `de`, `pl`, `zh`, `cs`, `it`, `sk`, `tr`, and `uk`, with English reading `food_summary.name` directly. Translation search uses the `simple` text-search configuration, since the table holds many languages — again inside `search_food_translation` rather than in the app.
4557

4658
## Setting up your own backend
4759

@@ -86,7 +98,12 @@ just build
8698

8799
That regenerates `lib/core/utils/env.g.dart` (which is gitignored) with the new values baked in. After that, a normal `flutter run` will pick them up. The app's `Supabase.initialize` call in `lib/core/utils/locator.dart` reads from `Env.supabaseProjectUrl` and `Env.supabaseProjectAnonKey`, so as long as the regenerated env file is in place you don't need to touch any other code.
88100

89-
To sanity-check the wiring, search for a common English food name (something like "apple raw") in the Add Meal screen. If you get backend results (rows with an FDC or BLS source chip), the database and the app are talking to each other. If you don't, the most common causes are: the RLS policies aren't in place (the anon role can't see the rows — `schema.sql` sets up public read, service_role write), the `food_summary` materialized view hasn't been refreshed after import (`import_fdc.py` does this automatically at the end of every run), or the full-text-search indexes are missing.
101+
To sanity-check the wiring, search for a common English food name (something like "apple raw") in the Add Meal screen. If you get backend results (rows with an FDC or BLS source chip), the database and the app are talking to each other. If you don't, the most likely causes, in order:
102+
103+
- **The search functions are missing, or `anon` cannot execute them.** This is the first thing to check on any database created before the RPC work, or from a partial schema run. `schema.sql` revokes execute from `public` and grants it to `anon` and `authenticated` explicitly, so a correctly populated database still returns nothing without those grants. An existing self-hosted copy needs the migrations in `sql/migrations/` applied — the search, portion-label and portions functions each arrived in one.
104+
- **The `food_summary` materialized view hasn't been refreshed** after import (`import_fdc.py` does this at the end of every run).
105+
- **Grants on the view are missing.** Note this is grants, not RLS: a materialized view has no row-level security, so `schema.sql` restricts it with `grant select on food_summary to anon, authenticated` instead. Base tables do use RLS — public read, `service_role` write.
106+
- **The full-text-search indexes are missing.** This one does not change the answer — a sequential scan returns the same rows — so it shows up as search that is slow, or that times out on a large table, rather than as no results.
90107

91108
## Attribution
92109

0 commit comments

Comments
 (0)