diff --git a/server/pruef-loeschen.mjs b/server/pruef-loeschen.mjs index 0daf8e04..c9706b0e 100644 --- a/server/pruef-loeschen.mjs +++ b/server/pruef-loeschen.mjs @@ -286,6 +286,60 @@ ok(/Nachricht zurückgenommen/.test(chatJs), "Gegenprobe: der Satz kommt in den Kommentaren durchaus vor —" + " die Suche oben unterscheidet also wirklich"); +/* ===================================================================== + 6. DIE UMSTELLUNG RAEUMT WIRKLICH AUF + + ENTSTANDEN AUS EINEM FEHLER, DER SCHON LIVE WAR (22.09.2026, 23:33). + + In der ersten Fassung stand `d.prepare("DELETE ...").changes` statt + `.run().changes` -- die Eigenschaft der vorbereiteten ANWEISUNG, + nicht die des Laufs. Die Anweisung wurde vorbereitet und nie + ausgefuehrt. + + NICHTS STUERZTE AB, NICHTS WAR ROT. Der einzige Hinweis war ein Wort + im Protokoll: "undefined zurueckgenommene Nachrichten endgueltig + entfernt". Haette dort niemand hingesehen, waeren die fuenf Zeilen + liegen geblieben -- und weil die Sicherung VOR dem Loeschen + geschrieben wird, waere bei jedem Neustart eine weitere + Sicherungsdatei entstanden. + + DESHALB WIRD HIER DIE ZAHL GEPRUEFT, nicht nur das Ergebnis. `0` + und `undefined` sehen in einer Meldung fast gleich aus; in einer + Bedingung nicht. + ===================================================================== */ +console.log(""); +console.log("--- Die Umstellung fuer alte Zeilen ---"); + +{ + const { loeschspurenAufraeumen } = await import("./workspace.js"); + const idAlt = await schreiben(keksModi, "Aus einer alten Fassung"); + /* Den alten Zustand von Hand herstellen: Text leer, weg_am gesetzt. + Genau so sahen die fuenf Zeilen in der echten Datenbank aus. */ + d.prepare("UPDATE chat_nachrichten SET text = '', weg_am = ? WHERE id = ?") + .run(new Date().toISOString(), idAlt); + const davor = d.prepare( + "SELECT COUNT(*) AS n FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").get().n; + ok(davor === 1, `eine alte zurueckgenommene Zeile steht da (${davor})`); + + /* OHNE PFAD: Dann schreibt die Funktion keine Sicherung -- hier wird + das Aufraeumen gemessen, nicht das Sichern. */ + const erg = loeschspurenAufraeumen(d, null); + ok(typeof erg.entfernt === "number", + `sie meldet eine ZAHL (${typeof erg.entfernt}: ${erg.entfernt})` + + " — genau hier stand vorher undefined"); + ok(erg.entfernt === 1, `und zwar die richtige (${erg.entfernt})`); + const danach = d.prepare( + "SELECT COUNT(*) AS n FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").get().n; + ok(danach === 0, `die Zeile ist wirklich weg (${danach} uebrig)`); + + /* WIEDERHOLBAR: Beim zweiten Mal gibt es nichts zu tun, und es wird + auch nichts gesichert. Ohne diese Zeile waere nicht bewiesen, dass + bei jedem Neustart nicht doch wieder etwas passiert. */ + const nochmal = loeschspurenAufraeumen(d, null); + ok(nochmal.entfernt === 0 && nochmal.gesichert === null, + `beim zweiten Mal passiert nichts (${nochmal.entfernt}, Sicherung ${nochmal.gesichert})`); +} + /* ---------- Ende ------------------------------------------------------ */ d.close(); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt manchmal */ } diff --git a/server/workspace.js b/server/workspace.js index c40ed239..7fa9bb3f 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -2191,6 +2191,85 @@ export function kanaeleFuer(person) { ? KANAELE : []; } +/* ===================================================================== + DIE ALTEN ZURUECKGENOMMENEN NACHRICHTEN GEHEN GANZ (22.09.2026) + ===================================================================== + + Filipe: "Geloeschte Nachrichten verschwinden vollstaendig -- keine + Spur, kein ‚wurde geloescht'-Hinweis, bei niemandem, auch nicht bei + DogFather." + + Seit dem 22.09.2026 loescht der Chat wirklich (DELETE). Was davor + zurueckgenommen wurde, steht aber noch als Zeile da: Text leer, + `weg_am` gesetzt. Die Anzeige zeigt sie nicht mehr -- in der + Datenbank waere sie trotzdem. "Keine Spur" gaelte dann fuer alles, + was ab heute passiert, und nicht fuer das, was ihn zu dem Satz + gebracht hat. + + ES GEHT NUR UM ZEILEN OHNE INHALT. `text = ''` ist die Bedingung, + nicht bloss `weg_am IS NOT NULL`: Der Text wurde beim Zuruecknehmen + geleert, also ist hier nichts zu verlieren. Stuende irgendwo doch + noch Text in einer zurueckgenommenen Zeile, bliebe sie liegen -- + lieber ein Rest, den jemand ansehen kann, als ein stiller Verlust. + + WIEDERHOLBAR: Beim zweiten Start gibt es nichts mehr zu tun, und es + passiert nichts. GESICHERT WIRD VORHER -- aber nur, wenn es wirklich + etwas zu tun gibt. Eine Sicherung bei jedem Start waere eine Datei + je Neustart und damit kein Schutz, sondern Muell. + + --------------------------------------------------------------------- + ALS EIGENE, EXPORTIERTE FUNKTION -- und das hat einen Grund, der an + genau dieser Stelle entstanden ist. + + In der ersten Fassung stand `d.prepare("DELETE ...").changes` statt + `.run().changes`: die Eigenschaft der vorbereiteten ANWEISUNG, nicht + die des Laufs. Die Anweisung wurde vorbereitet und nie ausgefuehrt. + Nichts stuerzte ab, nichts war rot. Im Protokoll stand "undefined + zurueckgenommene Nachrichten endgueltig entfernt" -- ein Zaehler, + der `undefined` sagt, hat nicht gezaehlt, und was nicht zaehlt, hat + meist auch nicht gearbeitet. Die fuenf Zeilen blieben liegen, und + weil die Sicherung VOR dem Loeschen geschrieben wird, waere bei + jedem Neustart eine weitere Sicherungsdatei entstanden. + + Eine Umstellung, die tief im Hochfahren steckt, kann keine Pruefung + aufrufen. Eine Funktion schon: pruef-loeschen.mjs gibt ihr eine + vorbereitete Datenbank und sieht nach, was sie WIRKLICH getan hat. + + @returns {{entfernt:number, gesichert:string|null}} + ===================================================================== */ +export function loeschspurenAufraeumen(d, dbPfad = DB_PFAD) { + try { + const spalten = d.prepare("PRAGMA table_info(chat_nachrichten)").all().map((x) => x.name); + if (!spalten.includes("weg_am")) return { entfernt: 0, gesichert: null }; + const offen = d.prepare( + "SELECT COUNT(*) AS n FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").get().n; + if (offen === 0) return { entfernt: 0, gesichert: null }; + + let sicherung = null; + if (dbPfad) { + const stempel = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14); + sicherung = `${dbPfad}.vor-loeschspuren-${stempel}`; + d.exec(`VACUUM INTO '${sicherung.replace(/'/g, "''")}'`); + console.log("[workspace] Sicherung vor dem Aufraeumen:", sicherung); + } + /* Reaktionen und Erwaehnungen haengen mit ON DELETE CASCADE daran + und gehen von selbst mit -- `PRAGMA foreign_keys = ON` steht + beim Oeffnen. */ + const entfernt = d.prepare( + "DELETE FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").run().changes; + /* Und die Raeume rechnen ihren letzten Zeitpunkt neu -- sonst + stuende einer oben in der Liste, zu dessen Zeitpunkt es nichts + mehr gibt. */ + d.exec(`UPDATE chat_raeume SET letzte_am = + (SELECT MAX(erstellt) FROM chat_nachrichten WHERE raum_id = chat_raeume.id)`); + console.log(`[workspace] ${entfernt} zurueckgenommene Nachrichten endgueltig entfernt.`); + return { entfernt, gesichert: sicherung }; + } catch (fehler) { + console.error("[workspace] Aufraeumen der Loeschspuren:", fehler?.message); + return { entfernt: 0, gesichert: null }; + } +} + export function kategorienFuer(person) { if (!person) return []; return TEAM_DOGI_ROLLEN.has(person.rolle) || person.rolle === "admin" @@ -3459,61 +3538,7 @@ function umstellungen(d) { } } - /* ===================================================================== - DIE ALTEN ZURUECKGENOMMENEN NACHRICHTEN GEHEN GANZ (22.09.2026) - ===================================================================== - - Filipe: „Geloeschte Nachrichten verschwinden vollstaendig -- keine - Spur, kein ‚wurde geloescht'-Hinweis, bei niemandem, auch nicht - bei DogFather." - - Seit heute loescht der Chat wirklich (DELETE). Was davor - zurueckgenommen wurde, steht aber noch als Zeile da: Text leer, - `weg_am` gesetzt. Die Anzeige zeigt sie nicht mehr -- in der - Datenbank waere sie trotzdem. „Keine Spur" gilt dann fuer alles, - was ab heute passiert, und nicht fuer das, was ihn zu dem Satz - gebracht hat. - - ES GEHT NUR UM ZEILEN OHNE INHALT. `text = ''` ist die Bedingung, - nicht bloss `weg_am IS NOT NULL`: Der Text wurde beim Zuruecknehmen - geleert, also ist hier nichts zu verlieren. Stuende irgendwo doch - noch Text in einer zurueckgenommenen Zeile, bliebe sie liegen -- - lieber ein Rest, den jemand ansehen kann, als ein stiller Verlust. - - WIEDERHOLBAR: Beim zweiten Start gibt es nichts mehr zu tun, und - es passiert nichts. Deshalb steht es hier bei den Umstellungen - und nicht in einem einmaligen Skript, das jemand vergessen - koennte. - - GESICHERT WIRD VORHER -- aber nur, wenn es wirklich etwas zu tun - gibt. Eine Sicherung bei jedem Start waere eine Datei je Neustart - und damit kein Schutz, sondern Muell. */ - try { - const spalten = d.prepare("PRAGMA table_info(chat_nachrichten)").all().map((x) => x.name); - if (spalten.includes("weg_am")) { - const offen = d.prepare( - "SELECT COUNT(*) AS n FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").get().n; - if (offen > 0) { - const stempel = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14); - const sicherung = `${DB_PFAD}.vor-loeschspuren-${stempel}`; - d.exec(`VACUUM INTO '${sicherung.replace(/'/g, "''")}'`); - console.log("[workspace] Sicherung vor dem Aufraeumen:", sicherung); - /* Reaktionen und Erwaehnungen haengen mit ON DELETE CASCADE - daran und gehen von selbst mit -- `PRAGMA foreign_keys = ON` - steht beim Oeffnen. Nachgesehen, nicht angenommen. */ - const weg = d.prepare( - "DELETE FROM chat_nachrichten WHERE weg_am IS NOT NULL AND text = ''").changes; - /* Und die Raeume rechnen ihren letzten Zeitpunkt neu -- sonst - stuende einer oben in der Liste, zu dessen Zeitpunkt es - nichts mehr gibt. */ - d.exec(`UPDATE chat_raeume SET letzte_am = - (SELECT MAX(erstellt) FROM chat_nachrichten WHERE raum_id = chat_raeume.id)`); - console.log(`[workspace] ${weg} zurueckgenommene Nachrichten endgueltig entfernt.`); - } - } - } catch (fehler) { - console.error("[workspace] Aufraeumen der Loeschspuren:", fehler?.message); - } + loeschspurenAufraeumen(d); /* ===================================================================== DIE BLAUEN HERZEN WERDEN BABYBLAU (22.09.2026)