- Fix missing await on FocusGroup.get_messages() (N-L1) - Replace time.sleep with asyncio.sleep in key_theme_service and focus_group_service (N-P10) - Replace flask import with quart in focus_groups.py (N-S3) - Add logger.error before all 500 returns in focus_groups.py (N-P6) - Add logging to silent except blocks across routes (N-M10, N-M11) - Add @rate_limit to 6 remaining AI endpoints (N-H4) - Add --confirm flag to populate scripts before delete_many (S-H2) - Remove hardcoded Azure ID fallbacks from msal_service.py and msalConfig.ts (A-M2, F-H4) - Centralize make_serializable() in utils.py, remove duplicates from 3 route files (N-P7) - Replace all datetime.utcnow() with datetime.now(timezone.utc) across entire backend (M-L2) - AuthContext.tsx: only mark token validated on 200 success, not on non-401 errors (F-H2) - Rename authType → auth_type in auth.py (N-S4) - Add security_report.md and security_report.pdf with full 92-finding status Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
78 lines
No EOL
3 KiB
Python
Executable file
78 lines
No EOL
3 KiB
Python
Executable file
from motor.motor_asyncio import AsyncIOMotorClient
|
|
from pymongo import MongoClient
|
|
import os
|
|
import logging
|
|
|
|
# Global Motor client singleton - per event loop
|
|
_motor_clients = {} # event_loop_id -> (client, database)
|
|
|
|
async def get_db():
|
|
"""Get database connection using singleton Motor client per event loop."""
|
|
import asyncio
|
|
|
|
# Get current event loop to ensure Motor client affinity
|
|
try:
|
|
current_loop = asyncio.get_running_loop()
|
|
loop_id = id(current_loop)
|
|
except RuntimeError:
|
|
raise RuntimeError("get_db() must be called from within an async context")
|
|
|
|
# Return cached database for this event loop if available
|
|
if loop_id in _motor_clients:
|
|
client, database = _motor_clients[loop_id]
|
|
return database
|
|
|
|
# Read MongoDB connection from environment
|
|
mongo_uri = os.environ.get('MONGO_URI')
|
|
mongo_user = os.environ.get('MONGO_USER')
|
|
mongo_pass = os.environ.get('MONGO_PASS')
|
|
mongo_host = os.environ.get('MONGO_HOST', 'localhost')
|
|
mongo_port = os.environ.get('MONGO_PORT', '27017')
|
|
|
|
# Build URI: prefer MONGO_URI, fall back to host+port with optional credentials
|
|
if not mongo_uri:
|
|
if mongo_user and mongo_pass:
|
|
mongo_uri = f"mongodb://{mongo_user}:{mongo_pass}@{mongo_host}:{mongo_port}/semblance_db?authSource=admin"
|
|
else:
|
|
mongo_uri = f"mongodb://{mongo_host}:{mongo_port}"
|
|
|
|
try:
|
|
motor_client = AsyncIOMotorClient(mongo_uri, serverSelectionTimeoutMS=5000)
|
|
database = motor_client.semblance_db
|
|
await database.command('ping')
|
|
logging.info("Successfully connected to MongoDB")
|
|
except Exception as e:
|
|
raise RuntimeError(f"Failed to connect to MongoDB: {e}. Check MONGO_URI in backend/.env.") from e
|
|
|
|
_motor_clients[loop_id] = (motor_client, database)
|
|
|
|
# Ensure indexes exist (idempotent)
|
|
try:
|
|
await database.users.create_index("username", unique=True, background=True)
|
|
await database.users.create_index("email", unique=True, background=True)
|
|
await database.personas.create_index("created_by", background=True)
|
|
await database.focus_groups.create_index("created_by", background=True)
|
|
await database.folders.create_index("created_by", background=True)
|
|
await database.folders.create_index("parent_folder_id", background=True)
|
|
except Exception as e:
|
|
logging.warning(f"Index creation warning (non-fatal): {e}")
|
|
|
|
return database
|
|
|
|
|
|
def close_db_connections():
|
|
"""Close all Motor clients and their PyMongo background threads."""
|
|
global _motor_clients
|
|
|
|
closed_count = 0
|
|
for loop_id, (client, database) in _motor_clients.items():
|
|
try:
|
|
client.close()
|
|
closed_count += 1
|
|
except Exception as e:
|
|
logging.warning(f"Error closing Motor client for loop {loop_id}: {e}")
|
|
|
|
if closed_count > 0:
|
|
logging.info(f"🗄️ Closed {closed_count} Motor clients - PyMongo threads should stop")
|
|
|
|
_motor_clients.clear() |