From 4a84c08a2b1a149afd724592e64fede9d9abf944 Mon Sep 17 00:00:00 2001 From: pratik Date: Wed, 15 Oct 2025 14:22:44 -0500 Subject: [PATCH] remove nginx, serve ui from api itself --- .claude/settings.local.json | 4 ++- Dockerfile | 33 ++++++++++++++++++++-- Dockerfile.frontend | 4 +-- app/main.py | 55 +++++++++++++++++++++++++++++-------- docker-compose.yml | 26 ++++-------------- quickstart.ps1 | 9 +++--- quickstart.sh | 5 ++-- 7 files changed, 91 insertions(+), 45 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1ae95ad..8bfe3f5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -35,7 +35,9 @@ "Bash(git commit:*)", "Bash(git merge:*)", "Bash(git rm:*)", - "Bash(git checkout:*)" + "Bash(git checkout:*)", + "Bash(git push:*)", + "Bash(Start-Sleep -Seconds 10)" ], "deny": [], "ask": [] diff --git a/Dockerfile b/Dockerfile index 4a688cc..5693917 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,39 @@ +# Multi-stage build: First stage builds Angular frontend +FROM node:18-alpine as frontend-builder + +# Install dependencies for native modules +RUN apk add --no-cache python3 make g++ + +WORKDIR /frontend + +# Copy package files first for better layer caching +COPY frontend/package*.json ./ + +# Clean install dependencies +RUN npm ci --force + +# Copy frontend source +COPY frontend/src ./src +COPY frontend/public ./public +COPY frontend/angular.json ./ +COPY frontend/tsconfig*.json ./ + +# Build the Angular app for production +RUN npm run build + +# Second stage: Python backend with Angular static files FROM python:3.11-alpine WORKDIR /app # Install system dependencies for Alpine -# Alpine uses apk instead of apt-get and is lighter/faster RUN apk add --no-cache \ gcc \ musl-dev \ postgresql-dev \ postgresql-client \ - linux-headers + linux-headers \ + curl # Copy requirements and install Python dependencies COPY requirements.txt . @@ -21,6 +45,9 @@ COPY utils/ ./utils/ COPY alembic/ ./alembic/ COPY alembic.ini . +# Copy Angular build from frontend-builder stage +COPY --from=frontend-builder /frontend/dist/frontend/browser ./static + # Create non-root user (Alpine uses adduser instead of useradd) RUN adduser -D -u 1000 appuser && chown -R appuser:appuser /app USER appuser @@ -30,7 +57,7 @@ EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD python -c "import requests; requests.get('http://localhost:8000/health')" + CMD curl -f http://localhost:8000/health # Run the application CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/Dockerfile.frontend b/Dockerfile.frontend index e03368d..f9d9d4c 100644 --- a/Dockerfile.frontend +++ b/Dockerfile.frontend @@ -25,8 +25,8 @@ RUN npm run build --verbose # Production image with nginx FROM nginx:alpine -# Copy built Angular app -COPY --from=frontend-builder /frontend/dist/frontend /usr/share/nginx/html +# Copy built Angular app from the browser subdirectory +COPY --from=frontend-builder /frontend/dist/frontend/browser /usr/share/nginx/html # Copy nginx configuration COPY nginx.conf /etc/nginx/nginx.conf diff --git a/app/main.py b/app/main.py index 95facbc..1e818b0 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse from app.api.artifacts import router as artifacts_router from app.api.seed import router as seed_router from app.api.tags import router as tags_router @@ -7,6 +9,7 @@ from app.database import init_db from app.config import settings import logging import os +from pathlib import Path # Configure logging logging.basicConfig( @@ -39,7 +42,8 @@ app.include_router(artifacts_router) app.include_router(seed_router) app.include_router(tags_router) -# Note: Frontend is now served separately as an Angular application +# Static files configuration - will be set up after routes +static_dir = Path("/app/static") @app.on_event("startup") @@ -65,16 +69,20 @@ async def api_root(): @app.get("/") -async def ui_root(): - """API root - Frontend is served separately""" - return { - "message": "Test Artifact Data Lake API", - "version": "1.0.0", - "docs": "/docs", - "frontend": "Frontend is served separately on port 4200 (development) or via reverse proxy (production)", - "deployment_mode": settings.deployment_mode, - "storage_backend": settings.storage_backend - } +async def serve_angular_app(): + """Serve Angular app index.html""" + static_dir = Path("/app/static") + if static_dir.exists(): + return FileResponse(static_dir / "index.html") + else: + # Fallback if static files not found + return { + "message": "Test Artifact Data Lake API", + "version": "1.0.0", + "docs": "/docs", + "deployment_mode": settings.deployment_mode, + "storage_backend": settings.storage_backend + } @app.get("/health") @@ -83,6 +91,31 @@ async def health_check(): return {"status": "healthy"} +# Catch-all route for Angular client-side routing +# This must be last to not interfere with API routes +@app.get("/{full_path:path}") +async def catch_all(full_path: str): + """Serve Angular app for all non-API routes (SPA routing)""" + if static_dir.exists(): + # Check if the requested path is a file in the static directory + file_path = static_dir / full_path + if file_path.is_file() and file_path.exists(): + # Determine media type based on file extension + media_type = None + if file_path.suffix == ".js": + media_type = "application/javascript" + elif file_path.suffix == ".css": + media_type = "text/css" + elif file_path.suffix in [".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico"]: + media_type = f"image/{file_path.suffix[1:]}" + return FileResponse(file_path, media_type=media_type) + # Otherwise, serve index.html for client-side routing + index_path = static_dir / "index.html" + if index_path.exists(): + return FileResponse(index_path, media_type="text/html") + return {"error": "Static files not found"} + + if __name__ == "__main__": import uvicorn uvicorn.run( diff --git a/docker-compose.yml b/docker-compose.yml index 175c176..b7587e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ services: timeout: 5s retries: 5 - api: + app: build: . ports: - "8000:8000" @@ -52,25 +52,11 @@ services: minio: condition: service_healthy healthcheck: - test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/health')"] - interval: 30s - timeout: 10s - retries: 3 - - frontend: - build: - context: . - dockerfile: Dockerfile.frontend - ports: - - "4200:80" - depends_on: - api: - condition: service_healthy - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:80"] - interval: 30s - timeout: 10s - retries: 3 + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 40s volumes: postgres_data: diff --git a/quickstart.ps1 b/quickstart.ps1 index 3451ab0..07203b3 100644 --- a/quickstart.ps1 +++ b/quickstart.ps1 @@ -71,13 +71,13 @@ if ($Rebuild) { if ($ComposeCmd -eq "docker compose") { & docker compose down Write-Host "Removing existing images for rebuild..." -ForegroundColor White - & docker compose down --rmi local 2>$null + & docker compose down --rmi local Write-Host "Building and starting all services..." -ForegroundColor White & docker compose up -d --build } else { & docker-compose down Write-Host "Removing existing images for rebuild..." -ForegroundColor White - & docker-compose down --rmi local 2>$null + & docker-compose down --rmi local Write-Host "Building and starting all services..." -ForegroundColor White & docker-compose up -d --build } @@ -99,8 +99,7 @@ Write-Host "=========================================" -ForegroundColor Cyan Write-Host "Complete Stack is running!" -ForegroundColor Green Write-Host "=========================================" -ForegroundColor Cyan Write-Host "" -Write-Host "Frontend: http://localhost:4200" -ForegroundColor White -Write-Host "API: http://localhost:8000" -ForegroundColor White +Write-Host "Application: http://localhost:8000" -ForegroundColor White Write-Host "API Docs: http://localhost:8000/docs" -ForegroundColor White Write-Host "MinIO Console: http://localhost:9001" -ForegroundColor White Write-Host " Username: minioadmin" -ForegroundColor Gray @@ -145,5 +144,5 @@ catch { Write-Host "" Write-Host "=========================================" -ForegroundColor Cyan Write-Host "Setup complete!" -ForegroundColor Green -Write-Host "Open http://localhost:4200 in your browser" -ForegroundColor Yellow +Write-Host "Open http://localhost:8000 in your browser" -ForegroundColor Yellow Write-Host "=========================================" -ForegroundColor Cyan diff --git a/quickstart.sh b/quickstart.sh index 3acfab8..75c8f9a 100644 --- a/quickstart.sh +++ b/quickstart.sh @@ -86,8 +86,7 @@ echo "=========================================" echo "Complete Stack is running! 🚀" echo "=========================================" echo "" -echo "Frontend: http://localhost:4200" -echo "API: http://localhost:8000" +echo "Application: http://localhost:8000" echo "API Docs: http://localhost:8000/docs" echo "MinIO Console: http://localhost:9001" echo " Username: minioadmin" @@ -125,5 +124,5 @@ fi echo "=========================================" echo "Setup complete! 🚀" -echo "Open http://localhost:4200 in your browser" +echo "Open http://localhost:8000 in your browser" echo "========================================="