Minisites — Data Model
minisite_content
Defined in libs/db/src/lib/schema/minisites.ts. One row per organization (unique on organization_id).
| Column | Type | Purpose |
|---|---|---|
id | uuid PK | Row identity. |
organization_id | uuid FK → organizations.id, unique | One minisite per org. |
content | jsonb | Working draft — section list, theme overrides, etc. Edited by the dashboard autosave. |
published_content | jsonb nullable | Snapshot served to the public. Null until first publish. |
theme | jsonb | Default theme tokens: primaryColor, secondaryColor, fontFamily, buttonStyle. |
custom_domain | varchar(255) unique | Apex/subdomain owned by the gym (e.g. fit.gymname.com). |
subdomain | varchar(255) unique | *.fitkit.fit subdomain assigned by FitKit. |
seo_title | varchar(255) | <title> override. |
seo_description | text | Meta description. |
favicon_url | text | Custom favicon. |
is_published | boolean (default false) | Public visibility gate. |
created_at / updated_at | timestamp tz | Audit columns. |
Relations
minisite_content↔organizationsone-to-one. Deleting an org cascades the minisite row via the FK.
Live data (not persisted in this module)
The platformData payload composed by the resolve endpoint pulls from existing tables:
| Section | Source |
|---|---|
programs | programs |
upcomingSessions | class_sessions joined to class_types |
plans | plans |
courses | courses |
coaches | memberships filtered by role = 'coach' joined to users |
organization | organizations |
No copy of these lives in minisite_content — the minisite always sees fresh data.
Indexes & uniqueness
custom_domainandsubdomainare independently unique to support either resolution path.- Lookup happens by host header at request time — both columns are read; performance relies on the unique indexes.
Content shape (informal)
type PublishedContent = {
title?: string;
locale?: 'he' | 'en' | 'ru';
theme?: ThemeOverride;
sections: Array<{
type: 'hero' | 'about' | 'classes' | 'schedule' | 'pricing'
| 'courses' | 'trainers' | 'contact' | 'gallery' | 'testimonials' | 'faq';
order: number;
isEnabled: boolean;
data: Record<string, unknown>; // section-specific payload
}>;
founderStory?: { name: string; bio: string; photoUrl?: string };
};The Astro page treats data as opaque per-section JSON — schema is enforced in the dashboard editor.