prod v1
This commit is contained in:
+68
-5
@@ -1,8 +1,16 @@
|
||||
import express, { Express, Request, Response } from 'express';
|
||||
import * as crypto from 'crypto';
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
import { Settings } from './types';
|
||||
import { createSupabase, supabaseConfigured } from './auth_bridge';
|
||||
|
||||
/** Per player on the match: deducted when a winner is recorded. */
|
||||
const MATCH_ENTRY_FEE_RC = 21;
|
||||
/** Added to the winning player's RC (after entry fee). */
|
||||
const MATCH_WINNER_RC_BONUS = 34;
|
||||
/** Added to each player's CC when a winner is recorded. */
|
||||
const MATCH_PARTICIPANT_CC_REWARD = 100;
|
||||
|
||||
function coerceId(id: unknown): number | null {
|
||||
if (id == null) return null;
|
||||
if (typeof id === 'bigint') return Number(id);
|
||||
@@ -14,11 +22,12 @@ function coerceId(id: unknown): number | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Insert a match row when a new room is created. First player in the room = red. */
|
||||
export async function insertMatchForNewRoom(settings: Settings, userRedId: number): Promise<number | null> {
|
||||
/** Insert a match row when a new room is created. First player in the room = red (if provided). */
|
||||
export async function insertMatchForNewRoom(settings: Settings, userRedId?: number): Promise<number | null> {
|
||||
if (!supabaseConfigured(settings)) return null;
|
||||
const supabase = createSupabase(settings);
|
||||
const { data, error } = await supabase.from('matches').insert({ user_red: userRedId }).select('id').single();
|
||||
const payload = userRedId == null ? {} : { user_red: userRedId };
|
||||
const { data, error } = await supabase.from('matches').insert(payload).select('id').single();
|
||||
if (error || !data) {
|
||||
console.error('matches: insert failed', error?.message);
|
||||
return null;
|
||||
@@ -58,6 +67,36 @@ export async function getPlayerLast10MatchRecord(
|
||||
return { l10_wins, l10_losses };
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry fee from each participant, winner bonus RC, CC for both. Idempotent callers should set winner only once.
|
||||
*/
|
||||
async function applyMatchWinnerEconomy(
|
||||
supabase: SupabaseClient,
|
||||
userRed: number | null,
|
||||
userBlue: number | null,
|
||||
winnerId: number
|
||||
): Promise<string | null> {
|
||||
const ids = [...new Set([userRed, userBlue].filter((x): x is number => x != null && x >= 1))];
|
||||
if (ids.length === 0) return null;
|
||||
|
||||
const { data: rows, error: selErr } = await supabase.from('users').select('id, rc, cc').in('id', ids);
|
||||
if (selErr) return selErr.message;
|
||||
if (!rows || rows.length !== ids.length) return 'One or more players not found in users';
|
||||
|
||||
for (const row of rows) {
|
||||
const uid = coerceId((row as { id: unknown }).id);
|
||||
if (uid == null) continue;
|
||||
const baseRc = Number((row as { rc: unknown }).rc ?? 0);
|
||||
const baseCc = Number((row as { cc: unknown }).cc ?? 0);
|
||||
let newRc = baseRc - MATCH_ENTRY_FEE_RC;
|
||||
if (uid === winnerId) newRc += MATCH_WINNER_RC_BONUS;
|
||||
const newCc = baseCc + MATCH_PARTICIPANT_CC_REWARD;
|
||||
const { error: upErr } = await supabase.from('users').update({ rc: newRc, cc: newCc }).eq('id', uid);
|
||||
if (upErr) return upErr.message;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Second player joining the room = blue. */
|
||||
export async function updateMatchUserBlue(settings: Settings, matchId: number, userBlueId: number): Promise<void> {
|
||||
if (!supabaseConfigured(settings)) return;
|
||||
@@ -233,6 +272,7 @@ export function registerMatchRoutes(app: Express, settings: Settings): void {
|
||||
.from('matches')
|
||||
.update({ winner_id: winnerId })
|
||||
.eq('id', matchId)
|
||||
.is('winner_id', null)
|
||||
.select('id')
|
||||
.maybeSingle();
|
||||
|
||||
@@ -241,9 +281,32 @@ export function registerMatchRoutes(app: Express, settings: Settings): void {
|
||||
return;
|
||||
}
|
||||
if (!updated) {
|
||||
res.status(404).json({ ok: false, error: 'Match not found' });
|
||||
const { data: existing } = await supabase.from('matches').select('id').eq('id', matchId).maybeSingle();
|
||||
if (!existing) {
|
||||
res.status(404).json({ ok: false, error: 'Match not found' });
|
||||
return;
|
||||
}
|
||||
res.status(409).json({ ok: false, error: 'Winner already set for this match' });
|
||||
return;
|
||||
}
|
||||
res.json({ ok: true, id: matchId, winner_id: winnerId });
|
||||
|
||||
const payErr = await applyMatchWinnerEconomy(supabase, userRed, userBlue, winnerId);
|
||||
if (payErr != null) {
|
||||
console.error('matches: economy after winner failed', matchId, payErr);
|
||||
await supabase.from('matches').update({ winner_id: null }).eq('id', matchId).eq('winner_id', winnerId);
|
||||
res.status(500).json({ ok: false, error: `Economy update failed: ${payErr}` });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
id: matchId,
|
||||
winner_id: winnerId,
|
||||
economy: {
|
||||
entry_fee_rc: MATCH_ENTRY_FEE_RC,
|
||||
winner_rc_bonus: MATCH_WINNER_RC_BONUS,
|
||||
participant_cc: MATCH_PARTICIPANT_CC_REWARD,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user