Initial commit
Dieser Commit ist enthalten in:
230
backend/routes/export.js
Normale Datei
230
backend/routes/export.js
Normale Datei
@ -0,0 +1,230 @@
|
||||
/**
|
||||
* TASKMATE - Export Routes
|
||||
* ========================
|
||||
* Export in JSON und CSV
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { getDb } = require('../database');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* GET /api/export/project/:id/json
|
||||
* Projekt als JSON exportieren
|
||||
*/
|
||||
router.get('/project/:id/json', (req, res) => {
|
||||
try {
|
||||
const projectId = req.params.id;
|
||||
const db = getDb();
|
||||
|
||||
// Projekt
|
||||
const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
|
||||
if (!project) {
|
||||
return res.status(404).json({ error: 'Projekt nicht gefunden' });
|
||||
}
|
||||
|
||||
// Spalten
|
||||
const columns = db.prepare('SELECT * FROM columns WHERE project_id = ? ORDER BY position').all(projectId);
|
||||
|
||||
// Labels
|
||||
const labels = db.prepare('SELECT * FROM labels WHERE project_id = ?').all(projectId);
|
||||
|
||||
// Aufgaben mit allen Details
|
||||
const tasks = db.prepare('SELECT * FROM tasks WHERE project_id = ?').all(projectId);
|
||||
|
||||
const tasksWithDetails = tasks.map(task => {
|
||||
const taskLabels = db.prepare(`
|
||||
SELECT l.* FROM labels l
|
||||
JOIN task_labels tl ON l.id = tl.label_id
|
||||
WHERE tl.task_id = ?
|
||||
`).all(task.id);
|
||||
|
||||
const subtasks = db.prepare('SELECT * FROM subtasks WHERE task_id = ? ORDER BY position').all(task.id);
|
||||
const comments = db.prepare(`
|
||||
SELECT c.*, u.display_name FROM comments c
|
||||
LEFT JOIN users u ON c.user_id = u.id
|
||||
WHERE c.task_id = ?
|
||||
`).all(task.id);
|
||||
const attachments = db.prepare('SELECT * FROM attachments WHERE task_id = ?').all(task.id);
|
||||
const links = db.prepare('SELECT * FROM links WHERE task_id = ?').all(task.id);
|
||||
|
||||
return {
|
||||
...task,
|
||||
labels: taskLabels,
|
||||
subtasks,
|
||||
comments,
|
||||
attachments,
|
||||
links
|
||||
};
|
||||
});
|
||||
|
||||
// Vorlagen
|
||||
const templates = db.prepare('SELECT * FROM task_templates WHERE project_id = ?').all(projectId);
|
||||
|
||||
const exportData = {
|
||||
exportedAt: new Date().toISOString(),
|
||||
exportedBy: req.user.username,
|
||||
version: '1.0',
|
||||
project: {
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
description: project.description,
|
||||
createdAt: project.created_at
|
||||
},
|
||||
columns: columns.map(c => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
position: c.position,
|
||||
color: c.color
|
||||
})),
|
||||
labels: labels.map(l => ({
|
||||
id: l.id,
|
||||
name: l.name,
|
||||
color: l.color
|
||||
})),
|
||||
tasks: tasksWithDetails,
|
||||
templates
|
||||
};
|
||||
|
||||
logger.info(`Projekt exportiert als JSON: ${project.name}`);
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${project.name.replace(/[^a-z0-9]/gi, '_')}_export.json"`);
|
||||
res.json(exportData);
|
||||
} catch (error) {
|
||||
logger.error('Fehler beim JSON-Export:', { error: error.message });
|
||||
res.status(500).json({ error: 'Interner Serverfehler' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/export/project/:id/csv
|
||||
* Aufgaben als CSV exportieren
|
||||
*/
|
||||
router.get('/project/:id/csv', (req, res) => {
|
||||
try {
|
||||
const projectId = req.params.id;
|
||||
const db = getDb();
|
||||
|
||||
const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
|
||||
if (!project) {
|
||||
return res.status(404).json({ error: 'Projekt nicht gefunden' });
|
||||
}
|
||||
|
||||
const tasks = db.prepare(`
|
||||
SELECT
|
||||
t.*,
|
||||
c.name as column_name,
|
||||
u.display_name as assigned_name
|
||||
FROM tasks t
|
||||
LEFT JOIN columns c ON t.column_id = c.id
|
||||
LEFT JOIN users u ON t.assigned_to = u.id
|
||||
WHERE t.project_id = ?
|
||||
ORDER BY c.position, t.position
|
||||
`).all(projectId);
|
||||
|
||||
// CSV Header
|
||||
const headers = [
|
||||
'ID', 'Titel', 'Beschreibung', 'Status', 'Priorität',
|
||||
'Fälligkeitsdatum', 'Zugewiesen an', 'Zeitschätzung (Min)',
|
||||
'Erstellt am', 'Archiviert'
|
||||
];
|
||||
|
||||
// CSV Zeilen
|
||||
const rows = tasks.map(task => [
|
||||
task.id,
|
||||
escapeCsvField(task.title),
|
||||
escapeCsvField(task.description || ''),
|
||||
task.column_name,
|
||||
task.priority,
|
||||
task.due_date || '',
|
||||
task.assigned_name || '',
|
||||
task.time_estimate_min || '',
|
||||
task.created_at,
|
||||
task.archived ? 'Ja' : 'Nein'
|
||||
]);
|
||||
|
||||
// CSV zusammenbauen
|
||||
const csv = [
|
||||
headers.join(';'),
|
||||
...rows.map(row => row.join(';'))
|
||||
].join('\n');
|
||||
|
||||
logger.info(`Projekt exportiert als CSV: ${project.name}`);
|
||||
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${project.name.replace(/[^a-z0-9]/gi, '_')}_export.csv"`);
|
||||
// BOM für Excel UTF-8 Erkennung
|
||||
res.send('\ufeff' + csv);
|
||||
} catch (error) {
|
||||
logger.error('Fehler beim CSV-Export:', { error: error.message });
|
||||
res.status(500).json({ error: 'Interner Serverfehler' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/export/all/json
|
||||
* Alle Daten exportieren (Backup)
|
||||
*/
|
||||
router.get('/all/json', (req, res) => {
|
||||
try {
|
||||
const db = getDb();
|
||||
|
||||
const projects = db.prepare('SELECT * FROM projects').all();
|
||||
const columns = db.prepare('SELECT * FROM columns').all();
|
||||
const labels = db.prepare('SELECT * FROM labels').all();
|
||||
const tasks = db.prepare('SELECT * FROM tasks').all();
|
||||
const subtasks = db.prepare('SELECT * FROM subtasks').all();
|
||||
const comments = db.prepare('SELECT * FROM comments').all();
|
||||
const taskLabels = db.prepare('SELECT * FROM task_labels').all();
|
||||
const attachments = db.prepare('SELECT * FROM attachments').all();
|
||||
const links = db.prepare('SELECT * FROM links').all();
|
||||
const templates = db.prepare('SELECT * FROM task_templates').all();
|
||||
const history = db.prepare('SELECT * FROM history').all();
|
||||
|
||||
const exportData = {
|
||||
exportedAt: new Date().toISOString(),
|
||||
exportedBy: req.user.username,
|
||||
version: '1.0',
|
||||
data: {
|
||||
projects,
|
||||
columns,
|
||||
labels,
|
||||
tasks,
|
||||
subtasks,
|
||||
comments,
|
||||
taskLabels,
|
||||
attachments,
|
||||
links,
|
||||
templates,
|
||||
history
|
||||
}
|
||||
};
|
||||
|
||||
logger.info('Vollständiger Export durchgeführt');
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="taskmate_backup_${Date.now()}.json"`);
|
||||
res.json(exportData);
|
||||
} catch (error) {
|
||||
logger.error('Fehler beim Voll-Export:', { error: error.message });
|
||||
res.status(500).json({ error: 'Interner Serverfehler' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Hilfsfunktion: CSV-Feld escapen
|
||||
*/
|
||||
function escapeCsvField(field) {
|
||||
if (typeof field !== 'string') return field;
|
||||
|
||||
// Wenn Feld Semikolon, Anführungszeichen oder Zeilenumbruch enthält
|
||||
if (field.includes(';') || field.includes('"') || field.includes('\n')) {
|
||||
// Anführungszeichen verdoppeln und in Anführungszeichen setzen
|
||||
return '"' + field.replace(/"/g, '""') + '"';
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren