threesome fixed
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
-- One-shot: run in Supabase Dashboard → SQL Editor.
|
||||
-- Adds per-match escrow accounting columns used by collect_match_entries / settle_match_reward.
|
||||
|
||||
alter table public.matches
|
||||
add column if not exists escrow_user_id bigint null,
|
||||
add column if not exists entries_collected_at timestamp with time zone null,
|
||||
add column if not exists settled_at timestamp with time zone null;
|
||||
|
||||
do $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1
|
||||
from pg_constraint
|
||||
where conname = 'matches_escrow_user_id_fkey'
|
||||
) then
|
||||
alter table public.matches
|
||||
add constraint matches_escrow_user_id_fkey
|
||||
foreign key (escrow_user_id) references public.users (id);
|
||||
end if;
|
||||
end $$;
|
||||
|
||||
comment on column public.matches.escrow_user_id is
|
||||
'Dedicated users row holding this match pot (username match_escrow_<id>); set on first successful collect.';
|
||||
comment on column public.matches.entries_collected_at is
|
||||
'When both players entry fees were moved into escrow; null until collect succeeds.';
|
||||
comment on column public.matches.settled_at is
|
||||
'When prize + house fee were paid from escrow; null until settle succeeds.';
|
||||
@@ -0,0 +1,11 @@
|
||||
-- One-shot: run in Supabase Dashboard → SQL Editor.
|
||||
-- Single active session per user for concurrent-device login blocking.
|
||||
|
||||
alter table public.users
|
||||
add column if not exists session_id text null,
|
||||
add column if not exists last_keepalive_at timestamp with time zone null;
|
||||
|
||||
comment on column public.users.session_id is
|
||||
'Current login session UUID (jti in the auth token). Null when logged out.';
|
||||
comment on column public.users.last_keepalive_at is
|
||||
'Last successful POST /auth/keepalive (also set at login/register). Session is active for 30s after this.';
|
||||
+24
-16
@@ -6,40 +6,48 @@ I have added my first record with remarks of 'supply',to acc id:1. a large sum.
|
||||
|
||||
All purchases of RC (in purchases rc api endpoint) will be deducted from acc id:1 and given to the requested user.
|
||||
|
||||
During reward distribution, Record the bet fee going to the acc id:1 and prize going to the winner seperately. and set match_id on reward distribution .
|
||||
## Match escrow (current)
|
||||
|
||||
Heres how a RC purchase record would look like
|
||||
Entry fees are collected when both players have joined (`red_joined_at` + `blue_joined_at`), into a **per-match escrow user** (`matches.escrow_user_id`, username `match_escrow_<matchId>`). At settle, the prize and house fee are paid **only from that escrow** — never from the system supply account.
|
||||
|
||||
See:
|
||||
- `schemas/alter_matches_escrow.sql`
|
||||
- `schemas/rpc_collect_match_entries.sql`
|
||||
- `schemas/rpc_settle_match_reward.sql`
|
||||
|
||||
### RC purchase
|
||||
|
||||
from: 1
|
||||
to: requested_user_id
|
||||
amount: requested_amount
|
||||
remarks: purchase
|
||||
|
||||
|
||||
Heres how a reward distribution would look like.
|
||||
|
||||
from a single players POV (there are 2 players per match).
|
||||
PS: bet fee is usually 10%, stored in settings table
|
||||
entry record 1/2
|
||||
### Collect (per player, ×2)
|
||||
|
||||
from: user_id
|
||||
to: 1
|
||||
to: escrow_user_id
|
||||
amount: entry fee - bet fee
|
||||
remarks: entry_hold
|
||||
match_id: match_id
|
||||
|
||||
entry record 2/2
|
||||
|
||||
from: user_id
|
||||
to: 1
|
||||
to: escrow_user_id
|
||||
amount: bet fee
|
||||
remarks: entry_fee
|
||||
match_id: match_id
|
||||
|
||||
And for giving the reward.
|
||||
### Settle
|
||||
|
||||
from: 1
|
||||
from: escrow_user_id
|
||||
to: winner_user_id
|
||||
amount: (already calculated properly)
|
||||
amount: rc_prize
|
||||
remarks: reward
|
||||
match_id: match_id
|
||||
match_id: match_id
|
||||
|
||||
from: escrow_user_id
|
||||
to: 1
|
||||
amount: bet fee × 2
|
||||
remarks: bet_fee
|
||||
match_id: match_id
|
||||
|
||||
PS: bet fee is usually 10%, stored in settings table (`key = bet_fee`).
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
-- One-shot: run in Supabase Dashboard → SQL Editor **after** schemas/alter_matches_escrow.sql
|
||||
-- Atomically moves both players' entry_fee RC into a per-match escrow user.
|
||||
-- Idempotent: if entries_collected_at is already set, returns success without changing balances.
|
||||
-- Requires users.rc to support numeric debits (prefer bigint via alter_users_rc_bigint.sql).
|
||||
|
||||
create or replace function public.collect_match_entries(p_match_id bigint)
|
||||
returns jsonb
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_match public.matches%rowtype;
|
||||
v_escrow_id bigint;
|
||||
v_entry_fee bigint;
|
||||
v_bet_fee_pct integer;
|
||||
v_fee_each bigint;
|
||||
v_hold_each bigint;
|
||||
v_bet_fee_raw text;
|
||||
begin
|
||||
if p_match_id is null or p_match_id < 1 then
|
||||
raise exception 'invalid_match';
|
||||
end if;
|
||||
|
||||
select * into v_match
|
||||
from public.matches
|
||||
where id = p_match_id
|
||||
for update;
|
||||
|
||||
if not found then
|
||||
raise exception 'match_not_found';
|
||||
end if;
|
||||
|
||||
if v_match.entries_collected_at is not null then
|
||||
return jsonb_build_object(
|
||||
'ok', true,
|
||||
'already_collected', true,
|
||||
'match_id', p_match_id,
|
||||
'escrow_user_id', v_match.escrow_user_id,
|
||||
'entry_fee', coalesce(v_match.entry_fee, 0),
|
||||
'entries_collected_at', v_match.entries_collected_at
|
||||
);
|
||||
end if;
|
||||
|
||||
if v_match.user_red is null or v_match.user_blue is null then
|
||||
raise exception 'players_incomplete';
|
||||
end if;
|
||||
|
||||
if v_match.red_joined_at is null or v_match.blue_joined_at is null then
|
||||
raise exception 'joins_incomplete';
|
||||
end if;
|
||||
|
||||
if v_match.settled_at is not null or v_match.winner_id is not null then
|
||||
raise exception 'match_already_settled';
|
||||
end if;
|
||||
|
||||
v_entry_fee := coalesce(v_match.entry_fee, 0);
|
||||
if v_entry_fee < 0 then
|
||||
raise exception 'invalid_entry_fee';
|
||||
end if;
|
||||
|
||||
select value into v_bet_fee_raw
|
||||
from public.settings
|
||||
where key = 'bet_fee'
|
||||
limit 1;
|
||||
|
||||
begin
|
||||
v_bet_fee_pct := coalesce(nullif(trim(v_bet_fee_raw), '')::integer, 10);
|
||||
exception when others then
|
||||
v_bet_fee_pct := 10;
|
||||
end;
|
||||
|
||||
if v_bet_fee_pct < 0 or v_bet_fee_pct > 100 then
|
||||
v_bet_fee_pct := 10;
|
||||
end if;
|
||||
|
||||
v_fee_each := ceil((v_entry_fee::numeric * v_bet_fee_pct) / 100)::bigint;
|
||||
v_hold_each := v_entry_fee - v_fee_each;
|
||||
if v_hold_each < 0 then
|
||||
raise exception 'invalid_fee_split';
|
||||
end if;
|
||||
|
||||
if v_match.escrow_user_id is not null then
|
||||
v_escrow_id := v_match.escrow_user_id;
|
||||
else
|
||||
insert into public.users (username, email, password, cc, rc)
|
||||
values (
|
||||
'match_escrow_' || p_match_id::text,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
0
|
||||
)
|
||||
returning id into v_escrow_id;
|
||||
|
||||
update public.matches
|
||||
set escrow_user_id = v_escrow_id
|
||||
where id = p_match_id;
|
||||
end if;
|
||||
|
||||
if v_entry_fee > 0 then
|
||||
update public.users
|
||||
set rc = coalesce(rc, 0) - v_entry_fee
|
||||
where id = v_match.user_red
|
||||
and coalesce(rc, 0) >= v_entry_fee;
|
||||
if not found then
|
||||
raise exception 'insufficient_rc_red';
|
||||
end if;
|
||||
|
||||
update public.users
|
||||
set rc = coalesce(rc, 0) - v_entry_fee
|
||||
where id = v_match.user_blue
|
||||
and coalesce(rc, 0) >= v_entry_fee;
|
||||
if not found then
|
||||
raise exception 'insufficient_rc_blue';
|
||||
end if;
|
||||
|
||||
update public.users
|
||||
set rc = coalesce(rc, 0) + (v_entry_fee * 2)
|
||||
where id = v_escrow_id;
|
||||
if not found then
|
||||
raise exception 'escrow_not_found';
|
||||
end if;
|
||||
|
||||
if v_hold_each > 0 then
|
||||
insert into public.transactions ("from", "to", amount, remarks, match_id)
|
||||
values
|
||||
(v_match.user_red, v_escrow_id, v_hold_each, 'entry_hold', p_match_id),
|
||||
(v_match.user_blue, v_escrow_id, v_hold_each, 'entry_hold', p_match_id);
|
||||
end if;
|
||||
if v_fee_each > 0 then
|
||||
insert into public.transactions ("from", "to", amount, remarks, match_id)
|
||||
values
|
||||
(v_match.user_red, v_escrow_id, v_fee_each, 'entry_fee', p_match_id),
|
||||
(v_match.user_blue, v_escrow_id, v_fee_each, 'entry_fee', p_match_id);
|
||||
end if;
|
||||
end if;
|
||||
|
||||
update public.matches
|
||||
set entries_collected_at = now()
|
||||
where id = p_match_id
|
||||
and entries_collected_at is null;
|
||||
|
||||
return jsonb_build_object(
|
||||
'ok', true,
|
||||
'already_collected', false,
|
||||
'match_id', p_match_id,
|
||||
'escrow_user_id', v_escrow_id,
|
||||
'entry_fee', v_entry_fee,
|
||||
'fee_each', v_fee_each,
|
||||
'pot_rc', v_entry_fee * 2,
|
||||
'entries_collected_at', now()
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
revoke all on function public.collect_match_entries(bigint) from public;
|
||||
grant execute on function public.collect_match_entries(bigint) to service_role;
|
||||
@@ -0,0 +1,200 @@
|
||||
-- One-shot: run in Supabase Dashboard → SQL Editor **after** schemas/rpc_collect_match_entries.sql
|
||||
-- Atomically settles a match: sets winner_id, pays prize + house fee only from the match escrow,
|
||||
-- grants prize_cc to both participants, marks settled_at. Idempotent if already settled for same winner.
|
||||
|
||||
create or replace function public.settle_match_reward(p_match_id bigint, p_winner_id bigint)
|
||||
returns jsonb
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_match public.matches%rowtype;
|
||||
v_system bigint := 1;
|
||||
v_entry_fee bigint;
|
||||
v_prize_cc bigint;
|
||||
v_bet_fee_pct integer;
|
||||
v_fee_each bigint;
|
||||
v_house_total bigint;
|
||||
v_rc_prize bigint;
|
||||
v_escrow_rc numeric;
|
||||
v_bet_fee_raw text;
|
||||
begin
|
||||
if p_match_id is null or p_match_id < 1 then
|
||||
raise exception 'invalid_match';
|
||||
end if;
|
||||
if p_winner_id is null or p_winner_id < 1 then
|
||||
raise exception 'invalid_winner';
|
||||
end if;
|
||||
|
||||
select * into v_match
|
||||
from public.matches
|
||||
where id = p_match_id
|
||||
for update;
|
||||
|
||||
if not found then
|
||||
raise exception 'match_not_found';
|
||||
end if;
|
||||
|
||||
if v_match.settled_at is not null then
|
||||
if v_match.winner_id = p_winner_id then
|
||||
return jsonb_build_object(
|
||||
'ok', true,
|
||||
'already_settled', true,
|
||||
'match_id', p_match_id,
|
||||
'winner_id', v_match.winner_id,
|
||||
'entry_fee', coalesce(v_match.entry_fee, 0),
|
||||
'rc_prize', null,
|
||||
'participant_cc', coalesce(v_match.prize_cc, 100),
|
||||
'settled_at', v_match.settled_at
|
||||
);
|
||||
end if;
|
||||
raise exception 'winner_already_set';
|
||||
end if;
|
||||
|
||||
if v_match.winner_id is not null and v_match.winner_id <> p_winner_id then
|
||||
raise exception 'winner_already_set';
|
||||
end if;
|
||||
|
||||
if v_match.entries_collected_at is null then
|
||||
raise exception 'entries_not_collected';
|
||||
end if;
|
||||
|
||||
if v_match.escrow_user_id is null then
|
||||
raise exception 'escrow_missing';
|
||||
end if;
|
||||
|
||||
if p_winner_id is distinct from v_match.user_red
|
||||
and p_winner_id is distinct from v_match.user_blue then
|
||||
raise exception 'winner_not_participant';
|
||||
end if;
|
||||
|
||||
v_entry_fee := coalesce(v_match.entry_fee, 0);
|
||||
v_prize_cc := coalesce(v_match.prize_cc, 100);
|
||||
if v_prize_cc < 0 then
|
||||
v_prize_cc := 100;
|
||||
end if;
|
||||
|
||||
select value into v_bet_fee_raw
|
||||
from public.settings
|
||||
where key = 'bet_fee'
|
||||
limit 1;
|
||||
|
||||
begin
|
||||
v_bet_fee_pct := coalesce(nullif(trim(v_bet_fee_raw), '')::integer, 10);
|
||||
exception when others then
|
||||
v_bet_fee_pct := 10;
|
||||
end;
|
||||
|
||||
if v_bet_fee_pct < 0 or v_bet_fee_pct > 100 then
|
||||
v_bet_fee_pct := 10;
|
||||
end if;
|
||||
|
||||
v_fee_each := ceil((v_entry_fee::numeric * v_bet_fee_pct) / 100)::bigint;
|
||||
v_house_total := v_fee_each * 2;
|
||||
v_rc_prize := (v_entry_fee * 2) - v_house_total;
|
||||
|
||||
if v_rc_prize < 0 then
|
||||
raise exception 'invalid_prize';
|
||||
end if;
|
||||
|
||||
select coalesce(rc, 0) into v_escrow_rc
|
||||
from public.users
|
||||
where id = v_match.escrow_user_id
|
||||
for update;
|
||||
|
||||
if not found then
|
||||
raise exception 'escrow_not_found';
|
||||
end if;
|
||||
|
||||
if v_escrow_rc <> (v_entry_fee * 2) then
|
||||
raise exception 'escrow_balance_mismatch';
|
||||
end if;
|
||||
|
||||
if v_rc_prize > 0 then
|
||||
update public.users
|
||||
set rc = coalesce(rc, 0) - v_rc_prize
|
||||
where id = v_match.escrow_user_id
|
||||
and coalesce(rc, 0) >= v_rc_prize;
|
||||
if not found then
|
||||
raise exception 'insufficient_escrow';
|
||||
end if;
|
||||
|
||||
update public.users
|
||||
set rc = coalesce(rc, 0) + v_rc_prize
|
||||
where id = p_winner_id;
|
||||
if not found then
|
||||
raise exception 'winner_not_found';
|
||||
end if;
|
||||
|
||||
insert into public.transactions ("from", "to", amount, remarks, match_id)
|
||||
values (v_match.escrow_user_id, p_winner_id, v_rc_prize, 'reward', p_match_id);
|
||||
end if;
|
||||
|
||||
if v_house_total > 0 then
|
||||
update public.users
|
||||
set rc = coalesce(rc, 0) - v_house_total
|
||||
where id = v_match.escrow_user_id
|
||||
and coalesce(rc, 0) >= v_house_total;
|
||||
if not found then
|
||||
raise exception 'insufficient_escrow';
|
||||
end if;
|
||||
|
||||
update public.users
|
||||
set rc = coalesce(rc, 0) + v_house_total
|
||||
where id = v_system;
|
||||
if not found then
|
||||
raise exception 'system_not_found';
|
||||
end if;
|
||||
|
||||
insert into public.transactions ("from", "to", amount, remarks, match_id)
|
||||
values (v_match.escrow_user_id, v_system, v_house_total, 'bet_fee', p_match_id);
|
||||
end if;
|
||||
|
||||
-- Escrow must be empty after settle.
|
||||
update public.users
|
||||
set rc = 0
|
||||
where id = v_match.escrow_user_id
|
||||
and coalesce(rc, 0) = 0;
|
||||
if not found then
|
||||
raise exception 'escrow_not_zero';
|
||||
end if;
|
||||
|
||||
if v_match.user_red is not null then
|
||||
update public.users
|
||||
set cc = coalesce(cc, 0) + v_prize_cc
|
||||
where id = v_match.user_red;
|
||||
end if;
|
||||
if v_match.user_blue is not null then
|
||||
update public.users
|
||||
set cc = coalesce(cc, 0) + v_prize_cc
|
||||
where id = v_match.user_blue;
|
||||
end if;
|
||||
|
||||
update public.matches
|
||||
set
|
||||
winner_id = p_winner_id,
|
||||
settled_at = now()
|
||||
where id = p_match_id
|
||||
and settled_at is null;
|
||||
|
||||
if not found then
|
||||
raise exception 'settle_race';
|
||||
end if;
|
||||
|
||||
return jsonb_build_object(
|
||||
'ok', true,
|
||||
'already_settled', false,
|
||||
'match_id', p_match_id,
|
||||
'winner_id', p_winner_id,
|
||||
'entry_fee', v_entry_fee,
|
||||
'rc_prize', v_rc_prize,
|
||||
'house_fee', v_house_total,
|
||||
'participant_cc', v_prize_cc,
|
||||
'settled_at', now()
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
revoke all on function public.settle_match_reward(bigint, bigint) from public;
|
||||
grant execute on function public.settle_match_reward(bigint, bigint) to service_role;
|
||||
@@ -9,8 +9,17 @@ create table public.matches (
|
||||
blue_joined_at timestamp with time zone null,
|
||||
status smallint null default '0'::smallint,
|
||||
winner_id bigint null,
|
||||
escrow_user_id bigint null,
|
||||
entries_collected_at timestamp with time zone null,
|
||||
settled_at timestamp with time zone null,
|
||||
constraint matches_pkey primary key (id),
|
||||
constraint matches_user_blue_fkey foreign KEY (user_blue) references users (id),
|
||||
constraint matches_user_red_fkey foreign KEY (user_red) references users (id),
|
||||
constraint matches_winner_id_fkey foreign KEY (winner_id) references users (id)
|
||||
) TABLESPACE pg_default;
|
||||
constraint matches_winner_id_fkey foreign KEY (winner_id) references users (id),
|
||||
constraint matches_escrow_user_id_fkey foreign KEY (escrow_user_id) references users (id)
|
||||
) TABLESPACE pg_default;
|
||||
|
||||
-- Apply migrations/RPCs in order:
|
||||
-- schemas/alter_matches_escrow.sql
|
||||
-- schemas/rpc_collect_match_entries.sql
|
||||
-- schemas/rpc_settle_match_reward.sql
|
||||
|
||||
@@ -8,5 +8,7 @@ create table public.users (
|
||||
cc real null default '0'::real,
|
||||
rc real null default '0'::real,
|
||||
last_logged_at timestamp with time zone null default now(),
|
||||
session_id text null,
|
||||
last_keepalive_at timestamp with time zone null,
|
||||
constraint users_pkey primary key (id)
|
||||
) TABLESPACE pg_default;
|
||||
Reference in New Issue
Block a user