Domify commited on
Commit
f4a5f4a
Β·
verified Β·
1 Parent(s): d14d883

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +103 -78
main.py CHANGED
@@ -1,13 +1,13 @@
1
- # main.py - Domify Academy Backend (Complete)
2
  from fastapi import FastAPI, Request, HTTPException
3
  from fastapi.middleware.cors import CORSMiddleware
4
  import sqlite3
5
  from datetime import datetime, timedelta
6
- import os
7
  import asyncio
8
  import aiohttp
9
- import threading
10
  import time
 
 
11
 
12
  app = FastAPI()
13
 
@@ -19,7 +19,7 @@ app.add_middleware(
19
  allow_origins=[
20
  "https://domify-academy.free.nf",
21
  "https://*.free.nf",
22
- "http://localhost:3000" # for local testing
23
  ],
24
  allow_credentials=True,
25
  allow_methods=["*"],
@@ -54,7 +54,21 @@ conn.commit()
54
  # ============================================
55
  # ADMIN IPS (add your own IP addresses here)
56
  # ============================================
57
- ADMIN_IPS = ["fe80::f26d:78ff:fe61:be53", "192.168.7.71" ,"10.92.98.240"] # <-- REPLACE WITH YOUR IPs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
  # ============================================
60
  # HELPER: get real client IP
@@ -66,14 +80,13 @@ def get_client_ip(request: Request) -> str:
66
  return request.client.host
67
 
68
  # ============================================
69
- # ENDPOINT 1: SIGNUP (receives data from frontend)
70
  # ============================================
71
  @app.post("/signup")
72
  async def signup(request: Request):
73
  try:
74
  data = await request.json()
75
 
76
- # Required fields
77
  required = ['userId', 'fullName', 'email', 'signupDate', 'country', 'source', 'ip', 'timestamp']
78
  for field in required:
79
  if field not in data:
@@ -95,13 +108,13 @@ async def signup(request: Request):
95
  raise HTTPException(status_code=500, detail=str(e))
96
 
97
  # ============================================
98
- # ENDPOINT 2: GET USER DATA (by email, user_id, or IP)
99
  # ============================================
100
  @app.get("/user")
101
  async def get_user(request: Request, email: str = None, user_id: str = None):
102
  client_ip = get_client_ip(request)
103
 
104
- # Admin override – full access
105
  if client_ip in ADMIN_IPS:
106
  return {
107
  "userId": "admin",
@@ -114,7 +127,7 @@ async def get_user(request: Request, email: str = None, user_id: str = None):
114
  "isAdmin": True
115
  }
116
 
117
- # Try to find user by email, user_id, or IP
118
  c.execute("""
119
  SELECT user_id, full_name, email, tier, expiry, cert_paid, cert_generated
120
  FROM users WHERE email=? OR user_id=? OR ip=?
@@ -133,102 +146,114 @@ async def get_user(request: Request, email: str = None, user_id: str = None):
133
  "isAdmin": False
134
  }
135
 
136
- # No user found – frontend will redirect to signup
137
  raise HTTPException(status_code=404, detail="User not found")
138
 
139
  # ============================================
140
- # ENDPOINT 3: PADDLE WEBHOOK (payment confirmation)
141
  # ============================================
142
-
143
- # ----- REPLACE THESE WITH YOUR ACTUAL PADDLE PRICE IDs -----
144
- PRICE_PROFESSIONAL = 'pri_01kn5rz5ezzb7b75b13g2pcwm3'
145
- PRICE_MASTER = 'pri_01kn5rqntftqhwsc8e0xfcek9v'
146
- PRICE_CERT_ADDON = 'pri_01kn5rce56k7pxrsr6fvs1g7xq'
147
-
148
- @app.post("/paddle-webhook")
149
- async def paddle_webhook(request: Request):
150
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  data = await request.json()
152
- event_type = data.get('event_type')
153
-
154
- # Only process successful payments
155
- if event_type != 'transaction.completed':
156
- return {"status": "ignored", "reason": f"Event {event_type} not processed"}
157
-
158
- # Extract customer email
159
- email = data.get('data', {}).get('customer_email')
160
- if not email:
161
- return {"status": "ignored", "reason": "No email in webhook"}
162
-
163
- # Extract price ID
164
- items = data.get('data', {}).get('items', [])
165
- if not items:
166
- return {"status": "ignored", "reason": "No items in webhook"}
167
- price_id = items[0].get('price', {}).get('id')
168
- if not price_id:
169
- return {"status": "ignored", "reason": "No price_id"}
170
-
171
- # Determine upgrade
172
- new_tier = None
173
- expiry_days = 0
174
- cert_paid = False
175
-
176
- if price_id == PRICE_PROFESSIONAL:
177
- new_tier = 'Professional'
178
- expiry_days = 30
179
- elif price_id == PRICE_MASTER:
180
- new_tier = 'Master'
181
- expiry_days = 30
182
- cert_paid = True
183
- elif price_id == PRICE_CERT_ADDON:
184
- cert_paid = True
185
- else:
186
- return {"status": "ignored", "reason": f"Unknown price_id: {price_id}"}
187
 
188
- # Update database
189
- expiry_date = (datetime.now() + timedelta(days=expiry_days)).isoformat() if expiry_days else None
 
 
 
 
 
 
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  c.execute("SELECT * FROM users WHERE email = ?", (email,))
192
  user = c.fetchone()
193
 
194
- if not user:
195
- return {"status": "error", "message": f"User {email} not found in database"}
196
-
197
- if new_tier:
198
- c.execute("UPDATE users SET tier = ?, expiry = ? WHERE email = ?", (new_tier, expiry_date, email))
199
- if cert_paid:
200
- c.execute("UPDATE users SET cert_paid = 1 WHERE email = ?", (email,))
 
 
 
 
 
 
 
 
 
201
 
202
  conn.commit()
203
- return {"status": "ok", "message": f"Updated {email} to {new_tier or 'cert_paid'}"}
204
 
205
  except Exception as e:
206
- print(f"Webhook error: {e}")
207
- raise HTTPException(status_code=500, detail=str(e))
208
 
209
  # ============================================
210
  # HEALTH CHECK
211
  # ============================================
212
  @app.get("/")
213
  def health():
214
- return {"status": "alive", "service": "Domify Academy Backend", "version": "2.0"}
215
-
216
-
217
-
218
 
 
 
 
219
  async def keep_alive_self():
220
- """Keep the HF Space awake by pinging itself every 5 minutes"""
221
- url = "https://domify-signup.hf.space/" # Your space URL
222
  while True:
223
  try:
224
  async with aiohttp.ClientSession() as session:
225
  async with session.get(url) as response:
226
- print(f"Self-ping at {time.ctime()}: {response.status}")
227
  except Exception as e:
228
- print(f"Self-ping failed: {e}")
229
- await asyncio.sleep(300) # Wait 5 minutes
230
 
231
- # Start the keep-alive task when the app starts
232
  @app.on_event("startup")
233
  async def startup_event():
234
- asyncio.create_task(keep_alive_self())
 
 
 
1
+ # main.py - Domify Academy Backend with Lemon Squeezy
2
  from fastapi import FastAPI, Request, HTTPException
3
  from fastapi.middleware.cors import CORSMiddleware
4
  import sqlite3
5
  from datetime import datetime, timedelta
 
6
  import asyncio
7
  import aiohttp
 
8
  import time
9
+ import hmac
10
+ import hashlib
11
 
12
  app = FastAPI()
13
 
 
19
  allow_origins=[
20
  "https://domify-academy.free.nf",
21
  "https://*.free.nf",
22
+ "http://localhost:3000"
23
  ],
24
  allow_credentials=True,
25
  allow_methods=["*"],
 
54
  # ============================================
55
  # ADMIN IPS (add your own IP addresses here)
56
  # ============================================
57
+ ADMIN_IPS = ["fe80::f26d:78ff:fe61:be53", "192.168.7.71", "10.92.98.240"]
58
+
59
+ # ============================================
60
+ # LEMON SQUEEZY CONFIGURATION
61
+ # ============================================
62
+ LEMON_STORE_ID = 342813
63
+ 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"
64
+ LEMON_WEBHOOK_SECRET = "whsec_8f3a9d2c5e1b7a4d6f8e2c9b5a7d3f1e"
65
+
66
+ # Map variant IDs to tiers
67
+ LEMON_PRODUCTS = {
68
+ 1519026: {"tier": "Professional", "days": 30},
69
+ 1519043: {"tier": "Master", "days": 30},
70
+ 1519056: {"tier": "Certificate", "days": 0},
71
+ }
72
 
73
  # ============================================
74
  # HELPER: get real client IP
 
80
  return request.client.host
81
 
82
  # ============================================
83
+ # ENDPOINT 1: SIGNUP
84
  # ============================================
85
  @app.post("/signup")
86
  async def signup(request: Request):
87
  try:
88
  data = await request.json()
89
 
 
90
  required = ['userId', 'fullName', 'email', 'signupDate', 'country', 'source', 'ip', 'timestamp']
91
  for field in required:
92
  if field not in data:
 
108
  raise HTTPException(status_code=500, detail=str(e))
109
 
110
  # ============================================
111
+ # ENDPOINT 2: GET USER DATA
112
  # ============================================
113
  @app.get("/user")
114
  async def get_user(request: Request, email: str = None, user_id: str = None):
115
  client_ip = get_client_ip(request)
116
 
117
+ # Admin override
118
  if client_ip in ADMIN_IPS:
119
  return {
120
  "userId": "admin",
 
127
  "isAdmin": True
128
  }
129
 
130
+ # Find user
131
  c.execute("""
132
  SELECT user_id, full_name, email, tier, expiry, cert_paid, cert_generated
133
  FROM users WHERE email=? OR user_id=? OR ip=?
 
146
  "isAdmin": False
147
  }
148
 
 
149
  raise HTTPException(status_code=404, detail="User not found")
150
 
151
  # ============================================
152
+ # ENDPOINT 3: LEMON SQUEEZY WEBHOOK
153
  # ============================================
154
+ @app.post("/webhook/lemonsqueezy")
155
+ async def lemon_webhook(request: Request):
 
 
 
 
 
 
156
  try:
157
+ # Get raw body and signature
158
+ body = await request.body()
159
+ signature = request.headers.get("X-Signature")
160
+
161
+ if not signature:
162
+ print("❌ No signature in webhook")
163
+ return {"status": "ignored", "reason": "no signature"}
164
+
165
+ # Verify signature
166
+ expected = hmac.new(
167
+ LEMON_WEBHOOK_SECRET.encode(),
168
+ body,
169
+ hashlib.sha256
170
+ ).hexdigest()
171
+
172
+ if not hmac.compare_digest(signature, expected):
173
+ print("❌ Invalid signature")
174
+ return {"status": "ignored", "reason": "invalid signature"}
175
+
176
+ # Parse data
177
  data = await request.json()
178
+ event_name = data.get("meta", {}).get("event_name")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
+ if event_name != "order_created":
181
+ return {"status": "ignored", "reason": f"event {event_name}"}
182
+
183
+ # Extract customer email and variant
184
+ order_data = data.get("data", {}).get("attributes", {})
185
+ email = order_data.get("user_email")
186
+ first_item = order_data.get("first_order_item", {})
187
+ variant_id = first_item.get("variant_id")
188
 
189
+ if not email or not variant_id:
190
+ print(f"❌ Missing email or variant")
191
+ return {"status": "ignored", "reason": "missing email or variant"}
192
+
193
+ # Map variant to product
194
+ product = LEMON_PRODUCTS.get(variant_id)
195
+ if not product:
196
+ print(f"❌ Unknown variant: {variant_id}")
197
+ return {"status": "ignored", "reason": f"unknown variant {variant_id}"}
198
+
199
+ print(f"βœ… Processing payment: {email} -> {product['tier']}")
200
+
201
+ # Calculate expiry
202
+ expiry = None
203
+ if product["days"] > 0:
204
+ expiry = (datetime.now() + timedelta(days=product["days"])).isoformat()
205
+
206
+ # Update database
207
  c.execute("SELECT * FROM users WHERE email = ?", (email,))
208
  user = c.fetchone()
209
 
210
+ if user:
211
+ c.execute("UPDATE users SET tier = ?, expiry = ? WHERE email = ?",
212
+ (product["tier"], expiry, email))
213
+ if product["tier"] == "Master":
214
+ c.execute("UPDATE users SET cert_paid = 1 WHERE email = ?", (email,))
215
+ print(f"βœ… Updated existing user: {email}")
216
+ else:
217
+ c.execute("""
218
+ INSERT INTO users (user_id, full_name, email, tier, expiry, cert_paid, timestamp)
219
+ VALUES (?, ?, ?, ?, ?, ?, ?)
220
+ """, (
221
+ f"auto_{int(time.time())}", email, email, product["tier"], expiry,
222
+ 1 if product["tier"] == "Master" else 0,
223
+ datetime.now().isoformat()
224
+ ))
225
+ print(f"βœ… Created new user: {email}")
226
 
227
  conn.commit()
228
+ return {"status": "ok", "message": f"Updated {email} to {product['tier']}"}
229
 
230
  except Exception as e:
231
+ print(f"❌ Webhook error: {e}")
232
+ return {"status": "error", "reason": str(e)}
233
 
234
  # ============================================
235
  # HEALTH CHECK
236
  # ============================================
237
  @app.get("/")
238
  def health():
239
+ return {"status": "alive", "service": "Domify Academy Backend", "version": "3.0"}
 
 
 
240
 
241
+ # ============================================
242
+ # KEEP-ALIVE FUNCTION (Prevents HF Space from sleeping)
243
+ # ============================================
244
  async def keep_alive_self():
245
+ url = "https://domify-signup.hf.space"
 
246
  while True:
247
  try:
248
  async with aiohttp.ClientSession() as session:
249
  async with session.get(url) as response:
250
+ print(f"βœ… Self-ping at {time.ctime()}: {response.status}")
251
  except Exception as e:
252
+ print(f"❌ Self-ping failed: {e}")
253
+ await asyncio.sleep(300)
254
 
 
255
  @app.on_event("startup")
256
  async def startup_event():
257
+ print("πŸš€ Domify Academy Backend Starting...")
258
+ asyncio.create_task(keep_alive_self())
259
+ print("βœ… Keep-alive task started (pings every 5 minutes)")