dojo: Plenty Cloud Tag-Manipulation + Silent-Deny-Diagnose

DOJO.md:
- #22 (klassische variation_tags-Endpoints) ergänzt um Cloud-Update-
  Hinweis: in Plenty Cloud gesperrt, Verweis auf #34
- #33 NEU: Silent-Deny erkennen via Content-Type-Check (200+text/html
  als Erfolg melden ist die häufigste Lügen-Quelle bei Plenty-Schreib-
  Operationen)
- #34 NEU: Variation-Tag-Manipulation in Plenty Cloud — Lese-Pfade,
  add-only PUT auf /rest/pim/variations für Hinzufügen, Bulk-DELETE
  /rest/pim/variations/tags für Entfernen; Plenty-Rollen-System hat
  keine Tag-Edit-Permission

ANTI-PATTERNS.md:
- #13 (Tag-Operationen idempotent) ergänzt um Cloud-Update-Hinweis
- #19 NEU: Klassische /variation_tags-Endpoints in Plenty Cloud nutzen
  (Silent-Deny-Pattern, Verweis auf DOJO #34 als Fix)
This commit is contained in:
Liborius
2026-05-01 11:54:37 +00:00
parent b4604877f3
commit 30f45a5889
2 changed files with 121 additions and 0 deletions
+32
View File
@@ -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
```
---
+89
View File
@@ -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": <vid>, "tagId": <tid> }, ...]
← { "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.
---