feat: S3 upload pipeline for APNG animated cards (#198) #210
No reviewers
Labels
No Label
ai-changes-requested
ai-failed
ai-merged
ai-pr-opened
ai-reviewed
ai-reviewing
ai-reviewing
ai-working
bug
enhancement
evolution
performance
phase-0
phase-1a
phase-1b
phase-1c
phase-1d
security
tech-debt
todo
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cal/paper-dynasty-database#210
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "issue/198-feat-s3-upload-pipeline-for-apng-animated-cards"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #198
Summary
Extends the S3 card storage pipeline to handle APNG animated cards.
app/services/card_storage.pybuild_apng_s3_key()— constructs S3 key with.apngextension using the same path pattern as PNG:cards/cardset-{csid:03d}/player-{pid}/v{variant}/{card_type}card.apngupload_apng_to_s3()— uploads raw APNG bytes withContentType=image/apngandCacheControl=public, max-age=86400(matching the animated endpoint's own cache header)upload_variant_apng()— background-safe end-to-end function: reads APNG from disk, builds key, uploads to S3. No DB update (noanimated_urlcolumn exists yet); logs success/failure, never raises.app/routers_v2/players.pyupload_variant_apngfromcard_storagebackground_tasks: BackgroundTasksparameter toget_animated_cardupload_variant_apngas a background task — skipped for?tier=dev preview overrides (tier is Noneguard)Other observations
animated_urlDB column exists onBattingCard/PitchingCard. The background task uploads to S3 but cannot store a URL back to the DB. A follow-up migration addinganimated_urlwould allow the bot/website to reference the CDN URL directly.AI Code Review
Files Reviewed
app/services/card_storage.py(modified — new functions:build_apng_s3_key,upload_apng_to_s3,upload_variant_apng)app/routers_v2/players.py(modified — new import +BackgroundTasksparam + background task trigger)Findings
Correctness
No issues found.
tier is Noneguard correctly gates S3 upload — dev preview renders (?tier=param) are excluded ✓finally: await page.close()block, so it only fires on a successful render (an exception fromgenerate_animated_cardpropagates pastfinallybefore theadd_taskcall) ✓os.path.isfile(cache_path) and tier is None) returns early before the background task is added — no redundant re-uploads on cache hits ✓BackgroundTasksalready imported atplayers.py:7✓upload_variant_apngis a top-level import (players.py:43) — follows CLAUDE.md lazy-import rule ✓except Exception+logger.exceptioninupload_variant_apngis correct for a background task (never raises) ✓CacheControl=public, max-age=86400on the S3 object matches the animated endpoint's ownCache-Controlheader — intentional and consistent ✓Security
No issues found.
apng_pathcomes fromapng_cache_path()(an internal utility), not from user-controlled request data ✓S3_BUCKETandS3_REGIONuse env var overrides ✓Style & Conventions
No issues found.
upload_variant_apngfollows the same structure asbackfill_variant_image_url— consistent pattern ✓Suggestions
animated_urlDB column is added,upload_variant_apngwill need to be extended to callbuild_s3_urland persist the URL (mirroring thebackfill_variant_image_urlpattern). Consider adding a# TODO: update animated_url once column existscomment insideupload_variant_apngto make the gap explicit for the follow-up PR.build_apng_s3_keydocstring describesvariantas1-4 = refractor tiers, omitting0 = base. Technically correct since the endpoint guards T3/T4 only, but the existingbuild_s3_keydocstring includes0 = basefor completeness. Cosmetic.Verdict: COMMENT
No correctness, security, or convention issues. Implementation is clean and follows the
backfill_variant_image_urlpattern faithfully. The self-review restriction prevents APPROVED — this is ready to merge.Automated review by Claude PR Reviewer
Approved via pd-pr
Approved via pd-pr