Completed matches scheduling

This commit is contained in:
2025-07-20 01:08:51 +02:00
parent 99e8b4ce55
commit f2efe52fcb
10 changed files with 554 additions and 368 deletions

205
index.js
View File

@@ -19,24 +19,11 @@ const PORT = process.env.PORT || 4000;
const JWT_SECRET = process.env.JWT_SECRET || 'changeme'; const JWT_SECRET = process.env.JWT_SECRET || 'changeme';
// Serve static files from the React build
app.use(express.static(path.join(__dirname, 'public')));
// API routes // API routes
app.get('/api/health', (req, res) => { app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() }); res.json({ status: 'ok', timestamp: new Date().toISOString() });
}); });
// Serve React app for all non-API routes
app.get('*', (req, res) => {
// Don't serve React app for API routes
if (req.path.startsWith('/api/')) {
return res.status(404).json({ error: 'API endpoint not found' });
}
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Player registration // Player registration
app.post('/api/players', async (req, res) => { app.post('/api/players', async (req, res) => {
const { name, email } = req.body; const { name, email } = req.body;
@@ -204,6 +191,177 @@ function generateRoundRobinPairs(teams) {
return pairs; return pairs;
} }
// Helper to reorder matches so no team plays two games consecutively
function reorderMatchesNoConsecutiveGames(pairs) {
const result = [];
const used = new Array(pairs.length).fill(false);
let lastTeams = new Set();
for (let i = 0; i < pairs.length; i++) {
let found = false;
for (let j = 0; j < pairs.length; j++) {
if (used[j]) continue;
const [a, b] = pairs[j];
if (!lastTeams.has(a.id) && !lastTeams.has(b.id)) {
result.push(pairs[j]);
used[j] = true;
lastTeams = new Set([a.id, b.id]);
found = true;
break;
}
}
if (!found) {
// If not possible, just pick the next unused
for (let j = 0; j < pairs.length; j++) {
if (!used[j]) {
result.push(pairs[j]);
used[j] = true;
lastTeams = new Set([pairs[j][0].id, pairs[j][1].id]);
break;
}
}
}
}
return result;
}
// Robust round robin scheduler (circle method)
function generateRoundRobinSchedule(pool) {
const teams = [...pool];
const n = teams.length;
const rounds = [];
// If odd, add a dummy team (bye)
let isOdd = false;
if (n % 2 === 1) {
teams.push({ id: -1, name: 'BYE' });
isOdd = true;
}
const numRounds = teams.length - 1;
const half = teams.length / 2;
for (let round = 0; round < numRounds; round++) {
const matches = [];
for (let i = 0; i < half; i++) {
const t1 = teams[i];
const t2 = teams[teams.length - 1 - i];
if (t1.id !== -1 && t2.id !== -1) {
matches.push([t1, t2]);
}
}
rounds.push(matches);
// Rotate teams (except the first)
teams.splice(1, 0, teams.pop());
}
return rounds;
}
// Interleave matches from rounds to avoid consecutive games for any team
function interleaveRounds(rounds) {
const interleaved = [];
const maxLen = Math.max(...rounds.map(r => r.length));
for (let i = 0; i < maxLen; i++) {
for (let j = 0; j < rounds.length; j++) {
if (rounds[j][i]) {
interleaved.push(rounds[j][i]);
}
}
}
return interleaved;
}
// Simple and working match ordering
function orderMatchesNoConsecutiveGames(pairs) {
console.log("orderMatchesNoConsecutiveGames called with", pairs.length, "pairs");
const n = pairs.length;
const result = [];
const used = new Array(n).fill(false);
// Keep track of teams that played in the last match
let lastMatchTeams = new Set();
// Simple approach: always pick a match where neither team played in the last match
while (result.length < n) {
let foundGoodMatch = false;
// First pass: look for matches with no consecutive games
for (let i = 0; i < n; i++) {
if (used[i]) continue;
const [team1, team2] = pairs[i];
// Check if either team played in the last match
if (lastMatchTeams.has(team1.id) || lastMatchTeams.has(team2.id)) {
continue; // Skip this match
}
// Found a good match
used[i] = true;
result.push(pairs[i]);
lastMatchTeams = new Set([team1.id, team2.id]);
foundGoodMatch = true;
break;
}
// If no good match found, pick any available match
if (!foundGoodMatch) {
for (let i = 0; i < n; i++) {
if (used[i]) continue;
used[i] = true;
result.push(pairs[i]);
const [team1, team2] = pairs[i];
lastMatchTeams = new Set([team1.id, team2.id]);
break;
}
}
}
console.log("orderMatchesNoConsecutiveGames returning", result.length, "matches");
return result;
}
// Simple greedy ordering for other cases
function simpleGreedyOrder(pairs) {
const n = pairs.length;
const result = [];
const used = new Array(n).fill(false);
let lastMatchTeams = new Set();
while (result.length < n) {
let foundGoodMatch = false;
// Look for matches with no consecutive games
for (let i = 0; i < n; i++) {
if (used[i]) continue;
const [team1, team2] = pairs[i];
if (lastMatchTeams.has(team1.id) || lastMatchTeams.has(team2.id)) {
continue;
}
used[i] = true;
result.push(pairs[i]);
lastMatchTeams = new Set([team1.id, team2.id]);
foundGoodMatch = true;
break;
}
// If no good match found, pick any available match
if (!foundGoodMatch) {
for (let i = 0; i < n; i++) {
if (used[i]) continue;
used[i] = true;
result.push(pairs[i]);
const [team1, team2] = pairs[i];
lastMatchTeams = new Set([team1.id, team2.id]);
break;
}
}
}
return result;
}
// Admin: Schedule round robin matches (teams) // Admin: Schedule round robin matches (teams)
app.post('/api/admin/schedule/roundrobin', requireAdmin, async (req, res) => { app.post('/api/admin/schedule/roundrobin', requireAdmin, async (req, res) => {
try { try {
@@ -264,8 +422,10 @@ app.post('/api/admin/schedule/roundrobin', requireAdmin, async (req, res) => {
const now = new Date(); const now = new Date();
let matchesData = []; let matchesData = [];
pools.forEach((pool, poolIdx) => { pools.forEach((pool, poolIdx) => {
const pairs = generateRoundRobinPairs(pool); const rounds = generateRoundRobinSchedule(pool);
matchesData.push(...pairs.map(([t1, t2]) => ({ const pairs = rounds.flat();
const orderedMatches = orderMatchesNoConsecutiveGames(pairs);
matchesData.push(...orderedMatches.map(([t1, t2]) => ({
stageId: stage.id, stageId: stage.id,
team1Id: t1.id, team1Id: t1.id,
team2Id: t2.id, team2Id: t2.id,
@@ -807,8 +967,10 @@ async function createPoolsAutomatically() {
const now = new Date(); const now = new Date();
let matchesData = []; let matchesData = [];
pools.forEach((pool, poolIdx) => { pools.forEach((pool, poolIdx) => {
const pairs = generateRoundRobinPairs(pool); const rounds = generateRoundRobinSchedule(pool);
matchesData.push(...pairs.map(([t1, t2]) => ({ const pairs = rounds.flat();
const orderedMatches = orderMatchesNoConsecutiveGames(pairs);
matchesData.push(...orderedMatches.map(([t1, t2]) => ({
stageId: stage.id, stageId: stage.id,
team1Id: t1.id, team1Id: t1.id,
team2Id: t2.id, team2Id: t2.id,
@@ -1081,6 +1243,15 @@ app.post('/api/admin/reset-tournament', requireAdmin, async (req, res) => {
} }
}); });
app.use(express.static(path.join(__dirname, 'public')));
app.get('/*', (req, res) => {
// Don't serve React app for API routes
if (req.path.startsWith('/api/')) {
return res.status(404).json({ error: 'API endpoint not found' });
}
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
const server = http.createServer(app); const server = http.createServer(app);
const io = new Server(server, { cors: { origin: '*' } }); const io = new Server(server, { cors: { origin: '*' } });

550
package-lock.json generated
View File

@@ -13,7 +13,7 @@
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.0", "dotenv": "^17.2.0",
"express": "^5.1.0", "express": "^4.21.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jspdf-autotable": "^5.0.2", "jspdf-autotable": "^5.0.2",
"multer": "^2.0.1", "multer": "^2.0.1",
@@ -153,13 +153,13 @@
"peer": true "peer": true
}, },
"node_modules/accepts": { "node_modules/accepts": {
"version": "2.0.0", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-types": "^3.0.0", "mime-types": "~2.1.34",
"negotiator": "^1.0.0" "negotiator": "0.6.3"
}, },
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -171,6 +171,12 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/atob": { "node_modules/atob": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
@@ -214,23 +220,27 @@
} }
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "2.2.0", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "^3.1.2", "bytes": "3.1.2",
"content-type": "^1.0.5", "content-type": "~1.0.5",
"debug": "^4.4.0", "debug": "2.6.9",
"http-errors": "^2.0.0", "depd": "2.0.0",
"iconv-lite": "^0.6.3", "destroy": "1.2.0",
"on-finished": "^2.4.1", "http-errors": "2.0.0",
"qs": "^6.14.0", "iconv-lite": "0.4.24",
"raw-body": "^3.0.0", "on-finished": "2.4.1",
"type-is": "^2.0.0" "qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/btoa": { "node_modules/btoa": {
@@ -344,9 +354,9 @@
} }
}, },
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "1.0.0", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safe-buffer": "5.2.1" "safe-buffer": "5.2.1"
@@ -374,13 +384,10 @@
} }
}, },
"node_modules/cookie-signature": { "node_modules/cookie-signature": {
"version": "1.2.2", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT", "license": "MIT"
"engines": {
"node": ">=6.6.0"
}
}, },
"node_modules/core-js": { "node_modules/core-js": {
"version": "3.44.0", "version": "3.44.0",
@@ -420,22 +427,20 @@
} }
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.1", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "2.0.0"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
} }
}, },
"node_modules/debug/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -445,6 +450,16 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.2.6", "version": "3.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
@@ -535,19 +550,6 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/engine.io/node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/debug": { "node_modules/engine.io/node_modules/debug": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@@ -565,36 +567,6 @@
} }
} }
}, },
"node_modules/engine.io/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -641,47 +613,60 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "5.1.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "^2.0.0", "accepts": "~1.3.8",
"body-parser": "^2.2.0", "array-flatten": "1.1.1",
"content-disposition": "^1.0.0", "body-parser": "1.20.3",
"content-type": "^1.0.5", "content-disposition": "0.5.4",
"cookie": "^0.7.1", "content-type": "~1.0.4",
"cookie-signature": "^1.2.1", "cookie": "0.7.1",
"debug": "^4.4.0", "cookie-signature": "1.0.6",
"encodeurl": "^2.0.0", "debug": "2.6.9",
"escape-html": "^1.0.3", "depd": "2.0.0",
"etag": "^1.8.1", "encodeurl": "~2.0.0",
"finalhandler": "^2.1.0", "escape-html": "~1.0.3",
"fresh": "^2.0.0", "etag": "~1.8.1",
"http-errors": "^2.0.0", "finalhandler": "1.3.1",
"merge-descriptors": "^2.0.0", "fresh": "0.5.2",
"mime-types": "^3.0.0", "http-errors": "2.0.0",
"on-finished": "^2.4.1", "merge-descriptors": "1.0.3",
"once": "^1.4.0", "methods": "~1.1.2",
"parseurl": "^1.3.3", "on-finished": "2.4.1",
"proxy-addr": "^2.0.7", "parseurl": "~1.3.3",
"qs": "^6.14.0", "path-to-regexp": "0.1.12",
"range-parser": "^1.2.1", "proxy-addr": "~2.0.7",
"router": "^2.2.0", "qs": "6.13.0",
"send": "^1.1.0", "range-parser": "~1.2.1",
"serve-static": "^2.2.0", "safe-buffer": "5.2.1",
"statuses": "^2.0.1", "send": "0.19.0",
"type-is": "^2.0.1", "serve-static": "1.16.2",
"vary": "^1.1.2" "setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 0.10.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/express/node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fflate": { "node_modules/fflate": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
@@ -690,17 +675,18 @@
"peer": true "peer": true
}, },
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "2.1.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.4.0", "debug": "2.6.9",
"encodeurl": "^2.0.0", "encodeurl": "~2.0.0",
"escape-html": "^1.0.3", "escape-html": "~1.0.3",
"on-finished": "^2.4.1", "on-finished": "2.4.1",
"parseurl": "^1.3.3", "parseurl": "~1.3.3",
"statuses": "^2.0.1" "statuses": "2.0.1",
"unpipe": "~1.0.0"
}, },
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -716,12 +702,12 @@
} }
}, },
"node_modules/fresh": { "node_modules/fresh": {
"version": "2.0.0", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.6"
} }
}, },
"node_modules/function-bind": { "node_modules/function-bind": {
@@ -837,22 +823,13 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/http-errors/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.6.3", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0" "safer-buffer": ">= 2.1.2 < 3"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -873,12 +850,6 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
@@ -1011,42 +982,60 @@
} }
}, },
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "1.1.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.6"
} }
}, },
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "2.0.0", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT", "license": "MIT",
"engines": {
"node": ">=18"
},
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": { "node_modules/mime-db": {
"version": "1.54.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/mime-types": { "node_modules/mime-types": {
"version": "3.0.1", "version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "^1.54.0" "mime-db": "1.52.0"
}, },
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -1097,53 +1086,10 @@
"node": ">= 10.16.0" "node": ">= 10.16.0"
} }
}, },
"node_modules/multer/node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/multer/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/multer/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/multer/node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/negotiator": { "node_modules/negotiator": {
"version": "1.0.0", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -1203,15 +1149,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -1222,13 +1159,10 @@
} }
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "8.2.0", "version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT", "license": "MIT"
"engines": {
"node": ">=16"
}
}, },
"node_modules/performance-now": { "node_modules/performance-now": {
"version": "2.1.0", "version": "2.1.0",
@@ -1405,12 +1339,12 @@
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.0", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"side-channel": "^1.1.0" "side-channel": "^1.0.6"
}, },
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
@@ -1440,14 +1374,14 @@
} }
}, },
"node_modules/raw-body": { "node_modules/raw-body": {
"version": "3.0.0", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"iconv-lite": "0.6.3", "iconv-lite": "0.4.24",
"unpipe": "1.0.0" "unpipe": "1.0.0"
}, },
"engines": { "engines": {
@@ -1487,22 +1421,6 @@
"node": ">= 0.8.15" "node": ">= 0.8.15"
} }
}, },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -1542,40 +1460,51 @@
} }
}, },
"node_modules/send": { "node_modules/send": {
"version": "1.2.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.3.5", "debug": "2.6.9",
"encodeurl": "^2.0.0", "depd": "2.0.0",
"escape-html": "^1.0.3", "destroy": "1.2.0",
"etag": "^1.8.1", "encodeurl": "~1.0.2",
"fresh": "^2.0.0", "escape-html": "~1.0.3",
"http-errors": "^2.0.0", "etag": "~1.8.1",
"mime-types": "^3.0.1", "fresh": "0.5.2",
"ms": "^2.1.3", "http-errors": "2.0.0",
"on-finished": "^2.4.1", "mime": "1.6.0",
"range-parser": "^1.2.1", "ms": "2.1.3",
"statuses": "^2.0.1" "on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
} }
}, },
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "2.2.0", "version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"encodeurl": "^2.0.0", "encodeurl": "~2.0.0",
"escape-html": "^1.0.3", "escape-html": "~1.0.3",
"parseurl": "^1.3.3", "parseurl": "~1.3.3",
"send": "^1.2.0" "send": "0.19.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 0.8.0"
} }
}, },
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
@@ -1731,19 +1660,6 @@
} }
} }
}, },
"node_modules/socket.io/node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/socket.io/node_modules/debug": { "node_modules/socket.io/node_modules/debug": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@@ -1761,36 +1677,6 @@
} }
} }
}, },
"node_modules/socket.io/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/socket.io/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/socket.io/node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/split2": { "node_modules/split2": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -1812,9 +1698,9 @@
} }
}, },
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -1876,14 +1762,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/type-is": { "node_modules/type-is": {
"version": "2.0.1", "version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"content-type": "^1.0.5", "media-typer": "0.3.0",
"media-typer": "^1.1.0", "mime-types": "~2.1.24"
"mime-types": "^3.0.0"
}, },
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -1916,6 +1801,15 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/utrie": { "node_modules/utrie": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
@@ -1960,12 +1854,6 @@
"webidl-conversions": "^3.0.0" "webidl-conversions": "^3.0.0"
} }
}, },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/ws": { "node_modules/ws": {
"version": "8.17.1", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",

View File

@@ -15,7 +15,7 @@
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.0", "dotenv": "^17.2.0",
"express": "^5.1.0", "express": "^4.21.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jspdf-autotable": "^5.0.2", "jspdf-autotable": "^5.0.2",
"multer": "^2.0.1", "multer": "^2.0.1",

View File

@@ -0,0 +1,26 @@
const { PrismaClient } = require('../generated/prisma');
async function main() {
const prisma = new PrismaClient();
// Find the round robin stage(s)
const rrStages = await prisma.tournamentStage.findMany({ where: { type: 'ROUND_ROBIN' } });
const rrStageIds = rrStages.map(s => s.id);
if (rrStageIds.length === 0) {
console.log('No round robin stage found.');
await prisma.$disconnect();
return;
}
// Find all matches for these stages
const rrMatches = await prisma.match.findMany({ where: { stageId: { in: rrStageIds } } });
const rrMatchIds = rrMatches.map(m => m.id);
// Delete results for these matches
await prisma.result.deleteMany({ where: { matchId: { in: rrMatchIds } } });
// Delete matches
await prisma.match.deleteMany({ where: { id: { in: rrMatchIds } } });
// Delete the round robin stages
await prisma.tournamentStage.deleteMany({ where: { id: { in: rrStageIds } } });
console.log(`Deleted ${rrStages.length} round robin stage(s), ${rrMatches.length} matches, and their results.`);
await prisma.$disconnect();
}
main().catch(e => { console.error(e); process.exit(1); });

View File

@@ -58,7 +58,7 @@ async function resetTournament() {
console.log('🏆 Creating new tournament...'); console.log('🏆 Creating new tournament...');
const tournament = await prisma.tournament.create({ const tournament = await prisma.tournament.create({
data: { data: {
name: 'Championship Tournament', name: 'Spring Championship 2024',
date: new Date(), date: new Date(),
location: 'Main Arena' location: 'Main Arena'
} }

74
scripts/testSchedule.js Normal file
View File

@@ -0,0 +1,74 @@
const fetch = require('node-fetch');
async function testSchedule() {
try {
console.log('Testing current round robin schedule...\n');
// Get all matches
const matchesRes = await fetch('http://localhost:4000/api/matches');
const matches = await matchesRes.json();
if (!Array.isArray(matches)) {
console.log('Failed to fetch matches:', matches);
return;
}
// Filter round robin matches
const roundRobinMatches = matches.filter(match => match.stage.type === 'ROUND_ROBIN');
if (roundRobinMatches.length === 0) {
console.log('No round robin matches found.');
return;
}
// Group matches by pool
const pools = {};
roundRobinMatches.forEach(match => {
if (!pools[match.pool]) {
pools[match.pool] = [];
}
pools[match.pool].push(match);
});
// Check each pool for consecutive games
Object.keys(pools).forEach(poolNum => {
console.log(`\n=== Pool ${poolNum} ===`);
const poolMatches = pools[poolNum];
let consecutiveGames = 0;
poolMatches.forEach((match, index) => {
const team1Id = match.team1.id;
const team2Id = match.team2.id;
console.log(`Match ${index + 1}: ${match.team1.name} vs ${match.team2.name}`);
// Check if either team played in the previous match
if (index > 0) {
const prevMatch = poolMatches[index - 1];
const prevTeams = [prevMatch.team1.id, prevMatch.team2.id];
if (prevTeams.includes(team1Id)) {
console.log(` ⚠️ ${match.team1.name} played in previous match!`);
consecutiveGames++;
}
if (prevTeams.includes(team2Id)) {
console.log(` ⚠️ ${match.team2.name} played in previous match!`);
consecutiveGames++;
}
}
});
console.log(`\nPool ${poolNum} consecutive games: ${consecutiveGames}`);
});
console.log('\n=== Summary ===');
console.log(`Total round robin matches: ${roundRobinMatches.length}`);
console.log(`Total pools: ${Object.keys(pools).length}`);
} catch (error) {
console.error('Error testing schedule:', error);
}
}
testSchedule();

View File

@@ -57,11 +57,22 @@ function App() {
setLoadingTournaments(true); setLoadingTournaments(true);
try { try {
const res = await fetch('/api/tournaments'); const res = await fetch('/api/tournaments');
if (res.ok) { const data = await res.json();
const data = await res.json(); if (Array.isArray(data)) {
setTournaments(data); setTournaments(data);
// Automatically select the most recent tournament if none is selected
const storedName = localStorage.getItem('tournamentName');
if ((!storedName || storedName === '') && data.length > 0) {
setTournamentName(data[0].name);
setTournamentDate(data[0].date);
setTournamentLocation(data[0].location);
localStorage.setItem('tournamentName', data[0].name);
localStorage.setItem('tournamentDate', data[0].date);
localStorage.setItem('tournamentLocation', data[0].location);
}
} else { } else {
console.error('Failed to load tournaments'); setTournaments([]);
console.error('API /api/tournaments did not return an array:', data);
} }
} catch (err) { } catch (err) {
console.error('Error loading tournaments:', err); console.error('Error loading tournaments:', err);

View File

@@ -3,12 +3,12 @@ import './MatchesSchedule.css';
import TeamLogo from './TeamLogo'; import TeamLogo from './TeamLogo';
const poolColors = [ const poolColors = [
'#f8fafc', // light blue '#bae6fd', // darker blue
'#fef9c3', // light yellow '#fde68a', // darker yellow
'#fce7f3', // light pink '#f9a8d4', // darker pink
'#d1fae5', // light green '#6ee7b7', // darker green
'#fee2e2', // light red '#fca5a5', // darker red
'#e0e7ff', // light purple '#a5b4fc', // darker purple
]; ];
function groupMatchesByPoolAndRound(matches) { function groupMatchesByPoolAndRound(matches) {

View File

@@ -2,14 +2,26 @@ import React, { useEffect, useState } from 'react';
import TeamLogo from './TeamLogo'; import TeamLogo from './TeamLogo';
const poolColors = [ const poolColors = [
'#f8fafc', // light blue '#bae6fd', // darker blue
'#fef9c3', // light yellow '#fde68a', // darker yellow
'#fce7f3', // light pink '#f9a8d4', // darker pink
'#d1fae5', // light green '#6ee7b7', // darker green
'#fee2e2', // light red '#fca5a5', // darker red
'#e0e7ff', // light purple '#a5b4fc', // darker purple
]; ];
// Helper to convert hex color to rgba with alpha
function hexToRgba(hex, alpha) {
// Remove # if present
hex = hex.replace('#', '');
// Parse r, g, b
const bigint = parseInt(hex, 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return `rgba(${r},${g},${b},${alpha})`;
}
const Pools = ({ token, onTournamentNameChange }) => { const Pools = ({ token, onTournamentNameChange }) => {
const [matches, setMatches] = useState([]); const [matches, setMatches] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -212,8 +224,12 @@ const Pools = ({ token, onTournamentNameChange }) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{poolMap[pool].map(match => ( {poolMap[pool].map((match, rowIdx) => (
<tr key={match.id}> <tr key={match.id} style={{
background: rowIdx % 2 === 0
? '#fff'
: hexToRgba(poolColors[idx % poolColors.length], 0.5)
}}>
<td style={{ textAlign: 'center', padding: '8px' }}> <td style={{ textAlign: 'center', padding: '8px' }}>
<TeamLogo team={match.team1} size="small" /> <TeamLogo team={match.team1} size="small" />
</td> </td>

View File

@@ -33,7 +33,7 @@ const UserMenu = ({ onSelect, token, onResetTournament, tournamentStarted }) =>
<li onClick={() => handleSelect('profile')}>Login</li> <li onClick={() => handleSelect('profile')}>Login</li>
) : ( ) : (
<> <>
<li onClick={() => handleSelect('create-tournament')}>Create tournament</li> <li onClick={() => handleSelect('create-tournament')}>Create new tournament</li>
<li onClick={() => handleSelect('load-tournaments')}>Load tournaments</li> <li onClick={() => handleSelect('load-tournaments')}>Load tournaments</li>
<li <li
className={`start-tournament-option${tournamentStarted ? ' disabled' : ''}`} className={`start-tournament-option${tournamentStarted ? ' disabled' : ''}`}