The content library is studio-wide material the trainee reads or watches in the app: recipes, videos, knowledge articles, PDFs and links. One optional banner per studio sits at the top of the trainee home screen.

ContentItem

Table content_items. Each macro value is validated as a number from 0 to 1000000, or null. Indexes: studioId, (studioId, type), (studioId, isFeatured), categoryId, (studioId, sortOrder, createdAt). Relations: category, tags (through ContentItemTag), homeBanners, unlocks.

steps

An array validated by recipeStep:
type is step or tip. text is required.

ContentCategory

Table content_categories. Unique on (studioId, name) and (studioId, systemKey). Every new studio gets the three default categories from defaultContentCategories(locale), named in the studio’s locale (Hebrew, English, French, German, Spanish and Arabic names are defined). They are ordinary editable rows. systemKey is how code finds them after a rename. permanentCategoryForType(type) maps an item type to its default category: recipe and video to their own, everything else to knowledge.

ContentTag and ContentItemTag

Tags are studio-wide. ContentTag (table content_tags): studioId, name (unique per studio), sortOrder, timestamps. ContentItemTag (table content_item_tags) is the join table. Its primary key is the pair (itemId, tagId). Both sides cascade. There is an index on tagId.

ContentItemUnlock

Table content_item_unlocks. A restricted item revealed to one trainee. Unique on (itemId, clientId).

HomeBanner

Table home_banners. One banner per studio: studioId is unique. The value lists come from home-banner/home-banner.schema.ts. They are plain strings in the database.

Media

Content media and thumbnails are uploaded to object storage and referenced by URL. A thumbnail generated automatically from a video is marked in its storage key so it can be told apart from one the coach uploaded. See File storage.