refactor: consolidate ontology download routes into a shared helper - #249
Open
alexskr wants to merge 2 commits into
Open
refactor: consolidate ontology download routes into a shared helper#249alexskr wants to merge 2 commits into
alexskr wants to merge 2 commits into
Conversation
The three download routes (ontology `/download`, submission `/download`, and `/download_diff`) each reimplemented format resolution, file streaming, and access/restriction checks inline. The copy-paste had drifted into bugs. Introduce `Sinatra::Helpers::DownloadHelper` with `enforce_download_access` and `send_submission_download`, and reduce each route to "resolve the submission, then delegate." This fixes several latent issues in one place: - Fix a NameError (undefined `latest_submission`) that made `submissions/:id/download?download_format=csv` return 500 for the latest submission — the one case CSV is supposed to work. The reference now correctly uses `submission`. - Replace nil-path 500s (`File.readable?(nil)` -> TypeError) on the ontology `/download` and `/download_diff` routes with a clean 404. - Unify access control to the strictest policy across all three routes: admins bypass `restrict_download`, and the `restricted?`/`accessible?` private-ontology ACL check now applies everywhere (previously only on the ontology route). - `diff` becomes just another download format, so `download_diff` reuses the shared streamer. - Clearer error when CSV is requested for a non-latest submission (whose CSV the archiver deletes) instead of "Invalid download format: csv". Also mark download responses `Cache-Control: no-store`. The global Rack::Cache (Redis entitystore) was heuristically caching large `send_file` ontology bodies: buffering the file into Redis on write and reading it back as a single multi-second Redis GET on hit (observed 64s of a 79s request in New Relic), defeating send_file streaming and bloating the HTTP Redis. Add a regression test exercising CSV download of both the latest submission (200) and a non-latest submission (400). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #249 +/- ##
===========================================
+ Coverage 78.30% 79.17% +0.87%
===========================================
Files 67 69 +2
Lines 3748 3876 +128
===========================================
+ Hits 2935 3069 +134
+ Misses 813 807 -6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Guards against silently reintroducing the Rack::Cache regression where large send_file ontology bodies were buffered into the Redis entitystore (confirmed in production: entitystore entries up to ~535 MB). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The three download routes — ontology
GET /:acronym/download, submissionGET /:acronym/submissions/:id/download, andGET /:acronym/submissions/:id/download_diff— each reimplemented format resolution, file streaming, and access/restriction checks inline. That copy-paste had drifted into several latent bugs. This introducesSinatra::Helpers::DownloadHelper(enforce_download_access+send_submission_download) and reduces each route to "resolve the submission, then delegate," fixing the bugs structurally in one place.What this fixes
submissions/:id/download?download_format=csvreferenced an undefinedlatest_submission(copy-paste from the ontology route) and returned 500 for the latest submission — the one case CSV is meant to work. Now usessubmission./downloadand/download_diffroutes calledFile.readable?(nil)(→TypeError→ 500) when a file path was unset. One guard now returns a clean 404.restrict_download, and therestricted?/accessible?private-ontology ACL check now applies to all three routes (previously only the ontology route).diffis now just a format, sodownload_diffreuses the shared streamer."Invalid download format: csv".Performance fix
New Relic showed a
submissions/:id/downloadtaking 79.6s, 64.0s of it a single Redis GET (80%). Cause: the globalRack::Cache(Redis entitystore) was heuristically caching largesend_fileontology bodies — buffering the file into Redis on write and reading it back as one multi-second RedisGETon hit, defeatingsend_filestreaming and bloating the HTTP Redis. Download responses are now markedCache-Control: no-store, so Rack::Cache skips them.Behavior change to review
Unifying access control means, for the submission and diff routes:
restrict_downloadbypass they lacked.restricted?/accessible?ACL check now applies — a non-admin outside a private ontology's ACL who could download those routes before may now get 403. Both existing ACL tests already expect this (blocked→403, allowed/admin→200).Tests
test_download_ontology_submission_csv— asserts latest-submission CSV → 200 (regression for the NameError) and non-latest CSV → 400.Out of scope (follow-ups)
GET /submissions/:id/diffstructured-JSON diff endpoint (parse_diff_report) — model support already exists in linked_data..csvon archive (contrast: the diff file is retained).X-Accel-Redirect) so Ruby/Redis never touch the bytes.🤖 Generated with Claude Code