first commit
Docker-Images bauen und veroeffentlichen / build-and-push (push) Failing after 1m32s

This commit is contained in:
Nick Szaunig committed 2026-09-27 06:06:56 +02:00
commit 59bb5a38a3
132 files changed
+19435

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
/**
* Legt ein Konto fuer den Adminbereich an. Es gibt keine Selbstregistrierung
* und kein Standardpasswort.
*
* pnpm admin:create --email [email protected] --name "Vorname Nachname"
*
* Passwort: aus ADMIN_PASSWORD (mind. 12 Zeichen) oder zufaellig erzeugt und
* EINMAL ausgegeben. Nach der ersten Anmeldung erzwingt der Adminbereich die
* Einrichtung der Zwei-Faktor-Anmeldung.
*/
import { randomBytes } from "node:crypto";
import { parseArgs } from "node:util";
import { createProvisioningAuth } from "@/lib/auth";
import { getDb } from "@/lib/db";
try {
process.loadEnvFile();
} catch {
// Umgebung kommt aus dem Container.
}
const { values } = parseArgs({ options: { email: { type: "string" }, name: { type: "string" } } });
const email = values.email?.trim().toLowerCase();
const name = values.name?.trim();
if (!email || !/^\S+@\S+\.\S+$/.test(email) || !name) {
console.error('Aufruf: pnpm admin:create --email <adresse> --name "<Name>"');
process.exit(1);
}
const provided = process.env.ADMIN_PASSWORD;
if (provided !== undefined && provided.length < 12) {
console.error("ADMIN_PASSWORD muss mindestens 12 Zeichen haben.");
process.exit(1);
}
const password = provided ?? randomBytes(18).toString("base64url");
const db = getDb();
try {
if (await db.user.findUnique({ where: { email } })) {
console.error("Für diese E-Mail-Adresse existiert bereits ein Konto.");
process.exitCode = 1;
} else {
// Offizieller Weg ueber Better Auth: Passwortregeln und Leak-Pruefung
// greifen dabei automatisch.
const result = await createProvisioningAuth().api.signUpEmail({ body: { email, name, password } });
await db.user.update({ where: { id: result.user.id }, data: { emailVerified: true } });
await db.auditLog.create({ data: { actorType: "SYSTEM", action: "admin.created", entityType: "User", entityId: result.user.id } });
console.log(`Konto angelegt: ${email}`);
if (!provided) console.log(`Einmaliges Passwort (jetzt sicher ablegen, wird nicht erneut angezeigt): ${password}`);
console.log("Bei der ersten Anmeldung wird die Zwei-Faktor-Anmeldung eingerichtet.");
}
} finally {
await db.$disconnect();
}
+49
View File
@@ -0,0 +1,49 @@
// Lokale PostgreSQL-Instanz fuer die Entwicklung ohne Docker.
//
// pnpm db:dev startet PostgreSQL auf Port 54329 (Strg+C beendet)
//
// Mit Docker stattdessen: docker compose up -d db (siehe docker-compose.yml).
//
// Das Datenverzeichnis liegt bewusst NICHT im Projektordner: dieser liegt
// hier in einem OneDrive-synchronisierten Verzeichnis, und ein
// PostgreSQL-Datenverzeichnis in einer Cloud-Synchronisierung fuehrt zu
// gesperrten oder beschaedigten Dateien.
import EmbeddedPostgres from "embedded-postgres";
import { existsSync } from "node:fs";
import os from "node:os";
import path from "node:path";
const baseDir = process.env.LOCALAPPDATA ?? path.join(os.homedir(), ".local", "share");
const databaseDir = path.join(baseDir, "artista-modelcamp", "dev-db");
const port = Number(process.env.DEV_DB_PORT ?? 54329);
const pg = new EmbeddedPostgres({
databaseDir,
user: "artista",
password: "artista-dev",
port,
persistent: true,
onLog: () => {},
});
if (!existsSync(path.join(databaseDir, "PG_VERSION"))) {
await pg.initialise();
}
await pg.start();
try {
await pg.createDatabase("artista");
} catch {
// existiert bereits
}
console.log(`PostgreSQL laeuft: postgresql://artista:artista-dev@localhost:${port}/artista`);
console.log(`Daten: ${databaseDir}`);
const shutdown = async () => {
await pg.stop();
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
// Prozess am Leben halten.
setInterval(() => {}, 1 << 30);
+40
View File
@@ -0,0 +1,40 @@
/**
* Testdaten NUR fuer Entwicklung und Staging, damit eine Stripe-Testbuchung
* sofort moeglich ist:
* - Preisphase "Early Bird" simuliert (wirkt nur mit Stripe-Testschluessel)
* - ein TEST-Anreisetag, ausdruecklich NICHT bestaetigt (erscheint deshalb
* nicht oeffentlich, erlaubt aber Ratenplaene im Testmodus)
* - ein Test-Gutschein TEST-STAMMMODEL (nicht der Beispielcode aus dem Briefing)
*
* Verweigert den Lauf mit APP_ENV=production.
*/
import { getDb, CAMP_ID } from "@/lib/db";
import { isoToDbDate } from "@/lib/time";
try {
process.loadEnvFile();
} catch {
// Umgebung kommt aus dem Container.
}
if ((process.env.APP_ENV ?? "development") === "production") {
console.error("seed-dev laeuft nicht mit APP_ENV=production.");
process.exit(1);
}
const db = getDb();
try {
await db.camp.upsert({
where: { id: CAMP_ID },
create: { id: CAMP_ID, testPhaseOverride: "EARLY_BIRD", campStartDate: isoToDbDate("2027-06-05"), dateConfirmed: false },
update: { testPhaseOverride: "EARLY_BIRD", campStartDate: isoToDbDate("2027-06-05"), dateConfirmed: false },
});
await db.voucher.upsert({
where: { code: "TEST-STAMMMODEL" },
create: { code: "TEST-STAMMMODEL", maxRedemptions: 3, internalNote: "Entwicklung: Testgutschein" },
update: {},
});
console.log("Entwicklungsdaten gesetzt: Early Bird simuliert, Test-Anreisetag 05.06.2027 (unbestätigt), Gutschein TEST-STAMMMODEL.");
} finally {
await db.$disconnect();
}
+34
View File
@@ -0,0 +1,34 @@
/**
* Hintergrundlauf fuer Reservierungen, Folgeraten, Zahlungsmethoden und
* E-Mails. Im Cluster als Kubernetes-CronJob (k8s/base/cronjob-worker.yaml),
* lokal per `pnpm worker`.
*
* Endet mit Exit-Code 1, wenn etwas fehlschlaegt — der CronJob-Lauf wird
* dann als fehlgeschlagen sichtbar statt still im Log zu verschwinden.
*/
import { getDb } from "@/lib/db";
import { env, requireAppSecret } from "@/lib/env";
import { getGateway } from "@/lib/payment-gateway";
import { runWorker } from "@/lib/worker-jobs";
try {
process.loadEnvFile();
} catch {
// Im Container kommen die Variablen aus der Umgebung.
}
const db = getDb();
try {
const result = await runWorker(db, getGateway(), {
now: new Date(),
secret: requireAppSecret(),
adminEmail: env().ADMIN_NOTIFY_EMAIL ?? null,
});
console.log(`[worker] ${new Date().toISOString()} ${JSON.stringify(result)}`);
process.exitCode = result.emails.failed > 0 ? 1 : 0;
} catch (error) {
console.error(`[worker] Abbruch: ${error instanceof Error ? error.message : "unbekannter Fehler"}`);
process.exitCode = 1;
} finally {
await db.$disconnect();
}