| |
| from fastapi import FastAPI, Request, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| import sqlite3 |
| from datetime import datetime, timedelta |
| import asyncio |
| import aiohttp |
| import time |
| import hmac |
| import hashlib |
| import sqlite3 |
| import requests |
| from datetime import datetime |
| from fastapi import FastAPI, Request |
| from fastapi.middleware.cors import CORSMiddleware |
|
|
| app = FastAPI() |
| app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) |
|
|
| |
| |
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=[ |
| "https://domify-academy.free.nf", |
| "https://*.free.nf", |
| "http://localhost:3000" |
| ], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| |
| |
| DB_PATH = "domify_users.db" |
| conn = sqlite3.connect(DB_PATH, check_same_thread=False) |
| c = conn.cursor() |
|
|
| c.execute(""" |
| CREATE TABLE IF NOT EXISTS users ( |
| user_id TEXT PRIMARY KEY, |
| full_name TEXT, |
| email TEXT UNIQUE, |
| signup_date TEXT, |
| country TEXT, |
| source TEXT, |
| ip TEXT, |
| timestamp TEXT, |
| tier TEXT DEFAULT 'Free', |
| expiry TEXT, |
| cert_paid INTEGER DEFAULT 0, |
| cert_generated INTEGER DEFAULT 0 |
| ) |
| """) |
| conn.commit() |
|
|
| |
| |
| |
| ADMIN_IPS = ["fe80::f26d:78ff:fe61:be53", "192.168.7.71", "10.92.98.240"] |
|
|
| |
| |
| |
| LEMON_STORE_ID = 342813 |
| LEMON_API_KEY = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI5NGQ1OWNlZi1kYmI4LTRlYTUtYjE3OC1kMjU0MGZjZDY5MTkiLCJqdGkiOiIyM2Q5MTk2NGQ2ZDk4OTc5ZGUyY2NmNGViYTNkZTA5ODMyZWFiNGIwZDk5MmI3MWVkY2E3NDExODU4ZmMzODA4NjVlMWZhNzlkNTUyNTY2NiIsImlhdCI6MTc3NTk2MzY4NS4zMjk5NjksIm5iZiI6MTc3NTk2MzY4NS4zMjk5NzIsImV4cCI6NDk0OTA3ODQwMC4wMzk4NjQsInN1YiI6IjY4Nzc3MzkiLCJzY29wZXMiOltdfQ.RqMkdIptrE52m_Dr_sI674lUae-FksX-Ey4metBS6QY4r33yyoYvD5DVOCLTa4q6dnKo2b5hhWl_hONYnNyjLwkFemsWOkyy6c17jWn4IC1S59co-iHaz5vYa3PTttWF9nSHQpoyRFhd9dNxGfBH7z4x06PoqDGUVEd3Am60xwXAoe5oXILrpigdC81Z0sk9pnw4gJXrzKlP88bKfjGmqHawowpXwvqNoiKjPSZCcYLQKWy2GJ6yPdO-z3SqWKP8RJ64lp3nHDLcfJGmJLlRMTLCubLdRvX_LXi6EE5bzMWBdOvbszL9h00NZE9DL_7Qd6tufh_uYgqg3b8LQK-Ur-i6E60DSAktke3wwI46MCXyl8zl-lMeuGYUq98TIV8eTGvSKroNYs-dwI068iFePI8qgt1YeF4MAQendj1vJlIVQqB3C79D7O0OlVLAkibJ50yCAf6B63q9F1leVOpfhXlJWZvZuLMkQy4vOGu7DYUPRRP0SRq3g_e_ozFgzbzb51e-hUUiU4Q_1lEWlsFuaSoXen0iJ4K2llrKmfR0FTKep2JYoRWQ473IvrL7Uxh_K87Z9SQzDUSHDL4b4tGqSwWrElT1kDFxHCDeKFjloYFHkfoAHgpeqstTj_PgdOpwaVvgTWDIj6Vw5sREEvamheEb6e9pRyg90fJlmLGdygo" |
| LEMON_WEBHOOK_SECRET = "whsec_8f3a9d2c5e1b7a4d6f8e2c9b5a7d3f1e" |
|
|
| |
| LEMON_PRODUCTS = { |
| 1519026: {"tier": "Professional", "days": 30}, |
| 1519043: {"tier": "Master", "days": 30}, |
| 1519056: {"tier": "Certificate", "days": 0}, |
| } |
|
|
| |
| |
| |
| def get_client_ip(request: Request) -> str: |
| forwarded = request.headers.get("X-Forwarded-For") |
| if forwarded: |
| return forwarded.split(",")[0].strip() |
| return request.client.host |
|
|
| |
| |
| |
| @app.post("/signup") |
| async def signup(request: Request): |
| try: |
| data = await request.json() |
| |
| required = ['userId', 'fullName', 'email', 'signupDate', 'country', 'source', 'ip', 'timestamp'] |
| for field in required: |
| if field not in data: |
| raise HTTPException(status_code=400, detail=f"Missing field: {field}") |
| |
| c.execute(""" |
| INSERT OR REPLACE INTO users |
| (user_id, full_name, email, signup_date, country, source, ip, timestamp, tier, expiry, cert_paid, cert_generated) |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| """, ( |
| data['userId'], data['fullName'], data['email'], data['signupDate'], |
| data['country'], data['source'], data['ip'], data['timestamp'], |
| data.get('tier', 'Free'), data.get('expiry'), |
| int(data.get('certPaid', False)), int(data.get('certGenerated', False)) |
| )) |
| conn.commit() |
| return {"status": "ok", "message": "User saved"} |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
| |
| |
| |
| @app.get("/user") |
| async def get_user(request: Request, email: str = None, user_id: str = None): |
| client_ip = get_client_ip(request) |
| |
| |
| if client_ip in ADMIN_IPS: |
| return { |
| "userId": "admin", |
| "name": "Administrator", |
| "email": "domifyacademy@gmail.com", |
| "tier": "Master", |
| "expiry": (datetime.now() + timedelta(days=365)).isoformat(), |
| "certPaid": True, |
| "certGenerated": False, |
| "isAdmin": True |
| } |
| |
| |
| c.execute(""" |
| SELECT user_id, full_name, email, tier, expiry, cert_paid, cert_generated |
| FROM users WHERE email=? OR user_id=? OR ip=? |
| """, (email, user_id, client_ip)) |
| row = c.fetchone() |
| |
| if row: |
| return { |
| "userId": row[0], |
| "name": row[1], |
| "email": row[2], |
| "tier": row[3], |
| "expiry": row[4], |
| "certPaid": bool(row[5]), |
| "certGenerated": bool(row[6]), |
| "isAdmin": False |
| } |
| |
| raise HTTPException(status_code=404, detail="User not found") |
|
|
| |
| |
| |
| @app.post("/webhook/lemonsqueezy") |
| async def lemon_webhook(request: Request): |
| try: |
| |
| body = await request.body() |
| signature = request.headers.get("X-Signature") |
| |
| if not signature: |
| print("❌ No signature in webhook") |
| return {"status": "ignored", "reason": "no signature"} |
| |
| |
| expected = hmac.new( |
| LEMON_WEBHOOK_SECRET.encode(), |
| body, |
| hashlib.sha256 |
| ).hexdigest() |
| |
| if not hmac.compare_digest(signature, expected): |
| print("❌ Invalid signature") |
| return {"status": "ignored", "reason": "invalid signature"} |
| |
| |
| data = await request.json() |
| event_name = data.get("meta", {}).get("event_name") |
| |
| if event_name != "order_created": |
| return {"status": "ignored", "reason": f"event {event_name}"} |
| |
| |
| order_data = data.get("data", {}).get("attributes", {}) |
| email = order_data.get("user_email") |
| first_item = order_data.get("first_order_item", {}) |
| variant_id = first_item.get("variant_id") |
| |
| if not email or not variant_id: |
| print(f"❌ Missing email or variant") |
| return {"status": "ignored", "reason": "missing email or variant"} |
| |
| |
| product = LEMON_PRODUCTS.get(variant_id) |
| if not product: |
| print(f"❌ Unknown variant: {variant_id}") |
| return {"status": "ignored", "reason": f"unknown variant {variant_id}"} |
| |
| print(f"✅ Processing payment: {email} -> {product['tier']}") |
| |
| |
| expiry = None |
| if product["days"] > 0: |
| expiry = (datetime.now() + timedelta(days=product["days"])).isoformat() |
| |
| |
| c.execute("SELECT * FROM users WHERE email = ?", (email,)) |
| user = c.fetchone() |
| |
| if user: |
| c.execute("UPDATE users SET tier = ?, expiry = ? WHERE email = ?", |
| (product["tier"], expiry, email)) |
| if product["tier"] == "Master": |
| c.execute("UPDATE users SET cert_paid = 1 WHERE email = ?", (email,)) |
| print(f"✅ Updated existing user: {email}") |
| else: |
| c.execute(""" |
| INSERT INTO users (user_id, full_name, email, tier, expiry, cert_paid, timestamp) |
| VALUES (?, ?, ?, ?, ?, ?, ?) |
| """, ( |
| f"auto_{int(time.time())}", email, email, product["tier"], expiry, |
| 1 if product["tier"] == "Master" else 0, |
| datetime.now().isoformat() |
| )) |
| print(f"✅ Created new user: {email}") |
| |
| conn.commit() |
| return {"status": "ok", "message": f"Updated {email} to {product['tier']}"} |
| |
| except Exception as e: |
| print(f"❌ Webhook error: {e}") |
| return {"status": "error", "reason": str(e)} |
|
|
| |
| |
| |
| @app.get("/") |
| def health(): |
| return {"status": "alive", "service": "Domify Academy Backend", "version": "3.0"} |
|
|
| |
| |
| |
| async def keep_alive_self(): |
| url = "https://domify-signup.hf.space" |
| while True: |
| try: |
| async with aiohttp.ClientSession() as session: |
| async with session.get(url) as response: |
| print(f"✅ Self-ping at {time.ctime()}: {response.status}") |
| except Exception as e: |
| print(f"❌ Self-ping failed: {e}") |
| await asyncio.sleep(300) |
|
|
| @app.on_event("startup") |
| async def startup_event(): |
| print("🚀 Domify Academy Backend Starting...") |
| asyncio.create_task(keep_alive_self()) |
| print("✅ Keep-alive task started (pings every 5 minutes)") |
|
|
|
|
|
|
| |
| |
| APPS_SCRIPT_URL = "https://script.google.com/macros/s/AKfycbwRuM5jLusScp0gzZqOmjO3_V_PXvn72vG4GSZ9P5jNb9kwav6_GVf5g0zWI7CwrXGb/exec" |
| |
| |
| |
| @app.post("/store-certificate-code") |
| async def store_certificate_code(request: Request): |
| try: |
| data = await request.json() |
| verification_code = data.get('verification_code') |
| user_name = data.get('user_name') |
| user_email = data.get('user_email') |
| user_id = data.get('user_id') |
| tier = data.get('tier') |
| date = data.get('date') |
| |
| |
| conn = sqlite3.connect('domify_users.db') |
| c = conn.cursor() |
| c.execute(""" |
| CREATE TABLE IF NOT EXISTS certificates ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| verification_code TEXT UNIQUE, |
| user_name TEXT, |
| user_email TEXT, |
| user_id TEXT, |
| tier TEXT, |
| date TEXT, |
| drive_url TEXT |
| ) |
| """) |
| c.execute(""" |
| INSERT OR REPLACE INTO certificates (verification_code, user_name, user_email, user_id, tier, date, drive_url) |
| VALUES (?, ?, ?, ?, ?, ?, ?) |
| """, (verification_code, user_name, user_email, user_id, tier, date, None)) |
| conn.commit() |
| conn.close() |
| |
| |
| try: |
| requests.post(APPS_SCRIPT_URL, json={ |
| "verification_code": verification_code, |
| "user_name": user_name, |
| "user_email": user_email, |
| "user_id": user_id, |
| "tier": tier, |
| "date": date, |
| "drive_url": "" |
| }, timeout=10) |
| print(f"✅ Sent to Google Sheet: {verification_code}") |
| except Exception as e: |
| print(f"Sheet error: {e}") |
| |
| return {"success": True, "message": "Certificate code stored"} |
| |
| except Exception as e: |
| return {"success": False, "error": str(e)} |
|
|
| |
| |
| |
| @app.post("/update-drive-url") |
| async def update_drive_url(request: Request): |
| try: |
| data = await request.json() |
| verification_code = data.get('verification_code') |
| drive_url = data.get('drive_url') |
| |
| |
| conn = sqlite3.connect('domify_users.db') |
| c = conn.cursor() |
| c.execute("UPDATE certificates SET drive_url = ? WHERE verification_code = ?", (drive_url, verification_code)) |
| conn.commit() |
| conn.close() |
| |
| |
| try: |
| |
| conn = sqlite3.connect('domify_users.db') |
| c = conn.cursor() |
| c.execute("SELECT user_name, user_email, user_id, tier, date FROM certificates WHERE verification_code = ?", (verification_code,)) |
| row = c.fetchone() |
| conn.close() |
| |
| if row: |
| requests.post(APPS_SCRIPT_URL, json={ |
| "verification_code": verification_code, |
| "user_name": row[0], |
| "user_email": row[1], |
| "user_id": row[2], |
| "tier": row[3], |
| "date": row[4], |
| "drive_url": drive_url |
| }, timeout=10) |
| except Exception as e: |
| print(f"Sheet update error: {e}") |
| |
| return {"success": True, "message": "Drive URL updated"} |
| |
| except Exception as e: |
| return {"success": False, "error": str(e)} |
|
|
| |
| |
| |
| @app.get("/verify-certificate") |
| async def verify_certificate(code: str, name: str): |
| conn = sqlite3.connect('domify_users.db') |
| c = conn.cursor() |
| c.execute(""" |
| SELECT user_name, tier, date, drive_url FROM certificates |
| WHERE verification_code = ? AND user_name = ? |
| """, (code.upper(), name)) |
| row = c.fetchone() |
| conn.close() |
| |
| if row: |
| return { |
| "valid": True, |
| "name": row[0], |
| "tier": row[1], |
| "date": row[2], |
| "imageUrl": row[3] |
| } |
| else: |
| return {"valid": False, "error": "No matching certificate found"} |