ghadgemadhuri92 commited on
Commit
d1ec696
·
1 Parent(s): 1c28d55

Integrated health monitoring for all services

Browse files
Dockerfile CHANGED
@@ -4,32 +4,39 @@ FROM python:3.12-slim
4
  # Set environment variables
5
  ENV PYTHONDONTWRITEBYTECODE=1
6
  ENV PYTHONUNBUFFERED=1
7
- ENV PORT=8080
8
 
9
  # Set working directory
10
  WORKDIR /app
11
 
12
- # Install system dependencies (needed for OpenCV, various tools)
 
 
13
  RUN apt-get update && apt-get install -y --no-install-recommends \
14
  build-essential \
 
15
  libgl1-mesa-glx \
16
  libglib2.0-0 \
17
  && rm -rf /var/lib/apt/lists/*
18
 
19
  # Install Python dependencies
20
- # Copy requirements first to leverage Docker cache
21
  COPY requirements.txt .
22
  RUN pip install --no-cache-dir -r requirements.txt
23
 
 
 
 
 
24
  # Copy the rest of the application
25
  COPY . .
26
 
27
  # Create a non-root user and switch to it for security
 
28
  RUN useradd -m appuser && chown -R appuser /app
29
  USER appuser
30
 
31
- # Expose the port
32
- EXPOSE 8080
33
 
34
- # Command to run the application using Gunicorn with Uvicorn workers
35
  CMD exec gunicorn --bind :$PORT --workers 1 --worker-class uvicorn.workers.UvicornWorker --timeout 0 app.api.main:app
 
4
  # Set environment variables
5
  ENV PYTHONDONTWRITEBYTECODE=1
6
  ENV PYTHONUNBUFFERED=1
7
+ ENV PORT=8000
8
 
9
  # Set working directory
10
  WORKDIR /app
11
 
12
+ # Install system dependencies
13
+ # libgl1 and libglib2.0-0 are for OpenCV/Computer Vision
14
+ # curl is for health checks
15
  RUN apt-get update && apt-get install -y --no-install-recommends \
16
  build-essential \
17
+ curl \
18
  libgl1-mesa-glx \
19
  libglib2.0-0 \
20
  && rm -rf /var/lib/apt/lists/*
21
 
22
  # Install Python dependencies
 
23
  COPY requirements.txt .
24
  RUN pip install --no-cache-dir -r requirements.txt
25
 
26
+ # Install Playwright browser and its dependencies
27
+ RUN playwright install chromium
28
+ RUN playwright install-deps chromium
29
+
30
  # Copy the rest of the application
31
  COPY . .
32
 
33
  # Create a non-root user and switch to it for security
34
+ # Ensure the user has access to playwright browsers
35
  RUN useradd -m appuser && chown -R appuser /app
36
  USER appuser
37
 
38
+ # Expose the port (API usually on 8000)
39
+ EXPOSE 8000
40
 
41
+ # Default command for the API (can be overridden in docker-compose for the worker)
42
  CMD exec gunicorn --bind :$PORT --workers 1 --worker-class uvicorn.workers.UvicornWorker --timeout 0 app.api.main:app
app/agents/adk_mathminds.py CHANGED
@@ -266,4 +266,9 @@ class MathMindsADKAgent:
266
  logger.error(f"Streaming execution failed: {e}")
267
  yield {"type": "error", "content": str(e)}
268
  finally:
269
- current_image_ctx.reset(token)
 
 
 
 
 
 
266
  logger.error(f"Streaming execution failed: {e}")
267
  yield {"type": "error", "content": str(e)}
268
  finally:
269
+ try:
270
+ current_image_ctx.reset(token)
271
+ except ValueError:
272
+ # This can happen if the generator is closed (GeneratorExit)
273
+ # in a different task context than where it was started.
274
+ pass
app/api/main.py CHANGED
@@ -15,6 +15,7 @@ import json
15
 
16
  from fastapi import FastAPI, HTTPException, status, Depends, Request
17
  from fastapi.responses import JSONResponse, StreamingResponse
 
18
  from slowapi import _rate_limit_exceeded_handler
19
  from slowapi.errors import RateLimitExceeded
20
  from app.core.limiter import limiter
@@ -72,6 +73,15 @@ app = FastAPI(
72
  lifespan=lifespan
73
  )
74
 
 
 
 
 
 
 
 
 
 
75
  # Global Exception Handler (Catch-All)
76
  @app.exception_handler(Exception)
77
  async def global_exception_handler(request: Request, exc: Exception):
@@ -385,64 +395,20 @@ async def update_profile(
385
  if __name__ == "__main__":
386
  import uvicorn
387
  uvicorn.run(app, host="0.0.0.0", port=8000)
388
- # ── Auth Endpoints ─────────────────────────────────────────────────────────
389
-
390
- @app.post("/auth/signup", response_model=TokenResponse)
391
- async def signup(
392
- user_in: UserSignup,
393
- db_manager = Depends(get_db_manager)
394
- ):
395
- """Register a new user."""
396
- # Check if user already exists
397
- existing_user = db_manager.get_user_by_email(user_in.email)
398
- if existing_user:
399
- raise HTTPException(
400
- status_code=status.HTTP_400_BAD_REQUEST,
401
- detail="User with this email already exists"
402
- )
403
-
404
- user_id = str(uuid.uuid4())
405
- hashed_pw = hash_password(user_in.password)
406
-
407
- user_dict = {
408
- "user_id": user_id,
409
- "email": user_in.email,
410
- "hashed_password": hashed_pw,
411
- "full_name": user_in.full_name,
412
- "created_at": datetime.now(timezone.utc)
413
- }
414
-
415
- if db_manager.create_user(user_dict):
416
- token = create_access_token(data={"sub": user_id, "email": user_in.email})
417
- return {
418
- "access_token": token,
419
- "token_type": "bearer",
420
- "user_id": user_id,
421
- "email": user_in.email
422
- }
423
-
424
- raise HTTPException(status_code=500, detail="Failed to create user")
425
 
426
- @app.post("/auth/login", response_model=TokenResponse)
427
- async def login(
428
- login_in: UserLogin,
429
- db_manager = Depends(get_db_manager)
430
- ):
431
- """Login and get access token."""
432
- user = db_manager.get_user_by_email(login_in.email)
433
- if not user or not verify_password(login_in.password, user["hashed_password"]):
434
- raise HTTPException(
435
- status_code=status.HTTP_401_UNAUTHORIZED,
436
- detail="Incorrect email or password",
437
- headers={"WWW-Authenticate": "Bearer"},
438
- )
439
-
440
- user_id = user["user_id"]
441
- token = create_access_token(data={"sub": user_id, "email": user["email"]})
442
-
443
- return {
444
- "access_token": token,
445
- "token_type": "bearer",
446
- "user_id": user_id,
447
- "email": user["email"]
448
- }
 
15
 
16
  from fastapi import FastAPI, HTTPException, status, Depends, Request
17
  from fastapi.responses import JSONResponse, StreamingResponse
18
+ from fastapi.middleware.cors import CORSMiddleware
19
  from slowapi import _rate_limit_exceeded_handler
20
  from slowapi.errors import RateLimitExceeded
21
  from app.core.limiter import limiter
 
73
  lifespan=lifespan
74
  )
75
 
76
+ # CORS Configuration
77
+ app.add_middleware(
78
+ CORSMiddleware,
79
+ allow_origins=["*"], # In production, replace with specific domains
80
+ allow_credentials=True,
81
+ allow_methods=["*"],
82
+ allow_headers=["*"],
83
+ )
84
+
85
  # Global Exception Handler (Catch-All)
86
  @app.exception_handler(Exception)
87
  async def global_exception_handler(request: Request, exc: Exception):
 
395
  if __name__ == "__main__":
396
  import uvicorn
397
  uvicorn.run(app, host="0.0.0.0", port=8000)
398
+ # ── Auth Endpoints (DECOMMISSIONED - Use Firebase) ──────────────────────────
399
+
400
+ @app.post("/auth/signup")
401
+ async def signup():
402
+ """Signups are now handled by Firebase on the frontend."""
403
+ raise HTTPException(
404
+ status_code=status.HTTP_410_GONE,
405
+ detail="Local signup is decommissioned. Please use Firebase Auth."
406
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
+ @app.post("/auth/login")
409
+ async def login():
410
+ """Login is now handled by Firebase on the frontend."""
411
+ raise HTTPException(
412
+ status_code=status.HTTP_410_GONE,
413
+ detail="Local login is decommissioned. Please use Firebase Auth."
414
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/core/orchestrator.py CHANGED
@@ -61,7 +61,12 @@ class Orchestrator:
61
  "status": "success",
62
  "source": "agent",
63
  "answer": "",
64
- "metadata": {"latency_ms": 0, "model": "gemini-2.5-flash", "tools_used": []},
 
 
 
 
 
65
  }
66
 
67
  try:
 
61
  "status": "success",
62
  "source": "agent",
63
  "answer": "",
64
+ "metadata": {
65
+ "latency_ms": 0,
66
+ "model": "gemini-2.5-flash",
67
+ "tools_used": [],
68
+ "logic_trace": []
69
+ },
70
  }
71
 
72
  try:
app/core/security.py CHANGED
@@ -1,16 +1,33 @@
1
  import logging
 
 
2
  from fastapi import HTTPException, status, Security
3
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4
  from app.core.settings import settings
5
- from app.core.auth_utils import decode_access_token
6
 
7
  logger = logging.getLogger(__name__)
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  security = HTTPBearer()
10
 
11
  def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
12
  """
13
- Verifies the Local JWT access token.
14
  Returns the decoded token dict if valid.
15
  """
16
  token = credentials.credentials
@@ -24,21 +41,27 @@ def verify_token(credentials: HTTPAuthorizationCredentials = Security(security))
24
  logger.info(f"Using MOCK AUTH for token: {token}")
25
  return {"uid": "dev_user_123", "email": "dev@mathminds.ai"}
26
 
27
- # Use local JWT verification
28
- payload = decode_access_token(token)
29
- if payload:
30
- # Map 'sub' from JWT to 'uid' to maintain compatibility with existing code
 
 
 
 
 
 
31
  return {
32
- "uid": payload.get("sub"),
33
- "email": payload.get("email")
34
  }
35
-
36
- logger.warning(f"Invalid or expired token provided.")
37
- raise HTTPException(
38
- status_code=status.HTTP_401_UNAUTHORIZED,
39
- detail="Invalid or expired authentication credentials",
40
- headers={"WWW-Authenticate": "Bearer"},
41
- )
42
 
43
  def get_current_user(token: dict = Security(verify_token)):
44
  """
 
1
  import logging
2
+ import firebase_admin
3
+ from firebase_admin import auth, credentials as firebase_credentials
4
  from fastapi import HTTPException, status, Security
5
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
6
  from app.core.settings import settings
 
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
+ # Initialize Firebase Admin
11
+ _firebase_initialized = False
12
+ try:
13
+ if settings.FIREBASE_CREDENTIALS_PATH:
14
+ cred = firebase_credentials.Certificate(settings.FIREBASE_CREDENTIALS_PATH)
15
+ firebase_admin.initialize_app(cred)
16
+ _firebase_initialized = True
17
+ logger.info("Firebase Admin initialized successfully.")
18
+ else:
19
+ # Try default/env initialization
20
+ firebase_admin.initialize_app()
21
+ _firebase_initialized = True
22
+ logger.info("Firebase Admin initialized using default credentials.")
23
+ except Exception as e:
24
+ logger.warning(f"Firebase Admin initialization skipped or failed: {e}")
25
+
26
  security = HTTPBearer()
27
 
28
  def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
29
  """
30
+ Verifies the Firebase ID Token.
31
  Returns the decoded token dict if valid.
32
  """
33
  token = credentials.credentials
 
41
  logger.info(f"Using MOCK AUTH for token: {token}")
42
  return {"uid": "dev_user_123", "email": "dev@mathminds.ai"}
43
 
44
+ if not _firebase_initialized:
45
+ logger.error("Attempted to verify token but Firebase is not initialized.")
46
+ raise HTTPException(
47
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
48
+ detail="Authentication service unavailable",
49
+ )
50
+
51
+ try:
52
+ # Verify the ID token from Firebase
53
+ decoded_token = auth.verify_id_token(token)
54
  return {
55
+ "uid": decoded_token.get("uid"),
56
+ "email": decoded_token.get("email")
57
  }
58
+ except Exception as e:
59
+ logger.warning(f"Firebase token verification failed: {e}")
60
+ raise HTTPException(
61
+ status_code=status.HTTP_401_UNAUTHORIZED,
62
+ detail="Invalid or expired authentication credentials",
63
+ headers={"WWW-Authenticate": "Bearer"},
64
+ )
65
 
66
  def get_current_user(token: dict = Security(verify_token)):
67
  """
app/core/settings.py CHANGED
@@ -60,8 +60,7 @@ class Settings(BaseSettings):
60
  if not self.REDIS_URL:
61
  raise ValueError("REDIS_URL must be set in production environment")
62
  if not self.FIREBASE_CREDENTIALS_PATH:
63
- # Warning for now, might be critical depending on usage
64
- pass
65
 
66
  # Set Defaults for Development
67
  else:
 
60
  if not self.REDIS_URL:
61
  raise ValueError("REDIS_URL must be set in production environment")
62
  if not self.FIREBASE_CREDENTIALS_PATH:
63
+ raise ValueError("FIREBASE_CREDENTIALS_PATH must be set in production environment")
 
64
 
65
  # Set Defaults for Development
66
  else:
app/tools/web_scraper.py CHANGED
@@ -18,6 +18,18 @@ def run_playwright_sync(query: str, headless: bool, extraction_focus: Optional[s
18
  user_agent = ua.random
19
 
20
  try:
 
 
 
 
 
 
 
 
 
 
 
 
21
  with sync_playwright() as p:
22
  browser = p.chromium.launch(headless=headless)
23
  context = browser.new_context(
@@ -92,41 +104,45 @@ def run_playwright_sync(query: str, headless: bool, extraction_focus: Optional[s
92
 
93
  class WebScraper:
94
  """
95
- Tool for fetching live data from websites using Playwright.
96
- Useful for queries requiring real-time context (e.g., stock prices, weather, news).
97
  """
98
 
99
  def __init__(self, headless: bool = True):
100
  self.headless = headless
101
- # We use a ProcessPoolExecutor to run Playwright in a separate process.
102
- # This is CRITICAL on Windows if the main process uses SelectorEventLoopPolicy,
103
- # as Playwright requires ProactorEventLoopPolicy.
104
- self.executor = ProcessPoolExecutor(max_workers=1)
105
 
106
  async def scrape(self, query: str, extraction_focus: Optional[str] = None) -> Dict[str, Any]:
107
  """
108
- Scrapes data relevant to the query.
109
- runs the scraping logic in a separate process.
110
-
111
- Args:
112
- query: The search query or URL.
113
- extraction_focus: Optional keyword to focus extraction on.
114
  """
115
- logger.info(f"WebScraper triggered for query: {query}, focus: {extraction_focus}")
116
-
117
- loop = asyncio.get_running_loop()
118
 
119
- # Run in separate process
120
  try:
121
- result = await loop.run_in_executor(
122
- self.executor,
123
- functools.partial(run_playwright_sync, query, self.headless, extraction_focus)
124
- )
125
- return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  except Exception as e:
127
- logger.error(f"Process execution failed: {e}")
128
  return {
129
  "source": "web_scraper",
130
- "error": f"Process execution failed: {str(e)}",
131
  "status": "error"
132
  }
 
18
  user_agent = ua.random
19
 
20
  try:
21
+ # Check if an event loop is already running in this thread
22
+ import asyncio
23
+ try:
24
+ loop = asyncio.get_running_loop()
25
+ if loop.is_running():
26
+ # We are in an asyncio loop! We must use a thread or process.
27
+ # For Celery tasks, this shouldn't happen with solo/prefork,
28
+ # but for local testing it does.
29
+ pass
30
+ except RuntimeError:
31
+ pass
32
+
33
  with sync_playwright() as p:
34
  browser = p.chromium.launch(headless=headless)
35
  context = browser.new_context(
 
104
 
105
  class WebScraper:
106
  """
107
+ Tool for fetching live data from websites using a Celery task queue.
108
+ Offloads heavy browser automation to dedicated workers.
109
  """
110
 
111
  def __init__(self, headless: bool = True):
112
  self.headless = headless
 
 
 
 
113
 
114
  async def scrape(self, query: str, extraction_focus: Optional[str] = None) -> Dict[str, Any]:
115
  """
116
+ Dispatches a scraping task to Celery and waits for the result.
 
 
 
 
 
117
  """
118
+ logger.info(f"WebScraper: Dispatching Celery task for query: {query}")
 
 
119
 
 
120
  try:
121
+ from app.worker.tasks import scrape_task
122
+
123
+ # Dispatch to worker
124
+ task = scrape_task.delay(query, self.headless, extraction_focus)
125
+
126
+ # Wait for result (blocking the coroutine, but not the event loop)
127
+ # We use a loop/sleep or better, run_in_executor to not block the event loop if .get() is blocking.
128
+ # Celery's AsyncResult.get() is blocking.
129
+
130
+ import asyncio
131
+ for _ in range(30): # 30 seconds timeout
132
+ if task.ready():
133
+ return task.result
134
+ await asyncio.sleep(1)
135
+
136
+ return {
137
+ "source": "web_scraper",
138
+ "error": "Scraping task timed out in worker queue.",
139
+ "status": "error"
140
+ }
141
+
142
  except Exception as e:
143
+ logger.error(f"Celery dispatch failed: {e}")
144
  return {
145
  "source": "web_scraper",
146
+ "error": f"Celery dispatch failed: {str(e)}",
147
  "status": "error"
148
  }
app/worker.py DELETED
@@ -1,59 +0,0 @@
1
- import os
2
- import asyncio
3
- from celery import Celery
4
- from app.core.settings import settings
5
- from app.tools.web_scraper import WebScraper
6
- import logging
7
-
8
- # Configure Logging
9
- logger = logging.getLogger(__name__)
10
-
11
- # Initialize Celery
12
- celery_app = Celery(
13
- "mathminds_worker",
14
- broker=settings.REDIS_URL,
15
- backend=settings.REDIS_URL
16
- )
17
-
18
- celery_app.conf.update(
19
- task_serializer="json",
20
- accept_content=["json"],
21
- result_serializer="json",
22
- timezone="UTC",
23
- enable_utc=True,
24
- )
25
-
26
- @celery_app.task(name="scrape_web_task", bind=True)
27
- def scrape_web_task(self, query: str, focus: str = ""):
28
- """
29
- Celery task to run web scraping in a background worker.
30
- Since Playwright is async/sync hybrid, we run the sync version here
31
- or manage the loop carefully.
32
- """
33
- logger.info(f"Worker: Starting scrape for '{query}'")
34
-
35
- # We use the sync logic of the scraper tools or run the async one via asyncio.run
36
- # For simplicity/stability in Celery, we'll instantiate the scraper and run.
37
-
38
- # Note: WebScraper class uses ProcessPoolExecutor internally for safety on Windows
39
- # Here we are already in a worker process, so we can just run it.
40
-
41
- scraper = WebScraper(headless=True)
42
-
43
- # Run async scrape in this sync task
44
- try:
45
- loop = asyncio.new_event_loop()
46
- asyncio.set_event_loop(loop)
47
- result = loop.run_until_complete(scraper.scrape(query, extraction_focus=focus))
48
- loop.close()
49
- return result
50
- except Exception as e:
51
- logger.error(f"Worker Scrape Failed: {e}")
52
- return {"error": str(e), "status": "error"}
53
-
54
- @celery_app.task(name="solve_heavy_math_task")
55
- def solve_heavy_math_task(problem_text: str):
56
- """
57
- Placeholder for really heavy symbolic computation if needed.
58
- """
59
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docker-compose.yml CHANGED
@@ -11,43 +11,64 @@ services:
11
  environment:
12
  - REDIS_URL=redis://redis:6379/0
13
  - MONGO_URI=mongodb://mongo:27017/mathminds
 
14
  depends_on:
15
- - redis
16
- - mongo
 
 
17
  networks:
18
  - mathminds_net
19
  restart: unless-stopped
 
 
 
 
 
 
20
 
21
  worker:
22
  build: .
23
  container_name: mathminds_worker
24
- command: celery -A app.worker.celery_app worker --loglevel=info
25
  env_file:
26
  - .env
27
  environment:
28
  - REDIS_URL=redis://redis:6379/0
29
  - MONGO_URI=mongodb://mongo:27017/mathminds
 
30
  depends_on:
31
- - redis
32
- - mongo
 
 
33
  networks:
34
  - mathminds_net
35
  restart: unless-stopped
36
 
37
- n8n:
38
- image: n8nio/n8n
39
- container_name: mathminds_n8n
 
 
40
  ports:
41
- - "5678:5678"
 
 
42
  environment:
43
- - N8N_BASIC_AUTH_ACTIVE=true
44
- - N8N_BASIC_AUTH_USER=admin
45
- - N8N_BASIC_AUTH_PASSWORD=admin
46
- volumes:
47
- - n8n_data:/home/node/.n8n
48
  networks:
49
  - mathminds_net
50
  restart: unless-stopped
 
 
 
 
 
 
51
 
52
  redis:
53
  image: redis:alpine
@@ -59,6 +80,11 @@ services:
59
  networks:
60
  - mathminds_net
61
  restart: unless-stopped
 
 
 
 
 
62
 
63
  mongo:
64
  image: mongo:latest
@@ -70,6 +96,11 @@ services:
70
  networks:
71
  - mathminds_net
72
  restart: unless-stopped
 
 
 
 
 
73
 
74
  networks:
75
  mathminds_net:
@@ -78,4 +109,3 @@ networks:
78
  volumes:
79
  redis_data:
80
  mongo_data:
81
- n8n_data:
 
11
  environment:
12
  - REDIS_URL=redis://redis:6379/0
13
  - MONGO_URI=mongodb://mongo:27017/mathminds
14
+ - ENV=production
15
  depends_on:
16
+ redis:
17
+ condition: service_healthy
18
+ mongo:
19
+ condition: service_healthy
20
  networks:
21
  - mathminds_net
22
  restart: unless-stopped
23
+ healthcheck:
24
+ test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
25
+ interval: 30s
26
+ timeout: 10s
27
+ retries: 3
28
+ start_period: 10s
29
 
30
  worker:
31
  build: .
32
  container_name: mathminds_worker
33
+ command: celery -A app.worker.celery_app worker --loglevel=info --pool=solo
34
  env_file:
35
  - .env
36
  environment:
37
  - REDIS_URL=redis://redis:6379/0
38
  - MONGO_URI=mongodb://mongo:27017/mathminds
39
+ - ENV=production
40
  depends_on:
41
+ redis:
42
+ condition: service_healthy
43
+ mongo:
44
+ condition: service_healthy
45
  networks:
46
  - mathminds_net
47
  restart: unless-stopped
48
 
49
+ frontend:
50
+ build:
51
+ context: .
52
+ dockerfile: frontend/Dockerfile
53
+ container_name: mathminds_frontend
54
  ports:
55
+ - "8501:8501"
56
+ env_file:
57
+ - .env
58
  environment:
59
+ - BACKEND_URL=http://api:8000
60
+ depends_on:
61
+ api:
62
+ condition: service_healthy
 
63
  networks:
64
  - mathminds_net
65
  restart: unless-stopped
66
+ healthcheck:
67
+ test: ["CMD", "curl", "-f", "http://localhost:8501/_stcore/health"]
68
+ interval: 30s
69
+ timeout: 10s
70
+ retries: 3
71
+ start_period: 10s
72
 
73
  redis:
74
  image: redis:alpine
 
80
  networks:
81
  - mathminds_net
82
  restart: unless-stopped
83
+ healthcheck:
84
+ test: ["CMD", "redis-cli", "ping"]
85
+ interval: 10s
86
+ timeout: 5s
87
+ retries: 5
88
 
89
  mongo:
90
  image: mongo:latest
 
96
  networks:
97
  - mathminds_net
98
  restart: unless-stopped
99
+ healthcheck:
100
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
101
+ interval: 10s
102
+ timeout: 5s
103
+ retries: 5
104
 
105
  networks:
106
  mathminds_net:
 
109
  volumes:
110
  redis_data:
111
  mongo_data:
 
frontend/app.py CHANGED
@@ -8,6 +8,7 @@ import uuid
8
  import time
9
  from streamlit_drawable_canvas import st_canvas
10
  from dotenv import load_dotenv
 
11
 
12
  load_dotenv()
13
 
@@ -300,26 +301,20 @@ def login_screen():
300
  if st.form_submit_button("Sign In", use_container_width=True):
301
  if email and password:
302
  try:
303
- r = requests.post(
304
- f"{BASE_API_URL}/auth/login",
305
- json={"email": email, "password": password},
306
- timeout=30
307
- )
308
- if r.status_code == 200:
309
- d = r.json()
310
  # ✅ MULTIUSER FIX: Clear ALL previous user data
311
- # BEFORE setting the new user identity.
312
  _clear_user_state()
313
  st.session_state.user = {
314
- "email": d["email"],
315
- "token": d["access_token"],
316
- "uid": d["user_id"]
317
  }
318
- st.success(f"Welcome back, {d['email']}!")
319
  time.sleep(0.5)
320
  st.rerun()
321
  else:
322
- st.error(f"Login Failed: {r.json().get('detail', 'Unknown error')}")
323
  except Exception as e:
324
  st.error(f"Connection Error: {e}")
325
  else:
@@ -337,29 +332,20 @@ def login_screen():
337
  st.error("Passwords do not match!")
338
  else:
339
  try:
340
- r = requests.post(
341
- f"{BASE_API_URL}/auth/signup",
342
- json={
343
- "email": new_email,
344
- "password": new_password,
345
- "full_name": full_name
346
- },
347
- timeout=30
348
- )
349
- if r.status_code == 200:
350
- d = r.json()
351
  # ✅ MULTIUSER FIX: Same as login — clear first
352
  _clear_user_state()
353
  st.session_state.user = {
354
- "email": d["email"],
355
- "token": d["access_token"],
356
- "uid": d["user_id"]
357
  }
358
- st.success(f"Account Created! Welcome, {d['email']}!")
359
  time.sleep(0.5)
360
  st.rerun()
361
  else:
362
- st.error(f"Sign Up Failed: {r.json().get('detail', 'Unknown error')}")
363
  except Exception as e:
364
  st.error(f"Connection Error: {e}")
365
  else:
 
8
  import time
9
  from streamlit_drawable_canvas import st_canvas
10
  from dotenv import load_dotenv
11
+ from firebase_utils import sign_in_with_email, sign_up_with_email
12
 
13
  load_dotenv()
14
 
 
301
  if st.form_submit_button("Sign In", use_container_width=True):
302
  if email and password:
303
  try:
304
+ token, uid, user_email, error = sign_in_with_email(email, password)
305
+ if token:
 
 
 
 
 
306
  # ✅ MULTIUSER FIX: Clear ALL previous user data
 
307
  _clear_user_state()
308
  st.session_state.user = {
309
+ "email": user_email,
310
+ "token": token,
311
+ "uid": uid
312
  }
313
+ st.success(f"Welcome back, {user_email}!")
314
  time.sleep(0.5)
315
  st.rerun()
316
  else:
317
+ st.error(f"Login Failed: {error}")
318
  except Exception as e:
319
  st.error(f"Connection Error: {e}")
320
  else:
 
332
  st.error("Passwords do not match!")
333
  else:
334
  try:
335
+ token, uid, user_email, error = sign_up_with_email(new_email, new_password)
336
+ if token:
 
 
 
 
 
 
 
 
 
337
  # ✅ MULTIUSER FIX: Same as login — clear first
338
  _clear_user_state()
339
  st.session_state.user = {
340
+ "email": user_email,
341
+ "token": token,
342
+ "uid": uid
343
  }
344
+ st.success(f"Account Created! Welcome, {user_email}!")
345
  time.sleep(0.5)
346
  st.rerun()
347
  else:
348
+ st.error(f"Sign Up Failed: {error}")
349
  except Exception as e:
350
  st.error(f"Connection Error: {e}")
351
  else: