Files
menulio/supabase/migrations/20260819000000_initial_schema.sql
T
2026-08-20 01:51:59 +03:00

283 lines
10 KiB
SQL

-- Menulio initial schema (PRD §22, §21)
-- User -> Restaurant -> Location kept as separate tables from day one even
-- though V1 UX is 1:1:1, so multi-location support (V2) needs no migration.
create extension if not exists "pgcrypto";
-- ---------------------------------------------------------------------------
-- Enums
-- ---------------------------------------------------------------------------
create type subscription_status as enum ('active', 'grace_period', 'suspended', 'deleted');
create type ai_import_status as enum ('pending', 'processing', 'completed', 'failed');
create type domain_status as enum ('pending', 'verified', 'failed');
-- ---------------------------------------------------------------------------
-- Core identity
-- ---------------------------------------------------------------------------
create table users (
id uuid primary key references auth.users (id) on delete cascade,
full_name text,
created_at timestamptz not null default now()
);
create table restaurants (
id uuid primary key default gen_random_uuid(),
name text not null,
slug text not null unique,
logo_url text,
phone text,
address text,
created_by uuid not null references users (id) on delete restrict,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table restaurant_members (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
user_id uuid not null references users (id) on delete cascade,
role text not null default 'owner',
created_at timestamptz not null default now(),
unique (restaurant_id, user_id)
);
create table locations (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
name text not null,
address text,
phone text,
created_at timestamptz not null default now()
);
-- ---------------------------------------------------------------------------
-- Menu
-- ---------------------------------------------------------------------------
create table menus (
id uuid primary key default gen_random_uuid(),
location_id uuid not null references locations (id) on delete cascade,
name text not null default 'Menü',
is_published boolean not null default false,
published_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table menu_categories (
id uuid primary key default gen_random_uuid(),
menu_id uuid not null references menus (id) on delete cascade,
name text not null,
description text,
sort_order integer not null default 0,
is_active boolean not null default true,
created_at timestamptz not null default now()
);
create table menu_items (
id uuid primary key default gen_random_uuid(),
category_id uuid not null references menu_categories (id) on delete cascade,
name text not null,
description text,
price numeric(10, 2) not null,
image_url text,
sort_order integer not null default 0,
is_active boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table menu_item_options (
id uuid primary key default gen_random_uuid(),
menu_item_id uuid not null references menu_items (id) on delete cascade,
group_name text not null,
label text not null,
price_delta numeric(10, 2) not null default 0,
sort_order integer not null default 0
);
-- ---------------------------------------------------------------------------
-- Theming
-- ---------------------------------------------------------------------------
create table themes (
id uuid primary key default gen_random_uuid(),
key text not null unique,
name text not null,
config jsonb not null default '{}'::jsonb
);
create table restaurant_themes (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
theme_id uuid not null references themes (id) on delete restrict,
overrides jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
unique (restaurant_id)
);
-- ---------------------------------------------------------------------------
-- Domains & QR
-- ---------------------------------------------------------------------------
create table domains (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
hostname text not null unique,
is_custom boolean not null default false,
status domain_status not null default 'pending',
verified_at timestamptz,
created_at timestamptz not null default now()
);
create table qr_codes (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
target_url text not null,
created_at timestamptz not null default now()
);
-- ---------------------------------------------------------------------------
-- AI import
-- ---------------------------------------------------------------------------
create table ai_imports (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
source_image text not null,
status ai_import_status not null default 'pending',
model text,
raw_response jsonb,
error text,
processed_at timestamptz,
created_at timestamptz not null default now()
);
create table ai_import_items (
id uuid primary key default gen_random_uuid(),
ai_import_id uuid not null references ai_imports (id) on delete cascade,
category_name text,
item_name text not null,
description text,
price numeric(10, 2),
confidence numeric(4, 3),
created_at timestamptz not null default now()
);
-- ---------------------------------------------------------------------------
-- Subscription
-- ---------------------------------------------------------------------------
create table subscriptions (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
revenuecat_entitlement text not null default 'pro',
status subscription_status not null default 'active',
product_id text,
trial_ends_at timestamptz,
current_period_ends_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (restaurant_id)
);
-- ---------------------------------------------------------------------------
-- Analytics & media
-- ---------------------------------------------------------------------------
create table analytics_events (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
event_type text not null,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now()
);
create table media (
id uuid primary key default gen_random_uuid(),
restaurant_id uuid not null references restaurants (id) on delete cascade,
storage_path text not null,
kind text not null default 'image',
created_at timestamptz not null default now()
);
-- ---------------------------------------------------------------------------
-- Indexes
-- ---------------------------------------------------------------------------
create index on restaurant_members (user_id);
create index on locations (restaurant_id);
create index on menus (location_id);
create index on menu_categories (menu_id);
create index on menu_items (category_id);
create index on menu_item_options (menu_item_id);
create index on domains (restaurant_id);
create index on ai_imports (restaurant_id);
create index on ai_import_items (ai_import_id);
create index on analytics_events (restaurant_id, created_at);
create index on media (restaurant_id);
-- ---------------------------------------------------------------------------
-- Row Level Security
-- ---------------------------------------------------------------------------
-- Business logic lives in the API layer (PRD §23), so most tables are locked
-- to the service role. Owner-facing tables get membership-scoped policies for
-- direct client reads; public menu tables get an anon read policy scoped to
-- published menus only.
alter table users enable row level security;
alter table restaurants enable row level security;
alter table restaurant_members enable row level security;
alter table locations enable row level security;
alter table menus enable row level security;
alter table menu_categories enable row level security;
alter table menu_items enable row level security;
alter table menu_item_options enable row level security;
alter table themes enable row level security;
alter table restaurant_themes enable row level security;
alter table domains enable row level security;
alter table qr_codes enable row level security;
alter table ai_imports enable row level security;
alter table ai_import_items enable row level security;
alter table subscriptions enable row level security;
alter table analytics_events enable row level security;
alter table media enable row level security;
create policy "users read own row" on users
for select using (id = auth.uid());
create policy "members read own restaurants" on restaurants
for select using (
exists (
select 1 from restaurant_members rm
where rm.restaurant_id = restaurants.id and rm.user_id = auth.uid()
)
);
create policy "members read own membership rows" on restaurant_members
for select using (user_id = auth.uid());
create policy "anyone can read published menus" on menus
for select using (is_published = true);
create policy "anyone can read categories of published menus" on menu_categories
for select using (
exists (
select 1 from menus m where m.id = menu_categories.menu_id and m.is_published = true
)
);
create policy "anyone can read items of published menus" on menu_items
for select using (
exists (
select 1
from menu_categories mc
join menus m on m.id = mc.menu_id
where mc.id = menu_items.category_id and m.is_published = true
)
);
create policy "themes are public" on themes for select using (true);