mirror of
https://github.com/freestylefly/awesome-gpt-image-2.git
synced 2026-09-14 14:00:36 +08:00
255 lines
7.6 KiB
PL/PgSQL
255 lines
7.6 KiB
PL/PgSQL
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,
|
|
full_name text,
|
|
avatar_url text,
|
|
role text not null default 'user' check (role in ('user', 'super_admin')),
|
|
credit_balance integer not null default 0 check (credit_balance >= 0),
|
|
free_generations_used integer not null default 0 check (free_generations_used >= 0),
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists public.credit_transactions (
|
|
id uuid primary key default gen_random_uuid(),
|
|
user_id uuid not null references auth.users(id) on delete cascade,
|
|
amount integer not null,
|
|
type text not null check (type in ('grant', 'purchase', 'generation', 'refund', 'adjustment')),
|
|
source text,
|
|
reference_id uuid,
|
|
metadata jsonb not null default '{}'::jsonb,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists public.generation_reservations (
|
|
id uuid primary key default gen_random_uuid(),
|
|
user_id uuid not null references auth.users(id) on delete cascade,
|
|
case_id integer not null,
|
|
prompt text not null,
|
|
status text not null default 'pending' check (status in ('pending', 'succeeded', 'failed')),
|
|
used_free_generation boolean not null default false,
|
|
credit_amount integer not null default 0 check (credit_amount >= 0),
|
|
error_code text,
|
|
created_at timestamptz not null default now(),
|
|
completed_at timestamptz
|
|
);
|
|
|
|
create index if not exists profiles_email_idx on public.profiles (lower(email));
|
|
create index if not exists profiles_role_idx on public.profiles (role);
|
|
create index if not exists credit_transactions_user_id_idx on public.credit_transactions (user_id, created_at desc);
|
|
create index if not exists generation_reservations_user_id_idx on public.generation_reservations (user_id, created_at desc);
|
|
create index if not exists generation_reservations_status_idx on public.generation_reservations (status);
|
|
|
|
alter table public.profiles enable row level security;
|
|
alter table public.credit_transactions enable row level security;
|
|
alter table public.generation_reservations enable row level security;
|
|
|
|
drop policy if exists "Users can read own profile" on public.profiles;
|
|
create policy "Users can read own profile"
|
|
on public.profiles for select
|
|
using ((select auth.uid()) = id);
|
|
|
|
drop policy if exists "Users can read own credit transactions" on public.credit_transactions;
|
|
create policy "Users can read own credit transactions"
|
|
on public.credit_transactions for select
|
|
using ((select auth.uid()) = user_id);
|
|
|
|
drop policy if exists "Users can read own generation reservations" on public.generation_reservations;
|
|
create policy "Users can read own generation reservations"
|
|
on public.generation_reservations for select
|
|
using ((select auth.uid()) = user_id);
|
|
|
|
create or replace function public.set_updated_at()
|
|
returns trigger
|
|
language plpgsql
|
|
set search_path = public
|
|
as $$
|
|
begin
|
|
new.updated_at = now();
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
drop trigger if exists profiles_set_updated_at on public.profiles;
|
|
create trigger profiles_set_updated_at
|
|
before update on public.profiles
|
|
for each row
|
|
execute function public.set_updated_at();
|
|
|
|
create or replace function public.reserve_generation_usage(
|
|
p_user_id uuid,
|
|
p_case_id integer,
|
|
p_prompt text
|
|
)
|
|
returns table (
|
|
reservation_id uuid,
|
|
used_free_generation boolean,
|
|
credit_amount integer,
|
|
free_generations_used integer,
|
|
credit_balance integer
|
|
)
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
declare
|
|
v_profile public.profiles%rowtype;
|
|
v_reservation_id uuid;
|
|
begin
|
|
select *
|
|
into v_profile
|
|
from public.profiles
|
|
where id = p_user_id
|
|
for update;
|
|
|
|
if not found then
|
|
raise exception 'PROFILE_NOT_FOUND' using errcode = 'P0001';
|
|
end if;
|
|
|
|
if v_profile.free_generations_used < 1 then
|
|
update public.profiles
|
|
set free_generations_used = free_generations_used + 1
|
|
where id = p_user_id
|
|
returning * into v_profile;
|
|
|
|
insert into public.generation_reservations (
|
|
user_id,
|
|
case_id,
|
|
prompt,
|
|
used_free_generation,
|
|
credit_amount
|
|
)
|
|
values (p_user_id, p_case_id, p_prompt, true, 0)
|
|
returning id into v_reservation_id;
|
|
|
|
return query
|
|
select v_reservation_id, true, 0, v_profile.free_generations_used, v_profile.credit_balance;
|
|
return;
|
|
end if;
|
|
|
|
if v_profile.credit_balance >= 1 then
|
|
update public.profiles
|
|
set credit_balance = credit_balance - 1
|
|
where id = p_user_id
|
|
returning * into v_profile;
|
|
|
|
insert into public.generation_reservations (
|
|
user_id,
|
|
case_id,
|
|
prompt,
|
|
used_free_generation,
|
|
credit_amount
|
|
)
|
|
values (p_user_id, p_case_id, p_prompt, false, 1)
|
|
returning id into v_reservation_id;
|
|
|
|
insert into public.credit_transactions (
|
|
user_id,
|
|
amount,
|
|
type,
|
|
source,
|
|
reference_id,
|
|
metadata
|
|
)
|
|
values (
|
|
p_user_id,
|
|
-1,
|
|
'generation',
|
|
'case_generation_test',
|
|
v_reservation_id,
|
|
jsonb_build_object('caseId', p_case_id)
|
|
);
|
|
|
|
return query
|
|
select v_reservation_id, false, 1, v_profile.free_generations_used, v_profile.credit_balance;
|
|
return;
|
|
end if;
|
|
|
|
raise exception 'CREDITS_REQUIRED' using errcode = 'P0001';
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.complete_generation_reservation(
|
|
p_reservation_id uuid
|
|
)
|
|
returns void
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
begin
|
|
update public.generation_reservations
|
|
set status = 'succeeded',
|
|
completed_at = now()
|
|
where id = p_reservation_id
|
|
and status = 'pending';
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.release_generation_reservation(
|
|
p_reservation_id uuid,
|
|
p_error_code text default null
|
|
)
|
|
returns void
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
declare
|
|
v_reservation public.generation_reservations%rowtype;
|
|
begin
|
|
select *
|
|
into v_reservation
|
|
from public.generation_reservations
|
|
where id = p_reservation_id
|
|
for update;
|
|
|
|
if not found or v_reservation.status <> 'pending' then
|
|
return;
|
|
end if;
|
|
|
|
if v_reservation.used_free_generation then
|
|
update public.profiles
|
|
set free_generations_used = greatest(free_generations_used - 1, 0)
|
|
where id = v_reservation.user_id;
|
|
elsif v_reservation.credit_amount > 0 then
|
|
update public.profiles
|
|
set credit_balance = credit_balance + v_reservation.credit_amount
|
|
where id = v_reservation.user_id;
|
|
|
|
insert into public.credit_transactions (
|
|
user_id,
|
|
amount,
|
|
type,
|
|
source,
|
|
reference_id,
|
|
metadata
|
|
)
|
|
values (
|
|
v_reservation.user_id,
|
|
v_reservation.credit_amount,
|
|
'refund',
|
|
'generation_failed',
|
|
v_reservation.id,
|
|
jsonb_build_object('caseId', v_reservation.case_id, 'error', p_error_code)
|
|
);
|
|
end if;
|
|
|
|
update public.generation_reservations
|
|
set status = 'failed',
|
|
error_code = p_error_code,
|
|
completed_at = now()
|
|
where id = p_reservation_id;
|
|
end;
|
|
$$;
|
|
|
|
revoke execute on function public.reserve_generation_usage(uuid, integer, text) from public, anon, authenticated;
|
|
revoke execute on function public.complete_generation_reservation(uuid) from public, anon, authenticated;
|
|
revoke execute on function public.release_generation_reservation(uuid, text) from public, anon, authenticated;
|
|
|
|
grant execute on function public.reserve_generation_usage(uuid, integer, text) to service_role;
|
|
grant execute on function public.complete_generation_reservation(uuid) to service_role;
|
|
grant execute on function public.release_generation_reservation(uuid, text) to service_role;
|