studioId = null is global and visible to every studio. A row with a studioId is that studio’s own. A studio never edits a global row. Its edits go to an override row keyed by studio and item, and reads merge the override over the shared row.
ExerciseLibraryItem
Table exercise_library_items.
Indexes:
studioId, (studioId, createdAt), trigram GIN on searchText and name, array GIN on targetMuscles, secondaryMuscles, bodyParts, exerciseTypes.
Relations: videoOverrides (ExerciseStudioOverride), sourceMaps (ExerciseSourceMap).
Catalog imports are scripts under packages/database/scripts/exercise-db-v2 and friends, run through the db:import:exercises-* commands in the root package.json.
ExerciseStudioOverride
Table exercise_video_overrides. A studio’s private edits to a library exercise.
The physical table keeps its original name. Renaming it would make prisma db push a drop and create, and the deploy does not pass --accept-data-loss.
Unique on
(studioId, exerciseId). Indexes on each.
Every scalar field is nullable and null means “inherit from the shared row”. Because the override keeps the same exerciseId, a rename applies to programs that were already assigned.
ExerciseSourceMap
Table exercise_source_maps. Resolves an external exercise to a global catalog row, so an import references the catalog and does not mint a studio-scoped copy.
Unique on
(source, sourceKey). The same exercise can be reachable through several keys. This table is global and has no studioId.
FoodLibraryItem
Table food_library_items.
Indexes:
studioId, (studioId, name), trigram GIN on name.
servingTiers
Validated by servingTierBody in foods/foods.schema.ts. The TypeScript type is ServingTier in foods/mbp.ts:
servingUnit is gram, ml or unit. householdMeasure is optional and one of cup, tsp, tbsp.
FoodLibraryOverride
Table food_library_overrides. A studio’s edits to a global food. System foods are editable per studio but cannot be deleted.
It repeats the editable columns of FoodLibraryItem: name, brand, category, categories, notes, betterAlternative, includeInShopping, showAsAlternative, servingSize, servingUnit, calories, protein, carbs, fat, macroType, servingTiers, englishName, the unit and household columns, isPreferred, and the three exchange quantities. Unlike the exercise override, name and the four macro columns are required, so a food override is a full replacement of those values.
Unique on (studioId, foodId). Indexes on each. Both relations cascade.
FoodBarcodeProduct
Table food_barcode_products. A cache of Open Food Facts products, keyed by barcode.
Global, no tenant. One scan anywhere fills it for every studio. Rows refresh lazily after
BARCODE_CACHE_TTL_MS, 30 days, in trainee.service.ts.
ShoppingListItem
Table shopping_list_items. The trainee’s shopping checklist.
Unique on
(clientId, foodId).