Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
117 changes: 35 additions & 82 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, Request, HTTPException, Depends, status
from fastapi.responses import Response
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.security import OpenIdConnect
from fastapi.responses import JSONResponse
import firebase_admin
from firebase_admin import credentials, auth as firebase_auth
import httpx
from pydantic_settings import BaseSettings

Expand All @@ -20,44 +19,26 @@ class Config:

settings = Settings()

# Initialize Firebase Admin if not already initialized.
services = {
"admin": settings.ADMIN_SERVICE_URL,
"notifications": settings.NOTIFICATION_SERVICE_URL,
"reviews": settings.REVIEW_SERVICE_URL,
"rides": settings.RIDE_SERVICE_URL,
"users": settings.USER_SERVICE_URL
}

if not firebase_admin._apps:
cred = credentials.Certificate("credentials.json")
cred = firebase_admin.credentials.Certificate("credentials.json")
firebase_admin.initialize_app(cred)

security_scheme = HTTPBearer()

def verify_jwt(credentials: HTTPAuthorizationCredentials = Depends(security_scheme)):
token = credentials.credentials
try:
decoded_token = firebase_auth.verify_id_token(token)
return decoded_token
except Exception:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired auth token"
)
openid_connect_url = f"https://securetoken.google.com/{cred.project_id}/.well-known/openid-configuration"
security_scheme = OpenIdConnect(openIdConnectUrl=openid_connect_url)

async def proxy_request(request: Request, base_url: str, new_path: str = None):
path = new_path if new_path is not None else request.url.path
url = f"{base_url}{path}"
if request.url.query:
url = f"{url}?{request.url.query}"
async def forward_request(service_url: str, method: str, path: str, body : dict = None, headers: dict = None):
async with httpx.AsyncClient() as client:
try:
resp = await client.request(
request.method,
url,
headers=request.headers,
content=await request.body(),
timeout=10.0
)
except httpx.RequestError as exc:
raise HTTPException(status_code=500, detail=str(exc))

content_type = resp.headers.get("Content-Type", "")
body = resp.text
return Response(content=body, status_code=resp.status_code, media_type=content_type)
url = f"{service_url}{path}"
response = await client.request(method, url, json=body, headers=headers)
return response

app = FastAPI(
root_path="/api",
Expand All @@ -74,53 +55,25 @@ async def proxy_request(request: Request, base_url: str, new_path: str = None):
allow_headers=["*"],
)

# Protect the gateway endpoints by adding the verify_jwt dependency.
@app.api_route("/users/login", methods=["POST"])
async def login(request: Request):
# Proxy the login request to the user service without JWT verification.
return await proxy_request(request, settings.USER_SERVICE_URL)

@app.api_route("/users/signup", methods=["POST"])
async def signup(request: Request):
# Proxy the signup request to the user service without JWT verification.
return await proxy_request(request, settings.USER_SERVICE_URL)

# Apply JWT verification for other /users endpoints.
@app.api_route("/users/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], operation_id="users_gateway_operation")
async def users_gateway(path: str, request: Request, token_data=Depends(verify_jwt)):
new_path = "/" + path
return await proxy_request(request, settings.USER_SERVICE_URL, new_path=new_path)

@app.api_route("/rides/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], operation_id="rides_gateway_operation")
async def rides_gateway(path: str, request: Request, token_data=Depends(verify_jwt)):
new_path = "/" + path
return await proxy_request(request, settings.RIDE_SERVICE_URL, new_path=new_path)

@app.api_route("/notifications/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], operation_id="notifications_gateway_operation")
async def notifications_gateway(path: str, request: Request, token_data=Depends(verify_jwt)):
new_path = "/" + path
return await proxy_request(request, settings.NOTIFICATION_SERVICE_URL, new_path=new_path)

@app.api_route("/reviews/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], operation_id="reviews_gateway_operation")
async def reviews_gateway(path: str, request: Request, token_data=Depends(verify_jwt)):
new_path = "/" + path
return await proxy_request(request, settings.REVIEW_SERVICE_URL, new_path=new_path)

@app.api_route("/admin/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], operation_id="admin_gateway_operation")
async def admin_gateway(path: str, request: Request, token_data=Depends(verify_jwt)):
new_path = "/" + path
return await proxy_request(request, settings.ADMIN_SERVICE_URL, new_path=new_path)

@app.get("/healthz", include_in_schema=False, root_path="")
async def root_healthz():
"""Health check endpoint for GKE ingress."""
return {"status": "ok"}

@app.get("/healthz", include_in_schema=False)
async def healthz():
@app.api_route(
"/{service}/{path:path}",
methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
dependencies=[Depends(security_scheme)],
operation_id="gateway")
async def gateway(service: str, path: str, request: Request):
if service not in services:
raise HTTPException(status_code=404, detail="Service not found")
service_url = services[service]
body = await request.json() if request.method in ["POST", "PUT", "PATCH"] else None
headers = dict(request.headers)
response = await forward_request(service_url, request.method, f"/{path}", body, headers)
return JSONResponse(status_code=response.status_code, content=response.json())

@app.get("/health", include_in_schema=False)
async def health():
"""Health check endpoint for GKE ingress."""
return {"status": "ok"}

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=settings.PORT)
uvicorn.run(app, host="0.0.0.0", port=settings.PORT, log_level="info")
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = "API gateway for all microservices"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi>=0.115.8",
"fastapi[standard]>=0.115.8",
"firebase-admin>=6.6.0",
"httpx>=0.28.1",
"pydantic-settings>=2.7.1",
Expand Down
2 changes: 1 addition & 1 deletion rideshare
Submodule rideshare updated 58 files
+7 −0 .env.example
+2 −0 .gitignore
+19 −0 Dockerfile
+1,383 −5 package-lock.json
+7 −1 package.json
+27 −11 src/App.tsx
+3 −4 src/components/AuthenticatedContent.tsx
+19 −10 src/components/Header.tsx
+2 −2 src/components/ListDrawer.tsx
+0 −146 src/components/LoginPanel.tsx
+72 −0 src/components/LoginPanel/LoginPanel.tsx
+41 −0 src/components/LoginPanel/components/AuthTabs.tsx
+40 −0 src/components/LoginPanel/components/LoginForm.tsx
+97 −0 src/components/LoginPanel/components/RegistrationForm.tsx
+46 −0 src/components/LoginPanel/components/SubmitButton.tsx
+71 −0 src/components/LoginPanel/hooks/useAuthForm.ts
+1 −0 src/components/LoginPanel/index.ts
+11 −0 src/components/LoginPanel/types.ts
+5 −7 src/components/MainContent.tsx
+133 −0 src/components/OnboardingPanel/OnboardingPanel.tsx
+35 −0 src/components/OnboardingPanel/components/DriverActiveToggle.tsx
+106 −0 src/components/OnboardingPanel/components/DriverDetailsForm.tsx
+21 −0 src/components/OnboardingPanel/components/OnboardingHeader.tsx
+36 −0 src/components/OnboardingPanel/components/RiderConfirmation.tsx
+61 −0 src/components/OnboardingPanel/components/StepControls.tsx
+65 −0 src/components/OnboardingPanel/components/UserTypeSelector.tsx
+207 −0 src/components/OnboardingPanel/hooks/useOnboardingForm.ts
+1 −0 src/components/OnboardingPanel/index.ts
+0 −22 src/components/ProfilePanel.tsx
+88 −0 src/components/ProfilePanel/ProfilePanel.tsx
+47 −0 src/components/ProfilePanel/components/DriverDetails.tsx
+26 −0 src/components/ProfilePanel/components/EmptyProfile.tsx
+33 −0 src/components/ProfilePanel/components/ProfileActions.tsx
+51 −0 src/components/ProfilePanel/components/ProfileHeader.tsx
+51 −0 src/components/ProfilePanel/hooks/useProfileData.ts
+1 −0 src/components/ProfilePanel/index.ts
+141 −0 src/components/RidePanel/LocationForm.tsx
+142 −0 src/components/RidePanel/RidePanel.tsx
+131 −0 src/components/RidePanel/RidesList.tsx
+1 −0 src/components/RidePanel/index.ts
+19 −0 src/components/ui/Spinner.tsx
+25 −0 src/context/auth/AuthContext.ts
+41 −0 src/context/auth/AuthProvider.tsx
+6 −0 src/context/auth/index.ts
+26 −0 src/context/auth/types.ts
+102 −0 src/context/auth/useAuthOperations.ts
+81 −0 src/context/auth/useAuthState.ts
+18 −0 src/firebase.ts
+85 −0 src/services/api/core/apiClient.ts
+29 −0 src/services/api/core/apiError.ts
+13 −0 src/services/api/core/apiTypes.ts
+127 −0 src/services/api/endpoints/rideApi.ts
+34 −0 src/services/api/endpoints/userApi.ts
+13 −0 src/services/api/index.ts
+35 −0 src/services/models/userTypes.ts
+32 −0 src/services/tokenService.ts
+6 −4 src/theme.tsx
+9 −1 vite.config.ts
2 changes: 1 addition & 1 deletion services/admin-service
Submodule admin-service updated 1 files
+34 −0 Dockerfile
2 changes: 1 addition & 1 deletion services/notification-service
Submodule notification-service updated 2 files
+34 −0 Dockerfile
+2 −2 main.py
2 changes: 1 addition & 1 deletion services/review-service
Submodule review-service updated 1 files
+34 −0 Dockerfile
2 changes: 1 addition & 1 deletion services/ride-service
Loading