Files
gongxue-base/supabase/migrations/202607120012_sms_send_reservation_limits.sql
2026-07-12 19:26:57 +08:00

53 lines
2.2 KiB
SQL

with ranked_active_codes as (
select id,
row_number() over (
partition by tenant_id, phone, purpose
order by created_at desc, id desc
) as row_no
from public.sms_verification_codes
where consumed_at is null and status in ('pending', 'sent')
)
update public.sms_verification_codes code
set status = 'expired',
metadata = code.metadata || '{"expiredBy":"sms-active-reservation-migration"}'::jsonb
from ranked_active_codes ranked
where code.id = ranked.id and ranked.row_no > 1;
create unique index if not exists idx_sms_codes_active_phone_reservation
on public.sms_verification_codes (tenant_id, phone, purpose)
where consumed_at is null and status in ('pending', 'sent');
create table if not exists app_private.sms_send_rate_limits (
tenant_id uuid not null references public.tenants(id) on delete cascade,
dimension text not null check (dimension in ('tenant', 'phone', 'ip', 'device')),
scope_hash text not null,
bucket_start timestamptz not null,
request_count integer not null default 0 check (request_count >= 0),
updated_at timestamptz not null default now(),
primary key (tenant_id, dimension, scope_hash, bucket_start)
);
revoke all on table app_private.sms_send_rate_limits from public, anon, authenticated;
alter table app_private.sms_send_rate_limits enable row level security;
drop policy if exists platform_admin_sms_send_rate_limits on app_private.sms_send_rate_limits;
create policy platform_admin_sms_send_rate_limits on app_private.sms_send_rate_limits
for all
using (app.is_platform_admin())
with check (app.is_platform_admin());
create index if not exists idx_sms_send_rate_limits_updated
on app_private.sms_send_rate_limits (updated_at);
comment on index public.idx_sms_codes_active_phone_reservation is
'Atomically permits one active SMS send reservation per tenant, phone and purpose.';
comment on table app_private.sms_send_rate_limits is
'Atomic hour/day SMS quota buckets. Dimension values are stored only as HMAC hashes.';
drop trigger if exists set_updated_at on app_private.sms_send_rate_limits;
create trigger set_updated_at
before update on app_private.sms_send_rate_limits
for each row execute function app.touch_updated_at();