feat(matches): add fixture type handling and notes to manual match creation; update localization for match types
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 56s

This commit is contained in:
Torsten Schulz (local)
2026-08-20 08:39:52 +02:00
parent 18ba120927
commit eb1c4d5842
7 changed files with 140 additions and 11 deletions

View File

@@ -6,6 +6,7 @@
"scripts": {
"postinstall": "cd ../frontend && npm install && npm run build",
"dev": "nodemon server.js",
"db:migrate": "node ./scripts/runMigrations.js",
"cleanup:usertoken": "node ./scripts/cleanupUserTokenKeys.js",
"cleanup:indexes": "node ./scripts/cleanupAllIndexes.js"
},

View File

@@ -0,0 +1,68 @@
import sequelize from '../database.js';
const migrations = [
{
id: '20260819_add_manual_match_fields',
async up() {
const [fixtureTypeColumns] = await sequelize.query("SHOW COLUMNS FROM `match` LIKE 'fixture_type'");
if (fixtureTypeColumns.length === 0) {
await sequelize.query("ALTER TABLE `match` ADD COLUMN `fixture_type` VARCHAR(32) NOT NULL DEFAULT 'league'");
}
const [notesColumns] = await sequelize.query("SHOW COLUMNS FROM `match` LIKE 'notes'");
if (notesColumns.length === 0) {
await sequelize.query('ALTER TABLE `match` ADD COLUMN `notes` TEXT NULL');
}
},
},
{
id: '20260819_create_kaisertisch_tournaments',
async up() {
await sequelize.query(`
CREATE TABLE IF NOT EXISTS kaisertisch_tournaments (
id INT AUTO_INCREMENT PRIMARY KEY,
club_id INT NOT NULL,
diary_date_id INT NOT NULL,
state JSON NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_kaisertisch_tournament_club_date (club_id, diary_date_id),
CONSTRAINT fk_kaisertisch_tournament_club FOREIGN KEY (club_id) REFERENCES clubs(id) ON DELETE CASCADE,
CONSTRAINT fk_kaisertisch_tournament_date FOREIGN KEY (diary_date_id) REFERENCES diary_dates(id) ON DELETE CASCADE
)
`);
},
},
];
async function runMigrations() {
await sequelize.authenticate();
await sequelize.query(`
CREATE TABLE IF NOT EXISTS schema_migrations (
id VARCHAR(191) PRIMARY KEY,
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
`);
const [appliedRows] = await sequelize.query('SELECT id FROM schema_migrations');
const applied = new Set(appliedRows.map((row) => row.id));
for (const migration of migrations) {
if (applied.has(migration.id)) continue;
console.log(`[migration] Applying ${migration.id}`);
await migration.up();
await sequelize.query('INSERT INTO schema_migrations (id) VALUES (:id)', {
replacements: { id: migration.id },
});
console.log(`[migration] Applied ${migration.id}`);
}
}
runMigrations()
.catch((error) => {
console.error('[migration] Failed:', error);
process.exitCode = 1;
})
.finally(async () => {
await sequelize.close();
});

View File

@@ -51,6 +51,10 @@ class MatchService {
const time = String(payload.time || '').trim();
const opponentName = String(payload.opponentName || '').trim();
const homeAway = payload.homeAway === 'away' ? 'away' : 'home';
const fixtureType = String(payload.fixtureType || 'league').trim();
if (!['league', 'cup'].includes(fixtureType)) {
throw new HttpError('Ungültiger Spieltyp', 400);
}
if (!Number.isInteger(clubTeamId) || !/^\d{4}-\d{2}-\d{2}$/.test(date) || !/^\d{2}:\d{2}$/.test(time) || !opponentName) {
throw new HttpError('Mannschaft, Gegner, Datum und Uhrzeit sind erforderlich', 400);
}
@@ -78,7 +82,7 @@ class MatchService {
date: new Date(`${date}T${time}:00`), time, clubId: parsedClubId, leagueId: league.id, locationId,
homeTeamId: homeAway === 'home' ? ownTeam.id : opponentTeam.id,
guestTeamId: homeAway === 'home' ? opponentTeam.id : ownTeam.id,
fixtureType: 'cup', notes: String(payload.notes || '').trim() || null
fixtureType, notes: String(payload.notes || '').trim() || null
});
return { id: match.id, clubId: match.clubId, match: await this.enrichMatch(match) };
}