-- VYNDR Web — user profile + scan throttle + parlay leg frequency -- Idempotent. Safe to apply multiple times. create extension if not exists "pgcrypto"; -- ──────────────────────────────────────────────────────────── -- user_profiles -- ──────────────────────────────────────────────────────────── create table if not exists public.user_profiles ( id uuid primary key references auth.users(id) on delete cascade, email text, tier text not null default 'free' check (tier in ('free', 'analyst', 'desk')), scan_count integer not null default 0, scan_reset_date date not null default current_date, subscription_start timestamptz, subscription_end timestamptz, subscription_status text not null default 'none' check (subscription_status in ('none','active','grace_period','expired','canceled')), cancel_at_period_end boolean not null default false, founder_pricing boolean not null default false, age_verified boolean not null default false, nexapay_customer_id text, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create index if not exists idx_user_profiles_tier on public.user_profiles(tier); create index if not exists idx_user_profiles_subscription_end on public.user_profiles(subscription_end); -- Auto-create profile on auth.users insert create or replace function public.handle_new_user() returns trigger as $$ begin insert into public.user_profiles (id, email) values (new.id, new.email) on conflict (id) do nothing; return new; end; $$ language plpgsql security definer set search_path = public; drop trigger if exists on_auth_user_created on auth.users; create trigger on_auth_user_created after insert on auth.users for each row execute function public.handle_new_user(); -- updated_at maintenance create or replace function public.touch_updated_at() returns trigger as $$ begin new.updated_at := now(); return new; end; $$ language plpgsql; drop trigger if exists set_user_profiles_updated_at on public.user_profiles; create trigger set_user_profiles_updated_at before update on public.user_profiles for each row execute function public.touch_updated_at(); -- RLS alter table public.user_profiles enable row level security; drop policy if exists "user can read own profile" on public.user_profiles; create policy "user can read own profile" on public.user_profiles for select using (auth.uid() = id); drop policy if exists "user can update own profile" on public.user_profiles; create policy "user can update own profile" on public.user_profiles for update using (auth.uid() = id) with check (auth.uid() = id); -- service role bypasses RLS automatically; no explicit policy needed. -- ──────────────────────────────────────────────────────────── -- parlay_leg_frequency — track scan + parlay popularity per day -- ──────────────────────────────────────────────────────────── create table if not exists public.parlay_leg_frequency ( id uuid primary key default gen_random_uuid(), player_name text not null, stat text not null, line numeric not null, over_under text not null check (over_under in ('over','under')), sport text not null check (sport in ('NBA','MLB','WNBA','NFL')), game_date date not null default current_date, scan_count integer not null default 0, parlay_count integer not null default 0, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (player_name, stat, line, over_under, sport, game_date) ); create index if not exists idx_plf_game_date_sport on public.parlay_leg_frequency(game_date, sport); create index if not exists idx_plf_parlay_rank on public.parlay_leg_frequency(game_date, parlay_count desc); create index if not exists idx_plf_scan_rank on public.parlay_leg_frequency(game_date, scan_count desc); drop trigger if exists set_plf_updated_at on public.parlay_leg_frequency; create trigger set_plf_updated_at before update on public.parlay_leg_frequency for each row execute function public.touch_updated_at(); -- Public read; writes restricted to service role alter table public.parlay_leg_frequency enable row level security; drop policy if exists "anyone can read parlay frequency" on public.parlay_leg_frequency; create policy "anyone can read parlay frequency" on public.parlay_leg_frequency for select using (true); -- Atomic increment helper create or replace function public.increment_parlay_leg_frequency( p_player text, p_stat text, p_line numeric, p_dir text, p_sport text, p_scan_delta integer default 0, p_parlay_delta integer default 0 ) returns void as $$ begin insert into public.parlay_leg_frequency (player_name, stat, line, over_under, sport, scan_count, parlay_count) values (p_player, p_stat, p_line, p_dir, p_sport, p_scan_delta, p_parlay_delta) on conflict (player_name, stat, line, over_under, sport, game_date) do update set scan_count = public.parlay_leg_frequency.scan_count + p_scan_delta, parlay_count = public.parlay_leg_frequency.parlay_count + p_parlay_delta, updated_at = now(); end; $$ language plpgsql security definer set search_path = public; -- ──────────────────────────────────────────────────────────── -- scan_history — for ledger + per-user analytics -- ──────────────────────────────────────────────────────────── create table if not exists public.scan_history ( id uuid primary key default gen_random_uuid(), user_id uuid references auth.users(id) on delete set null, sport text not null, player_name text not null, stat text not null, line numeric not null, direction text not null, grade text, projection numeric, confidence integer, factors jsonb, created_at timestamptz not null default now() ); create index if not exists idx_scan_history_user on public.scan_history(user_id, created_at desc); create index if not exists idx_scan_history_sport on public.scan_history(sport, created_at desc); alter table public.scan_history enable row level security; drop policy if exists "user reads own scans" on public.scan_history; create policy "user reads own scans" on public.scan_history for select using (auth.uid() = user_id); drop policy if exists "user inserts own scans" on public.scan_history; create policy "user inserts own scans" on public.scan_history for insert with check (auth.uid() = user_id);