new controllers
This commit is contained in:
375
src/controllers/matchController.js
Normal file
375
src/controllers/matchController.js
Normal file
@ -0,0 +1,375 @@
|
||||
// src/controllers/matchController.js
|
||||
const db = require('../db/db');
|
||||
|
||||
// Helper function to get player names for matches
|
||||
const enrichMatchesWithPlayerNames = async (matches) => {
|
||||
if (!matches || matches.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const playerIds = new Set();
|
||||
matches.forEach(m => {
|
||||
if (m.player1_id) playerIds.add(m.player1_id);
|
||||
if (m.player2_id) playerIds.add(m.player2_id);
|
||||
if (m.winner_id) playerIds.add(m.winner_id);
|
||||
});
|
||||
|
||||
if (playerIds.size === 0) return matches;
|
||||
|
||||
const playerResult = await db.query(
|
||||
'SELECT player_id, first_name, last_name FROM players WHERE player_id = ANY($1::uuid[])',
|
||||
[Array.from(playerIds)]
|
||||
);
|
||||
const playerMap = new Map(playerResult.rows.map(p => [p.player_id, `${p.first_name} ${p.last_name}`]));
|
||||
|
||||
return matches.map(m => ({
|
||||
...m,
|
||||
player1_name: m.player1_id ? playerMap.get(m.player1_id) || 'Unbekannt' : null,
|
||||
player2_name: m.player2_id ? playerMap.get(m.player2_id) || 'Unbekannt' : null,
|
||||
winner_name: m.winner_id ? playerMap.get(m.winner_id) || 'Unbekannt' : null,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
// Get all matches, filtered by tournament_id (required) and optionally status
|
||||
exports.getAllMatches = async (req, res, next) => {
|
||||
const { tournamentId, status } = req.query;
|
||||
|
||||
if (!tournamentId) {
|
||||
return res.status(400).json({ message: 'tournamentId query parameter ist erforderlich.' });
|
||||
}
|
||||
|
||||
let query = `
|
||||
SELECT m.*
|
||||
FROM matches m
|
||||
WHERE m.tournament_id = $1`;
|
||||
const params = [tournamentId];
|
||||
let paramIndex = 2;
|
||||
|
||||
if (status) {
|
||||
query += ` AND m.status = $${paramIndex++}`;
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
query += ' ORDER BY m.round, m.match_number_in_round, m.scheduled_time NULLS LAST, m.created_at';
|
||||
|
||||
try {
|
||||
const result = await db.query(query, params);
|
||||
const enrichedMatches = await enrichMatchesWithPlayerNames(result.rows);
|
||||
res.json(enrichedMatches);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Get a single match by ID
|
||||
exports.getMatchById = async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
// Fetch match and associated sets
|
||||
const matchResult = await db.query('SELECT * FROM matches WHERE match_id = $1', [id]);
|
||||
if (matchResult.rows.length === 0) {
|
||||
return res.status(404).json({ message: 'Spiel nicht gefunden.' });
|
||||
}
|
||||
|
||||
const setsResult = await db.query('SELECT set_number, player1_score, player2_score FROM sets WHERE match_id = $1 ORDER BY set_number', [id]);
|
||||
|
||||
const match = matchResult.rows[0];
|
||||
match.sets = setsResult.rows; // Add sets to the match object
|
||||
|
||||
// Enrich with player names
|
||||
const [enrichedMatch] = await enrichMatchesWithPlayerNames([match]);
|
||||
|
||||
res.json(enrichedMatch);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new match manually (Admin only)
|
||||
exports.createMatch = async (req, res, next) => {
|
||||
const {
|
||||
tournament_id,
|
||||
round,
|
||||
match_number_in_round,
|
||||
player1_id,
|
||||
player2_id,
|
||||
scheduled_time,
|
||||
table_number,
|
||||
status // Optional, defaults to 'scheduled'
|
||||
} = req.body;
|
||||
|
||||
if (!tournament_id) {
|
||||
return res.status(400).json({ message: 'tournament_id ist erforderlich.' });
|
||||
}
|
||||
if (player1_id && player1_id === player2_id) {
|
||||
return res.status(400).json({ message: 'Spieler 1 und Spieler 2 dürfen nicht identisch sein.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await db.query(
|
||||
`INSERT INTO matches (tournament_id, round, match_number_in_round, player1_id, player2_id, scheduled_time, table_number, status)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING *`,
|
||||
[
|
||||
tournament_id,
|
||||
round || null,
|
||||
match_number_in_round || null,
|
||||
player1_id || null,
|
||||
player2_id || null,
|
||||
scheduled_time || null,
|
||||
table_number || null,
|
||||
status || 'scheduled'
|
||||
]
|
||||
);
|
||||
const [enrichedMatch] = await enrichMatchesWithPlayerNames(result.rows);
|
||||
res.status(201).json(enrichedMatch);
|
||||
} catch (error) {
|
||||
// Handle foreign key violation if tournament_id or player_ids are invalid
|
||||
if (error.code === '23503') {
|
||||
return res.status(400).json({ message: `Ungültige Turnier- oder Spieler-ID angegeben. (${error.detail})` });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Update match details (Admin only)
|
||||
exports.updateMatch = async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
round,
|
||||
match_number_in_round,
|
||||
player1_id,
|
||||
player2_id,
|
||||
scheduled_time,
|
||||
status,
|
||||
table_number,
|
||||
referee_id,
|
||||
// winner_id should generally be set via score update, but allow manual override if needed
|
||||
winner_id
|
||||
} = req.body;
|
||||
|
||||
const fieldsToUpdate = [];
|
||||
const values = [];
|
||||
let queryIndex = 1;
|
||||
|
||||
const addUpdate = (field, value) => {
|
||||
// Allow explicitly setting fields to null
|
||||
if (value !== undefined) {
|
||||
fieldsToUpdate.push(`${field} = $${queryIndex++}`);
|
||||
values.push(value === '' ? null : value); // Treat empty string as null
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Prevent setting player1 and player2 to the same ID during update
|
||||
const currentMatchRes = await db.query('SELECT player1_id, player2_id FROM matches WHERE match_id = $1', [id]);
|
||||
if (currentMatchRes.rows.length === 0) {
|
||||
return res.status(404).json({ message: 'Spiel nicht gefunden.' });
|
||||
}
|
||||
const currentMatch = currentMatchRes.rows[0];
|
||||
const p1 = player1_id !== undefined ? player1_id : currentMatch.player1_id;
|
||||
const p2 = player2_id !== undefined ? player2_id : currentMatch.player2_id;
|
||||
if (p1 && p2 && p1 === p2) {
|
||||
return res.status(400).json({ message: 'Spieler 1 und Spieler 2 dürfen nicht identisch sein.' });
|
||||
}
|
||||
|
||||
|
||||
addUpdate('round', round);
|
||||
addUpdate('match_number_in_round', match_number_in_round);
|
||||
addUpdate('player1_id', player1_id);
|
||||
addUpdate('player2_id', player2_id);
|
||||
addUpdate('scheduled_time', scheduled_time);
|
||||
addUpdate('status', status);
|
||||
addUpdate('table_number', table_number);
|
||||
addUpdate('referee_id', referee_id);
|
||||
addUpdate('winner_id', winner_id);
|
||||
|
||||
|
||||
if (fieldsToUpdate.length === 0) {
|
||||
return res.status(400).json({ message: 'Keine Daten zum Aktualisieren angegeben.' });
|
||||
}
|
||||
|
||||
values.push(id); // Add match_id for WHERE clause
|
||||
|
||||
const updateQuery = `UPDATE matches SET ${fieldsToUpdate.join(', ')}, updated_at = CURRENT_TIMESTAMP WHERE match_id = $${queryIndex} RETURNING *`;
|
||||
|
||||
const result = await db.query(updateQuery, values);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
// Should have been caught earlier, but as a safeguard
|
||||
return res.status(404).json({ message: 'Spiel nicht gefunden.' });
|
||||
}
|
||||
const [enrichedMatch] = await enrichMatchesWithPlayerNames(result.rows);
|
||||
res.json(enrichedMatch);
|
||||
|
||||
} catch (error) {
|
||||
// Handle foreign key violation if player_ids/referee_id are invalid
|
||||
if (error.code === '23503') {
|
||||
return res.status(400).json({ message: `Ungültige Spieler- oder Schiedsrichter-ID angegeben. (${error.detail})` });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete a match (Admin only)
|
||||
exports.deleteMatch = async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
// ON DELETE CASCADE for 'sets' table should handle associated set scores
|
||||
const result = await db.query('DELETE FROM matches WHERE match_id = $1 RETURNING match_id', [id]);
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ message: 'Spiel nicht gefunden.' });
|
||||
}
|
||||
res.status(204).send(); // No Content
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Enter/Update scores for a match (Admin or Referee)
|
||||
exports.updateMatchScore = async (req, res, next) => {
|
||||
const { id: matchId } = req.params;
|
||||
const { sets } = req.body; // Expecting an array: [{ set_number: 1, player1_score: 11, player2_score: 9 }, ...]
|
||||
|
||||
if (!Array.isArray(sets) || sets.length === 0) {
|
||||
return res.status(400).json({ message: 'Ein Array von Satzergebnissen ist erforderlich.' });
|
||||
}
|
||||
|
||||
// --- Validation ---
|
||||
for (const set of sets) {
|
||||
if (
|
||||
set.set_number == null || set.player1_score == null || set.player2_score == null ||
|
||||
isNaN(parseInt(set.set_number)) || isNaN(parseInt(set.player1_score)) || isNaN(parseInt(set.player2_score)) ||
|
||||
parseInt(set.set_number) <= 0 || parseInt(set.player1_score) < 0 || parseInt(set.player2_score) < 0
|
||||
) {
|
||||
return res.status(400).json({ message: 'Ungültige Satzdaten. Jeder Satz benötigt set_number (>0), player1_score (>=0), player2_score (>=0).' });
|
||||
}
|
||||
}
|
||||
|
||||
const client = await db.pool.connect(); // Use a transaction
|
||||
|
||||
try {
|
||||
await client.query('BEGIN'); // Start transaction
|
||||
|
||||
// 1. Fetch match details (including player IDs and tournament game type)
|
||||
const matchRes = await client.query(`
|
||||
SELECT m.match_id, m.player1_id, m.player2_id, m.status, t.game_type
|
||||
FROM matches m
|
||||
JOIN tournaments t ON m.tournament_id = t.tournament_id
|
||||
WHERE m.match_id = $1
|
||||
`, [matchId]);
|
||||
|
||||
if (matchRes.rows.length === 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(404).json({ message: 'Spiel nicht gefunden.' });
|
||||
}
|
||||
const match = matchRes.rows[0];
|
||||
|
||||
if (!match.player1_id || !match.player2_id) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ message: 'Spiel kann nicht bewertet werden, da nicht beide Spieler zugewiesen sind.' });
|
||||
}
|
||||
|
||||
// 2. Insert or Update Set Scores
|
||||
// Use ON CONFLICT to handle cases where scores for a set are updated
|
||||
for (const set of sets) {
|
||||
await client.query(
|
||||
`INSERT INTO sets (match_id, set_number, player1_score, player2_score)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (match_id, set_number)
|
||||
DO UPDATE SET player1_score = EXCLUDED.player1_score,
|
||||
player2_score = EXCLUDED.player2_score`,
|
||||
[matchId, parseInt(set.set_number), parseInt(set.player1_score), parseInt(set.player2_score)]
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Determine Winner (Simplified Logic - needs refinement based on actual rules)
|
||||
// Fetch all sets for this match again after potential updates
|
||||
const allSetsRes = await client.query('SELECT set_number, player1_score, player2_score FROM sets WHERE match_id = $1 ORDER BY set_number', [matchId]);
|
||||
const allSets = allSetsRes.rows;
|
||||
|
||||
let player1SetsWon = 0;
|
||||
let player2SetsWon = 0;
|
||||
const pointsToWinSet = match.game_type === '21_points' ? 21 : 11; // Determine points needed per set
|
||||
|
||||
for (const s of allSets) {
|
||||
// Basic win condition (must win by 2 unless score is high, e.g. 10-10 -> 12-10)
|
||||
// This simplified logic doesn't handle deuce perfectly.
|
||||
const p1Win = s.player1_score >= pointsToWinSet && s.player1_score >= s.player2_score + 2;
|
||||
const p2Win = s.player2_score >= pointsToWinSet && s.player2_score >= s.player1_score + 2;
|
||||
// Handle edge case like 11-10 vs 10-11 -> requires deuce logic
|
||||
// TODO: Refine set win logic for edge cases and deuce
|
||||
|
||||
if (p1Win) {
|
||||
player1SetsWon++;
|
||||
} else if (p2Win) {
|
||||
player2SetsWon++;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine match winner based on sets won (e.g., first to 3 sets in best-of-5)
|
||||
// TODO: Make "sets needed to win match" configurable per tournament type (KO/Group) or globally
|
||||
const setsNeededToWinMatch = 3; // Assuming best of 5 for now
|
||||
let winnerId = null;
|
||||
let finalStatus = 'ongoing'; // Default status unless match is finished
|
||||
|
||||
if (player1SetsWon >= setsNeededToWinMatch) {
|
||||
winnerId = match.player1_id;
|
||||
finalStatus = 'finished';
|
||||
} else if (player2SetsWon >= setsNeededToWinMatch) {
|
||||
winnerId = match.player2_id;
|
||||
finalStatus = 'finished';
|
||||
}
|
||||
// Optional: If all possible sets are played (e.g., 5 sets) and no one reached setsNeededToWinMatch (unlikely with correct logic), mark as finished?
|
||||
|
||||
// 4. Update Match Status and Winner
|
||||
await client.query(
|
||||
'UPDATE matches SET winner_id = $1, status = $2, updated_at = CURRENT_TIMESTAMP WHERE match_id = $3',
|
||||
[winnerId, finalStatus, matchId]
|
||||
);
|
||||
|
||||
await client.query('COMMIT'); // Commit transaction
|
||||
|
||||
// Fetch the updated match data to return
|
||||
const updatedMatchResult = await db.query('SELECT * FROM matches WHERE match_id = $1', [matchId]);
|
||||
const updatedSetsResult = await db.query('SELECT set_number, player1_score, player2_score FROM sets WHERE match_id = $1 ORDER BY set_number', [matchId]);
|
||||
const finalMatchData = updatedMatchResult.rows[0];
|
||||
finalMatchData.sets = updatedSetsResult.rows;
|
||||
const [enrichedFinalMatch] = await enrichMatchesWithPlayerNames([finalMatchData]);
|
||||
|
||||
|
||||
res.json({ message: 'Ergebnis erfolgreich gespeichert.', match: enrichedFinalMatch });
|
||||
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK'); // Rollback transaction on error
|
||||
next(error);
|
||||
} finally {
|
||||
client.release(); // Release client back to the pool
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- Placeholder functions for other features ---
|
||||
|
||||
exports.exportMatchesPdf = async (req, res, next) => {
|
||||
// TODO: Implement PDF export logic
|
||||
// 1. Fetch match data (filtered by tournament, status, date range etc.)
|
||||
// 2. Use 'pdfkit' library
|
||||
// 3. Define PDF structure (header, table layout)
|
||||
// 4. Add match data to the PDF table
|
||||
// 5. Set response headers for PDF download
|
||||
// 6. Pipe PDF stream to response
|
||||
res.status(501).json({ message: 'Funktion PDF-Export noch nicht implementiert.' });
|
||||
};
|
||||
|
||||
exports.generateBracket = async (req, res, next) => {
|
||||
// TODO: Implement bracket generation logic (very complex)
|
||||
// Requires: Tournament ID, list of registered/seeded players
|
||||
// Logic depends heavily on tournament_type ('knockout' or 'group')
|
||||
// - Knockout: Seeding, pairing logic, generating match records for each round.
|
||||
// - Group: Group creation, round-robin match generation within groups.
|
||||
// Needs to create match records in the 'matches' table.
|
||||
const { tournamentId } = req.body;
|
||||
if (!tournamentId) return res.status(400).json({ message: "tournamentId ist erforderlich." });
|
||||
res.status(501).json({ message: 'Funktion Turnierbaum-Generierung noch nicht implementiert.' });
|
||||
};
|
200
src/controllers/playerController.js
Normal file
200
src/controllers/playerController.js
Normal file
@ -0,0 +1,200 @@
|
||||
// src/controllers/playerController.js
|
||||
const db = require('../db/db');
|
||||
|
||||
// Get all players - potentially with filtering/sorting later
|
||||
exports.getAllPlayers = async (req, res, next) => {
|
||||
// Basic filtering example (add more as needed)
|
||||
const { search } = req.query;
|
||||
let query = 'SELECT player_id, first_name, last_name, club, qttr_points, age_class FROM players';
|
||||
const params = [];
|
||||
|
||||
if (search) {
|
||||
query += ' WHERE first_name ILIKE $1 OR last_name ILIKE $1 OR club ILIKE $1';
|
||||
params.push(`%${search}%`);
|
||||
}
|
||||
|
||||
query += ' ORDER BY last_name, first_name'; // Default sorting
|
||||
|
||||
try {
|
||||
const result = await db.query(query, params);
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Get a single player by ID
|
||||
exports.getPlayerById = async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
const result = await db.query('SELECT player_id, first_name, last_name, club, qttr_points, age_class, created_at, updated_at FROM players WHERE player_id = $1', [id]);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ message: 'Spieler nicht gefunden.' });
|
||||
}
|
||||
res.json(result.rows[0]);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new player (Admin only)
|
||||
exports.createPlayer = async (req, res, next) => {
|
||||
const { first_name, last_name, club, qttr_points, age_class } = req.body;
|
||||
|
||||
// Basic validation
|
||||
if (!first_name || !last_name) {
|
||||
return res.status(400).json({ message: 'Vorname und Nachname sind erforderlich.' });
|
||||
}
|
||||
|
||||
// Ensure qttr_points is a number or null
|
||||
const qtt_numeric = qttr_points ? parseInt(qttr_points, 10) : null;
|
||||
if (qttr_points && isNaN(qtt_numeric)) {
|
||||
return res.status(400).json({ message: 'QTTR-Punkte müssen eine Zahl sein.' });
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const result = await db.query(
|
||||
`INSERT INTO players (first_name, last_name, club, qttr_points, age_class)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING player_id, first_name, last_name, club, qttr_points, age_class`,
|
||||
[first_name, last_name, club || null, qtt_numeric, age_class || null]
|
||||
);
|
||||
res.status(201).json(result.rows[0]);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Update an existing player (Admin only)
|
||||
exports.updatePlayer = async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
const { first_name, last_name, club, qttr_points, age_class } = req.body;
|
||||
|
||||
// Build query dynamically
|
||||
const fieldsToUpdate = [];
|
||||
const values = [];
|
||||
let queryIndex = 1;
|
||||
|
||||
const addUpdate = (field, value) => {
|
||||
// Allow explicitly setting fields to null or empty string (which becomes null for text fields)
|
||||
if (value !== undefined) {
|
||||
let processedValue = value;
|
||||
if (field === 'qttr_points') {
|
||||
processedValue = value ? parseInt(value, 10) : null;
|
||||
if (value && isNaN(processedValue)) {
|
||||
throw new Error('QTTR-Punkte müssen eine Zahl sein.');
|
||||
}
|
||||
} else {
|
||||
// Treat empty strings as null for nullable text fields
|
||||
processedValue = value === '' ? null : value;
|
||||
}
|
||||
fieldsToUpdate.push(`${field} = $${queryIndex++}`);
|
||||
values.push(processedValue);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Validate required fields if they are being updated to empty
|
||||
if (first_name === '') return res.status(400).json({ message: 'Vorname darf nicht leer sein.' });
|
||||
if (last_name === '') return res.status(400).json({ message: 'Nachname darf nicht leer sein.' });
|
||||
|
||||
addUpdate('first_name', first_name);
|
||||
addUpdate('last_name', last_name);
|
||||
addUpdate('club', club);
|
||||
addUpdate('qttr_points', qttr_points);
|
||||
addUpdate('age_class', age_class);
|
||||
|
||||
if (fieldsToUpdate.length === 0) {
|
||||
return res.status(400).json({ message: 'Keine Daten zum Aktualisieren angegeben.' });
|
||||
}
|
||||
|
||||
values.push(id); // Add player_id for WHERE clause
|
||||
|
||||
const updateQuery = `UPDATE players SET ${fieldsToUpdate.join(', ')}, updated_at = CURRENT_TIMESTAMP WHERE player_id = $${queryIndex} RETURNING player_id, first_name, last_name, club, qttr_points, age_class`;
|
||||
|
||||
const result = await db.query(updateQuery, values);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ message: 'Spieler nicht gefunden.' });
|
||||
}
|
||||
res.json(result.rows[0]);
|
||||
|
||||
} catch (error) {
|
||||
// Handle potential validation errors from addUpdate
|
||||
if (error.message.includes('QTTR-Punkte')) {
|
||||
return res.status(400).json({ message: error.message });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete a player (Admin only)
|
||||
exports.deletePlayer = async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
// The database schema uses ON DELETE CASCADE/SET NULL for relationships,
|
||||
// so deleting a player might automatically remove them from tournaments
|
||||
// or set player references in matches to NULL. Review init.sql for details.
|
||||
const result = await db.query('DELETE FROM players WHERE player_id = $1 RETURNING player_id', [id]);
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ message: 'Spieler nicht gefunden.' });
|
||||
}
|
||||
res.status(204).send(); // No Content
|
||||
} catch (error) {
|
||||
// Handle potential foreign key issues if not handled by CASCADE/SET NULL
|
||||
if (error.code === '23503') {
|
||||
console.warn(`Attempted to delete player ${id} which might still be referenced elsewhere unexpectedly.`);
|
||||
return res.status(409).json({ message: 'Spieler konnte nicht gelöscht werden, da er noch in Verwendung ist (z.B. in einem aktiven Spiel). Überprüfen Sie die Datenbank-Constraints.' });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- Placeholder functions for Import/Export ---
|
||||
|
||||
exports.importPlayers = async (req, res, next) => {
|
||||
// TODO: Implement CSV import logic
|
||||
// 1. Use 'multer' middleware in the route to handle file upload (e.g., req.file)
|
||||
// 2. Read the file stream (req.file.buffer or req.file.path)
|
||||
// 3. Use 'csv-parser' to parse the CSV data row by row
|
||||
// 4. Validate each row's data
|
||||
// 5. Insert valid player data into the database (consider bulk insert for performance)
|
||||
// 6. Report success or errors (e.g., which rows failed)
|
||||
console.log("Received file for import (implement logic):", req.file); // Example if using multer
|
||||
res.status(501).json({ message: 'Funktion Spieler-Import noch nicht implementiert.' });
|
||||
};
|
||||
|
||||
exports.exportPlayers = async (req, res, next) => {
|
||||
// TODO: Implement CSV export logic
|
||||
// 1. Fetch player data from the database (potentially filtered)
|
||||
// 2. Use 'fast-csv' or similar to format data as CSV
|
||||
// 3. Set response headers for CSV download:
|
||||
// res.setHeader('Content-Type', 'text/csv');
|
||||
// res.setHeader('Content-Disposition', 'attachment; filename="spieler_export.csv"');
|
||||
// 4. Pipe the CSV stream to the response object (res)
|
||||
try {
|
||||
const players = await db.query('SELECT first_name, last_name, club, qttr_points, age_class FROM players ORDER BY last_name');
|
||||
// Placeholder - just send JSON for now until CSV lib is added
|
||||
if (players.rows.length === 0) {
|
||||
return res.status(404).json({ message: "Keine Spieler zum Exportieren gefunden." });
|
||||
}
|
||||
// Actual implementation would use fast-csv here
|
||||
res.setHeader('Content-Type', 'application/json'); // Change to text/csv for actual export
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="spieler_export.json"'); // Change filename
|
||||
res.json(players.rows); // Send JSON as placeholder
|
||||
|
||||
// Example with fast-csv (requires installation and import):
|
||||
// const csvStream = format({ headers: true });
|
||||
// res.setHeader('Content-Type', 'text/csv');
|
||||
// res.setHeader('Content-Disposition', 'attachment; filename="spieler_export.csv"');
|
||||
// csvStream.pipe(res);
|
||||
// players.rows.forEach(player => csvStream.write(player));
|
||||
// csvStream.end();
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
// res.status(501).json({ message: 'Funktion Spieler-Export noch nicht implementiert.' }); // Remove this line when implemented
|
||||
};
|
Reference in New Issue
Block a user