Files
serv_benchmark/backend/app/api/docs.py
gilles soulier c6a8e8e83d feat: Complete MVP implementation of Linux BenchTools
 Features:
- Backend FastAPI complete (25 Python files)
  - 5 SQLAlchemy models (Device, HardwareSnapshot, Benchmark, Link, Document)
  - Pydantic schemas for validation
  - 4 API routers (benchmark, devices, links, docs)
  - Authentication with Bearer token
  - Automatic score calculation
  - File upload support

- Frontend web interface (13 files)
  - 4 HTML pages (Dashboard, Devices, Device Detail, Settings)
  - 7 JavaScript modules
  - Monokai dark theme CSS
  - Responsive design
  - Complete CRUD operations

- Client benchmark script (500+ lines Bash)
  - Hardware auto-detection
  - CPU, RAM, Disk, Network benchmarks
  - JSON payload generation
  - Robust error handling

- Docker deployment
  - Optimized Dockerfile
  - docker-compose with 2 services
  - Persistent volumes
  - Environment variables

- Documentation & Installation
  - Automated install.sh script
  - README, QUICKSTART, DEPLOYMENT guides
  - Complete API documentation
  - Project structure documentation

📊 Stats:
- ~60 files created
- ~5000 lines of code
- Full MVP feature set implemented

🚀 Ready for production deployment!

🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 14:46:10 +01:00

154 lines
4.2 KiB
Python

"""
Linux BenchTools - Documents API
"""
import os
import hashlib
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from typing import List
from datetime import datetime
from app.db.session import get_db
from app.core.config import settings
from app.schemas.document import DocumentResponse
from app.models.document import Document
from app.models.device import Device
router = APIRouter()
def generate_file_hash(content: bytes) -> str:
"""Generate a unique hash for file storage"""
return hashlib.sha256(content).hexdigest()[:16]
@router.get("/devices/{device_id}/docs", response_model=List[DocumentResponse])
async def get_device_documents(
device_id: int,
db: Session = Depends(get_db)
):
"""Get all documents for a device"""
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="Device not found")
docs = db.query(Document).filter(Document.device_id == device_id).all()
return [
DocumentResponse(
id=doc.id,
device_id=doc.device_id,
doc_type=doc.doc_type,
filename=doc.filename,
mime_type=doc.mime_type,
size_bytes=doc.size_bytes,
uploaded_at=doc.uploaded_at.isoformat()
)
for doc in docs
]
@router.post("/devices/{device_id}/docs", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED)
async def upload_document(
device_id: int,
file: UploadFile = File(...),
doc_type: str = Form(...),
db: Session = Depends(get_db)
):
"""Upload a document for a device"""
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="Device not found")
# Read file content
content = await file.read()
file_size = len(content)
# Check file size
if file_size > settings.MAX_UPLOAD_SIZE:
raise HTTPException(
status_code=413,
detail=f"File too large. Maximum size: {settings.MAX_UPLOAD_SIZE} bytes"
)
# Generate unique filename
file_hash = generate_file_hash(content)
ext = os.path.splitext(file.filename)[1]
stored_filename = f"{file_hash}_{device_id}{ext}"
stored_path = os.path.join(settings.UPLOAD_DIR, stored_filename)
# Ensure upload directory exists
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
# Save file
with open(stored_path, "wb") as f:
f.write(content)
# Create database record
doc = Document(
device_id=device_id,
doc_type=doc_type,
filename=file.filename,
stored_path=stored_path,
mime_type=file.content_type or "application/octet-stream",
size_bytes=file_size,
uploaded_at=datetime.utcnow()
)
db.add(doc)
db.commit()
db.refresh(doc)
return DocumentResponse(
id=doc.id,
device_id=doc.device_id,
doc_type=doc.doc_type,
filename=doc.filename,
mime_type=doc.mime_type,
size_bytes=doc.size_bytes,
uploaded_at=doc.uploaded_at.isoformat()
)
@router.get("/docs/{doc_id}/download")
async def download_document(
doc_id: int,
db: Session = Depends(get_db)
):
"""Download a document"""
doc = db.query(Document).filter(Document.id == doc_id).first()
if not doc:
raise HTTPException(status_code=404, detail="Document not found")
if not os.path.exists(doc.stored_path):
raise HTTPException(status_code=404, detail="File not found on disk")
return FileResponse(
path=doc.stored_path,
filename=doc.filename,
media_type=doc.mime_type
)
@router.delete("/docs/{doc_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_document(
doc_id: int,
db: Session = Depends(get_db)
):
"""Delete a document"""
doc = db.query(Document).filter(Document.id == doc_id).first()
if not doc:
raise HTTPException(status_code=404, detail="Document not found")
# Delete file from disk
if os.path.exists(doc.stored_path):
os.remove(doc.stored_path)
# Delete from database
db.delete(doc)
db.commit()
return None