Spaces:
Sleeping
Sleeping
Commit ·
7326f4e
1
Parent(s): 4b40f63
feat: initialize user management service with core functionality
Browse files- add FastAPI application with health checks and security middleware
- implement MongoDB and Redis connectivity with health monitoring
- include environment validation and configuration settings
- set up Docker and Kubernetes deployment configurations
- add user router with basic health endpoint
- .env.example +9 -0
- .gitignore +82 -0
- Dockerfile +13 -0
- app/__init__.py +2 -0
- app/app.py +184 -0
- app/config/__init__.py +2 -0
- app/config/validation.py +27 -0
- app/middleware/__init__.py +4 -0
- app/middleware/security_middleware.py +20 -0
- app/nosql.py +144 -0
- app/routers/__init__.py +2 -0
- app/routers/user.py +9 -0
- deployment/docker-compose.yml +20 -0
- deployment/kubernetes/bms-ums-deployment.yaml +128 -0
- requirements.txt +22 -0
- settings.py +34 -0
.env.example
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ENVIRONMENT=development
|
| 2 |
+
LOG_LEVEL=INFO
|
| 3 |
+
MONGO_URI=mongodb+srv://user:password@cluster.example.mongodb.net/?retryWrites=true&w=majority
|
| 4 |
+
MONGO_DB_NAME=insightfy-bloom
|
| 5 |
+
CACHE_URL=redis://localhost:6379/0
|
| 6 |
+
CACHE_URI=
|
| 7 |
+
CACHE_K=
|
| 8 |
+
CACHE_DB=0
|
| 9 |
+
|
.gitignore
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
develop-eggs/
|
| 9 |
+
dist/
|
| 10 |
+
downloads/
|
| 11 |
+
eggs/
|
| 12 |
+
.eggs/
|
| 13 |
+
lib/
|
| 14 |
+
lib64/
|
| 15 |
+
parts/
|
| 16 |
+
sdist/
|
| 17 |
+
var/
|
| 18 |
+
wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
MANIFEST
|
| 23 |
+
|
| 24 |
+
# Testing
|
| 25 |
+
.pytest_cache/
|
| 26 |
+
.hypothesis/
|
| 27 |
+
.coverage
|
| 28 |
+
htmlcov/
|
| 29 |
+
.tox/
|
| 30 |
+
.nox/
|
| 31 |
+
.cache
|
| 32 |
+
nosetests.xml
|
| 33 |
+
coverage.xml
|
| 34 |
+
*.cover
|
| 35 |
+
.nyc_output
|
| 36 |
+
|
| 37 |
+
# Environment
|
| 38 |
+
.env
|
| 39 |
+
.venv*
|
| 40 |
+
venv*
|
| 41 |
+
ENV/
|
| 42 |
+
env.bak/
|
| 43 |
+
venv.bak/
|
| 44 |
+
env/
|
| 45 |
+
venv/
|
| 46 |
+
ENV/
|
| 47 |
+
env.bak/
|
| 48 |
+
venv.bak/
|
| 49 |
+
|
| 50 |
+
# IDE
|
| 51 |
+
.vscode/
|
| 52 |
+
.idea/
|
| 53 |
+
*.swp
|
| 54 |
+
*.swo
|
| 55 |
+
*~
|
| 56 |
+
|
| 57 |
+
# OS
|
| 58 |
+
.DS_Store
|
| 59 |
+
.DS_Store?
|
| 60 |
+
._*
|
| 61 |
+
.Spotlight-V100
|
| 62 |
+
.Trashes
|
| 63 |
+
ehthumbs.db
|
| 64 |
+
Thumbs.db
|
| 65 |
+
|
| 66 |
+
# Logs
|
| 67 |
+
*.log
|
| 68 |
+
logs/
|
| 69 |
+
|
| 70 |
+
# Runtime data
|
| 71 |
+
pids
|
| 72 |
+
*.pid
|
| 73 |
+
*.seed
|
| 74 |
+
*.pid.lock
|
| 75 |
+
|
| 76 |
+
# Temporary files
|
| 77 |
+
*.tmp
|
| 78 |
+
*.temp
|
| 79 |
+
|
| 80 |
+
# API validation reports (generated files)
|
| 81 |
+
*_report.json
|
| 82 |
+
api_validation_report.json
|
Dockerfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim-bullseye AS base
|
| 2 |
+
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 PATH="/home/user/.local/bin:$PATH"
|
| 3 |
+
RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/*
|
| 4 |
+
RUN useradd -m -u 1000 user
|
| 5 |
+
USER user
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 8 |
+
RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir --upgrade -r requirements.txt
|
| 9 |
+
RUN python -m spacy download en_core_web_sm
|
| 10 |
+
COPY --chown=user . /app
|
| 11 |
+
EXPOSE 7861
|
| 12 |
+
CMD ["uvicorn", "app.app:app", "--host", "0.0.0.0", "--port", "7861", "--workers", "4", "--log-level", "info"]
|
| 13 |
+
|
app/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = ["app"]
|
| 2 |
+
|
app/app.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from contextlib import asynccontextmanager
|
| 2 |
+
from fastapi import FastAPI, Request
|
| 3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
from fastapi.responses import JSONResponse
|
| 5 |
+
from insightfy_utils.logging import setup_logging, get_logger
|
| 6 |
+
from insightfy_utils.config import load_env
|
| 7 |
+
from app.routers.user import router as user_router
|
| 8 |
+
from app.middleware.security_middleware import create_security_middleware
|
| 9 |
+
from app.config.validation import validate_environment_variables, validate_database_isolation
|
| 10 |
+
import os
|
| 11 |
+
import time
|
| 12 |
+
import asyncio
|
| 13 |
+
|
| 14 |
+
load_env()
|
| 15 |
+
_env = os.getenv("ENVIRONMENT", "development")
|
| 16 |
+
_log_level = os.getenv("LOG_LEVEL") or ("WARNING" if _env == "development" else "INFO")
|
| 17 |
+
setup_logging(level=_log_level, format_type="json", app_name="insightfy-bms-ms-ums")
|
| 18 |
+
logger = get_logger(__name__)
|
| 19 |
+
startup_time = time.time()
|
| 20 |
+
|
| 21 |
+
@asynccontextmanager
|
| 22 |
+
async def lifespan(app: FastAPI):
|
| 23 |
+
s = time.time()
|
| 24 |
+
try:
|
| 25 |
+
config_result = validate_environment_variables()
|
| 26 |
+
isolation_valid = validate_database_isolation()
|
| 27 |
+
from app.nosql import connect_stores
|
| 28 |
+
await connect_stores()
|
| 29 |
+
startup_duration = time.time() - s
|
| 30 |
+
logger.info("startup_ok", extra={"startup_duration_seconds": round(startup_duration, 3), "service": "insightfy-bms-ms-ums", "config": config_result, "isolation_valid": isolation_valid})
|
| 31 |
+
except Exception as e:
|
| 32 |
+
logger.error("startup_failed", extra={"error": str(e), "service": "insightfy-bms-ms-ums"}, exc_info=True)
|
| 33 |
+
raise
|
| 34 |
+
yield
|
| 35 |
+
try:
|
| 36 |
+
from app.nosql import disconnect_stores
|
| 37 |
+
await disconnect_stores()
|
| 38 |
+
except Exception as e:
|
| 39 |
+
logger.error("shutdown_failed", extra={"error": str(e), "service": "insightfy-bms-ms-ums"}, exc_info=True)
|
| 40 |
+
|
| 41 |
+
app = FastAPI(
|
| 42 |
+
title="Insightfy BMS - User Management Service",
|
| 43 |
+
version="1.0.0",
|
| 44 |
+
description="User Management Service API for Insightfy BMS platform",
|
| 45 |
+
lifespan=lifespan,
|
| 46 |
+
redirect_slashes=False
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
app.add_middleware(
|
| 50 |
+
create_security_middleware,
|
| 51 |
+
enable_security_headers=True
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
allowed_origins = [
|
| 55 |
+
"http://localhost:3000",
|
| 56 |
+
"http://localhost:3001",
|
| 57 |
+
"https://api.insightfybloom.com",
|
| 58 |
+
"https://insightfybloom.com",
|
| 59 |
+
"https://insightfybloom.in",
|
| 60 |
+
]
|
| 61 |
+
if os.getenv("ENVIRONMENT") == "development":
|
| 62 |
+
allowed_origins.extend([
|
| 63 |
+
"http://localhost:8000",
|
| 64 |
+
"http://127.0.0.1:3000",
|
| 65 |
+
"http://127.0.0.1:3001",
|
| 66 |
+
])
|
| 67 |
+
app.add_middleware(
|
| 68 |
+
CORSMiddleware,
|
| 69 |
+
allow_origins=allowed_origins,
|
| 70 |
+
allow_credentials=True,
|
| 71 |
+
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
| 72 |
+
allow_headers=[
|
| 73 |
+
"Accept",
|
| 74 |
+
"Accept-Language",
|
| 75 |
+
"Content-Language",
|
| 76 |
+
"Content-Type",
|
| 77 |
+
"Authorization",
|
| 78 |
+
"X-Requested-With",
|
| 79 |
+
"Origin",
|
| 80 |
+
"Cache-Control",
|
| 81 |
+
"Pragma",
|
| 82 |
+
"X-CSRF-Token",
|
| 83 |
+
"X-Request-ID",
|
| 84 |
+
],
|
| 85 |
+
expose_headers=["X-Request-ID", "X-Service"],
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
@app.middleware("http")
|
| 89 |
+
async def add_service_headers(request: Request, call_next):
|
| 90 |
+
response = await call_next(request)
|
| 91 |
+
response.headers["X-Service"] = "insightfy-bms-ms-ums"
|
| 92 |
+
response.headers["X-Service-Version"] = "1.0.0"
|
| 93 |
+
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
|
| 94 |
+
response.headers["X-Powered-By"] = "Insightfy"
|
| 95 |
+
return response
|
| 96 |
+
|
| 97 |
+
app.include_router(
|
| 98 |
+
user_router,
|
| 99 |
+
prefix="/ums/v1/users",
|
| 100 |
+
tags=["Users"],
|
| 101 |
+
responses={404: {"description": "Not found"}, 500: {"description": "Internal error"}},
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
@app.get("/", tags=["Health"])
|
| 105 |
+
async def root():
|
| 106 |
+
return {
|
| 107 |
+
"service": "Insightfy BMS - User Management Service",
|
| 108 |
+
"version": "1.0.0",
|
| 109 |
+
"status": "running",
|
| 110 |
+
"description": "User Management Service API for Insightfy BMS platform",
|
| 111 |
+
"docs_url": "/docs",
|
| 112 |
+
"health_check": "/ums/health",
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
@app.get("/ums/health", tags=["Health"])
|
| 116 |
+
async def health_check():
|
| 117 |
+
return {"status": "healthy", "service": "insightfy-bms-ms-ums", "version": "1.0.0", "timestamp": time.time()}
|
| 118 |
+
|
| 119 |
+
@app.get("/ums/health/detailed", tags=["Health"])
|
| 120 |
+
async def detailed_health_check():
|
| 121 |
+
try:
|
| 122 |
+
health_status = await get_comprehensive_health_status()
|
| 123 |
+
return health_status
|
| 124 |
+
except Exception as e:
|
| 125 |
+
logger.error("detailed_health_failed", extra={"error": str(e), "service": "insightfy-bms-ms-ums"}, exc_info=True)
|
| 126 |
+
return {"status": "unhealthy", "service": "insightfy-bms-ms-ums", "version": "1.0.0", "error": "Health check failed", "timestamp": time.time()}
|
| 127 |
+
|
| 128 |
+
@app.get("/ums/health/metrics", tags=["Health"])
|
| 129 |
+
async def health_metrics():
|
| 130 |
+
try:
|
| 131 |
+
from app.nosql import get_connection_metrics
|
| 132 |
+
connection_metrics = await get_connection_metrics()
|
| 133 |
+
return {"service": "insightfy-bms-ms-ums", "version": "1.0.0", "connection_metrics": connection_metrics, "status": "healthy", "timestamp": time.time(), "environment": os.getenv("ENVIRONMENT", "development")}
|
| 134 |
+
except Exception as e:
|
| 135 |
+
logger.error("metrics_failed", extra={"error": str(e), "service": "insightfy-bms-ms-ums"}, exc_info=True)
|
| 136 |
+
return {"service": "insightfy-bms-ms-ums", "version": "1.0.0", "error": "Failed to retrieve metrics", "status": "unhealthy", "timestamp": time.time()}
|
| 137 |
+
|
| 138 |
+
@app.get("/ums/health/readiness", tags=["Health"])
|
| 139 |
+
async def readiness_check():
|
| 140 |
+
try:
|
| 141 |
+
from app.nosql import ping_mongo, ping_redis
|
| 142 |
+
mongo_ready = await ping_mongo()
|
| 143 |
+
redis_ready = await ping_redis()
|
| 144 |
+
if mongo_ready and redis_ready:
|
| 145 |
+
return {"status": "ready", "service": "insightfy-bms-ms-ums", "version": "1.0.0", "timestamp": time.time()}
|
| 146 |
+
else:
|
| 147 |
+
return JSONResponse(status_code=503, content={"status": "not_ready", "service": "insightfy-bms-ms-ums", "version": "1.0.0", "components": {"mongodb": "ready" if mongo_ready else "not_ready", "redis": "ready" if redis_ready else "not_ready"}, "timestamp": time.time()})
|
| 148 |
+
except Exception as e:
|
| 149 |
+
logger.error("readiness_failed", extra={"error": str(e), "service": "insightfy-bms-ms-ums"}, exc_info=True)
|
| 150 |
+
return JSONResponse(status_code=503, content={"status": "not_ready", "service": "insightfy-bms-ms-ums", "error": "Readiness check failed", "timestamp": time.time()})
|
| 151 |
+
|
| 152 |
+
@app.get("/ums/health/liveness", tags=["Health"])
|
| 153 |
+
async def liveness_check():
|
| 154 |
+
current_time = time.time()
|
| 155 |
+
return {"status": "alive", "service": "insightfy-bms-ms-ums", "version": "1.0.0", "timestamp": current_time, "uptime_seconds": round(current_time - startup_time, 2)}
|
| 156 |
+
|
| 157 |
+
async def get_comprehensive_health_status():
|
| 158 |
+
from app.nosql import get_database_status
|
| 159 |
+
from app.config.validation import validate_environment_variables, validate_database_isolation
|
| 160 |
+
db_status = await get_database_status()
|
| 161 |
+
try:
|
| 162 |
+
config_status = validate_environment_variables()
|
| 163 |
+
config_valid = True
|
| 164 |
+
except Exception as e:
|
| 165 |
+
config_status = {"error": str(e)}
|
| 166 |
+
config_valid = False
|
| 167 |
+
isolation_valid = validate_database_isolation()
|
| 168 |
+
overall_healthy = (db_status["overall_status"] == "healthy" and config_valid and isolation_valid)
|
| 169 |
+
return {
|
| 170 |
+
"status": "healthy" if overall_healthy else "unhealthy",
|
| 171 |
+
"service": "insightfy-bms-ms-ums",
|
| 172 |
+
"version": "1.0.0",
|
| 173 |
+
"components": {
|
| 174 |
+
"mongodb": "healthy" if db_status["mongodb"]["connected"] else "unhealthy",
|
| 175 |
+
"redis": "healthy" if db_status["redis"]["connected"] else "unhealthy",
|
| 176 |
+
"configuration": "healthy" if config_valid else "unhealthy",
|
| 177 |
+
"database_isolation": "healthy" if isolation_valid else "unhealthy",
|
| 178 |
+
},
|
| 179 |
+
"database": {"name": db_status["mongodb"]["database"], "connected": db_status["mongodb"]["connected"], "isolation_valid": isolation_valid},
|
| 180 |
+
"configuration": config_status,
|
| 181 |
+
"metrics": {"check_duration_ms": db_status["check_duration_ms"], "timestamp": db_status["timestamp"]},
|
| 182 |
+
"environment": os.getenv("ENVIRONMENT", "development"),
|
| 183 |
+
}
|
| 184 |
+
|
app/config/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = ["validation"]
|
| 2 |
+
|
app/config/validation.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
def validate_environment_variables():
|
| 4 |
+
env = os.getenv("ENVIRONMENT", "development")
|
| 5 |
+
log_level = os.getenv("LOG_LEVEL", "INFO")
|
| 6 |
+
mongo_uri = os.getenv("MONGO_URI")
|
| 7 |
+
mongo_db = os.getenv("MONGO_DB_NAME", "insightfy-bloom").strip()
|
| 8 |
+
cache_url = os.getenv("CACHE_URL") or os.getenv("REDIS_URL")
|
| 9 |
+
cache_uri = os.getenv("CACHE_URI")
|
| 10 |
+
ok_mongo = bool(mongo_uri and mongo_db)
|
| 11 |
+
ok_cache = bool(cache_url or cache_uri)
|
| 12 |
+
if not ok_mongo:
|
| 13 |
+
raise ValueError("Missing Mongo configuration")
|
| 14 |
+
if not ok_cache:
|
| 15 |
+
raise ValueError("Missing Redis configuration")
|
| 16 |
+
return {
|
| 17 |
+
"environment": env,
|
| 18 |
+
"log_level": log_level,
|
| 19 |
+
"mongo_db": mongo_db,
|
| 20 |
+
"cache_defined": ok_cache
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
def validate_database_isolation():
|
| 24 |
+
expected = "insightfy-bloom"
|
| 25 |
+
mongo_db = os.getenv("MONGO_DB_NAME", "insightfy-bloom").strip()
|
| 26 |
+
return mongo_db == expected
|
| 27 |
+
|
app/middleware/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .security_middleware import create_security_middleware
|
| 2 |
+
|
| 3 |
+
__all__ = ["create_security_middleware"]
|
| 4 |
+
|
app/middleware/security_middleware.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Callable
|
| 2 |
+
from fastapi import Request, Response
|
| 3 |
+
from starlette.middleware.base import BaseHTTPMiddleware
|
| 4 |
+
|
| 5 |
+
class InsightfySecurityMiddleware(BaseHTTPMiddleware):
|
| 6 |
+
def __init__(self, app, enable_security_headers: bool = True):
|
| 7 |
+
super().__init__(app)
|
| 8 |
+
self.enable_security_headers = enable_security_headers
|
| 9 |
+
|
| 10 |
+
async def dispatch(self, request: Request, call_next: Callable):
|
| 11 |
+
response: Response = await call_next(request)
|
| 12 |
+
if self.enable_security_headers:
|
| 13 |
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
| 14 |
+
response.headers["X-Frame-Options"] = "DENY"
|
| 15 |
+
response.headers["X-XSS-Protection"] = "1; mode=block"
|
| 16 |
+
return response
|
| 17 |
+
|
| 18 |
+
def create_security_middleware(app, **kwargs):
|
| 19 |
+
return InsightfySecurityMiddleware(app, enable_security_headers=kwargs.get("enable_security_headers", True))
|
| 20 |
+
|
app/nosql.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
from datetime import datetime, timezone
|
| 3 |
+
from typing import Dict, Any
|
| 4 |
+
from insightfy_utils.logging import get_logger
|
| 5 |
+
from insightfy_utils.db.mongo_connector import create_mongo_connection
|
| 6 |
+
from insightfy_utils.db.redis_connector import create_redis_connection
|
| 7 |
+
from settings import (
|
| 8 |
+
MONGO_URI,
|
| 9 |
+
MONGO_DB_NAME,
|
| 10 |
+
CACHE_URL,
|
| 11 |
+
CACHE_URI,
|
| 12 |
+
CACHE_K,
|
| 13 |
+
CACHE_DB,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
logger = get_logger(__name__)
|
| 17 |
+
|
| 18 |
+
if not MONGO_URI or not MONGO_DB_NAME:
|
| 19 |
+
raise ValueError("MongoDB settings missing: ensure MONGO_URI and MONGO_DB_NAME are set.")
|
| 20 |
+
|
| 21 |
+
if not CACHE_URL and not CACHE_URI:
|
| 22 |
+
raise ValueError("Cache settings missing: set CACHE_URL or CACHE_URI.")
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
mongo_client, mongo_db = create_mongo_connection(
|
| 26 |
+
MONGO_URI,
|
| 27 |
+
MONGO_DB_NAME,
|
| 28 |
+
server_selection_timeout_ms=60000,
|
| 29 |
+
socket_timeout_ms=60000,
|
| 30 |
+
connect_timeout_ms=60000,
|
| 31 |
+
maxPoolSize=20,
|
| 32 |
+
retryWrites=True,
|
| 33 |
+
)
|
| 34 |
+
logger.info("MongoDB client initialized", extra={"db_name": MONGO_DB_NAME, "service": "insightfy-bms-ms-ums"})
|
| 35 |
+
except Exception as e:
|
| 36 |
+
logger.error("Failed to initialize MongoDB client", exc_info=e)
|
| 37 |
+
raise
|
| 38 |
+
|
| 39 |
+
def _redis_from_settings():
|
| 40 |
+
if CACHE_URL:
|
| 41 |
+
import redis.asyncio as redis
|
| 42 |
+
return redis.from_url(
|
| 43 |
+
CACHE_URL,
|
| 44 |
+
decode_responses=True,
|
| 45 |
+
socket_timeout=5,
|
| 46 |
+
socket_connect_timeout=5,
|
| 47 |
+
retry_on_timeout=True,
|
| 48 |
+
health_check_interval=30,
|
| 49 |
+
)
|
| 50 |
+
host, port_str = CACHE_URI.split(":")
|
| 51 |
+
port = int(port_str)
|
| 52 |
+
return create_redis_connection(
|
| 53 |
+
host=host,
|
| 54 |
+
port=port,
|
| 55 |
+
password=CACHE_K or None,
|
| 56 |
+
db=CACHE_DB,
|
| 57 |
+
decode_responses=True,
|
| 58 |
+
socket_timeout=5,
|
| 59 |
+
socket_connect_timeout=5,
|
| 60 |
+
retry_on_timeout=True,
|
| 61 |
+
health_check_interval=30,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
redis_client = _redis_from_settings()
|
| 66 |
+
logger.info("Redis client initialized", extra={"service": "insightfy-bms-ms-ums"})
|
| 67 |
+
except Exception as e:
|
| 68 |
+
logger.error("Failed to initialize Redis client", exc_info=e)
|
| 69 |
+
raise
|
| 70 |
+
|
| 71 |
+
async def ping_mongo() -> bool:
|
| 72 |
+
try:
|
| 73 |
+
await mongo_db.command("ping")
|
| 74 |
+
return True
|
| 75 |
+
except Exception:
|
| 76 |
+
return False
|
| 77 |
+
|
| 78 |
+
async def ping_redis() -> bool:
|
| 79 |
+
try:
|
| 80 |
+
pong = await redis_client.ping()
|
| 81 |
+
return bool(pong)
|
| 82 |
+
except Exception:
|
| 83 |
+
return False
|
| 84 |
+
|
| 85 |
+
async def get_database_status() -> Dict[str, Any]:
|
| 86 |
+
start_time = datetime.now(timezone.utc)
|
| 87 |
+
mongo_status = await ping_mongo()
|
| 88 |
+
redis_status = await ping_redis()
|
| 89 |
+
end_time = datetime.now(timezone.utc)
|
| 90 |
+
total_check_time = (end_time - start_time).total_seconds() * 1000
|
| 91 |
+
return {
|
| 92 |
+
"mongodb": {"connected": mongo_status, "database": MONGO_DB_NAME, "service": "insightfy-bms-ms-ums"},
|
| 93 |
+
"redis": {"connected": redis_status, "database": CACHE_DB, "service": "insightfy-bms-ms-ums"},
|
| 94 |
+
"overall_status": "healthy" if (mongo_status and redis_status) else "unhealthy",
|
| 95 |
+
"check_duration_ms": total_check_time,
|
| 96 |
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
| 97 |
+
"service": "insightfy-bms-ms-ums",
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
async def connect_stores() -> None:
|
| 101 |
+
ok_mongo = await ping_mongo()
|
| 102 |
+
ok_redis = await ping_redis()
|
| 103 |
+
if not (ok_mongo and ok_redis):
|
| 104 |
+
raise RuntimeError("Store connectivity check failed")
|
| 105 |
+
|
| 106 |
+
async def disconnect_stores() -> None:
|
| 107 |
+
try:
|
| 108 |
+
mongo_client.close()
|
| 109 |
+
except Exception:
|
| 110 |
+
pass
|
| 111 |
+
try:
|
| 112 |
+
await redis_client.close()
|
| 113 |
+
except Exception:
|
| 114 |
+
pass
|
| 115 |
+
|
| 116 |
+
async def get_connection_metrics() -> Dict[str, Any]:
|
| 117 |
+
try:
|
| 118 |
+
mongo_metrics = {"status": "connected"}
|
| 119 |
+
redis_metrics = {"status": "connected"}
|
| 120 |
+
try:
|
| 121 |
+
mongo_stats = await mongo_db.command("serverStatus")
|
| 122 |
+
mongo_metrics.update({
|
| 123 |
+
"connections": mongo_stats.get("connections", {}),
|
| 124 |
+
"network": mongo_stats.get("network", {}),
|
| 125 |
+
"opcounters": mongo_stats.get("opcounters", {}),
|
| 126 |
+
})
|
| 127 |
+
except Exception:
|
| 128 |
+
mongo_metrics["note"] = "Limited metrics"
|
| 129 |
+
try:
|
| 130 |
+
redis_info = await redis_client.info()
|
| 131 |
+
redis_metrics.update({
|
| 132 |
+
"connected_clients": redis_info.get("connected_clients", 0),
|
| 133 |
+
"used_memory": redis_info.get("used_memory", 0),
|
| 134 |
+
"keyspace_hits": redis_info.get("keyspace_hits", 0),
|
| 135 |
+
"keyspace_misses": redis_info.get("keyspace_misses", 0),
|
| 136 |
+
})
|
| 137 |
+
except Exception:
|
| 138 |
+
redis_metrics["note"] = "Limited metrics"
|
| 139 |
+
return {"mongodb": mongo_metrics, "redis": redis_metrics, "timestamp": datetime.now(timezone.utc).isoformat(), "service": "insightfy-bms-ms-ums"}
|
| 140 |
+
except Exception:
|
| 141 |
+
return {"error": "Failed to retrieve metrics", "timestamp": datetime.now(timezone.utc).isoformat(), "service": "insightfy-bms-ms-ums"}
|
| 142 |
+
|
| 143 |
+
db = mongo_db
|
| 144 |
+
|
app/routers/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = ["user"]
|
| 2 |
+
|
app/routers/user.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
import time
|
| 3 |
+
|
| 4 |
+
router = APIRouter()
|
| 5 |
+
|
| 6 |
+
@router.get("/health")
|
| 7 |
+
async def user_health():
|
| 8 |
+
return {"status": "healthy", "service": "insightfy-bms-ms-ums", "version": "1.0.0", "timestamp": time.time()}
|
| 9 |
+
|
deployment/docker-compose.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: "3.9"
|
| 2 |
+
services:
|
| 3 |
+
ms-bms-ums:
|
| 4 |
+
build: .
|
| 5 |
+
image: insightfy/ms-bms-ums:latest
|
| 6 |
+
container_name: ms-bms-ums
|
| 7 |
+
ports:
|
| 8 |
+
- "7861:7861"
|
| 9 |
+
environment:
|
| 10 |
+
- SERVICE_NAME=insightfy-bms-ms-ums
|
| 11 |
+
- MONGO_URI=${MONGO_URI}
|
| 12 |
+
- MONGO_DB_NAME=${MONGO_DB_NAME}
|
| 13 |
+
- CACHE_URL=${CACHE_URL}
|
| 14 |
+
- CACHE_URI=${CACHE_URI}
|
| 15 |
+
- CACHE_K=${CACHE_K}
|
| 16 |
+
- CACHE_DB=${CACHE_DB}
|
| 17 |
+
- ENVIRONMENT=${ENVIRONMENT:-development}
|
| 18 |
+
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
| 19 |
+
restart: unless-stopped
|
| 20 |
+
|
deployment/kubernetes/bms-ums-deployment.yaml
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
apiVersion: apps/v1
|
| 3 |
+
kind: Deployment
|
| 4 |
+
metadata:
|
| 5 |
+
name: ms-bms-ums
|
| 6 |
+
namespace: insightfy-app
|
| 7 |
+
labels:
|
| 8 |
+
app: ms-bms-ums
|
| 9 |
+
service: bms-ums
|
| 10 |
+
version: "1.0.0"
|
| 11 |
+
component: microservice
|
| 12 |
+
spec:
|
| 13 |
+
replicas: 2
|
| 14 |
+
strategy:
|
| 15 |
+
type: RollingUpdate
|
| 16 |
+
rollingUpdate:
|
| 17 |
+
maxUnavailable: 1
|
| 18 |
+
maxSurge: 1
|
| 19 |
+
selector:
|
| 20 |
+
matchLabels:
|
| 21 |
+
app: ms-bms-ums
|
| 22 |
+
template:
|
| 23 |
+
metadata:
|
| 24 |
+
labels:
|
| 25 |
+
app: ms-bms-ums
|
| 26 |
+
service: bms-ums
|
| 27 |
+
version: "1.0.0"
|
| 28 |
+
annotations:
|
| 29 |
+
prometheus.io/scrape: "true"
|
| 30 |
+
prometheus.io/port: "7861"
|
| 31 |
+
prometheus.io/path: "/ums/health/metrics"
|
| 32 |
+
spec:
|
| 33 |
+
containers:
|
| 34 |
+
- name: bms-ums
|
| 35 |
+
image: insightfy/ms-bms-ums:latest
|
| 36 |
+
imagePullPolicy: IfNotPresent
|
| 37 |
+
ports:
|
| 38 |
+
- containerPort: 7861
|
| 39 |
+
name: http
|
| 40 |
+
protocol: TCP
|
| 41 |
+
envFrom:
|
| 42 |
+
- configMapRef:
|
| 43 |
+
name: shared-env
|
| 44 |
+
- secretRef:
|
| 45 |
+
name: app-secrets
|
| 46 |
+
env:
|
| 47 |
+
- name: SERVICE_NAME
|
| 48 |
+
value: "insightfy-bms-ms-ums"
|
| 49 |
+
- name: MONGO_DB_NAME
|
| 50 |
+
value: "insightfy-bloom"
|
| 51 |
+
- name: ENVIRONMENT
|
| 52 |
+
value: "production"
|
| 53 |
+
- name: LOG_LEVEL
|
| 54 |
+
value: "INFO"
|
| 55 |
+
readinessProbe:
|
| 56 |
+
httpGet:
|
| 57 |
+
path: /ums/health/readiness
|
| 58 |
+
port: 7861
|
| 59 |
+
initialDelaySeconds: 10
|
| 60 |
+
periodSeconds: 15
|
| 61 |
+
livenessProbe:
|
| 62 |
+
httpGet:
|
| 63 |
+
path: /ums/health/liveness
|
| 64 |
+
port: 7861
|
| 65 |
+
initialDelaySeconds: 20
|
| 66 |
+
periodSeconds: 30
|
| 67 |
+
resources:
|
| 68 |
+
requests:
|
| 69 |
+
cpu: "100m"
|
| 70 |
+
memory: "128Mi"
|
| 71 |
+
limits:
|
| 72 |
+
cpu: "500m"
|
| 73 |
+
memory: "512Mi"
|
| 74 |
+
---
|
| 75 |
+
apiVersion: policy/v1
|
| 76 |
+
kind: PodDisruptionBudget
|
| 77 |
+
metadata:
|
| 78 |
+
name: ms-bms-ums-pdb
|
| 79 |
+
namespace: insightfy-app
|
| 80 |
+
labels:
|
| 81 |
+
app: ms-bms-ums
|
| 82 |
+
service: bms-ums
|
| 83 |
+
spec:
|
| 84 |
+
minAvailable: 1
|
| 85 |
+
selector:
|
| 86 |
+
matchLabels:
|
| 87 |
+
app: ms-bms-ums
|
| 88 |
+
---
|
| 89 |
+
apiVersion: networking.k8s.io/v1
|
| 90 |
+
kind: NetworkPolicy
|
| 91 |
+
metadata:
|
| 92 |
+
name: ms-bms-ums-netpol
|
| 93 |
+
namespace: insightfy-app
|
| 94 |
+
labels:
|
| 95 |
+
app: ms-bms-ums
|
| 96 |
+
service: bms-ums
|
| 97 |
+
spec:
|
| 98 |
+
podSelector:
|
| 99 |
+
matchLabels:
|
| 100 |
+
app: ms-bms-ums
|
| 101 |
+
policyTypes:
|
| 102 |
+
- Ingress
|
| 103 |
+
- Egress
|
| 104 |
+
ingress:
|
| 105 |
+
- from:
|
| 106 |
+
- namespaceSelector:
|
| 107 |
+
matchLabels:
|
| 108 |
+
name: insightfy-app
|
| 109 |
+
- namespaceSelector:
|
| 110 |
+
matchLabels:
|
| 111 |
+
name: ingress-nginx
|
| 112 |
+
ports:
|
| 113 |
+
- protocol: TCP
|
| 114 |
+
port: 7861
|
| 115 |
+
egress:
|
| 116 |
+
- to: []
|
| 117 |
+
ports:
|
| 118 |
+
- protocol: TCP
|
| 119 |
+
port: 27017
|
| 120 |
+
- protocol: TCP
|
| 121 |
+
port: 6379
|
| 122 |
+
- protocol: TCP
|
| 123 |
+
port: 443
|
| 124 |
+
- protocol: TCP
|
| 125 |
+
port: 80
|
| 126 |
+
- protocol: UDP
|
| 127 |
+
port: 53
|
| 128 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.104.0
|
| 2 |
+
uvicorn[standard]>=0.24.0
|
| 3 |
+
pydantic>=2.0.0
|
| 4 |
+
pydantic-settings>=2.0.0
|
| 5 |
+
motor>=3.3.0
|
| 6 |
+
redis>=5.0.0
|
| 7 |
+
python-jose[cryptography]>=3.3.0
|
| 8 |
+
passlib[bcrypt]>=1.7.4
|
| 9 |
+
python-multipart>=0.0.6
|
| 10 |
+
python-dotenv>=1.0.0
|
| 11 |
+
insightfy-utils>=0.1.0
|
| 12 |
+
pytest>=7.0.0
|
| 13 |
+
pytest-asyncio>=0.21.0
|
| 14 |
+
hypothesis>=6.0.0
|
| 15 |
+
email-validator>=2.0.0
|
| 16 |
+
httpx>=0.24.0
|
| 17 |
+
databases>=0.8.0
|
| 18 |
+
pandas>=1.5.0
|
| 19 |
+
bleach>=6.0.0
|
| 20 |
+
psutil>=5.9.0
|
| 21 |
+
spacy>=3.5.0
|
| 22 |
+
|
settings.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
from urllib.parse import quote_plus, urlencode
|
| 3 |
+
import os
|
| 4 |
+
from insightfy_utils.config import load_env
|
| 5 |
+
load_env()
|
| 6 |
+
SECRET_KEY = os.getenv("SECRET_KEY")
|
| 7 |
+
ALGORITHM = os.getenv("ALGORITHM", "HS256")
|
| 8 |
+
MONGO_URI = os.getenv('MONGO_URI')
|
| 9 |
+
MONGO_DB_NAME = os.getenv("MONGO_DB_NAME", "insightfy-bloom").strip()
|
| 10 |
+
|
| 11 |
+
def _sanitize_mongo_uri(uri: str | None) -> str | None:
|
| 12 |
+
if not uri:
|
| 13 |
+
return uri
|
| 14 |
+
uri = uri.replace("??", "?")
|
| 15 |
+
if "?" in uri:
|
| 16 |
+
base_uri, options_part = uri.split("?", 1)
|
| 17 |
+
options = options_part.split("&")
|
| 18 |
+
valid_options = []
|
| 19 |
+
for opt in options:
|
| 20 |
+
if not opt:
|
| 21 |
+
continue
|
| 22 |
+
key = opt.split("=")[0] if "=" in opt else opt
|
| 23 |
+
if key in ["?ssl", "ssl_cert_reqs"]:
|
| 24 |
+
continue
|
| 25 |
+
valid_options.append(opt)
|
| 26 |
+
return f"{base_uri}?{'&'.join(valid_options)}"
|
| 27 |
+
return uri
|
| 28 |
+
|
| 29 |
+
MONGO_URI = _sanitize_mongo_uri(os.getenv('MONGO_URI'))
|
| 30 |
+
CACHE_URL = os.getenv("CACHE_URL") or os.getenv("REDIS_URL")
|
| 31 |
+
CACHE_URI = os.getenv('CACHE_URI')
|
| 32 |
+
CACHE_K = os.getenv('CACHE_K')
|
| 33 |
+
CACHE_DB = int(os.getenv("CACHE_DB", "0"))
|
| 34 |
+
|