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
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
This commit is contained in:
108
backend/services/pushNotificationService.js
Normal file
108
backend/services/pushNotificationService.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user