Files
PolyWeather/scripts/supabase/schema.sql
T

283 lines
10 KiB
PL/PgSQL
Raw Normal View History

-- PolyWeather minimal commerce/auth schema (P0)
-- Run in Supabase SQL editor.
create extension if not exists pgcrypto;
create table if not exists public.profiles (
id uuid primary key references auth.users(id) on delete cascade,
email text not null default '',
telegram_user_id bigint,
telegram_username text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
2026-05-29 17:22:33 +08:00
create index if not exists idx_profiles_email
on public.profiles(email)
include (id);
create index if not exists idx_profiles_id_lookup
on public.profiles(id)
include (email, created_at);
create table if not exists public.subscriptions (
id bigserial primary key,
user_id uuid not null references auth.users(id) on delete cascade,
plan_code text not null,
status text not null check (status in ('active', 'paused', 'expired', 'cancelled')),
starts_at timestamptz not null default now(),
expires_at timestamptz not null,
source text not null default 'manual',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists idx_subscriptions_user_status_expiry
2026-05-29 17:22:33 +08:00
on public.subscriptions(user_id, expires_at desc)
include (id, starts_at, plan_code, source)
where status = 'active';
create index if not exists idx_subscriptions_status_expiry
on public.subscriptions(expires_at asc)
include (user_id, starts_at, plan_code)
where status = 'active';
create index if not exists idx_subscriptions_user_created
on public.subscriptions(user_id, created_at desc)
include (id, status, plan_code, source, starts_at, expires_at, updated_at);
create table if not exists public.payments (
id bigserial primary key,
user_id uuid not null references auth.users(id) on delete cascade,
amount numeric(18, 6) not null,
currency text not null default 'USDC',
chain text not null default 'polygon',
tx_hash text unique,
status text not null check (status in ('pending', 'confirmed', 'failed', 'refunded')),
raw_payload jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
2026-05-29 17:22:33 +08:00
create index if not exists idx_payments_created_at
on public.payments(created_at desc)
include (id, user_id, amount, currency, chain, tx_hash, status);
create table if not exists public.entitlement_events (
id bigserial primary key,
user_id uuid not null references auth.users(id) on delete cascade,
action text not null,
reason text not null default '',
actor text not null default 'system',
payload jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now()
);
create table if not exists public.user_wallets (
id bigserial primary key,
user_id uuid not null references auth.users(id) on delete cascade,
chain_id integer not null default 137,
address text not null,
status text not null default 'active' check (status in ('active', 'revoked')),
is_primary boolean not null default false,
verified_at timestamptz,
last_used_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique(chain_id, address)
);
create index if not exists idx_user_wallets_user_chain
2026-05-29 17:22:33 +08:00
on public.user_wallets(user_id, chain_id, is_primary desc, verified_at desc)
include (id, address)
where status = 'active';
create index if not exists idx_user_wallets_chain_address_owner
on public.user_wallets(chain_id, address)
include (user_id, status);
2026-05-29 19:24:46 +08:00
create table if not exists public.trial_claims (
id bigserial primary key,
user_id uuid not null references auth.users(id) on delete cascade,
email text not null default '',
telegram_user_id bigint,
primary_wallet_address text,
metadata jsonb not null default '{}'::jsonb,
claimed_at timestamptz not null default now(),
created_at timestamptz not null default now()
);
create unique index if not exists uq_trial_claims_user
on public.trial_claims(user_id);
create unique index if not exists uq_trial_claims_email
on public.trial_claims(lower(email))
where email <> '';
create unique index if not exists uq_trial_claims_telegram
on public.trial_claims(telegram_user_id)
where telegram_user_id is not null;
create table if not exists public.trial_claim_wallets (
id bigserial primary key,
trial_claim_id bigint not null references public.trial_claims(id) on delete cascade,
wallet_address text not null,
created_at timestamptz not null default now()
);
create unique index if not exists uq_trial_claim_wallets_address
on public.trial_claim_wallets(lower(wallet_address));
create table if not exists public.referral_codes (
id bigserial primary key,
user_id uuid not null references auth.users(id) on delete cascade,
code text not null,
status text not null default 'active' check (status in ('active', 'revoked')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create unique index if not exists uq_referral_codes_user
on public.referral_codes(user_id);
create unique index if not exists uq_referral_codes_code
on public.referral_codes(upper(code));
create table if not exists public.referral_attributions (
id bigserial primary key,
referrer_user_id uuid not null references auth.users(id) on delete cascade,
referred_user_id uuid not null references auth.users(id) on delete cascade,
code text not null,
status text not null default 'pending' check (status in ('pending', 'converted', 'capped', 'cancelled')),
converted_payment_intent_id text,
converted_tx_hash text,
converted_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
check (referrer_user_id <> referred_user_id)
);
create unique index if not exists uq_referral_attributions_referred
on public.referral_attributions(referred_user_id);
create index if not exists idx_referral_attributions_pending
on public.referral_attributions(referred_user_id, created_at desc)
include (id, code, referrer_user_id)
where status = 'pending';
create table if not exists public.referral_rewards (
id bigserial primary key,
referral_attribution_id bigint not null references public.referral_attributions(id) on delete cascade,
referrer_user_id uuid not null references auth.users(id) on delete cascade,
referred_user_id uuid not null references auth.users(id) on delete cascade,
payment_intent_id text not null,
tx_hash text,
reward_days integer not null default 3 check (reward_days > 0 and reward_days <= 30),
created_at timestamptz not null default now()
);
create unique index if not exists uq_referral_rewards_attribution
on public.referral_rewards(referral_attribution_id);
create index if not exists idx_referral_rewards_referrer_month
on public.referral_rewards(referrer_user_id, created_at desc)
include (id, reward_days);
create table if not exists public.wallet_link_challenges (
id bigserial primary key,
user_id uuid not null references auth.users(id) on delete cascade,
chain_id integer not null default 137,
address text not null,
nonce text not null unique,
message text not null,
expires_at timestamptz not null,
consumed_at timestamptz,
created_at timestamptz not null default now()
);
create table if not exists public.payment_intents (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
plan_code text not null,
plan_id bigint not null,
chain_id integer not null default 137,
token_address text not null,
receiver_address text not null,
amount_units numeric(78,0) not null,
payment_mode text not null default 'strict' check (payment_mode in ('strict', 'flex', 'direct')),
allowed_wallet text,
order_id_hex text not null unique,
status text not null default 'created' check (status in ('created', 'submitted', 'confirmed', 'expired', 'failed', 'cancelled')),
tx_hash text,
expires_at timestamptz not null,
confirmed_at timestamptz,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
2026-05-29 17:22:33 +08:00
create index if not exists idx_payment_intents_status_updated
on public.payment_intents(status, updated_at desc)
include (user_id)
where status in ('submitted', 'confirmed');
create index if not exists idx_payment_intents_user_status_updated
on public.payment_intents(user_id, status, updated_at desc);
create index if not exists idx_payment_intents_submitted_tx_updated
on public.payment_intents(updated_at asc)
include (id, user_id, tx_hash, chain_id)
where status = 'submitted' and tx_hash is not null;
create index if not exists idx_payment_intents_user_created
on public.payment_intents(user_id, created_at desc);
create index if not exists idx_payment_intents_tx_hash
2026-05-29 17:22:33 +08:00
on public.payment_intents(tx_hash)
include (id, user_id)
where tx_hash is not null;
create table if not exists public.payment_transactions (
id bigserial primary key,
intent_id uuid not null references public.payment_intents(id) on delete cascade,
tx_hash text not null unique,
chain_id integer not null default 137,
from_address text,
to_address text not null,
block_number bigint,
payment_method text not null default 'wallet' check (payment_method in ('wallet', 'manual', 'direct')),
status text not null default 'submitted' check (status in ('submitted', 'confirmed', 'failed', 'duplicate', 'refund_required')),
raw_receipt jsonb not null default '{}'::jsonb,
raw_tx jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists idx_payment_transactions_intent
on public.payment_transactions(intent_id, created_at desc);
2026-05-29 17:22:33 +08:00
create index if not exists idx_payment_transactions_tx_hash_intent
on public.payment_transactions(tx_hash)
include (intent_id);
create or replace function public.sync_profile_from_auth()
returns trigger
language plpgsql
security definer
set search_path = public
as $$
begin
insert into public.profiles (id, email)
values (new.id, coalesce(new.email, ''))
on conflict (id) do update
set email = excluded.email,
updated_at = now();
return new;
end;
$$;
drop trigger if exists on_auth_user_created_polyweather on auth.users;
create trigger on_auth_user_created_polyweather
after insert on auth.users
for each row execute function public.sync_profile_from_auth();