import uuid as _uuid from pathlib import Path from fastapi import APIRouter, Depends, File, Form, Request, UploadFile from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy import or_ from sqlalchemy.orm import Session from starlette.status import HTTP_303_SEE_OTHER from app.db import get_db from app.models.filament import Filament from app.models.job_log import JobLog from app.models.print_job import JobStatus, PrintJob from app.models.printer import Printer, PrinterStatus from app.models.printer_log import PrinterLog from app.models.printer_type import PrinterType from app.models.pricing import PricingConfig from app.models.todo import Todo from app.stl import get_stl_dir, list_stl_files router = APIRouter() templates = Jinja2Templates(directory="app/templates") # ── Dashboard ────────────────────────────────────────────────────────────────── @router.get("/", response_class=HTMLResponse) def dashboard(request: Request, db: Session = Depends(get_db)): printers = db.query(Printer).order_by(Printer.name).all() return templates.TemplateResponse("dashboard.html", { "request": request, "active": "dashboard", "printers": printers, "total_printers": len(printers), "printing_count": sum(1 for p in printers if p.status == PrinterStatus.printing), "active_jobs": db.query(PrintJob).filter( PrintJob.status.in_([JobStatus.queued, JobStatus.printing]) ).count(), "total_filaments": db.query(Filament).count(), "recent_jobs": db.query(PrintJob).order_by(PrintJob.created_at.desc()).limit(5).all(), }) # ── Printers ─────────────────────────────────────────────────────────────────── @router.get("/printers", response_class=HTMLResponse) def printers_page(request: Request, db: Session = Depends(get_db)): return templates.TemplateResponse("printers.html", { "request": request, "active": "printers", "printers": db.query(Printer).order_by(Printer.name).all(), "printer_types": db.query(PrinterType).order_by(PrinterType.name).all(), }) @router.post("/printers") def create_printer( name: str = Form(...), location: str = Form(""), type_id: str = Form(""), bought_at: str = Form(""), db: Session = Depends(get_db), ): from datetime import date printer = Printer( name=name.strip(), location=location.strip() or None, type_id=int(type_id) if type_id else None, bought_at=date.fromisoformat(bought_at) if bought_at else None, ) db.add(printer) db.commit() return RedirectResponse(url="/printers", status_code=HTTP_303_SEE_OTHER) @router.get("/printers/{printer_id}", response_class=HTMLResponse) def printer_detail(printer_id: int, request: Request, db: Session = Depends(get_db)): from fastapi import HTTPException printer = db.query(Printer).filter(Printer.id == printer_id).first() if not printer: raise HTTPException(status_code=404, detail="Printer not found") return templates.TemplateResponse("printer_detail.html", { "request": request, "active": "printers", "printer": printer, }) @router.post("/printers/{printer_id}/log") def add_printer_log(printer_id: int, message: str = Form(...), db: Session = Depends(get_db)): db.add(PrinterLog(printer_id=printer_id, message=message.strip())) db.commit() return RedirectResponse(url=f"/printers/{printer_id}", status_code=HTTP_303_SEE_OTHER) # ── Printer types ────────────────────────────────────────────────────────────── @router.get("/printer-types", response_class=HTMLResponse) def printer_types_page(request: Request, db: Session = Depends(get_db)): return templates.TemplateResponse("printer_types.html", { "request": request, "active": "printers", "printer_types": db.query(PrinterType).order_by(PrinterType.name).all(), }) @router.post("/printer-types") def create_printer_type( name: str = Form(...), bed_x_mm: int = Form(...), bed_y_mm: int = Form(...), bed_z_mm: int = Form(...), db: Session = Depends(get_db), ): db.add(PrinterType(name=name.strip(), bed_x_mm=bed_x_mm, bed_y_mm=bed_y_mm, bed_z_mm=bed_z_mm)) db.commit() return RedirectResponse(url="/printer-types", status_code=HTTP_303_SEE_OTHER) # ── Jobs ─────────────────────────────────────────────────────────────────────── @router.get("/jobs", response_class=HTMLResponse) def jobs_page(request: Request, q: str = "", db: Session = Depends(get_db)): query = db.query(PrintJob) if q: like = f"%{q}%" query = query.filter( or_(PrintJob.customer.ilike(like), PrintJob.file_name.ilike(like)) ) return templates.TemplateResponse("jobs.html", { "request": request, "active": "jobs", "jobs": query.order_by(PrintJob.created_at.desc()).all(), "q": q, "printers": db.query(Printer).order_by(Printer.name).all(), "filaments": db.query(Filament).order_by(Filament.material, Filament.color).all(), "stl_files": list_stl_files(), }) @router.post("/jobs") async def create_job( printer_id: str = Form(""), customer: str = Form(""), filament_id: str = Form(""), notes: str = Form(""), stl_file: UploadFile = File(None), stl_select: str = Form(""), db: Session = Depends(get_db), ): filename = None if stl_file and stl_file.filename: filename = stl_file.filename (get_stl_dir() / filename).write_bytes(await stl_file.read()) elif stl_select: filename = stl_select job = PrintJob( job_uuid=str(_uuid.uuid4()), name=Path(filename).stem if filename else (customer.strip() or "Unnamed"), file_name=filename, printer_id=int(printer_id) if printer_id else None, filament_id=int(filament_id) if filament_id else None, customer=customer.strip() or None, notes=notes.strip() or None, ) db.add(job) db.flush() db.add(JobLog(job_id=job.id, message="Job created")) db.commit() return RedirectResponse(url="/jobs", status_code=HTTP_303_SEE_OTHER) # ── Filaments ────────────────────────────────────────────────────────────────── @router.get("/filaments", response_class=HTMLResponse) def filaments_page(request: Request, db: Session = Depends(get_db)): return templates.TemplateResponse("filaments.html", { "request": request, "active": "filaments", "filaments": db.query(Filament).order_by(Filament.brand, Filament.material).all(), }) # ── To-Do ────────────────────────────────────────────────────────────────────── @router.get("/todos", response_class=HTMLResponse) def todos_page(request: Request, db: Session = Depends(get_db)): return templates.TemplateResponse("todos.html", { "request": request, "active": "todos", "todos": db.query(Todo).order_by(Todo.done, Todo.created_at.desc()).all(), }) # ── Pricing ──────────────────────────────────────────────────────────────────── @router.get("/pricing", response_class=HTMLResponse) def pricing_page(request: Request, db: Session = Depends(get_db)): return templates.TemplateResponse("pricing.html", { "request": request, "active": "pricing", "configs": db.query(PricingConfig).order_by(PricingConfig.valid_from.desc()).all(), })