Initial commit
Dieser Commit ist enthalten in:
269
backend/routes/import.js
Normale Datei
269
backend/routes/import.js
Normale Datei
@ -0,0 +1,269 @@
|
||||
/**
|
||||
* TASKMATE - Import Routes
|
||||
* ========================
|
||||
* Import von JSON-Backups
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { getDb } = require('../database');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* POST /api/import/project
|
||||
* Projekt aus JSON importieren
|
||||
*/
|
||||
router.post('/project', (req, res) => {
|
||||
try {
|
||||
const { data, overwrite = false } = req.body;
|
||||
|
||||
if (!data || !data.project) {
|
||||
return res.status(400).json({ error: 'Ungültiges Import-Format' });
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
|
||||
// Transaktion starten
|
||||
const importProject = db.transaction(() => {
|
||||
const importData = data;
|
||||
|
||||
// Projekt erstellen
|
||||
const projectResult = db.prepare(`
|
||||
INSERT INTO projects (name, description, created_by)
|
||||
VALUES (?, ?, ?)
|
||||
`).run(
|
||||
importData.project.name + (overwrite ? '' : ' (Import)'),
|
||||
importData.project.description,
|
||||
req.user.id
|
||||
);
|
||||
|
||||
const newProjectId = projectResult.lastInsertRowid;
|
||||
|
||||
// Mapping für alte -> neue IDs
|
||||
const columnMap = new Map();
|
||||
const labelMap = new Map();
|
||||
const taskMap = new Map();
|
||||
|
||||
// Spalten importieren
|
||||
if (importData.columns) {
|
||||
const insertColumn = db.prepare(`
|
||||
INSERT INTO columns (project_id, name, position, color)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
importData.columns.forEach(col => {
|
||||
const result = insertColumn.run(newProjectId, col.name, col.position, col.color);
|
||||
columnMap.set(col.id, result.lastInsertRowid);
|
||||
});
|
||||
}
|
||||
|
||||
// Labels importieren
|
||||
if (importData.labels) {
|
||||
const insertLabel = db.prepare(`
|
||||
INSERT INTO labels (project_id, name, color)
|
||||
VALUES (?, ?, ?)
|
||||
`);
|
||||
|
||||
importData.labels.forEach(label => {
|
||||
const result = insertLabel.run(newProjectId, label.name, label.color);
|
||||
labelMap.set(label.id, result.lastInsertRowid);
|
||||
});
|
||||
}
|
||||
|
||||
// Aufgaben importieren
|
||||
if (importData.tasks) {
|
||||
const insertTask = db.prepare(`
|
||||
INSERT INTO tasks (
|
||||
project_id, column_id, title, description, priority,
|
||||
due_date, time_estimate_min, position, created_by
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
importData.tasks.forEach(task => {
|
||||
const newColumnId = columnMap.get(task.column_id);
|
||||
if (!newColumnId) return;
|
||||
|
||||
const result = insertTask.run(
|
||||
newProjectId,
|
||||
newColumnId,
|
||||
task.title,
|
||||
task.description,
|
||||
task.priority || 'medium',
|
||||
task.due_date,
|
||||
task.time_estimate_min,
|
||||
task.position,
|
||||
req.user.id
|
||||
);
|
||||
|
||||
taskMap.set(task.id, result.lastInsertRowid);
|
||||
|
||||
// Task-Labels
|
||||
if (task.labels) {
|
||||
const insertTaskLabel = db.prepare(
|
||||
'INSERT INTO task_labels (task_id, label_id) VALUES (?, ?)'
|
||||
);
|
||||
task.labels.forEach(label => {
|
||||
const newLabelId = labelMap.get(label.id);
|
||||
if (newLabelId) {
|
||||
try {
|
||||
insertTaskLabel.run(result.lastInsertRowid, newLabelId);
|
||||
} catch (e) { /* Ignorieren */ }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Subtasks
|
||||
if (task.subtasks) {
|
||||
const insertSubtask = db.prepare(
|
||||
'INSERT INTO subtasks (task_id, title, completed, position) VALUES (?, ?, ?, ?)'
|
||||
);
|
||||
task.subtasks.forEach(st => {
|
||||
insertSubtask.run(
|
||||
result.lastInsertRowid,
|
||||
st.title,
|
||||
st.completed ? 1 : 0,
|
||||
st.position
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Links
|
||||
if (task.links) {
|
||||
const insertLink = db.prepare(
|
||||
'INSERT INTO links (task_id, title, url, created_by) VALUES (?, ?, ?, ?)'
|
||||
);
|
||||
task.links.forEach(link => {
|
||||
insertLink.run(result.lastInsertRowid, link.title, link.url, req.user.id);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Vorlagen importieren
|
||||
if (importData.templates) {
|
||||
const insertTemplate = db.prepare(`
|
||||
INSERT INTO task_templates (
|
||||
project_id, name, title_template, description,
|
||||
priority, labels, subtasks, time_estimate_min
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
importData.templates.forEach(tmpl => {
|
||||
// Labels-IDs mappen
|
||||
let newLabels = null;
|
||||
if (tmpl.labels) {
|
||||
const oldLabels = typeof tmpl.labels === 'string' ? JSON.parse(tmpl.labels) : tmpl.labels;
|
||||
const mappedLabels = oldLabels.map(id => labelMap.get(id)).filter(id => id);
|
||||
newLabels = JSON.stringify(mappedLabels);
|
||||
}
|
||||
|
||||
insertTemplate.run(
|
||||
newProjectId,
|
||||
tmpl.name,
|
||||
tmpl.title_template,
|
||||
tmpl.description,
|
||||
tmpl.priority,
|
||||
newLabels,
|
||||
tmpl.subtasks,
|
||||
tmpl.time_estimate_min
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
projectId: newProjectId,
|
||||
columnsImported: columnMap.size,
|
||||
labelsImported: labelMap.size,
|
||||
tasksImported: taskMap.size
|
||||
};
|
||||
});
|
||||
|
||||
const result = importProject();
|
||||
|
||||
logger.info(`Projekt importiert: ID ${result.projectId} (${result.tasksImported} Aufgaben)`);
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Import erfolgreich',
|
||||
...result
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Fehler beim Import:', { error: error.message });
|
||||
res.status(500).json({ error: 'Import fehlgeschlagen: ' + error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/import/validate
|
||||
* Import-Datei validieren
|
||||
*/
|
||||
router.post('/validate', (req, res) => {
|
||||
try {
|
||||
const { data } = req.body;
|
||||
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
|
||||
if (!data) {
|
||||
errors.push('Keine Daten vorhanden');
|
||||
return res.json({ valid: false, errors, warnings });
|
||||
}
|
||||
|
||||
// Version prüfen
|
||||
if (!data.version) {
|
||||
warnings.push('Keine Versionsangabe gefunden');
|
||||
}
|
||||
|
||||
// Projekt prüfen
|
||||
if (!data.project) {
|
||||
errors.push('Kein Projekt in den Daten gefunden');
|
||||
} else {
|
||||
if (!data.project.name) {
|
||||
errors.push('Projektname fehlt');
|
||||
}
|
||||
}
|
||||
|
||||
// Spalten prüfen
|
||||
if (!data.columns || data.columns.length === 0) {
|
||||
errors.push('Keine Spalten in den Daten gefunden');
|
||||
}
|
||||
|
||||
// Aufgaben prüfen
|
||||
if (data.tasks) {
|
||||
data.tasks.forEach((task, idx) => {
|
||||
if (!task.title) {
|
||||
warnings.push(`Aufgabe ${idx + 1} hat keinen Titel`);
|
||||
}
|
||||
if (!task.column_id) {
|
||||
warnings.push(`Aufgabe "${task.title || idx + 1}" hat keine Spalten-ID`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Statistiken
|
||||
const stats = {
|
||||
projectName: data.project?.name || 'Unbekannt',
|
||||
columns: data.columns?.length || 0,
|
||||
labels: data.labels?.length || 0,
|
||||
tasks: data.tasks?.length || 0,
|
||||
templates: data.templates?.length || 0
|
||||
};
|
||||
|
||||
res.json({
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
warnings,
|
||||
stats
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Fehler bei Import-Validierung:', { error: error.message });
|
||||
res.status(400).json({
|
||||
valid: false,
|
||||
errors: ['Ungültiges JSON-Format'],
|
||||
warnings: []
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren