diff --git a/backend/src/routes/contacts.js b/backend/src/routes/contacts.js index 4407cad..225755e 100644 --- a/backend/src/routes/contacts.js +++ b/backend/src/routes/contacts.js @@ -2,6 +2,7 @@ import { Router } from 'express'; import { z } from 'zod'; import { pool } from '../db/connection.js'; import { requireAuth } from '../middleware/auth.js'; +import { createError, sendError } from '../utils/errorHandler.js'; const router = Router(); @@ -19,7 +20,7 @@ const getDealParticipants = async (dealId) => { return rows[0] || null; } catch (error) { console.error('Database error while fetching deal participants for dealId:', dealId, error); - throw new Error('Database error while fetching deal participants'); + throw createError('DATABASE_ERROR', 'Database error while fetching deal participants'); } }; @@ -31,20 +32,20 @@ router.post('/request', requireAuth, async (req, res) => { }).safeParse(req.body); if (!parsed.success) { - return res.status(400).json({ error: 'Invalid input data', details: parsed.error.flatten() }); + throw createError('VALIDATION_ERROR', 'Invalid input data', parsed.error.flatten()); } const { dealId, targetUserId } = parsed.data; const deal = await getDealParticipants(dealId); if (!deal) { - return res.status(404).json({ error: 'Deal not found' }); + throw createError('NOT_FOUND_ERROR', 'Deal not found'); } const participants = [deal.requester_id, deal.helper_id]; if (!participants.includes(req.user.userId) || !participants.includes(targetUserId) || req.user.userId === targetUserId) { - return res.status(403).json({ error: 'Forbidden' }); + throw createError('AUTHORIZATION_ERROR', 'Forbidden'); } const [existing] = await pool.query( @@ -54,7 +55,7 @@ router.post('/request', requireAuth, async (req, res) => { ); if (existing.length) { - return res.status(409).json({ error: 'Request already exists' }); + throw createError('CONFLICT_ERROR', 'Request already exists'); } const [result] = await pool.query( @@ -66,7 +67,7 @@ router.post('/request', requireAuth, async (req, res) => { res.status(201).json({ id: result.insertId }); } catch (error) { console.error('Error in contacts request route:', error); - res.status(500).json({ error: 'Internal server error' }); + sendError(res, error); } }); @@ -78,7 +79,7 @@ router.post('/respond', requireAuth, async (req, res) => { }).safeParse(req.body); if (!parsed.success) { - return res.status(400).json({ error: 'Invalid input data', details: parsed.error.flatten() }); + throw createError('VALIDATION_ERROR', 'Invalid input data', parsed.error.flatten()); } const { requestId, accept } = parsed.data; @@ -90,11 +91,11 @@ router.post('/respond', requireAuth, async (req, res) => { const row = rows[0]; if (!row) { - return res.status(404).json({ error: 'Request not found' }); + throw createError('NOT_FOUND_ERROR', 'Request not found'); } if (row.target_id !== req.user.userId) { - return res.status(403).json({ error: 'Forbidden' }); + throw createError('AUTHORIZATION_ERROR', 'Forbidden'); } if (accept) { @@ -106,7 +107,7 @@ router.post('/respond', requireAuth, async (req, res) => { res.json({ status: 'rejected' }); } catch (error) { console.error('Error in contacts respond route:', error); - res.status(500).json({ error: 'Internal server error' }); + sendError(res, error); } }); diff --git a/backend/src/utils/errorHandler.js b/backend/src/utils/errorHandler.js new file mode 100644 index 0000000..749af72 --- /dev/null +++ b/backend/src/utils/errorHandler.js @@ -0,0 +1,56 @@ +/** + * Einheitliche Fehlerstruktur für die Anwendung + */ +export const createError = (code, message, details = null) => { + const error = new Error(message); + error.code = code; + error.details = details; + error.requestId = generateRequestId(); + return error; +}; + +/** + * Generiert eine eindeutige Request-ID + */ +export const generateRequestId = () => { + return 'req_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5); +}; + +/** + * Sendet eine konsistente Fehlermeldung an den Client + */ +export const sendError = (res, error) => { + const errorResponse = { + code: error.code, + message: error.message, + details: error.details, + requestId: error.requestId + }; + + // Logge den Fehler für Debugging + console.error('API Error:', errorResponse); + + res.status(getStatusCode(error.code)).json(errorResponse); +}; + +/** + * Bestimmt den HTTP-Statuscode basierend auf dem Fehlercode + */ +export const getStatusCode = (code) => { + switch (code) { + case 'VALIDATION_ERROR': + return 400; + case 'AUTHENTICATION_ERROR': + return 401; + case 'AUTHORIZATION_ERROR': + return 403; + case 'NOT_FOUND_ERROR': + return 404; + case 'CONFLICT_ERROR': + return 409; + case 'DATABASE_ERROR': + return 500; + default: + return 500; + } +}; \ No newline at end of file diff --git a/docs/adr/001-error-handling.md b/docs/adr/001-error-handling.md new file mode 100644 index 0000000..a901669 --- /dev/null +++ b/docs/adr/001-error-handling.md @@ -0,0 +1,24 @@ +# 1. Einheitliches Fehlerformat + +## Status + +Akzeptiert + +## Kontext + +Die Anwendung hat momentan unklare und inkonsistente Fehlermeldungen, was die UX und das Debugging erschwert. Es ist notwendig, ein einheitliches Format für Fehlermeldungen zu definieren. + +## Entscheidung + +Wir implementieren ein einheitliches Fehlerformat mit folgenden Feldern: + +- `code`: Ein eindeutiger Fehlercode (z.B. `VALIDATION_ERROR`, `DATABASE_ERROR`) +- `message`: Eine menschenlesbare Fehlermeldung +- `details`: Zusätzliche technische Details zur Fehlerursache +- `requestId`: Eine eindeutige ID für die Anfrage, um Debugging zu erleichtern + +## Konsequenzen + +- Verbesserte UX durch konsistente Fehlermeldungen +- Einfacheres Debugging durch einheitliche Fehlerstruktur +- Bessere Dokumentation der API-Fehler \ No newline at end of file