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.
-- ---------------------------------------------------------------------------