Workspace: hellere Kategoriekacheln + Dateien gezielt an mehrere Personen
ZWEI WUENSCHE VON FILIPE.
1) KACHELN BESSER SICHTBAR
Erster Versuch war zu zaghaft: die Flaeche ging nur von RGB(17,24,37)
auf (27,36,51), der sichtbare Gewinn kam fast nur vom staerkeren Rand.
Nachgemessen an echten Bildpunkten -- Seite liegt bei RGB(5,7,13).
Jetzt RGB(38,49,67), dazu kraeftigerer Rand und hellerer Symbolring.
Der Beschreibungstext stand auf --text-still und lag damit bei 3,6:1 auf
der neuen Flaeche -- zu blass fuer laufenden Text. Jetzt --text-leise,
gemessen 6,2:1.
Leere Kategorien waren mit opacity .55 fast unlesbar. Jetzt .82 -- sie
sollen erkennbar bleiben, nur zurueckhaltender.
2) DATEIEN AN MEHRERE PERSONEN GEZIELT FREIGEBEN
Bisher hatte eine Datei genau EINEN Bereich (creator_id). Damit liess
sie sich nicht zweien geben, ohne sie zweimal hochzuladen.
Neue Tabelle datei_personen (datei_id + person_id). creator_id bleibt
und behaelt seine Bedeutung: Es sagt, zu wessen BEREICH eine Datei
gehoert -- die neue Tabelle sagt, WER sie sehen darf. Zwei verschiedene
Fragen, deshalb zwei Felder.
Auswahl als einzelne Schalter, nicht als <select multiple>: Dort
verliert man mit einem Fehlklick die ganze Auswahl, und auf dem Handy
ist sie kaum bedienbar. Jeder Name ist ein Schalter, der sichtbar an
oder aus ist, eingefaerbt nach Rolle -- man sieht auf einen Blick, ob
eine Datei an Creator, Scouts oder beide geht.
Wer wen auswaehlen darf:
- DogFather jeden aktiven Menschen ausser sich selbst
- ein Scout NUR die Creator, die er betreut -- sonst koennte er sich
ueber eine Freigabe Zugang zu fremden Bereichen verschaffen
- ein Creator gar niemanden
Jede Id wird beim Speichern erneut gegen die erlaubte Auswahl geprueft.
Geprueft: Sam schickt die Ids 3, 6 und 7 mit (alles Creator, die er
NICHT betreut) -- die Datei landet bei niemandem. Kein Fehler, kein
Zugang: die Ids fallen still durch das Raster.
Weiter geprueft:
- Chef sieht alle drei Testdateien, Luna nur ihre, Sam nur seine,
Patrick keine
- Creator bekommt 403 beim Aendern von Freigaben
- Freigaben sind an jeder Datei sichtbar ("Sichtbar fuer ..."), auch
fuer die, die sie nicht aendern duerfen -- niemand soll raten muessen
- Die Auswahl wird nach dem Hochladen geleert, sonst bekaeme die
naechste Datei stillschweigend dasselbe Publikum
This commit is contained in:
@@ -29,7 +29,7 @@ import { randomBytes } from "node:crypto";
|
||||
import { join, extname, basename } from "node:path";
|
||||
import { mkdirSync, createReadStream, writeFileSync, unlinkSync, statSync } from "node:fs";
|
||||
import {
|
||||
db, protokolliere, echteIp, sitzungLesen, DATEN_ORDNER, betreutWo, darfCreator,
|
||||
db, protokolliere, echteIp, sitzungLesen, DATEN_ORDNER, betreutWo, darfCreator, betreuteIds,
|
||||
} from "./workspace.js";
|
||||
|
||||
export const dateienRouter = express.Router();
|
||||
@@ -69,13 +69,71 @@ dateienRouter.use("/workspace/api/dateien", angemeldet);
|
||||
vergisst. */
|
||||
export function sichtbar(person) {
|
||||
if (person.rolle === "admin") return { wo: "1=1", werte: [] };
|
||||
|
||||
/* Ausdrueckliche Freigabe -- gilt fuer jede Rolle gleich. Wer in der
|
||||
Liste steht, sieht die Datei, egal ob Creator oder Scout. */
|
||||
const freigabe = "EXISTS (SELECT 1 FROM datei_personen dp WHERE dp.datei_id = d.id AND dp.person_id = ?)";
|
||||
|
||||
if (person.rolle === "creator") {
|
||||
return { wo: "(d.creator_id = ? OR d.hochgeladen_von = ?)", werte: [person.id, person.id] };
|
||||
return {
|
||||
wo: `(d.creator_id = ? OR d.hochgeladen_von = ? OR ${freigabe})`,
|
||||
werte: [person.id, person.id, person.id],
|
||||
};
|
||||
}
|
||||
const b = betreutWo(person, "d.creator_id");
|
||||
return b
|
||||
? { wo: `(d.hochgeladen_von = ? OR ${b.wo})`, werte: [person.id, ...b.werte] }
|
||||
: { wo: "d.hochgeladen_von = ?", werte: [person.id] };
|
||||
? { wo: `(d.hochgeladen_von = ? OR ${freigabe} OR ${b.wo})`,
|
||||
werte: [person.id, person.id, ...b.werte] }
|
||||
: { wo: `(d.hochgeladen_von = ? OR ${freigabe})`, werte: [person.id, person.id] };
|
||||
}
|
||||
|
||||
/* Wem eine Datei freigegeben ist -- fuer die Anzeige. */
|
||||
export function freigabenZu(ids) {
|
||||
if (!ids.length) return new Map();
|
||||
const platz = ids.map(() => "?").join(",");
|
||||
const karte = new Map();
|
||||
for (const z of db().prepare(`
|
||||
SELECT dp.datei_id, p.id, p.name, p.rolle
|
||||
FROM datei_personen dp JOIN personen p ON p.id = dp.person_id
|
||||
WHERE dp.datei_id IN (${platz})
|
||||
ORDER BY p.rolle, p.name`).all(...ids)) {
|
||||
if (!karte.has(z.datei_id)) karte.set(z.datei_id, []);
|
||||
karte.get(z.datei_id).push({ id: z.id, name: z.name, rolle: z.rolle });
|
||||
}
|
||||
return karte;
|
||||
}
|
||||
|
||||
/* Wen darf diese Person ueberhaupt auswaehlen? DogFather jeden aktiven
|
||||
Menschen ausser sich selbst, ein Scout nur die Creator, die er
|
||||
betreut -- sonst koennte er sich ueber eine Freigabe Zugang zu
|
||||
fremden Bereichen verschaffen. */
|
||||
export function moeglicheEmpfaenger(person) {
|
||||
if (person.rolle === "admin") {
|
||||
return db().prepare(
|
||||
"SELECT id, name, rolle FROM personen WHERE aktiv = 1 AND id <> ? ORDER BY rolle, name")
|
||||
.all(person.id);
|
||||
}
|
||||
if (person.rolle === "scout") {
|
||||
const ids = betreuteIds(person);
|
||||
if (!ids.length) return [];
|
||||
return db().prepare(
|
||||
`SELECT id, name, rolle FROM personen WHERE aktiv = 1 AND id IN (${ids.map(() => "?").join(",")})
|
||||
ORDER BY name`).all(...ids);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/* Freigaben setzen. Prueft jede Id gegen die erlaubte Auswahl -- ein
|
||||
geschickter Aufruf soll keine Person erreichen, die im Formular gar
|
||||
nicht zur Wahl stand. */
|
||||
export function freigabenSetzen(dateiId, ids, person) {
|
||||
const erlaubt = new Set(moeglicheEmpfaenger(person).map((p) => p.id));
|
||||
const sauber = [...new Set(ids.map(Number).filter((n) => Number.isInteger(n) && erlaubt.has(n)))];
|
||||
const d = db();
|
||||
d.prepare("DELETE FROM datei_personen WHERE datei_id = ?").run(dateiId);
|
||||
const einf = d.prepare("INSERT OR IGNORE INTO datei_personen (datei_id, person_id) VALUES (?,?)");
|
||||
for (const pid of sauber) einf.run(dateiId, pid);
|
||||
return sauber.length;
|
||||
}
|
||||
|
||||
const SPALTEN = `
|
||||
@@ -93,11 +151,16 @@ const VERBUND = `
|
||||
dateienRouter.get("/workspace/api/dateien", (req, res) => {
|
||||
try {
|
||||
const { wo, werte } = sichtbar(req.person);
|
||||
res.json({
|
||||
dateien: db().prepare(`
|
||||
const liste = db().prepare(`
|
||||
SELECT ${SPALTEN} ${VERBUND}
|
||||
WHERE ${wo}
|
||||
ORDER BY d.erstellt DESC`).all(...werte),
|
||||
ORDER BY d.erstellt DESC`).all(...werte);
|
||||
const karte = freigabenZu(liste.map((d) => d.id));
|
||||
for (const d of liste) d.freigaben = karte.get(d.id) || [];
|
||||
res.json({
|
||||
dateien: liste,
|
||||
empfaenger: moeglicheEmpfaenger(req.person),
|
||||
darf_freigeben: req.person.rolle !== "creator",
|
||||
max_bytes: MAX_BYTES,
|
||||
});
|
||||
} catch (fehler) {
|
||||
@@ -195,9 +258,19 @@ dateienRouter.post("/workspace/api/dateien",
|
||||
(req.get("content-type") || "application/octet-stream").slice(0, 100),
|
||||
notiz, creatorId, req.person.id, jetzt());
|
||||
|
||||
/* Ausdrueckliche Freigaben. Der Kopf traegt eine Liste von Ids --
|
||||
geprueft wird jede einzelne gegen die erlaubte Auswahl. */
|
||||
let anzahl = 0;
|
||||
const roh = req.get("x-personen");
|
||||
if (roh) {
|
||||
anzahl = freigabenSetzen(Number(lastInsertRowid),
|
||||
String(roh).split(",").map((x) => x.trim()).filter(Boolean), req.person);
|
||||
}
|
||||
|
||||
protokolliere("datei_hochgeladen", {
|
||||
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
|
||||
detail: `#${lastInsertRowid} ${name} (${Math.round(req.body.length / 1024)} KB)`.slice(0, 120),
|
||||
detail: `#${lastInsertRowid} ${name} (${Math.round(req.body.length / 1024)} KB)`
|
||||
+ (anzahl ? `, freigegeben fuer ${anzahl}` : "").slice(0, 40),
|
||||
});
|
||||
res.status(201).json({ id: Number(lastInsertRowid) });
|
||||
} catch (fehler) {
|
||||
@@ -255,6 +328,24 @@ dateienRouter.patch("/workspace/api/dateien/:id", gleicheHerkunft, (req, res) =>
|
||||
const datei = db().prepare(`SELECT d.* ${VERBUND} WHERE ${wo} AND d.id = ?`).get(...werte, id);
|
||||
if (!datei) return res.status(404).json({ fehler: "nicht_gefunden" });
|
||||
|
||||
/* Freigaben aendern. Steht vor den Feldern, weil es eine eigene
|
||||
Tabelle ist und nicht in das UPDATE unten passt. Ein Creator darf
|
||||
das nie -- er wuerde sich sonst selbst Publikum verschaffen. */
|
||||
if (req.body?.freigaben !== undefined) {
|
||||
if (req.person.rolle === "creator") {
|
||||
return res.status(403).json({ fehler: "Freigeben darf nur DogFather oder ein Scout." });
|
||||
}
|
||||
if (!Array.isArray(req.body.freigaben)) {
|
||||
return res.status(400).json({ fehler: "Ungültige Auswahl." });
|
||||
}
|
||||
const n = freigabenSetzen(id, req.body.freigaben, req.person);
|
||||
protokolliere("datei_freigaben", {
|
||||
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
|
||||
detail: `#${id} an ${n} Person(en)`.slice(0, 120),
|
||||
});
|
||||
if (Object.keys(req.body).length === 1) return res.json({ ok: true });
|
||||
}
|
||||
|
||||
const setz = [];
|
||||
const daten = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user