Spaces:
Sleeping
Sleeping
| import json | |
| import os | |
| import hashlib | |
| import secrets | |
| import time | |
| import random | |
| import urllib.request | |
| import urllib.error | |
| # ββ PATHS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| USER_FILE = "/app/users.json" | |
| OTP_FILE = "/app/otps.json" | |
| # ββ BREVO HTTP API CONFIG (set in HuggingFace Secrets) βββββββββββββββββββββββ | |
| BREVO_API_KEY = os.environ.get("BREVO_API_KEY", "") # Brevo API key (starts with xkeysib-) | |
| EMAIL_SENDER = os.environ.get("EMAIL_SENDER", "") # your verified sender email | |
| SENDER_NAME = "FitPlan Pro" | |
| # ββ CORE HELPERS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def hash_password(password): | |
| return hashlib.sha256(password.encode()).hexdigest() | |
| def generate_token(): | |
| return secrets.token_hex(32) | |
| def generate_otp(): | |
| return str(random.randint(100000, 999999)) | |
| # ββ FILE HELPERS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_users(): | |
| if not os.path.exists(USER_FILE): | |
| return {} | |
| try: | |
| with open(USER_FILE, "r") as f: | |
| return json.load(f) | |
| except: | |
| return {} | |
| def save_users(users): | |
| with open(USER_FILE, "w") as f: | |
| json.dump(users, f, indent=2) | |
| def load_otps(): | |
| if not os.path.exists(OTP_FILE): | |
| return {} | |
| try: | |
| with open(OTP_FILE, "r") as f: | |
| return json.load(f) | |
| except: | |
| return {} | |
| def save_otps(otps): | |
| with open(OTP_FILE, "w") as f: | |
| json.dump(otps, f, indent=2) | |
| # ββ SEND OTP via Brevo HTTP API βββββββββββββββββββββββββββββββββββββββββββββββ | |
| def send_otp_email(to_email, otp, purpose="signup"): | |
| if not BREVO_API_KEY: | |
| return False, "BREVO_API_KEY not set in Space Secrets." | |
| if not EMAIL_SENDER: | |
| return False, "EMAIL_SENDER not set in Space Secrets." | |
| html_body = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <body style="margin:0;padding:0;background:#f0f0f0;font-family:Arial,sans-serif;"> | |
| <table width="100%" cellpadding="0" cellspacing="0" style="padding:30px 0;"> | |
| <tr><td align="center"> | |
| <table width="460" cellpadding="0" cellspacing="0" | |
| style="background:white;border-radius:14px;overflow:hidden; | |
| box-shadow:0 4px 20px rgba(0,0,0,0.1);"> | |
| <tr> | |
| <td style="background:linear-gradient(135deg,#cc0000,#880000); | |
| padding:26px 32px;text-align:center;"> | |
| <h1 style="color:white;font-size:22px;margin:0;">🏋 FitPlan Pro</h1> | |
| <p style="color:rgba(255,255,255,0.7);margin:6px 0 0;font-size:11px; | |
| letter-spacing:2px;text-transform:uppercase;">Personalized Fitness</p> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="padding:30px 34px;"> | |
| <p style="color:#333;font-size:15px;margin:0 0 20px;"> | |
| Your email verification code is: | |
| </p> | |
| <table width="100%" cellpadding="0" cellspacing="0"> | |
| <tr> | |
| <td style="background:#fff5f5;border:2px dashed #cc0000; | |
| border-radius:12px;padding:24px;text-align:center;"> | |
| <span style="font-size:40px;font-weight:900; | |
| letter-spacing:14px;color:#cc0000; | |
| font-family:Arial,sans-serif;">""" + otp + """</span> | |
| </td> | |
| </tr> | |
| </table> | |
| <p style="color:#999;font-size:12px;margin:18px 0 0;"> | |
| This code expires in <strong>10 minutes</strong>.<br> | |
| If you did not request this, please ignore this email. | |
| </p> | |
| <hr style="border:none;border-top:1px solid #eee;margin:22px 0;"> | |
| <p style="color:#ccc;font-size:11px;text-align:center;margin:0;"> | |
| FitPlan Pro — Your Personalized Fitness Journey | |
| </p> | |
| </td> | |
| </tr> | |
| </table> | |
| </td></tr> | |
| </table> | |
| </body> | |
| </html> | |
| """ | |
| payload = json.dumps({ | |
| "sender": { | |
| "name": SENDER_NAME, | |
| "email": EMAIL_SENDER | |
| }, | |
| "to": [{"email": to_email}], | |
| "subject": "FitPlan Pro - Your Verification Code: " + otp, | |
| "htmlContent": html_body | |
| }).encode("utf-8") | |
| req = urllib.request.Request( | |
| "https://api.brevo.com/v3/smtp/email", | |
| data=payload, | |
| headers={ | |
| "accept": "application/json", | |
| "api-key": BREVO_API_KEY, | |
| "content-type": "application/json" | |
| }, | |
| method="POST" | |
| ) | |
| try: | |
| with urllib.request.urlopen(req, timeout=15) as resp: | |
| if resp.status in [200, 201, 202]: | |
| return True, "OTP sent successfully!" | |
| else: | |
| return False, "Brevo API error: status " + str(resp.status) | |
| except urllib.error.HTTPError as e: | |
| body = e.read().decode("utf-8", errors="ignore") | |
| return False, "Brevo error: " + str(e.code) + " - " + body | |
| except Exception as e: | |
| return False, "Failed to send OTP: " + str(e) | |
| # ββ STORE / VERIFY OTP ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def store_otp(email, otp, purpose="signup"): | |
| otps = load_otps() | |
| key = purpose + ":" + email.lower().strip() | |
| otps[key] = { | |
| "otp": otp, | |
| "created_at": time.time(), | |
| "expires_at": time.time() + 600 | |
| } | |
| save_otps(otps) | |
| def verify_otp(email, otp_input, purpose="signup"): | |
| otps = load_otps() | |
| key = purpose + ":" + email.lower().strip() | |
| if key not in otps: | |
| return False, "OTP not found. Please request a new one." | |
| record = otps[key] | |
| if time.time() > record["expires_at"]: | |
| del otps[key] | |
| save_otps(otps) | |
| return False, "OTP expired. Please request a new one." | |
| if record["otp"] != otp_input.strip(): | |
| return False, "Incorrect OTP. Please try again." | |
| del otps[key] | |
| save_otps(otps) | |
| return True, "OTP verified!" | |
| # ββ INITIATE SIGNUP βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def initiate_signup(username, email, password): | |
| users = load_users() | |
| if username.strip().lower() in [k.lower() for k in users]: | |
| return False, "Username already taken. Please choose another." | |
| for u in users.values(): | |
| if isinstance(u, dict) and u.get("email", "").lower() == email.lower().strip(): | |
| return False, "Email already registered. Please login." | |
| if len(password) < 6: | |
| return False, "Password must be at least 6 characters." | |
| otp = generate_otp() | |
| ok, msg = send_otp_email(email, otp, purpose="signup") | |
| if not ok: | |
| return False, msg | |
| store_otp(email, otp, purpose="signup") | |
| return True, "OTP sent to " + email | |
| # ββ COMPLETE SIGNUP βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def complete_signup(username, email, password, otp_input): | |
| ok, msg = verify_otp(email, otp_input, purpose="signup") | |
| if not ok: | |
| return False, None, msg | |
| users = load_users() | |
| token = generate_token() | |
| users[username.strip()] = { | |
| "email": email.lower().strip(), | |
| "password": hash_password(password), | |
| "token": token, | |
| "verified": True, | |
| "created_at": time.time() | |
| } | |
| save_users(users) | |
| return True, token, "Email verified! Account created. Please sign in." | |
| # ββ LOGIN βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def login(username_or_email, password): | |
| users = load_users() | |
| matched_key = None | |
| for k, v in users.items(): | |
| if k.lower() == username_or_email.strip().lower(): | |
| matched_key = k | |
| break | |
| if isinstance(v, dict) and v.get("email", "").lower() == username_or_email.strip().lower(): | |
| matched_key = k | |
| break | |
| if not matched_key: | |
| return False, None, "Account not found. Please sign up first." | |
| user = users[matched_key] | |
| stored_pw = user["password"] if isinstance(user, dict) else user | |
| if stored_pw != hash_password(password): | |
| return False, None, "Incorrect password. Please try again." | |
| new_token = generate_token() | |
| users[matched_key]["token"] = new_token | |
| users[matched_key]["last_login"] = time.time() | |
| save_users(users) | |
| return True, new_token, "Login successful!" | |
| # ββ VERIFY TOKEN ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def verify_token(username, token): | |
| users = load_users() | |
| for k, v in users.items(): | |
| if k.lower() == username.strip().lower(): | |
| if isinstance(v, dict): | |
| return v.get("token") == token | |
| return False | |
| # ββ LOGOUT ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def logout(username): | |
| users = load_users() | |
| for k in users: | |
| if k.lower() == username.strip().lower(): | |
| if isinstance(users[k], dict): | |
| users[k]["token"] = None | |
| users[k]["last_logout"] = time.time() | |
| save_users(users) | |
| return True | |
| return False |