| | """
|
| | FastAPI Main Application
|
| | Backend API for Cancer@Home
|
| | """
|
| |
|
| | from fastapi import FastAPI, HTTPException
|
| | from fastapi.middleware.cors import CORSMiddleware
|
| | from fastapi.staticfiles import StaticFiles
|
| | from fastapi.responses import HTMLResponse
|
| | from strawberry.fastapi import GraphQLRouter
|
| | from pathlib import Path
|
| | import uvicorn
|
| |
|
| | from backend.neo4j.graphql_schema import schema
|
| | from backend.neo4j.db_manager import DatabaseManager
|
| | from backend.boinc.client import BOINCClient, BOINCTaskManager
|
| | from backend.gdc.client import GDCClient
|
| | from backend.pipeline import (
|
| | FASTQProcessor,
|
| | BLASTRunner,
|
| | VariantCaller
|
| | )
|
| |
|
| |
|
| | app = FastAPI(
|
| | title="Cancer@Home v2",
|
| | description="Distributed cancer genomics research platform",
|
| | version="2.0.0"
|
| | )
|
| |
|
| |
|
| | app.add_middleware(
|
| | CORSMiddleware,
|
| | allow_origins=["*"],
|
| | allow_credentials=True,
|
| | allow_methods=["*"],
|
| | allow_headers=["*"],
|
| | )
|
| |
|
| |
|
| | graphql_app = GraphQLRouter(schema)
|
| | app.include_router(graphql_app, prefix="/graphql")
|
| |
|
| |
|
| | frontend_path = Path("frontend/dist")
|
| | if frontend_path.exists():
|
| | app.mount("/static", StaticFiles(directory=str(frontend_path)), name="static")
|
| |
|
| |
|
| | @app.get("/", response_class=HTMLResponse)
|
| | async def root():
|
| | """Serve main dashboard"""
|
| | html_file = Path("frontend/index.html")
|
| | if html_file.exists():
|
| | with open(html_file, 'r') as f:
|
| | return f.read()
|
| |
|
| |
|
| | return """
|
| | <!DOCTYPE html>
|
| | <html>
|
| | <head>
|
| | <title>Cancer@Home v2</title>
|
| | <style>
|
| | body {
|
| | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| | margin: 0;
|
| | padding: 0;
|
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| | color: white;
|
| | }
|
| | .container {
|
| | max-width: 1200px;
|
| | margin: 0 auto;
|
| | padding: 40px 20px;
|
| | }
|
| | h1 {
|
| | font-size: 3em;
|
| | margin-bottom: 20px;
|
| | }
|
| | .card {
|
| | background: rgba(255, 255, 255, 0.1);
|
| | border-radius: 10px;
|
| | padding: 30px;
|
| | margin: 20px 0;
|
| | backdrop-filter: blur(10px);
|
| | }
|
| | .links {
|
| | display: grid;
|
| | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| | gap: 20px;
|
| | margin-top: 30px;
|
| | }
|
| | .link-card {
|
| | background: rgba(255, 255, 255, 0.15);
|
| | border-radius: 8px;
|
| | padding: 20px;
|
| | text-decoration: none;
|
| | color: white;
|
| | transition: transform 0.2s;
|
| | }
|
| | .link-card:hover {
|
| | transform: translateY(-5px);
|
| | background: rgba(255, 255, 255, 0.25);
|
| | }
|
| | .link-card h3 {
|
| | margin-top: 0;
|
| | }
|
| | </style>
|
| | </head>
|
| | <body>
|
| | <div class="container">
|
| | <h1>🧬 Cancer@Home v2</h1>
|
| | <div class="card">
|
| | <h2>Welcome to Cancer@Home</h2>
|
| | <p>A distributed computing platform for cancer genomics research</p>
|
| | </div>
|
| |
|
| | <div class="links">
|
| | <a href="/api/docs" class="link-card">
|
| | <h3>📚 API Documentation</h3>
|
| | <p>Interactive API docs with Swagger UI</p>
|
| | </a>
|
| | <a href="/graphql" class="link-card">
|
| | <h3>🔍 GraphQL Playground</h3>
|
| | <p>Query cancer data with GraphQL</p>
|
| | </a>
|
| | <a href="http://localhost:7474" class="link-card">
|
| | <h3>📊 Neo4j Browser</h3>
|
| | <p>Visualize graph database</p>
|
| | </a>
|
| | <a href="/api/health" class="link-card">
|
| | <h3>💚 Health Check</h3>
|
| | <p>Check system status</p>
|
| | </a>
|
| | </div>
|
| | </div>
|
| | </body>
|
| | </html>
|
| | """
|
| |
|
| |
|
| | @app.get("/api/health")
|
| | async def health_check():
|
| | """Health check endpoint"""
|
| | db = DatabaseManager()
|
| | try:
|
| | db.execute_query("RETURN 1")
|
| | neo4j_status = "healthy"
|
| | except Exception as e:
|
| | neo4j_status = f"unhealthy: {str(e)}"
|
| | finally:
|
| | db.close()
|
| |
|
| | return {
|
| | "status": "healthy",
|
| | "neo4j": neo4j_status,
|
| | "version": "2.0.0"
|
| | }
|
| |
|
| |
|
| |
|
| | @app.get("/api/boinc/tasks")
|
| | async def get_boinc_tasks(status: str = None):
|
| | """Get BOINC tasks"""
|
| | client = BOINCClient()
|
| | tasks = client.list_tasks(status=status)
|
| | return {"tasks": [vars(t) for t in tasks]}
|
| |
|
| |
|
| | @app.post("/api/boinc/submit")
|
| | async def submit_boinc_task(workunit_type: str, input_file: str):
|
| | """Submit new BOINC task"""
|
| | manager = BOINCTaskManager()
|
| |
|
| | if workunit_type == "variant_calling":
|
| | task_id = manager.submit_variant_calling(input_file)
|
| | elif workunit_type == "blast_search":
|
| | task_id = manager.submit_blast_search(input_file)
|
| | else:
|
| | task_id = manager.client.submit_task(workunit_type, input_file)
|
| |
|
| | return {"task_id": task_id, "status": "submitted"}
|
| |
|
| |
|
| | @app.get("/api/boinc/statistics")
|
| | async def get_boinc_statistics():
|
| | """Get BOINC statistics"""
|
| | client = BOINCClient()
|
| | stats = client.get_statistics()
|
| | return stats
|
| |
|
| |
|
| |
|
| | @app.get("/api/gdc/projects")
|
| | async def get_gdc_projects():
|
| | """Get available GDC projects"""
|
| | projects = [
|
| | {"id": "TCGA-BRCA", "name": "Breast Cancer", "cases": 1098},
|
| | {"id": "TCGA-LUAD", "name": "Lung Adenocarcinoma", "cases": 585},
|
| | {"id": "TCGA-COAD", "name": "Colon Adenocarcinoma", "cases": 461},
|
| | {"id": "TCGA-GBM", "name": "Glioblastoma", "cases": 617},
|
| | {"id": "TARGET-AML", "name": "Acute Myeloid Leukemia", "cases": 238},
|
| | ]
|
| | return {"projects": projects}
|
| |
|
| |
|
| | @app.get("/api/gdc/files/{project_id}")
|
| | async def search_gdc_files(project_id: str, limit: int = 10):
|
| | """Search GDC files for a project"""
|
| | client = GDCClient()
|
| | files = client.get_project_files(project_id, limit=limit)
|
| | return {"files": [vars(f) for f in files]}
|
| |
|
| |
|
| | @app.post("/api/gdc/download")
|
| | async def download_gdc_file(file_id: str):
|
| | """Download a file from GDC"""
|
| | client = GDCClient()
|
| | file_path = client.download_file(file_id)
|
| |
|
| | if file_path:
|
| | return {"status": "success", "file_path": str(file_path)}
|
| | else:
|
| | raise HTTPException(status_code=500, detail="Download failed")
|
| |
|
| |
|
| |
|
| | @app.post("/api/pipeline/fastq/qc")
|
| | async def run_fastq_qc(file_path: str):
|
| | """Run FASTQ quality control"""
|
| | processor = FASTQProcessor()
|
| | stats = processor.calculate_statistics(Path(file_path))
|
| | return {"statistics": stats}
|
| |
|
| |
|
| | @app.post("/api/pipeline/blast")
|
| | async def run_blast(query_file: str):
|
| | """Run BLAST search"""
|
| | runner = BLASTRunner()
|
| | output_file = runner.run_blastn(Path(query_file))
|
| |
|
| | if output_file:
|
| | hits = runner.parse_results(output_file)
|
| | return {
|
| | "status": "success",
|
| | "output_file": str(output_file),
|
| | "total_hits": len(hits),
|
| | "hits": hits[:10]
|
| | }
|
| | else:
|
| | raise HTTPException(status_code=500, detail="BLAST search failed")
|
| |
|
| |
|
| | @app.post("/api/pipeline/variants")
|
| | async def call_variants(alignment_file: str, reference_genome: str):
|
| | """Call variants from alignment"""
|
| | caller = VariantCaller()
|
| | vcf_file = caller.call_variants(
|
| | Path(alignment_file),
|
| | Path(reference_genome)
|
| | )
|
| |
|
| | variants = caller.filter_variants(vcf_file)
|
| |
|
| | return {
|
| | "status": "success",
|
| | "vcf_file": str(vcf_file),
|
| | "total_variants": len(variants),
|
| | "variants": [vars(v) for v in variants]
|
| | }
|
| |
|
| |
|
| |
|
| | @app.get("/api/neo4j/summary")
|
| | async def get_database_summary():
|
| | """Get database summary statistics"""
|
| | db = DatabaseManager()
|
| |
|
| | query = """
|
| | MATCH (g:Gene) WITH count(g) as genes
|
| | MATCH (m:Mutation) WITH genes, count(m) as mutations
|
| | MATCH (p:Patient) WITH genes, mutations, count(p) as patients
|
| | MATCH (c:CancerType) WITH genes, mutations, patients, count(c) as cancer_types
|
| | RETURN genes, mutations, patients, cancer_types
|
| | """
|
| |
|
| | result = db.execute_query(query)
|
| | db.close()
|
| |
|
| | return result[0] if result else {}
|
| |
|
| |
|
| | @app.get("/api/neo4j/genes/{symbol}")
|
| | async def get_gene_info(symbol: str):
|
| | """Get gene information"""
|
| | db = DatabaseManager()
|
| | from backend.neo4j.db_manager import GeneRepository
|
| |
|
| | repo = GeneRepository(db)
|
| | gene = repo.get_gene_by_symbol(symbol)
|
| |
|
| | if gene:
|
| | mutations = repo.get_gene_mutations(gene['gene_id'])
|
| | db.close()
|
| | return {
|
| | "gene": gene,
|
| | "mutations": mutations,
|
| | "mutation_count": len(mutations)
|
| | }
|
| |
|
| | db.close()
|
| | raise HTTPException(status_code=404, detail="Gene not found")
|
| |
|
| |
|
| | if __name__ == "__main__":
|
| | uvicorn.run(app, host="0.0.0.0", port=5000)
|
| |
|