|
9 | 9 | from fastapi.concurrency import run_in_threadpool |
10 | 10 | from fastapi.middleware.cors import CORSMiddleware |
11 | 11 | from fastapi.responses import FileResponse, JSONResponse |
12 | | -from fastapi.templating import Jinja2Templates |
13 | 12 |
|
14 | 13 | from backend.app import version |
15 | 14 |
|
|
22 | 21 |
|
23 | 22 |
|
24 | 23 | def create_app() -> FastAPI: |
| 24 | + from .embed_preview import ( |
| 25 | + get_token, |
| 26 | + is_embed_bot, |
| 27 | + render_embed_preview, |
| 28 | + ) |
| 29 | + |
25 | 30 | logging.basicConfig(level=logging.INFO, format="%(levelname)s [%(name)s] %(message)s") |
26 | 31 |
|
27 | 32 | Path(settings.storage_path).mkdir(parents=True, exist_ok=True) |
28 | 33 | Path(settings.config_path).mkdir(parents=True, exist_ok=True) |
29 | 34 |
|
30 | | - templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates")) |
31 | | - |
32 | 35 | @asynccontextmanager |
33 | 36 | async def lifespan(app: FastAPI): |
34 | 37 | """ |
@@ -151,94 +154,41 @@ def app_version() -> dict[str, str]: |
151 | 154 |
|
152 | 155 | frontend_dir: Path = Path(settings.frontend_export_path).resolve() |
153 | 156 |
|
| 157 | + def serve_static(): |
| 158 | + if frontend_dir.exists(): |
| 159 | + index_file = frontend_dir / "index.html" |
| 160 | + if index_file.exists(): |
| 161 | + return FileResponse(index_file, status_code=status.HTTP_200_OK) |
| 162 | + |
| 163 | + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) |
| 164 | + |
| 165 | + @app.get("/e/{token}", name="token_embed") |
| 166 | + @app.get("/e/{token}/") |
| 167 | + async def token_embed(request: Request, token: str): |
| 168 | + """Render a static embed preview page.""" |
| 169 | + if not settings.allow_public_downloads: |
| 170 | + return serve_static() |
| 171 | + |
| 172 | + from backend.app.db import get_db |
| 173 | + |
| 174 | + async for db in get_db(): |
| 175 | + if not (token_row := await get_token(db, token)): |
| 176 | + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Token not found") |
| 177 | + |
| 178 | + return await render_embed_preview(request, db, token_row, user=True) |
| 179 | + |
154 | 180 | @app.get("/f/{token}", name="share_page") |
155 | 181 | @app.get("/f/{token}/") |
156 | 182 | async def share_page(token: str, request: Request, user_agent: Annotated[str | None, Header()] = None): |
157 | 183 | """Handle /f/{token} with bot detection for embed preview.""" |
158 | | - from sqlalchemy import select |
159 | | - |
160 | | - from backend.app import models, utils |
161 | 184 | from backend.app.db import get_db |
162 | 185 |
|
163 | | - user_agent_lower: str = (user_agent or "").lower() |
164 | | - is_bot = any(bot in user_agent_lower for bot in ["discordbot", "twitterbot", "slackbot", "facebookexternalhit", "whatsapp"]) |
165 | | - |
166 | | - if is_bot and settings.allow_public_downloads: |
| 186 | + if is_embed_bot(user_agent) and settings.allow_public_downloads: |
167 | 187 | async for db in get_db(): |
168 | | - stmt = select(models.UploadToken).where((models.UploadToken.token == token) | (models.UploadToken.download_token == token)) |
169 | | - result = await db.execute(stmt) |
170 | | - token_row = result.scalar_one_or_none() |
171 | | - |
172 | | - if token_row: |
173 | | - uploads_stmt = ( |
174 | | - select(models.UploadRecord) |
175 | | - .where(models.UploadRecord.token_id == token_row.id, models.UploadRecord.status == "completed") |
176 | | - .order_by(models.UploadRecord.created_at.desc()) |
177 | | - ) |
178 | | - uploads_result = await db.execute(uploads_stmt) |
179 | | - uploads = uploads_result.scalars().all() |
180 | | - |
181 | | - media_files = [u for u in uploads if u.mimetype and utils.is_multimedia(u.mimetype)] |
182 | | - |
183 | | - if media_files: |
184 | | - first_media = media_files[0] |
185 | | - |
186 | | - is_video = first_media.mimetype.startswith("video/") |
187 | | - ffprobe_data = None |
188 | | - if first_media.meta_data and isinstance(first_media.meta_data, dict): |
189 | | - ffprobe_data = first_media.meta_data.get("ffprobe") |
190 | | - |
191 | | - video_metadata = utils.extract_video_metadata(ffprobe_data) |
192 | | - |
193 | | - other_files = [ |
194 | | - { |
195 | | - "name": u.filename or "Unknown", |
196 | | - "size": utils.format_file_size(u.size_bytes) if u.size_bytes else "Unknown", |
197 | | - } |
198 | | - for u in uploads |
199 | | - if u.public_id != first_media.public_id |
200 | | - ] |
201 | | - |
202 | | - media_url = str( |
203 | | - request.url_for("download_file", download_token=token_row.download_token, upload_id=first_media.public_id) |
204 | | - ) |
205 | | - share_url = str(request.url_for("share_page", token=token)) |
206 | | - |
207 | | - is_video = first_media.mimetype.startswith("video/") |
208 | | - is_audio = first_media.mimetype.startswith("audio/") |
209 | | - |
210 | | - context = { |
211 | | - "request": request, |
212 | | - "title": first_media.filename or "Shared Media", |
213 | | - "description": f"{len(uploads)} file(s) shared" if len(uploads) > 1 else "Shared file", |
214 | | - "og_type": "video.other" if is_video else "music.song", |
215 | | - "share_url": share_url, |
216 | | - "media_url": media_url, |
217 | | - "mime_type": first_media.mimetype, |
218 | | - "is_video": is_video, |
219 | | - "is_audio": is_audio, |
220 | | - "width": video_metadata.get("width"), |
221 | | - "height": video_metadata.get("height"), |
222 | | - "duration": video_metadata.get("duration"), |
223 | | - "duration_formatted": utils.format_duration(video_metadata["duration"]) |
224 | | - if video_metadata.get("duration") |
225 | | - else None, |
226 | | - "file_size": utils.format_file_size(first_media.size_bytes) if first_media.size_bytes else None, |
227 | | - "other_files": other_files, |
228 | | - } |
229 | | - |
230 | | - return templates.TemplateResponse( |
231 | | - request=request, |
232 | | - name="share_preview.html", |
233 | | - context=context, |
234 | | - status_code=status.HTTP_200_OK, |
235 | | - ) |
| 188 | + if token_row := await get_token(db, token): |
| 189 | + return await render_embed_preview(request, db, token_row) |
236 | 190 |
|
237 | | - if frontend_dir.exists(): |
238 | | - index_file = frontend_dir / "index.html" |
239 | | - if index_file.exists(): |
240 | | - return FileResponse(index_file, status_code=status.HTTP_200_OK) |
241 | | - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) |
| 191 | + return serve_static() |
242 | 192 |
|
243 | 193 | @app.get("/t/{token}", name="upload_page") |
244 | 194 | @app.get("/t/{token}/") |
|
0 commit comments