""" Anchor routes for L2 server. Handles OpenTimestamps anchoring and verification. """ import logging from typing import Optional from fastapi import APIRouter, Request, Depends, HTTPException from fastapi.responses import HTMLResponse, FileResponse from artdag_common import render from artdag_common.middleware import wants_html, wants_json from ..config import settings from ..dependencies import get_templates, require_auth, get_user_from_cookie router = APIRouter() logger = logging.getLogger(__name__) @router.get("") async def list_anchors( request: Request, offset: int = 0, limit: int = 20, ): """List user's anchors.""" import db username = get_user_from_cookie(request) if not username: if wants_json(request): raise HTTPException(401, "Authentication required") from fastapi.responses import RedirectResponse return RedirectResponse(url="/login", status_code=302) anchors = await db.get_user_anchors(username, offset=offset, limit=limit) has_more = len(anchors) >= limit if wants_json(request): return {"anchors": anchors, "offset": offset, "limit": limit} templates = get_templates(request) return render(templates, "anchors/list.html", request, anchors=anchors, user={"username": username}, offset=offset, limit=limit, has_more=has_more, active_tab="anchors", ) @router.post("") async def create_anchor( request: Request, user: dict = Depends(require_auth), ): """Create a new timestamp anchor.""" import db import anchoring body = await request.json() content_hash = body.get("content_hash") if not content_hash: raise HTTPException(400, "content_hash required") # Create OTS timestamp try: ots_data = await anchoring.create_timestamp(content_hash) except Exception as e: logger.error(f"Failed to create timestamp: {e}") raise HTTPException(500, f"Timestamping failed: {e}") # Save anchor anchor_id = await db.create_anchor( username=user["username"], content_hash=content_hash, ots_data=ots_data, ) return { "anchor_id": anchor_id, "content_hash": content_hash, "status": "pending", "message": "Anchor created, pending Bitcoin confirmation", } @router.get("/{anchor_id}") async def get_anchor( anchor_id: str, request: Request, ): """Get anchor details.""" import db anchor = await db.get_anchor(anchor_id) if not anchor: raise HTTPException(404, "Anchor not found") if wants_json(request): return anchor username = get_user_from_cookie(request) templates = get_templates(request) return render(templates, "anchors/detail.html", request, anchor=anchor, user={"username": username} if username else None, active_tab="anchors", ) @router.get("/{anchor_id}/ots") async def download_ots(anchor_id: str): """Download OTS proof file.""" import db anchor = await db.get_anchor(anchor_id) if not anchor: raise HTTPException(404, "Anchor not found") ots_data = anchor.get("ots_data") if not ots_data: raise HTTPException(404, "OTS data not available") # Return as file download from fastapi.responses import Response return Response( content=ots_data, media_type="application/octet-stream", headers={ "Content-Disposition": f"attachment; filename={anchor['content_hash']}.ots" }, ) @router.post("/{anchor_id}/verify") async def verify_anchor( anchor_id: str, request: Request, user: dict = Depends(require_auth), ): """Verify anchor status (check Bitcoin confirmation).""" import db import anchoring anchor = await db.get_anchor(anchor_id) if not anchor: raise HTTPException(404, "Anchor not found") try: result = await anchoring.verify_timestamp( anchor["content_hash"], anchor["ots_data"], ) # Update anchor status if result.get("confirmed"): await db.update_anchor( anchor_id, status="confirmed", bitcoin_block=result.get("block_height"), confirmed_at=result.get("confirmed_at"), ) if wants_html(request): if result.get("confirmed"): return HTMLResponse( f'Confirmed in block {result["block_height"]}' ) return HTMLResponse('Pending confirmation') return result except Exception as e: logger.error(f"Verification failed: {e}") raise HTTPException(500, f"Verification failed: {e}") @router.delete("/{anchor_id}") async def delete_anchor( anchor_id: str, user: dict = Depends(require_auth), ): """Delete an anchor.""" import db anchor = await db.get_anchor(anchor_id) if not anchor: raise HTTPException(404, "Anchor not found") if anchor.get("username") != user["username"]: raise HTTPException(403, "Not authorized") success = await db.delete_anchor(anchor_id) if not success: raise HTTPException(400, "Failed to delete anchor") return {"deleted": True}