203 lines
7.2 KiB
JavaScript
203 lines
7.2 KiB
JavaScript
const sqlite3 = require('sqlite3').verbose();
|
|
const logger = require('./logger');
|
|
|
|
const dbPath = process.env.DB_PATH || './ratings.db';
|
|
const db = new sqlite3.Database(dbPath);
|
|
|
|
function initializeDatabase() {
|
|
return new Promise((resolve, reject) => {
|
|
db.serialize(() => {
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS players (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
pdga_number INTEGER UNIQUE NOT NULL,
|
|
name TEXT NOT NULL,
|
|
current_rating INTEGER,
|
|
rating_change INTEGER,
|
|
last_updated DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
last_round_update DATETIME DEFAULT NULL
|
|
)
|
|
`);
|
|
|
|
db.get("PRAGMA table_info(players)", (err, info) => {
|
|
if (err) {
|
|
logger.error('Error checking table schema:', err);
|
|
return;
|
|
}
|
|
|
|
db.all("PRAGMA table_info(players)", (err, columns) => {
|
|
if (err) {
|
|
logger.error('Error getting table info:', err);
|
|
return;
|
|
}
|
|
|
|
const hasLastRoundUpdate = columns.some(col => col.name === 'last_round_update');
|
|
const hasPredictedRating = columns.some(col => col.name === 'predicted_rating');
|
|
const hasStdDev = columns.some(col => col.name === 'std_dev');
|
|
const hasExcludedRoundsCount = columns.some(col => col.name === 'excluded_rounds_count');
|
|
const hasCutoffRating = columns.some(col => col.name === 'cutoff_rating');
|
|
|
|
if (!hasLastRoundUpdate) {
|
|
logger.info('Adding last_round_update column to players table...');
|
|
db.run(`ALTER TABLE players ADD COLUMN last_round_update DATETIME DEFAULT NULL`, (err) => {
|
|
if (err) logger.error('Error adding last_round_update column:', err.message);
|
|
else logger.info('Successfully added last_round_update column');
|
|
});
|
|
}
|
|
|
|
if (!hasPredictedRating) {
|
|
logger.info('Adding predicted_rating column to players table...');
|
|
db.run(`ALTER TABLE players ADD COLUMN predicted_rating INTEGER DEFAULT NULL`, (err) => {
|
|
if (err) logger.error('Error adding predicted_rating column:', err.message);
|
|
else logger.info('Successfully added predicted_rating column');
|
|
});
|
|
}
|
|
|
|
if (!hasStdDev) {
|
|
logger.info('Adding std_dev column to players table...');
|
|
db.run(`ALTER TABLE players ADD COLUMN std_dev INTEGER DEFAULT NULL`, (err) => {
|
|
if (err) logger.error('Error adding std_dev column:', err.message);
|
|
else logger.info('Successfully added std_dev column');
|
|
});
|
|
}
|
|
|
|
if (!hasExcludedRoundsCount) {
|
|
logger.info('Adding excluded_rounds_count column to players table...');
|
|
db.run(`ALTER TABLE players ADD COLUMN excluded_rounds_count INTEGER DEFAULT NULL`, (err) => {
|
|
if (err) logger.error('Error adding excluded_rounds_count column:', err.message);
|
|
else logger.info('Successfully added excluded_rounds_count column');
|
|
});
|
|
}
|
|
|
|
if (!hasCutoffRating) {
|
|
logger.info('Adding cutoff_rating column to players table...');
|
|
db.run(`ALTER TABLE players ADD COLUMN cutoff_rating INTEGER DEFAULT NULL`, (err) => {
|
|
if (err) logger.error('Error adding cutoff_rating column:', err.message);
|
|
else logger.info('Successfully added cutoff_rating column');
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS round_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
player_id INTEGER NOT NULL,
|
|
date DATE NOT NULL,
|
|
competition_name TEXT NOT NULL,
|
|
rating INTEGER NOT NULL,
|
|
FOREIGN KEY (player_id) REFERENCES players (id)
|
|
)
|
|
`);
|
|
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS rating_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
player_id INTEGER NOT NULL,
|
|
date DATE NOT NULL,
|
|
rating INTEGER NOT NULL,
|
|
FOREIGN KEY (player_id) REFERENCES players (id)
|
|
)
|
|
`);
|
|
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS courses (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
link TEXT UNIQUE NOT NULL,
|
|
city TEXT,
|
|
last_updated DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS layouts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
course_id INTEGER NOT NULL,
|
|
name TEXT NOT NULL,
|
|
par INTEGER NOT NULL,
|
|
mean_rating INTEGER,
|
|
rating_count INTEGER DEFAULT 0,
|
|
last_calculated DATETIME,
|
|
FOREIGN KEY (course_id) REFERENCES courses (id),
|
|
UNIQUE(course_id, name, par)
|
|
)
|
|
`, (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
db.run(`ALTER TABLE layouts ADD COLUMN mean_rating INTEGER`, () => {
|
|
db.run(`ALTER TABLE layouts ADD COLUMN rating_count INTEGER DEFAULT 0`, () => {
|
|
db.run(`ALTER TABLE layouts ADD COLUMN last_calculated DATETIME`, () => {
|
|
db.run(`ALTER TABLE layouts ADD COLUMN last_played DATE`, () => {
|
|
logger.info('Database initialized successfully');
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
async function checkAndPopulateDatabase() {
|
|
const fs = require('fs');
|
|
const { scrapePDGARating } = require('./services/player-service');
|
|
|
|
try {
|
|
const playerCount = await new Promise((resolve, reject) => {
|
|
db.get('SELECT COUNT(*) as count FROM players', [], (err, row) => {
|
|
if (err) reject(err);
|
|
else resolve(row.count);
|
|
});
|
|
});
|
|
|
|
if (playerCount > 0) {
|
|
logger.info(`✓ Database already has ${playerCount} players - skipping text file import`);
|
|
logger.info('📝 Note: pdga-numbers.txt is only used when database is empty');
|
|
return;
|
|
}
|
|
|
|
logger.info('=== Database is empty - populating from PDGA numbers file ===');
|
|
|
|
const pdgaNumbers = fs.readFileSync('pdga-numbers.txt', 'utf-8')
|
|
.split('\n')
|
|
.map(num => num.trim())
|
|
.filter(num => num);
|
|
|
|
logger.info(`Found ${pdgaNumbers.length} PDGA numbers in file`);
|
|
|
|
if (pdgaNumbers.length === 0) {
|
|
logger.info('⚠ No PDGA numbers found in file');
|
|
return;
|
|
}
|
|
|
|
logger.info('Populating database with players from file...');
|
|
|
|
for (let i = 0; i < pdgaNumbers.length; i++) {
|
|
const pdgaNumber = pdgaNumbers[i];
|
|
logger.info(`[${i + 1}/${pdgaNumbers.length}] Adding PDGA ${pdgaNumber}...`);
|
|
|
|
try {
|
|
const playerData = await scrapePDGARating(pdgaNumber);
|
|
logger.info(` ✓ Added ${playerData.name}`);
|
|
|
|
if (i < pdgaNumbers.length - 1) {
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
}
|
|
} catch (error) {
|
|
logger.error(` ✗ Failed to add PDGA ${pdgaNumber}:`, error.message);
|
|
}
|
|
}
|
|
|
|
logger.info('=== Database population complete ===');
|
|
|
|
} catch (error) {
|
|
logger.error('Error during database population check:', error.message);
|
|
}
|
|
}
|
|
|
|
module.exports = { db, initializeDatabase, checkAndPopulateDatabase };
|