jebin2 commited on
Commit
dd5c695
Β·
1 Parent(s): 8e4a45e

restructure

Browse files
CREDIT_SERVICE_EXAMPLE.py DELETED
@@ -1,181 +0,0 @@
1
- """
2
- Example app.py configuration for Credit Service
3
-
4
- This shows how to configure the new middleware-based credit system.
5
- """
6
-
7
- # ============================================================================
8
- # Credit Service Registration
9
- # ============================================================================
10
-
11
- from services.credit_service import CreditServiceConfig
12
-
13
- # Register credit service with route configurations
14
- CreditServiceConfig.register(
15
- route_configs={
16
- # Synchronous operations - credits confirmed/refunded immediately
17
- "/gemini/generate-animation-prompt": {
18
- "cost": 1,
19
- "type": "sync"
20
- },
21
- "/gemini/edit-image": {
22
- "cost": 1,
23
- "type": "sync"
24
- },
25
- "/gemini/generate-text": {
26
- "cost": 1,
27
- "type": "sync"
28
- },
29
- "/gemini/analyze-image": {
30
- "cost": 1,
31
- "type": "sync"
32
- },
33
-
34
- # Asynchronous operations - credits kept reserved until job completes
35
- "/gemini/generate-video": {
36
- "cost": 10,
37
- "type": "async",
38
- "status_endpoint": "/gemini/job/{job_id}" # Optional metadata
39
- },
40
-
41
- # Status check endpoints - no additional cost, but can trigger confirm/refund
42
- "/gemini/job/{job_id}": {
43
- "cost": 0,
44
- "type": "async"
45
- }
46
- }
47
- )
48
-
49
- # ============================================================================
50
- # Middleware Chain (IMPORTANT: Order matters!)
51
- # ============================================================================
52
-
53
- # 1. AuthMiddleware MUST come first (sets request.state.user)
54
- app.add_middleware(AuthServiceConfig.get_middleware())
55
-
56
- # 2. CreditMiddleware comes after auth (needs request.state.user)
57
- app.add_middleware(CreditServiceConfig.get_middleware())
58
-
59
- # 3. Other middleware can follow
60
- # app.add_middleware(...)
61
-
62
- # ============================================================================
63
- # How It Works - Examples
64
- # ============================================================================
65
-
66
- """
67
- SYNCHRONOUS OPERATION EXAMPLE:
68
- ==============================
69
-
70
- 1. User requests: POST /gemini/analyze-image
71
- β†’ Middleware: Reserve 1 credit
72
- β†’ Application: Process image analysis
73
- β†’ Application: Return 200 + analysis result
74
- β†’ Middleware: Confirm credit (success)
75
-
76
- 2. User requests: POST /gemini/analyze-image
77
- β†’ Middleware: Reserve 1 credit
78
- β†’ Application: Image validation fails
79
- β†’ Application: Return 400 + error
80
- β†’ Middleware: Refund credit (request error)
81
-
82
-
83
- ASYNCHRONOUS OPERATION EXAMPLE:
84
- ===============================
85
-
86
- 1. User requests: POST /gemini/generate-video
87
- β†’ Middleware: Reserve 10 credits
88
- β†’ Application: Create job, return job_id
89
- β†’ Application: Return 200 + {"job_id": "abc123", "status": "queued"}
90
- β†’ Middleware: Keep credits reserved (job pending)
91
-
92
- 2. User polls: GET /gemini/job/abc123
93
- β†’ Middleware: No additional cost (cost=0)
94
- β†’ Application: Fetch job status
95
- β†’ Application: Return 200 + {"job_id": "abc123", "status": "processing"}
96
- β†’ Middleware: Keep credits reserved (still processing)
97
-
98
- 3. User polls: GET /gemini/job/abc123
99
- β†’ Middleware: No additional cost
100
- β†’ Application: Fetch job status
101
- β†’ Application: Return 200 + {"job_id": "abc123", "status": "completed", "video_url": "..."}
102
- β†’ Middleware: Confirm 10 credits (job completed)
103
-
104
- 4. Alternative - Job fails with refundable error:
105
- GET /gemini/job/abc123
106
- β†’ Application: Return 200 + {"status": "failed", "error_message": "API_KEY_INVALID"}
107
- β†’ Middleware: Refund 10 credits (refundable server error)
108
-
109
- 5. Alternative - Job fails with user error:
110
- GET /gemini/job/abc123
111
- β†’ Application: Return 200 + {"status": "failed", "error_message": "safety filter"}
112
- β†’ Middleware: Confirm 10 credits (non-refundable user error)
113
-
114
-
115
- TRANSACTION HISTORY:
116
- ===================
117
-
118
- All operations are recorded in credit_transactions table:
119
-
120
- | transaction_id | type | amount | balance_before | balance_after | reason |
121
- |----------------|---------|--------|----------------|---------------|------------------------------|
122
- | ctx_abc123 | reserve | -10 | 100 | 90 | POST /gemini/generate-video |
123
- | cfm_def456 | confirm | 0 | 90 | 90 | Confirmed usage for ctx_abc |
124
-
125
- User can query their history via: GET /credits/transactions
126
- """
127
-
128
- # ============================================================================
129
- # Payment Integration (Still uses transaction manager directly)
130
- # ============================================================================
131
-
132
- """
133
- In routers/payments.py, use CreditTransactionManager directly:
134
-
135
- from services.credit_service import CreditTransactionManager
136
-
137
- async def process_successful_payment(...):
138
- await CreditTransactionManager.add_credits(
139
- session=db,
140
- user=user,
141
- amount=credits_purchased,
142
- source="payment",
143
- reference_type="payment",
144
- reference_id=transaction_id,
145
- reason=f"Purchase: {package_id}",
146
- metadata={"gateway": "razorpay", ...}
147
- )
148
- """
149
-
150
- # ============================================================================
151
- # Application Layer Changes
152
- # ============================================================================
153
-
154
- """
155
- NO CHANGES NEEDED in application routers!
156
-
157
- Before (old approach):
158
- @router.post("/gemini/analyze-image")
159
- async def analyze_image(
160
- request: Request,
161
- user: User = Depends(verify_credits) # ← Remove this
162
- ):
163
- # Had to manually handle credits
164
- pass
165
-
166
- After (new approach):
167
- @router.post("/gemini/analyze-image")
168
- async def analyze_image(request: Request):
169
- user = request.state.user # Set by AuthMiddleware
170
- # Credits automatically handled by middleware!
171
- # Just focus on business logic
172
- pass
173
-
174
- Old dependencies to REMOVE:
175
- - Depends(verify_credits)
176
- - Depends(verify_video_credits)
177
- - Manual credit deductions: user.credits -= X
178
- - Manual refunds in endpoints
179
-
180
- Everything is now handled automatically by middleware!
181
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
add_credit_transactions_table.py DELETED
@@ -1,120 +0,0 @@
1
- """
2
- Database migration script to add credit_transactions table.
3
-
4
- Run this script to add the credit transaction tracking table to your database.
5
-
6
- Usage:
7
- python add_credit_transactions_table.py
8
- """
9
- import sqlite3
10
- import sys
11
- from pathlib import Path
12
-
13
- # Find database file
14
- DB_FILE = "apigateway_dev.db"
15
- if not Path(DB_FILE).exists():
16
- DB_FILE = "apigateway_development.db"
17
- if not Path(DB_FILE).exists():
18
- print(f"Error: Database file not found. Looking for 'apigateway_dev.db' or 'apigateway_development.db'")
19
- sys.exit(1)
20
-
21
- print(f"Using database: {DB_FILE}")
22
-
23
- # SQL to create credit_transactions table
24
- CREATE_TABLE_SQL = """
25
- CREATE TABLE IF NOT EXISTS credit_transactions (
26
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27
- transaction_id VARCHAR(100) UNIQUE NOT NULL,
28
- user_id INTEGER NOT NULL,
29
-
30
- -- Transaction details
31
- transaction_type VARCHAR(20) NOT NULL,
32
- amount INTEGER NOT NULL,
33
- balance_before INTEGER NOT NULL,
34
- balance_after INTEGER NOT NULL,
35
-
36
- -- Context
37
- source VARCHAR(50) NOT NULL,
38
- reference_type VARCHAR(30),
39
- reference_id VARCHAR(100),
40
-
41
- -- Request/Response metadata
42
- request_path VARCHAR(500),
43
- request_method VARCHAR(10),
44
- response_status INTEGER,
45
-
46
- -- Additional details
47
- reason TEXT,
48
- metadata JSON,
49
-
50
- -- Timestamps
51
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
52
- deleted_at DATETIME,
53
-
54
- FOREIGN KEY (user_id) REFERENCES users(id)
55
- );
56
- """
57
-
58
- # SQL to create indexes
59
- CREATE_INDEXES_SQL = [
60
- "CREATE INDEX IF NOT EXISTS ix_credit_transactions_transaction_id ON credit_transactions(transaction_id);",
61
- "CREATE INDEX IF NOT EXISTS ix_user_transactions ON credit_transactions(user_id, created_at);",
62
- "CREATE INDEX IF NOT EXISTS ix_transaction_type ON credit_transactions(transaction_type);",
63
- "CREATE INDEX IF NOT EXISTS ix_reference ON credit_transactions(reference_type, reference_id);",
64
- "CREATE INDEX IF NOT EXISTS ix_credit_transactions_created_at ON credit_transactions(created_at);",
65
- "CREATE INDEX IF NOT EXISTS ix_credit_transactions_deleted_at ON credit_transactions(deleted_at);",
66
- ]
67
-
68
- def run_migration():
69
- """Run the migration to add credit_transactions table."""
70
- try:
71
- conn = sqlite3.connect(DB_FILE)
72
- cursor = conn.cursor()
73
-
74
- # Check if table already exists
75
- cursor.execute("""
76
- SELECT name FROM sqlite_master
77
- WHERE type='table' AND name='credit_transactions';
78
- """)
79
-
80
- if cursor.fetchone():
81
- print("βœ… Table 'credit_transactions' already exists. Skipping creation.")
82
- else:
83
- print("Creating 'credit_transactions' table...")
84
- cursor.execute(CREATE_TABLE_SQL)
85
- print("βœ… Table created successfully!")
86
-
87
- # Create indexes
88
- print("\nCreating indexes...")
89
- for index_sql in CREATE_INDEXES_SQL:
90
- cursor.execute(index_sql)
91
- print("βœ… Indexes created successfully!")
92
-
93
- # Commit changes
94
- conn.commit()
95
-
96
- # Verify table
97
- cursor.execute("PRAGMA table_info(credit_transactions);")
98
- columns = cursor.fetchall()
99
-
100
- print(f"\nTable schema (columns: {len(columns)}):")
101
- for col in columns:
102
- print(f" - {col[1]} ({col[2]})")
103
-
104
- conn.close()
105
-
106
- print("\nβœ… Migration completed successfully!")
107
- print("\nNext steps:")
108
- print(" 1. Update app.py to register credit service with route configs")
109
- print(" 2. The middleware will automatically track all credit operations")
110
- print(" 3. Check transaction history via /credits/transactions endpoint")
111
-
112
- except Exception as e:
113
- print(f"\n❌ Migration failed: {e}")
114
- sys.exit(1)
115
-
116
- if __name__ == "__main__":
117
- print("="*60)
118
- print("Credit Transactions Table Migration")
119
- print("="*60)
120
- run_migration()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
check_exceptions.py DELETED
@@ -1,15 +0,0 @@
1
-
2
- try:
3
- from google.api_core import exceptions
4
- print("google.api_core.exceptions found")
5
- print(f"ResourceExhausted: {exceptions.ResourceExhausted}")
6
- print(f"Unauthenticated: {exceptions.Unauthenticated}")
7
- print(f"PermissionDenied: {exceptions.PermissionDenied}")
8
- except ImportError:
9
- print("google.api_core.exceptions NOT found")
10
-
11
- try:
12
- from google import genai
13
- print("google.genai found")
14
- except ImportError:
15
- print("google.genai NOT found")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
check_token_config.sh DELETED
@@ -1,54 +0,0 @@
1
- #!/bin/bash
2
- # Quick Token Configuration Diagnostic
3
-
4
- echo "=========================================="
5
- echo " JWT Token Configuration Diagnostic"
6
- echo "=========================================="
7
-
8
- echo -e "\n1️⃣ Environment Variables:"
9
- echo " JWT_ACCESS_EXPIRY_MINUTES: $(grep JWT_ACCESS_EXPIRY_MINUTES .env 2>/dev/null | cut -d'=' -f2 || echo 'NOT SET')"
10
- echo " JWT_REFRESH_EXPIRY_DAYS: $(grep JWT_REFRESH_EXPIRY_DAYS .env 2>/dev/null | cut -d'=' -f2 || echo 'NOT SET')"
11
-
12
- echo -e "\n2️⃣ Server Status:"
13
- if pgrep -f "python app.py" > /dev/null; then
14
- echo " βœ… Server is running"
15
- echo " PID: $(pgrep -f 'python app.py')"
16
- echo " Started: $(ps -p $(pgrep -f 'python app.py') -o lstart= 2>/dev/null)"
17
- else
18
- echo " ❌ Server is NOT running"
19
- fi
20
-
21
- echo -e "\n3️⃣ File Modification Times:"
22
- echo " .env modified: $(stat -c '%y' .env 2>/dev/null | cut -d'.' -f1 || echo 'Unknown')"
23
-
24
- echo -e "\n4️⃣ Recommendation:"
25
- if pgrep -f "python app.py" > /dev/null; then
26
- SERVER_START=$(ps -p $(pgrep -f "python app.py") -o etimes= | tr -d ' ')
27
- if [ -f .env ]; then
28
- ENV_MOD=$(stat -c '%Y' .env)
29
- SERVER_START_TIME=$(($(date +%s) - SERVER_START))
30
-
31
- if [ $ENV_MOD -gt $SERVER_START_TIME ]; then
32
- echo " ⚠️ .env was modified AFTER server started!"
33
- echo " ⚠️ Server is using OLD configuration!"
34
- echo " "
35
- echo " πŸ‘‰ ACTION REQUIRED: Restart the server"
36
- echo " "
37
- echo " Run these commands:"
38
- echo " pkill -f 'python app.py'"
39
- echo " python app.py"
40
- else
41
- echo " βœ… Server has latest .env configuration"
42
- echo " "
43
- echo " πŸ’‘ Token expiry IS working, but you may not see it because:"
44
- echo " - Frontend automatically refreshes tokens"
45
- echo " - Refresh happens silently every ~1 minute"
46
- echo " "
47
- echo " To verify, check browser DevTools β†’ Network tab for /auth/refresh calls"
48
- fi
49
- fi
50
- else
51
- echo " πŸ‘‰ Start the server first: python app.py"
52
- fi
53
-
54
- echo -e "\n=========================================="
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/README.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Setup Scripts
2
+
3
+ Utilities for initial setup and configuration validation.
4
+
5
+ ## Environment Configuration
6
+
7
+ ### `check_env_config.py` ⭐
8
+ **Validate your entire environment configuration before running the app.**
9
+
10
+ ```bash
11
+ # Check configuration and show warnings
12
+ python scripts/setup/check_env_config.py
13
+
14
+ # Strict mode - exit with error if any issues found
15
+ python scripts/setup/check_env_config.py --strict
16
+ ```
17
+
18
+ **What it checks:**
19
+ - βœ… Required variables (JWT_SECRET, CORS_ORIGINS, Google OAuth)
20
+ - ⚠️ Recommended variables (Gemini API, Razorpay, etc.)
21
+ - βš™οΈ Configuration values (token expiry, rate limits)
22
+ - πŸ”’ Security issues (production CORS, HTTPS)
23
+
24
+ **Run this before:**
25
+ - First time setup
26
+ - Deploying to production
27
+ - After changing .env file
28
+
29
+ ---
30
+
31
+ ## Secret Generation
32
+
33
+ ### `generate_jwt_secret.py`
34
+ Generate cryptographically secure JWT secret keys.
35
+
36
+ ```bash
37
+ # Basic usage
38
+ python scripts/setup/generate_jwt_secret.py
39
+
40
+ # Custom length
41
+ python scripts/setup/generate_jwt_secret.py --length 128
42
+
43
+ # Different output formats
44
+ python scripts/setup/generate_jwt_secret.py --format docker
45
+ python scripts/setup/generate_jwt_secret.py --format export
46
+ python scripts/setup/generate_jwt_secret.py --format raw
47
+ ```
48
+
49
+ Add the generated secret to your `.env` file as `JWT_SECRET`.
50
+
51
+ ---
52
+
53
+ ## OAuth Setup
54
+
55
+ ### `get_google_token.py`
56
+ Generate Google OAuth refresh tokens for Gmail and Drive services.
57
+
58
+ ```bash
59
+ python scripts/setup/get_google_token.py
60
+ ```
61
+
62
+ **Prerequisites:**
63
+ 1. Download OAuth 2.0 credentials from [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
64
+ 2. Save as `client_secret.json` in project root
65
+ 3. Run the script and follow browser authentication flow
66
+
67
+ **Outputs:**
68
+ - `GOOGLE_CLIENT_ID`
69
+ - `GOOGLE_CLIENT_SECRET`
70
+ - `GOOGLE_REFRESH_TOKEN`
71
+
72
+ Add these to your `.env` file.
scripts/setup/check_env_config.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Environment Configuration Checker
4
+
5
+ Validates that all required environment variables are properly configured.
6
+ Run this before deploying or starting the application.
7
+
8
+ Usage:
9
+ python scripts/setup/check_env_config.py
10
+ python scripts/setup/check_env_config.py --strict # Exit with error if any issues found
11
+ """
12
+
13
+ import os
14
+ import sys
15
+ import argparse
16
+ from pathlib import Path
17
+ from typing import Dict, List, Tuple, Optional
18
+
19
+
20
+ class EnvChecker:
21
+ """Check environment configuration for completeness and validity."""
22
+
23
+ # Required environment variables
24
+ REQUIRED_VARS = {
25
+ "JWT_SECRET": {
26
+ "description": "Secret key for signing JWT tokens",
27
+ "validate": lambda v: len(v) > 32 and v != "your-secret-key-here-change-me",
28
+ "error": "Must be a secure random string (>32 chars). Generate with: python scripts/setup/generate_jwt_secret.py"
29
+ },
30
+ "CORS_ORIGINS": {
31
+ "description": "Allowed CORS origins for frontend",
32
+ "validate": lambda v: len(v) > 0 and "localhost" in v.lower() or "http" in v,
33
+ "error": "Must specify at least one origin (e.g., http://localhost:3000)"
34
+ },
35
+ "AUTH_SIGN_IN_GOOGLE_CLIENT_ID": {
36
+ "description": "Google OAuth Client ID",
37
+ "validate": lambda v: v.endswith(".apps.googleusercontent.com"),
38
+ "error": "Must be a valid Google Client ID from console.cloud.google.com"
39
+ },
40
+ }
41
+
42
+ # Optional but recommended
43
+ RECOMMENDED_VARS = {
44
+ "ENVIRONMENT": {
45
+ "description": "Environment mode (production/development)",
46
+ "validate": lambda v: v in ["production", "development"],
47
+ "default": "development",
48
+ "warning": "Should be 'production' or 'development'"
49
+ },
50
+ "GEMINI_API_KEYS": {
51
+ "description": "Gemini API keys for video generation",
52
+ "validate": lambda v: len(v) > 20 and v != "your-gemini-api-key",
53
+ "default": None,
54
+ "warning": "Required for video generation features"
55
+ },
56
+ "RAZORPAY_KEY_ID": {
57
+ "description": "Razorpay payment gateway key ID",
58
+ "validate": lambda v: len(v) > 10 and v != "your_razorpay_key_id",
59
+ "default": None,
60
+ "warning": "Required for payment features"
61
+ },
62
+ "RAZORPAY_KEY_SECRET": {
63
+ "description": "Razorpay payment gateway secret",
64
+ "validate": lambda v: len(v) > 10 and v != "your_razorpay_key_secret",
65
+ "default": None,
66
+ "warning": "Required for payment features"
67
+ },
68
+ "RAZORPAY_WEBHOOK_SECRET": {
69
+ "description": "Razorpay webhook signature verification secret",
70
+ "validate": lambda v: len(v) > 10 and v != "your_webhook_secret",
71
+ "default": None,
72
+ "warning": "Required for payment webhook verification"
73
+ },
74
+ }
75
+
76
+ # Configuration validation
77
+ CONFIG_CHECKS = {
78
+ "JWT_ACCESS_EXPIRY_MINUTES": {
79
+ "description": "Access token expiry time",
80
+ "validate": lambda v: v.isdigit() and 5 <= int(v) <= 60,
81
+ "default": "15",
82
+ "warning": "Recommended: 5-15 min (production), 30-60 min (development)"
83
+ },
84
+ "JWT_REFRESH_EXPIRY_DAYS": {
85
+ "description": "Refresh token expiry time",
86
+ "validate": lambda v: v.isdigit() and 1 <= int(v) <= 90,
87
+ "default": "7",
88
+ "warning": "Recommended: 7-14 days (production), 30-90 days (development)"
89
+ },
90
+ "JOB_PER_API_KEY": {
91
+ "description": "Concurrent jobs per Gemini API key",
92
+ "validate": lambda v: v.isdigit() and 1 <= int(v) <= 5,
93
+ "default": "2",
94
+ "warning": "Recommended: 1-3 to avoid rate limits"
95
+ },
96
+ }
97
+
98
+ def __init__(self, env_file: str = ".env"):
99
+ self.env_file = Path(env_file)
100
+ self.env_vars = self._load_env()
101
+ self.errors: List[Tuple[str, str]] = []
102
+ self.warnings: List[Tuple[str, str]] = []
103
+ self.info: List[Tuple[str, str]] = []
104
+
105
+ def _load_env(self) -> Dict[str, str]:
106
+ """Load environment variables from .env file."""
107
+ env_vars = {}
108
+ if not self.env_file.exists():
109
+ return env_vars
110
+
111
+ with open(self.env_file) as f:
112
+ for line in f:
113
+ line = line.strip()
114
+ if line and not line.startswith("#") and "=" in line:
115
+ key, value = line.split("=", 1)
116
+ env_vars[key.strip()] = value.strip()
117
+
118
+ return env_vars
119
+
120
+ def check_required(self) -> None:
121
+ """Check required environment variables."""
122
+ for var, config in self.REQUIRED_VARS.items():
123
+ value = self.env_vars.get(var) or os.getenv(var)
124
+
125
+ if not value:
126
+ self.errors.append((var, f"MISSING - {config['description']}. {config['error']}"))
127
+ elif not config["validate"](value):
128
+ self.errors.append((var, f"INVALID - {config['error']}"))
129
+ else:
130
+ self.info.append((var, "βœ“ Configured"))
131
+
132
+ def check_recommended(self) -> None:
133
+ """Check recommended environment variables."""
134
+ for var, config in self.RECOMMENDED_VARS.items():
135
+ value = self.env_vars.get(var) or os.getenv(var)
136
+
137
+ if not value:
138
+ if config.get("default"):
139
+ self.info.append((var, f"Using default: {config['default']}"))
140
+ else:
141
+ self.warnings.append((var, f"NOT SET - {config['warning']}"))
142
+ elif not config["validate"](value):
143
+ self.warnings.append((var, config["warning"]))
144
+ else:
145
+ self.info.append((var, "βœ“ Configured"))
146
+
147
+ def check_config(self) -> None:
148
+ """Check configuration values."""
149
+ for var, config in self.CONFIG_CHECKS.items():
150
+ value = self.env_vars.get(var) or os.getenv(var) or config.get("default", "")
151
+
152
+ if value and not config["validate"](str(value)):
153
+ self.warnings.append((var, f"{value} - {config['warning']}"))
154
+
155
+ def check_file_exists(self) -> None:
156
+ """Check if .env file exists."""
157
+ if not self.env_file.exists():
158
+ self.errors.append((".env", "File not found! Copy .env.example to .env and configure it."))
159
+
160
+ def check_cors_security(self) -> None:
161
+ """Check CORS configuration security."""
162
+ cors = self.env_vars.get("CORS_ORIGINS", "")
163
+ env = self.env_vars.get("ENVIRONMENT", "development")
164
+
165
+ if env == "production":
166
+ if "localhost" in cors.lower():
167
+ self.warnings.append(("CORS_ORIGINS", "Contains localhost in production environment"))
168
+ if not cors.startswith("https://"):
169
+ self.warnings.append(("CORS_ORIGINS", "Should use HTTPS in production"))
170
+
171
+ def run_all_checks(self) -> bool:
172
+ """Run all validation checks. Returns True if no errors."""
173
+ self.check_file_exists()
174
+ if self.env_file.exists():
175
+ self.check_required()
176
+ self.check_recommended()
177
+ self.check_config()
178
+ self.check_cors_security()
179
+
180
+ return len(self.errors) == 0
181
+
182
+ def print_report(self) -> None:
183
+ """Print validation report."""
184
+ print("\n" + "=" * 70)
185
+ print(" Environment Configuration Check")
186
+ print("=" * 70)
187
+
188
+ if self.errors:
189
+ print("\n❌ ERRORS (Must Fix):")
190
+ print("-" * 70)
191
+ for var, msg in self.errors:
192
+ print(f" [{var}]")
193
+ print(f" {msg}\n")
194
+
195
+ if self.warnings:
196
+ print("\n⚠️ WARNINGS (Review Recommended):")
197
+ print("-" * 70)
198
+ for var, msg in self.warnings:
199
+ print(f" [{var}] {msg}")
200
+
201
+ if self.info and not self.errors:
202
+ print("\nβœ… Required Variables:")
203
+ print("-" * 70)
204
+ for var, msg in self.info:
205
+ if var in self.REQUIRED_VARS:
206
+ print(f" {var}: {msg}")
207
+
208
+ print("\n" + "=" * 70)
209
+
210
+ if not self.errors and not self.warnings:
211
+ print("βœ… All checks passed! Environment is properly configured.")
212
+ elif not self.errors:
213
+ print("⚠️ Configuration is valid but has warnings. Review recommended.")
214
+ else:
215
+ print("❌ Configuration has errors. Please fix them before running the app.")
216
+
217
+ print("=" * 70 + "\n")
218
+
219
+
220
+ def main():
221
+ parser = argparse.ArgumentParser(
222
+ description="Validate environment configuration",
223
+ formatter_class=argparse.RawDescriptionHelpFormatter
224
+ )
225
+ parser.add_argument(
226
+ "--env-file",
227
+ default=".env",
228
+ help="Path to .env file (default: .env)"
229
+ )
230
+ parser.add_argument(
231
+ "--strict",
232
+ action="store_true",
233
+ help="Exit with error code if any issues found"
234
+ )
235
+
236
+ args = parser.parse_args()
237
+
238
+ checker = EnvChecker(args.env_file)
239
+ is_valid = checker.run_all_checks()
240
+ checker.print_report()
241
+
242
+ if args.strict and (checker.errors or checker.warnings):
243
+ sys.exit(1)
244
+ elif not is_valid:
245
+ sys.exit(1)
246
+
247
+ sys.exit(0)
248
+
249
+
250
+ if __name__ == "__main__":
251
+ main()
generate_jwt_secret.py β†’ scripts/setup/generate_jwt_secret.py RENAMED
File without changes
get_google_token.py β†’ scripts/setup/get_google_token.py RENAMED
File without changes