Files
dogfather-universe/server-internal/db.js
T
DogFatherGitandClaude Opus 5 65b98ace89 Die Uhr laeuft erst, wenn die Anzahlung da ist
Bisher begann der Liefertermin mit der Annahme. Das ist unfair in beide
Richtungen: Wer zehn Tage bis zur Zahlung braucht, verbraucht zehn Tage
der zugesagten Zeit, ohne dass ein Handschlag Arbeit passiert waere --
und ich stehe am Ende als der da, der seinen Termin reisst.

Ein Projekt hat jetzt drei Abschnitte statt zwei:
  1. angenommen, wartet auf Anzahlung  -> Uhr steht
  2. Anzahlung da                      -> Uhr laeuft, Termin ab HEUTE neu
  3. uebergeben oder abgebrochen       -> Uhr steht wieder

Der Zahlungseingang loest alles Weitere von selbst aus: Uhr starten,
Termin neu rechnen, Status von briefing auf design, 'wer ist am Zug' auf
mich, Benachrichtigung in der Verwaltung. Der bei der Annahme genannte
Termin bleibt als termin_geplant_am erhalten, und die Meldung nennt
BEIDE -- so sieht man, dass sich etwas verschoben hat, ohne nachrechnen
zu muessen.

Eingehaengt an der Stelle, an der beide Wege zusammenlaufen (PayPals
Meldung UND das Vermerken von Hand). Nur am Webhook haenge sich die
Seite verschieden verhalten, je nachdem WIE das Geld ankam -- eine von
Hand verbuchte Zahlung startete die Uhr nie.

Zwei Grundsaetze fuer die Automatik: Sie setzt Dinge in Gang, nimmt aber
nie eine Entscheidung zurueck, die ein Mensch getroffen hat (ein von Hand
pausiertes Projekt wird nicht kommentarlos wieder gestartet). Und jeder
Schritt hinterlaesst eine Spur im Verlauf UND als Meldung -- eine
Automatik, die stillschweigend arbeitet, ist kein Helfer, sondern ein
Raetsel.

ABBRECHEN
Ein angenommener Auftrag bleibt abbrechbar: Der Kunde zahlt nicht,
meldet sich nicht, springt ab. Vorschau und Ausfuehrung sind getrennt --
die Seite rechnet aus dem Aufgabenfortschritt vor, wie viel Leistung
erbracht wurde, und schlaegt daraus einen Erstattungsbetrag vor. Der
Betrag ist ein VORSCHLAG: Ob im Einzelfall mehr oder weniger angemessen
ist, haengt an Dingen, die keine Tabelle kennt. Offene Rechnungen werden
storniert (eine Zahlungsaufforderung ohne Gegenleistung), bezahlte
bleiben unangetastet, und die Rueckzahlung loest die Seite bewusst NICHT
selbst aus -- PayPal-Rueckzahlungen sind nicht umkehrbar.

BENACHRICHTIGUNGEN
Eigene Tabelle statt im Verlauf: Der Verlauf haelt fest, WAS geschehen
ist -- vollstaendig, zum Nachschlagen. Eine Benachrichtigung ist ein
Anstupsen, das gelesen und weggelegt wird. Beides in einer Tabelle
hiesse: entweder ein Verlauf voller Rauschen oder Meldungen, die man
nicht wegklicken kann. Wegklicken markiert nur als gelesen, loescht
nichts.

Dazu die Liste der Projekte, die seit ueber einer Woche auf ihre
Anzahlung warten. Sie stehen in keiner anderen Zahl, weil ihre Uhr nie
zu laufen begann -- ohne diesen Hinweis vergisst man sie.

Geprueft: 44 gegen eine echte Datenbank. Darunter der Kern -- die
Annahme wird zehn Tage zurueckdatiert, und der Termin muss danach
trotzdem volle 20 Werktage entfernt liegen. Beim Bauen des Tests selbst
ein Fehler gefunden: Die erste Fassung datierte nur die Annahme zurueck,
nicht den damals errechneten Termin, und bildete damit genau den Fall
nicht ab, um den es geht.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-24 11:56:08 +02:00

85 lines
3.8 KiB
JavaScript

/* =====================================================================
db.js — SQLite-Datenbank (better-sqlite3), Ersatz für Cloudflare D1.
Führt beim ersten Start alle Migrationen aus ../cloudflare-worker/migrations/
in der ursprünglichen Reihenfolge aus (0001 Basis-Schema, 0002 app_settings,
0003 Supporter-Abo, 0004 TikTok/Discord-Spalten, 0005 Supporter-Abstimmungen)
— anders als bei VanVans Shop ist hier NIE alles in einer konsolidierten
schema.sql zusammengefasst worden, deshalb müssen wir hier tatsächlich alle
der Reihe nach anwenden.
===================================================================== */
import Database from "better-sqlite3";
import { readFileSync, existsSync, mkdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const DB_PATH = process.env.DB_PATH || "/var/lib/dogfather-internal/dogfather-internal.db";
const MIGRATIONS_DIR = join(__dirname, "..", "cloudflare-worker", "migrations");
mkdirSync(dirname(DB_PATH), { recursive: true });
export const db = new Database(DB_PATH);
db.pragma("journal_mode = WAL");
db.pragma("foreign_keys = ON");
const MIGRATION_FILES = [
"0001_init.sql",
"0002_app_settings.sql",
"0003_supporter_abo.sql",
"0004_supporter_oauth_providers.sql",
"0005_supporter_polls.sql",
"0006_testimonials.sql",
"0007_team_members.sql",
"0008_team_members_rich.sql",
"0009_supporter_access_code.sql",
"0010_testimonials_avatar.sql",
// Webdesign-Bereich (22.08.2026): Anfragen, Kunden, Projekte, Dateien,
// Nachrichten, Änderungswünsche, pflegbare Inhalte, Änderungsverlauf.
// Alle Tabellen tragen das Präfix "wd_" und sind fachlich vollständig
// vom Universe getrennt — Begründung im Kopf der Migration.
"0011_webdesign.sql",
// Zahlungen im Webdesign-Bereich (22.08.2026): PayPal-Einmalzahlungen
// fuer Anzahlung/Restbetrag/Zusatzangebote, PayPal-Abo fuer die
// monatliche Betreuung, Webhook-Merkliste gegen Doppelverbuchung.
"0012_webdesign_zahlungen.sql",
// Aufgabenliste je Projekt ("was ist schon gemacht von dem, was im Plan
// war") und ein allgemeines Postfach fuer Nachrichten OHNE Projektbezug
// (22.08.2026). Begruendung fuer die getrennte Postfach-Tabelle steht
// im Kopf der Migration.
"0013_webdesign_aufgaben.sql",
// Elektronische Widerrufsfunktion (§ 356a BGB, seit 19.06.2026) und der
// Nachweis der Zustimmung zum vorzeitigen Leistungsbeginn (§ 356 Abs. 4
// BGB). Ausfuehrliche Begruendung im Kopf der Migration -- kurz: Im
// Portal werden Zusatzangebote per Klick verbindlich angenommen, das ist
// ein Vertragsschluss ueber eine Online-Benutzeroberflaeche.
"0014_webdesign_widerruf.sql",
// Annehmen und Ablehnen einer Anfrage: echter Liefertermin (termin_am)
// fuer den Countdown, Zeitpunkt der Annahme, Ablehnungsgrund.
"0015_webdesign_annahme.sql",
// Die Uhr laeuft erst bei Anzahlung; Abbruch mit Erstattung;
// Benachrichtigungen fuer die Verwaltung.
"0016_webdesign_automatik.sql",
];
function alreadyApplied(name) {
const row = db.prepare(`SELECT 1 FROM _migrations WHERE name = ?`).get(name);
return !!row;
}
export function initDb() {
db.exec(`CREATE TABLE IF NOT EXISTS _migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL)`);
for (const file of MIGRATION_FILES) {
if (alreadyApplied(file)) continue;
const path = join(MIGRATIONS_DIR, file);
if (!existsSync(path)) {
console.warn(`⚠️ Migration ${file} nicht gefunden unter ${path}, übersprungen.`);
continue;
}
const sql = readFileSync(path, "utf-8");
db.exec(sql);
db.prepare(`INSERT INTO _migrations (name, applied_at) VALUES (?, ?)`).run(file, new Date().toISOString());
console.log(`Migration angewendet: ${file}`);
}
}