/api/files is the library's home: list a user's done files (by folder, searched, sorted, or the ones shared with them), rename / move / keep / delete a file, make, rename and delete own folders (a deleted folder's files go back to their source folders), and set how long any folder keeps a file — done files shift by the difference, never closer than the GC's warning window. An upload can name its folder (an own folder or Uploads), checked before the body is read and again before the file lands; it is hashed while it streams, and any file can be hashed on first ask. The download says the hash (Repr-Digest) once it is known; a new file at an asset's path forgets it. Uploads, deletes, keeps, moves and folder changes are audited (ids only). The fileless "expired" rows of the old retention are deleted by migration 0103: an expired file now leaves the library. The shared route helpers moved to routes/files.py; the old /api/downloads library routes delegate to the new ones until the client moves over.
33 lines
1.2 KiB
Python
33 lines
1.2 KiB
Python
"""The Files library: a file's SHA-256, and the expired rows retired
|
|
|
|
Revision ID: 0103_files_sha256
|
|
Revises: 0102_file_folders
|
|
Create Date: 2026-10-11
|
|
|
|
`media_assets.sha256` holds the file's hash once it is known (an upload is hashed while it streams
|
|
in, any other file on the first ask), so a machine fetching a file can check what it got.
|
|
|
|
An expired file now leaves its user's library (`downloads.gc`). Before, an expired download stayed
|
|
as a fileless "expired · download again" row (a done job with no asset). Those rows hold nothing on
|
|
disk and the library no longer shows them, so they are deleted here rather than left behind
|
|
unseen; their shares and public links go with them (ON DELETE CASCADE), and those already served
|
|
nothing. The downgrade cannot bring them back.
|
|
"""
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "0103_files_sha256"
|
|
down_revision: str | None = "0102_file_folders"
|
|
branch_labels: str | None = None
|
|
depends_on: str | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.add_column("media_assets", sa.Column("sha256", sa.String(64)))
|
|
op.execute("DELETE FROM download_jobs WHERE status = 'done' AND asset_id IS NULL")
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_column("media_assets", "sha256")
|