Switch to angular #1
@@ -35,7 +35,9 @@
|
|||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(git merge:*)",
|
"Bash(git merge:*)",
|
||||||
"Bash(git rm:*)",
|
"Bash(git rm:*)",
|
||||||
"Bash(git checkout:*)"
|
"Bash(git checkout:*)",
|
||||||
|
"Bash(git push:*)",
|
||||||
|
"Bash(Start-Sleep -Seconds 10)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
33
Dockerfile
33
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
|
FROM python:3.11-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install system dependencies for Alpine
|
# Install system dependencies for Alpine
|
||||||
# Alpine uses apk instead of apt-get and is lighter/faster
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
gcc \
|
gcc \
|
||||||
musl-dev \
|
musl-dev \
|
||||||
postgresql-dev \
|
postgresql-dev \
|
||||||
postgresql-client \
|
postgresql-client \
|
||||||
linux-headers
|
linux-headers \
|
||||||
|
curl
|
||||||
|
|
||||||
# Copy requirements and install Python dependencies
|
# Copy requirements and install Python dependencies
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
@@ -21,6 +45,9 @@ COPY utils/ ./utils/
|
|||||||
COPY alembic/ ./alembic/
|
COPY alembic/ ./alembic/
|
||||||
COPY alembic.ini .
|
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)
|
# Create non-root user (Alpine uses adduser instead of useradd)
|
||||||
RUN adduser -D -u 1000 appuser && chown -R appuser:appuser /app
|
RUN adduser -D -u 1000 appuser && chown -R appuser:appuser /app
|
||||||
USER appuser
|
USER appuser
|
||||||
@@ -30,7 +57,7 @@ EXPOSE 8000
|
|||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
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
|
# Run the application
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ RUN npm run build --verbose
|
|||||||
# Production image with nginx
|
# Production image with nginx
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
# Copy built Angular app
|
# Copy built Angular app from the browser subdirectory
|
||||||
COPY --from=frontend-builder /frontend/dist/frontend /usr/share/nginx/html
|
COPY --from=frontend-builder /frontend/dist/frontend/browser /usr/share/nginx/html
|
||||||
|
|
||||||
# Copy nginx configuration
|
# Copy nginx configuration
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|||||||
55
app/main.py
55
app/main.py
@@ -1,5 +1,7 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
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.artifacts import router as artifacts_router
|
||||||
from app.api.seed import router as seed_router
|
from app.api.seed import router as seed_router
|
||||||
from app.api.tags import router as tags_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
|
from app.config import settings
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -39,7 +42,8 @@ app.include_router(artifacts_router)
|
|||||||
app.include_router(seed_router)
|
app.include_router(seed_router)
|
||||||
app.include_router(tags_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")
|
@app.on_event("startup")
|
||||||
@@ -65,16 +69,20 @@ async def api_root():
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def ui_root():
|
async def serve_angular_app():
|
||||||
"""API root - Frontend is served separately"""
|
"""Serve Angular app index.html"""
|
||||||
return {
|
static_dir = Path("/app/static")
|
||||||
"message": "Test Artifact Data Lake API",
|
if static_dir.exists():
|
||||||
"version": "1.0.0",
|
return FileResponse(static_dir / "index.html")
|
||||||
"docs": "/docs",
|
else:
|
||||||
"frontend": "Frontend is served separately on port 4200 (development) or via reverse proxy (production)",
|
# Fallback if static files not found
|
||||||
"deployment_mode": settings.deployment_mode,
|
return {
|
||||||
"storage_backend": settings.storage_backend
|
"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")
|
@app.get("/health")
|
||||||
@@ -83,6 +91,31 @@ async def health_check():
|
|||||||
return {"status": "healthy"}
|
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__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
api:
|
app:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
@@ -52,25 +52,11 @@ services:
|
|||||||
minio:
|
minio:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/health')"]
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||||
interval: 30s
|
interval: 10s
|
||||||
timeout: 10s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 5
|
||||||
|
start_period: 40s
|
||||||
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
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|||||||
@@ -71,13 +71,13 @@ if ($Rebuild) {
|
|||||||
if ($ComposeCmd -eq "docker compose") {
|
if ($ComposeCmd -eq "docker compose") {
|
||||||
& docker compose down
|
& docker compose down
|
||||||
Write-Host "Removing existing images for rebuild..." -ForegroundColor White
|
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
|
Write-Host "Building and starting all services..." -ForegroundColor White
|
||||||
& docker compose up -d --build
|
& docker compose up -d --build
|
||||||
} else {
|
} else {
|
||||||
& docker-compose down
|
& docker-compose down
|
||||||
Write-Host "Removing existing images for rebuild..." -ForegroundColor White
|
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
|
Write-Host "Building and starting all services..." -ForegroundColor White
|
||||||
& docker-compose up -d --build
|
& docker-compose up -d --build
|
||||||
}
|
}
|
||||||
@@ -99,8 +99,7 @@ Write-Host "=========================================" -ForegroundColor Cyan
|
|||||||
Write-Host "Complete Stack is running!" -ForegroundColor Green
|
Write-Host "Complete Stack is running!" -ForegroundColor Green
|
||||||
Write-Host "=========================================" -ForegroundColor Cyan
|
Write-Host "=========================================" -ForegroundColor Cyan
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Frontend: http://localhost:4200" -ForegroundColor White
|
Write-Host "Application: http://localhost:8000" -ForegroundColor White
|
||||||
Write-Host "API: http://localhost:8000" -ForegroundColor White
|
|
||||||
Write-Host "API Docs: http://localhost:8000/docs" -ForegroundColor White
|
Write-Host "API Docs: http://localhost:8000/docs" -ForegroundColor White
|
||||||
Write-Host "MinIO Console: http://localhost:9001" -ForegroundColor White
|
Write-Host "MinIO Console: http://localhost:9001" -ForegroundColor White
|
||||||
Write-Host " Username: minioadmin" -ForegroundColor Gray
|
Write-Host " Username: minioadmin" -ForegroundColor Gray
|
||||||
@@ -145,5 +144,5 @@ catch {
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "=========================================" -ForegroundColor Cyan
|
Write-Host "=========================================" -ForegroundColor Cyan
|
||||||
Write-Host "Setup complete!" -ForegroundColor Green
|
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
|
Write-Host "=========================================" -ForegroundColor Cyan
|
||||||
|
|||||||
@@ -86,8 +86,7 @@ echo "========================================="
|
|||||||
echo "Complete Stack is running! 🚀"
|
echo "Complete Stack is running! 🚀"
|
||||||
echo "========================================="
|
echo "========================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Frontend: http://localhost:4200"
|
echo "Application: http://localhost:8000"
|
||||||
echo "API: http://localhost:8000"
|
|
||||||
echo "API Docs: http://localhost:8000/docs"
|
echo "API Docs: http://localhost:8000/docs"
|
||||||
echo "MinIO Console: http://localhost:9001"
|
echo "MinIO Console: http://localhost:9001"
|
||||||
echo " Username: minioadmin"
|
echo " Username: minioadmin"
|
||||||
@@ -125,5 +124,5 @@ fi
|
|||||||
|
|
||||||
echo "========================================="
|
echo "========================================="
|
||||||
echo "Setup complete! 🚀"
|
echo "Setup complete! 🚀"
|
||||||
echo "Open http://localhost:4200 in your browser"
|
echo "Open http://localhost:8000 in your browser"
|
||||||
echo "========================================="
|
echo "========================================="
|
||||||
|
|||||||
Reference in New Issue
Block a user