Skip to content

Comments

Add file download endpoint for beacons#582

Open
oatkins8 wants to merge 2 commits intomainfrom
feature/file-download-endpoint
Open

Add file download endpoint for beacons#582
oatkins8 wants to merge 2 commits intomainfrom
feature/file-download-endpoint

Conversation

@oatkins8
Copy link
Contributor

@oatkins8 oatkins8 commented Feb 3, 2026

What Issue Does This PR Cover, If Any?

Resolves: #568

What Changed? And Why Did It Change?

Beacons need to download documents that are attached to their associated topics. Previously there was no endpoint to serve these files with proper authentication and authorization. This PR adds a files endpoint that allows beacons to download documents only from topics they have access to. The endpoint uses ActiveStorage streaming to support both full file downloads and partial content requests via Range headers, which is important for efficient delivery of large files.

The accessible_blobs method was added to the Beacon model to provide the authorization layer, ensuring beacons can only access documents from their own topics. The namespace was also standardised from beacons to beacon for consistency across the API.

How Has This Been Tested?

Comprehensive request specs cover authentication, authorization, full downloads, Range header support with single and multiple byte ranges, not found scenarios, and cross-beacon authorization checks. Model specs verify the accessible_blobs method correctly scopes files to the beacon's topics.

@oatkins8 oatkins8 changed the title Add authenticated file download endpoint for beacons [WIP] Add authenticated file download endpoint for beacons Feb 3, 2026
@oatkins8 oatkins8 marked this pull request as draft February 3, 2026 16:23
end

def accessible_blobs
ActiveStorage::Blob
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to use sql in this query? I know it is faster but it is less accessible to everyone. (Not saying don't use sql, asking the question)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I know this is in draft and not finished so ignore this ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good shout - just changed it to ActiveRecord 👍

@oatkins8 oatkins8 changed the title [WIP] Add authenticated file download endpoint for beacons Add authenticated file download endpoint for beacons Feb 4, 2026
@oatkins8 oatkins8 force-pushed the feature/file-download-endpoint branch 2 times, most recently from 8a9bcf5 to 5aa9416 Compare February 4, 2026 14:36
@oatkins8 oatkins8 changed the title Add authenticated file download endpoint for beacons Add file download endpoint for beacons Feb 4, 2026
@oatkins8 oatkins8 marked this pull request as ready for review February 4, 2026 14:37
Beacons need to scope file access to only documents attached
to their associated topics. The accessible_blobs method provides
this authorisation layer by joining through the attachments and
beacon_topics associations to ensure beacons cannot access files
from topics belonging to other beacons.
Beacons receive a manifest listing files they need, then download
each file individually via this endpoint. The controller validates
that the requesting beacon has access through its assigned topics
and supports resumable downloads via Range headers for reliability
on unstable connections.
@oatkins8 oatkins8 force-pushed the feature/file-download-endpoint branch from 5aa9416 to f8f7b33 Compare February 17, 2026 08:03
resources :tags, only: %i[index show]

namespace :beacons do
resource :manifest, only: :show
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we introduce manifests API here?


it "does not return blobs from other beacons' topics" do
other_blob_ids = other_topic.documents.map(&:blob_id)
expect(beacon.accessible_blobs.pluck(:id)).not_to include(*other_blob_ids)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this case is already covered by previous test

b
end

let(:raw_key) { beacon; @raw_key }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need beacon to be called first you can use: let!(:beacon) on line 8

headers: beacon_auth_headers(raw_key).merge("Range" => "bytes=0-1023")

expect(response.headers["Content-Range"]).to be_present
expect(response.headers["Content-Range"]).to match(/bytes 0-1023\/\d+/)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this assumption previous one becomes redundant

module V1
module Beacons
class FilesController < Beacons::BaseController
include ActiveStorage::Streaming
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if the way to do this (since we're in a controller) should more closely follow this section of the Rails guides

The reason being, I couldn't find #send_blob_byte_range_data in the official Rails API but it is present here in API Dock

Also, this page on ActiveStorage::Streaming references the ActionController::Live as an included module; the code is related.

I've never worked with "streaming bytes", etc, though!

Comment on lines +16 to +20
let(:topic) do
create(:topic, :with_documents, provider: provider, language: language).tap do |t|
beacon.topics << t
end
end
Copy link
Collaborator

@devjona devjona Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to hold this up now but this seems like an opportunity to add trait to our factories. Just a thought.

Specifically, the Beacon factory could have a :with_topics trait 👍

Copy link
Collaborator

@devjona devjona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid work! My main question is about the best approach to handle the "streaming" from the controller (left a comment with links).

Dmitry left a few comments as well.

I'm looking forward to seeing this merged; thanks!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File Download Endpoint

4 participants