MongoDB-Ablage für Adressen, Anrufe und Jobs; Webhook-URLs je Datenart
Adress-, Anruf- und Jobdaten landen dauerhaft in der MongoDB (ArchiveService, best-effort auf eigenem Thread): Die Startseite meldet Adress-Cache und Anruf-Ereignisse an POST /api/addresses bzw. /api/calls, der Webhook legt seine Nutzlasten selbst ab. Adressen tragen eine generierte Kennung, die Rufnummer erkennt per Upsert die Dublette. Der Webhook bekommt je Datenart eine eigene URL – /api/webhook/kunden, /api/webhook/kuriere, /api/webhook/jobs –, die URL bestimmt die Ablage, ein Kennzeichen in der Nutzlast ist nicht mehr nötig. Die Herkunft steht als channel am Ereignis. Der generische POST /api/webhook bleibt. Die Controller-Tests ersetzen den ArchiveService (MockitoBean) und laufen damit ohne MongoDB. Markenrot laut Styleguide in beiden Farbmodi. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import type { Contact } from './swyx/protocol'
|
||||
|
||||
/**
|
||||
* Meldet den Adress-Cache der SwyxTray-App an das Backend, das die Einträge in
|
||||
* der MongoDB ablegt (Aktualisierung statt Verdopplung, siehe ArchiveService).
|
||||
*
|
||||
* Die WebSocket-Verbindung zur App hält nur der Browser; das Backend käme an
|
||||
* diese Adressdaten sonst nicht heran. Fire-and-forget wie bei den
|
||||
* Anrufdaten: Ist das Backend nicht erreichbar, läuft die Anzeige weiter.
|
||||
*/
|
||||
export function reportAddresses(contacts: Contact[]): void {
|
||||
if (contacts.length === 0) return
|
||||
void fetch('/api/addresses', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ addresses: contacts }),
|
||||
}).catch((e: unknown) => {
|
||||
console.warn('Adressdaten nicht an das Backend gemeldet:', e)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { CallEvent } from './swyx/protocol'
|
||||
|
||||
/**
|
||||
* Meldet Anruf-Ereignisse an das Backend, das sie in der MongoDB ablegt.
|
||||
*
|
||||
* Die Ereignisse entstehen nur hier im Browser (aus dem Vergleich zweier
|
||||
* SwyxTray-Snapshots); das Backend sieht die WebSocket-Verbindung nicht.
|
||||
* Fire-and-forget: Ist das Backend nicht erreichbar, läuft die Anzeige
|
||||
* unverändert weiter – die Speicherung ist Beiwerk, nicht Voraussetzung.
|
||||
*/
|
||||
export function reportCallEvents(events: CallEvent[]): void {
|
||||
if (events.length === 0) return
|
||||
void fetch('/api/calls', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(events),
|
||||
// Auch beim Schließen des Tabs noch absetzen, z. B. das "ended" beim Auflegen.
|
||||
keepalive: true,
|
||||
}).catch((e: unknown) => {
|
||||
console.warn('Anrufdaten nicht an das Backend gemeldet:', e)
|
||||
})
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { SwyxTrayClient, type ConnectionStatus, type RawMessage } from '../swyx/SwyxTrayClient'
|
||||
import { mergeSnapshot, type CallEvent, type Contact, type SnapshotMessage } from '../swyx/protocol'
|
||||
import { loadDirectory as loadDirectoryFrom, type SweepOptions } from '../swyx/directory'
|
||||
import { reportCallEvents } from '../calls'
|
||||
import { reportAddresses } from '../addresses'
|
||||
|
||||
export interface LogEntry extends RawMessage {
|
||||
id: number
|
||||
@@ -64,7 +66,11 @@ export function useSwyxTray() {
|
||||
}),
|
||||
client.on('error', (message) => setError(message)),
|
||||
// Unaufgefordert: beim Verbinden und wenn die App ihren Cache erneuert.
|
||||
client.on('addresses', (contacts) => setAddressCache(contacts)),
|
||||
client.on('addresses', (contacts) => {
|
||||
setAddressCache(contacts)
|
||||
// Dauerhaft in die MongoDB, über das Backend – die Anzeige wartet nicht darauf.
|
||||
reportAddresses(contacts)
|
||||
}),
|
||||
client.on('raw', (message) => {
|
||||
setLog((entries) => {
|
||||
const next = [...entries, { ...message, id: logIdRef.current++ }]
|
||||
@@ -89,6 +95,8 @@ export function useSwyxTray() {
|
||||
setLines(next)
|
||||
if (events.length > 0) {
|
||||
setHistory((entries) => [...[...events].reverse(), ...entries].slice(0, MAX_HISTORY))
|
||||
// Dauerhaft in die MongoDB, über das Backend – die Anzeige wartet nicht darauf.
|
||||
reportCallEvents(events)
|
||||
}
|
||||
}),
|
||||
]
|
||||
@@ -139,15 +147,19 @@ export function useSwyxTray() {
|
||||
*/
|
||||
const loadDirectory = useCallback(
|
||||
(options: SweepOptions = {}) =>
|
||||
run(() =>
|
||||
loadDirectoryFrom(
|
||||
run(async () => {
|
||||
const directory = await loadDirectoryFrom(
|
||||
{
|
||||
listAddresses: async () => (await client.listAddresses()).addresses ?? [],
|
||||
search: async (query) => (await client.searchContacts(query)).contacts ?? [],
|
||||
},
|
||||
options,
|
||||
),
|
||||
),
|
||||
)
|
||||
// Auch dieser Bestand dauerhaft in die MongoDB – der unaufgeforderte
|
||||
// Adress-Push deckt ältere App-Versionen ohne Cache nicht ab.
|
||||
reportAddresses(directory.contacts)
|
||||
return directory
|
||||
}),
|
||||
[client, run],
|
||||
)
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
--border-strong: #b4b4b5;
|
||||
--text: #222222;
|
||||
--text-muted: #7c7f7e;
|
||||
/* Vollflächen (Buttons, Balken) – immer das Markenrot mit weißer Schrift. */
|
||||
/* Markenrot laut Styleguide (RGB 202/13/56, Pantone 185 C) – in beiden
|
||||
Farbmodi identisch, das Brandbook kennt nur diesen einen Rotwert. */
|
||||
--brand: #ca0d38;
|
||||
--brand-strong: #a30a2d;
|
||||
--brand-soft: rgb(202 13 56 / 8%);
|
||||
/* Rot als Text- und Konturfarbe; im Dunkelmodus eine hellere Abstufung. */
|
||||
--accent: #ca0d38;
|
||||
--ok: #2f9e6e;
|
||||
--warn: #b98a1c;
|
||||
@@ -35,10 +35,7 @@
|
||||
--border-strong: #55565c;
|
||||
--text: #f1f1f2;
|
||||
--text-muted: #a4a7a8;
|
||||
--brand: #ca0d38;
|
||||
--brand-strong: #e33e60;
|
||||
--brand-soft: rgb(227 62 96 / 14%);
|
||||
--accent: #ef7d95;
|
||||
--brand-soft: rgb(202 13 56 / 14%);
|
||||
--ok: #43c78f;
|
||||
--warn: #e0b341;
|
||||
--err: #f2665b;
|
||||
|
||||
@@ -13,6 +13,11 @@ export interface WebhookEvent {
|
||||
id: number
|
||||
/** Eingangszeit im Backend, ISO-8601. */
|
||||
receivedAt: string
|
||||
/**
|
||||
* Über welche URL die Nachricht kam: `kunden`, `kuriere` oder `jobs`;
|
||||
* fehlt (bzw. `null`) beim generischen `POST /api/webhook`.
|
||||
*/
|
||||
channel?: string | null
|
||||
/** Das empfangene JSON, unverändert. Objekt, Liste oder ein einfacher Wert. */
|
||||
payload: unknown
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user