From 5b6579423b78110de8ea6019a98745541304ba92 Mon Sep 17 00:00:00 2001 From: flfeders Date: Wed, 9 Sep 2026 12:55:26 +0200 Subject: [PATCH] feat: add employee push test button --- backend/src/routes/notifications.ts | 56 +++++++++++++++++++++++++- frontend/composables/useDesktopPush.js | 7 ++++ frontend/pages/staff/profiles/[id].vue | 55 +++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/backend/src/routes/notifications.ts b/backend/src/routes/notifications.ts index 2fe494c..aba9cbc 100644 --- a/backend/src/routes/notifications.ts +++ b/backend/src/routes/notifications.ts @@ -1,6 +1,6 @@ import { FastifyInstance } from "fastify" import { and, eq, isNull } from "drizzle-orm" -import { authUsers, notificationMobilePushDevices } from "../../db/schema" +import { authProfiles, authUsers, notificationMobilePushDevices } from "../../db/schema" import { NotificationService, UserDirectory } from "../modules/notification.service" import { pushServerClient } from "../modules/push-server.client" @@ -174,6 +174,60 @@ export default async function notificationsRoutes(server: FastifyInstance) { }) }) + server.post("/notifications/test-push/profile/:profileId", async (req, reply) => { + const tenantId = requireTenant(req.user.tenant_id) + const { profileId } = req.params as { profileId: string } + const [profile] = await server.db + .select({ + userId: authProfiles.user_id, + }) + .from(authProfiles) + .where(and( + eq(authProfiles.id, profileId), + eq(authProfiles.tenant_id, tenantId) + )) + .limit(1) + + if (!profile) { + return reply.code(404).send({ error: "Mitarbeiter nicht gefunden" }) + } + + if (!profile.userId) { + return reply.code(409).send({ + error: "Der Mitarbeiter ist noch nicht mit einem Benutzerkonto verknüpft", + }) + } + + const result = await svc.trigger({ + tenantId, + userId: profile.userId, + eventType: "system.test_push", + title: "FEDEO Push ist aktiv", + message: "Diese Testbenachrichtigung wurde einmalig über das Mitarbeiterprofil ausgelöst.", + payload: { + link: "/", + icon: "/favicon.ico", + }, + channels: ["push"], + }) + + if (result.created === 0) { + return reply.code(409).send({ + error: "Pushnachrichten sind für diesen Mitarbeiter deaktiviert", + result, + }) + } + + if (!result.success || result.delivered === 0) { + return reply.code(424).send({ + error: "Die Pushnachricht konnte an kein aktives Gerät zugestellt werden", + result, + }) + } + + return result + }) + server.post("/notifications/trigger", async (req, reply) => { try { const body = req.body as any diff --git a/frontend/composables/useDesktopPush.js b/frontend/composables/useDesktopPush.js index 4b62e77..d80775b 100644 --- a/frontend/composables/useDesktopPush.js +++ b/frontend/composables/useDesktopPush.js @@ -76,6 +76,12 @@ export const useDesktopPush = () => { }) } + const sendTestPushToProfile = async (profileId) => { + return await $api(`/api/notifications/test-push/profile/${encodeURIComponent(profileId)}`, { + method: "POST", + }) + } + return { supported, permission, @@ -85,5 +91,6 @@ export const useDesktopPush = () => { loadConfig, subscribe, sendTestPush, + sendTestPushToProfile, } } diff --git a/frontend/pages/staff/profiles/[id].vue b/frontend/pages/staff/profiles/[id].vue index b2c02bd..9ddaea1 100644 --- a/frontend/pages/staff/profiles/[id].vue +++ b/frontend/pages/staff/profiles/[id].vue @@ -4,6 +4,7 @@ const route = useRoute() const toast = useToast() const auth = useAuthStore() const admin = useAdmin() +const desktopPush = useDesktopPush() const { $api } = useNuxtApp() const runtimeConfig = useRuntimeConfig() @@ -17,6 +18,7 @@ const creatingLinkedUser = ref(false) const createLinkedUserModalOpen = ref(false) const createdLinkedUserPassword = ref("") const generatingCalendarSubscription = ref(false) +const sendingTestPush = ref(false) const createLinkedUserForm = reactive({ email: "", }) @@ -270,6 +272,30 @@ async function copyCalendarSubscriptionUrl(value: string, successTitle: string) } } +async function sendTestPush() { + if (!profile.value?.user_id || sendingTestPush.value) return + + sendingTestPush.value = true + + try { + await desktopPush.sendTestPushToProfile(profile.value.id) + toast.add({ + title: "Test-Push gesendet", + description: `Die Nachricht wurde einmalig an ${profile.value.full_name || "den Mitarbeiter"} gesendet.`, + color: "green" + }) + } catch (err: any) { + console.error("[sendTestPush]", err) + toast.add({ + title: "Test-Push fehlgeschlagen", + description: err?.data?.error || err?.message || "Die Testnachricht konnte nicht zugestellt werden.", + color: "red" + }) + } finally { + sendingTestPush.value = false + } +} + const weekdays = [ { key: '1', label: 'Montag' }, { key: '2', label: 'Dienstag' }, @@ -456,6 +482,35 @@ onMounted(async () => { description="Lege fest, unter welcher Nebenstelle dieser Benutzer erreichbar ist." /> + + + +
+
+

Push-Zustellung testen

+

+ + +

+
+ + + Test-Push senden + +
+
+