diff --git a/ANTI-PATTERNS.md b/ANTI-PATTERNS.md index e5c5d74..a956c51 100644 --- a/ANTI-PATTERNS.md +++ b/ANTI-PATTERNS.md @@ -198,6 +198,8 @@ if (res.status !== 200) throw new Error('Tag konnte nicht gesetzt werden'); **Fix:** Diese HTTP-Codes als Erfolg behandeln, nicht als Fehler. +> **⚠ Update 2026-05-01 — Plenty Cloud:** In Plenty Cloud kommen 409/404 für Tag-Operationen auf `/variation_tags` gar nicht erst — der Endpoint ist gesperrt und antwortet mit Silent-Deny (siehe #19). Das hier dokumentierte Idempotenz-Pattern bleibt für On-Premise-Installationen gültig. + --- ## 14. Neue Eigenschaften über den Merkmale-Endpoint setzen @@ -276,3 +278,33 @@ for (const item of items) { **Fix:** AIMD Rate Limiting: bei Erfolg schneller werden, bei 429 verdoppeln. Konvergiert automatisch zum Optimum. **Siehe DOJO.md #30** --- + +## 19. Klassische `/variation_tags`-Endpoints in Plenty Cloud nutzen + +```javascript +// FALSCH: in Plenty Cloud Silent-Deny — schweigend ignoriert +await client.delete( + `/rest/items/${itemId}/variations/${vid}/variation_tags/${tagId}`, +); +// → 200 OK + Content-Type: text/html +// → Tag bleibt aber an der Variation hängen +``` + +**Problem:** Plenty Cloud hat die klassischen Tag-Manipulations-Endpoints für REST-User komplett gesperrt. Plenty antwortet mit der HTML-Login-Page (`x-location: /index.php`), nicht mit 401/403. Naive Clients erkennen das als Erfolg und melden falsch — der Tag bleibt in der Realität aber dran. + +Plenty's Rollen-System bietet **keine eigene Tag-Edit-Permission** — der Endpoint ist nicht über User-Rollen freischaltbar. Stundenlange Permission-Recherche endet im Sackgassen-Modus. + +**Fix:** PIM-Bulk-Endpoint nutzen + Content-Type prüfen. **Siehe DOJO.md #34** + +```javascript +// RICHTIG +const resp = await client.delete('/rest/pim/variations/tags', { + data: [{ variationId, tagId }], + headers: { 'Content-Type': 'application/json' }, +}); +const ct = resp.headers?.['content-type']?.toLowerCase() ?? ''; +if (!ct.includes('application/json')) throw new Error('Silent-Deny'); +// + Verify-Read via /rest/pim/variations?with=tags.tag +``` + +--- diff --git a/DOJO.md b/DOJO.md index 3500fee..05a9a52 100644 --- a/DOJO.md +++ b/DOJO.md @@ -523,6 +523,8 @@ DELETE /rest/items/{itemId}/variations/{variationId}/variation_tags/{tagId} - `404 Not Found` beim Löschen = Tag existiert nicht → ignorieren - Beides sind erwartete Zustände, keine Fehler +> **⚠ Update 2026-05-01 — Plenty Cloud:** Diese klassischen Endpoints sind in Plenty Cloud für REST-User **gesperrt** (Silent-Deny mit 200 + text/html). Tag-Manipulation läuft dort über die PIM-API — siehe **#34**. Die hier dokumentierten Endpoints können in älteren On-Premise-Installationen weiterhin funktionieren, in der Cloud aber nicht. + --- # IX. Cycle Times & KPIs @@ -903,3 +905,90 @@ await client.put(`/rest/properties/relations/${existing.id}`, { **Entdeckt:** 2026-04-10. Atradius-LimitMgmt. Wert nach PUT immer noch der alte. --- + +## 33. Silent-Deny erkennen: Content-Type-Check bei Schreib-Operationen + +**Lektion:** Plenty antwortet bei nicht-erlaubten Routen mit `200 OK + Content-Type: text/html + Header x-location: /index.php` (statt 401/403). Naive Clients, die nur den HTTP-Status prüfen, melden Erfolg, obwohl Plenty nichts geschrieben hat. + +**Pattern:** +```javascript +const resp = await client.delete(url); +const ct = String(resp.headers?.['content-type'] || '').toLowerCase(); +if (!ct.includes('application/json')) { + throw new Error( + 'Plenty Silent-Deny: Antwort ohne JSON — vermutlich Permission-Problem ' + + 'oder Endpoint nicht für den API-User freigegeben.', + ); +} +``` + +**Anti-Pattern:** Nur `resp.status === 200` prüfen — Plenty lügt freundlich: + +```javascript +// FALSCH: 200 OK reicht nicht +await client.delete(url); +return true; // ← lügt: Plenty hat HTML geliefert, nichts geändert +``` + +**Diagnose-Tipps:** +- `x-location: /index.php` als zusätzliches Indiz, aber **nicht verlässlich** — manche legitime Endpoints senden den Header auch +- Body ist bei Silent-Deny meist leer +- Verify-Read nach jeder Schreib-Operation ist die robusteste Variante + +**Entdeckt:** 2026-05-01 bei Tag-DELETE-Operationen, die monatelang Erfolg meldeten ohne Tags zu entfernen. Erst Content-Type-Check + Verify-Read deckte den Lügenmodus auf. + +--- + +## 34. Variation-Tag-Manipulation in Plenty Cloud: PIM-API + Bulk-DELETE-Endpoint + +**Lektion:** Die klassischen Tag-Endpoints unter `/rest/items/{id}/variations/{vid}/variation_tags*` sind in Plenty Cloud für REST-User **komplett gesperrt** (Silent-Deny via #33). Der einzige funktionierende Schreib-Pfad führt über die PIM-API. + +**Lese-Pfade** (beide funktionieren): +``` +GET /rest/pim/variations?ids=in:{vid}&with=tags.tag +GET /rest/items/{itemId}/variations/{vid}?with=tags +``` + +Tag-Beziehungen kommen je nach Endpoint in unterschiedlichen Shapes — robuster Predicate: +```javascript +const matchesTag = (row, tagId) => { + const candidates = [row.tagId, row.id, row.tag?.id, row.tag?.tagId]; + return candidates.some(v => Number(v) === tagId); +}; +``` + +**Tag-Hinzufügen** — additive PUT auf der Variation (Plenty's eigenes Backend nutzt das): +``` +PUT /rest/pim/variations?with=...&returnAffectedVariations=false +Body: [{ id, base: {...}, tags: [...altList, {tagId: neuId}] }] +``` +Wichtig: das `tags`-Array beim PUT ist **add-only**. Tags entfernen via Weglassen oder leeres Array funktioniert NICHT — `_destroy: true`-Marker auch nicht. + +**Tag-Entfernen** — separater Bulk-Endpoint: +``` +DELETE /rest/pim/variations/tags +Content-Type: application/json +Body: [{ "variationId": , "tagId": }, ...] +← { "affectedRows": N } +``` + +**Pattern:** +```javascript +const resp = await client.delete('/rest/pim/variations/tags', { + data: items.map(i => ({ variationId: i.variationId, tagId: i.tagId })), + headers: { 'Content-Type': 'application/json' }, +}); +const ct = String(resp.headers?.['content-type'] || '').toLowerCase(); +if (!ct.includes('application/json')) throw new Error('Plenty Silent-Deny'); +// `affectedRows` kann sporadisch 0 sein, obwohl Tag wirklich weg ist +// → Verify-Read über GET ist verbindlich, nicht der affectedRows-Wert. +``` + +**Permission-Hintergrund:** +- Plenty's Rollen-System hat **keine Tag-Edit-Permission** (`tagShow.SHOW` ist die einzige Tag-Permission im ganzen Pool, kein `.EDIT/.DELETE`) +- Der PIM-Bulk-Endpoint ist über die generische API-Rolle freigeschaltet, nicht route-spezifisch konfigurierbar +- Wer eine Permission-UI sucht, sucht vergeblich — der Endpoint ist eine Plenty-interne Whitelist + +**Entdeckt:** 2026-05-01 nach mehrstündiger Recherche. Korrekter Endpoint gefunden via Browser-Network-Capture des Plenty-Backends; Plenty's eigenes UI nutzt genau diesen Bulk-Pfad. + +---