Files
deckerr/src/services/tradesService.ts
2025-11-26 19:12:07 +01:00

328 lines
8.2 KiB
TypeScript

import { supabase } from '../lib/supabase';
export interface TradeItem {
id: string;
trade_id: string;
owner_id: string;
card_id: string;
quantity: number;
}
export interface Trade {
id: string;
user1_id: string;
user2_id: string;
status: 'pending' | 'accepted' | 'declined' | 'cancelled';
message: string | null;
created_at: string | null;
updated_at: string | null;
version: number;
editor_id: string | null;
user1?: { username: string | null };
user2?: { username: string | null };
items?: TradeItem[];
}
export interface TradeHistoryEntry {
id: string;
trade_id: string;
version: number;
editor_id: string;
message: string | null;
created_at: string;
editor?: { username: string | null };
items?: TradeHistoryItem[];
}
export interface TradeHistoryItem {
id: string;
history_id: string;
owner_id: string;
card_id: string;
quantity: number;
}
export interface CreateTradeParams {
user1Id: string;
user2Id: string;
message?: string;
user1Cards: { cardId: string; quantity: number }[];
user2Cards: { cardId: string; quantity: number }[];
}
export interface UpdateTradeParams {
tradeId: string;
editorId: string;
message?: string;
myCards: { cardId: string; quantity: number }[];
theirCards: { cardId: string; quantity: number }[];
}
// Get all trades for a user
export async function getTrades(userId: string): Promise<Trade[]> {
const { data, error } = await supabase
.from('trades')
.select(`
*,
user1:profiles!trades_user1_id_fkey(username),
user2:profiles!trades_user2_id_fkey(username),
items:trade_items(*)
`)
.or(`user1_id.eq.${userId},user2_id.eq.${userId}`)
.order('created_at', { ascending: false });
if (error) throw error;
return data as Trade[];
}
// Get pending trades for a user
export async function getPendingTrades(userId: string): Promise<Trade[]> {
const { data, error } = await supabase
.from('trades')
.select(`
*,
user1:profiles!trades_user1_id_fkey(username),
user2:profiles!trades_user2_id_fkey(username),
items:trade_items(*)
`)
.eq('status', 'pending')
.or(`user1_id.eq.${userId},user2_id.eq.${userId}`)
.order('created_at', { ascending: false });
if (error) throw error;
return data as Trade[];
}
// Get trade by ID
export async function getTradeById(tradeId: string): Promise<Trade | null> {
const { data, error } = await supabase
.from('trades')
.select(`
*,
user1:profiles!trades_user1_id_fkey(username),
user2:profiles!trades_user2_id_fkey(username),
items:trade_items(*)
`)
.eq('id', tradeId)
.single();
if (error) throw error;
return data as Trade;
}
// Create a new trade with items
export async function createTrade(params: CreateTradeParams): Promise<Trade> {
const { user1Id, user2Id, message, user1Cards, user2Cards } = params;
// Create the trade
const { data: trade, error: tradeError } = await supabase
.from('trades')
.insert({
user1_id: user1Id,
user2_id: user2Id,
message,
status: 'pending',
// editor_id starts as null - gets set when someone edits the trade
})
.select()
.single();
if (tradeError) throw tradeError;
// Add user1's cards
const user1Items = user1Cards.map((card) => ({
trade_id: trade.id,
owner_id: user1Id,
card_id: card.cardId,
quantity: card.quantity,
}));
// Add user2's cards
const user2Items = user2Cards.map((card) => ({
trade_id: trade.id,
owner_id: user2Id,
card_id: card.cardId,
quantity: card.quantity,
}));
const allItems = [...user1Items, ...user2Items];
if (allItems.length > 0) {
const { error: itemsError } = await supabase
.from('trade_items')
.insert(allItems);
if (itemsError) throw itemsError;
}
return trade;
}
// Accept a trade (executes the card transfer)
export async function acceptTrade(tradeId: string): Promise<boolean> {
const { data, error } = await supabase.rpc('execute_trade', {
trade_id: tradeId,
});
if (error) throw error;
return data as boolean;
}
// Decline a trade
export async function declineTrade(tradeId: string): Promise<Trade> {
const { data, error } = await supabase
.from('trades')
.update({ status: 'declined', updated_at: new Date().toISOString() })
.eq('id', tradeId)
.select()
.single();
if (error) throw error;
return data;
}
// Cancel a trade (sender only)
export async function cancelTrade(tradeId: string): Promise<Trade> {
const { data, error } = await supabase
.from('trades')
.update({ status: 'cancelled', updated_at: new Date().toISOString() })
.eq('id', tradeId)
.select()
.single();
if (error) throw error;
return data;
}
// Get trade history (completed/cancelled/declined trades)
export async function getTradeHistory(userId: string): Promise<Trade[]> {
const { data, error } = await supabase
.from('trades')
.select(`
*,
user1:profiles!trades_user1_id_fkey(username),
user2:profiles!trades_user2_id_fkey(username),
items:trade_items(*)
`)
.or(`user1_id.eq.${userId},user2_id.eq.${userId}`)
.in('status', ['accepted', 'declined', 'cancelled'])
.order('updated_at', { ascending: false })
.limit(50);
if (error) throw error;
return data as Trade[];
}
// Update an existing trade (for edits and counter-offers)
export async function updateTrade(params: UpdateTradeParams): Promise<Trade> {
const { tradeId, editorId, message, myCards, theirCards } = params;
// Get current trade info
const { data: currentTrade, error: tradeError } = await supabase
.from('trades')
.select('version, user1_id, user2_id')
.eq('id', tradeId)
.single();
if (tradeError) throw tradeError;
const newVersion = (currentTrade.version || 1) + 1;
// Determine the other user's ID
const otherUserId = currentTrade.user1_id === editorId
? currentTrade.user2_id
: currentTrade.user1_id;
// Save current state to history before updating
const { data: historyEntry, error: historyError } = await supabase
.from('trade_history')
.insert({
trade_id: tradeId,
version: currentTrade.version || 1,
editor_id: editorId,
message: message || null,
})
.select()
.single();
if (historyError) throw historyError;
// Save current items to history
const { data: currentItems } = await supabase
.from('trade_items')
.select('*')
.eq('trade_id', tradeId);
if (currentItems && currentItems.length > 0) {
const historyItems = currentItems.map(item => ({
history_id: historyEntry.id,
owner_id: item.owner_id,
card_id: item.card_id,
quantity: item.quantity,
}));
await supabase.from('trade_history_items').insert(historyItems);
}
// Update the trade
const { data: updatedTrade, error: updateError } = await supabase
.from('trades')
.update({
message,
version: newVersion,
editor_id: editorId,
updated_at: new Date().toISOString(),
})
.eq('id', tradeId)
.select()
.single();
if (updateError) throw updateError;
// Delete existing items
await supabase.from('trade_items').delete().eq('trade_id', tradeId);
// Add new items (myCards belong to editor, theirCards belong to other user)
const myItems = myCards.map((card) => ({
trade_id: tradeId,
owner_id: editorId,
card_id: card.cardId,
quantity: card.quantity,
}));
const theirItems = theirCards.map((card) => ({
trade_id: tradeId,
owner_id: otherUserId,
card_id: card.cardId,
quantity: card.quantity,
}));
const allItems = [...myItems, ...theirItems];
if (allItems.length > 0) {
const { error: itemsError } = await supabase
.from('trade_items')
.insert(allItems);
if (itemsError) throw itemsError;
}
return updatedTrade;
}
// Get version history for a trade
export async function getTradeVersionHistory(tradeId: string): Promise<TradeHistoryEntry[]> {
const { data, error } = await supabase
.from('trade_history')
.select(`
*,
editor:profiles!trade_history_editor_id_fkey(username),
items:trade_history_items(*)
`)
.eq('trade_id', tradeId)
.order('version', { ascending: true });
if (error) throw error;
return data as TradeHistoryEntry[];
}