Workspace: Aufgabenbrett und Dashboard-Zahlen
Erster echter Arbeitsbereich aus dem Konzept. Kanban mit offen / in Arbeit / Review / erledigt, dazu Prioritaet, Frist, Verantwortlicher und Zuordnung zu einem Creator-Bereich. Kern ist die Datentrennung, und die sitzt AUSSCHLIESSLICH im Server -- in jeder einzelnen Abfrage, an einer Stelle definiert (sichtbar()): admin sieht alles creator sieht seinen Bereich und was ihm zugewiesen ist scout sieht nur, was ihm zugewiesen ist Geprueft mit vier Testkonten: - Chef sieht 4, Luna 2, Mika 1, Sam 1 Aufgaben - Luna auf Mikas Aufgabe: 404 (nicht 403 -- sonst liesse sich durch Ausprobieren herausfinden, welche Nummern es gibt) - Luna legt Aufgabe mit creator_id=Mika an: wird still auf ihren eigenen Bereich umgebogen, Mika sieht sie nicht - Anfrage mit fremdem Origin: 403 - ohne Anmeldung: 401, ungueltiger Status/leerer Titel: 400 - Scout bekommt in der Personenliste nur sich selbst - Dashboard-Zahlen je Rolle korrekt eingegrenzt Weitere Punkte: - Texte werden im Browser nur ueber textContent gesetzt, nie innerHTML -- ein Aufgabentitel darf keine Auszeichnung einschleusen - ueberfaellig = Frist vorbei UND nicht erledigt; die Kachel faerbt sich nur, wenn wirklich etwas ansteht - erledigt_am wird gesetzt bzw. wieder geleert, wenn eine Aufgabe zurueckgeholt wird - Erledigtes verschwindet nicht, wie im Konzept gefordert
This commit is contained in:
+27
-4
@@ -58,7 +58,7 @@ const SCRYPT = { N: 32768, r: 8, p: 1, keylen: 64 };
|
||||
let _db = null;
|
||||
let _dbFehler = null;
|
||||
|
||||
function db() {
|
||||
export function db() {
|
||||
if (_db) return _db;
|
||||
if (_dbFehler) throw _dbFehler;
|
||||
try {
|
||||
@@ -102,6 +102,29 @@ function db() {
|
||||
ip TEXT
|
||||
);
|
||||
|
||||
/* Aufgaben. creator_id sagt, ZU WEM die Aufgabe gehört (wessen
|
||||
Bereich), verantwortlich_id, WER sie erledigt. Beides getrennt,
|
||||
weil im Konzept auch Aufgaben vorkommen, die das Management für
|
||||
einen Creator anlegt. */
|
||||
CREATE TABLE IF NOT EXISTS aufgaben (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
titel TEXT NOT NULL,
|
||||
beschreibung TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'offen'
|
||||
CHECK (status IN ('offen','arbeit','review','erledigt')),
|
||||
prioritaet TEXT NOT NULL DEFAULT 'mittel'
|
||||
CHECK (prioritaet IN ('hoch','mittel','niedrig')),
|
||||
creator_id INTEGER REFERENCES personen(id) ON DELETE SET NULL,
|
||||
verantwortlich_id INTEGER REFERENCES personen(id) ON DELETE SET NULL,
|
||||
frist TEXT,
|
||||
erstellt TEXT NOT NULL,
|
||||
erstellt_von INTEGER REFERENCES personen(id) ON DELETE SET NULL,
|
||||
geaendert TEXT,
|
||||
erledigt_am TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_aufgaben_status ON aufgaben (status);
|
||||
CREATE INDEX IF NOT EXISTS idx_aufgaben_creator ON aufgaben (creator_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS versuche (
|
||||
ip TEXT NOT NULL,
|
||||
zeitpunkt TEXT NOT NULL
|
||||
@@ -138,7 +161,7 @@ const jetzt = () => new Date().toISOString();
|
||||
von Cloudflare direkt anspricht, könnte den Kopf frei setzen. Deshalb
|
||||
wird auf die Peer-Adresse zurückgefallen, sobald der Kopf fehlt -- und
|
||||
beide Werte landen im Protokoll, damit so etwas auffällt. */
|
||||
function echteIp(req) {
|
||||
export function echteIp(req) {
|
||||
const cf = req.get("cf-connecting-ip");
|
||||
if (cf && cf.length <= 45) return cf.trim();
|
||||
return req.ip || "?";
|
||||
@@ -161,7 +184,7 @@ function gleich(a, b) {
|
||||
|
||||
const tokenHash = (t) => createHash("sha256").update(t).digest("hex");
|
||||
|
||||
function protokolliere(aktion, { personId = null, rolle = null, detail = null, ip = null } = {}) {
|
||||
export function protokolliere(aktion, { personId = null, rolle = null, detail = null, ip = null } = {}) {
|
||||
try {
|
||||
db().prepare(
|
||||
"INSERT INTO protokoll (zeitpunkt, person_id, rolle, aktion, detail, ip) VALUES (?,?,?,?,?,?)"
|
||||
@@ -224,7 +247,7 @@ export const workspaceRouter = express.Router();
|
||||
|
||||
/* Schutz der angemeldeten Seiten. Serverseitig, nicht nur im Browser --
|
||||
sonst könnte man die Seite einfach direkt aufrufen. */
|
||||
const GESCHUETZT = ["/workspace/start.html"];
|
||||
const GESCHUETZT = ["/workspace/start.html", "/workspace/aufgaben.html"];
|
||||
|
||||
workspaceRouter.use((req, res, next) => {
|
||||
if (!GESCHUETZT.includes(req.path)) return next();
|
||||
|
||||
Reference in New Issue
Block a user