fix(api,frontend): render files by name without fetching, previewable share links

This commit is contained in:
hh committed 2026-08-13 03:49:29 +02:00
1 parent ef739838a5
commit e833de245a
13 files changed
+348 -45

No files matched your search

+36 -8
View File
@@ -6,7 +6,12 @@ from dishka.integrations.fastapi import DishkaRoute, FromDishka
from fastapi import APIRouter, Query, Request
from fastapi.responses import FileResponse, PlainTextResponse
from utils.files import content_disposition, counts_as_download, is_inline_mime
from utils.files import (
content_disposition,
counts_as_download,
is_inline_mime,
resolve_mime,
)
from utils.read import shares
from utils.storage import ContentAddressedStorage
@@ -35,13 +40,13 @@ def _exhausted(row: asyncpg.Record) -> bool:
return limit is not None and row["download_count"] >= limit
@router.api_route("/f/{token}", methods=["GET", "HEAD"], response_model=None)
async def serve_shared_file(
async def _serve(
request: Request,
pool: FromDishka[asyncpg.Pool],
storage: FromDishka[ContentAddressedStorage],
pool: asyncpg.Pool,
storage: ContentAddressedStorage,
token: str,
dl: Annotated[bool, Query()] = False,
*,
dl: bool,
) -> FileResponse | PlainTextResponse:
row = await shares.share_by_token(pool, token)
if row is None:
@@ -64,8 +69,8 @@ async def serve_shared_file(
return PlainTextResponse(_GONE, status_code=410, headers=_NO_STORE)
await shares.record_hit(pool, row["id"], method, ip, agent, counted=counts)
mime = row["mime"] or "application/octet-stream"
attachment = dl or not is_inline_mime(row["mime"])
mime = resolve_mime(row["kind"], row["mime"], row["file_name"])
attachment = dl or not is_inline_mime(mime)
return FileResponse(
storage.url(row["storage_key"]),
media_type=mime,
@@ -76,3 +81,26 @@ async def serve_shared_file(
),
},
)
@router.api_route("/f/{token}", methods=["GET", "HEAD"], response_model=None)
async def serve_shared_file(
request: Request,
pool: FromDishka[asyncpg.Pool],
storage: FromDishka[ContentAddressedStorage],
token: str,
dl: Annotated[bool, Query()] = False,
) -> FileResponse | PlainTextResponse:
return await _serve(request, pool, storage, token, dl=dl)
@router.api_route("/f/{token}/{name}", methods=["GET", "HEAD"], response_model=None)
async def serve_shared_file_named(
request: Request,
pool: FromDishka[asyncpg.Pool],
storage: FromDishka[ContentAddressedStorage],
token: str,
name: str, # noqa: ARG001
dl: Annotated[bool, Query()] = False,
) -> FileResponse | PlainTextResponse:
return await _serve(request, pool, storage, token, dl=dl)
+3 -3
View File
@@ -6,7 +6,7 @@ from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import FileResponse
from utils.cache import DAY_HEADERS, IMMUTABLE_HEADERS
from utils.files import content_disposition, media_file_name
from utils.files import content_disposition, media_file_name, resolve_mime
from utils.read.media import (
get_media,
get_media_version,
@@ -63,7 +63,7 @@ async def serve_media_version(
)
return FileResponse(
storage.url(version.storage_key),
media_type=version.mime or "application/octet-stream",
media_type=resolve_mime(version.kind, version.mime),
headers=headers,
)
@@ -101,6 +101,6 @@ async def serve_media(
headers = _attachment(headers, media.file_name or f"media_{media_id}")
return FileResponse(
storage.url(media.storage_key),
media_type=media.mime or "application/octet-stream",
media_type=resolve_mime(media.kind, media.mime, media.file_name),
headers=headers,
)
+12 -5
View File
@@ -5,7 +5,12 @@ from dishka.integrations.fastapi import DishkaRoute, FromDishka
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
from utils.files import expiry_from_seconds, media_file_name, story_file_name
from utils.files import (
expiry_from_seconds,
media_file_name,
resolve_mime,
story_file_name,
)
from utils.read import chats, peers, shares
from utils.read.media import get_media, get_media_version
from utils.read.models import DEFAULT_LIMIT, FileShareHitView, FileShareView, Page
@@ -56,10 +61,11 @@ async def _media_subject(pool: asyncpg.Pool, media_id: int) -> Subject:
raise HTTPException(status_code=404, detail="media not found")
if not media.downloaded or media.storage_key is None:
raise HTTPException(status_code=409, detail=_NOT_STORED)
file_name = media.file_name or f"media_{media.id}"
return Subject(
storage_key=media.storage_key,
file_name=media.file_name or f"media_{media.id}",
mime=media.mime,
file_name=file_name,
mime=resolve_mime(media.kind, media.mime, file_name),
file_size=media.file_size,
title=await _chat_title(pool, media.account_id, media.chat_id),
chat_id=media.chat_id,
@@ -71,10 +77,11 @@ async def _version_subject(pool: asyncpg.Pool, version_id: int) -> Subject:
version = await get_media_version(pool, version_id)
if version is None:
raise HTTPException(status_code=404, detail="media version not found")
version_name = media_file_name(version.kind, version.mime, version_id)
return Subject(
storage_key=version.storage_key,
file_name=media_file_name(version.kind, version.mime, version_id),
mime=version.mime,
file_name=version_name,
mime=resolve_mime(version.kind, version.mime, version_name),
file_size=version.file_size,
title=None,
chat_id=None,