From a7c452ffd8d5c514e4d3b645e57a4a4cfe320c56 Mon Sep 17 00:00:00 2001 From: beabigegg Date: Wed, 7 Jan 2026 21:53:16 +0800 Subject: [PATCH] fix: resolve duplicate header and improve Redis management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dashboard: Remove redundant header (Layout already provides it) - projectctl.sh: Add start_redis/stop_redis functions for automatic Redis lifecycle management on project start/stop - rate_limiter.py: Add fallback to memory storage when Redis unavailable 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/app/core/rate_limiter.py | 49 ++++++++++++++-- frontend/src/pages/Dashboard.tsx | 97 ++++++-------------------------- projectctl.sh | 92 +++++++++++++++++++++++++++++- 3 files changed, 154 insertions(+), 84 deletions(-) diff --git a/backend/app/core/rate_limiter.py b/backend/app/core/rate_limiter.py index 3e19d3d..8e9a5cd 100644 --- a/backend/app/core/rate_limiter.py +++ b/backend/app/core/rate_limiter.py @@ -5,6 +5,7 @@ This module provides rate limiting functionality to protect against brute force attacks and DoS attempts on sensitive endpoints. """ +import logging import os from slowapi import Limiter @@ -12,10 +13,50 @@ from slowapi.util import get_remote_address from app.core.config import settings -# Use memory storage for testing, Redis for production -# This allows tests to run without a Redis connection -_testing = os.environ.get("TESTING", "").lower() in ("true", "1", "yes") -_storage_uri = "memory://" if _testing else settings.REDIS_URL +logger = logging.getLogger(__name__) + + +def _get_storage_uri() -> str: + """ + Determine the appropriate storage URI for rate limiting. + + Priority: + 1. Use memory storage if TESTING environment variable is set + 2. Try Redis if available + 3. Fall back to memory storage if Redis is unavailable (with warning) + + Note: Memory storage is acceptable for development but Redis should + be used in production for consistent rate limiting across workers. + """ + # Use memory storage for testing + testing = os.environ.get("TESTING", "").lower() in ("true", "1", "yes") + if testing: + return "memory://" + + # Try to connect to Redis + redis_url = settings.REDIS_URL + try: + import redis + r = redis.Redis( + host=settings.REDIS_HOST, + port=settings.REDIS_PORT, + db=settings.REDIS_DB, + socket_connect_timeout=1, + ) + r.ping() + logger.info("Rate limiter using Redis storage") + return redis_url + except Exception as e: + logger.warning( + f"Redis unavailable for rate limiting ({e}). " + "Falling back to in-memory storage. " + "Note: In production, Redis should be running for consistent " + "rate limiting across multiple workers." + ) + return "memory://" + + +_storage_uri = _get_storage_uri() # Create limiter instance with appropriate storage # Uses the client's remote address (IP) as the key for rate limiting diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index cd51dea..cf2b17d 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,95 +1,34 @@ import { useAuth } from '../contexts/AuthContext' export default function Dashboard() { - const { user, logout } = useAuth() - - const handleLogout = async () => { - await logout() - } + const { user } = useAuth() return (
-
-

Project Control

-
- {user?.name} - {user?.is_system_admin && ( - Admin - )} - -
-
- -
-
-

Welcome, {user?.name}!

-

Email: {user?.email}

-

Role: {user?.role || 'No role assigned'}

- {user?.is_system_admin && ( -

- You have system administrator privileges. -

- )} -
- -
-

Getting Started

-

- This is the Project Control system dashboard. Features will be - added as development progresses. +

+

Welcome, {user?.name}!

+

Email: {user?.email}

+

Role: {user?.role || 'No role assigned'}

+ {user?.is_system_admin && ( +

+ You have system administrator privileges.

-
-
+ )} +
+ +
+

Getting Started

+

+ This is the Project Control system dashboard. Features will be + added as development progresses. +

+
) } const styles: { [key: string]: React.CSSProperties } = { container: { - minHeight: '100vh', - backgroundColor: '#f5f5f5', - }, - header: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: '16px 24px', - backgroundColor: 'white', - boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)', - }, - title: { - fontSize: '20px', - fontWeight: 600, - color: '#333', - margin: 0, - }, - userInfo: { - display: 'flex', - alignItems: 'center', - gap: '12px', - }, - userName: { - color: '#666', - }, - badge: { - backgroundColor: '#0066cc', - color: 'white', - padding: '2px 8px', - borderRadius: '4px', - fontSize: '12px', - fontWeight: 500, - }, - logoutButton: { - padding: '8px 16px', - backgroundColor: '#f5f5f5', - border: '1px solid #ddd', - borderRadius: '4px', - cursor: 'pointer', - fontSize: '14px', - }, - main: { padding: '24px', maxWidth: '1200px', margin: '0 auto', diff --git a/projectctl.sh b/projectctl.sh index 2cb6cdb..25e0926 100755 --- a/projectctl.sh +++ b/projectctl.sh @@ -213,6 +213,94 @@ check_redis() { return 0 } +start_redis() { + local env_file="$BACKEND_DIR/.env" + local redis_host redis_port + redis_host="$(get_env_value "REDIS_HOST" "$env_file")" + redis_port="$(get_env_value "REDIS_PORT" "$env_file")" + redis_host="${redis_host:-localhost}" + redis_port="${redis_port:-6379}" + + # Only manage local Redis + if [[ "$redis_host" != "localhost" && "$redis_host" != "127.0.0.1" ]]; then + log "Redis host is remote ($redis_host), skipping local Redis management" + return 0 + fi + + # Check if Redis is already running + if command -v redis-cli >/dev/null 2>&1; then + if redis-cli -h "$redis_host" -p "$redis_port" ping >/dev/null 2>&1; then + log "Redis already running at $redis_host:$redis_port" + return 0 + fi + fi + + # Try to start Redis using brew services (macOS) + if command -v brew >/dev/null 2>&1; then + if brew services list 2>/dev/null | grep -q "redis"; then + log "Starting Redis via brew services..." + brew services start redis >/dev/null 2>&1 || true + sleep 2 + if redis-cli -h "$redis_host" -p "$redis_port" ping >/dev/null 2>&1; then + log "Redis started successfully" + env_log "Redis: Started via brew services" + return 0 + fi + fi + fi + + # Try to start redis-server directly + if command -v redis-server >/dev/null 2>&1; then + log "Starting Redis server directly..." + redis-server --daemonize yes --port "$redis_port" >/dev/null 2>&1 || true + sleep 1 + if redis-cli -h "$redis_host" -p "$redis_port" ping >/dev/null 2>&1; then + log "Redis started successfully" + env_log "Redis: Started directly" + return 0 + fi + fi + + log "WARN: Could not start Redis automatically" + return 1 +} + +stop_redis() { + local env_file="$BACKEND_DIR/.env" + local redis_host redis_port + redis_host="$(get_env_value "REDIS_HOST" "$env_file")" + redis_port="$(get_env_value "REDIS_PORT" "$env_file")" + redis_host="${redis_host:-localhost}" + redis_port="${redis_port:-6379}" + + # Only manage local Redis + if [[ "$redis_host" != "localhost" && "$redis_host" != "127.0.0.1" ]]; then + echo "Redis host is remote ($redis_host), skipping local Redis management" + return 0 + fi + + # Try to stop Redis using brew services (macOS) + if command -v brew >/dev/null 2>&1; then + if brew services list 2>/dev/null | grep -q "redis.*started"; then + echo "Stopping Redis via brew services..." + brew services stop redis >/dev/null 2>&1 || true + return 0 + fi + fi + + # Try to stop redis-server via redis-cli + if command -v redis-cli >/dev/null 2>&1; then + if redis-cli -h "$redis_host" -p "$redis_port" ping >/dev/null 2>&1; then + echo "Stopping Redis via redis-cli shutdown..." + redis-cli -h "$redis_host" -p "$redis_port" shutdown nosave >/dev/null 2>&1 || true + return 0 + fi + fi + + echo "Redis: not running or not managed locally" + return 0 +} + check_mysql() { local env_file="$BACKEND_DIR/.env" local mysql_host mysql_port @@ -397,7 +485,8 @@ start() { check_env_file check_python_deps check_node_deps - check_redis || true # Warn but don't fail + start_redis || true # Start Redis if not running (warn but don't fail) + check_redis || true # Verify Redis is available check_mysql || true # Warn but don't fail check_ports @@ -425,6 +514,7 @@ stop() { fi stop_service "backend" "$dir/backend.pid" stop_service "frontend" "$dir/frontend.pid" + stop_redis } status() {