Files
vyndr/supabase/migrations/011_user_profiles_web.sql
T

165 lines
7.2 KiB
PL/PgSQL

-- 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);