Spaces:
Sleeping
Sleeping
restructure
Browse files- CREDIT_SERVICE_EXAMPLE.py +0 -181
- add_credit_transactions_table.py +0 -120
- check_exceptions.py +0 -15
- check_token_config.sh +0 -54
- scripts/README.md +72 -0
- scripts/setup/check_env_config.py +251 -0
- generate_jwt_secret.py β scripts/setup/generate_jwt_secret.py +0 -0
- get_google_token.py β scripts/setup/get_google_token.py +0 -0
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
|