Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@ tasks:
- name: backend
init: |
cd docker
# Set ALLOWED_ORIGINS for backend CORS
echo "ALLOWED_ORIGINS=\"['$(gp url 4000)']\"" >> .env
echo "ALLOWED_ORIGINS set to: ['$(gp url 4000)']"
echo 'Building backend container'
docker-compose build backend
echo 'Built backend'
command: |
gp await-port 7432
cd docker
# Ensure ALLOWED_ORIGINS is set for backend CORS
sed -i '/ALLOWED_ORIGINS/d' .env
echo "ALLOWED_ORIGINS=\"['$(gp url 4000)']\"" >> .env
echo "ALLOWED_ORIGINS set to: ['$(gp url 4000)']"
echo 'Starting backend container'
docker-compose up -d backend
echo 'Started backend'
Expand All @@ -31,14 +38,23 @@ tasks:
echo 'Copying .env-gitpod to .env'
cp .env-gitpod .env
eval $(gp env -e)
export VITE_API_BASE_URL=$(gp url 8300)
# Set VITE_API_BASE_URL for Gitpod environment (include /api/v1 path)
export VITE_API_BASE_URL="$(gp url 8300)/api/v1"
# Update the .env file with the Gitpod URL
sed -i '/VITE_API_BASE_URL/d' .env
echo "VITE_API_BASE_URL=$(gp url 8300)/api/v1" >> .env
echo "VITE_API_BASE_URL set to: $(gp url 8300)/api/v1"
echo 'Building frontend container'
docker-compose build frontend
echo 'Built frontend'
command: |
gp await-port 8300
eval $(gp env -e)
export VITE_API_BASE_URL=$(gp url 8300)
export VITE_API_BASE_URL="$(gp url 8300)/api/v1"
# Ensure the .env file has the latest Gitpod URL
sed -i '/VITE_API_BASE_URL/d' .env
echo "VITE_API_BASE_URL=$(gp url 8300)/api/v1" >> .env
echo "VITE_API_BASE_URL set to: $(gp url 8300)/api/v1"
cd docker
echo 'Starting frontend container'
docker-compose up -d frontend
Expand All @@ -58,4 +74,4 @@ ports:
- name: postgres
description: Port 7432 for the postgres
port: 7432
onOpen: ignore
onOpen: ignore
138 changes: 129 additions & 9 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from fastapi import FastAPI
import os
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
import uvicorn
import os

from app.api.v1.api import api_router

Expand All @@ -11,29 +12,140 @@
description="A modern FastAPI boilerplate and PostgreSQL integration",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc"
redoc_url="/redoc",
# Trust proxy headers for Gitpod environment
root_path_in_servers=True,
# Disable automatic trailing slash redirects to prevent HTTP redirects
redirect_slashes=False,
)

# Add CORS middleware

# Create a custom ASGI app wrapper for Gitpod HTTPS enforcement
class HTTPSEnforcer:
def __init__(self, app):
self.app = app

async def __call__(self, scope, receive, send):
# Force HTTPS scheme for Gitpod environment
if os.getenv("GITPOD_WORKSPACE_ID"):
scope["scheme"] = "https"
print(
f"ASGI: HTTPS scheme enforced at scope level for: {scope.get('path', 'unknown')}"
)

await self.app(scope, receive, send)


# Wrap the FastAPI app with HTTPS enforcer
if os.getenv("GITPOD_WORKSPACE_ID"):
app = HTTPSEnforcer(app)
print("HTTPS Enforcer ASGI wrapper applied")


# Get allowed origins from environment variable
def get_secure_origins() -> list[str]:
"""Get and validate CORS origins from environment"""
allowed_origins = os.getenv("ALLOWED_ORIGINS", "http://localhost:4000")

# Parse the string representation of the list
if allowed_origins.startswith("[") and allowed_origins.endswith("]"):
# Remove brackets and split by comma, then strip quotes
origins = [
origin.strip().strip("'\"") for origin in allowed_origins[1:-1].split(",")
]
else:
origins = [allowed_origins]

# Validate origins (basic URL format check)
valid_origins = []
for origin in origins:
if origin.startswith(("http://", "https://")) and (
"localhost" in origin or "gitpod.io" in origin
):
valid_origins.append(origin)
else:
print(f"Warning: Skipping invalid origin: {origin}")

if not valid_origins:
print("Warning: No valid origins found, defaulting to localhost")
valid_origins = ["http://localhost:4000"]

return valid_origins


origins = get_secure_origins()
print(f"Configured CORS origins: {origins}")

# Add CORS middleware with secure origins
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Configure this properly for production
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
)


# Add middleware to handle HTTPS scheme for Gitpod environment
@app.middleware("http")
async def handle_https_scheme(request: Request, call_next):
# Check if we're running in Gitpod environment
if os.getenv("GITPOD_WORKSPACE_ID"):
# Log headers for debugging
print(f"Request headers: {dict(request.headers)}")
print(f"Request scheme: {request.scope['scheme']}")
print(f"Request URL: {request.url}")

# Force HTTPS scheme for ALL Gitpod requests (more aggressive approach)
request.scope["scheme"] = "https"
print(f"HTTPS scheme forced for Gitpod request to: {request.url.path}")

# Also check for X-Forwarded-Proto header as backup
forwarded_proto = request.headers.get("x-forwarded-proto")
if forwarded_proto == "https":
print(f"X-Forwarded-Proto: https confirmed for: {request.url.path}")

response = await call_next(request)

# Force HTTPS URLs in response headers for Gitpod environment
if os.getenv("GITPOD_WORKSPACE_ID"):
# Check if there are any Location headers that need to be converted to HTTPS
if "location" in response.headers:
location = response.headers["location"]
if location.startswith("http://") and "gitpod.io" in location:
https_location = location.replace("http://", "https://")
response.headers["location"] = https_location
print(f"Converted Location header from {location} to {https_location}")

return response


# Include API router
app.include_router(api_router, prefix="/api/v1")


# Custom exception handler for Gitpod environment to ensure HTTPS redirects
@app.exception_handler(HTTPException)
async def https_redirect_handler(request: Request, exc: HTTPException):
if os.getenv("GITPOD_WORKSPACE_ID") and exc.status_code == 307:
# Convert any HTTP redirects to HTTPS
current_url = str(request.url)
if current_url.startswith("http://") and "gitpod.io" in current_url:
https_url = current_url.replace("http://", "https://")
print(f"Converting HTTP redirect to HTTPS: {current_url} -> {https_url}")
return RedirectResponse(url=https_url, status_code=307)

# For all other exceptions, return the default response
return exc


@app.get("/")
async def root():
return {
"message": "Welcome to FastAPI Boilerplate with PostgreSQL!",
"status": "running",
"docs": "/docs",
"api": "/api/v1"
"api": "/api/v1",
}


Expand All @@ -42,9 +154,17 @@ async def health_check():
return {
"status": "healthy",
"service": "FastAPI Boilerplate with PostgreSQL",
"database": "connected (migrations handled by Alembic)"
"database": "connected (migrations handled by Alembic)",
}


if __name__ == "__main__":
port = int(os.getenv("PORT", 8300))
uvicorn.run(app, host="0.0.0.0", port=port)
# Configure uvicorn for Gitpod environment with proxy headers
uvicorn.run(
app,
host="0.0.0.0",
port=port,
proxy_headers=True,
forwarded_allow_ips="127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
)
4 changes: 2 additions & 2 deletions docker/.env-gitpod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ FRONTEND_HTTP_PORT=4000


# Frontend API base
VITE_API_BASE_URL=http://localhost:3000
VITE_API_BASE_URL=http://localhost:8300/api/v1

# Backend DATABASE_URL
DATABASE_URL=postgresql://fastapi_user:fastapi_password@postgres:5432/fastapi_db
DATABASE_URL=postgresql://fastapi_user:fastapi_password@postgres:5432/fastapi_db
6 changes: 3 additions & 3 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ services:
context: ../frontend
dockerfile: Dockerfile
args:
- VITE_API_BASE_URL=${VITE_API_BASE_URL:-http://localhost:3000}
- VITE_API_BASE_URL=${VITE_API_BASE_URL:-http://localhost:8300/api/v1}
container_name: app-frontend
# depends_on:
# - backend
env_file:
- ./.env
environment:
- VITE_API_BASE_URL=${VITE_API_BASE_URL:-http://localhost:3000}
- VITE_API_BASE_URL=${VITE_API_BASE_URL:-http://localhost:8300/api/v1}
ports:
- "${FRONTEND_HTTP_PORT:-4000}:80"
restart: unless-stopped
Expand All @@ -38,7 +38,7 @@ services:
timeout: 10s
retries: 3
start_period: 40s

postgres:
image: postgres:15-alpine
container_name: fastapi-postgres
Expand Down
4 changes: 3 additions & 1 deletion frontend/.prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
"semi": true,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "all"
"tabWidth": 2,
"trailingComma": "all",
"endOfLine": "lf"
}
5 changes: 5 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

# Accept build argument for VITE_API_BASE_URL
ARG VITE_API_BASE_URL
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL

RUN npm run build

# Use Nginx to serve static files + proxy
Expand Down
8 changes: 3 additions & 5 deletions frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,18 @@ export default [
plugins: {
vue,
'@typescript-eslint': typescript,
prettier
prettier,
},
rules: {

// TypeScript rules
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',

// General rules
'no-console': 'error',
'no-console': 'off',
'no-debugger': 'error',
// Prettier integration
'prettier/prettier': 'error',

'prettier/prettier': 'error',
},
},
];
Loading