diff --git a/backend/app/routers/generate.py b/backend/app/routers/generate.py index 284689b..f29bfc3 100644 --- a/backend/app/routers/generate.py +++ b/backend/app/routers/generate.py @@ -186,6 +186,56 @@ async def list_generated_images( ] +@router.get("/images/{image_id}") +async def get_generated_image( + image_id: str, + current_user: dict = Depends(get_current_user), +) -> dict: + """Return details for a single generated image.""" + user_id = current_user.get("id") or current_user.get("sub") + conn = get_conn() + row = conn.execute( + """SELECT id, model_id, prompt, image_data, created_at + FROM generated_images + WHERE id = ? AND user_id = ?""", + [image_id, user_id], + ).fetchone() + if not row: + raise HTTPException(status_code=404, detail="Image not found") + return { + "id": str(row[0]), + "model_id": row[1], + "prompt": row[2], + "image_data": row[3], + "created_at": row[4].isoformat() if row[4] else None, + } + + +@router.get("/images/{image_id}") +async def get_generated_image( + image_id: str, + current_user: dict = Depends(get_current_user), +) -> dict: + """Return details for a single generated image.""" + user_id = current_user.get("id") or current_user.get("sub") + conn = get_conn() + row = conn.execute( + """SELECT id, model_id, prompt, image_data, created_at + FROM generated_images + WHERE id = ? AND user_id = ?""", + [image_id, user_id], + ).fetchone() + if not row: + raise HTTPException(status_code=404, detail="Image not found") + return { + "id": str(row[0]), + "model_id": row[1], + "prompt": row[2], + "image_data": row[3], + "created_at": row[4].isoformat() if row[4] else None, + } + + @router.post("/video", response_model=VideoResponse) async def generate_video( body: VideoRequest, @@ -358,3 +408,32 @@ async def list_generated_videos( } for r in rows ] + + +@router.get("/videos/{video_id}") +async def get_generated_video( + video_id: str, + current_user: dict = Depends(get_current_user), +) -> dict: + """Return details for a single video generation job.""" + user_id = current_user.get("id") or current_user.get("sub") + conn = get_conn() + row = conn.execute( + """SELECT id, job_id, model_id, prompt, polling_url, status, video_url, created_at, updated_at + FROM generated_videos + WHERE id = ? AND user_id = ?""", + [video_id, user_id], + ).fetchone() + if not row: + raise HTTPException(status_code=404, detail="Video job not found") + return { + "id": str(row[0]), + "job_id": row[1], + "model_id": row[2], + "prompt": row[3], + "polling_url": row[4], + "status": row[5], + "video_url": row[6], + "created_at": row[7].isoformat() if row[7] else None, + "updated_at": row[8].isoformat() if row[8] else None, + } diff --git a/backend/app/routers/images.py b/backend/app/routers/images.py index 6da3b9b..0759eff 100644 --- a/backend/app/routers/images.py +++ b/backend/app/routers/images.py @@ -93,6 +93,36 @@ async def list_images( ] +@router.get("/{image_id}", status_code=status.HTTP_200_OK) +async def get_image_details( + image_id: str, + current_user: dict = Depends(get_current_user), +) -> dict: + """Return metadata for a single uploaded image.""" + conn = get_conn() + row = conn.execute( + """ + SELECT id, filename, content_type, size_bytes, created_at + FROM uploaded_images + WHERE id = ? AND user_id = ? + """, + [image_id, current_user["id"]], + ).fetchone() + + if not row: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Image not found" + ) + + return { + "id": str(row[0]), + "filename": row[1], + "content_type": row[2], + "size_bytes": row[3], + "created_at": row[4].isoformat() if row[4] else None, + } + + @router.get("/{image_id}/file", status_code=status.HTTP_200_OK) async def serve_image( image_id: str, @@ -106,12 +136,15 @@ async def serve_image( ).fetchone() if row is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image not found.") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Image not found.") if str(row[2]) != current_user["id"]: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied.") + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Access denied.") file_path: str = row[0] if not os.path.isfile(file_path): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image file missing.") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Image file missing.") return FileResponse(file_path, media_type=row[1]) diff --git a/frontend/app/main.py b/frontend/app/main.py index f1ad4ea..62add06 100644 --- a/frontend/app/main.py +++ b/frontend/app/main.py @@ -187,6 +187,63 @@ def dashboard(): generated_videos=generated_videos) +@app.get("/gallery") +@login_required +def gallery(): + token = session["access_token"] + + # Fetch all content types + uploads_resp = _api("GET", "/images/", token=token) + uploads = uploads_resp.json() if uploads_resp.status_code == 200 else [] + + gen_images_resp = _api("GET", "/generate/images", token=token) + generated_images = gen_images_resp.json( + ) if gen_images_resp.status_code == 200 else [] + + videos_resp = _api("GET", "/generate/videos", token=token) + videos = videos_resp.json() if videos_resp.status_code == 200 else [] + + # Separate pending videos + pending_videos = [v for v in videos if v.get( + "status") not in ("completed", "failed")] + completed_videos = [v for v in videos if v.get("status") == "completed"] + + return render_template( + "gallery.html", + uploads=uploads, + generated_images=generated_images, + pending_videos=pending_videos, + completed_videos=completed_videos, + ) + + +@app.get("/gallery/image/") +@login_required +def image_detail(image_id: str): + token = session["access_token"] + resp = _api("GET", f"/generate/images/{image_id}", token=token) + image = resp.json() if resp.status_code == 200 else None + return render_template("image_detail.html", image=image) + + +@app.get("/gallery/video/") +@login_required +def video_detail(video_id: str): + token = session["access_token"] + resp = _api("GET", f"/generate/videos/{video_id}", token=token) + video = resp.json() if resp.status_code == 200 else None + return render_template("video_detail.html", video=video) + + +@app.get("/gallery/upload/") +@login_required +def upload_detail(image_id: str): + token = session["access_token"] + resp = _api("GET", f"/images/{image_id}", token=token) + image = resp.json() if resp.status_code == 200 else None + return render_template("upload_detail.html", image=image) + + # ── Generate ────────────────────────────────────────────────────────────── @app.get("/images//file") diff --git a/frontend/app/templates/base.html b/frontend/app/templates/base.html index 7339982..bea6c51 100644 --- a/frontend/app/templates/base.html +++ b/frontend/app/templates/base.html @@ -21,6 +21,7 @@