Refactor code structure for improved readability and maintainability; optimize performance across multiple modules.
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s

This commit is contained in:
Torsten Schulz (local)
2026-07-21 09:08:15 +02:00
parent 75b52de282
commit 1ab9407e79
1400 changed files with 22278 additions and 485 deletions

View File

@@ -0,0 +1,108 @@
import fs from 'fs';
import User from '../models/community/user.js';
import PushNotificationDevice from '../models/community/push_notification_device.js';
import PushNotificationSetting from '../models/community/push_notification_setting.js';
const INVALID_TOKEN_CODES = new Set([
'messaging/invalid-registration-token',
'messaging/registration-token-not-registered',
]);
const EVENT_CONFIG = {
chatMessage: { preference: 'chatEnabled', title: 'Neue Nachricht', body: 'Du hast eine neue Chat-Nachricht.', route: 'chat' },
friendloginchanged: { preference: 'friendLoginEnabled', title: 'Freund online', body: 'Ein Freund ist jetzt online.', route: 'social' },
familychanged: { preference: 'falukantEnabled', title: 'Falukant', body: 'Deine Familie hat Neuigkeiten.', route: 'falukant' },
falukantUpdateFamily: { preference: 'falukantEnabled', title: 'Falukant', body: 'Deine Familie wurde aktualisiert.', route: 'falukant' },
falukantUpdateStatus: { preference: 'falukantEnabled', title: 'Falukant', body: 'Es gibt einen neuen Falukant-Status.', route: 'falukant' },
vocabReminder: { preference: 'vocabReminderEnabled', title: 'Vokabeltraining', body: 'Deine Wiederholung wartet auf dich.', route: 'vocab' },
};
let messagingPromise;
async function getMessaging() {
if (messagingPromise !== undefined) return messagingPromise;
messagingPromise = (async () => {
const inlineCredentials = process.env.FIREBASE_SERVICE_ACCOUNT_JSON;
const credentialPath = process.env.FIREBASE_SERVICE_ACCOUNT_PATH;
if (!inlineCredentials && !credentialPath) return null;
try {
const { cert, getApps, initializeApp } = await import('firebase-admin/app');
const { getMessaging: getFirebaseMessaging } = await import('firebase-admin/messaging');
const serviceAccount = JSON.parse(inlineCredentials || fs.readFileSync(credentialPath, 'utf8'));
const app = getApps()[0] || initializeApp({ credential: cert(serviceAccount) });
return getFirebaseMessaging(app);
} catch (error) {
console.error('[push] Firebase Admin konnte nicht initialisiert werden:', error.message);
return null;
}
})();
return messagingPromise;
}
async function getUserByHashedId(hashedUserId) {
const user = await User.findOne({ where: { hashedId: hashedUserId } });
if (!user) throw new Error('usernotfound');
return user;
}
async function getSettings(userId) {
const [settings] = await PushNotificationSetting.findOrCreate({ where: { userId } });
return settings;
}
export async function registerDevice(hashedUserId, { token, enabled = true }) {
const user = await getUserByHashedId(hashedUserId);
const device = await PushNotificationDevice.findOne({ where: { token } });
if (device) {
await device.update({ userId: user.id, platform: 'android', enabled });
} else {
await PushNotificationDevice.create({ userId: user.id, token, platform: 'android', enabled });
}
return { registered: true };
}
export async function disableDevice(hashedUserId, token) {
const user = await getUserByHashedId(hashedUserId);
await PushNotificationDevice.update({ enabled: false }, { where: { userId: user.id, token } });
return { disabled: true };
}
export async function loadSettings(hashedUserId) {
const user = await getUserByHashedId(hashedUserId);
return (await getSettings(user.id)).toJSON();
}
export async function updateSettings(hashedUserId, values) {
const user = await getUserByHashedId(hashedUserId);
const settings = await getSettings(user.id);
await settings.update(values);
return settings.toJSON();
}
export async function dispatchRealtimeEvent(hashedUserId, event, data = {}) {
const config = EVENT_CONFIG[event];
if (!config) return;
const user = await getUserByHashedId(hashedUserId);
const settings = await getSettings(user.id);
if (!settings.enabled || !settings[config.preference]) return;
const messaging = await getMessaging();
if (!messaging) return;
const devices = await PushNotificationDevice.findAll({ where: { userId: user.id, enabled: true, platform: 'android' } });
await Promise.all(devices.map(async (device) => {
try {
await messaging.send({
token: device.token,
notification: { title: config.title, body: config.body },
data: { route: config.route, event, ...Object.fromEntries(Object.entries(data || {}).map(([key, value]) => [key, String(value)])) },
android: { priority: 'high', notification: { channelId: 'yourpart_updates' } },
});
} catch (error) {
if (INVALID_TOKEN_CODES.has(error.code)) {
await device.destroy();
} else {
console.error('[push] Zustellung fehlgeschlagen:', error.message);
}
}
}));
}