Browse Source

CLAUDE.md: fotoboek-endpoint mijn_fotos + de valkuilen eromheen

Drupal-kant van "foto's kiezen uit het Drupal-fotoboek" is af en getest op
devbob en productie. Vastgelegd omdat een volgende sessie hier anders lang
op zoekt:
- mijn_fotos/index + /verwijder, en dat create alleen nog eigen fids pakt
- filteren op uid, met public://go_out/ (de importer) uitgesloten
- IMCE claimt elk bestand permanent in file_usage -> alleen type=node telt
- file_delete() geeft bij weigering een array terug die als TRUE evalueert
- node_delete() neemt het bestand mee zodra de laatste node-usage weg is

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 7 hours ago
parent
commit
8c73621
1 changed files with 41 additions and 0 deletions
  1. 41 0
      CLAUDE.md

+ 41 - 0
CLAUDE.md

@@ -5051,6 +5051,47 @@ Let bij grote foto's op: de app stuurt base64 in een JSON-body (5 MB foto ≈
 7 MB POST), dus `post_max_size` moet daar boven zitten, en de mediakiezer in
 FlutterFlow kan met *Max Width/Height* vooraf verkleinen.
 
+**Het "fotoboek" van een gebruiker = endpoint `mijn_fotos` (live sinds
+2026-09-22 op devbob + productie).** `/nl/flutterdrup/mijn_fotos.json`
+(sessie verplicht, `page`/`limit`/`zoek`) geeft de eigen afbeeldingen uit
+`file_managed`, nieuwste eerst, met `fid`, `bestandsnaam`, `url`, `thumb`
+(image style `square_thumbnail`), afmetingen, `bytes`, `datum` en
+`in_gebruik`. Verwijderen via `POST mijn_fotos/verwijder.json` met
+`{"fid": N}`. Code: `_custom_mijn_fotos_service()` /
+`_custom_mijn_fotos_verwijder()` in `custom.evenementen_aanmaken.inc`.
+Een gekozen `fid` gaat rechtstreeks als `logo_fid`/`fotos_fids` naar
+`evenementen/create` of `stadsactiviteiten/create`; die accepteren sinds
+dezelfde datum **alleen fids van de ingelogde gebruiker** (403). Vijf dingen
+die je moet weten vóór je hieraan sleutelt:
+- **Filteren gaat op `uid`, niet op map.** De mappen lopen door elkaar:
+  `users/<uid>` (IMCE), `user/<uid>` (media-widget op het websiteformulier),
+  `pictures/`, `s3/`, `public://` zelf, `evenementen_uploads/` (de app).
+- ⚠️ **`public://go_out/` wordt UITGESLOTEN — dat is de importer.** Gemeten
+  2026-09-22: 39.980 van 40.000 importbestanden staan daar, en de importer
+  zet ze op de uid van de zaak zelf (bij bobhoreca 244 van de 254). Zonder
+  die uitsluiting staat een fotoboek vol importmateriaal. Bewust een zwarte
+  lijst op die ene map en géén witte lijst op de upload-mappen: mensen
+  uploaden ook rechtstreeks naar `public://` en naar `pictures/`.
+- ⚠️ **IMCE zet voor élk bestand in een gebruikersmap een eigen
+  `file_usage`-rij** (`module=imce, type=file, id=de fid zelf`) die daar
+  permanent blijft staan — 44.112 rijen site-breed. Gevolg als je dat niet
+  weet: `in_gebruik` staat bij een IMCE-gebruiker op **100%** van zijn
+  foto's en `file_delete()` weigert ze allemaal. Daarom telt alleen
+  `type = 'node'` als "in gebruik" (conditie in de JOIN), en ruimt de
+  verwijder-actie de imce-claim op vóór `file_delete()`. Verschil in de
+  praktijk: 0 → 6 van 9 verwijderbaar bij bobhoreca, 0 → 34 van 50 bij
+  uid 393.
+- ⚠️ **`file_delete($file)` geeft bij een weigering een GEVULDE ARRAY terug
+  die als TRUE evalueert** — staat letterlijk zo in het commentaar van
+  `includes/file.inc`. Een kale `if (file_delete($file))` meldt dus succes
+  terwijl het bestand blijft staan. Altijd `=== TRUE` testen.
+- **`node_delete()` neemt het bestand mee.** `file_field_delete_file()` roept
+  `file_delete()` aan zodra de laatste node-usage verdwijnt, dus een
+  zelf-geüploade foto verdwijnt óók uit het fotoboek als het bijbehorende
+  evenement wordt verwijderd. IMCE-bestanden zijn daartegen beschermd door
+  hun eigen claim, app-uploads in `evenementen_uploads/` niet. Core-gedrag,
+  bewust niet omzeild: het voorkomt weesbestanden.
+
 **"Woorden plakken aan elkaar" in een omschrijving zit in de OPGESLAGEN body,
 niet in Views of `_custom_clean_html()`.** Bevestigd 2026-09-15: de rauwe body
 van nid 222090 bevat al `<p>Onderdeel vanElectronic` — er is geen tag die een