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
+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);
},
};