Skip to content

Add media storage pipeline#3

Merged
Eoic merged 5 commits into
masterfrom
codex/media-storage-pipeline
Jul 12, 2026
Merged

Add media storage pipeline#3
Eoic merged 5 commits into
masterfrom
codex/media-storage-pipeline

Conversation

@Eoic

@Eoic Eoic commented Jun 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Add authenticated media asset storage backed by server disk
  • Add upload, download, delete, and usage endpoints with quota enforcement
  • Add synced book media references and validation for uploaded media IDs
  • Delete owned media assets when synced books are deleted

Client support

Verification

  • .venv/bin/ruff check papyrus tests
  • .venv/bin/pytest -q

@Eoic Eoic marked this pull request as ready for review July 12, 2026 12:53
Copilot AI review requested due to automatic review settings July 12, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a private, authenticated media-asset pipeline to Papyrus: media files are stored on server disk, tracked in the database, referenced from synced books, and cleaned up when media is replaced or a synced book is deleted.

Changes:

  • Introduces MediaAsset model + Alembic migration, plus file_media_id / cover_media_id fields on SyncBook.
  • Adds /v1/media upload/download/delete and /v1/media/usage quota endpoints, backed by a new papyrus.services.media service.
  • Extends PowerSync upload handling to validate media references and delete book-owned media files after a successful delete commit, with new regression tests for concurrency/quota and rollback behavior.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_models.py Adds a metadata-level assertion for the (book_id, kind) uniqueness constraint on MediaAsset.
tests/api/routes/test_sync.py Adds PowerSync coverage for media ID validation and post-commit file deletion behavior.
tests/api/routes/test_media.py New route/service-level coverage for upload/download/delete, quota enforcement, concurrency, and rollback semantics.
papyrus/services/sync.py Validates file_media_id / cover_media_id in PowerSync mutations and defers physical deletes until after commit.
papyrus/services/media.py Implements disk-backed upload, quota checks, replacement semantics, ownership checks, and physical deletion helpers.
papyrus/schemas/sync.py Allows file_media_id / cover_media_id in sync payload fields.
papyrus/schemas/media.py Adds response schemas for media assets and quota usage.
papyrus/models/sync.py Adds file_media_id / cover_media_id columns to the books table model.
papyrus/models/media.py New MediaAsset SQLAlchemy model and uniqueness constraint per book/kind.
papyrus/models/__init__.py Registers MediaAsset in the metadata exports for Alembic discovery.
papyrus/config.py Adds media_storage_root setting (default .media).
papyrus/api/routes/media.py New authenticated media routes (upload, usage, download, delete).
papyrus/api/routes/__init__.py Registers the new media router under /media.
alembic/versions/c3f8b2a9d1e4_add_media_assets.py Schema migration for media_assets table and book media ID columns.
.gitignore Ignores .media/ upload storage directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread papyrus/services/media.py
Comment on lines +161 to +168
asset = await session.get(MediaAsset, asset_id)
if asset is None:
raise ValidationError(f"{field_name} was not found")
if asset.owner_user_id != user_id or asset.book_id != book_id:
raise ForbiddenError(f"{field_name} does not belong to this book")
if asset.kind != expected_kind:
raise ValidationError(f"{field_name} has the wrong media kind")
return asset.asset_id
Comment thread papyrus/services/media.py
Comment on lines +239 to +241
def _delete_path(path: Path) -> None:
if path.exists():
path.unlink()
Comment thread papyrus/services/media.py
Comment on lines +196 to +200
with temp_path.open("wb") as output:
while chunk := await file.read(UPLOAD_CHUNK_SIZE):
size_bytes += len(chunk)
if size_bytes > quota_remaining:
raise ConflictError("Storage quota exceeded")
@Eoic Eoic merged commit 8784931 into master Jul 12, 2026
1 check passed
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.

2 participants