Publish an optional per-file upstream URL for large files
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 25/100
Research direction
Start with kolibri_content/base_models.py and kolibri_public/models.py, then trace File conversion in publish.py:714, mapping in mapper.py:225, and field selection in import_metadata_view.py:160. Check the vendored schema_versions.py and the infrastructure trigger's 512 MiB filter. Done means the nullable field flows through uploads, publishing, both API paths, and an idempotent resumable backfill without breaking older imports.
Written by the indexing model from the issue text.
Description
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
Studio records nothing about where a content file can be fetched from — Kolibri derives the URL from its own configuration, so every file is assumed to be served by Studio. Content files over 512 MiB are now mirrored to Cloudflare R2 and served from a different host. Store an upstream_url on File, set it for files above that threshold, and carry it into published channels and the public import-metadata API.
Complexity: Medium
Target branch: hotfixes
Context
kolibri_content/base_models.pyis a declared verbatim copy of Kolibri'sbase_models.py, andkolibri_public.models.LocalFileis a bare subclass of it. One field definition reaches both the published channel database and the public import-metadata API.publish.py:714is where a StudioFilebecomes akolibri_contentLocalFile, andmapper.py:225is where those rows reachkolibri_public.import_metadata_view.py:160validates the requestedschema_versionagainst the minimum and current versions, then selects fields from the current base model. A new field therefore appears in responses at every accepted version, not onlyVERSION_7.- Studio's vendored
CONTENT_SCHEMA_VERSIONisVERSION_6and tracks Kolibri's. The field arrives withVERSION_7. - Objects are copied into R2 by a GCS finalize trigger that filters at 512 MiB, so a
Filegiven anupstream_urlbelow that threshold would point at an object that was never copied. - Existing objects over the threshold are copied into R2 by a one-off infrastructure backfill, which has to have run before any existing
Filerow is given a URL.
The Change
Fileshould carry anupstream_urlrecording where the file can be fetched from.upstream_urlshould be a plain URL with no R2-specific meaning, so it can later hold a link to a host Studio never writes to.- The vendored
kolibri_contentschema should gain the field atVERSION_7, matching Kolibri's definition rather than diverging from it. - The field should reach both a published channel database and the public import-metadata API, since Kolibri imports through either.
- Existing rows should be populated without re-uploading or republishing, because the affected files are the largest ones we hold.
- The threshold should be a named constant with its coupling to the infrastructure trigger recorded, since a mismatch produces URLs pointing at objects that were never copied.
Out of Scope
- Adding the field to Kolibri's
base_models.pyand generating theVERSION_7schema. That lands in Kolibri first, and this issue vendors the result. - Copying objects into R2, and the one-off backfill of objects already over the threshold.
- Changing how files are uploaded to Studio — resumable uploads and the
file_sizewidening are learningequality/kolibri-ecosystem#53. - Populating
upstream_urlwith anything other than the R2 URL. Hosts Studio never writes to come later.
Acceptance Criteria
-
File.upstream_urlis nullable, and is set at upload for files above the mirroring threshold. - A file at or below the threshold is uploaded with
upstream_urlunset. -
LocalFile.upstream_urlis present in the vendoredVERSION_7schema in bothkolibri_contentandkolibri_public. - Publishing a channel writes each file's
upstream_urlinto the channel database. - A client requesting
schema_version=6from the import-metadata API still imports successfully with the field present in the response. - Existing
Filerows above the threshold haveupstream_urlpopulated by a backfill that is idempotent and resumable. - The backfill leaves rows at or below the threshold unchanged.
- The threshold constant matches the size filter on the infrastructure copy trigger.
- A channel published before this change still imports into Kolibri without error.
AI usage
I used Claude (Opus 5, via le-skills:writing-github-issues) to work through the design and draft this issue section by section. The decision to record where a file lives as data on the file, rather than making it an infrastructure concern, was mine, as was the field name. I had Claude read the publish and import-metadata paths to ground the constraints, and edited the drafts where they over-specified or got details wrong.
- Dominant language
- Python
- Stars
- 191
- Forks
- 308
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 54
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from learningequality/studio
-
DEV: frontend P3 - low
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
learningequality/studio#5868 ·
Maintainers usually reply within 1 day
-
TAG: tech update / debt
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
learningequality/studio#2245 ·
Maintainers usually reply within 1 day
-
bug DEV: backend
Difficulty 3/5 Half a day Newbie friendliness 35/100
learningequality/studio#6294 ·
Maintainers usually reply within 1 day
-
DEV: frontend
Difficulty 2/5 1-3 hours Newbie friendliness 28/100
learningequality/studio#6292 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 55/100
learningequality/studio#6290 ·
Maintainers usually reply within 1 day
All issues in learningequality/studio
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
modelcontextprotocol/python-sdk#3648 ·
Maintainers usually reply within 1 day
-
docs good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
VenetoStato/giorgio#6 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 70/100
EclipseFdn/open-vsx.org#13831 ·
Maintainers usually reply within 1 day
-
feature request
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 2 days