refactor: remove all SQLite code, standardize on PostgreSQL

- Remove database provider switching logic from prisma.ts and auth.ts
- Hardcode PostgreSQL as the only supported database
- Remove switch-database.js and use-dev-db.js scripts
- Remove Python utility scripts that used sqlite3 directly
- Update justfile to remove SQLite test targets
- Update package.json to remove db:switch script
- Update Dockerfile.ci-base to default to PostgreSQL
- Update .env.example to remove SQLite mention
- Update playwright.config.ts comments
- Update .gitignore to remove SQLite file patterns

This eliminates the root cause of test failures: the dev server and test
Prisma client were using different databases (PostgreSQL vs SQLite).
This commit is contained in:
2026-05-02 04:09:37 -07:00
parent 08152fba51
commit 28fecdfaad
14 changed files with 91 additions and 873 deletions
-138
View File
@@ -1,138 +0,0 @@
#!/usr/bin/env python3
"""
Generate 100+ games to test ELO rating calculations
"""
import sqlite3
import random
from datetime import datetime, timedelta
DB_PATH = "prisma/prisma/dev.db"
def get_players():
"""Get all players from the database"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT id, name, currentElo FROM players")
players = cursor.fetchall()
conn.close()
return players
def generate_game(players, game_num, base_date):
"""Generate a single game with random players and scores"""
# Randomly select 4 different players
selected_players = random.sample(players, 4)
p1, p2, p3, p4 = selected_players
# Randomly assign teams (Team 1 vs Team 2)
team1_p1 = p1
team1_p2 = p2
team2_p1 = p3
team2_p2 = p4
# Generate realistic scores (Euchre games typically 10 points max)
# Team 1 wins 60% of the time for variety
team1_wins = random.random() < 0.6
if team1_wins:
# Team 1 wins - generate scores
team1_score = random.randint(10, 15)
team2_score = random.randint(0, 9)
else:
# Team 2 wins - generate scores
team2_score = random.randint(10, 15)
team1_score = random.randint(0, 9)
# Generate random date in the past 30 days
days_ago = random.randint(0, 30)
game_date = base_date - timedelta(
days=days_ago, hours=random.randint(0, 23), minutes=random.randint(0, 59)
)
return {
"team1P1Id": team1_p1[0],
"team1P2Id": team1_p2[0],
"team2P1Id": team2_p1[0],
"team2P2Id": team2_p2[0],
"team1Score": team1_score,
"team2Score": team2_score,
"playedAt": game_date.isoformat() + "Z",
"status": "completed",
"eventId": None,
}
def insert_games(games):
"""Insert games into the database"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
for game in games:
cursor.execute(
"""
INSERT INTO matches
(team1P1Id, team1P2Id, team2P1Id, team2P2Id, team1Score, team2Score, playedAt, status, eventId, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
game["team1P1Id"],
game["team1P2Id"],
game["team2P1Id"],
game["team2P2Id"],
game["team1Score"],
game["team2Score"],
game["playedAt"],
game["status"],
game["eventId"],
datetime.now().isoformat() + "Z",
datetime.now().isoformat() + "Z",
),
)
conn.commit()
conn.close()
def main():
print("Generating 150 games to test ELO ratings...")
print("=" * 60)
players = get_players()
print(f"Found {len(players)} players in database")
# Generate 150 games
num_games = 150
base_date = datetime.now()
games = []
for i in range(num_games):
game = generate_game(players, i, base_date)
games.append(game)
print(f"Generated {len(games)} games")
# Insert games into database
print("Inserting games into database...")
insert_games(games)
print("Games inserted successfully!")
# Show sample games
print("\nSample games:")
print("-" * 80)
for i, game in enumerate(games[:3]):
print(
f"Game {i + 1}: Team 1 ({game['team1Score']}) vs Team 2 ({game['team2Score']})"
)
print(f" Team 1: Player {game['team1P1Id']} + Player {game['team1P2Id']}")
print(f" Team 2: Player {game['team2P1Id']} + Player {game['team2P2Id']}")
print(f" Date: {game['playedAt']}")
print()
print("=" * 60)
print(f"Total games generated: {num_games}")
print("Now check the ELO ratings by running the application!")
if __name__ == "__main__":
main()
-234
View File
@@ -1,234 +0,0 @@
#!/usr/bin/env python3
"""
Update partnership statistics based on matches in the database
"""
import sqlite3
import math
from datetime import datetime
DB_PATH = "prisma/prisma/dev.db"
K_FACTOR = 32
def get_all_matches():
"""Get all matches from the database"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
SELECT id, team1P1Id, team1P2Id, team2P1Id, team2P2Id,
team1Score, team2Score, playedAt
FROM matches
ORDER BY playedAt
""")
matches = cursor.fetchall()
conn.close()
return matches
def get_or_create_partnership(player1_id, player2_id):
"""Get or create a partnership record"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# Sort IDs to ensure consistent partnership lookup
p1 = min(player1_id, player2_id)
p2 = max(player1_id, player2_id)
cursor.execute(
"""
SELECT id, gamesPlayed, wins, losses, totalEloChange
FROM partnership_stats
WHERE player1Id = ? AND player2Id = ?
""",
(p1, p2),
)
result = cursor.fetchone()
if result:
conn.close()
return result
# Create new partnership record
cursor.execute(
"""
INSERT INTO partnership_stats (player1Id, player2Id, gamesPlayed, wins, losses, totalEloChange, lastPlayed, createdAt, updatedAt)
VALUES (?, ?, 0, 0, 0, 0, NULL, ?, ?)
""",
(p1, p2, datetime.now().isoformat() + "Z", datetime.now().isoformat() + "Z"),
)
partnership_id = cursor.lastrowid
conn.commit()
conn.close()
return (partnership_id, 0, 0, 0, 0)
def update_partnership_stats(player1_id, player2_id, won, elo_change):
"""Update partnership statistics"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# Sort IDs to ensure consistent partnership lookup
p1 = min(player1_id, player2_id)
p2 = max(player1_id, player2_id)
# Get current partnership stats
cursor.execute(
"""
SELECT id, gamesPlayed, wins, losses, totalEloChange
FROM partnership_stats
WHERE player1Id = ? AND player2Id = ?
""",
(p1, p2),
)
result = cursor.fetchone()
if not result:
# Create new partnership record
cursor.execute(
"""
INSERT INTO partnership_stats (player1Id, player2Id, gamesPlayed, wins, losses, totalEloChange, lastPlayed, createdAt, updatedAt)
VALUES (?, ?, 1, ?, ?, ?, ?, ?, ?)
""",
(
p1,
p2,
1 if won else 0,
0 if won else 1,
elo_change,
datetime.now().isoformat() + "Z",
datetime.now().isoformat() + "Z",
datetime.now().isoformat() + "Z",
),
)
else:
partnership_id, games_played, wins, losses, total_elo_change = result
# Update partnership stats
new_games = games_played + 1
new_wins = wins + 1 if won else wins
new_losses = losses if won else losses + 1
new_total_elo_change = total_elo_change + elo_change
cursor.execute(
"""
UPDATE partnership_stats
SET gamesPlayed = ?, wins = ?, losses = ?, totalEloChange = ?, lastPlayed = ?, updatedAt = ?
WHERE id = ?
""",
(
new_games,
new_wins,
new_losses,
new_total_elo_change,
datetime.now().isoformat() + "Z",
datetime.now().isoformat() + "Z",
partnership_id,
),
)
conn.commit()
conn.close()
def main():
print("Updating partnership statistics based on matches...")
print("=" * 60)
# Get all matches
matches = get_all_matches()
print(f"Found {len(matches)} matches in database")
# Reset partnership stats
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("DELETE FROM partnership_stats")
conn.commit()
conn.close()
print("Reset all partnership statistics")
# Process each match
match_count = 0
for (
match_id,
team1_p1,
team1_p2,
team2_p1,
team2_p2,
team1_score,
team2_score,
played_at,
) in matches:
# Determine winners
team1_won = team1_score > team2_score
team2_won = team2_score > team1_score
# Update partnership stats for Team 1
if team1_won:
update_partnership_stats(
team1_p1, team1_p2, True, 0
) # Elo change will be calculated separately
else:
update_partnership_stats(team1_p1, team1_p2, False, 0)
# Update partnership stats for Team 2
if team2_won:
update_partnership_stats(team2_p1, team2_p2, True, 0)
else:
update_partnership_stats(team2_p1, team2_p2, False, 0)
match_count += 1
if match_count % 20 == 0:
print(f"Processed {match_count}/{len(matches)} matches...")
print(f"Processed {match_count} matches")
# Display partnership stats for top players
print("\n" + "=" * 60)
print("Partnership Stats for Top Players:")
print("-" * 60)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# Get top players by ELO
cursor.execute("SELECT id, name FROM players ORDER BY currentElo DESC LIMIT 5")
top_players = cursor.fetchall()
for player_id, player_name in top_players:
print(f"\n{player_name}:")
# Get partnership stats for this player
cursor.execute(
"""
SELECT
CASE
WHEN player1Id = ? THEN (SELECT name FROM players WHERE id = player2Id)
ELSE (SELECT name FROM players WHERE id = player1Id)
END as partner_name,
gamesPlayed, wins, losses, totalEloChange
FROM partnership_stats
WHERE player1Id = ? OR player2Id = ?
ORDER BY gamesPlayed DESC
LIMIT 3
""",
(player_id, player_id, player_id),
)
partnerships = cursor.fetchall()
for partner_name, games, wins, losses, elo_change in partnerships:
win_rate = (wins / games * 100) if games > 0 else 0
print(
f" - with {partner_name}: {games} games, {wins}/{losses} ({win_rate:.1f}%) ELO: {elo_change:+d}"
)
conn.close()
print("\n" + "=" * 60)
print("Partnership statistics updated successfully!")
if __name__ == "__main__":
main()
-195
View File
@@ -1,195 +0,0 @@
#!/usr/bin/env python3
"""
Update player statistics (ELO, gamesPlayed, wins, losses) based on matches in the database
"""
import sqlite3
import math
DB_PATH = "prisma/prisma/dev.db"
K_FACTOR = 32 # Standard K-factor for Elo calculations
def get_all_matches():
"""Get all matches from the database"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
SELECT id, team1P1Id, team1P2Id, team2P1Id, team2P2Id,
team1Score, team2Score, playedAt
FROM matches
ORDER BY playedAt
""")
matches = cursor.fetchall()
conn.close()
return matches
def get_player(player_id):
"""Get a player by ID"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(
"SELECT id, name, currentElo, gamesPlayed, wins, losses FROM players WHERE id = ?",
(player_id,),
)
player = cursor.fetchone()
conn.close()
return player
def update_player(player_id, current_elo, games_played, wins, losses):
"""Update a player's statistics"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(
"""
UPDATE players
SET currentElo = ?, gamesPlayed = ?, wins = ?, losses = ?
WHERE id = ?
""",
(current_elo, games_played, wins, losses, player_id),
)
conn.commit()
conn.close()
def calculate_elo_change(rating_a, rating_b, score_a, score_b):
"""Calculate Elo change for a match"""
# Calculate expected scores
expected_a = 1 / (1 + math.pow(10, (rating_b - rating_a) / 400))
expected_b = 1 - expected_a
# Actual scores (1 for win, 0.5 for tie, 0 for loss)
actual_a = 0.5 if score_a == score_b else (1 if score_a > score_b else 0)
actual_b = 0.5 if score_a == score_b else (1 if score_b > score_a else 0)
# Calculate Elo change
elo_change_a = K_FACTOR * (actual_a - expected_a)
elo_change_b = K_FACTOR * (actual_b - expected_b)
return elo_change_a, elo_change_b
def main():
print("Updating player statistics based on matches in database...")
print("=" * 60)
# Get all matches
matches = get_all_matches()
print(f"Found {len(matches)} matches in database")
# Reset all player stats to 0 before recalculating
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(
"UPDATE players SET currentElo = 1000, gamesPlayed = 0, wins = 0, losses = 0"
)
conn.commit()
conn.close()
print("Reset all player statistics to initial values (ELO: 1000, games: 0)")
# Process each match
match_count = 0
for (
match_id,
team1_p1,
team1_p2,
team2_p1,
team2_p2,
team1_score,
team2_score,
played_at,
) in matches:
# Get player data
p1 = get_player(team1_p1)
p2 = get_player(team1_p2)
p3 = get_player(team2_p1)
p4 = get_player(team2_p2)
if not all([p1, p2, p3, p4]):
print(f"Warning: Could not find all players for match {match_id}")
continue
# Calculate team ratings
team1_rating = (p1[2] + p2[2]) / 2 # currentElo
team2_rating = (p3[2] + p4[2]) / 2 # currentElo
# Calculate Elo changes
team1_elo_change, team2_elo_change = calculate_elo_change(
team1_rating, team2_rating, team1_score, team2_score
)
# Individual Elo changes (split evenly between team members)
p1_elo_change = team1_elo_change / 2
p2_elo_change = team1_elo_change / 2
p3_elo_change = team2_elo_change / 2
p4_elo_change = team2_elo_change / 2
# Determine winners
team1_won = team1_score > team2_score
team2_won = team2_score > team1_score
# Update player 1 stats
p1_new_elo = int(p1[2] + p1_elo_change)
p1_new_games = p1[3] + 1
p1_new_wins = p1[4] + 1 if team1_won else p1[4]
p1_new_losses = p1[5] if team1_won else p1[5] + 1
update_player(p1[0], p1_new_elo, p1_new_games, p1_new_wins, p1_new_losses)
# Update player 2 stats
p2_new_elo = int(p2[2] + p2_elo_change)
p2_new_games = p2[3] + 1
p2_new_wins = p2[4] + 1 if team1_won else p2[4]
p2_new_losses = p2[5] if team1_won else p2[5] + 1
update_player(p2[0], p2_new_elo, p2_new_games, p2_new_wins, p2_new_losses)
# Update player 3 stats
p3_new_elo = int(p3[2] + p3_elo_change)
p3_new_games = p3[3] + 1
p3_new_wins = p3[4] + 1 if team2_won else p3[4]
p3_new_losses = p3[5] if team2_won else p3[5] + 1
update_player(p3[0], p3_new_elo, p3_new_games, p3_new_wins, p3_new_losses)
# Update player 4 stats
p4_new_elo = int(p4[2] + p4_elo_change)
p4_new_games = p4[3] + 1
p4_new_wins = p4[4] + 1 if team2_won else p4[4]
p4_new_losses = p4[5] if team2_won else p4[5] + 1
update_player(p4[0], p4_new_elo, p4_new_games, p4_new_wins, p4_new_losses)
match_count += 1
if match_count % 20 == 0:
print(f"Processed {match_count}/{len(matches)} matches...")
print(f"Processed {match_count} matches")
# Display updated player rankings
print("\n" + "=" * 60)
print("Top 10 Players by ELO Rating:")
print("-" * 60)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
SELECT id, name, currentElo, gamesPlayed, wins, losses
FROM players
ORDER BY currentElo DESC
LIMIT 10
""")
top_players = cursor.fetchall()
conn.close()
for rank, (player_id, name, elo, games, wins, losses) in enumerate(top_players, 1):
win_rate = (wins / games * 100) if games > 0 else 0
print(
f"{rank:2}. {name:15} | ELO: {elo:4} | Games: {games:3} | W/L: {wins}/{losses} ({win_rate:.1f}%)"
)
print("\n" + "=" * 60)
print("Player statistics updated successfully!")
print("Now run the application to see updated rankings.")
if __name__ == "__main__":
main()
-108
View File
@@ -1,108 +0,0 @@
#!/usr/bin/env node
/**
* Script to switch between SQLite and PostgreSQL databases
* Usage: node scripts/switch-database.js [sqlite|postgres]
*/
const fs = require('fs');
const path = require('path');
const envFile = '.env';
const envExampleFile = '.env.example';
function updateEnvFile(provider) {
const envPath = path.join(process.cwd(), envFile);
// Read current .env file or create from example
let envContent = '';
if (fs.existsSync(envPath)) {
envContent = fs.readFileSync(envPath, 'utf8');
} else if (fs.existsSync(envExampleFile)) {
envContent = fs.readFileSync(envExampleFile, 'utf8');
}
// Update DATABASE_PROVIDER (note: this is for reference only, not used by Prisma)
const providerRegex = /^DATABASE_PROVIDER=.*$/m;
if (providerRegex.test(envContent)) {
envContent = envContent.replace(providerRegex, `DATABASE_PROVIDER=${provider}`);
} else {
envContent += `\nDATABASE_PROVIDER=${provider}\n`;
}
// Update DATABASE_URL based on provider
if (provider === 'sqlite') {
const sqliteUrl = 'DATABASE_URL="file:./prisma/dev.db"';
const urlRegex = /^DATABASE_URL=.*$/m;
if (urlRegex.test(envContent)) {
envContent = envContent.replace(urlRegex, sqliteUrl);
} else {
envContent += `${sqliteUrl}\n`;
}
} else if (provider === 'postgresql') {
const pgUrl = 'DATABASE_URL="postgresql://username:password@localhost:5432/euchre_camp"';
const urlRegex = /^DATABASE_URL=.*$/m;
if (urlRegex.test(envContent)) {
envContent = envContent.replace(urlRegex, pgUrl);
} else {
envContent += `${pgUrl}\n`;
}
}
// Write updated content
fs.writeFileSync(envPath, envContent);
console.log(`✅ Updated ${envFile} to use ${provider} database`);
}
function updateSchemaFile(provider) {
const schemaPath = path.join(process.cwd(), 'prisma', 'schema.prisma');
if (!fs.existsSync(schemaPath)) {
console.error(`❌ Schema file not found: ${schemaPath}`);
return false;
}
let schemaContent = fs.readFileSync(schemaPath, 'utf8');
// Update the provider in the datasource block
const providerRegex = /(datasource db\s*\{[^}]*provider\s*=\s*)"[^"]+"/;
if (providerRegex.test(schemaContent)) {
schemaContent = schemaContent.replace(
providerRegex,
`$1"${provider}"`
);
fs.writeFileSync(schemaPath, schemaContent);
console.log(`✅ Updated schema.prisma to use ${provider} provider`);
return true;
} else {
console.error(`❌ Could not find provider declaration in schema.prisma`);
return false;
}
}
function main() {
const args = process.argv.slice(2);
const provider = args[0];
if (!provider) {
console.error('❌ Usage: node scripts/switch-database.js [sqlite|postgres]');
process.exit(1);
}
if (!['sqlite', 'postgres', 'postgresql'].includes(provider)) {
console.error(`❌ Invalid provider: ${provider}. Must be 'sqlite' or 'postgres'`);
process.exit(1);
}
const normalizedProvider = provider === 'postgres' ? 'postgresql' : provider;
updateEnvFile(normalizedProvider);
updateSchemaFile(normalizedProvider);
console.log('\nNext steps:');
console.log('1. Run: npx prisma generate');
console.log('2. Run: npx prisma migrate deploy');
console.log('3. Restart your development server');
}
main();
-49
View File
@@ -1,49 +0,0 @@
#!/usr/bin/env node
/**
* Switch to development database
* Creates a .env.development.local file with development database settings
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const envDevPath = path.join(__dirname, '..', '.env.development');
const envDevLocalPath = path.join(__dirname, '..', '.env.development.local');
console.log('🔧 Setting up development database...\n');
// Check if .env.development exists
if (!fs.existsSync(envDevPath)) {
console.error('❌ .env.development file not found');
console.error('Please create it first or run: npm run db:setup-dev');
process.exit(1);
}
// Copy .env.development to .env.development.local if it doesn't exist
if (!fs.existsSync(envDevLocalPath)) {
fs.copyFileSync(envDevPath, envDevLocalPath);
console.log('✅ Created .env.development.local');
} else {
console.log('️ .env.development.local already exists');
}
// Set NODE_ENV for the current session
process.env.NODE_ENV = 'development';
process.env.DATABASE_PROVIDER = 'postgresql';
// Read the development database URL
const envContent = fs.readFileSync(envDevPath, 'utf8');
const match = envContent.match(/DATABASE_URL="([^"]+)"/);
if (match) {
process.env.DATABASE_URL = match[1];
console.log(`✅ Development database URL: ${process.env.DATABASE_URL}`);
}
console.log('\n✅ Development database configured!');
console.log('\nNext steps:');
console.log('1. Setup the dev database: npm run db:setup-dev');
console.log('2. Start development server: npm run dev');
console.log('\nNote: This script sets environment variables for the current session.');
console.log('For persistent configuration, use .env.development.local');