Personen loeschen -- mit Vorschau und einer Sicherung unmittelbar davor
Wunsch Filipe: "ich will auch die moeglichkeit haben die loeschen zu
koennen."
Das ist die einzige Handlung im ganzen Workspace, die sich nicht
rueckgaengig machen laesst. Vier Vorkehrungen:
1. NUR DOGFATHER. Nicht die Leitung, nicht ein Manager -- "nur DogFather
hat alle endgueltigen Rechte" heisst genau hier etwas. Ein Manager
kann weiterhin sperren; das reicht fuer den Alltag und ist umkehrbar.
Alle anderen bekommen 404, auch fuer die Vorschau: Die verraet, wie
viel an einer Person haengt.
2. VORSCHAU. Vor dem Klick steht da, was MITGEHT (Profil, Start-Check,
Content-Saeulen, Sitzungen, Zustaendigkeit) und was BLEIBT und nur
seine Zuordnung verliert (Aufgaben, Termine, Bereichseintraege,
Dateien, Leads). Eine Aufgabe verschwinden zu lassen, weil jemand
geht, waere Geschichtsfaelschung.
Dazu die gefaehrlichste Einzelwarnung: Wer einen Scout loescht, nimmt
seinen Creators die zustaendige Person weg. Die Vorschau nennt sie
beim Namen.
3. EINE SICHERUNG DIREKT DAVOR -- und wenn sie scheitert, wird NICHT
geloescht. Damit ist "geloescht" wiederherstellbar. Das ist der
eigentliche Gewinn aus der Sicherungsarbeit von heute Nachmittag.
4. Der Name muss getippt werden. Nicht als Schikane: Der Loeschknopf
sitzt neben dem Sperrknopf, und die beiden sind sehr verschieden. Wer
den Namen tippt, hat die Zeile gelesen, die er trifft.
ZWEI FEHLER, die erst die Pruefung sichtbar gemacht hat:
a) Alle Loeschungen schrieben in DIESELBE Tagessicherung. Nach der
dritten kannte sie die erste geloeschte Person nicht mehr -- die
Sicherung haette genau in dem Fall versagt, fuer den sie da ist.
Loeschungen bekommen jetzt eine eigene Datei ("vorher-…") mit
Zeitstempel, die nie ueberschrieben wird. Aufgefallen nur, weil die
Pruefung die ANZAHL der Dateien zaehlt und nicht bloss, ob eine da ist.
b) Der Zeitstempel hatte Sekundenaufloesung -- drei Loeschungen in
derselben Sekunde ergaben wieder eine einzige Datei. Jetzt mit
Millisekunden, plus Zaehler als Notloesung. Und: Eine Sicherung vor
dem Loeschen setzt NICHT den Vermerk fuer den planmaessigen Lauf,
sonst faellt die naechtliche Sicherung aus.
Dabei auch ein Fehler in meiner eigenen Pruefung gefunden: Sie griff die
alphabetisch erste Datei und nannte sie "die aelteste" -- "vorher-…-2.db"
sortiert aber VOR "vorher-….db", weil "-" kleiner ist als ".". Sie prueft
jetzt die Eigenschaft selbst: JEDE geloeschte Person muss sich aus
irgendeiner Sicherung zurueckholen lassen.
43 Pruefungen, Schwerpunkt auf dem, was NICHT gehen darf.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -38,7 +38,7 @@ import express from "express";
|
||||
import {
|
||||
readdirSync, statSync, unlinkSync, mkdirSync, existsSync, statfsSync, renameSync,
|
||||
} from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { join, dirname, sep } from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
import { randomBytes, timingSafeEqual } from "node:crypto";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
@@ -59,7 +59,7 @@ const ORDNER = join(DATEN_ORDNER, "sicherungen");
|
||||
/* Wie viele wovon aufgehoben werden. Grossvater-Vater-Sohn: viele frische
|
||||
fuer "ups, eben geloescht", wenige alte fuer "das war schon letzten
|
||||
Monat falsch". Bei 2 MB je Datei kostet das nichts. */
|
||||
const BEHALTEN = { taeglich: 7, woechentlich: 4, monatlich: 6 };
|
||||
const BEHALTEN = { taeglich: 7, woechentlich: 4, monatlich: 6, vorher: 12 };
|
||||
|
||||
const zahl2 = (n) => String(n).padStart(2, "0");
|
||||
|
||||
@@ -128,7 +128,18 @@ function pruefen(pfad) {
|
||||
} finally { try { p.close(); } catch { /* egal */ } }
|
||||
}
|
||||
|
||||
export function sicherungJetzt(grund = "planmaessig") {
|
||||
/* `eigeneDatei` ist fuer Sicherungen VOR einer unumkehrbaren Handlung.
|
||||
|
||||
Der Grund dafuer kam aus der Pruefung: Wer drei Personen hintereinander
|
||||
loescht, ueberschreibt sonst dreimal dieselbe Tagesdatei -- und die
|
||||
letzte Fassung kennt die erste geloeschte Person schon nicht mehr. Die
|
||||
Sicherung haette dann genau in dem Fall versagt, fuer den sie gemacht
|
||||
ist.
|
||||
|
||||
Deshalb bekommt jede solche Sicherung einen eigenen Namen mit Uhrzeit
|
||||
und kann nichts ueberschreiben. Zwoelf davon werden aufgehoben; bei
|
||||
rund 200 KB je Datei kostet das nichts. */
|
||||
export function sicherungJetzt(grund = "planmaessig", eigeneDatei = false) {
|
||||
const begonnen = Date.now();
|
||||
const d = db();
|
||||
ordnerSicherstellen();
|
||||
@@ -143,18 +154,40 @@ export function sicherungJetzt(grund = "planmaessig") {
|
||||
checkpoint = { fehler: f?.message };
|
||||
}
|
||||
|
||||
const art = artFuer();
|
||||
const ziel = join(ORDNER, `${art}-${heuteOrt()}.db`);
|
||||
const art = eigeneDatei ? "vorher" : artFuer();
|
||||
const n = new Date();
|
||||
const stempel = eigeneDatei
|
||||
/* Millisekunden mit dabei: Ohne sie trugen drei Loeschungen in
|
||||
derselben Sekunde denselben Namen, und die Zaehler-Notloesung
|
||||
darunter musste jedes Mal greifen -- die Dateinamen sortierten
|
||||
sich dann nicht mehr nach Zeit ("...-2.db" steht vor "....db"). */
|
||||
? `${heuteOrt()}-${zahl2(n.getHours())}${zahl2(n.getMinutes())}${zahl2(n.getSeconds())}`
|
||||
+ String(n.getMilliseconds()).padStart(3, "0")
|
||||
: heuteOrt();
|
||||
|
||||
let ziel = join(ORDNER, `${art}-${stempel}.db`);
|
||||
if (eigeneDatei) {
|
||||
/* Niemals ueberschreiben, egal wie fein die Uhr ist. Drei Loeschungen
|
||||
in derselben Sekunde ergaben mit reinem Zeitstempel EINE Datei --
|
||||
die zweite und dritte loeschten die vorherige. Genau der Fall, fuer
|
||||
den die Sicherung da ist, waere damit ungesichert gewesen.
|
||||
Aufgefallen ist es nur, weil die Pruefung die ANZAHL der Dateien
|
||||
gezaehlt hat und nicht bloss, ob eine da ist. */
|
||||
let zaehler = 2;
|
||||
while (existsSync(ziel)) ziel = join(ORDNER, `${art}-${stempel}-${zaehler++}.db`);
|
||||
} else {
|
||||
/* VACUUM INTO weigert sich, wenn die Zieldatei schon existiert. Am
|
||||
selben Tag zweimal planmaessig zu sichern ist erlaubt -- die neue
|
||||
ersetzt dann die alte. */
|
||||
try { if (existsSync(ziel)) unlinkSync(ziel); } catch { /* egal */ }
|
||||
}
|
||||
const dateiname = ziel.slice(ziel.lastIndexOf(sep) + 1);
|
||||
|
||||
/* Erst neben das Ziel schreiben, dann pruefen, dann umbenennen. Sonst
|
||||
stuende nach einem Abbruch eine halbe Datei unter dem richtigen
|
||||
Namen -- und die wuerde man fuer gut halten. */
|
||||
const vorlaeufig = ziel + ".teil";
|
||||
try { if (existsSync(vorlaeufig)) unlinkSync(vorlaeufig); } catch { /* egal */ }
|
||||
/* VACUUM INTO weigert sich, wenn die Zieldatei schon existiert. Am
|
||||
selben Tag zweimal zu sichern ist erlaubt (etwa von Hand vor einer
|
||||
groesseren Aenderung) -- die neue ersetzt dann die alte. */
|
||||
try { if (existsSync(ziel)) unlinkSync(ziel); } catch { /* egal */ }
|
||||
|
||||
d.exec(`VACUUM INTO '${vorlaeufig.replace(/'/g, "''")}'`);
|
||||
|
||||
@@ -170,7 +203,7 @@ export function sicherungJetzt(grund = "planmaessig") {
|
||||
|
||||
const ergebnis = {
|
||||
zeit: new Date().toISOString(),
|
||||
datei: `${art}-${heuteOrt()}.db`,
|
||||
datei: dateiname,
|
||||
art,
|
||||
groesse,
|
||||
personen: geprueft.personen,
|
||||
@@ -181,8 +214,13 @@ export function sicherungJetzt(grund = "planmaessig") {
|
||||
? `${checkpoint.busy ?? "?"}/${checkpoint.log ?? "?"}/${checkpoint.checkpointed ?? "?"}`
|
||||
: (checkpoint?.fehler || null),
|
||||
};
|
||||
einstellungSetzen("sicherung_letzte", JSON.stringify(ergebnis));
|
||||
einstellungSetzen("sicherung_fehler", "");
|
||||
/* Eine Sicherung VOR einer Loeschung ist kein planmaessiger Lauf. Wuerde
|
||||
sie den Vermerk setzen, glaubte die Zustandsanzeige, die naechtliche
|
||||
Sicherung sei erledigt -- und in der Nacht liefe keine mehr. */
|
||||
if (!eigeneDatei) {
|
||||
einstellungSetzen("sicherung_letzte", JSON.stringify(ergebnis));
|
||||
einstellungSetzen("sicherung_fehler", "");
|
||||
}
|
||||
return ergebnis;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user