pretty much ready to 'ship'

This commit is contained in:
cottongin
2025-10-30 17:18:30 -04:00
parent 7bb3aabd72
commit 8f3a12ad76
10 changed files with 518 additions and 190 deletions

View File

@@ -84,6 +84,46 @@ function initializeDatabase() {
// Column already exists, ignore error
}
// Add upvotes and downvotes columns to games if they don't exist
try {
db.exec(`ALTER TABLE games ADD COLUMN upvotes INTEGER DEFAULT 0`);
} catch (err) {
// Column already exists, ignore error
}
try {
db.exec(`ALTER TABLE games ADD COLUMN downvotes INTEGER DEFAULT 0`);
} catch (err) {
// Column already exists, ignore error
}
// Migrate existing popularity_score to upvotes/downvotes if needed
try {
const gamesWithScore = db.prepare(`
SELECT id, popularity_score FROM games
WHERE popularity_score != 0 AND (upvotes = 0 AND downvotes = 0)
`).all();
if (gamesWithScore.length > 0) {
const updateGame = db.prepare(`
UPDATE games
SET upvotes = ?, downvotes = ?
WHERE id = ?
`);
for (const game of gamesWithScore) {
if (game.popularity_score > 0) {
updateGame.run(game.popularity_score, 0, game.id);
} else {
updateGame.run(0, Math.abs(game.popularity_score), game.id);
}
}
console.log(`Migrated popularity scores for ${gamesWithScore.length} games`);
}
} catch (err) {
console.error('Error migrating popularity scores:', err);
}
// Packs table for pack-level favoriting
db.exec(`
CREATE TABLE IF NOT EXISTS packs (
@@ -113,6 +153,20 @@ function initializeDatabase() {
)
`);
// Add message_hash column if it doesn't exist
try {
db.exec(`ALTER TABLE chat_logs ADD COLUMN message_hash TEXT`);
} catch (err) {
// Column already exists, ignore error
}
// Create index on message_hash for fast duplicate checking
try {
db.exec(`CREATE INDEX IF NOT EXISTS idx_chat_logs_hash ON chat_logs(message_hash)`);
} catch (err) {
// Index already exists, ignore error
}
console.log('Database initialized successfully');
}

View File

@@ -1,9 +1,18 @@
const express = require('express');
const crypto = require('crypto');
const { authenticateToken } = require('../middleware/auth');
const db = require('../database');
const router = express.Router();
// Helper function to create a hash of a message
function createMessageHash(username, message, timestamp) {
return crypto
.createHash('sha256')
.update(`${username}:${message}:${timestamp}`)
.digest('hex');
}
// Get all sessions
router.get('/', (req, res) => {
try {
@@ -175,7 +184,9 @@ router.get('/:id/games', (req, res) => {
g.game_type,
g.min_players,
g.max_players,
g.popularity_score
g.popularity_score,
g.upvotes,
g.downvotes
FROM session_games sg
JOIN games g ON sg.game_id = g.id
WHERE sg.session_id = ?
@@ -270,7 +281,7 @@ router.post('/:id/chat-import', authenticateToken, (req, res) => {
// Get all games played in this session with timestamps
const sessionGames = db.prepare(`
SELECT sg.game_id, sg.played_at, g.title
SELECT sg.game_id, sg.played_at, g.title, g.upvotes, g.downvotes
FROM session_games sg
JOIN games g ON sg.game_id = g.id
WHERE sg.session_id = ?
@@ -282,15 +293,26 @@ router.post('/:id/chat-import', authenticateToken, (req, res) => {
}
let votesProcessed = 0;
let duplicatesSkipped = 0;
const votesByGame = {};
const sessionId = parseInt(req.params.id);
const voteMatches = []; // Debug: track which votes matched to which games
const insertChatLog = db.prepare(`
INSERT INTO chat_logs (session_id, chatter_name, message, timestamp, parsed_vote)
VALUES (?, ?, ?, ?, ?)
INSERT INTO chat_logs (session_id, chatter_name, message, timestamp, parsed_vote, message_hash)
VALUES (?, ?, ?, ?, ?, ?)
`);
const updatePopularity = db.prepare(`
UPDATE games SET popularity_score = popularity_score + ? WHERE id = ?
const checkDuplicate = db.prepare(`
SELECT id FROM chat_logs WHERE message_hash = ? LIMIT 1
`);
const updateUpvote = db.prepare(`
UPDATE games SET upvotes = upvotes + 1, popularity_score = popularity_score + 1 WHERE id = ?
`);
const updateDownvote = db.prepare(`
UPDATE games SET downvotes = downvotes + 1, popularity_score = popularity_score - 1 WHERE id = ?
`);
const processVotes = db.transaction((messages) => {
@@ -301,6 +323,16 @@ router.post('/:id/chat-import', authenticateToken, (req, res) => {
continue;
}
// Create hash for this message
const messageHash = createMessageHash(username, message, timestamp);
// Check if we've already imported this exact message
const existing = checkDuplicate.get(messageHash);
if (existing) {
duplicatesSkipped++;
continue; // Skip this duplicate message
}
// Check for vote patterns
let vote = null;
if (message.includes('thisgame++')) {
@@ -309,13 +341,14 @@ router.post('/:id/chat-import', authenticateToken, (req, res) => {
vote = 'thisgame--';
}
// Insert into chat logs
// Insert into chat logs with hash
insertChatLog.run(
req.params.id,
sessionId,
username,
message,
timestamp,
vote
vote,
messageHash
);
if (vote) {
@@ -345,8 +378,24 @@ router.post('/:id/chat-import', authenticateToken, (req, res) => {
}
if (matchedGame) {
const points = vote === 'thisgame++' ? 1 : -1;
updatePopularity.run(points, matchedGame.game_id);
const isUpvote = vote === 'thisgame++';
// Debug: track this vote match
voteMatches.push({
username: username,
vote: vote,
timestamp: timestamp,
timestamp_ms: messageTime,
matched_game: matchedGame.title,
game_played_at: matchedGame.played_at,
game_played_at_ms: new Date(matchedGame.played_at).getTime()
});
if (isUpvote) {
updateUpvote.run(matchedGame.game_id);
} else {
updateDownvote.run(matchedGame.game_id);
}
if (!votesByGame[matchedGame.game_id]) {
votesByGame[matchedGame.game_id] = {
@@ -356,7 +405,7 @@ router.post('/:id/chat-import', authenticateToken, (req, res) => {
};
}
if (points > 0) {
if (isUpvote) {
votesByGame[matchedGame.game_id].upvotes++;
} else {
votesByGame[matchedGame.game_id].downvotes++;
@@ -373,8 +422,17 @@ router.post('/:id/chat-import', authenticateToken, (req, res) => {
res.json({
message: 'Chat log imported and processed successfully',
messagesImported: chatData.length,
duplicatesSkipped,
votesProcessed,
votesByGame
votesByGame,
debug: {
sessionGamesTimeline: sessionGames.map(sg => ({
title: sg.title,
played_at: sg.played_at,
played_at_ms: new Date(sg.played_at).getTime()
})),
voteMatches: voteMatches
}
});
} catch (error) {
res.status(500).json({ error: error.message });

View File

@@ -14,14 +14,14 @@ router.get('/', (req, res) => {
activeSessions: db.prepare('SELECT COUNT(*) as count FROM sessions WHERE is_active = 1').get(),
totalGamesPlayed: db.prepare('SELECT COUNT(*) as count FROM session_games').get(),
mostPlayedGames: db.prepare(`
SELECT g.id, g.title, g.pack_name, g.play_count, g.popularity_score
SELECT g.id, g.title, g.pack_name, g.play_count, g.popularity_score, g.upvotes, g.downvotes
FROM games g
WHERE g.play_count > 0
ORDER BY g.play_count DESC
LIMIT 10
`).all(),
topRatedGames: db.prepare(`
SELECT g.id, g.title, g.pack_name, g.play_count, g.popularity_score
SELECT g.id, g.title, g.pack_name, g.play_count, g.popularity_score, g.upvotes, g.downvotes
FROM games g
WHERE g.popularity_score > 0
ORDER BY g.popularity_score DESC