/** * TASKMATE - Kontakte API * ======================== * REST API für Kontaktmanagement */ const router = require('express').Router(); const { getDb } = require('../database'); const logger = require('../utils/logger'); const { authenticateToken } = require('../middleware/auth'); const { upload } = require('../middleware/upload'); // ===================== // KONTAKTE CRUD // ===================== // GET /api/contacts - Alle Kontakte abrufen router.get('/', authenticateToken, (req, res) => { // Kein Caching für Kontakte res.set('Cache-Control', 'no-store, no-cache, must-revalidate'); res.set('Pragma', 'no-cache'); try { const db = getDb(); const currentUser = req.user; const { project_id, type, status, search } = req.query; logger.info('Kontakte abrufen', { user_id: currentUser.id, filters: req.query }); let query = ` SELECT c.*, u.display_name as created_by_name, pc.display_name as parent_company_name FROM contacts c LEFT JOIN users u ON c.created_by = u.id LEFT JOIN contacts pc ON c.parent_company_id = pc.id WHERE 1=1 `; const params = []; // Filter nach Projekt if (project_id) { query += ' AND c.project_id = ?'; params.push(project_id); } // Filter nach Typ if (type && ['person', 'company'].includes(type)) { query += ' AND c.type = ?'; params.push(type); } // Filter nach Status if (status && ['active', 'inactive', 'archived'].includes(status)) { query += ' AND c.status = ?'; params.push(status); } // Suche if (search) { query += ` AND ( c.display_name LIKE ? OR c.first_name LIKE ? OR c.last_name LIKE ? OR c.company_name LIKE ? OR c.tags LIKE ? OR c.notes LIKE ? )`; const searchPattern = `%${search}%`; params.push(searchPattern, searchPattern, searchPattern, searchPattern, searchPattern, searchPattern); } query += ' ORDER BY c.display_name ASC'; const contacts = db.prepare(query).all(...params); // Details für jeden Kontakt abrufen const contactsWithDetails = contacts.map(contact => { // Kontaktdetails abrufen const details = db.prepare(` SELECT * FROM contact_details WHERE contact_id = ? ORDER BY is_primary DESC, type, label `).all(contact.id); return { ...contact, details }; }); res.json({ success: true, data: contactsWithDetails }); } catch (error) { logger.error('Fehler beim Abrufen der Kontakte:', error); res.status(500).json({ success: false, error: error.message }); } }); // GET /api/contacts/:id - Einzelnen Kontakt abrufen router.get('/:id', authenticateToken, (req, res) => { try { const db = getDb(); const { id } = req.params; const contact = db.prepare(` SELECT c.*, u.display_name as created_by_name, pc.display_name as parent_company_name FROM contacts c LEFT JOIN users u ON c.created_by = u.id LEFT JOIN contacts pc ON c.parent_company_id = pc.id WHERE c.id = ? `).get(id); if (!contact) { return res.status(404).json({ success: false, error: 'Kontakt nicht gefunden' }); } // Details abrufen contact.details = db.prepare(` SELECT * FROM contact_details WHERE contact_id = ? ORDER BY is_primary DESC, type, label `).all(id); // Interaktionen abrufen contact.interactions = db.prepare(` SELECT ci.*, u.display_name as created_by_name FROM contact_interactions ci LEFT JOIN users u ON ci.created_by = u.id WHERE ci.contact_id = ? ORDER BY ci.interaction_date DESC LIMIT 10 `).all(id); res.json({ success: true, data: contact }); } catch (error) { logger.error('Fehler beim Abrufen des Kontakts:', error); res.status(500).json({ success: false, error: error.message }); } }); // POST /api/contacts - Neuen Kontakt erstellen router.post('/', authenticateToken, (req, res) => { try { const db = getDb(); const currentUser = req.user; const { type, project_id, details = [], ...contactData } = req.body; // Validierung if (!type || !['person', 'company'].includes(type)) { return res.status(400).json({ success: false, error: 'Kontakttyp muss "person" oder "company" sein' }); } if (!project_id) { return res.status(400).json({ success: false, error: 'Projekt-ID ist erforderlich' }); } // Display Name generieren falls nicht vorhanden if (!contactData.display_name) { if (type === 'person') { contactData.display_name = `${contactData.first_name || ''} ${contactData.last_name || ''}`.trim(); } else { contactData.display_name = contactData.company_name || ''; } } // Transaktion starten const insertContact = db.prepare(` INSERT INTO contacts ( type, project_id, created_by, display_name, status, tags, notes, avatar_url, salutation, first_name, last_name, position, department, parent_company_id, company_name, company_type, industry, website ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) `); const insertDetail = db.prepare(` INSERT INTO contact_details (contact_id, type, label, value, is_primary) VALUES (?, ?, ?, ?, ?) `); const result = db.transaction(() => { // Kontakt einfügen const info = insertContact.run( type, project_id, currentUser.id, contactData.display_name, contactData.status || 'active', contactData.tags || null, contactData.notes || null, contactData.avatar_url || null, // Person-Felder type === 'person' ? contactData.salutation : null, type === 'person' ? contactData.first_name : null, type === 'person' ? contactData.last_name : null, type === 'person' ? contactData.position : null, type === 'person' ? contactData.department : null, type === 'person' ? contactData.parent_company_id : null, // Company-Felder type === 'company' ? contactData.company_name : null, type === 'company' ? contactData.company_type : null, type === 'company' ? contactData.industry : null, type === 'company' ? contactData.website : null ); const contactId = info.lastInsertRowid; // Details einfügen for (const detail of details) { if (detail.value) { insertDetail.run( contactId, detail.type, detail.label || 'Arbeit', detail.value, detail.is_primary ? 1 : 0 ); } } return contactId; })(); // Neu erstellten Kontakt mit Details abrufen const newContact = db.prepare(` SELECT * FROM contacts WHERE id = ? `).get(result); newContact.details = db.prepare(` SELECT * FROM contact_details WHERE contact_id = ? `).all(result); logger.info('Kontakt erstellt', { contact_id: result, user_id: currentUser.id }); res.json({ success: true, data: newContact, message: 'Kontakt erfolgreich erstellt' }); } catch (error) { logger.error('Fehler beim Erstellen des Kontakts:', error); res.status(500).json({ success: false, error: error.message }); } }); // PUT /api/contacts/:id - Kontakt aktualisieren router.put('/:id', authenticateToken, (req, res) => { try { const db = getDb(); const { id } = req.params; const { details = [], ...contactData } = req.body; // Prüfen ob Kontakt existiert const existing = db.prepare('SELECT * FROM contacts WHERE id = ?').get(id); if (!existing) { return res.status(404).json({ success: false, error: 'Kontakt nicht gefunden' }); } // Update-Query dynamisch aufbauen const fields = []; const values = []; // Erlaubte Felder für Update const allowedFields = [ 'display_name', 'status', 'tags', 'notes', 'avatar_url', 'salutation', 'first_name', 'last_name', 'position', 'department', 'parent_company_id', 'company_name', 'company_type', 'industry', 'website' ]; for (const field of allowedFields) { if (contactData.hasOwnProperty(field)) { fields.push(`${field} = ?`); values.push(contactData[field]); } } values.push(id); // Transaktion für Updates db.transaction(() => { // Kontakt aktualisieren if (fields.length > 0) { db.prepare(` UPDATE contacts SET ${fields.join(', ')} WHERE id = ? `).run(...values); } // Details aktualisieren - alte löschen und neue einfügen db.prepare('DELETE FROM contact_details WHERE contact_id = ?').run(id); const insertDetail = db.prepare(` INSERT INTO contact_details (contact_id, type, label, value, is_primary) VALUES (?, ?, ?, ?, ?) `); for (const detail of details) { if (detail.value) { insertDetail.run( id, detail.type, detail.label || 'Arbeit', detail.value, detail.is_primary ? 1 : 0 ); } } })(); // Aktualisierten Kontakt abrufen const updatedContact = db.prepare(` SELECT * FROM contacts WHERE id = ? `).get(id); updatedContact.details = db.prepare(` SELECT * FROM contact_details WHERE contact_id = ? `).all(id); logger.info('Kontakt aktualisiert', { contact_id: id }); res.json({ success: true, data: updatedContact, message: 'Kontakt erfolgreich aktualisiert' }); } catch (error) { logger.error('Fehler beim Aktualisieren des Kontakts:', error); res.status(500).json({ success: false, error: error.message }); } }); // DELETE /api/contacts/:id - Kontakt löschen router.delete('/:id', authenticateToken, (req, res) => { try { const db = getDb(); const { id } = req.params; const result = db.prepare('DELETE FROM contacts WHERE id = ?').run(id); if (result.changes === 0) { return res.status(404).json({ success: false, error: 'Kontakt nicht gefunden' }); } logger.info('Kontakt gelöscht', { contact_id: id }); res.json({ success: true, message: 'Kontakt erfolgreich gelöscht' }); } catch (error) { logger.error('Fehler beim Löschen des Kontakts:', error); res.status(500).json({ success: false, error: error.message }); } }); // ===================== // INTERAKTIONEN (nach /:id Routen) // ===================== // POST /api/contacts/:id/interactions - Neue Interaktion hinzufügen router.post('/:id/interactions', authenticateToken, (req, res) => { try { const db = getDb(); const currentUser = req.user; const { id } = req.params; const { type, subject, content } = req.body; if (!type || !['call', 'email', 'meeting', 'note', 'task'].includes(type)) { return res.status(400).json({ success: false, error: 'Ungültiger Interaktionstyp' }); } const result = db.prepare(` INSERT INTO contact_interactions ( contact_id, type, subject, content, created_by ) VALUES (?, ?, ?, ?, ?) `).run(id, type, subject, content, currentUser.id); const newInteraction = db.prepare(` SELECT ci.*, u.display_name as created_by_name FROM contact_interactions ci LEFT JOIN users u ON ci.created_by = u.id WHERE ci.id = ? `).get(result.lastInsertRowid); res.json({ success: true, data: newInteraction }); } catch (error) { logger.error('Fehler beim Hinzufügen der Interaktion:', error); res.status(500).json({ success: false, error: error.message }); } }); // ===================== // DATEI-UPLOAD (nach /:id Routen) // ===================== // POST /api/contacts/:id/avatar - Avatar hochladen router.post('/:id/avatar', authenticateToken, upload.single('files'), async (req, res) => { try { const db = getDb(); const { id } = req.params; if (!req.file) { return res.status(400).json({ success: false, error: 'Keine Datei hochgeladen' }); } // Avatar-URL speichern const avatarUrl = `/uploads/${req.file.filename}`; db.prepare('UPDATE contacts SET avatar_url = ? WHERE id = ?') .run(avatarUrl, id); res.json({ success: true, data: { avatar_url: avatarUrl }, message: 'Avatar erfolgreich hochgeladen' }); } catch (error) { logger.error('Fehler beim Avatar-Upload:', error); res.status(500).json({ success: false, error: error.message }); } }); module.exports = router;