Refaktoriere Controller-Methoden zur Benutzer-, Event- und Menü-Datenverwaltung, indem die Logik in separate Service-Klassen ausgelagert wird. Implementiere eine verbesserte Fehlerbehandlung und sichere Rückgaben. Füge eine neue Route zur Passwortänderung im Benutzer-Router hinzu.
This commit is contained in:
@@ -1,205 +1,29 @@
|
|||||||
const bcrypt = require('bcryptjs');
|
const AuthService = require('../services/AuthService');
|
||||||
const { User, PasswordResetToken } = require('../models');
|
const ErrorHandler = require('../utils/ErrorHandler');
|
||||||
const jwt = require('jsonwebtoken');
|
|
||||||
const { addTokenToBlacklist } = require('../utils/blacklist');
|
|
||||||
const { transporter, getPasswordResetEmailTemplate } = require('../config/email');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
function delay(ms) {
|
exports.register = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
const result = await AuthService.register(req.body);
|
||||||
}
|
ErrorHandler.successResponse(res, result, 'Benutzer erfolgreich registriert', 201);
|
||||||
|
});
|
||||||
|
|
||||||
exports.register = async (req, res) => {
|
exports.login = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
const { name, email, password } = req.body;
|
const result = await AuthService.login(req.body);
|
||||||
if (!name || !email || !password) {
|
ErrorHandler.successResponse(res, result, result.message);
|
||||||
return res.status(400).json({ message: 'Alle Felder sind erforderlich' });
|
});
|
||||||
}
|
|
||||||
try {
|
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
|
||||||
console.log('Register: creating user', { email });
|
|
||||||
|
|
||||||
const maxAttempts = 3;
|
exports.forgotPassword = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
let attempt = 0;
|
const result = await AuthService.forgotPassword(req.body.email);
|
||||||
let createdUser = null;
|
ErrorHandler.successResponse(res, result, result.message);
|
||||||
let lastError = null;
|
});
|
||||||
|
|
||||||
while (attempt < maxAttempts && !createdUser) {
|
exports.resetPassword = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const result = await AuthService.resetPassword(req.body.token, req.body.password);
|
||||||
createdUser = await User.create({ name, email, password: hashedPassword, active: true });
|
ErrorHandler.successResponse(res, result, result.message);
|
||||||
} catch (err) {
|
});
|
||||||
lastError = err;
|
|
||||||
// Spezifisch auf Lock-Timeout reagieren und erneut versuchen
|
|
||||||
if ((err.code === 'ER_LOCK_WAIT_TIMEOUT' || err?.parent?.code === 'ER_LOCK_WAIT_TIMEOUT') && attempt < maxAttempts - 1) {
|
|
||||||
const backoffMs = 300 * (attempt + 1);
|
|
||||||
console.warn(`Register: ER_LOCK_WAIT_TIMEOUT, retry in ${backoffMs}ms (attempt ${attempt + 1}/${maxAttempts})`);
|
|
||||||
await delay(backoffMs);
|
|
||||||
attempt++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!createdUser && lastError) {
|
exports.logout = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
console.error('Register error (after retries):', lastError);
|
|
||||||
return res.status(503).json({ message: 'Zeitüberschreitung beim Zugriff auf die Datenbank. Bitte erneut versuchen.' });
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Register: user created', { id: createdUser.id });
|
|
||||||
|
|
||||||
const safeUser = {
|
|
||||||
id: createdUser.id,
|
|
||||||
name: createdUser.name,
|
|
||||||
email: createdUser.email,
|
|
||||||
active: createdUser.active,
|
|
||||||
created_at: createdUser.created_at
|
|
||||||
};
|
|
||||||
|
|
||||||
return res.status(201).json({ message: 'Benutzer erfolgreich registriert', user: safeUser });
|
|
||||||
} catch (error) {
|
|
||||||
if (error.name === 'SequelizeUniqueConstraintError') {
|
|
||||||
return res.status(400).json({ message: 'Email-Adresse bereits in Verwendung' });
|
|
||||||
}
|
|
||||||
console.error('Register error:', error);
|
|
||||||
return res.status(500).json({ message: 'Ein Fehler ist aufgetreten', error: error.message });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.login = async (req, res) => {
|
|
||||||
const { email, password } = req.body;
|
|
||||||
if (!email || !password) {
|
|
||||||
return res.status(400).json({ message: 'Email und Passwort sind erforderlich' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const user = await User.findOne({ where: { email } });
|
|
||||||
if (!user) {
|
|
||||||
return res.status(401).json({ message: 'Ungültige Anmeldedaten' });
|
|
||||||
}
|
|
||||||
const validPassword = await bcrypt.compare(password, user.password);
|
|
||||||
if (!validPassword) {
|
|
||||||
return res.status(401).json({ message: 'Ungültige Anmeldedaten' });
|
|
||||||
}
|
|
||||||
if (!user.active) {
|
|
||||||
return res.status(403).json({ message: 'Benutzerkonto ist nicht aktiv' });
|
|
||||||
}
|
|
||||||
const token = jwt.sign({ id: user.id, name: user.name, email: user.email }, 'zTxVgptmPl9!_dr%xxx9999(dd)', { expiresIn: '1h' });
|
|
||||||
return res.status(200).json({ message: 'Login erfolgreich', token, 'user': user });
|
|
||||||
} catch (error) {
|
|
||||||
return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.forgotPassword = async (req, res) => {
|
|
||||||
const { email } = req.body;
|
|
||||||
if (!email) {
|
|
||||||
return res.status(400).json({ message: 'E-Mail-Adresse ist erforderlich' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const user = await User.findOne({ where: { email } });
|
|
||||||
if (!user) {
|
|
||||||
// Aus Sicherheitsgründen immer Erfolg melden, auch wenn E-Mail nicht existiert
|
|
||||||
return res.status(200).json({ message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alte Reset-Tokens für diesen User löschen
|
|
||||||
await PasswordResetToken.destroy({ where: { userId: user.id } });
|
|
||||||
|
|
||||||
// Neuen Reset-Token generieren
|
|
||||||
const token = crypto.randomBytes(32).toString('hex');
|
|
||||||
const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 Stunde
|
|
||||||
|
|
||||||
await PasswordResetToken.create({
|
|
||||||
userId: user.id,
|
|
||||||
token,
|
|
||||||
expiresAt
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset-URL generieren
|
|
||||||
const resetUrl = `${process.env.FRONTEND_URL || 'http://localhost:8080'}/reset-password?token=${token}`;
|
|
||||||
|
|
||||||
// E-Mail versenden
|
|
||||||
const emailTemplate = getPasswordResetEmailTemplate(resetUrl, user.name);
|
|
||||||
|
|
||||||
const mailOptions = {
|
|
||||||
from: process.env.SMTP_FROM || 'noreply@miriamgemeinde.de',
|
|
||||||
to: email,
|
|
||||||
subject: emailTemplate.subject,
|
|
||||||
html: emailTemplate.html,
|
|
||||||
text: emailTemplate.text
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('=== EMAIL SENDING DEBUG ===');
|
|
||||||
console.log('From:', mailOptions.from);
|
|
||||||
console.log('To:', mailOptions.to);
|
|
||||||
console.log('Subject:', mailOptions.subject);
|
|
||||||
console.log('Reset URL:', resetUrl);
|
|
||||||
console.log('===========================');
|
|
||||||
|
|
||||||
await transporter.sendMail(mailOptions);
|
|
||||||
|
|
||||||
console.log('Password reset email sent to:', email);
|
|
||||||
return res.status(200).json({ message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Forgot password error:', error);
|
|
||||||
return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.resetPassword = async (req, res) => {
|
|
||||||
const { token, password } = req.body;
|
|
||||||
if (!token || !password) {
|
|
||||||
return res.status(400).json({ message: 'Token und neues Passwort sind erforderlich' });
|
|
||||||
}
|
|
||||||
if (password.length < 6) {
|
|
||||||
return res.status(400).json({ message: 'Passwort muss mindestens 6 Zeichen lang sein' });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Token validieren
|
|
||||||
const resetToken = await PasswordResetToken.findOne({
|
|
||||||
where: {
|
|
||||||
token,
|
|
||||||
used: false,
|
|
||||||
expiresAt: {
|
|
||||||
[require('sequelize').Op.gt]: new Date()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
include: [{ model: User, as: 'user' }]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!resetToken) {
|
|
||||||
return res.status(400).json({ message: 'Ungültiger oder abgelaufener Token' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Passwort hashen und aktualisieren
|
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
|
||||||
await User.update(
|
|
||||||
{ password: hashedPassword },
|
|
||||||
{ where: { id: resetToken.userId } }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Token als verwendet markieren
|
|
||||||
await resetToken.update({ used: true });
|
|
||||||
|
|
||||||
console.log('Password reset successful for user:', resetToken.userId);
|
|
||||||
return res.status(200).json({ message: 'Passwort erfolgreich zurückgesetzt' });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Reset password error:', error);
|
|
||||||
return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.logout = async (req, res) => {
|
|
||||||
const authHeader = req.header('Authorization');
|
const authHeader = req.header('Authorization');
|
||||||
if (!authHeader) {
|
const token = authHeader ? authHeader.replace('Bearer ', '') : null;
|
||||||
return res.status(400).json({ message: 'Kein Token bereitgestellt' });
|
const result = await AuthService.logout(token);
|
||||||
}
|
ErrorHandler.successResponse(res, result, result.message);
|
||||||
const token = authHeader.replace('Bearer ', '');
|
});
|
||||||
try {
|
|
||||||
addTokenToBlacklist(token);
|
|
||||||
return res.status(200).json({ message: 'Logout erfolgreich' });
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return res.status(500).json({ message: 'Ein Fehler ist beim Logout aufgetreten' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,188 +1,32 @@
|
|||||||
const { Event, Institution, EventPlace, ContactPerson, EventType } = require('../models');
|
const EventService = require('../services/EventService');
|
||||||
const { Op } = require('sequelize');
|
const ErrorHandler = require('../utils/ErrorHandler');
|
||||||
const moment = require('moment'); // Import von Moment.js
|
|
||||||
|
|
||||||
const getAllEvents = async (req, res) => {
|
exports.getAllEvents = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const events = await EventService.getAllEvents();
|
||||||
const events = await Event.findAll({
|
ErrorHandler.successResponse(res, events, 'Events erfolgreich abgerufen');
|
||||||
include: [
|
});
|
||||||
{ model: Institution, as: 'institution' },
|
|
||||||
{ model: EventPlace, as: 'eventPlace' },
|
|
||||||
{ model: EventType, as: 'eventType' },
|
|
||||||
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } }
|
|
||||||
],
|
|
||||||
order: ['name', 'date', 'time']
|
|
||||||
});
|
|
||||||
res.json(events);
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to fetch events' });
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterEvents = async (req, res) => {
|
exports.getEventById = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const event = await EventService.getEventById(req.params.id);
|
||||||
const request = req.body;
|
ErrorHandler.successResponse(res, event, 'Event erfolgreich abgerufen');
|
||||||
const where = {
|
});
|
||||||
[Op.or]: [
|
|
||||||
{
|
|
||||||
date: {
|
|
||||||
[Op.or]: [
|
|
||||||
{ [Op.gte]: moment().startOf('day').toDate() },
|
|
||||||
{ [Op.eq]: null }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ dayOfWeek: { [Op.gte]: 0 } }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
const order = [
|
|
||||||
['date', 'ASC'],
|
|
||||||
['time', 'ASC']
|
|
||||||
];
|
|
||||||
|
|
||||||
if (request.id === 'all') {
|
exports.filterEvents = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
const events = await Event.findAll({
|
const result = await EventService.filterEvents(req.body);
|
||||||
where,
|
ErrorHandler.successResponse(res, result, 'Events erfolgreich gefiltert');
|
||||||
include: [
|
});
|
||||||
{ model: Institution, as: 'institution' },
|
|
||||||
{ model: EventPlace, as: 'eventPlace' },
|
|
||||||
{ model: EventType, as: 'eventType' },
|
|
||||||
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } }
|
|
||||||
],
|
|
||||||
order: order,
|
|
||||||
logging: console.log // Log the generated SQL query
|
|
||||||
});
|
|
||||||
return res.json({ events });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.id === 'home') {
|
exports.createEvent = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
const events = await Event.findAll({
|
const event = await EventService.createEvent(req.body);
|
||||||
where: {
|
ErrorHandler.successResponse(res, event, 'Event erfolgreich erstellt', 201);
|
||||||
alsoOnHomepage: 1,
|
});
|
||||||
date: { [Op.gte]: moment().startOf('day').toDate() }
|
|
||||||
},
|
|
||||||
include: [
|
|
||||||
{ model: Institution, as: 'institution' },
|
|
||||||
{ model: EventPlace, as: 'eventPlace' },
|
|
||||||
{ model: EventType, as: 'eventType' },
|
|
||||||
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } },
|
|
||||||
],
|
|
||||||
order: order,
|
|
||||||
});
|
|
||||||
return res.json({ events });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.id && !request.places && !request.types) {
|
exports.updateEvent = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
return res.json({ events: [], places: [], types: [], contactPersons: [] });
|
const event = await EventService.updateEvent(req.params.id, req.body);
|
||||||
}
|
ErrorHandler.successResponse(res, event, 'Event erfolgreich aktualisiert');
|
||||||
|
});
|
||||||
|
|
||||||
if (request.id) {
|
exports.deleteEvent = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
where.id = request.id;
|
const result = await EventService.deleteEvent(req.params.id);
|
||||||
}
|
ErrorHandler.successResponse(res, result, result.message);
|
||||||
|
});
|
||||||
if (request.places && request.places.length > 0) {
|
|
||||||
where.event_place_id = {
|
|
||||||
[Op.in]: request.places.map(id => parseInt(id))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.types && request.types.length > 0) {
|
|
||||||
where.eventTypeId = {
|
|
||||||
[Op.in]: request.types.map(id => parseInt(id))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = await Event.findAll({
|
|
||||||
where,
|
|
||||||
include: [
|
|
||||||
{ model: Institution, as: 'institution' },
|
|
||||||
{ model: EventPlace, as: 'eventPlace' },
|
|
||||||
{ model: EventType, as: 'eventType' },
|
|
||||||
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } }
|
|
||||||
],
|
|
||||||
order: order,
|
|
||||||
});
|
|
||||||
const displayFields = request.display ? request.display : [];
|
|
||||||
|
|
||||||
const filteredEvents = events.map(event => {
|
|
||||||
const filteredEvent = { ...event.toJSON() };
|
|
||||||
|
|
||||||
if (!displayFields.includes('name')) delete filteredEvent.name;
|
|
||||||
if (!displayFields.includes('type')) delete filteredEvent.eventType;
|
|
||||||
if (!displayFields.includes('place')) delete filteredEvent.eventPlace;
|
|
||||||
if (!displayFields.includes('description')) delete filteredEvent.description;
|
|
||||||
if (!displayFields.includes('time')) delete filteredEvent.time;
|
|
||||||
if (!displayFields.includes('time')) delete filteredEvent.endTime;
|
|
||||||
if (!displayFields.includes('contactPerson')) delete filteredEvent.contactPersons;
|
|
||||||
if (!displayFields.includes('day')) delete filteredEvent.dayOfWeek;
|
|
||||||
if (!displayFields.includes('institution')) delete filteredEvent.institution;
|
|
||||||
|
|
||||||
return filteredEvent;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json({ events: filteredEvents });
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to filter events' });
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createEvent = async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { contactPersonIds, ...eventData } = req.body;
|
|
||||||
eventData.alsoOnHomepage = eventData.alsoOnHomepage ?? 0;
|
|
||||||
const event = await Event.create(eventData);
|
|
||||||
if (contactPersonIds) {
|
|
||||||
await event.setContactPersons(contactPersonIds);
|
|
||||||
}
|
|
||||||
res.status(201).json(event);
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to create event' });
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateEvent = async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { contactPersonIds, ...eventData } = req.body;
|
|
||||||
const event = await Event.findByPk(id);
|
|
||||||
if (!event) {
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
}
|
|
||||||
await event.update(eventData);
|
|
||||||
if (contactPersonIds) {
|
|
||||||
await event.setContactPersons(contactPersonIds);
|
|
||||||
}
|
|
||||||
res.status(200).json(event);
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to update event' });
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteEvent = async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
const deleted = await Event.destroy({
|
|
||||||
where: { id: id }
|
|
||||||
});
|
|
||||||
if (deleted) {
|
|
||||||
res.status(204).json();
|
|
||||||
} else {
|
|
||||||
res.status(404).json({ error: 'Event not found' });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to delete event' });
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getAllEvents,
|
|
||||||
createEvent,
|
|
||||||
updateEvent,
|
|
||||||
deleteEvent,
|
|
||||||
filterEvents
|
|
||||||
};
|
|
||||||
@@ -1,30 +1,12 @@
|
|||||||
const { MenuItem } = require('../models');
|
const MenuDataService = require('../services/MenuDataService');
|
||||||
const fetchMenuData = require('../utils/fetchMenuData');
|
const ErrorHandler = require('../utils/ErrorHandler');
|
||||||
|
|
||||||
exports.getMenuData = async (req, res) => {
|
exports.getMenuData = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const menuData = await MenuDataService.getMenuData();
|
||||||
const menuData = await fetchMenuData();
|
ErrorHandler.successResponse(res, menuData, 'Menü-Daten erfolgreich abgerufen');
|
||||||
res.json(menuData);
|
});
|
||||||
} catch (error) {
|
|
||||||
res.status(500).send('Error fetching menu data');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.saveMenuData = async (req, res) => {
|
exports.saveMenuData = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const result = await MenuDataService.saveMenuData(req.body);
|
||||||
const menuData = req.body;
|
ErrorHandler.successResponse(res, result, result.message);
|
||||||
const adjustedMenuData = menuData.map(item => {
|
});
|
||||||
item.parent_id = item.parent_id < 0 ? null : item.parent_id;
|
|
||||||
return item;
|
|
||||||
})
|
|
||||||
.sort((a, b) => (a.parent_id === null ? -1 : 1) - (b.parent_id === null ? -1 : 1));
|
|
||||||
await MenuItem.destroy({ where: {} });
|
|
||||||
for (const item of adjustedMenuData) {
|
|
||||||
await MenuItem.create(item);
|
|
||||||
}
|
|
||||||
res.status(200).send('Menü-Daten erfolgreich gespeichert');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Speichern der Menü-Daten:', error);
|
|
||||||
res.status(500).send('Fehler beim Speichern der Menü-Daten');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,48 +1,27 @@
|
|||||||
// controllers/pageController.js
|
const PageService = require('../services/PageService');
|
||||||
const { Page } = require('../models');
|
const ErrorHandler = require('../utils/ErrorHandler');
|
||||||
|
|
||||||
exports.getMenuData = async (req, res) => {
|
exports.getMenuData = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const pages = await PageService.getAllPages();
|
||||||
const pages = await Page.findAll({
|
ErrorHandler.successResponse(res, pages, 'Seiten erfolgreich abgerufen');
|
||||||
attributes: ['link', 'name']
|
});
|
||||||
});
|
|
||||||
res.json(pages);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Abrufen der Seiten:', error);
|
|
||||||
res.status(500).json({ message: 'Fehler beim Abrufen der Seiten' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getPageContent = async (req, res) => {
|
exports.getPageContent = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const result = await PageService.getPageContent(req.query.link);
|
||||||
const page = await Page.findOne({
|
ErrorHandler.successResponse(res, result, 'Seiteninhalt erfolgreich abgerufen');
|
||||||
where: { link: req.query.link }
|
});
|
||||||
});
|
|
||||||
if (page) {
|
|
||||||
res.json({ content: page.content });
|
|
||||||
} else {
|
|
||||||
res.json({ content: "" });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Laden des Seiteninhalts:', error);
|
|
||||||
res.status(500).json({ message: 'Fehler beim Laden des Seiteninhalts' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.savePageContent = async (req, res) => {
|
exports.savePageContent = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const result = await PageService.savePageContent(req.body);
|
||||||
const { link, name, content } = req.body;
|
ErrorHandler.successResponse(res, result, result.message);
|
||||||
let page = await Page.findOne({ where: { link } });
|
});
|
||||||
if (page) {
|
|
||||||
page.content = content;
|
exports.getPageById = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
page.name = name;
|
const page = await PageService.getPageById(req.params.id);
|
||||||
} else {
|
ErrorHandler.successResponse(res, page, 'Seite erfolgreich abgerufen');
|
||||||
page = await Page.create({ link, name, content });
|
});
|
||||||
}
|
|
||||||
await page.save();
|
exports.deletePage = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
res.json({ message: 'Seiteninhalt gespeichert', page });
|
const result = await PageService.deletePage(req.params.id);
|
||||||
} catch (error) {
|
ErrorHandler.successResponse(res, result, result.message);
|
||||||
console.error('Fehler beim Speichern des Seiteninhalts:', error);
|
});
|
||||||
res.status(500).json({ message: 'Fehler beim Speichern des Seiteninhalts' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,104 +1,42 @@
|
|||||||
const { User } = require('../models');
|
const UserService = require('../services/UserService');
|
||||||
|
const UserValidator = require('../validators/UserValidator');
|
||||||
|
const ErrorHandler = require('../utils/ErrorHandler');
|
||||||
|
|
||||||
exports.getAllUsers = async (req, res) => {
|
exports.getAllUsers = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
const users = await UserService.getAllUsers();
|
||||||
const users = await User.findAll({
|
ErrorHandler.successResponse(res, users, 'Benutzer erfolgreich abgerufen');
|
||||||
order: [['name', 'ASC']],
|
});
|
||||||
attributes: ['id', 'name', 'email', 'active', 'created_at'] // Passwort ausschließen
|
|
||||||
});
|
|
||||||
res.status(200).json(users);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching users:', error);
|
|
||||||
res.status(500).json({ message: 'Error fetching users' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getUserById = async (req, res) => {
|
exports.getUserById = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
UserValidator.validateId(req.params.id);
|
||||||
const user = await User.findByPk(req.params.id, {
|
const user = await UserService.getUserById(req.params.id);
|
||||||
attributes: ['id', 'name', 'email', 'active', 'created_at'] // Passwort ausschließen
|
ErrorHandler.successResponse(res, user, 'Benutzer erfolgreich abgerufen');
|
||||||
});
|
});
|
||||||
if (user) {
|
|
||||||
res.status(200).json(user);
|
|
||||||
} else {
|
|
||||||
res.status(404).json({ message: 'User not found' });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching user:', error);
|
|
||||||
res.status(500).json({ message: 'Error fetching user' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.createUser = async (req, res) => {
|
exports.createUser = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
UserValidator.validateCreateUser(req.body);
|
||||||
const user = await User.create(req.body);
|
const user = await UserService.createUser(req.body);
|
||||||
|
ErrorHandler.successResponse(res, user, 'Benutzer erfolgreich erstellt', 201);
|
||||||
// Sichere User-Daten zurückgeben (ohne Passwort)
|
});
|
||||||
const safeUser = {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name,
|
|
||||||
email: user.email,
|
|
||||||
active: user.active,
|
|
||||||
created_at: user.created_at
|
|
||||||
};
|
|
||||||
|
|
||||||
res.status(201).json(safeUser);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating user:', error);
|
|
||||||
res.status(500).json({ message: 'Error creating user' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.updateUser = async (req, res) => {
|
exports.updateUser = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
UserValidator.validateId(req.params.id);
|
||||||
const user = await User.findByPk(req.params.id);
|
UserValidator.validateUpdateUser(req.body);
|
||||||
if (user) {
|
const user = await UserService.updateUser(req.params.id, req.body);
|
||||||
// Erstelle eine Kopie der Request-Daten ohne sensible Felder
|
ErrorHandler.successResponse(res, user, 'Benutzer erfolgreich aktualisiert');
|
||||||
const updateData = { ...req.body };
|
});
|
||||||
|
|
||||||
// Entferne sensible Felder, die niemals über diese Route geändert werden dürfen
|
|
||||||
delete updateData.password;
|
|
||||||
delete updateData.id;
|
|
||||||
delete updateData.created_at;
|
|
||||||
|
|
||||||
// Setze updated_at auf aktuelle Zeit
|
|
||||||
updateData.updated_at = new Date();
|
|
||||||
|
|
||||||
// Logging für Debugging
|
|
||||||
console.log('Updating user:', req.params.id, 'with data:', updateData);
|
|
||||||
|
|
||||||
await user.update(updateData);
|
|
||||||
|
|
||||||
// Sichere User-Daten zurückgeben (ohne Passwort)
|
|
||||||
const safeUser = {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name,
|
|
||||||
email: user.email,
|
|
||||||
active: user.active,
|
|
||||||
created_at: user.created_at
|
|
||||||
};
|
|
||||||
|
|
||||||
res.status(200).json(safeUser);
|
|
||||||
} else {
|
|
||||||
res.status(404).json({ message: 'User not found' });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating user:', error);
|
|
||||||
res.status(500).json({ message: 'Error updating user' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.deleteUser = async (req, res) => {
|
exports.deleteUser = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
try {
|
UserValidator.validateId(req.params.id);
|
||||||
const user = await User.findByPk(req.params.id);
|
await UserService.deleteUser(req.params.id);
|
||||||
if (user) {
|
ErrorHandler.successResponse(res, null, 'Benutzer erfolgreich gelöscht');
|
||||||
await user.destroy();
|
});
|
||||||
res.status(200).json({ message: 'User deleted successfully' });
|
|
||||||
} else {
|
// Neue Route für Passwort-Änderung
|
||||||
res.status(404).json({ message: 'User not found' });
|
exports.changePassword = ErrorHandler.asyncHandler(async (req, res) => {
|
||||||
}
|
const { currentPassword, newPassword } = req.body;
|
||||||
} catch (error) {
|
UserValidator.validateId(req.params.id);
|
||||||
console.error('Error deleting user:', error);
|
UserValidator.validatePasswordChange(currentPassword, newPassword);
|
||||||
res.status(500).json({ message: 'Error deleting user' });
|
await UserService.changePassword(req.params.id, currentPassword, newPassword);
|
||||||
}
|
ErrorHandler.successResponse(res, null, 'Passwort erfolgreich geändert');
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { getAllUsers, createUser, updateUser, deleteUser, getUserById } = require('../controllers/userController');
|
const { getAllUsers, createUser, updateUser, deleteUser, getUserById, changePassword } = require('../controllers/userController');
|
||||||
const authMiddleware = require('../middleware/authMiddleware');
|
const authMiddleware = require('../middleware/authMiddleware');
|
||||||
|
|
||||||
router.get('/', authMiddleware, getAllUsers);
|
router.get('/', authMiddleware, getAllUsers);
|
||||||
router.get('/:id', authMiddleware, getUserById);
|
router.get('/:id', authMiddleware, getUserById);
|
||||||
router.post('/', authMiddleware, createUser);
|
router.post('/', authMiddleware, createUser);
|
||||||
router.put('/:id', authMiddleware, updateUser);
|
router.put('/:id', authMiddleware, updateUser);
|
||||||
|
router.put('/:id/change-password', authMiddleware, changePassword);
|
||||||
router.delete('/:id', authMiddleware, deleteUser);
|
router.delete('/:id', authMiddleware, deleteUser);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
186
services/AuthService.js
Normal file
186
services/AuthService.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
const { User, PasswordResetToken } = require('../models');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const { addTokenToBlacklist } = require('../utils/blacklist');
|
||||||
|
const { transporter, getPasswordResetEmailTemplate } = require('../config/email');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
class AuthService {
|
||||||
|
/**
|
||||||
|
* User registrieren
|
||||||
|
*/
|
||||||
|
async register(userData) {
|
||||||
|
const { name, email, password } = userData;
|
||||||
|
|
||||||
|
if (!name || !email || !password) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Alle Felder sind erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob E-Mail bereits existiert
|
||||||
|
const existingUser = await User.findOne({ where: { email } });
|
||||||
|
if (existingUser) {
|
||||||
|
throw new Error('EMAIL_ALREADY_EXISTS');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
const user = await User.create({
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
password: hashedPassword,
|
||||||
|
active: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.getSafeUserData(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User einloggen
|
||||||
|
*/
|
||||||
|
async login(credentials) {
|
||||||
|
const { email, password } = credentials;
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Email und Passwort sind erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOne({ where: { email } });
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('INVALID_CREDENTIALS');
|
||||||
|
}
|
||||||
|
|
||||||
|
const validPassword = await bcrypt.compare(password, user.password);
|
||||||
|
if (!validPassword) {
|
||||||
|
throw new Error('INVALID_CREDENTIALS');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.active) {
|
||||||
|
throw new Error('ACCOUNT_INACTIVE');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = jwt.sign(
|
||||||
|
{ id: user.id, name: user.name, email: user.email },
|
||||||
|
'zTxVgptmPl9!_dr%xxx9999(dd)',
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: 'Login erfolgreich',
|
||||||
|
token,
|
||||||
|
user: this.getSafeUserData(user)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User ausloggen
|
||||||
|
*/
|
||||||
|
async logout(token) {
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Kein Token bereitgestellt');
|
||||||
|
}
|
||||||
|
|
||||||
|
addTokenToBlacklist(token);
|
||||||
|
return { message: 'Logout erfolgreich' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passwort vergessen - E-Mail senden
|
||||||
|
*/
|
||||||
|
async forgotPassword(email) {
|
||||||
|
if (!email) {
|
||||||
|
throw new Error('VALIDATION_ERROR: E-Mail-Adresse ist erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOne({ where: { email } });
|
||||||
|
if (!user) {
|
||||||
|
// Aus Sicherheitsgründen immer Erfolg melden
|
||||||
|
return { message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alte Reset-Tokens für diesen User löschen
|
||||||
|
await PasswordResetToken.destroy({ where: { userId: user.id } });
|
||||||
|
|
||||||
|
// Neuen Reset-Token generieren
|
||||||
|
const token = crypto.randomBytes(32).toString('hex');
|
||||||
|
const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 Stunde
|
||||||
|
|
||||||
|
await PasswordResetToken.create({
|
||||||
|
userId: user.id,
|
||||||
|
token,
|
||||||
|
expiresAt
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset-URL generieren
|
||||||
|
const resetUrl = `${process.env.FRONTEND_URL || 'http://localhost:8080'}/reset-password?token=${token}`;
|
||||||
|
|
||||||
|
// E-Mail versenden
|
||||||
|
const emailTemplate = getPasswordResetEmailTemplate(resetUrl, user.name);
|
||||||
|
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: process.env.SMTP_FROM || 'noreply@miriamgemeinde.de',
|
||||||
|
to: email,
|
||||||
|
subject: emailTemplate.subject,
|
||||||
|
html: emailTemplate.html,
|
||||||
|
text: emailTemplate.text
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Password reset email sent to:', email);
|
||||||
|
return { message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passwort zurücksetzen
|
||||||
|
*/
|
||||||
|
async resetPassword(token, newPassword) {
|
||||||
|
if (!token || !newPassword) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Token und neues Passwort sind erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword.length < 6) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Passwort muss mindestens 6 Zeichen lang sein');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token validieren
|
||||||
|
const resetToken = await PasswordResetToken.findOne({
|
||||||
|
where: {
|
||||||
|
token,
|
||||||
|
used: false,
|
||||||
|
expiresAt: {
|
||||||
|
[require('sequelize').Op.gt]: new Date()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: [{ model: User, as: 'user' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resetToken) {
|
||||||
|
throw new Error('INVALID_RESET_TOKEN');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passwort hashen und aktualisieren
|
||||||
|
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||||||
|
await User.update(
|
||||||
|
{ password: hashedPassword },
|
||||||
|
{ where: { id: resetToken.userId } }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Token als verwendet markieren
|
||||||
|
await resetToken.update({ used: true });
|
||||||
|
|
||||||
|
console.log('Password reset successful for user:', resetToken.userId);
|
||||||
|
return { message: 'Passwort erfolgreich zurückgesetzt' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sichere User-Daten extrahieren (ohne Passwort)
|
||||||
|
*/
|
||||||
|
getSafeUserData(user) {
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
active: user.active,
|
||||||
|
created_at: user.created_at
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new AuthService();
|
||||||
276
services/EventService.js
Normal file
276
services/EventService.js
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
const { Event, Institution, EventPlace, ContactPerson, EventType } = require('../models');
|
||||||
|
const { Op } = require('sequelize');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
class EventService {
|
||||||
|
/**
|
||||||
|
* Alle Events abrufen
|
||||||
|
*/
|
||||||
|
async getAllEvents() {
|
||||||
|
try {
|
||||||
|
const events = await Event.findAll({
|
||||||
|
include: [
|
||||||
|
{ model: Institution, as: 'institution' },
|
||||||
|
{ model: EventPlace, as: 'eventPlace' },
|
||||||
|
{ model: EventType, as: 'eventType' },
|
||||||
|
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } }
|
||||||
|
],
|
||||||
|
order: ['name', 'date', 'time']
|
||||||
|
});
|
||||||
|
|
||||||
|
return events;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching all events:', error);
|
||||||
|
throw new Error('EVENTS_FETCH_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event anhand ID abrufen
|
||||||
|
*/
|
||||||
|
async getEventById(id) {
|
||||||
|
try {
|
||||||
|
if (!id || isNaN(parseInt(id))) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Ungültige ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await Event.findByPk(id, {
|
||||||
|
include: [
|
||||||
|
{ model: Institution, as: 'institution' },
|
||||||
|
{ model: EventPlace, as: 'eventPlace' },
|
||||||
|
{ model: EventType, as: 'eventType' },
|
||||||
|
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
throw new Error('EVENT_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching event by ID:', error);
|
||||||
|
throw new Error('EVENT_FETCH_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events filtern
|
||||||
|
*/
|
||||||
|
async filterEvents(filterData) {
|
||||||
|
try {
|
||||||
|
const { id, places, types, display } = filterData;
|
||||||
|
|
||||||
|
// Basis-Where-Klausel für zukünftige Events
|
||||||
|
const where = {
|
||||||
|
[Op.or]: [
|
||||||
|
{
|
||||||
|
date: {
|
||||||
|
[Op.or]: [
|
||||||
|
{ [Op.gte]: moment().startOf('day').toDate() },
|
||||||
|
{ [Op.eq]: null }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ dayOfWeek: { [Op.gte]: 0 } }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const order = [
|
||||||
|
['date', 'ASC'],
|
||||||
|
['time', 'ASC']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Spezielle Filter
|
||||||
|
if (id === 'all') {
|
||||||
|
return await this._getAllFutureEvents(where, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id === 'home') {
|
||||||
|
return await this._getHomepageEvents(where, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id && !places && !types) {
|
||||||
|
return { events: [], places: [], types: [], contactPersons: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weitere Filter anwenden
|
||||||
|
if (id) {
|
||||||
|
where.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (places && places.length > 0) {
|
||||||
|
where.event_place_id = {
|
||||||
|
[Op.in]: places.map(id => parseInt(id))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types && types.length > 0) {
|
||||||
|
where.eventTypeId = {
|
||||||
|
[Op.in]: types.map(id => parseInt(id))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = await Event.findAll({
|
||||||
|
where,
|
||||||
|
include: [
|
||||||
|
{ model: Institution, as: 'institution' },
|
||||||
|
{ model: EventPlace, as: 'eventPlace' },
|
||||||
|
{ model: EventType, as: 'eventType' },
|
||||||
|
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } }
|
||||||
|
],
|
||||||
|
order: order,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Events basierend auf Display-Feldern filtern
|
||||||
|
const displayFields = display || [];
|
||||||
|
const filteredEvents = this._filterEventFields(events, displayFields);
|
||||||
|
|
||||||
|
return { events: filteredEvents };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error filtering events:', error);
|
||||||
|
throw new Error('EVENT_FILTER_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event erstellen
|
||||||
|
*/
|
||||||
|
async createEvent(eventData) {
|
||||||
|
try {
|
||||||
|
const { contactPersonIds, ...eventDataWithoutContacts } = eventData;
|
||||||
|
|
||||||
|
// Validierung
|
||||||
|
if (!eventDataWithoutContacts.name) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Event-Name ist erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
eventDataWithoutContacts.alsoOnHomepage = eventDataWithoutContacts.alsoOnHomepage ?? 0;
|
||||||
|
|
||||||
|
const event = await Event.create(eventDataWithoutContacts);
|
||||||
|
|
||||||
|
if (contactPersonIds && contactPersonIds.length > 0) {
|
||||||
|
await event.setContactPersons(contactPersonIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating event:', error);
|
||||||
|
throw new Error('EVENT_CREATE_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event aktualisieren
|
||||||
|
*/
|
||||||
|
async updateEvent(id, eventData) {
|
||||||
|
try {
|
||||||
|
if (!id || isNaN(parseInt(id))) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Ungültige ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { contactPersonIds, ...eventDataWithoutContacts } = eventData;
|
||||||
|
|
||||||
|
const event = await Event.findByPk(id);
|
||||||
|
if (!event) {
|
||||||
|
throw new Error('EVENT_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
await event.update(eventDataWithoutContacts);
|
||||||
|
|
||||||
|
if (contactPersonIds !== undefined) {
|
||||||
|
await event.setContactPersons(contactPersonIds || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating event:', error);
|
||||||
|
throw new Error('EVENT_UPDATE_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event löschen
|
||||||
|
*/
|
||||||
|
async deleteEvent(id) {
|
||||||
|
try {
|
||||||
|
if (!id || isNaN(parseInt(id))) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Ungültige ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await Event.findByPk(id);
|
||||||
|
if (!event) {
|
||||||
|
throw new Error('EVENT_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
await event.destroy();
|
||||||
|
return { message: 'Event erfolgreich gelöscht' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting event:', error);
|
||||||
|
throw new Error('EVENT_DELETE_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle zukünftigen Events abrufen
|
||||||
|
*/
|
||||||
|
async _getAllFutureEvents(where, order) {
|
||||||
|
const events = await Event.findAll({
|
||||||
|
where,
|
||||||
|
include: [
|
||||||
|
{ model: Institution, as: 'institution' },
|
||||||
|
{ model: EventPlace, as: 'eventPlace' },
|
||||||
|
{ model: EventType, as: 'eventType' },
|
||||||
|
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } }
|
||||||
|
],
|
||||||
|
order: order,
|
||||||
|
logging: console.log
|
||||||
|
});
|
||||||
|
|
||||||
|
return { events };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Homepage Events abrufen
|
||||||
|
*/
|
||||||
|
async _getHomepageEvents(where, order) {
|
||||||
|
const events = await Event.findAll({
|
||||||
|
where: {
|
||||||
|
alsoOnHomepage: 1,
|
||||||
|
date: { [Op.gte]: moment().startOf('day').toDate() }
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{ model: Institution, as: 'institution' },
|
||||||
|
{ model: EventPlace, as: 'eventPlace' },
|
||||||
|
{ model: EventType, as: 'eventType' },
|
||||||
|
{ model: ContactPerson, as: 'contactPersons', through: { attributes: [] } },
|
||||||
|
],
|
||||||
|
order: order,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { events };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event-Felder basierend auf Display-Feldern filtern
|
||||||
|
*/
|
||||||
|
_filterEventFields(events, displayFields) {
|
||||||
|
return events.map(event => {
|
||||||
|
const filteredEvent = { ...event.toJSON() };
|
||||||
|
|
||||||
|
if (!displayFields.includes('name')) delete filteredEvent.name;
|
||||||
|
if (!displayFields.includes('type')) delete filteredEvent.eventType;
|
||||||
|
if (!displayFields.includes('place')) delete filteredEvent.eventPlace;
|
||||||
|
if (!displayFields.includes('description')) delete filteredEvent.description;
|
||||||
|
if (!displayFields.includes('time')) delete filteredEvent.time;
|
||||||
|
if (!displayFields.includes('time')) delete filteredEvent.endTime;
|
||||||
|
if (!displayFields.includes('contactPerson')) delete filteredEvent.contactPersons;
|
||||||
|
if (!displayFields.includes('day')) delete filteredEvent.dayOfWeek;
|
||||||
|
if (!displayFields.includes('institution')) delete filteredEvent.institution;
|
||||||
|
|
||||||
|
return filteredEvent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new EventService();
|
||||||
101
services/MenuDataService.js
Normal file
101
services/MenuDataService.js
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
const { MenuItem } = require('../models');
|
||||||
|
|
||||||
|
class MenuDataService {
|
||||||
|
/**
|
||||||
|
* Alle Menü-Daten abrufen
|
||||||
|
*/
|
||||||
|
async getMenuData() {
|
||||||
|
try {
|
||||||
|
const menuItems = await MenuItem.findAll({
|
||||||
|
order: [['order_id', 'ASC']],
|
||||||
|
include: [{
|
||||||
|
model: MenuItem,
|
||||||
|
as: 'submenu',
|
||||||
|
required: false,
|
||||||
|
order: [['order_id', 'ASC']]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuData = this.buildMenuStructure(menuItems);
|
||||||
|
return menuData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching menu data:', error);
|
||||||
|
throw new Error('MENU_DATA_FETCH_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menü-Daten speichern
|
||||||
|
*/
|
||||||
|
async saveMenuData(menuData) {
|
||||||
|
try {
|
||||||
|
if (!Array.isArray(menuData)) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Menü-Daten müssen ein Array sein');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menü-Daten anpassen
|
||||||
|
const adjustedMenuData = menuData.map(item => {
|
||||||
|
item.parent_id = item.parent_id < 0 ? null : item.parent_id;
|
||||||
|
return item;
|
||||||
|
}).sort((a, b) => (a.parent_id === null ? -1 : 1) - (b.parent_id === null ? -1 : 1));
|
||||||
|
|
||||||
|
// Alle bestehenden Menü-Items löschen
|
||||||
|
await MenuItem.destroy({ where: {} });
|
||||||
|
|
||||||
|
// Neue Menü-Items erstellen
|
||||||
|
for (const item of adjustedMenuData) {
|
||||||
|
await MenuItem.create(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { message: 'Menü-Daten erfolgreich gespeichert' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving menu data:', error);
|
||||||
|
throw new Error('MENU_DATA_SAVE_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menü-Struktur aufbauen
|
||||||
|
*/
|
||||||
|
buildMenuStructure(menuItems) {
|
||||||
|
const menu = [];
|
||||||
|
const itemMap = {};
|
||||||
|
|
||||||
|
// Alle Items in Map speichern
|
||||||
|
menuItems.forEach(item => {
|
||||||
|
itemMap[item.id] = {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
link: item.link,
|
||||||
|
component: item.component,
|
||||||
|
showInMenu: item.show_in_menu,
|
||||||
|
requiresAuth: item.requires_auth,
|
||||||
|
order_id: item.order_id,
|
||||||
|
pageTitle: item.page_title,
|
||||||
|
image: item.image,
|
||||||
|
submenu: []
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hierarchie aufbauen
|
||||||
|
menuItems.forEach(item => {
|
||||||
|
if (item.parent_id) {
|
||||||
|
if (itemMap[item.parent_id]) {
|
||||||
|
itemMap[item.parent_id].submenu.push(itemMap[item.id]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menu.push(itemMap[item.id]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sortierung anwenden
|
||||||
|
menu.sort((a, b) => a.order_id - b.order_id);
|
||||||
|
menu.forEach(item => {
|
||||||
|
item.submenu.sort((a, b) => a.order_id - b.order_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new MenuDataService();
|
||||||
132
services/PageService.js
Normal file
132
services/PageService.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
const { Page } = require('../models');
|
||||||
|
|
||||||
|
class PageService {
|
||||||
|
/**
|
||||||
|
* Seiteninhalt anhand Link abrufen
|
||||||
|
*/
|
||||||
|
async getPageContent(link) {
|
||||||
|
try {
|
||||||
|
if (!link) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Link ist erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = await Page.findOne({ where: { link } });
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
throw new Error('PAGE_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: page.content || '',
|
||||||
|
title: page.title || '',
|
||||||
|
link: page.link
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching page content:', error);
|
||||||
|
throw new Error('PAGE_CONTENT_FETCH_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seiteninhalt speichern
|
||||||
|
*/
|
||||||
|
async savePageContent(pageData) {
|
||||||
|
try {
|
||||||
|
const { link, name, content } = pageData;
|
||||||
|
|
||||||
|
if (!link || !name) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Link und Name sind erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob Seite bereits existiert
|
||||||
|
const existingPage = await Page.findOne({ where: { link } });
|
||||||
|
|
||||||
|
if (existingPage) {
|
||||||
|
// Seite aktualisieren
|
||||||
|
await existingPage.update({
|
||||||
|
content: content || '',
|
||||||
|
title: name,
|
||||||
|
updated_at: new Date()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Neue Seite erstellen
|
||||||
|
await Page.create({
|
||||||
|
link,
|
||||||
|
title: name,
|
||||||
|
content: content || '',
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { message: 'Seiteninhalt erfolgreich gespeichert' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving page content:', error);
|
||||||
|
throw new Error('PAGE_CONTENT_SAVE_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Seiten abrufen
|
||||||
|
*/
|
||||||
|
async getAllPages() {
|
||||||
|
try {
|
||||||
|
const pages = await Page.findAll({
|
||||||
|
order: [['title', 'ASC']],
|
||||||
|
attributes: ['id', 'link', 'title', 'created_at', 'updated_at']
|
||||||
|
});
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching all pages:', error);
|
||||||
|
throw new Error('PAGES_FETCH_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seite anhand ID abrufen
|
||||||
|
*/
|
||||||
|
async getPageById(id) {
|
||||||
|
try {
|
||||||
|
if (!id || isNaN(parseInt(id))) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Ungültige ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = await Page.findByPk(id);
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
throw new Error('PAGE_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
return page;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching page by ID:', error);
|
||||||
|
throw new Error('PAGE_FETCH_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seite löschen
|
||||||
|
*/
|
||||||
|
async deletePage(id) {
|
||||||
|
try {
|
||||||
|
if (!id || isNaN(parseInt(id))) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Ungültige ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = await Page.findByPk(id);
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
throw new Error('PAGE_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.destroy();
|
||||||
|
return { message: 'Seite erfolgreich gelöscht' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting page:', error);
|
||||||
|
throw new Error('PAGE_DELETE_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new PageService();
|
||||||
140
services/UserService.js
Normal file
140
services/UserService.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
const { User } = require('../models');
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
|
||||||
|
class UserService {
|
||||||
|
/**
|
||||||
|
* Alle User abrufen (ohne sensible Daten)
|
||||||
|
*/
|
||||||
|
async getAllUsers() {
|
||||||
|
const users = await User.findAll({
|
||||||
|
order: [['name', 'ASC']],
|
||||||
|
attributes: ['id', 'name', 'email', 'active', 'created_at', 'updated_at']
|
||||||
|
});
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User anhand ID abrufen (ohne sensible Daten)
|
||||||
|
*/
|
||||||
|
async getUserById(id) {
|
||||||
|
const user = await User.findByPk(id, {
|
||||||
|
attributes: ['id', 'name', 'email', 'active', 'created_at', 'updated_at']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('USER_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Neuen User erstellen
|
||||||
|
*/
|
||||||
|
async createUser(userData) {
|
||||||
|
// Passwort hashen falls vorhanden
|
||||||
|
if (userData.password) {
|
||||||
|
userData.password = await bcrypt.hash(userData.password, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.create(userData);
|
||||||
|
|
||||||
|
// Sichere User-Daten zurückgeben
|
||||||
|
return this.getSafeUserData(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User aktualisieren (ohne sensible Felder)
|
||||||
|
*/
|
||||||
|
async updateUser(id, updateData) {
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('USER_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erstelle sichere Update-Daten
|
||||||
|
const safeUpdateData = this.getSafeUpdateData(updateData);
|
||||||
|
|
||||||
|
await user.update(safeUpdateData);
|
||||||
|
|
||||||
|
return this.getSafeUserData(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User löschen
|
||||||
|
*/
|
||||||
|
async deleteUser(id) {
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('USER_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
await user.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User anhand E-Mail abrufen (für interne Verwendung)
|
||||||
|
*/
|
||||||
|
async getUserByEmail(email) {
|
||||||
|
return await User.findOne({ where: { email } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passwort ändern (separate Methode für sichere Passwort-Änderung)
|
||||||
|
*/
|
||||||
|
async changePassword(id, currentPassword, newPassword) {
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('USER_NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktuelles Passwort prüfen
|
||||||
|
const isValidPassword = await bcrypt.compare(currentPassword, user.password);
|
||||||
|
if (!isValidPassword) {
|
||||||
|
throw new Error('INVALID_CURRENT_PASSWORD');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neues Passwort hashen und speichern
|
||||||
|
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||||||
|
await user.update({ password: hashedPassword });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sichere User-Daten extrahieren (ohne Passwort)
|
||||||
|
*/
|
||||||
|
getSafeUserData(user) {
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
active: user.active,
|
||||||
|
created_at: user.created_at,
|
||||||
|
updated_at: user.updated_at
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sichere Update-Daten erstellen (ohne sensible Felder)
|
||||||
|
*/
|
||||||
|
getSafeUpdateData(updateData) {
|
||||||
|
const safeData = { ...updateData };
|
||||||
|
|
||||||
|
// Entferne sensible Felder
|
||||||
|
delete safeData.password;
|
||||||
|
delete safeData.id;
|
||||||
|
delete safeData.created_at;
|
||||||
|
|
||||||
|
// Setze updated_at
|
||||||
|
safeData.updated_at = new Date();
|
||||||
|
|
||||||
|
return safeData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new UserService();
|
||||||
71
utils/ErrorHandler.js
Normal file
71
utils/ErrorHandler.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
class ErrorHandler {
|
||||||
|
/**
|
||||||
|
* Error in HTTP Response umwandeln
|
||||||
|
*/
|
||||||
|
handleError(error, res) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
|
||||||
|
// Validation Errors
|
||||||
|
if (error.message.startsWith('VALIDATION_ERROR:')) {
|
||||||
|
const message = error.message.replace('VALIDATION_ERROR: ', '');
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: message,
|
||||||
|
type: 'VALIDATION_ERROR'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Business Logic Errors
|
||||||
|
switch (error.message) {
|
||||||
|
case 'USER_NOT_FOUND':
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Benutzer nicht gefunden',
|
||||||
|
type: 'NOT_FOUND'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'INVALID_CURRENT_PASSWORD':
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Aktuelles Passwort ist falsch',
|
||||||
|
type: 'INVALID_PASSWORD'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'EMAIL_ALREADY_EXISTS':
|
||||||
|
return res.status(409).json({
|
||||||
|
success: false,
|
||||||
|
message: 'E-Mail-Adresse bereits vorhanden',
|
||||||
|
type: 'DUPLICATE_EMAIL'
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Ein interner Fehler ist aufgetreten',
|
||||||
|
type: 'INTERNAL_ERROR'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success Response erstellen
|
||||||
|
*/
|
||||||
|
successResponse(res, data, message = 'Erfolgreich', statusCode = 200) {
|
||||||
|
return res.status(statusCode).json({
|
||||||
|
success: true,
|
||||||
|
message: message,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async Error Wrapper für Controller
|
||||||
|
*/
|
||||||
|
asyncHandler(fn) {
|
||||||
|
return (req, res, next) => {
|
||||||
|
Promise.resolve(fn(req, res, next)).catch(next);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new ErrorHandler();
|
||||||
109
validators/UserValidator.js
Normal file
109
validators/UserValidator.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
class UserValidator {
|
||||||
|
/**
|
||||||
|
* User-Erstellungsdaten validieren
|
||||||
|
*/
|
||||||
|
validateCreateUser(userData) {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
if (!userData.name || userData.name.trim().length < 2) {
|
||||||
|
errors.push('Name muss mindestens 2 Zeichen lang sein');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userData.email || !this.isValidEmail(userData.email)) {
|
||||||
|
errors.push('Gültige E-Mail-Adresse ist erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userData.password || userData.password.length < 6) {
|
||||||
|
errors.push('Passwort muss mindestens 6 Zeichen lang sein');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new Error(`VALIDATION_ERROR: ${errors.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-Update-Daten validieren
|
||||||
|
*/
|
||||||
|
validateUpdateUser(updateData) {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
if (updateData.name !== undefined && (updateData.name.trim().length < 2)) {
|
||||||
|
errors.push('Name muss mindestens 2 Zeichen lang sein');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateData.email !== undefined && !this.isValidEmail(updateData.email)) {
|
||||||
|
errors.push('Gültige E-Mail-Adresse ist erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateData.active !== undefined && typeof updateData.active !== 'boolean') {
|
||||||
|
errors.push('Active muss ein Boolean-Wert sein');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnung für sensible Felder
|
||||||
|
if (updateData.password !== undefined) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Passwort kann nicht über diese Route geändert werden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateData.id !== undefined) {
|
||||||
|
throw new Error('VALIDATION_ERROR: ID kann nicht geändert werden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateData.created_at !== undefined) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Erstellungsdatum kann nicht geändert werden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new Error(`VALIDATION_ERROR: ${errors.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passwort-Änderung validieren
|
||||||
|
*/
|
||||||
|
validatePasswordChange(currentPassword, newPassword) {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
if (!currentPassword) {
|
||||||
|
errors.push('Aktuelles Passwort ist erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newPassword || newPassword.length < 6) {
|
||||||
|
errors.push('Neues Passwort muss mindestens 6 Zeichen lang sein');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPassword === newPassword) {
|
||||||
|
errors.push('Neues Passwort muss sich vom aktuellen unterscheiden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new Error(`VALIDATION_ERROR: ${errors.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* E-Mail-Format validieren
|
||||||
|
*/
|
||||||
|
isValidEmail(email) {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID validieren
|
||||||
|
*/
|
||||||
|
validateId(id) {
|
||||||
|
if (!id || isNaN(parseInt(id))) {
|
||||||
|
throw new Error('VALIDATION_ERROR: Ungültige ID');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new UserValidator();
|
||||||
Reference in New Issue
Block a user