fix(images): percent-encode original-image URLs ('#' in paths 404'd)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 18s
CI / backend-lint-and-test (push) Successful in 26s
CI / integration (push) Successful in 3m17s

An image whose on-disk path contains '#' (post folders like 'BLUE#59')
served its hash-named thumbnail fine but 404'd the original: the unencoded
'#' in image_url was parsed by the browser as a URL fragment, so
'#59/01_timelapse.jpg' never reached the /images route. Add a shared
image_url(path) helper that percent-encodes the path (safe='/') and route
the 3 raw builders (gallery detail + 2 in series) through it. Not a
cleanup-tool deletion — the file is on disk; only the URL was wrong.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 00:36:44 -04:00
parent 3e1303ea3c
commit 7c4b24c80d
3 changed files with 31 additions and 4 deletions
+3 -3
View File
@@ -19,7 +19,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from ..models import Artist, ImageRecord, Post, Tag, TagKind
from ..models.series_chapter import SeriesChapter
from ..models.series_page import SeriesPage
from .gallery_service import thumbnail_url
from .gallery_service import image_url, thumbnail_url
from .page_number_parser import parse_page_range
@@ -194,7 +194,7 @@ class SeriesService:
"thumbnail_url": thumbnail_url(
r.thumbnail_path, r.sha256, r.mime
),
"image_url": f"/images/{r.path.split('/images/', 1)[-1]}",
"image_url": image_url(r.path),
"source_post": (
{"id": r.primary_post_id, "title": r.post_title}
if r.primary_post_id is not None
@@ -282,7 +282,7 @@ class SeriesService:
"thumbnail_url": thumbnail_url(
r.thumbnail_path, r.sha256, r.mime
),
"image_url": f"/images/{r.path.split('/images/', 1)[-1]}",
"image_url": image_url(r.path),
}
)
# Group start page = the post's parsed starting page (stored on the