Add comprehensive README and update API/Web code

This commit is contained in:
2026-06-12 10:08:21 +02:00
parent 3f4f88ba62
commit bf7bda556b
3 changed files with 96 additions and 0 deletions

46
README.md Normal file
View File

@@ -0,0 +1,46 @@
# Mitwirkung
Mitwirkung ist ein MVP für nachvollziehbare politische Beteiligung. Registrierte Nutzerinnen und Nutzer können eigene Gesetzesentwürfe formulieren, bestehende Vorschläge nach Thema und Status durchsuchen und mit Dafür, Dagegen oder Enthaltung abstimmen.
## Produktidee
Die Plattform strukturiert politische Vorschläge in drei prüfbare Teile: Ausgangslage, konkrete Lösung und erwartete Wirkung. Abstimmungen sind an Nutzerkonten gebunden, können bis zum Ablauf geändert werden und werden transparent aggregiert. Der Demonstrator ist bewusst keine staatliche Plattform und ersetzt kein formelles Gesetzgebungsverfahren.
## Funktionen
- Registrierung, Anmeldung und sichere HTTP-only Session-Cookies
- Thematische Übersicht mit Filterung und Sortierung
- Detailseiten mit Frist, Urheber, Begründung und Live-Ergebnis
- Einmalige, änderbare Abstimmung pro Nutzerkonto und Entwurf
- Strukturierter Editor für eigene Gesetzesentwürfe
- Responsives Frontend für Desktop und Mobilgeräte
- SQLite-Datenbank mit Demo-Inhalten
## Lokaler Start
```bash
npm install
npm run dev
```
Frontend: `http://localhost:5173`
API: `http://localhost:4000`
Demo-Anmeldung: `leonie@mitwirkung.de` / `demo1234`
## Produktion und Tests
```bash
npm test
npm run build
npm start
```
Nach dem Build liefert der Express-Server unter `http://localhost:4000` auch das React-Frontend aus.
## Technik
- React 19, TypeScript, Vite
- Express 5, Zod, JWT, bcrypt
- SQLite über `better-sqlite3`
- Vitest und Supertest

38
apps/api/src/app.test.ts Normal file
View File

@@ -0,0 +1,38 @@
import request from "supertest";
import { describe, expect, it } from "vitest";
import { app } from "./app.js";
describe("Mitwirkung API", () => {
it("liefert Themen und initiale Entwürfe", async () => {
const [topics, bills] = await Promise.all([
request(app).get("/api/topics"),
request(app).get("/api/bills"),
]);
expect(topics.status).toBe(200);
expect(topics.body.topics.length).toBeGreaterThanOrEqual(6);
expect(bills.status).toBe(200);
expect(bills.body.bills.length).toBeGreaterThanOrEqual(5);
});
it("meldet einen Nutzer an und speichert genau eine aktuelle Stimme", async () => {
const agent = request.agent(app);
const login = await agent.post("/api/auth/login").send({
email: "leonie@mitwirkung.de",
password: "demo1234",
});
expect(login.status).toBe(200);
const vote = await agent.post("/api/bills/1/vote").send({ value: "yes" });
expect(vote.status).toBe(200);
const detail = await agent.get("/api/bills/1");
expect(detail.status).toBe(200);
expect(detail.body.bill.userVote).toBe("yes");
});
it("verhindert anonyme Abstimmungen", async () => {
const response = await request(app).post("/api/bills/1/vote").send({ value: "no" });
expect(response.status).toBe(401);
});
});

View File

@@ -1,6 +1,18 @@
import { app } from "./app.js"; import { app } from "./app.js";
import express from "express";
import { existsSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const port = Number(process.env.PORT || 4000); const port = Number(process.env.PORT || 4000);
const here = dirname(fileURLToPath(import.meta.url));
const webDist = resolve(here, "../../web/dist");
if (existsSync(webDist)) {
app.use(express.static(webDist));
app.get(/^(?!\/api).*/, (_req, res) => res.sendFile(resolve(webDist, "index.html")));
}
app.listen(port, () => { app.listen(port, () => {
console.log(`Mitwirkung API läuft auf http://localhost:${port}`); console.log(`Mitwirkung API läuft auf http://localhost:${port}`);
}); });