|
| 1 | +"use strict"; |
| 2 | +var __importDefault = (this && this.__importDefault) || function (mod) { |
| 3 | + return (mod && mod.__esModule) ? mod : { "default": mod }; |
| 4 | +}; |
| 5 | +Object.defineProperty(exports, "__esModule", { value: true }); |
| 6 | +const express_1 = __importDefault(require("express")); |
| 7 | +const pg_1 = require("pg"); |
| 8 | +const dotenv_1 = __importDefault(require("dotenv")); |
| 9 | +const path_1 = __importDefault(require("path")); |
| 10 | +dotenv_1.default.config(); |
| 11 | +const app = (0, express_1.default)(); |
| 12 | +const pool = new pg_1.Pool({ connectionString: process.env.DATABASE_URL }); |
| 13 | +app.set("view engine", "ejs"); |
| 14 | +app.set("views", path_1.default.join(__dirname, "../views")); |
| 15 | +app.use(express_1.default.static(path_1.default.join(__dirname, "../public"))); |
| 16 | +app.use(express_1.default.json()); |
| 17 | +app.get("/", async (_req, res) => { |
| 18 | + const result = await pool.query(` |
| 19 | + SELECT u.name, s.score, to_char(s.timestamp, 'HH24:MI:SS') AS time |
| 20 | + FROM scores s |
| 21 | + JOIN users u ON u.id = s.user_id |
| 22 | + ORDER BY s.score DESC |
| 23 | + LIMIT 10 |
| 24 | + `); |
| 25 | + res.render("index", { scores: result.rows }); |
| 26 | +}); |
| 27 | +app.get("/player/:name", async (req, res) => { |
| 28 | + const name = req.params.name; |
| 29 | + const stats = await pool.query(` |
| 30 | + SELECT |
| 31 | + COUNT(*) AS games_played, |
| 32 | + MAX(score) AS high_score, |
| 33 | + ROUND(AVG(score))::int AS avg_score, |
| 34 | + MAX(timestamp) AS last_played |
| 35 | + FROM scores s |
| 36 | + JOIN users u ON u.id = s.user_id |
| 37 | + WHERE u.name = $1 |
| 38 | + `, [name]); |
| 39 | + const recent = await pool.query(` |
| 40 | + SELECT score, to_char(s.timestamp, 'YYYY-MM-DD HH24:MI') as time |
| 41 | + FROM scores s |
| 42 | + JOIN users u ON u.id = s.user_id |
| 43 | + WHERE u.name = $1 |
| 44 | + ORDER BY s.timestamp DESC |
| 45 | + LIMIT 10 |
| 46 | + `, [name]); |
| 47 | + const leaderboardContext = await pool.query(` |
| 48 | + WITH ranked AS ( |
| 49 | + SELECT |
| 50 | + u.name, |
| 51 | + s.score, |
| 52 | + RANK() OVER (ORDER BY s.score DESC) AS rank |
| 53 | + FROM scores s |
| 54 | + JOIN users u ON u.id = s.user_id |
| 55 | + ), |
| 56 | + target AS ( |
| 57 | + SELECT rank FROM ranked WHERE name = $1 ORDER BY score DESC LIMIT 1 |
| 58 | + ) |
| 59 | + SELECT * FROM ranked |
| 60 | + WHERE rank BETWEEN (SELECT rank FROM target) - 2 AND (SELECT rank FROM target) + 2 |
| 61 | + ORDER BY rank; |
| 62 | + `, [name]); |
| 63 | + res.render("player", { |
| 64 | + name, |
| 65 | + stats: stats.rows[0], |
| 66 | + recent: recent.rows, |
| 67 | + leaderboardContext: leaderboardContext.rows, |
| 68 | + }); |
| 69 | +}); |
| 70 | +app.post("/submit", async (req, res) => { |
| 71 | + const { name, score } = req.body; |
| 72 | + const parsedScore = parseInt(score, 10); |
| 73 | + if (!name || isNaN(parsedScore)) |
| 74 | + return res.status(400).send("Invalid input"); |
| 75 | + const userId = await findOrCreateUser(name); |
| 76 | + await pool.query(`INSERT INTO scores (user_id, score) VALUES ($1, $2)`, [ |
| 77 | + userId, |
| 78 | + parsedScore, |
| 79 | + ]); |
| 80 | + res.redirect(`/player/${encodeURIComponent(name)}`); |
| 81 | +}); |
| 82 | +// Helpers |
| 83 | +async function findOrCreateUser(name) { |
| 84 | + const existing = await pool.query(`SELECT id FROM users WHERE name = $1`, [ |
| 85 | + name, |
| 86 | + ]); |
| 87 | + if (existing.rows.length > 0) |
| 88 | + return existing.rows[0].id; |
| 89 | + const created = await pool.query(`INSERT INTO users (name) VALUES ($1) RETURNING id`, [name]); |
| 90 | + return created.rows[0].id; |
| 91 | +} |
| 92 | +const PORT = process.env.PORT || 3123; |
| 93 | +app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`)); |
0 commit comments