forked from wangziqi/gongxue-base
53 lines
2.2 KiB
SQL
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();
|