added the /files view and auth for api and files

This commit is contained in:
Christian Krakau-Louis
2025-03-26 02:51:49 +01:00
parent f754b25c76
commit 48fce5d661
3 changed files with 159 additions and 10 deletions
+51 -7
View File
@@ -1,17 +1,26 @@
from fastapi import APIRouter, Request, HTTPException, status # app/api.py
from fastapi import APIRouter, Request, HTTPException, status, Depends
from hashlib import md5 from hashlib import md5
from sqlalchemy.orm import Session
from typing import List
from app.auth import require_login
from app.database import SessionLocal
from app.models import FileRecord
router = APIRouter() router = APIRouter()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.get("/whoami") @router.get("/whoami")
async def whoami(request: Request): async def whoami(request: Request):
""" """
Returns user info if logged in, else 401. Returns user info if logged in, else 401.
Example response:
{
"email": "someone@example.com",
"picture": "https://www.gravatar.com/avatar/..."
}
""" """
user = request.session.get("user") user = request.session.get("user")
if not user: if not user:
@@ -22,7 +31,6 @@ async def whoami(request: Request):
raise HTTPException(status_code=400, detail="User has no email in session") raise HTTPException(status_code=400, detail="User has no email in session")
# Generate Gravatar URL from email # Generate Gravatar URL from email
# For more options, see: https://en.gravatar.com/site/implement/images/
email_hash = md5(email.strip().lower().encode()).hexdigest() email_hash = md5(email.strip().lower().encode()).hexdigest()
gravatar_url = f"https://www.gravatar.com/avatar/{email_hash}?d=identicon" gravatar_url = f"https://www.gravatar.com/avatar/{email_hash}?d=identicon"
@@ -30,3 +38,39 @@ async def whoami(request: Request):
"email": email, "email": email,
"picture": gravatar_url "picture": gravatar_url
} }
@router.get("/files")
@require_login
def list_files_api(request: Request, db: Session = Depends(get_db)):
"""
Returns a JSON list of all FileRecord entries.
Protected by `@require_login`, so only logged-in sessions can access.
Example response:
[
{
"id": 123,
"filehash": "abc123...",
"original_filename": "example.pdf",
"local_filename": "/workdir/tmp/<uuid>.pdf",
"file_size": 1048576,
"mime_type": "application/pdf",
"created_at": "2025-05-01T12:34:56.789000"
},
...
]
"""
files = db.query(FileRecord).order_by(FileRecord.created_at.desc()).all()
# Return a simple list of dicts
result = []
for f in files:
result.append({
"id": f.id,
"filehash": f.filehash,
"original_filename": f.original_filename,
"local_filename": f.local_filename,
"file_size": f.file_size,
"mime_type": f.mime_type,
"created_at": f.created_at.isoformat() if f.created_at else None
})
return result
+23 -2
View File
@@ -1,16 +1,37 @@
# app/frontend.py # app/frontend.py
import os import os
from pathlib import Path from pathlib import Path
from fastapi import APIRouter, Request from fastapi import APIRouter, Request, Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from app.auth import require_login from app.auth import require_login
from app.database import SessionLocal
router = APIRouter() router = APIRouter()
# Point templates_dir to "frontend/templates"
templates_dir = Path(__file__).parent.parent / "frontend" / "templates" templates_dir = Path(__file__).parent.parent / "frontend" / "templates"
templates = Jinja2Templates(directory=str(templates_dir)) templates = Jinja2Templates(directory=str(templates_dir))
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.get("/files")
@require_login
def files_page(request: Request):
"""
Return the 'files.html' template.
The actual file data is fetched via XHR from /api/files in the template.
"""
return templates.TemplateResponse("files.html", {"request": request})
# ... existing routes for /, /upload, /about, etc. ...
@router.get("/", include_in_schema=False) @router.get("/", include_in_schema=False)
async def serve_index(request: Request): async def serve_index(request: Request):
return templates.TemplateResponse("index.html", {"request": request}) return templates.TemplateResponse("index.html", {"request": request})
+84
View File
@@ -0,0 +1,84 @@
{% extends "base.html" %}
{% block title %}Files{% endblock %}
{% block content %}
<div class="max-w-7xl mx-auto px-4 py-8">
<h2 class="text-3xl font-bold mb-6">File Records</h2>
<!-- Placeholder for the table -->
<div id="fileTableContainer" class="overflow-x-auto bg-white shadow-md rounded">
<!-- Well fill this via JS -->
<table id="fileTable" class="min-w-full text-left border-collapse hidden">
<thead>
<tr class="border-b bg-gray-100">
<th class="py-3 px-4">ID</th>
<th class="py-3 px-4">Original Filename</th>
<th class="py-3 px-4">Size</th>
<th class="py-3 px-4">Mime Type</th>
<th class="py-3 px-4">Created At</th>
</tr>
</thead>
<tbody id="fileTableBody">
<!-- Rows inserted by JS -->
</tbody>
</table>
</div>
<p id="noFilesMsg" class="text-gray-600 hidden">
No files found.
</p>
</div>
{% endblock %}
{% block scripts %}
<script>
// Once the DOM is ready, fetch from /api/files
document.addEventListener("DOMContentLoaded", async () => {
const table = document.getElementById("fileTable");
const tableBody = document.getElementById("fileTableBody");
const noFilesMsg = document.getElementById("noFilesMsg");
try {
const resp = await fetch("/api/files");
if (!resp.ok) {
// e.g. 401 => not logged in, or 403 => forbidden
if (resp.status === 401) {
// Could redirect to /login
window.location.href = "/login";
return;
}
throw new Error(`HTTP ${resp.status} - ${resp.statusText}`);
}
const data = await resp.json(); // array of file objects
// If empty, show "No files" message
if (!data || data.length === 0) {
noFilesMsg.classList.remove("hidden");
return;
}
// Otherwise, fill the table
data.forEach(file => {
const row = document.createElement("tr");
row.className = "border-b hover:bg-gray-50";
row.innerHTML = `
<td class="py-3 px-4">${file.id}</td>
<td class="py-3 px-4">${file.original_filename ?? ""}</td>
<td class="py-3 px-4">${file.file_size}</td>
<td class="py-3 px-4">${file.mime_type}</td>
<td class="py-3 px-4">${file.created_at ? file.created_at : ""}</td>
`;
tableBody.appendChild(row);
});
// Show the table now that we have data
table.classList.remove("hidden");
} catch (err) {
console.error("Failed to load file records:", err);
alert("Could not load files from /api/files. Check console for details.");
}
});
</script>
{% endblock %}