Dieser Commit ist enthalten in:
hendrik_gebhardt@gmx.de
2026-01-06 21:49:26 +00:00
committet von Server Deploy
Ursprung 623bbdf5dd
Commit 7d67557be4
34 geänderte Dateien mit 21416 neuen und 2367 gelöschten Zeilen

439
backend/routes/contacts.js Normale Datei
Datei anzeigen

@ -0,0 +1,439 @@
/**
* TASKMATE - Contact Routes
* =========================
* CRUD für Kontakte
*/
const express = require('express');
const router = express.Router();
const { getDb } = require('../database');
const logger = require('../utils/logger');
const { validators } = require('../middleware/validation');
/**
* GET /api/contacts
* Alle Kontakte abrufen mit optionalem Filter
*/
router.get('/', (req, res) => {
try {
const db = getDb();
const { search, tag, sortBy = 'created_at', sortOrder = 'desc' } = req.query;
let query = `
SELECT c.*, u.display_name as creator_name
FROM contacts c
LEFT JOIN users u ON c.created_by = u.id
WHERE 1=1
`;
const params = [];
// Suchfilter
if (search) {
query += ` AND (
c.first_name LIKE ? OR
c.last_name LIKE ? OR
c.company LIKE ? OR
c.email LIKE ? OR
c.phone LIKE ? OR
c.mobile LIKE ?
)`;
const searchParam = `%${search}%`;
params.push(searchParam, searchParam, searchParam, searchParam, searchParam, searchParam);
}
// Tag-Filter
if (tag) {
query += ` AND c.tags LIKE ?`;
params.push(`%${tag}%`);
}
// Sortierung
const validSortFields = ['first_name', 'last_name', 'company', 'created_at', 'updated_at'];
const sortField = validSortFields.includes(sortBy) ? sortBy : 'created_at';
const order = sortOrder.toLowerCase() === 'asc' ? 'ASC' : 'DESC';
query += ` ORDER BY c.${sortField} ${order}`;
const contacts = db.prepare(query).all(params);
res.json(contacts.map(c => ({
id: c.id,
firstName: c.first_name,
lastName: c.last_name,
company: c.company,
position: c.position,
email: c.email,
phone: c.phone,
mobile: c.mobile,
address: c.address,
postalCode: c.postal_code,
city: c.city,
country: c.country,
website: c.website,
notes: c.notes,
tags: c.tags ? c.tags.split(',').map(t => t.trim()) : [],
createdAt: c.created_at,
updatedAt: c.updated_at,
createdBy: c.created_by,
creatorName: c.creator_name
})));
} catch (error) {
logger.error('Fehler beim Abrufen der Kontakte:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/contacts/:id
* Einzelnen Kontakt abrufen
*/
router.get('/:id', (req, res) => {
try {
const db = getDb();
const contactId = req.params.id;
const contact = db.prepare(`
SELECT c.*, u.display_name as creator_name
FROM contacts c
LEFT JOIN users u ON c.created_by = u.id
WHERE c.id = ?
`).get(contactId);
if (!contact) {
return res.status(404).json({ error: 'Kontakt nicht gefunden' });
}
res.json({
id: contact.id,
firstName: contact.first_name,
lastName: contact.last_name,
company: contact.company,
position: contact.position,
email: contact.email,
phone: contact.phone,
mobile: contact.mobile,
address: contact.address,
postalCode: contact.postal_code,
city: contact.city,
country: contact.country,
website: contact.website,
notes: contact.notes,
tags: contact.tags ? contact.tags.split(',').map(t => t.trim()) : [],
createdAt: contact.created_at,
updatedAt: contact.updated_at,
createdBy: contact.created_by,
creatorName: contact.creator_name
});
} catch (error) {
logger.error('Fehler beim Abrufen des Kontakts:', { error: error.message, contactId: req.params.id });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* POST /api/contacts
* Neuen Kontakt erstellen
*/
router.post('/', validators.contact, (req, res) => {
try {
const db = getDb();
const userId = req.user.id;
const {
firstName,
lastName,
company,
position,
email,
phone,
mobile,
address,
postalCode,
city,
country,
website,
notes,
tags
} = req.body;
const result = db.prepare(`
INSERT INTO contacts (
first_name, last_name, company, position,
email, phone, mobile, address, postal_code,
city, country, website, notes, tags,
created_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
firstName || null,
lastName || null,
company || null,
position || null,
email || null,
phone || null,
mobile || null,
address || null,
postalCode || null,
city || null,
country || null,
website || null,
notes || null,
Array.isArray(tags) ? tags.join(', ') : null,
userId
);
const newContact = db.prepare(`
SELECT c.*, u.display_name as creator_name
FROM contacts c
LEFT JOIN users u ON c.created_by = u.id
WHERE c.id = ?
`).get(result.lastInsertRowid);
// Socket.io Event
const io = req.app.get('io');
io.emit('contact:created', {
contact: {
id: newContact.id,
firstName: newContact.first_name,
lastName: newContact.last_name,
company: newContact.company,
position: newContact.position,
email: newContact.email,
phone: newContact.phone,
mobile: newContact.mobile,
address: newContact.address,
postalCode: newContact.postal_code,
city: newContact.city,
country: newContact.country,
website: newContact.website,
notes: newContact.notes,
tags: newContact.tags ? newContact.tags.split(',').map(t => t.trim()) : [],
createdAt: newContact.created_at,
updatedAt: newContact.updated_at,
createdBy: newContact.created_by,
creatorName: newContact.creator_name
},
userId
});
res.status(201).json({
id: newContact.id,
firstName: newContact.first_name,
lastName: newContact.last_name,
company: newContact.company,
position: newContact.position,
email: newContact.email,
phone: newContact.phone,
mobile: newContact.mobile,
address: newContact.address,
postalCode: newContact.postal_code,
city: newContact.city,
country: newContact.country,
website: newContact.website,
notes: newContact.notes,
tags: newContact.tags ? newContact.tags.split(',').map(t => t.trim()) : [],
createdAt: newContact.created_at,
updatedAt: newContact.updated_at,
createdBy: newContact.created_by,
creatorName: newContact.creator_name
});
logger.info('Kontakt erstellt', { contactId: newContact.id, userId });
} catch (error) {
logger.error('Fehler beim Erstellen des Kontakts:', { error: error.message, body: req.body });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* PUT /api/contacts/:id
* Kontakt aktualisieren
*/
router.put('/:id', validators.contact, (req, res) => {
try {
const db = getDb();
const contactId = req.params.id;
const userId = req.user.id;
const {
firstName,
lastName,
company,
position,
email,
phone,
mobile,
address,
postalCode,
city,
country,
website,
notes,
tags
} = req.body;
// Prüfen ob Kontakt existiert
const existing = db.prepare('SELECT id FROM contacts WHERE id = ?').get(contactId);
if (!existing) {
return res.status(404).json({ error: 'Kontakt nicht gefunden' });
}
// Update
db.prepare(`
UPDATE contacts SET
first_name = ?,
last_name = ?,
company = ?,
position = ?,
email = ?,
phone = ?,
mobile = ?,
address = ?,
postal_code = ?,
city = ?,
country = ?,
website = ?,
notes = ?,
tags = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(
firstName || null,
lastName || null,
company || null,
position || null,
email || null,
phone || null,
mobile || null,
address || null,
postalCode || null,
city || null,
country || null,
website || null,
notes || null,
Array.isArray(tags) ? tags.join(', ') : null,
contactId
);
const updatedContact = db.prepare(`
SELECT c.*, u.display_name as creator_name
FROM contacts c
LEFT JOIN users u ON c.created_by = u.id
WHERE c.id = ?
`).get(contactId);
// Socket.io Event
const io = req.app.get('io');
io.emit('contact:updated', {
contact: {
id: updatedContact.id,
firstName: updatedContact.first_name,
lastName: updatedContact.last_name,
company: updatedContact.company,
position: updatedContact.position,
email: updatedContact.email,
phone: updatedContact.phone,
mobile: updatedContact.mobile,
address: updatedContact.address,
postalCode: updatedContact.postal_code,
city: updatedContact.city,
country: updatedContact.country,
website: updatedContact.website,
notes: updatedContact.notes,
tags: updatedContact.tags ? updatedContact.tags.split(',').map(t => t.trim()) : [],
createdAt: updatedContact.created_at,
updatedAt: updatedContact.updated_at,
createdBy: updatedContact.created_by,
creatorName: updatedContact.creator_name
},
userId
});
res.json({
id: updatedContact.id,
firstName: updatedContact.first_name,
lastName: updatedContact.last_name,
company: updatedContact.company,
position: updatedContact.position,
email: updatedContact.email,
phone: updatedContact.phone,
mobile: updatedContact.mobile,
address: updatedContact.address,
postalCode: updatedContact.postal_code,
city: updatedContact.city,
country: updatedContact.country,
website: updatedContact.website,
notes: updatedContact.notes,
tags: updatedContact.tags ? updatedContact.tags.split(',').map(t => t.trim()) : [],
createdAt: updatedContact.created_at,
updatedAt: updatedContact.updated_at,
createdBy: updatedContact.created_by,
creatorName: updatedContact.creator_name
});
logger.info('Kontakt aktualisiert', { contactId, userId });
} catch (error) {
logger.error('Fehler beim Aktualisieren des Kontakts:', { error: error.message, contactId: req.params.id });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* DELETE /api/contacts/:id
* Kontakt löschen
*/
router.delete('/:id', (req, res) => {
try {
const db = getDb();
const contactId = req.params.id;
const userId = req.user.id;
// Prüfen ob Kontakt existiert
const existing = db.prepare('SELECT id FROM contacts WHERE id = ?').get(contactId);
if (!existing) {
return res.status(404).json({ error: 'Kontakt nicht gefunden' });
}
// Löschen
db.prepare('DELETE FROM contacts WHERE id = ?').run(contactId);
// Socket.io Event
const io = req.app.get('io');
io.emit('contact:deleted', { contactId, userId });
res.json({ success: true });
logger.info('Kontakt gelöscht', { contactId, userId });
} catch (error) {
logger.error('Fehler beim Löschen des Kontakts:', { error: error.message, contactId: req.params.id });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/contacts/tags
* Alle verwendeten Tags abrufen
*/
router.get('/tags/all', (req, res) => {
try {
const db = getDb();
const contacts = db.prepare('SELECT DISTINCT tags FROM contacts WHERE tags IS NOT NULL').all();
// Alle Tags sammeln und deduplizieren
const allTags = new Set();
contacts.forEach(contact => {
if (contact.tags) {
contact.tags.split(',').forEach(tag => {
const trimmedTag = tag.trim();
if (trimmedTag) {
allTags.add(trimmedTag);
}
});
}
});
res.json(Array.from(allTags).sort());
} catch (error) {
logger.error('Fehler beim Abrufen der Tags:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
module.exports = router;

Datei anzeigen

@ -110,13 +110,13 @@ router.post('/categories', (req, res) => {
return res.status(400).json({ error: 'Eine Kategorie mit diesem Namen existiert bereits' });
}
// Position ermitteln
const lastPosition = db.prepare(
'SELECT MAX(position) as max_pos FROM knowledge_categories'
).get();
const position = (lastPosition.max_pos ?? -1) + 1;
// Alle bestehenden Kategorien um 1 nach unten verschieben
db.prepare(`
UPDATE knowledge_categories
SET position = position + 1
`).run();
// Einfügen
// Neue Kategorie an Position 0 (ganz oben) einfügen
const result = db.prepare(`
INSERT INTO knowledge_categories (name, description, color, icon, position, created_by)
VALUES (?, ?, ?, ?, ?, ?)
@ -125,7 +125,7 @@ router.post('/categories', (req, res) => {
description ? stripHtml(description) : null,
color || '#3B82F6',
icon || null,
position,
0, // Neue Kategorien immer an Position 0 (oben)
req.user.id
);
@ -473,13 +473,14 @@ router.post('/entries', (req, res) => {
return res.status(404).json({ error: 'Kategorie nicht gefunden' });
}
// Position ermitteln
const lastPosition = db.prepare(
'SELECT MAX(position) as max_pos FROM knowledge_entries WHERE category_id = ?'
).get(categoryId);
const position = (lastPosition.max_pos ?? -1) + 1;
// Alle bestehenden Einträge um 1 nach unten verschieben
db.prepare(`
UPDATE knowledge_entries
SET position = position + 1
WHERE category_id = ?
`).run(categoryId);
// Einfügen
// Neuen Eintrag an Position 0 (ganz oben) einfügen
const result = db.prepare(`
INSERT INTO knowledge_entries (category_id, title, url, notes, position, created_by)
VALUES (?, ?, ?, ?, ?, ?)
@ -488,7 +489,7 @@ router.post('/entries', (req, res) => {
stripHtml(title),
url || null,
notes || null,
position,
0, // Neue Einträge immer an Position 0 (oben)
req.user.id
);

360
backend/routes/reminders.js Normale Datei
Datei anzeigen

@ -0,0 +1,360 @@
/**
* TASKMATE - Reminders API
* ========================
* API endpoints für Erinnerungen
*/
const express = require('express');
const router = express.Router();
const { getDb } = require('../database');
const reminderService = require('../services/reminderService');
// GET /api/reminders - Alle Erinnerungen für ein Projekt abrufen
router.get('/', (req, res) => {
try {
const { project_id } = req.query;
const db = getDb();
if (!project_id) {
return res.status(400).json({
success: false,
error: 'project_id ist erforderlich'
});
}
const reminders = db.prepare(`
SELECT r.*, u.display_name as creator_name
FROM reminders r
LEFT JOIN users u ON r.created_by = u.id
WHERE r.project_id = ? AND r.is_active = 1
ORDER BY r.reminder_date ASC, r.reminder_time ASC
`).all(project_id);
// Advance days von String zu Array konvertieren
reminders.forEach(reminder => {
reminder.advance_days = reminder.advance_days ? reminder.advance_days.split(',') : ['1'];
});
res.json({
success: true,
data: reminders
});
} catch (error) {
console.error('Error fetching reminders:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
// GET /api/reminders/:id - Einzelne Erinnerung abrufen
router.get('/:id', (req, res) => {
try {
const db = getDb();
const reminder = db.prepare(`
SELECT r.*, u.display_name as creator_name
FROM reminders r
LEFT JOIN users u ON r.created_by = u.id
WHERE r.id = ?
`).get(req.params.id);
if (!reminder) {
return res.status(404).json({
success: false,
error: 'Erinnerung nicht gefunden'
});
}
// Advance days von String zu Array konvertieren
reminder.advance_days = reminder.advance_days ? reminder.advance_days.split(',') : ['1'];
res.json({
success: true,
data: reminder
});
} catch (error) {
console.error('Error fetching reminder:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
// POST /api/reminders - Neue Erinnerung erstellen
router.post('/', (req, res) => {
try {
const {
project_id,
title,
description,
reminder_date,
reminder_time,
color,
advance_days,
repeat_type,
repeat_interval
} = req.body;
if (!project_id || !title || !reminder_date) {
return res.status(400).json({
success: false,
error: 'project_id, title und reminder_date sind erforderlich'
});
}
const db = getDb();
// Advance days Array zu String konvertieren
const advanceDaysStr = Array.isArray(advance_days) ? advance_days.join(',') : '1';
const result = db.prepare(`
INSERT INTO reminders (
project_id, title, description, reminder_date, reminder_time,
color, advance_days, repeat_type, repeat_interval, created_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
project_id,
title,
description || null,
reminder_date,
reminder_time || '09:00',
color || '#F59E0B',
advanceDaysStr,
repeat_type || 'none',
repeat_interval || 1,
req.user.id
);
// Benachrichtigungs-Termine mit ReminderService erstellen
if (advance_days && Array.isArray(advance_days)) {
const serviceInstance = reminderService.getInstance();
serviceInstance.createNotificationSchedule(result.lastInsertRowid, reminder_date, advance_days);
}
// Neue Erinnerung abrufen
const newReminder = db.prepare(`
SELECT r.*, u.display_name as creator_name
FROM reminders r
LEFT JOIN users u ON r.created_by = u.id
WHERE r.id = ?
`).get(result.lastInsertRowid);
newReminder.advance_days = newReminder.advance_days ? newReminder.advance_days.split(',') : ['1'];
res.status(201).json({
success: true,
data: newReminder
});
} catch (error) {
console.error('Error creating reminder:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
// PUT /api/reminders/:id - Erinnerung bearbeiten
router.put('/:id', (req, res) => {
try {
const {
title,
description,
reminder_date,
reminder_time,
color,
advance_days,
repeat_type,
repeat_interval,
is_active
} = req.body;
const db = getDb();
// Prüfen ob Erinnerung existiert
const existing = db.prepare('SELECT * FROM reminders WHERE id = ?').get(req.params.id);
if (!existing) {
return res.status(404).json({
success: false,
error: 'Erinnerung nicht gefunden'
});
}
// Advance days Array zu String konvertieren
const advanceDaysStr = Array.isArray(advance_days) ? advance_days.join(',') : existing.advance_days;
db.prepare(`
UPDATE reminders SET
title = ?,
description = ?,
reminder_date = ?,
reminder_time = ?,
color = ?,
advance_days = ?,
repeat_type = ?,
repeat_interval = ?,
is_active = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(
title || existing.title,
description !== undefined ? description : existing.description,
reminder_date || existing.reminder_date,
reminder_time || existing.reminder_time,
color || existing.color,
advanceDaysStr,
repeat_type || existing.repeat_type,
repeat_interval || existing.repeat_interval,
is_active !== undefined ? is_active : existing.is_active,
req.params.id
);
// Benachrichtigungs-Termine neu berechnen wenn sich das Datum geändert hat
if (reminder_date || advance_days) {
const finalAdvanceDays = advance_days || existing.advance_days.split(',');
const finalReminderDate = reminder_date || existing.reminder_date;
const serviceInstance = reminderService.getInstance();
serviceInstance.createNotificationSchedule(req.params.id, finalReminderDate, finalAdvanceDays);
}
// Aktualisierte Erinnerung abrufen
const updatedReminder = db.prepare(`
SELECT r.*, u.display_name as creator_name
FROM reminders r
LEFT JOIN users u ON r.created_by = u.id
WHERE r.id = ?
`).get(req.params.id);
updatedReminder.advance_days = updatedReminder.advance_days ? updatedReminder.advance_days.split(',') : ['1'];
res.json({
success: true,
data: updatedReminder
});
} catch (error) {
console.error('Error updating reminder:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
// DELETE /api/reminders/:id - Erinnerung löschen
router.delete('/:id', (req, res) => {
try {
const db = getDb();
const result = db.prepare('DELETE FROM reminders WHERE id = ?').run(req.params.id);
if (result.changes === 0) {
return res.status(404).json({
success: false,
error: 'Erinnerung nicht gefunden'
});
}
res.json({
success: true,
message: 'Erinnerung erfolgreich gelöscht'
});
} catch (error) {
console.error('Error deleting reminder:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
// GET /api/reminders/due/check - Fällige Erinnerungen prüfen (für Cron-Job)
router.get('/due/check', (req, res) => {
try {
const db = getDb();
const today = new Date().toISOString().split('T')[0];
// Fällige Benachrichtigungen finden
const dueNotifications = db.prepare(`
SELECT rn.*, r.title, r.description, r.project_id, r.created_by, r.reminder_date, r.color
FROM reminder_notifications rn
JOIN reminders r ON rn.reminder_id = r.id
WHERE rn.notification_date <= ? AND rn.sent = 0 AND r.is_active = 1
ORDER BY rn.notification_date ASC
`).all(today);
res.json({
success: true,
data: dueNotifications
});
} catch (error) {
console.error('Error checking due reminders:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
// POST /api/reminders/due/mark-sent - Benachrichtigung als gesendet markieren
router.post('/due/mark-sent', (req, res) => {
try {
const { notification_id } = req.body;
const db = getDb();
db.prepare('UPDATE reminder_notifications SET sent = 1 WHERE id = ?').run(notification_id);
res.json({
success: true,
message: 'Benachrichtigung als gesendet markiert'
});
} catch (error) {
console.error('Error marking notification as sent:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
// GET /api/reminders/stats - Debug-Statistiken für Reminder Service
router.get('/stats', (req, res) => {
try {
const serviceInstance = reminderService.getInstance();
const stats = serviceInstance.getStats();
res.json({
success: true,
data: stats
});
} catch (error) {
console.error('Error getting reminder stats:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
// POST /api/reminders/check-now - Manuelle Prüfung fälliger Erinnerungen
router.post('/check-now', async (req, res) => {
try {
const serviceInstance = reminderService.getInstance();
await serviceInstance.manualCheck();
res.json({
success: true,
message: 'Manuelle Reminder-Prüfung durchgeführt'
});
} catch (error) {
console.error('Error during manual reminder check:', error);
res.status(500).json({
success: false,
error: 'Interner Server-Fehler'
});
}
});
module.exports = router;