Files
simple-nanoshare/routes/api/download.py
T
daniel156161 42064de633
Build and Push Docker Container / build-and-push (push) Successful in 1m3s
feat(cli): add private NanoShare sync client
- Add a separate installable NanoShare CLI package under cli/.
- Implement browser login with local callback and refresh-token config.
- Add upload, list, download, sync, and watch CLI commands.
- Add private owner-only file download endpoint for CLI downloads.
- Add CLI auth endpoints for browser login and token refresh.
- Return file_id from ServiceLink uploads for reliable sync state.
- Exclude the CLI package from NanoShare container builds.
- Include tests for private downloads and sync state updates.
2026-07-27 15:52:34 +02:00

55 lines
1.9 KiB
Python

from __future__ import annotations
from my_modules.app.setup import LIMITER
from my_modules.decoratory.header import token_required
from quart_common.web.wide_event import add_wide_event_context
from quart import Blueprint, abort, current_app, send_file
api_download_bp = Blueprint('api_download', __name__)
def _last_modified_from_file(file_data: dict):
uploaded_at = file_data.get('uploaded_at')
if uploaded_at is None:
return None
try:
return int(uploaded_at) / 1000
except (TypeError, ValueError):
return None
@api_download_bp.get('/api/files/<path:file_id>/download')
@LIMITER.limit('60 per minute;1000 per hour;')
@token_required(['files', 'mesh'])
async def api_file_download(file_id: str, user: dict):
add_wide_event_context(nanoshare={'operation': 'api_file_download', 'file_id': file_id})
file_data = await current_app.convex.get_file_informations(file_id=file_id, user_id=user['sub'])
if not file_data:
add_wide_event_context(nanoshare={'operation_status': 'not_found'})
abort(404)
if file_data.get('expired'):
add_wide_event_context(nanoshare={'operation_status': 'expired'})
abort(410)
storage_id = file_data.get('db_image_url')
if not storage_id:
add_wide_event_context(nanoshare={'operation_status': 'missing_storage'})
abort(404)
content_type = file_data.get('content_type') or 'application/octet-stream'
file_name = file_data.get('file_name') or file_id
add_wide_event_context(nanoshare={'operation_status': 'served', 'content_type': content_type})
response = await send_file(
filename_or_io=await current_app.convex.get_from_storage(storage_id),
mimetype=content_type,
as_attachment=True,
attachment_filename=file_name,
conditional=True,
cache_timeout=0,
last_modified=_last_modified_from_file(file_data),
)
response.headers['Cache-Control'] = 'private, no-store'
return response