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:
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();
|
||||
Reference in New Issue
Block a user