Files
step-competition/src/database.js
sascha 04891bf449 Refactor monster_catches from table to view and add overwrite option
- Convert monster_catches from table to view for automatic calculation
- Add overwrite checkbox to monster import UI
- Remove manual INSERT/UPDATE logic for catches (now handled by view)
- Simplify API endpoints to query view instead of managing state
- Add confirmation dialog for overwrite operation

Benefits:
- No data duplication
- Always accurate catch status based on current steps
- Simpler codebase with less state management
- Easier to reset steps without orphaned catch records

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 16:08:02 +02:00

123 lines
3.6 KiB
JavaScript

const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const dbPath = path.join(__dirname, '..', 'step_competition.db');
const db = new sqlite3.Database(dbPath);
// Initialize database schema
function initializeDatabase() {
db.serialize(() => {
// Teams table
db.run(`
CREATE TABLE IF NOT EXISTS teams (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Participants table
db.run(`
CREATE TABLE IF NOT EXISTS participants (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
team_id INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (team_id) REFERENCES teams(id)
)
`);
// Daily steps table
db.run(`
CREATE TABLE IF NOT EXISTS daily_steps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
participant_id INTEGER NOT NULL,
date DATE NOT NULL,
steps INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (participant_id) REFERENCES participants(id),
UNIQUE(participant_id, date)
)
`);
// Daily monsters table
db.run(`
CREATE TABLE IF NOT EXISTS daily_monsters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL UNIQUE,
monster_name TEXT NOT NULL,
monster_description TEXT,
step_goal INTEGER NOT NULL,
monster_icon TEXT DEFAULT '👹',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Monster catches view - derived from actual step data
// Drop the old table if it exists and recreate as a view
db.run(`DROP TABLE IF EXISTS monster_catches`);
db.run(`
CREATE VIEW IF NOT EXISTS monster_catches AS
SELECT
dm.id as monster_id,
dm.date,
t.id as team_id,
t.name as team_name,
COALESCE(SUM(ds.steps), 0) as final_steps,
dm.step_goal
FROM daily_monsters dm
CROSS JOIN teams t
LEFT JOIN participants p ON t.id = p.team_id
LEFT JOIN daily_steps ds ON p.id = ds.participant_id AND ds.date = dm.date
GROUP BY dm.id, dm.date, t.id, t.name, dm.step_goal
HAVING final_steps >= dm.step_goal
`);
// Create indexes for better query performance
db.run(`CREATE INDEX IF NOT EXISTS idx_daily_steps_date ON daily_steps(date)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_daily_steps_participant ON daily_steps(participant_id)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_participants_team ON participants(team_id)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_daily_monsters_date ON daily_monsters(date)`);
console.log('Database initialized successfully');
});
}
// Helper function to run queries with promises
function runQuery(sql, params = []) {
return new Promise((resolve, reject) => {
db.run(sql, params, function(err) {
if (err) reject(err);
else resolve({ id: this.lastID, changes: this.changes });
});
});
}
function getQuery(sql, params = []) {
return new Promise((resolve, reject) => {
db.get(sql, params, (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
}
function allQuery(sql, params = []) {
return new Promise((resolve, reject) => {
db.all(sql, params, (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
}
module.exports = {
db,
initializeDatabase,
runQuery,
getQuery,
allQuery
};