Termine-Outbox: notEqual mit genau einem Wert (Appwrite 1.8.1), nachrangige Mails kommen wieder dran

Auf der echten Appwrite 1.8.1 lehnt notEqual mit Liste die Abfrage ab (HTTP 400
general_query_invalid, "NotEqual queries require exactly one value"). Die Klasse normal der
Mail-Warteschlange schickte immer eine Liste, jeder Lauf warf dort, und die nachrangige Klasse
(G05, K20, K21) kam nie dran.

- appwriteAdmin: Query.notEqual nimmt genau einen Wert (eine Liste wirft einen TypeError, der
  keinerVon nennt); neu keinerVon(attribute, werte) = je Wert ein notEqual (Appwrite schneidet
  sie); dokumenteUrl() baut die Listen-URL ohne Netz, mit derselben Serialisierung wie die Anfrage.
- outbox: klassenFilter liefert fuer jede Klasse eine Liste von Abfragen (normal = sechs notEqual
  auf art), die Ausschlussliste ist keinerVon('featureId', ...). AUSSCHLUSS_MAX 90 -> 30 (der nginx
  antwortet in dieser Abfrageform ab 46 Funktions-IDs mit 414); der Kommentar "100 Werte je Abfrage"
  entfaellt.
- Nachbau (appwriteMock): notEqual mit Liste oder ohne Wert => 400 wie Appwrite; ein Dokument mit
  Feld null oder ohne Feld passt nicht zu notEqual (SQL-Semantik).
- Tests: Query-Helfer, keinerVon, dokumenteUrl, Nachbau (400, null), ein Lauf bedient alle drei
  Klassen, Ausschlussliste hoechstens 30 und URL unter 6000 Zeichen im schlimmsten Fall.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-05 14:47:29 +00:00
1 parent af63b69ec1
commit 2c7cd26532
5 files changed
+196 -39

No files matched your search

+17 -9
View File
@@ -1,5 +1,5 @@
import { config } from '../../config.js'
import { Query, countDocuments, createDocument, deleteDocument, getDocument, listAllDocuments, listDocuments, updateDocument } from '../appwriteAdmin.js'
import { Query, countDocuments, createDocument, deleteDocument, getDocument, keinerVon, listAllDocuments, listDocuments, updateDocument } from '../appwriteAdmin.js'
import { withLocks } from '../keyedLock.js'
import { sendGuestMail } from '../mailer.js'
import { renderMail } from '../mailTemplates.js'
@@ -372,16 +372,24 @@ async function speichereFehlschlag(doc, err, nowMs) {
const KLASSEN = ['vorrang', 'normal', 'nachrangig']
/** Hoechstens so viele Abfragen je Klasse und Lauf: die erste und bis zu vier weitere Runden mit neuer Ausschlussliste. */
const RUNDEN_MAX = 5
/** Hoechstens so viele Funktionen in der Ausschlussliste einer Abfrage (Appwrite erlaubt hoechstens 100 Werte je Abfrage). */
const AUSSCHLUSS_MAX = 90
/**
* Hoechstens so viele Funktionen in der Ausschlussliste einer Abfrage. Jede ist ein eigenes notEqual in der URL (Appwrite 1.8.1
* nimmt je notEqual nur einen Wert, M1). Gemessen am Produktionsserver (M4, 2026-10-05): in dieser Abfrageform antwortet der nginx
* ab 46 Funktions-IDs mit 36 Zeichen mit 414 (URL zu lang); 30 lassen Luft fuer laengere IDs. Eine blockierte Funktion ausserhalb
* der Liste wird gelesen und uebersprungen (verarbeiteKlasse), das kostet nur Platz in der Liste.
*/
export const AUSSCHLUSS_MAX = 30
/** Die Felder der Liste: genug fuer pruefePlatz und sendeEintrag, nie ein Mailtext. */
const LISTENFELDER = ['$id', 'featureId', 'art', 'naechsterVersuch']
/** Filter auf die Art der Mail: die Arten der Klasse (equal) bzw. alle anderen (notEqual, die Klasse normal). */
/**
* Filter auf die Art der Mail, immer eine Liste von Abfragen: die Arten der Klasse (equal mit Liste, das erlaubt Appwrite) bzw.
* alle anderen (die Klasse normal: je Art ein notEqual, Appwrite schneidet sie; art ist an jedem Eintrag gesetzt, einreihen prueft es).
*/
function klassenFilter(klasse) {
if (klasse === 'vorrang') return Query.equal('art', [...MAIL_VORRANG])
if (klasse === 'nachrangig') return Query.equal('art', [...MAIL_NACHRANGIG])
return Query.notEqual('art', [...MAIL_VORRANG, ...MAIL_NACHRANGIG])
if (klasse === 'vorrang') return [Query.equal('art', [...MAIL_VORRANG])]
if (klasse === 'nachrangig') return [Query.equal('art', [...MAIL_NACHRANGIG])]
return keinerVon('art', [...MAIL_VORRANG, ...MAIL_NACHRANGIG])
}
/** Eine Runde der Klasse: faellige wartende Eintraege, fuer die Funktionen ohne Kontingent nicht mitgelesen werden. */
@@ -389,8 +397,8 @@ function listenAbfrage(klasse, nowMs, blockiert) {
return [
Query.equal('status', 'wartet'),
Query.lessThanEqual('naechsterVersuch', iso(nowMs)),
klassenFilter(klasse),
...(blockiert.size > 0 ? [Query.notEqual('featureId', [...blockiert].slice(0, AUSSCHLUSS_MAX))] : []),
...klassenFilter(klasse),
...keinerVon('featureId', [...blockiert].slice(0, AUSSCHLUSS_MAX)),
Query.orderAsc('naechsterVersuch'),
Query.limit(config.termine.mailListe),
Query.select(LISTENFELDER),