DOGFATHER UNIVERSE: erste lauffähige Website + Bewerbungs-Postfach + Hosting-Vorbereitung

This commit is contained in:
2026-07-30 20:09:09 +02:00
commit f8e4cf82db
45 changed files with 3462 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
# DOGFATHER UNIVERSE — Bewerbungs-Postfach (Cloudflare Worker)
Dieser Worker nimmt Bewerbungen von `bewerben.html` entgegen und speichert sie in Cloudflare KV.
Nur wer den richtigen Code kennt, kann sie über `postfach.html` einsehen — der Code wird
**serverseitig** geprüft (nicht im Browser-Code sichtbar), das ist der eigentliche Schutz.
Kosten: läuft im kostenlosen Cloudflare-Tier (Workers Free + KV Free reichen für dieses Volumen locker).
## Voraussetzungen
- Ein (kostenloser) Cloudflare-Account
- Node.js installiert (für `npx wrangler`)
## Deploy in 5 Schritten
Alles im Ordner `cloudflare-worker/` ausführen:
```bash
cd cloudflare-worker
# 1. Bei Cloudflare einloggen (öffnet Browser zur Anmeldung)
npx wrangler login
# 2. KV-Namespace anlegen (Speicher für die Bewerbungen)
npx wrangler kv namespace create BEWERBUNGEN
```
Die Ausgabe von Schritt 2 enthält eine `id = "..."`. Diese `id` in `wrangler.toml` bei
`REPLACE_ME` eintragen.
```bash
# 3. Geheimen Zugangscode für das Postfach festlegen
# (wird abgefragt, nirgends im Code sichtbar — frei wählbar, z.B. ein langes Passwort)
npx wrangler secret put POSTFACH_CODE
# 4. Worker deployen
npx wrangler deploy
```
Schritt 4 gibt eine URL aus, z.B. `https://dogfather-universe-postfach.<dein-account>.workers.dev`.
```bash
# 5. Kurz testen
curl https://dogfather-universe-postfach.<dein-account>.workers.dev/health
# sollte {"ok":true,"service":"dogfather-universe-postfach"} zurückgeben
```
## Website mit dem Worker verbinden
Nach dem Deploy die Worker-URL an zwei Stellen eintragen:
1. `assets/js/forms.js` → Konstante `API_BASE_URL` ganz oben auf die Worker-URL setzen
(ohne abschließenden Slash).
2. `postfach.html` → Konstante `API_BASE_URL` im `<script>`-Block genauso setzen.
Danach gehen neue Bewerbungen automatisch an den Worker statt (nur) an den `mailto:`-Fallback,
und `postfach.html` kann sie mit dem in Schritt 3 gesetzten Code anzeigen.
## Wer den Code kennen darf
Der Code aus Schritt 3 ist der einzige Schlüssel zum Postfach. Nur an Personen weitergeben, die
wirklich Zugriff auf Bewerbungen haben sollen. Code ändern: einfach `wrangler secret put
POSTFACH_CODE` erneut ausführen und neuen Wert eingeben — alte Bewerbungen bleiben erhalten,
nur der Zugangscode ändert sich.
## Wartung
- Bewerbungen ansehen: über `postfach.html` mit Code (empfohlen), oder direkt im Cloudflare
Dashboard unter Workers & Pages → KV → `BEWERBUNGEN`.
- Bewerbungen löschen: im Cloudflare Dashboard unter KV die einzelnen `sub:...`-Einträge entfernen.
- CORS ist aktuell offen (`*`) — sobald die finale Domain feststeht, in `src/worker.js`
`corsHeaders()` auf die echte Domain einschränken (siehe TODO-Kommentar dort).
+119
View File
@@ -0,0 +1,119 @@
/* =====================================================================
DOGFATHER UNIVERSE — Bewerbungs-Postfach Worker
Nimmt Bewerbungen von bewerben.html entgegen, speichert sie in KV,
und gibt sie nur nach korrektem Code (serverseitig geprüft, per
Secret gespeichert) an postfach.html zurück.
Endpoints:
POST /submit { type: "creator"|"scout"|"kooperation", data: {...} }
POST /postfach { code: "..." } -> Liste aller Bewerbungen
GET /health -> einfacher Check ob der Worker läuft
Secrets/Bindings (siehe wrangler.toml + README.md):
KV Binding: BEWERBUNGEN (Cloudflare KV Namespace)
Secret: POSTFACH_CODE (mit `wrangler secret put POSTFACH_CODE` setzen)
===================================================================== */
const ALLOWED_TYPES = ["creator", "scout", "kooperation"];
function corsHeaders() {
// TODO: sobald die Domain final steht, statt "*" die echte Domain eintragen
// (z.B. "https://dogfather-universe.example"), damit nur die eigene Seite
// an die API senden kann.
return {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
}
function json(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: { "Content-Type": "application/json", ...corsHeaders() },
});
}
async function handleSubmit(request, env) {
let body;
try {
body = await request.json();
} catch {
return json({ ok: false, error: "Ungültiges JSON." }, 400);
}
const { type, data } = body || {};
if (!ALLOWED_TYPES.includes(type) || typeof data !== "object" || !data) {
return json({ ok: false, error: "Ungültige Bewerbungsdaten." }, 400);
}
// simple Server-seitige Validierung: keine leeren Kernfelder
const values = Object.values(data).map((v) => String(v || "").trim());
if (values.every((v) => v === "")) {
return json({ ok: false, error: "Leere Bewerbung." }, 400);
}
const id = crypto.randomUUID();
const timestamp = new Date().toISOString();
const key = `sub:${timestamp}:${id}`;
await env.BEWERBUNGEN.put(
key,
JSON.stringify({ id, type, data, timestamp })
);
return json({ ok: true });
}
async function handlePostfach(request, env) {
let body;
try {
body = await request.json();
} catch {
return json({ ok: false, error: "Ungültiges JSON." }, 400);
}
const code = (body && body.code) || "";
if (!env.POSTFACH_CODE || code !== env.POSTFACH_CODE) {
// bewusst generische Fehlermeldung, kein Hinweis ob Code existiert
return json({ ok: false, error: "Falscher Code." }, 401);
}
const list = await env.BEWERBUNGEN.list({ prefix: "sub:", limit: 500 });
const items = await Promise.all(
list.keys.map(async (k) => {
const raw = await env.BEWERBUNGEN.get(k.name);
return raw ? JSON.parse(raw) : null;
})
);
const bewerbungen = items
.filter(Boolean)
.sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)); // neueste zuerst
return json({ ok: true, bewerbungen });
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders() });
}
if (url.pathname === "/health") {
return json({ ok: true, service: "dogfather-universe-postfach" });
}
if (url.pathname === "/submit" && request.method === "POST") {
return handleSubmit(request, env);
}
if (url.pathname === "/postfach" && request.method === "POST") {
return handlePostfach(request, env);
}
return json({ ok: false, error: "Not found." }, 404);
},
};
+14
View File
@@ -0,0 +1,14 @@
# =====================================================================
# DOGFATHER UNIVERSE — Bewerbungs-Postfach Worker Konfiguration
# Siehe README.md für die Schritt-für-Schritt-Deploy-Anleitung.
# =====================================================================
name = "dogfather-universe-postfach"
main = "src/worker.js"
compatibility_date = "2024-01-01"
# TODO: nach `wrangler kv namespace create BEWERBUNGEN` die ausgegebene
# "id" hier eintragen (ersetzt "REPLACE_ME").
[[kv_namespaces]]
binding = "BEWERBUNGEN"
id = "REPLACE_ME"