Vollständiges Zugangs-/Rollen-/Rechtesystem gebaut (Anforderungsdokument): D1-Datenbank, AES-verschlüsselte Codes nur für Owner einsehbar, granulare Permissions, Session-Auth mit Lockout, Zugänge-&-Teammitglieder-Verwaltungsseite, Bewerbungen mit Status/Notizen/Entwürfen
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
/* =====================================================================
|
||||
routes/applications.js — Bewerbungen (Postfach), jetzt in D1 mit
|
||||
vollem Rollen-/Rechte-Modell statt dem alten pauschalen Owner/Helfer-
|
||||
Zugriff. Status, Zuweisung, Notizen und Antwortentwürfe pro Bewerbung.
|
||||
===================================================================== */
|
||||
import { generateId, nowIso } from "../lib/crypto.js";
|
||||
import { hasPermission } from "../lib/permissions.js";
|
||||
import { logAction } from "../lib/audit.js";
|
||||
import { json } from "../lib/http.js";
|
||||
|
||||
const ALLOWED_TYPES = ["creator", "scout", "kooperation"];
|
||||
|
||||
function can(session, key) {
|
||||
if (!session) return false;
|
||||
if (session.isOwner) return true;
|
||||
return hasPermission(session.permissions, session.overrides, key);
|
||||
}
|
||||
|
||||
function viewPermissionForType(type) {
|
||||
return type === "creator" ? "APPLICATIONS_VIEW_CREATOR" : "APPLICATIONS_VIEW_MANAGER_SCOUT";
|
||||
}
|
||||
|
||||
export async function submitApplication(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);
|
||||
}
|
||||
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 = generateId();
|
||||
await env.DB.prepare(
|
||||
`INSERT INTO applications (id, type, data, status, archived, created_at) VALUES (?, ?, ?, 'neu', 0, ?)`
|
||||
)
|
||||
.bind(id, type, JSON.stringify(data), nowIso())
|
||||
.run();
|
||||
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
export async function listApplications(request, env, session) {
|
||||
if (!session) return json({ ok: false, error: "Nicht angemeldet." }, 401);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const includeArchived = !!body.includeArchived;
|
||||
|
||||
const allowedTypes = ALLOWED_TYPES.filter((t) => session.isOwner || can(session, viewPermissionForType(t)));
|
||||
if (allowedTypes.length === 0) return json({ ok: true, applications: [] });
|
||||
if (includeArchived && !session.isOwner && !can(session, "APPLICATIONS_VIEW_ARCHIVED")) {
|
||||
return json({ ok: false, error: "Keine Berechtigung für archivierte Bewerbungen." }, 403);
|
||||
}
|
||||
|
||||
const placeholders = allowedTypes.map(() => "?").join(",");
|
||||
const archivedClause = includeArchived ? "" : "AND archived = 0";
|
||||
const rows = await env.DB.prepare(
|
||||
`SELECT * FROM applications WHERE type IN (${placeholders}) ${archivedClause} ORDER BY created_at DESC LIMIT 300`
|
||||
)
|
||||
.bind(...allowedTypes)
|
||||
.all();
|
||||
|
||||
const applications = (rows.results || []).map((r) => ({
|
||||
id: r.id,
|
||||
type: r.type,
|
||||
data: session.isOwner || can(session, "APPLICATIONS_OPEN_FULL") ? JSON.parse(r.data) : null,
|
||||
status: r.status,
|
||||
assignedTo: r.assigned_to,
|
||||
archived: !!r.archived,
|
||||
createdAt: r.created_at,
|
||||
}));
|
||||
|
||||
return json({ ok: true, applications });
|
||||
}
|
||||
|
||||
export async function changeApplicationStatus(request, env, session) {
|
||||
if (!can(session, "APPLICATIONS_CHANGE_STATUS")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const id = String(body.id || "");
|
||||
const status = String(body.status || "").slice(0, 40);
|
||||
await env.DB.prepare(`UPDATE applications SET status = ? WHERE id = ?`).bind(status, id).run();
|
||||
await logAction(env, session.actor, "application.status", id, { status });
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
export async function takeOverApplication(request, env, session) {
|
||||
if (!can(session, "APPLICATIONS_TAKE_OVER")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const id = String(body.id || "");
|
||||
const who = session.isOwner ? "owner" : session.userId;
|
||||
await env.DB.prepare(`UPDATE applications SET assigned_to = ? WHERE id = ?`).bind(who, id).run();
|
||||
await logAction(env, session.actor, "application.take_over", id, null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
export async function assignApplication(request, env, session) {
|
||||
if (!can(session, "APPLICATIONS_ASSIGN")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const id = String(body.id || "");
|
||||
const assignedTo = body.assignedTo ? String(body.assignedTo) : null;
|
||||
await env.DB.prepare(`UPDATE applications SET assigned_to = ? WHERE id = ?`).bind(assignedTo, id).run();
|
||||
await logAction(env, session.actor, "application.assign", id, { assignedTo });
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
export async function archiveApplication(request, env, session) {
|
||||
if (!can(session, "APPLICATIONS_ARCHIVE")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const id = String(body.id || "");
|
||||
await env.DB.prepare(`UPDATE applications SET archived = 1 WHERE id = ?`).bind(id).run();
|
||||
await logAction(env, session.actor, "application.archive", id, null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
export async function restoreApplication(request, env, session) {
|
||||
if (!can(session, "APPLICATIONS_RESTORE")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const id = String(body.id || "");
|
||||
await env.DB.prepare(`UPDATE applications SET archived = 0 WHERE id = ?`).bind(id).run();
|
||||
await logAction(env, session.actor, "application.restore", id, null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
export async function deleteApplication(request, env, session) {
|
||||
if (!can(session, "APPLICATIONS_DELETE")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const id = String(body.id || "");
|
||||
await env.DB.prepare(`DELETE FROM application_notes WHERE application_id = ?`).bind(id).run();
|
||||
await env.DB.prepare(`DELETE FROM application_drafts WHERE application_id = ?`).bind(id).run();
|
||||
await env.DB.prepare(`DELETE FROM applications WHERE id = ?`).bind(id).run();
|
||||
await logAction(env, session.actor, "application.delete", id, null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
export async function markAnswered(request, env, session) {
|
||||
if (!can(session, "APPLICATIONS_MARK_ANSWERED")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const id = String(body.id || "");
|
||||
await env.DB.prepare(`UPDATE applications SET status = 'beantwortet' WHERE id = ?`).bind(id).run();
|
||||
await logAction(env, session.actor, "application.mark_answered", id, null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
/* ---- Notizen ---- */
|
||||
export async function listNotes(request, env, session) {
|
||||
if (!can(session, "NOTES_READ")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const rows = await env.DB.prepare(
|
||||
`SELECT * FROM application_notes WHERE application_id = ? ORDER BY created_at ASC`
|
||||
)
|
||||
.bind(String(body.applicationId || ""))
|
||||
.all();
|
||||
return json({ ok: true, notes: rows.results || [] });
|
||||
}
|
||||
|
||||
export async function addNote(request, env, session) {
|
||||
if (!can(session, "NOTES_WRITE")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const text = String(body.text || "").slice(0, 2000);
|
||||
if (!text.trim()) return json({ ok: false, error: "Leere Notiz." }, 400);
|
||||
await env.DB.prepare(
|
||||
`INSERT INTO application_notes (application_id, author, text, created_at) VALUES (?, ?, ?, ?)`
|
||||
)
|
||||
.bind(String(body.applicationId || ""), session.actor, text, nowIso())
|
||||
.run();
|
||||
await logAction(env, session.actor, "note.add", body.applicationId, null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
export async function deleteNote(request, env, session) {
|
||||
if (!can(session, "NOTES_DELETE")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
const body = await request.json().catch(() => ({}));
|
||||
await env.DB.prepare(`DELETE FROM application_notes WHERE id = ?`).bind(Number(body.id)).run();
|
||||
await logAction(env, session.actor, "note.delete", String(body.id), null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
/* ---- Antwortentwürfe ---- */
|
||||
export async function listDrafts(request, env, session) {
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const rows = await env.DB.prepare(
|
||||
`SELECT * FROM application_drafts WHERE application_id = ? ORDER BY updated_at DESC`
|
||||
)
|
||||
.bind(String(body.applicationId || ""))
|
||||
.all();
|
||||
return json({ ok: true, drafts: rows.results || [] });
|
||||
}
|
||||
|
||||
export async function saveDraft(request, env, session) {
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const content = String(body.content || "").slice(0, 5000);
|
||||
|
||||
if (body.id) {
|
||||
const existing = await env.DB.prepare(`SELECT * FROM application_drafts WHERE id = ?`).bind(Number(body.id)).first();
|
||||
if (!existing) return json({ ok: false, error: "Entwurf nicht gefunden." }, 404);
|
||||
const isOwnDraft = existing.author === session.actor;
|
||||
if (!can(session, isOwnDraft ? "DRAFTS_EDIT_OWN" : "DRAFTS_EDIT_OTHERS")) {
|
||||
return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
}
|
||||
await env.DB.prepare(`UPDATE application_drafts SET content = ?, updated_at = ? WHERE id = ?`)
|
||||
.bind(content, nowIso(), Number(body.id))
|
||||
.run();
|
||||
await logAction(env, session.actor, "draft.update", String(body.id), null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
if (!can(session, "DRAFTS_CREATE")) return json({ ok: false, error: "Keine Berechtigung." }, 403);
|
||||
await env.DB.prepare(
|
||||
`INSERT INTO application_drafts (application_id, author, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`
|
||||
)
|
||||
.bind(String(body.applicationId || ""), session.actor, content, nowIso(), nowIso())
|
||||
.run();
|
||||
await logAction(env, session.actor, "draft.create", body.applicationId, null);
|
||||
return json({ ok: true });
|
||||
}
|
||||
Reference in New Issue
Block a user