mirror of
https://github.com/DeNNiiInc/Connect-5.git
synced 2026-04-17 18:26:01 +00:00
- Replace @supabase/supabase-js with native pg library - Rewrite database.js to use PostgreSQL connection pool - Update server.js with PostgreSQL connection testing - Create postgres-schema.sql with complete database schema - Add apply-schema.js script for easy schema deployment - Update all documentation (README.md, DEPLOYMENT.md, deploy.sh) - Remove Supabase-specific files and references - Update db.config.example.js with PostgreSQL format
248 lines
7.9 KiB
JavaScript
248 lines
7.9 KiB
JavaScript
const { Pool } = require('pg');
|
|
|
|
// Import database configuration from external file
|
|
// This file (db.config.js) is not committed to git for security
|
|
// Use db.config.example.js as a template
|
|
const dbConfig = require('./db.config.js');
|
|
|
|
// Create PostgreSQL connection pool
|
|
const pool = new Pool({
|
|
host: dbConfig.HOST,
|
|
user: dbConfig.USER,
|
|
password: dbConfig.PASSWORD,
|
|
database: dbConfig.DB,
|
|
port: 5432,
|
|
max: dbConfig.pool.max,
|
|
min: dbConfig.pool.min,
|
|
acquireTimeoutMillis: dbConfig.pool.acquire,
|
|
idleTimeoutMillis: dbConfig.pool.idle
|
|
});
|
|
|
|
// Handle pool errors
|
|
pool.on('error', (err) => {
|
|
console.error('Unexpected error on idle PostgreSQL client', err);
|
|
});
|
|
|
|
// Initialize database schema
|
|
async function initializeDatabase() {
|
|
try {
|
|
console.log('🔄 Initializing PostgreSQL database schema...');
|
|
|
|
// Check if tables exist by trying to query them
|
|
const result = await pool.query(`
|
|
SELECT EXISTS (
|
|
SELECT FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'players'
|
|
);
|
|
`);
|
|
|
|
if (!result.rows[0].exists) {
|
|
console.log('⚠️ Tables not found. Please run the following SQL in your PostgreSQL database:');
|
|
console.log(`
|
|
Run the postgres-schema.sql file in your PostgreSQL database:
|
|
psql -h ${dbConfig.HOST} -U ${dbConfig.USER} -d ${dbConfig.DB} -f postgres-schema.sql
|
|
`);
|
|
throw new Error('Database tables not initialized. Please run postgres-schema.sql first.');
|
|
}
|
|
|
|
console.log('✅ Database schema verified successfully');
|
|
} catch (error) {
|
|
console.error('❌ Error initializing database:', error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Database query functions
|
|
const db = {
|
|
// Create or get player
|
|
async createPlayer(username) {
|
|
try {
|
|
// First try to get existing player
|
|
const selectResult = await pool.query(
|
|
'SELECT id FROM players WHERE username = $1',
|
|
[username]
|
|
);
|
|
|
|
if (selectResult.rows.length > 0) {
|
|
return selectResult.rows[0].id;
|
|
}
|
|
|
|
// If not found, create new player
|
|
const insertResult = await pool.query(
|
|
'INSERT INTO players (username) VALUES ($1) RETURNING id',
|
|
[username]
|
|
);
|
|
|
|
return insertResult.rows[0].id;
|
|
} catch (error) {
|
|
console.error('Error creating player:', error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
// Get player by username
|
|
async getPlayer(username) {
|
|
const result = await pool.query(
|
|
'SELECT * FROM players WHERE username = $1',
|
|
[username]
|
|
);
|
|
|
|
return result.rows[0] || null;
|
|
},
|
|
|
|
// Get player by ID
|
|
async getPlayerById(playerId) {
|
|
const result = await pool.query(
|
|
'SELECT * FROM players WHERE id = $1',
|
|
[playerId]
|
|
);
|
|
|
|
return result.rows[0] || null;
|
|
},
|
|
|
|
// Add active session
|
|
async addSession(sessionId, playerId, username) {
|
|
await pool.query(
|
|
`INSERT INTO active_sessions (session_id, player_id, username, last_heartbeat)
|
|
VALUES ($1, $2, $3, NOW())
|
|
ON CONFLICT (session_id)
|
|
DO UPDATE SET last_heartbeat = NOW()`,
|
|
[sessionId, playerId, username]
|
|
);
|
|
},
|
|
|
|
// Remove session
|
|
async removeSession(sessionId) {
|
|
await pool.query(
|
|
'DELETE FROM active_sessions WHERE session_id = $1',
|
|
[sessionId]
|
|
);
|
|
},
|
|
|
|
// Get all active players
|
|
async getActivePlayers() {
|
|
const result = await pool.query(
|
|
`SELECT
|
|
s.session_id,
|
|
s.username,
|
|
p.total_wins,
|
|
p.total_losses,
|
|
p.total_draws
|
|
FROM active_sessions s
|
|
INNER JOIN players p ON s.player_id = p.id
|
|
WHERE s.last_heartbeat > NOW() - INTERVAL '2 minutes'`
|
|
);
|
|
|
|
return result.rows;
|
|
},
|
|
|
|
// Create new game
|
|
async createGame(player1Id, player2Id, player1Username, player2Username, boardSize) {
|
|
const result = await pool.query(
|
|
`INSERT INTO games (player1_id, player2_id, player1_username, player2_username, board_size, game_state)
|
|
VALUES ($1, $2, $3, $4, $5, 'active')
|
|
RETURNING id`,
|
|
[player1Id, player2Id, player1Username, player2Username, boardSize]
|
|
);
|
|
|
|
return result.rows[0].id;
|
|
},
|
|
|
|
// Record move
|
|
async recordMove(gameId, playerId, row, col, moveNumber) {
|
|
await pool.query(
|
|
`INSERT INTO game_moves (game_id, player_id, row_position, col_position, move_number)
|
|
VALUES ($1, $2, $3, $4, $5)`,
|
|
[gameId, playerId, row, col, moveNumber]
|
|
);
|
|
},
|
|
|
|
// Complete game
|
|
async completeGame(gameId, winnerId) {
|
|
// Update game status
|
|
await pool.query(
|
|
`UPDATE games
|
|
SET game_state = 'completed', winner_id = $1, completed_at = NOW()
|
|
WHERE id = $2`,
|
|
[winnerId, gameId]
|
|
);
|
|
|
|
// Update player stats
|
|
if (winnerId) {
|
|
// Get game details
|
|
const gameResult = await pool.query(
|
|
'SELECT player1_id, player2_id FROM games WHERE id = $1',
|
|
[gameId]
|
|
);
|
|
|
|
if (gameResult.rows.length > 0) {
|
|
const game = gameResult.rows[0];
|
|
const loserId = game.player1_id === winnerId ? game.player2_id : game.player1_id;
|
|
|
|
// Update winner
|
|
await pool.query('SELECT increment_wins($1)', [winnerId]);
|
|
|
|
// Update loser
|
|
await pool.query('SELECT increment_losses($1)', [loserId]);
|
|
}
|
|
} else {
|
|
// Draw - update both players
|
|
const gameResult = await pool.query(
|
|
'SELECT player1_id, player2_id FROM games WHERE id = $1',
|
|
[gameId]
|
|
);
|
|
|
|
if (gameResult.rows.length > 0) {
|
|
const game = gameResult.rows[0];
|
|
await pool.query('SELECT increment_draws($1)', [game.player1_id]);
|
|
await pool.query('SELECT increment_draws($1)', [game.player2_id]);
|
|
}
|
|
}
|
|
},
|
|
|
|
// Abandon game
|
|
async abandonGame(gameId, winnerId) {
|
|
// Update game status
|
|
await pool.query(
|
|
`UPDATE games
|
|
SET game_state = 'abandoned', winner_id = $1, completed_at = NOW()
|
|
WHERE id = $2`,
|
|
[winnerId, gameId]
|
|
);
|
|
|
|
// Update stats (winner gets win, other player gets loss)
|
|
if (winnerId) {
|
|
const gameResult = await pool.query(
|
|
'SELECT player1_id, player2_id FROM games WHERE id = $1',
|
|
[gameId]
|
|
);
|
|
|
|
if (gameResult.rows.length > 0) {
|
|
const game = gameResult.rows[0];
|
|
const loserId = game.player1_id === winnerId ? game.player2_id : game.player1_id;
|
|
await pool.query('SELECT increment_wins($1)', [winnerId]);
|
|
await pool.query('SELECT increment_losses($1)', [loserId]);
|
|
}
|
|
}
|
|
},
|
|
|
|
// Update heartbeat
|
|
async updateHeartbeat(sessionId) {
|
|
await pool.query(
|
|
'UPDATE active_sessions SET last_heartbeat = NOW() WHERE session_id = $1',
|
|
[sessionId]
|
|
);
|
|
},
|
|
|
|
// Clean up stale sessions
|
|
async cleanupStaleSessions() {
|
|
await pool.query(
|
|
`DELETE FROM active_sessions
|
|
WHERE last_heartbeat < NOW() - INTERVAL '2 minutes'`
|
|
);
|
|
}
|
|
};
|
|
|
|
module.exports = { pool, initializeDatabase, db };
|