B4 + B5: Loeschen heisst Loeschen, die Schrift wechselt mit, und Blau wird Babyblau
Die beiden Auftraege gehoeren zusammen, und zwar in dieser Reihenfolge:
Eine babyblaue Kachel ist HELL. Ohne mitwechselnde Schrift waere sie
unlesbar (1,63:1 gemessen). Erst B4 macht B5 moeglich.
B4a -- LOESCHEN HEISST LOESCHEN
"Geloeschte Nachrichten verschwinden vollstaendig - keine Spur, kein
'wurde geloescht'-Hinweis, bei niemandem, auch nicht bei DogFather."
Bis heute blieb die Zeile stehen: Text geleert, weg_am gesetzt, und im
Chat stand "Nachricht zurueckgenommen". Das war ausdruecklich so
begruendet ("ein Loch im Verlauf wirft mehr Fragen auf"). Das Argument
beantwortet aber eine andere Frage: Ein Hinweis "hier stand etwas"
MARKIERT die Stelle. Wer etwas aus Versehen schreibt, will es weg
haben und nicht unterstrichen.
Jetzt ein echtes DELETE. Vier Raender, an denen eine Spur bleiben
koennte, alle gemessen:
1. die Zeile selbst
2. chat_reaktionen (ON DELETE CASCADE - nachgesehen, nicht angenommen)
3. chat_erwaehnungen (ebenso)
4. letzte_am am Raum - wird neu gerechnet, sonst stuende er oben in
der Liste mit einem Zeitpunkt, zu dem es
nichts mehr gibt
Dazu: ein Zitat auf eine geloeschte Nachricht wird weggelassen; in der
Gespraechsliste steht kein Hinweis mehr; der Knopf heisst "loeschen".
Die 5 alten zurueckgenommenen Zeilen (Text bereits leer) raeumt eine
einmalige, wiederholbare Umstellung ab - mit Sicherung davor, und nur
wenn es wirklich etwas zu tun gibt.
B4b -- WER DARF WAS
"jeder nur seine eigenen - ausser DogFather und rechte Hand"
`darfJedeNachrichtLoeschen` = admin oder hand. NICHT fuehrtTeamDogi
(das schloesse die linke Hand ein) und nicht istLeitung (das schloesse
Spicy Media ein, die private Chats nicht einmal sehen darf). Das Recht
kommt vom Server ins Skript, nicht aus einer Rolle im Browser: chat.js
bekommt jeder, der die Seite oeffnet.
B4c -- DIE SCHRIFT WECHSELT MIT
"am besten schwarz auf hellen Kacheln - und weiss, wenn jemand eine
schwarze Kachel waehlt"
`schriftFuer(farbe)` waehlt zwischen zwei Paaren, gerechnet aus der
Leuchtdichte, mit denselben Schwellen wie das Rechenwerkzeug (7:1 fuer
den Text, 4,5:1 fuer die Fusszeile). Keine dritte Spalte in
CHAT_KACHELN, die jemand pflegen muesste. Flaeche und Schrift werden
im Browser in EINEM Griff gesetzt (blaseFaerben) - zwei Stellen waeren
irgendwann eine helle Kachel mit heller Schrift.
B4d -- DIE NAMEN
Der Name ist jetzt ein eigener Streifen mit Kante darunter, .84rem,
und der Rollenpunkt wird ein 3x14-Balken. Vorher stand er als erste
ZEILE in der Blase und las sich wie der Anfang des Satzes.
B5 -- BABYBLAU
"jedes normales blaues herz durch babyblaues herz ersetzen ... jeder
normale blaue farbe, sei es die kachel im chat oder emojis, nur
babyblau bitte."
* Herz: U+1F499 -> U+1FA75. Nicht geglaubt, sondern gemessen: 43,9 px
breit wie die anderen Herzen, ein Ersatzkaestchen waere 20,7. Die
vier vorhandenen blauen Herzen in der Datenbank wandern mit - sonst
waeren es vier Reaktionen, die ERLAUBT nicht mehr kennt: still weg.
* Kachel: neue, HELLE Kachel "Babyblau" mit dem Wert von --akzent.
Gemessen: Text 10,78:1, Fusszeile 4,87:1. Sie steht bewusst nicht in
der Rechnung des Werkzeugs (das rechnet elf Toene auf EINE
Leuchtdichte) - sie hat eine andere Leuchtdichte und dafuer ihre
eigene Schrift. Dasselbe Versprechen, anderer Weg.
PRUEFUNGEN
pruef-loeschen.mjs (NEU, 20/0): alle vier Raender, die vier Faelle
der Rechtegrenze (auch: die linke Hand darf NICHT), das babyblaue
Herz am laufenden Server, und dass der Satz "Nachricht
zurueckgenommen" nur noch in Kommentaren steht - mit Gegenprobe,
dass die Suche diesen Unterschied wirklich macht.
pruef-chatkachel.mjs (33/0): misst jede Kachel mit IHRER Schrift und
fragt dafuer dieselbe Funktion wie der Server. Neu: dass die
Schrift ueberhaupt wechselt. Die Leuchtdichte-Regel gilt jetzt fuer
die gerechneten Toene - das Versprechen dahinter loest die
Kontrastzeile darueber direkt ein.
Vier alte Pruefungen umgedreht, die den alten Zustand festgeschrieben
hatten: pruef-chat, pruef-chat-ausbau, pruef-chat-aufloesen,
pruef-chat-optik. Alle mit scharferer Messung als vorher (Zahl
davor/danach statt "ein Hinweis ist da").
Mitgelaufen und gruen: pruef-alle-sehen-es, pruef-treffchat (110/0).
Gesichert: workspace-vor-loeschumbau-20260922-233313.db
(946 KB, integrity_check ok, 111 Nachrichten, 49 Reaktionen).
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+188
-34
@@ -235,6 +235,30 @@ function bildWegFuer(personId) {
|
||||
===================================================================== */
|
||||
export const CHAT_KACHELN = [
|
||||
{ wert: "grund", name: "Neutral", farbe: "#4f545c" },
|
||||
/* BABYBLAU -- die einzige helle Kachel (22.09.2026).
|
||||
------------------------------------------------------------------
|
||||
Filipe: „jeder normale blaue farbe, sei es die kachel im chat oder
|
||||
emojis, nur babyblau bitte." Und im selben Auftrag: „Textfarbe
|
||||
soll mitwechseln, am besten schwarz auf hellen Kacheln -- und
|
||||
weiss, wenn jemand eine schwarze Kachel waehlt."
|
||||
|
||||
Die zwei Saetze gehoeren zusammen, und in dieser Reihenfolge.
|
||||
Babyblau ist HELL (Leuchtdichte 0,547); die helle Schrift der
|
||||
Blase kaeme darauf auf 1,63:1 -- unlesbar. Erst weil die Schrift
|
||||
jetzt mitwechselt (schriftFuer weiter unten), ist diese Kachel
|
||||
ueberhaupt moeglich.
|
||||
|
||||
Sie steht deshalb NICHT in der Rechnung von
|
||||
tools/chat-kacheln-rechnen.mjs: Das Werkzeug rechnet elf bunte
|
||||
Toene auf EINE gemeinsame Leuchtdichte, damit niemand sich durch
|
||||
seine Wahl schlechter lesbar machen kann. Diese hier hat eine
|
||||
andere Leuchtdichte und dafuer ihre eigene Schrift -- gemessen:
|
||||
Text 10,78:1 (noetig 7), Fusszeile 4,87:1 (noetig 4,5). Dasselbe
|
||||
Versprechen, auf anderem Weg eingeloest.
|
||||
|
||||
Der Wert ist exakt `--akzent` des Hauses. Eine zweite Zahl daneben
|
||||
waere zwei Babyblau, und beim naechsten Mal drei. */
|
||||
{ wert: "babyblau", name: "Babyblau", farbe: "#8ec9ff" },
|
||||
{ wert: "tinte", name: "Tinte", farbe: "#0150af" },
|
||||
{ wert: "veilchen", name: "Veilchen", farbe: "#6a01e5" },
|
||||
{ wert: "pflaume", name: "Pflaume", farbe: "#9301a2" },
|
||||
@@ -248,6 +272,71 @@ export const CHAT_KACHELN = [
|
||||
{ wert: "ozean", name: "Ozean", farbe: "#045b74" },
|
||||
];
|
||||
|
||||
/* =====================================================================
|
||||
WELCHE SCHRIFT AUF WELCHER KACHEL (22.09.2026)
|
||||
=====================================================================
|
||||
|
||||
Filipe: „Textfarbe soll mitwechseln, am besten schwarz auf hellen
|
||||
Kacheln -- und weiss, wenn jemand eine schwarze Kachel waehlt."
|
||||
|
||||
GERECHNET, NICHT JE KACHEL EINGETRAGEN. Eine dritte Spalte in
|
||||
CHAT_KACHELN waere eine Liste, die jemand pflegen muss -- und beim
|
||||
naechsten Ton vergisst. Hier steht stattdessen die Regel, und die
|
||||
Farbe folgt aus der Flaeche.
|
||||
|
||||
ZWEI PAARE, NICHT BELIEBIGE FARBEN: Ein frei gerechneter Grauwert je
|
||||
Kachel ergaebe zwoelf verschiedene Schriften, und der Chat saehe aus
|
||||
wie zwoelf verschiedene Programme. Es gibt ein helles Paar (das
|
||||
bisherige, aus chat.css) und ein dunkles; genommen wird das, das die
|
||||
noetigen Kontraste schafft.
|
||||
|
||||
DIE SCHWELLEN SIND DIESELBEN WIE IM RECHENWERKZEUG: 7:1 (AAA) fuer
|
||||
den Text -- der Chat ist die Seite, auf der am laengsten gelesen
|
||||
wird -- und 4,5:1 fuer die Fusszeile. Sie stehen hier einmal und
|
||||
werden von pruef-chatkachel.mjs aus DIESER Datei gelesen; zwei
|
||||
Zahlen in zwei Dateien waeren der Widerspruch, den niemand bemerkt.
|
||||
===================================================================== */
|
||||
export const SCHRIFT_NOETIG = { text: 7, leise: 4.5 };
|
||||
|
||||
/** Das helle Paar -- dieselben Werte wie `--blase-text` / `--blase-leise`
|
||||
* in chat.css. Sie stehen dort als Rueckfall fuer den Fall, dass gar
|
||||
* keine Kachel gesetzt ist. */
|
||||
export const SCHRIFT_HELL = { text: "#f3f7fc", leise: "#c4d2e2" };
|
||||
/** Das dunkle Paar fuer helle Kacheln. Gemessen auf Babyblau:
|
||||
* 10,78:1 und 4,87:1. */
|
||||
export const SCHRIFT_DUNKEL = { text: "#0b1119", leise: "#3b4e63" };
|
||||
|
||||
const alsLinear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
|
||||
function leuchtdichte(hex) {
|
||||
const z = String(hex).replace("#", "");
|
||||
const [r, g, b] = [0, 2, 4]
|
||||
.map((i) => parseInt(z.slice(i, i + 2), 16) / 255).map(alsLinear);
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
}
|
||||
function kontrastZwischen(a, b) {
|
||||
const x = leuchtdichte(a), y = leuchtdichte(b);
|
||||
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
|
||||
}
|
||||
|
||||
/** Welches Schriftpaar traegt auf dieser Flaeche?
|
||||
*
|
||||
* GIBT IMMER EINES ZURUECK. Ein dritter Ausgang ("keines passt")
|
||||
* waere hier falsch: Der Chat muss etwas anzeigen. Dass beide Paare
|
||||
* reichen, ist die Aufgabe von pruef-chatkachel.mjs -- sie misst
|
||||
* jeden Ton der Liste gegen genau diese Funktion. Faellt einer durch,
|
||||
* ist das ein Befund und keine Notlage zur Laufzeit. */
|
||||
export function schriftFuer(farbe) {
|
||||
const passt = (paar) =>
|
||||
kontrastZwischen(paar.text, farbe) >= SCHRIFT_NOETIG.text
|
||||
&& kontrastZwischen(paar.leise, farbe) >= SCHRIFT_NOETIG.leise;
|
||||
if (passt(SCHRIFT_HELL)) return SCHRIFT_HELL;
|
||||
if (passt(SCHRIFT_DUNKEL)) return SCHRIFT_DUNKEL;
|
||||
/* Schafft keines beide Schwellen, entscheidet der TEXT -- er ist
|
||||
das, was man liest. */
|
||||
return kontrastZwischen(SCHRIFT_HELL.text, farbe)
|
||||
>= kontrastZwischen(SCHRIFT_DUNKEL.text, farbe) ? SCHRIFT_HELL : SCHRIFT_DUNKEL;
|
||||
}
|
||||
|
||||
/** Nur ein Ton aus der Liste kommt durch -- sonst nichts.
|
||||
*
|
||||
* DIE PRUEFUNG STEHT HIER UND NICHT NUR IM FORMULAR. Was aus der
|
||||
@@ -727,7 +816,10 @@ chatRouter.get("/workspace/api/chat/partner", (req, res) => {
|
||||
chatRouter.get("/workspace/api/chat/kacheln", (req, res) => {
|
||||
try {
|
||||
res.json({
|
||||
kacheln: CHAT_KACHELN,
|
||||
/* DIE SCHRIFT GEHT MIT. Der Browser soll sie nicht selbst
|
||||
rechnen -- dann staende die Regel zweimal da, und beim
|
||||
naechsten Ton waere eine der beiden Fassungen aelter. */
|
||||
kacheln: CHAT_KACHELN.map((k) => ({ ...k, schrift: schriftFuer(k.farbe) })),
|
||||
/* Die eigene WAHL, nicht der getragene Ton -- sonst stuende im
|
||||
Waehler ein Haken bei einer Farbe, die niemand gewaehlt hat. */
|
||||
meine: gewaehlteKachelFuer(req.person.id),
|
||||
@@ -1270,6 +1362,12 @@ chatRouter.get("/workspace/api/chat/raeume/:id/nachrichten", (req, res) => {
|
||||
assets/js/chat.js bekommt jeder, der die Seite oeffnet. Ein
|
||||
Ja/Nein verraet nichts ueber die Rollen dahinter. */
|
||||
darf_anheften: darfAnheften(req.person, raumId),
|
||||
/* WER JEDE NACHRICHT LOESCHEN DARF -- vom Server, nicht aus
|
||||
der Rolle im Browser geraten. assets/js/chat.js bekommt
|
||||
JEDER, der die Seite oeffnet; ein `rolle === 'hand'` darin
|
||||
waere eine Auskunft ueber das Haus an jeden Leser. Und es
|
||||
waere eine zweite Wahrheit neben der Schranke im Server. */
|
||||
darf_alles_loeschen: darfJedeNachrichtLoeschen(req.person),
|
||||
angeheftet: angeheftet.map((a) => ({
|
||||
id: a.id,
|
||||
von: a.von || "Gelöscht",
|
||||
@@ -2239,60 +2337,116 @@ chatRouter.put("/workspace/api/chat/nachrichten/:id/anheften", gleicheHerkunft,
|
||||
}
|
||||
});
|
||||
|
||||
/** Eine eigene Nachricht zurücknehmen. */
|
||||
/* =====================================================================
|
||||
LÖSCHEN HEISST LÖSCHEN (22.09.2026)
|
||||
=====================================================================
|
||||
|
||||
Filipe: „Gelöschte Nachrichten verschwinden vollständig – keine
|
||||
Spur, kein ‚wurde gelöscht'-Hinweis, bei niemandem, auch nicht bei
|
||||
DogFather." Und: „Löschen darf weiterhin jeder nur seine eigenen –
|
||||
außer DogFather und rechte Hand: die dürfen jede Nachricht löschen,
|
||||
für den Notfall."
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WAS SICH DAMIT UMDREHT
|
||||
|
||||
Hier stand bis heute das Gegenteil, und zwar begründet: „Ein Loch im
|
||||
Verlauf wirft mehr Fragen auf als der Hinweis, dass hier etwas
|
||||
zurückgenommen wurde." Die Zeile blieb, der Text wurde geleert, und
|
||||
der Chat zeigte „Nachricht zurückgenommen".
|
||||
|
||||
Das Argument war nicht falsch -- es beantwortet nur eine andere
|
||||
Frage als die, die Filipe stellt. Ein Hinweis „hier stand etwas"
|
||||
ist für den, der gelöscht hat, oft schlimmer als der Satz selbst:
|
||||
Er markiert die Stelle. Wer etwas aus Versehen schreibt, will es
|
||||
weg haben und nicht unterstrichen.
|
||||
|
||||
DESHALB JETZT EIN ECHTES DELETE. Das geht sauber, weil die beiden
|
||||
abhängigen Tabellen es mittragen:
|
||||
|
||||
chat_reaktionen ... REFERENCES chat_nachrichten(id) ON DELETE CASCADE
|
||||
chat_erwaehnungen ... REFERENCES chat_nachrichten(id) ON DELETE CASCADE
|
||||
|
||||
und `PRAGMA foreign_keys = ON` beim Öffnen steht. Nachgesehen, nicht
|
||||
angenommen.
|
||||
|
||||
DREI DINGE, DIE SONST EINE SPUR HINTERLIESSEN:
|
||||
|
||||
1. `antwort_auf` zeigt weiter auf die Nummer. Der Verlauf holt das
|
||||
Zitat über einen LEFT JOIN -- es kommt nichts mehr zurück, und
|
||||
die Anzeige lässt das Zitat dann weg. Ein Zitat auf etwas, das
|
||||
es nicht gibt, wäre die Spur.
|
||||
2. `letzte_am` am Raum. War die gelöschte die letzte Nachricht,
|
||||
stünde die Gesprächsliste weiter auf ihrem Zeitpunkt -- ein
|
||||
Raum, der ganz oben steht und nichts Neues hat. Wird neu
|
||||
gerechnet.
|
||||
3. Der Anhang auf der Platte. Das war schon vorher richtig gelöst
|
||||
und bleibt so: erst die Zeile, dann die Datei.
|
||||
|
||||
WAS BLEIBT: die Protokollzeile. Sie nennt WER WESSEN Nachricht
|
||||
gelöscht hat -- keinen Text, keinen Inhalt. Beim Notfall-Löschen
|
||||
durch DogFather oder die rechte Hand ist das der Unterschied
|
||||
zwischen einer Befugnis und einer unsichtbaren Macht. Das Protokoll
|
||||
ist kein Chatverlauf; es steht an einer anderen Stelle und
|
||||
beantwortet eine andere Frage.
|
||||
===================================================================== */
|
||||
|
||||
/** Wer JEDE Nachricht löschen darf -- für den Notfall.
|
||||
*
|
||||
* NICHT `fuehrtTeamDogi`: Das schlösse die linke Hand ein, und Filipe
|
||||
* hat ausdrücklich zwei genannt. Nicht `istLeitung`: Das schlösse
|
||||
* Spicy Media ein -- die private Chats ausdrücklich nicht einmal
|
||||
* sehen darf. Zwei Rollen, wörtlich die zwei aus dem Auftrag. */
|
||||
const darfJedeNachrichtLoeschen = (person) =>
|
||||
istDogFather(person) || person?.rolle === "hand";
|
||||
|
||||
chatRouter.delete("/workspace/api/chat/nachrichten/:id", gleicheHerkunft, (req, res) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
if (!Number.isInteger(id)) return res.status(400).json({ fehler: "ungueltig" });
|
||||
|
||||
const n = db().prepare(
|
||||
const d = db();
|
||||
const n = d.prepare(
|
||||
"SELECT id, raum_id, person_id, anhang_datei FROM chat_nachrichten WHERE id = ?").get(id);
|
||||
if (!n || !istDrin(n.raum_id, req.person)) {
|
||||
return res.status(404).json({ fehler: "nicht_gefunden" });
|
||||
}
|
||||
/* NUR EIGENE. DogFather ebenfalls nicht: Fremde Worte aus einem
|
||||
Gespräch zu nehmen, in dem Absprachen stehen, wäre ein Eingriff
|
||||
in die Geschichte -- und niemand könnte ihm nachher glauben. */
|
||||
if (n.person_id !== req.person.id) {
|
||||
const fremd = n.person_id !== req.person.id;
|
||||
if (fremd && !darfJedeNachrichtLoeschen(req.person)) {
|
||||
return res.status(403).json({ fehler: "Nur eigene Nachrichten." });
|
||||
}
|
||||
|
||||
/* EIN ANHANG WIRD WIRKLICH GELÖSCHT, nicht nur ausgeblendet.
|
||||
|
||||
Beim Text reicht das Leeren -- er steht nur in der Zeile. Eine
|
||||
Datei liegt daneben auf der Platte und hätte ihre eigene Adresse
|
||||
behalten. "Zurückgenommen" hiesse dann: im Verlauf weg, über den
|
||||
Link weiter da. Das wäre keine Rücknahme, sondern der Anschein
|
||||
einer. Erst die Zeile leeren, dann die Datei -- scheitert das
|
||||
Entfernen (Datei schon weg), ist der Verweis trotzdem fort. */
|
||||
const dateiWeg = n.anhang_datei;
|
||||
/* UND SIE FAELLT VOM AUSHANG (10.09.2026). Ohne diese zwei
|
||||
Spalten stuende die Nachricht weiter oben angeheftet -- mit dem
|
||||
Text "Nachricht zurueckgenommen", an der auffaelligsten Stelle
|
||||
des Raums. Die Anzeige filtert zwar auf `weg_am IS NULL`, aber
|
||||
der Platz waere belegt: PIN_MAX zaehlt mit, und niemand kaeme
|
||||
darauf, warum sich nichts mehr anheften laesst. */
|
||||
db().prepare(`UPDATE chat_nachrichten
|
||||
SET text = '', weg_am = ?,
|
||||
angeheftet_am = NULL, angeheftet_von = NULL,
|
||||
anhang_datei = NULL, anhang_name = NULL, anhang_art = NULL,
|
||||
anhang_typ = NULL, anhang_groesse = NULL,
|
||||
anhang_breite = NULL, anhang_hoehe = NULL
|
||||
WHERE id = ?`).run(jetzt(), id);
|
||||
/* ERST DIE ZEILE, DANN DIE DATEI. Scheitert das Entfernen (Datei
|
||||
schon weg), ist der Verweis trotzdem fort -- andersherum bliebe
|
||||
im schlechten Fall eine Zeile ohne Datei stehen. */
|
||||
d.prepare("DELETE FROM chat_nachrichten WHERE id = ?").run(id);
|
||||
if (dateiWeg) {
|
||||
try { unlinkSync(join(ANHANG_ORDNER, dateiWeg)); }
|
||||
catch (f) { if (f?.code !== "ENOENT") console.error("[chat] Anhang bleibt liegen:", f?.message); }
|
||||
}
|
||||
const empfaenger = teilnehmerVon(n.raum_id).map((t) => t.id);
|
||||
chatEreignis(n.raum_id, { id, raum_id: n.raum_id, zurueckgenommen: true }, [], "weg");
|
||||
/* DER RAUM RECHNET SEINEN LETZTEN ZEITPUNKT NEU. Ohne das stuende
|
||||
er in der Liste weiterhin ganz oben, mit einem Zeitpunkt, zu dem
|
||||
es nichts mehr gibt. */
|
||||
d.prepare(`UPDATE chat_raeume
|
||||
SET letzte_am = (SELECT MAX(erstellt) FROM chat_nachrichten WHERE raum_id = ?)
|
||||
WHERE id = ?`).run(n.raum_id, n.raum_id);
|
||||
|
||||
protokolliere("chat_zurueckgenommen", {
|
||||
const empfaenger = teilnehmerVon(n.raum_id).map((t) => t.id);
|
||||
/* `geloescht: true` STATT `zurueckgenommen`. Das Wort entscheidet,
|
||||
was der Browser tut: Das alte hiess "zeig einen Hinweis", das
|
||||
neue heisst "nimm es aus dem Bild". Beides gleichzeitig zu
|
||||
schicken waere die Fassung, bei der eine alte Seite den Hinweis
|
||||
weiter anzeigt. */
|
||||
chatEreignis(n.raum_id, { id, raum_id: n.raum_id, geloescht: true }, [], "weg");
|
||||
|
||||
protokolliere("chat_geloescht", {
|
||||
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
|
||||
detail: `#${id}`,
|
||||
detail: fremd ? `#${id} (fremde Nachricht von #${n.person_id})` : `#${id}`,
|
||||
});
|
||||
res.json({ ok: true, empfaenger: empfaenger.length });
|
||||
res.json({ ok: true, geloescht: true, empfaenger: empfaenger.length });
|
||||
} catch (fehler) {
|
||||
console.error("[chat] Zurücknehmen:", fehler?.message);
|
||||
console.error("[chat] Löschen:", fehler?.message);
|
||||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user