File size: 6,154 Bytes
16e1aa7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | -- Etsy Listing Optimizer - Supabase (Postgres) schema
-- Run this in the Supabase SQL editor for your project (see DEPLOY.md).
-- Assumes Supabase Auth is enabled; auth.users is Supabase's built-in table.
-- ---------------------------------------------------------------------------
-- profiles: one row per auth user, holds subscription/tier state
-- ---------------------------------------------------------------------------
create table if not exists public.profiles (
user_id uuid primary key references auth.users (id) on delete cascade,
email text not null,
tier text not null default 'free' check (tier in ('free', 'starter', 'pro', 'business')),
stripe_customer_id text,
stripe_subscription_id text,
stripe_subscription_status text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
alter table public.profiles enable row level security;
create policy "profiles_select_own" on public.profiles
for select using (auth.uid() = user_id);
create policy "profiles_update_own" on public.profiles
for update using (auth.uid() = user_id);
-- Inserts happen via a service-role backend call on signup, not directly by
-- the client, so no client-facing insert policy is defined here.
-- ---------------------------------------------------------------------------
-- generation_counters: one row per user per calendar month
-- ---------------------------------------------------------------------------
create table if not exists public.generation_counters (
user_id uuid not null references auth.users (id) on delete cascade,
year_month text not null, -- e.g. '2026-07'
count integer not null default 0,
updated_at timestamptz not null default now(),
primary key (user_id, year_month)
);
alter table public.generation_counters enable row level security;
create policy "counters_select_own" on public.generation_counters
for select using (auth.uid() = user_id);
-- Writes to counters go through the backend (service role) so quota can't
-- be tampered with by the client; no client insert/update policy.
-- ---------------------------------------------------------------------------
-- brand_voice_profiles
-- ---------------------------------------------------------------------------
create table if not exists public.brand_voice_profiles (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users (id) on delete cascade,
name text not null,
tone text,
target_audience text,
favorite_words text,
created_at timestamptz not null default now()
);
alter table public.brand_voice_profiles enable row level security;
create policy "brand_voice_select_own" on public.brand_voice_profiles
for select using (auth.uid() = user_id);
create policy "brand_voice_insert_own" on public.brand_voice_profiles
for insert with check (auth.uid() = user_id);
create policy "brand_voice_update_own" on public.brand_voice_profiles
for update using (auth.uid() = user_id);
create policy "brand_voice_delete_own" on public.brand_voice_profiles
for delete using (auth.uid() = user_id);
-- ---------------------------------------------------------------------------
-- listings: generation history
-- ---------------------------------------------------------------------------
create table if not exists public.listings (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users (id) on delete cascade,
brand_voice_profile_id uuid references public.brand_voice_profiles (id) on delete set null,
product_description text not null,
target_keywords text,
titles jsonb not null,
tags jsonb not null,
description text not null,
category_hints jsonb,
provider_used text,
created_at timestamptz not null default now()
);
alter table public.listings enable row level security;
create policy "listings_select_own" on public.listings
for select using (auth.uid() = user_id);
create policy "listings_insert_own" on public.listings
for insert with check (auth.uid() = user_id);
create policy "listings_delete_own" on public.listings
for delete using (auth.uid() = user_id);
create index if not exists listings_user_created_idx on public.listings (user_id, created_at desc);
-- ---------------------------------------------------------------------------
-- shops: for Business tier "multiple shops/clients"
-- ---------------------------------------------------------------------------
create table if not exists public.shops (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users (id) on delete cascade,
name text not null,
created_at timestamptz not null default now()
);
alter table public.shops enable row level security;
create policy "shops_select_own" on public.shops
for select using (auth.uid() = user_id);
create policy "shops_insert_own" on public.shops
for insert with check (auth.uid() = user_id);
create policy "shops_delete_own" on public.shops
for delete using (auth.uid() = user_id);
-- ---------------------------------------------------------------------------
-- helper: touch updated_at on profiles
-- ---------------------------------------------------------------------------
create or replace function public.set_updated_at()
returns trigger as $$
begin
new.updated_at = now();
return new;
end;
$$ language plpgsql;
drop trigger if exists profiles_set_updated_at on public.profiles;
create trigger profiles_set_updated_at
before update on public.profiles
for each row execute function public.set_updated_at();
-- ---------------------------------------------------------------------------
-- Reminder (see DEPLOY.md):
-- - Enable "Leaked password protection" under Auth > Policies in the Supabase
-- dashboard - this is a project setting, not a SQL statement.
-- - Provision this project in an EU region (e.g. Frankfurt) at creation time
-- for GDPR data residency - also a dashboard setting, not SQL.
-- ---------------------------------------------------------------------------
|