fix(api): Dateien bereits veröffentlichter oder gesperrter Karten nicht wiederverwenden

Veröffentlichen lehnt mit 403 "Diese Bilder sind bereits veröffentlicht oder
gesperrt." ab, wenn eine Datei kein read("any") trägt (ausgeblendet) oder von
einer Galerie-Zeile des Autors schon als Bild oder Vorschau verwendet wird
(auch überkreuz, auch ausgeblendete Karten). Die Nutzungsprüfung liest die
Zeilen des Autors über den vorhandenen ownerId-Index seitenweise mit Cursor
(auf die Datei-ID-Spalten gibt es keinen Index) und läuft zusammen mit dem
Anlegen je Autor nacheinander. 92 Tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 14:27:29 +00:00
1 parent cd55d4c891
commit 7bbe0a9fe6
6 files changed
+235 -63

No files matched your search

+10 -2
View File
@@ -8,7 +8,7 @@
* GET|PATCH|DELETE .../rows/{id} PATCH {data?, permissions?}
* GET|PUT|DELETE /v1/storage/buckets/{b}/files/{f} PUT {permissions}
*
* Abfragen: `queries[]=<json>` mit den Methoden equal und limit. Zeilen werden gegen die Spaltenlängen der
* Abfragen: `queries[]=<json>` mit den Methoden equal, cursorAfter und limit. Zeilen werden gegen die Spaltenlängen der
* echten Tabellen geprüft (document_invalid_structure), damit zu lange Werte im Test auffallen.
* Dateien werden nach ID gesucht und liefern ihren echten `bucketId` zurück (der Bucket in der URL zählt nicht),
* so lässt sich der Fall „Datei liegt in einem anderen Bucket“ nachstellen.
@@ -270,10 +270,11 @@ function safeParse(text) {
}
}
/** Wendet equal/limit an; null bei unbekannter Abfrage. */
/** Wendet equal, cursorAfter und limit an (Reihenfolge = Anlegereihenfolge); null bei unbekannter Abfrage. */
function runQueries(rows, search) {
let result = rows;
let limit = 25;
let cursor = null;
for (const part of search.split('&').filter(Boolean)) {
const eq = part.indexOf('=');
const name = decodeURIComponent(part.slice(0, eq));
@@ -288,10 +289,17 @@ function runQueries(rows, search) {
result = result.filter((row) => query.values.includes(row[query.attribute]));
} else if (query.method === 'limit') {
limit = query.values[0];
} else if (query.method === 'cursorAfter') {
cursor = query.values[0];
} else {
return null;
}
}
if (cursor !== null) {
const index = result.findIndex((row) => row.$id === cursor);
if (index === -1) return null;
result = result.slice(index + 1);
}
return result.slice(0, limit);
}