Skip to content

Fixed thumbnail 404 issue#374

Closed
tuj wants to merge 2 commits into
release/3.0.0from
feature/v3-bug-thumbnail-404
Closed

Fixed thumbnail 404 issue#374
tuj wants to merge 2 commits into
release/3.0.0from
feature/v3-bug-thumbnail-404

Conversation

@tuj

@tuj tuj commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Link to issue

#370

Link to ticket

https://leantime.itkdev.dk/#/tickets/showTicket/6871

Description

Added generation of thumbnail when it does not exist when mediaProvider is called.

Checklist

  • My code is covered by test cases.
  • My code passes our test (all our tests).
  • My code passes our static analysis suite.
  • My code passes our continuous integration process.

@tuj tuj added this to the 3.0.0 milestone Apr 10, 2026
@tuj tuj self-assigned this Apr 10, 2026
@tuj tuj added the bug Something isn't working label Apr 10, 2026
@tuj tuj linked an issue Apr 10, 2026 that may be closed by this pull request
@tuj
tuj requested a review from turegjorup April 13, 2026 08:57
@turegjorup

turegjorup commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Root cause investigation

The server-side pre-bake in this PR (src/State/MediaProvider.php:168-176) is a workaround, but worth documenting what actually causes the 404 so we can fix it properly upstream rather than papering over it here.

What's actually happening

Liip's /media/cache/resolve/{filter}/{path} endpoint is unreachable on the deployed nginx — the request never gets to PHP-FPM. Pre-fix MediaProvider::getBrowserPath() returns a /media/cache/resolve/… URL for any thumbnail that isn't already on disk, the browser fetches it, and nginx 404s without handing off to Symfony.

Evidence

On os2displayv3.itkdev.dk (running 3.0.0-beta27):

  • Source file exists and is readable: ls -la /app/public/media/itkdev/screenshot-2026-04-13-at-14-14-19-69e67dce8d323243920211.png-rw-r--r-- deploy:deploy
  • kernel.project_dir = /app, Liip data_root = /app/public — paths consistent
  • bin/console liip:imagine:cache:resolve … succeeds from the CLI (generates and stores the thumbnail)
  • But the matching HTTP request returns 404, and docker compose logs os2display (PHP-FPM access log) shows no corresponding GET /index.php entry for the resolve URL — only the /v2/media?page=1 request at the same timestamp

The actual cause — running nginx config

# Static files with caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

In nginx, regex locations (~*) beat prefix locations (location /). Every .png/.jpg/.gif/.svg URL — including /media/cache/resolve/thumbnail/…png — matches this block first. It has no try_files, so nginx serves the URL as a static file from root and, when the file doesn't exist, returns 404 without falling through to /index.php. Liip's controller is never invoked.

This also explains:

  • Cached thumbnails at /media/cache/thumbnail/… 200 — file exists, served statically
  • apples-….jfif 200 — .jfif not in the regex, falls through to location / and try_files
  • CLI resolve works — doesn't go through nginx

This config is not the one in infrastructure/itkdev/nginx/etc/confd/templates/default.conf.tmpl (which has no such regex). It's baked into ghcr.io/itk-dev/os2display-api-service-nginx from a different source.

Proper fix (in the nginx image repo)

location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
    try_files $uri /index.php$is_args$args;   # ← add this
}

Or carve Liip's endpoint out ahead of the regex:

location ^~ /media/cache/resolve/ {
    try_files $uri /index.php$is_args$args;
}

Concrete problems with the PHP "fix" in this PR

  • Every media GET now does a filesystem stat + possibly a synchronous resize in the request path. Slow, memory-heavy, and blocks the API response on I/O that should be out-of-band.
  • Collection endpoints will hammer this N times per page load.
  • Couples DTO serialization to image processing — fails loudly if the image binary is gone or the filter config changes.
  • The locked-in test (assertStringNotContainsString('/media/cache/resolve/')) cements the anti-pattern; future devs can't revert to the idiomatic approach without ripping the test.

Recommend fixing this at the nginx layer instead.

@tuj

tuj commented Apr 21, 2026

Copy link
Copy Markdown
Contributor Author

Closed. Will fix the issue in nginx config instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants