Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
from flask import Flask, request, Response, render_template_string, jsonify, redirect, url_for
|
| 3 |
import hmac
|
|
@@ -69,9 +71,10 @@ def download_data_from_hf():
|
|
| 69 |
return True
|
| 70 |
except RepositoryNotFoundError:
|
| 71 |
logging.error(f"Hugging Face repository '{REPO_ID}' not found. Cannot download data.")
|
|
|
|
| 72 |
except Exception as e:
|
| 73 |
logging.error(f"Error downloading data from Hugging Face: {e}")
|
| 74 |
-
|
| 75 |
|
| 76 |
def load_visitor_data():
|
| 77 |
global visitor_data_cache
|
|
@@ -83,28 +86,25 @@ def load_visitor_data():
|
|
| 83 |
logging.info("Visitor data loaded from local JSON.")
|
| 84 |
except FileNotFoundError:
|
| 85 |
logging.warning(f"{DATA_FILE} not found locally. Starting with empty data.")
|
| 86 |
-
visitor_data_cache = {
|
| 87 |
except json.JSONDecodeError:
|
| 88 |
logging.error(f"Error decoding {DATA_FILE}. Starting with empty data.")
|
| 89 |
-
visitor_data_cache = {
|
| 90 |
except Exception as e:
|
| 91 |
logging.error(f"Unexpected error loading visitor data: {e}")
|
| 92 |
-
visitor_data_cache = {
|
| 93 |
|
| 94 |
if "organization_details" not in visitor_data_cache:
|
| 95 |
visitor_data_cache["organization_details"] = {}
|
| 96 |
|
| 97 |
-
return visitor_data_cache
|
| 98 |
-
|
| 99 |
def save_visitor_data():
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
logging.error(f"Error saving visitor data: {e}")
|
| 108 |
|
| 109 |
def upload_data_to_hf():
|
| 110 |
if not HF_TOKEN_WRITE:
|
|
@@ -117,8 +117,7 @@ def upload_data_to_hf():
|
|
| 117 |
try:
|
| 118 |
api = HfApi()
|
| 119 |
with _data_lock:
|
| 120 |
-
|
| 121 |
-
if not file_content_exists:
|
| 122 |
logging.warning(f"{DATA_FILE} is empty. Skipping upload.")
|
| 123 |
return
|
| 124 |
|
|
@@ -156,9 +155,7 @@ def verify_telegram_data(init_data_str):
|
|
| 156 |
if not received_hash:
|
| 157 |
return None, False
|
| 158 |
|
| 159 |
-
data_check_list = []
|
| 160 |
-
for key, value in sorted(parsed_data.items()):
|
| 161 |
-
data_check_list.append(f"{key}={value[0]}")
|
| 162 |
data_check_string = "\n".join(data_check_list)
|
| 163 |
|
| 164 |
secret_key = hmac.new("WebAppData".encode(), BOT_TOKEN.encode(), hashlib.sha256).digest()
|
|
@@ -166,12 +163,11 @@ def verify_telegram_data(init_data_str):
|
|
| 166 |
|
| 167 |
if calculated_hash == received_hash:
|
| 168 |
auth_date = int(parsed_data.get('auth_date', [0])[0])
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
logging.warning(f"Telegram InitData is older than 24 hours (Auth Date: {auth_date}, Current: {current_time}).")
|
| 172 |
return parsed_data, True
|
| 173 |
else:
|
| 174 |
-
logging.warning(f"Data verification failed.
|
| 175 |
return parsed_data, False
|
| 176 |
except Exception as e:
|
| 177 |
logging.error(f"Error verifying Telegram data: {e}")
|
|
@@ -462,7 +458,7 @@ TEMPLATE = """
|
|
| 462 |
{% for phone in org_details.phone_numbers %}
|
| 463 |
<li class="business-card-phone-item">
|
| 464 |
<a href="tel:{{ phone }}">
|
| 465 |
-
<img src="data:image/svg+xml;base64,
|
| 466 |
{{ phone }}
|
| 467 |
</a>
|
| 468 |
</li>
|
|
@@ -1375,31 +1371,23 @@ ADMIN_TEMPLATE = """
|
|
| 1375 |
@app.route('/')
|
| 1376 |
def index():
|
| 1377 |
user_id_str = request.args.get('user_id_for_test')
|
| 1378 |
-
all_data = load_visitor_data()
|
| 1379 |
user_data = {}
|
| 1380 |
is_first_visit = False
|
| 1381 |
|
| 1382 |
-
if user_id_str and user_id_str in
|
| 1383 |
-
user_data =
|
| 1384 |
user_data['id'] = user_id_str
|
| 1385 |
-
|
| 1386 |
is_first_visit = not user_data.get('has_been_welcomed', False)
|
| 1387 |
-
|
| 1388 |
bonus_history = user_data.get('history', [])
|
| 1389 |
-
for item in bonus_history: item['transaction_type'] = 'bonus'
|
| 1390 |
debt_history = user_data.get('debt_history', [])
|
|
|
|
| 1391 |
for item in debt_history: item['transaction_type'] = 'debt'
|
| 1392 |
-
|
| 1393 |
-
combined_history = sorted(bonus_history + debt_history, key=lambda x: x['date'], reverse=True)
|
| 1394 |
-
user_data['combined_history'] = combined_history
|
| 1395 |
user_data['invoices'] = user_data.get('invoices', [])
|
| 1396 |
else:
|
| 1397 |
-
user_data = {
|
| 1398 |
-
"id": "N/A", "bonuses": 0, "debts": 0, "history": [], "debt_history": [],
|
| 1399 |
-
"combined_history": [], "invoices": [], "referral_code": "N/A"
|
| 1400 |
-
}
|
| 1401 |
|
| 1402 |
-
org_details =
|
| 1403 |
return render_template_string(TEMPLATE, user=user_data, org_details=org_details, is_first_visit=is_first_visit)
|
| 1404 |
|
| 1405 |
@app.route('/verify', methods=['POST'])
|
|
@@ -1414,8 +1402,7 @@ def verify_data():
|
|
| 1414 |
user_info_dict = {}
|
| 1415 |
if user_data_parsed and 'user' in user_data_parsed:
|
| 1416 |
try:
|
| 1417 |
-
|
| 1418 |
-
user_info_dict = json.loads(user_json_str)
|
| 1419 |
except Exception as e:
|
| 1420 |
logging.error(f"Could not parse user JSON: {e}")
|
| 1421 |
|
|
@@ -1423,38 +1410,39 @@ def verify_data():
|
|
| 1423 |
tg_user_id = user_info_dict.get('id')
|
| 1424 |
if tg_user_id:
|
| 1425 |
now = datetime.now(BISHKEK_TZ)
|
| 1426 |
-
|
| 1427 |
-
|
| 1428 |
-
|
| 1429 |
-
|
| 1430 |
-
|
| 1431 |
-
|
| 1432 |
-
|
| 1433 |
-
|
| 1434 |
-
|
| 1435 |
-
|
| 1436 |
-
|
| 1437 |
-
|
| 1438 |
-
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
|
| 1445 |
-
|
| 1446 |
-
|
| 1447 |
-
|
| 1448 |
-
|
| 1449 |
-
|
| 1450 |
-
|
| 1451 |
-
|
| 1452 |
-
|
|
|
|
|
|
|
| 1453 |
return jsonify({"status": "ok", "verified": True, "user_id": user_id_to_save})
|
| 1454 |
else:
|
| 1455 |
return jsonify({"status": "error", "verified": True, "message": "User ID not found"}), 400
|
| 1456 |
else:
|
| 1457 |
-
logging.warning(f"Verification failed for user: {user_info_dict.get('id')}")
|
| 1458 |
return jsonify({"status": "error", "verified": False, "message": "Invalid data"}), 403
|
| 1459 |
except Exception as e:
|
| 1460 |
logging.exception("Error in /verify endpoint")
|
|
@@ -1466,36 +1454,32 @@ def submit_referral():
|
|
| 1466 |
data = request.get_json()
|
| 1467 |
user_id = str(data.get('user_id'))
|
| 1468 |
referral_code = data.get('referral_code')
|
| 1469 |
-
all_data = load_visitor_data()
|
| 1470 |
-
|
| 1471 |
-
if not user_id or user_id not in all_data:
|
| 1472 |
-
return jsonify({"status": "error", "message": "Пользователь не найден."}), 404
|
| 1473 |
|
| 1474 |
-
|
| 1475 |
-
|
| 1476 |
-
|
| 1477 |
|
| 1478 |
-
|
|
|
|
|
|
|
| 1479 |
|
| 1480 |
-
|
| 1481 |
-
referrer_id = next((u_id for u_id, u in all_data.items() if u_id != "organization_details" and u.get('referral_code') == referral_code), None)
|
| 1482 |
-
|
| 1483 |
-
if not referrer_id:
|
| 1484 |
-
return jsonify({"status": "error", "message": "Промокод не найден."}), 404
|
| 1485 |
-
|
| 1486 |
-
if referrer_id == user_id:
|
| 1487 |
-
return jsonify({"status": "error", "message": "Нельзя использовать свой промокод."}), 400
|
| 1488 |
|
| 1489 |
-
|
| 1490 |
-
|
| 1491 |
-
|
| 1492 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1493 |
|
| 1494 |
save_visitor_data()
|
| 1495 |
-
|
| 1496 |
-
|
| 1497 |
-
save_visitor_data()
|
| 1498 |
-
return jsonify({"status": "ok", "message": "Добро пожаловать!"}), 200
|
| 1499 |
|
| 1500 |
except Exception as e:
|
| 1501 |
logging.exception("Error in /submit_referral endpoint")
|
|
@@ -1503,20 +1487,17 @@ def submit_referral():
|
|
| 1503 |
|
| 1504 |
@app.route('/admin')
|
| 1505 |
def admin_panel():
|
| 1506 |
-
all_data = load_visitor_data()
|
| 1507 |
users_list = []
|
| 1508 |
-
|
| 1509 |
-
user_name_map = {uid: f"{ud.get('first_name', '')} {ud.get('last_name', '')}".strip() or f"ID: {uid}" for uid, ud in all_data.items() if uid != "organization_details"}
|
| 1510 |
|
| 1511 |
-
for user_id, user_data in
|
| 1512 |
if user_id == "organization_details": continue
|
| 1513 |
-
|
| 1514 |
-
|
| 1515 |
-
|
| 1516 |
-
referrer_id =
|
| 1517 |
-
|
| 1518 |
-
|
| 1519 |
-
users_list.append(user_data)
|
| 1520 |
|
| 1521 |
total_users = len(users_list)
|
| 1522 |
total_bonuses = sum(u.get('bonuses', 0) for u in users_list)
|
|
@@ -1535,25 +1516,24 @@ def add_client():
|
|
| 1535 |
data = request.get_json()
|
| 1536 |
phone_number = data.get('phone_number')
|
| 1537 |
first_name = data.get('first_name')
|
| 1538 |
-
|
| 1539 |
if not phone_number or not first_name:
|
| 1540 |
return jsonify({"status": "error", "message": "Имя и номер телефона обязательны."}), 400
|
| 1541 |
|
| 1542 |
-
|
| 1543 |
-
|
| 1544 |
-
|
| 1545 |
-
|
| 1546 |
-
|
| 1547 |
-
|
| 1548 |
-
|
| 1549 |
-
|
| 1550 |
-
|
| 1551 |
-
|
| 1552 |
-
|
| 1553 |
-
|
| 1554 |
-
|
| 1555 |
-
|
| 1556 |
-
|
| 1557 |
return jsonify({"status": "ok", "message": "Client added successfully"}), 201
|
| 1558 |
except Exception as e:
|
| 1559 |
logging.exception("Error in /admin/add_client endpoint")
|
|
@@ -1568,31 +1548,29 @@ def add_transaction():
|
|
| 1568 |
deduct_amount = float(data.get('deduct_amount', 0))
|
| 1569 |
add_debt_amount = float(data.get('add_debt_amount', 0))
|
| 1570 |
repay_debt_amount = float(data.get('repay_debt_amount', 0))
|
| 1571 |
-
|
| 1572 |
if not user_id: return jsonify({"status": "error", "message": "User ID is required"}), 400
|
| 1573 |
-
all_data = load_visitor_data()
|
| 1574 |
-
if user_id not in all_data: return jsonify({"status": "error", "message": "User not found"}), 404
|
| 1575 |
-
|
| 1576 |
-
user = all_data[user_id]
|
| 1577 |
-
now = datetime.now(BISHKEK_TZ)
|
| 1578 |
-
now_iso, now_str = now.isoformat(), now.strftime('%Y-%m-%d %H:%M:%S')
|
| 1579 |
|
| 1580 |
-
|
| 1581 |
-
|
| 1582 |
-
|
| 1583 |
-
|
| 1584 |
-
|
| 1585 |
-
|
| 1586 |
-
|
| 1587 |
-
|
| 1588 |
-
|
| 1589 |
-
|
| 1590 |
-
|
| 1591 |
-
|
| 1592 |
-
|
| 1593 |
-
|
| 1594 |
-
|
| 1595 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1596 |
except Exception as e:
|
| 1597 |
logging.exception("Error in /admin/add_transaction endpoint")
|
| 1598 |
return jsonify({"status": "error", "message": str(e)}), 500
|
|
@@ -1604,25 +1582,20 @@ def add_invoice():
|
|
| 1604 |
user_id = str(data.get('user_id'))
|
| 1605 |
total_amount = float(data.get('total_amount', 0))
|
| 1606 |
items = data.get('items', [])
|
| 1607 |
-
|
| 1608 |
if not user_id: return jsonify({"status": "error", "message": "User ID is required"}), 400
|
| 1609 |
if not items: return jsonify({"status": "error", "message": "Необходимо добавить товары в накладную."}), 400
|
| 1610 |
|
| 1611 |
-
|
| 1612 |
-
|
| 1613 |
-
|
| 1614 |
-
|
| 1615 |
-
|
| 1616 |
-
|
| 1617 |
-
|
| 1618 |
-
|
| 1619 |
-
|
| 1620 |
-
|
| 1621 |
-
|
| 1622 |
-
if 'invoices' not in user: user['invoices'] = []
|
| 1623 |
-
user['invoices'].append(new_invoice)
|
| 1624 |
-
|
| 1625 |
-
save_visitor_data()
|
| 1626 |
return jsonify({"status": "ok", "message": "Invoice added successfully", "invoice_id": invoice_id}), 200
|
| 1627 |
except Exception as e:
|
| 1628 |
logging.exception("Error in /admin/add_invoice endpoint")
|
|
@@ -1635,17 +1608,14 @@ def delete_invoice():
|
|
| 1635 |
user_id, invoice_id = str(data.get('user_id')), data.get('invoice_id')
|
| 1636 |
if not user_id or not invoice_id: return jsonify({"status": "error", "message": "User ID and Invoice ID are required"}), 400
|
| 1637 |
|
| 1638 |
-
|
| 1639 |
-
|
| 1640 |
-
|
| 1641 |
-
|
| 1642 |
-
|
| 1643 |
-
|
| 1644 |
-
|
| 1645 |
-
|
| 1646 |
-
if len(user['invoices']) == original_count: return jsonify({"status": "error", "message": "Invoice not found for this user"}), 404
|
| 1647 |
-
|
| 1648 |
-
save_visitor_data()
|
| 1649 |
return jsonify({"status": "ok", "message": "Invoice deleted successfully"}), 200
|
| 1650 |
except Exception as e:
|
| 1651 |
logging.exception("Error in /admin/delete_invoice endpoint")
|
|
@@ -1655,21 +1625,21 @@ def delete_invoice():
|
|
| 1655 |
def delete_client():
|
| 1656 |
try:
|
| 1657 |
user_id = str(request.get_json().get('user_id'))
|
| 1658 |
-
if not user_id:
|
| 1659 |
-
return jsonify({"status": "error", "message": "User ID is required"}), 400
|
| 1660 |
-
|
| 1661 |
-
all_data = load_visitor_data()
|
| 1662 |
-
|
| 1663 |
-
if user_id not in all_data:
|
| 1664 |
-
return jsonify({"status": "error", "message": "User not found"}), 404
|
| 1665 |
-
|
| 1666 |
-
if all_data[user_id].get('telegram_id') is not None:
|
| 1667 |
-
return jsonify({"status": "error", "message": "Cannot delete a Telegram-linked user"}), 403
|
| 1668 |
-
|
| 1669 |
-
del all_data[user_id]
|
| 1670 |
-
|
| 1671 |
-
save_visitor_data()
|
| 1672 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1673 |
return jsonify({"status": "ok", "message": "Client deleted successfully"}), 200
|
| 1674 |
except Exception as e:
|
| 1675 |
logging.exception("Error in /admin/delete_client endpoint")
|
|
@@ -1678,8 +1648,7 @@ def delete_client():
|
|
| 1678 |
@app.route('/admin/organization_details', methods=['GET'])
|
| 1679 |
def get_organization_details():
|
| 1680 |
try:
|
| 1681 |
-
|
| 1682 |
-
return jsonify(all_data.get('organization_details', {})), 200
|
| 1683 |
except Exception as e:
|
| 1684 |
logging.exception("Error getting organization details")
|
| 1685 |
return jsonify({"status": "error", "message": str(e)}), 500
|
|
@@ -1688,14 +1657,13 @@ def get_organization_details():
|
|
| 1688 |
def save_organization_details():
|
| 1689 |
try:
|
| 1690 |
data = request.get_json()
|
| 1691 |
-
|
| 1692 |
-
|
| 1693 |
-
|
| 1694 |
-
|
| 1695 |
-
|
| 1696 |
-
|
| 1697 |
-
|
| 1698 |
-
save_visitor_data()
|
| 1699 |
return jsonify({"status": "ok", "message": "Organization details saved successfully"}), 200
|
| 1700 |
except Exception as e:
|
| 1701 |
logging.exception("Error saving organization details")
|
|
@@ -1705,26 +1673,29 @@ if __name__ == '__main__':
|
|
| 1705 |
print("--- BONUS SYSTEM SERVER ---")
|
| 1706 |
print(f"Server starting on http://{HOST}:{PORT}")
|
| 1707 |
|
| 1708 |
-
print("
|
| 1709 |
load_visitor_data()
|
| 1710 |
|
| 1711 |
-
if
|
| 1712 |
-
print("Local data not found or empty.
|
| 1713 |
-
|
| 1714 |
-
|
| 1715 |
-
|
|
|
|
|
|
|
|
|
|
| 1716 |
else:
|
| 1717 |
-
|
|
|
|
| 1718 |
|
| 1719 |
-
if not HF_TOKEN_READ or not HF_TOKEN_WRITE:
|
| 1720 |
-
print("WARNING: Hugging Face token(s) not set. Backup/restore functionality will be limited.")
|
| 1721 |
-
|
| 1722 |
print("WARNING: The /admin route is NOT protected. Implement proper authentication for production.")
|
| 1723 |
-
|
| 1724 |
if HF_TOKEN_WRITE:
|
| 1725 |
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
| 1726 |
backup_thread.start()
|
| 1727 |
print("Periodic backup thread started (every hour).")
|
|
|
|
|
|
|
| 1728 |
|
| 1729 |
print("--- Server Ready ---")
|
| 1730 |
app.run(host=HOST, port=PORT, debug=False)
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
import os
|
| 4 |
from flask import Flask, request, Response, render_template_string, jsonify, redirect, url_for
|
| 5 |
import hmac
|
|
|
|
| 71 |
return True
|
| 72 |
except RepositoryNotFoundError:
|
| 73 |
logging.error(f"Hugging Face repository '{REPO_ID}' not found. Cannot download data.")
|
| 74 |
+
return False
|
| 75 |
except Exception as e:
|
| 76 |
logging.error(f"Error downloading data from Hugging Face: {e}")
|
| 77 |
+
return False
|
| 78 |
|
| 79 |
def load_visitor_data():
|
| 80 |
global visitor_data_cache
|
|
|
|
| 86 |
logging.info("Visitor data loaded from local JSON.")
|
| 87 |
except FileNotFoundError:
|
| 88 |
logging.warning(f"{DATA_FILE} not found locally. Starting with empty data.")
|
| 89 |
+
visitor_data_cache = {}
|
| 90 |
except json.JSONDecodeError:
|
| 91 |
logging.error(f"Error decoding {DATA_FILE}. Starting with empty data.")
|
| 92 |
+
visitor_data_cache = {}
|
| 93 |
except Exception as e:
|
| 94 |
logging.error(f"Unexpected error loading visitor data: {e}")
|
| 95 |
+
visitor_data_cache = {}
|
| 96 |
|
| 97 |
if "organization_details" not in visitor_data_cache:
|
| 98 |
visitor_data_cache["organization_details"] = {}
|
| 99 |
|
|
|
|
|
|
|
| 100 |
def save_visitor_data():
|
| 101 |
+
try:
|
| 102 |
+
with open(DATA_FILE, 'w', encoding='utf-8') as f:
|
| 103 |
+
json.dump(visitor_data_cache, f, ensure_ascii=False, indent=4)
|
| 104 |
+
logging.info(f"Visitor data successfully saved to {DATA_FILE}.")
|
| 105 |
+
upload_data_to_hf_async()
|
| 106 |
+
except Exception as e:
|
| 107 |
+
logging.error(f"Error saving visitor data: {e}")
|
|
|
|
| 108 |
|
| 109 |
def upload_data_to_hf():
|
| 110 |
if not HF_TOKEN_WRITE:
|
|
|
|
| 117 |
try:
|
| 118 |
api = HfApi()
|
| 119 |
with _data_lock:
|
| 120 |
+
if not os.path.getsize(DATA_FILE) > 0:
|
|
|
|
| 121 |
logging.warning(f"{DATA_FILE} is empty. Skipping upload.")
|
| 122 |
return
|
| 123 |
|
|
|
|
| 155 |
if not received_hash:
|
| 156 |
return None, False
|
| 157 |
|
| 158 |
+
data_check_list = [f"{key}={value[0]}" for key, value in sorted(parsed_data.items())]
|
|
|
|
|
|
|
| 159 |
data_check_string = "\n".join(data_check_list)
|
| 160 |
|
| 161 |
secret_key = hmac.new("WebAppData".encode(), BOT_TOKEN.encode(), hashlib.sha256).digest()
|
|
|
|
| 163 |
|
| 164 |
if calculated_hash == received_hash:
|
| 165 |
auth_date = int(parsed_data.get('auth_date', [0])[0])
|
| 166 |
+
if (int(time.time()) - auth_date) > 86400:
|
| 167 |
+
logging.warning(f"Telegram InitData is older than 24 hours.")
|
|
|
|
| 168 |
return parsed_data, True
|
| 169 |
else:
|
| 170 |
+
logging.warning(f"Data verification failed.")
|
| 171 |
return parsed_data, False
|
| 172 |
except Exception as e:
|
| 173 |
logging.error(f"Error verifying Telegram data: {e}")
|
|
|
|
| 458 |
{% for phone in org_details.phone_numbers %}
|
| 459 |
<li class="business-card-phone-item">
|
| 460 |
<a href="tel:{{ phone }}">
|
| 461 |
+
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q2vb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJtMjIuMDIyIDEuOTY3LTMuMDkgMy41MjNhOC40OCA4LjQ4IDAgMCAwLTEzLjA5OSAwTDEuOTc4IDEuOTY3Yy0uOTc0LTEuMTA3LTIuNTI1LS40MS0yLjUxMiAxLjAzNXYxOC44NjhjLjAwMSAxLjQ0NSAxLjUyOCAyLjEzNiAyLjQ4OCAxLjAzN2wzLjA5LTMuNTIzYTEwLjE2IDEwLjE2IDAgMCAwIDEzLjA5OSAwbDMuMDkgMy41MjNjLjk3NCAxLjEwNyAyLjUyNS40MSAyLjUxMi0xLjAzN1YyLjAwMWMtLjAwMS0xLjQ0NS0xLjUyOC0yLjEzNi0yLjQ4OC0xLjAzN3oiLz48cGF0aCBkPSJtNy41IDIuOTc2YzEuMjI1LjA2MSAyLjQ1LjA5MSAzLjY3NS4wOTFzMi40NS0uMDMxIDMuNjc1LS4wOTFjLjY4Ny0uMDM0IDEuNDMzLjMyNSAxLjcwOS43NDdsMS40NzMgMS42NzhjLjQ5LjU1Ny44MzcgMS4yNjcuODk5IDIuMDgzLjA0MS41NTEuMDYyIDEuMS4wNjIgMS42NnYxLjUwNGMwIC43NzUtLjE3IDEuNTUzLS41MDggMi4yMzMtLjQ3Mi45MjUtMS4xMTcgMS43NDgtMS45MzUgMi4zODhsLS4xMzQuMTA1Yy0uNzU0LjU4My0xLjY0Ny45NzUtMi41NjIgMS4xODQtLjYwMi4xMzYtMS4wMDkuMjA4LTEuNzI1LjIwOC0xLjExNSAwLjc1NC0uMTUzIDMuNjgyLS4xNTMgNS41NDJzLjE1NCA0Ljc4Ni4xNTMgNS41NDJjLjAwMi0uNjU3LS4wNzgtMS4yNy0uMjItMS44MjQtLjE0OC0uNTU4LS40MTctMS4wODgtLjc2NC0xLjU1MS0uNjMyLS44NDUtMS40NDctMS41NTctMi40MTItMi4wNjItLjk2My0uNTA3LTEuOTk0LS44Mi0zLjA0Ni0uOTUyLS44MTMt.MTA0LTEuNTcxLS4xNDUtMi4zMzgtLjA5OS0uNjk0LjAxMS0xLjMzNy4xMDYtMS45MjQuMjg1LS41ODkuMTg0LTEuMTI2LjQyMS0xLjYwMS42OTMtLjQ3Ni4yNzMtLjkwNi41NzQtMS4yOTcuODktLjM4OC4zMTQtLjc0My42NDctMS4wNjcuOTk4LS4zMjYuMzUzLS42NDYuNzIyLS45NTkuMTA1OS0uMzEzLjMyOC0uNjIuNjUzLS45MjEuOTc1LS4yOTQuMzExLS41NzYuNjIzLS44NDQuOTMyLS4yNy4zMDktLjUyMy42MTctLjc1Ny45MTgtLjE5Ny4yNTQtLjM2MS40OTMtLjQ4MS43MjUtLjExOS4yMzItLjE4NS40NTItLjE5My41OTMtLjAwOS4xNDUtLjAxNC4yOTMtLjAxNi40NDF2MS43NmMwIC41NzYtLjE3NSAxLjEyMi0uNDQ3IDEuNTYtLjIyNy4zOTMtLjU2NS41OTktMS4wMTkuNTk5LTEuMTg2LS4wMDEtMS45OTYtMS4zOTctMi4yOTYtMi44NDItLjMyMy0xLjU1OC0uMzIzLTQuNTY5LS4zMjMtNi40MTFzLjAxNS00Ljg1NC4zMjMtNi40MTJjLjI5OS0xLjQ0NSAxLjExLTIuODQxIDIuMjk2LTIuODQyLjQ1NC4wMDcuNzgxLjI1OSAxLjAxOS42MDkuMjE1LjMzNC4zMjMuNzMuMzIzIDEuMTQ3di45NWMuMDMgMS4zMTQtLjAxNSAyLjYxLS4xNDcgMy44NzUtLjEwNiAxLjAzLS4yMzQgMi4wNDYtLjM1MSAyLjk5NmwuNTkzLS4zNzljLjMyNi0uMjA2LjY4Mi0uMzgxIDEuMDQ5LS41NzEuMzg2LS4xOTcgLjc5LS4zNjUgMS4xOTQtLjQ5NC40MDUtLjEyOS43ODctLjIzMyAxLjEyOC0uMjkwLjM0Mi0uMDU4LjYwNC0uMDc0Ljc4Mi0uMDQ3WiIvPjwvc3ZnPg==">
|
| 462 |
{{ phone }}
|
| 463 |
</a>
|
| 464 |
</li>
|
|
|
|
| 1371 |
@app.route('/')
|
| 1372 |
def index():
|
| 1373 |
user_id_str = request.args.get('user_id_for_test')
|
|
|
|
| 1374 |
user_data = {}
|
| 1375 |
is_first_visit = False
|
| 1376 |
|
| 1377 |
+
if user_id_str and user_id_str in visitor_data_cache:
|
| 1378 |
+
user_data = visitor_data_cache[user_id_str]
|
| 1379 |
user_data['id'] = user_id_str
|
|
|
|
| 1380 |
is_first_visit = not user_data.get('has_been_welcomed', False)
|
|
|
|
| 1381 |
bonus_history = user_data.get('history', [])
|
|
|
|
| 1382 |
debt_history = user_data.get('debt_history', [])
|
| 1383 |
+
for item in bonus_history: item['transaction_type'] = 'bonus'
|
| 1384 |
for item in debt_history: item['transaction_type'] = 'debt'
|
| 1385 |
+
user_data['combined_history'] = sorted(bonus_history + debt_history, key=lambda x: x['date'], reverse=True)
|
|
|
|
|
|
|
| 1386 |
user_data['invoices'] = user_data.get('invoices', [])
|
| 1387 |
else:
|
| 1388 |
+
user_data = {"id": "N/A", "bonuses": 0, "debts": 0, "combined_history": [], "invoices": [], "referral_code": "N/A"}
|
|
|
|
|
|
|
|
|
|
| 1389 |
|
| 1390 |
+
org_details = visitor_data_cache.get('organization_details', {})
|
| 1391 |
return render_template_string(TEMPLATE, user=user_data, org_details=org_details, is_first_visit=is_first_visit)
|
| 1392 |
|
| 1393 |
@app.route('/verify', methods=['POST'])
|
|
|
|
| 1402 |
user_info_dict = {}
|
| 1403 |
if user_data_parsed and 'user' in user_data_parsed:
|
| 1404 |
try:
|
| 1405 |
+
user_info_dict = json.loads(unquote(user_data_parsed['user'][0]))
|
|
|
|
| 1406 |
except Exception as e:
|
| 1407 |
logging.error(f"Could not parse user JSON: {e}")
|
| 1408 |
|
|
|
|
| 1410 |
tg_user_id = user_info_dict.get('id')
|
| 1411 |
if tg_user_id:
|
| 1412 |
now = datetime.now(BISHKEK_TZ)
|
| 1413 |
+
user_id_to_save = None
|
| 1414 |
+
|
| 1415 |
+
with _data_lock:
|
| 1416 |
+
existing_user_key = next((k for k, u in visitor_data_cache.items() if k != "organization_details" and str(u.get('telegram_id')) == str(tg_user_id)), None)
|
| 1417 |
+
|
| 1418 |
+
if existing_user_key:
|
| 1419 |
+
user_entry = visitor_data_cache[existing_user_key]
|
| 1420 |
+
user_entry.update({
|
| 1421 |
+
'first_name': user_info_dict.get('first_name'), 'last_name': user_info_dict.get('last_name'),
|
| 1422 |
+
'username': user_info_dict.get('username'), 'photo_url': user_info_dict.get('photo_url'),
|
| 1423 |
+
'visited_at': now.timestamp(), 'visited_at_str': now.strftime('%Y-%m-%d %H:%M:%S')
|
| 1424 |
+
})
|
| 1425 |
+
user_id_to_save = existing_user_key
|
| 1426 |
+
else:
|
| 1427 |
+
new_user_id = generate_unique_id(visitor_data_cache)
|
| 1428 |
+
user_entry = {
|
| 1429 |
+
'id': new_user_id, 'telegram_id': tg_user_id,
|
| 1430 |
+
'first_name': user_info_dict.get('first_name'), 'last_name': user_info_dict.get('last_name'),
|
| 1431 |
+
'username': user_info_dict.get('username'), 'photo_url': user_info_dict.get('photo_url'),
|
| 1432 |
+
'is_premium': user_info_dict.get('is_premium', False), 'phone_number': None,
|
| 1433 |
+
'visited_at': now.timestamp(), 'visited_at_str': now.strftime('%Y-%m-%d %H:%M:%S'),
|
| 1434 |
+
'bonuses': 0, 'history': [], 'debts': 0, 'debt_history': [], 'invoices': [],
|
| 1435 |
+
'referral_code': f'PROMO{new_user_id}', 'referred_by': None, 'referrals': [], 'has_been_welcomed': False
|
| 1436 |
+
}
|
| 1437 |
+
visitor_data_cache[new_user_id] = user_entry
|
| 1438 |
+
user_id_to_save = new_user_id
|
| 1439 |
+
|
| 1440 |
+
save_visitor_data()
|
| 1441 |
+
|
| 1442 |
return jsonify({"status": "ok", "verified": True, "user_id": user_id_to_save})
|
| 1443 |
else:
|
| 1444 |
return jsonify({"status": "error", "verified": True, "message": "User ID not found"}), 400
|
| 1445 |
else:
|
|
|
|
| 1446 |
return jsonify({"status": "error", "verified": False, "message": "Invalid data"}), 403
|
| 1447 |
except Exception as e:
|
| 1448 |
logging.exception("Error in /verify endpoint")
|
|
|
|
| 1454 |
data = request.get_json()
|
| 1455 |
user_id = str(data.get('user_id'))
|
| 1456 |
referral_code = data.get('referral_code')
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1457 |
|
| 1458 |
+
with _data_lock:
|
| 1459 |
+
if not user_id or user_id not in visitor_data_cache:
|
| 1460 |
+
return jsonify({"status": "error", "message": "Пользователь не найден."}), 404
|
| 1461 |
|
| 1462 |
+
user = visitor_data_cache[user_id]
|
| 1463 |
+
if user.get('has_been_welcomed', False):
|
| 1464 |
+
return jsonify({"status": "ok", "message": "Вы уже прошли этот шаг."}), 200
|
| 1465 |
|
| 1466 |
+
user['has_been_welcomed'] = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1467 |
|
| 1468 |
+
if referral_code:
|
| 1469 |
+
referrer = next((u for u_id, u in visitor_data_cache.items() if u_id != "organization_details" and u.get('referral_code') == referral_code), None)
|
| 1470 |
+
|
| 1471 |
+
if not referrer:
|
| 1472 |
+
return jsonify({"status": "error", "message": "Промокод не найден."}), 404
|
| 1473 |
+
if referrer['id'] == user_id:
|
| 1474 |
+
return jsonify({"status": "error", "message": "Нельзя использовать свой промокод."}), 400
|
| 1475 |
+
|
| 1476 |
+
user['referred_by'] = referrer['id']
|
| 1477 |
+
if 'referrals' not in referrer: referrer['referrals'] = []
|
| 1478 |
+
referrer['referrals'].append(user_id)
|
| 1479 |
|
| 1480 |
save_visitor_data()
|
| 1481 |
+
message = "Промокод успешно применен!" if referral_code else "Добро пожаловать!"
|
| 1482 |
+
return jsonify({"status": "ok", "message": message}), 200
|
|
|
|
|
|
|
| 1483 |
|
| 1484 |
except Exception as e:
|
| 1485 |
logging.exception("Error in /submit_referral endpoint")
|
|
|
|
| 1487 |
|
| 1488 |
@app.route('/admin')
|
| 1489 |
def admin_panel():
|
|
|
|
| 1490 |
users_list = []
|
| 1491 |
+
user_name_map = {uid: f"{ud.get('first_name', '')} {ud.get('last_name', '')}".strip() or f"ID: {uid}" for uid, ud in visitor_data_cache.items() if uid != "organization_details"}
|
|
|
|
| 1492 |
|
| 1493 |
+
for user_id, user_data in visitor_data_cache.items():
|
| 1494 |
if user_id == "organization_details": continue
|
| 1495 |
+
user_data_copy = user_data.copy()
|
| 1496 |
+
user_data_copy['id'] = user_id
|
| 1497 |
+
user_data_copy['referrals_count'] = len(user_data_copy.get('referrals', []))
|
| 1498 |
+
referrer_id = user_data_copy.get('referred_by')
|
| 1499 |
+
user_data_copy['referrer_info'] = user_name_map.get(referrer_id, None)
|
| 1500 |
+
users_list.append(user_data_copy)
|
|
|
|
| 1501 |
|
| 1502 |
total_users = len(users_list)
|
| 1503 |
total_bonuses = sum(u.get('bonuses', 0) for u in users_list)
|
|
|
|
| 1516 |
data = request.get_json()
|
| 1517 |
phone_number = data.get('phone_number')
|
| 1518 |
first_name = data.get('first_name')
|
|
|
|
| 1519 |
if not phone_number or not first_name:
|
| 1520 |
return jsonify({"status": "error", "message": "Имя и номер телефона обязательны."}), 400
|
| 1521 |
|
| 1522 |
+
with _data_lock:
|
| 1523 |
+
if any(u.get('phone_number') == phone_number for k, u in visitor_data_cache.items() if k != "organization_details"):
|
| 1524 |
+
return jsonify({"status": "error", "message": "Клиент с таким номером телефона уже существует."}), 409
|
| 1525 |
+
|
| 1526 |
+
now = datetime.now(BISHKEK_TZ)
|
| 1527 |
+
new_id = generate_unique_id(visitor_data_cache)
|
| 1528 |
+
new_client = {
|
| 1529 |
+
'id': new_id, 'telegram_id': None, 'first_name': first_name, 'last_name': None,
|
| 1530 |
+
'username': None, 'photo_url': None, 'is_premium': False, 'phone_number': phone_number,
|
| 1531 |
+
'visited_at': now.timestamp(), 'visited_at_str': now.strftime('%Y-%m-%d %H:%M:%S'),
|
| 1532 |
+
'bonuses': 0, 'history': [], 'debts': 0, 'debt_history': [], 'invoices': [],
|
| 1533 |
+
'referral_code': f'PROMO{new_id}', 'referred_by': None, 'referrals': [], 'has_been_welcomed': True
|
| 1534 |
+
}
|
| 1535 |
+
visitor_data_cache[new_id] = new_client
|
| 1536 |
+
save_visitor_data()
|
| 1537 |
return jsonify({"status": "ok", "message": "Client added successfully"}), 201
|
| 1538 |
except Exception as e:
|
| 1539 |
logging.exception("Error in /admin/add_client endpoint")
|
|
|
|
| 1548 |
deduct_amount = float(data.get('deduct_amount', 0))
|
| 1549 |
add_debt_amount = float(data.get('add_debt_amount', 0))
|
| 1550 |
repay_debt_amount = float(data.get('repay_debt_amount', 0))
|
|
|
|
| 1551 |
if not user_id: return jsonify({"status": "error", "message": "User ID is required"}), 400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1552 |
|
| 1553 |
+
with _data_lock:
|
| 1554 |
+
if user_id not in visitor_data_cache: return jsonify({"status": "error", "message": "User not found"}), 404
|
| 1555 |
+
user = visitor_data_cache[user_id]
|
| 1556 |
+
now = datetime.now(BISHKEK_TZ)
|
| 1557 |
+
now_iso, now_str = now.isoformat(), now.strftime('%Y-%m-%d %H:%M:%S')
|
| 1558 |
+
if deduct_amount > user.get('bonuses', 0): return jsonify({"status": "error", "message": "Недостаточно бонусов для списания"}), 400
|
| 1559 |
+
if repay_debt_amount > user.get('debts', 0): return jsonify({"status": "error", "message": "Сумма погашения превышает текущий долг"}), 400
|
| 1560 |
+
|
| 1561 |
+
accrual_amount = purchase_amount * 0.02
|
| 1562 |
+
user['bonuses'] = round(user.get('bonuses', 0) + accrual_amount - deduct_amount, 2)
|
| 1563 |
+
if 'history' not in user: user['history'] = []
|
| 1564 |
+
if accrual_amount > 0: user['history'].append({"type": "accrual", "amount": round(accrual_amount, 2), "description": f"Начисление с покупки {round(purchase_amount, 2)}", "date": now_iso, "date_str": now_str})
|
| 1565 |
+
if deduct_amount > 0: user['history'].append({"type": "deduction", "amount": round(deduct_amount, 2), "description": "Списание бонусов", "date": now_iso, "date_str": now_str})
|
| 1566 |
+
|
| 1567 |
+
user['debts'] = round(user.get('debts', 0) + add_debt_amount - repay_debt_amount, 2)
|
| 1568 |
+
if 'debt_history' not in user: user['debt_history'] = []
|
| 1569 |
+
if add_debt_amount > 0: user['debt_history'].append({"type": "accrual", "amount": round(add_debt_amount, 2), "description": "Добавление долга", "date": now_iso, "date_str": now_str})
|
| 1570 |
+
if repay_debt_amount > 0: user['debt_history'].append({"type": "payment", "amount": round(repay_debt_amount, 2), "description": "Погашение долга", "date": now_iso, "date_str": now_str})
|
| 1571 |
+
|
| 1572 |
+
save_visitor_data()
|
| 1573 |
+
return jsonify({"status": "ok", "message": "Transaction successful", "new_balance": user['bonuses'], "new_debt": user['debts']}), 200
|
| 1574 |
except Exception as e:
|
| 1575 |
logging.exception("Error in /admin/add_transaction endpoint")
|
| 1576 |
return jsonify({"status": "error", "message": str(e)}), 500
|
|
|
|
| 1582 |
user_id = str(data.get('user_id'))
|
| 1583 |
total_amount = float(data.get('total_amount', 0))
|
| 1584 |
items = data.get('items', [])
|
|
|
|
| 1585 |
if not user_id: return jsonify({"status": "error", "message": "User ID is required"}), 400
|
| 1586 |
if not items: return jsonify({"status": "error", "message": "Необходимо добавить товары в накладную."}), 400
|
| 1587 |
|
| 1588 |
+
with _data_lock:
|
| 1589 |
+
if user_id not in visitor_data_cache: return jsonify({"status": "error", "message": "User not found"}), 404
|
| 1590 |
+
user = visitor_data_cache[user_id]
|
| 1591 |
+
now = datetime.now(BISHKEK_TZ)
|
| 1592 |
+
now_iso, now_str = now.isoformat(), now.strftime('%Y-%m-%d %H:%M:%S')
|
| 1593 |
+
invoice_id = str(uuid.uuid4().hex[:8]).upper()
|
| 1594 |
+
processed_items = [{"product_name": item.get('product_name'), "quantity": float(item.get('quantity', 0)), "unit_price": float(item.get('unit_price', 0)), "item_total": round(float(item.get('quantity', 0)) * float(item.get('unit_price', 0)), 2)} for item in items]
|
| 1595 |
+
new_invoice = {"invoice_id": invoice_id, "date": now_iso, "date_str": now_str, "total_amount": round(total_amount, 2), "items": processed_items}
|
| 1596 |
+
if 'invoices' not in user: user['invoices'] = []
|
| 1597 |
+
user['invoices'].append(new_invoice)
|
| 1598 |
+
save_visitor_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1599 |
return jsonify({"status": "ok", "message": "Invoice added successfully", "invoice_id": invoice_id}), 200
|
| 1600 |
except Exception as e:
|
| 1601 |
logging.exception("Error in /admin/add_invoice endpoint")
|
|
|
|
| 1608 |
user_id, invoice_id = str(data.get('user_id')), data.get('invoice_id')
|
| 1609 |
if not user_id or not invoice_id: return jsonify({"status": "error", "message": "User ID and Invoice ID are required"}), 400
|
| 1610 |
|
| 1611 |
+
with _data_lock:
|
| 1612 |
+
if user_id not in visitor_data_cache: return jsonify({"status": "error", "message": "User not found"}), 404
|
| 1613 |
+
user = visitor_data_cache[user_id]
|
| 1614 |
+
if 'invoices' not in user: return jsonify({"status": "error", "message": "User has no invoices"}), 404
|
| 1615 |
+
original_count = len(user['invoices'])
|
| 1616 |
+
user['invoices'] = [inv for inv in user['invoices'] if inv.get('invoice_id') != invoice_id]
|
| 1617 |
+
if len(user['invoices']) == original_count: return jsonify({"status": "error", "message": "Invoice not found for this user"}), 404
|
| 1618 |
+
save_visitor_data()
|
|
|
|
|
|
|
|
|
|
| 1619 |
return jsonify({"status": "ok", "message": "Invoice deleted successfully"}), 200
|
| 1620 |
except Exception as e:
|
| 1621 |
logging.exception("Error in /admin/delete_invoice endpoint")
|
|
|
|
| 1625 |
def delete_client():
|
| 1626 |
try:
|
| 1627 |
user_id = str(request.get_json().get('user_id'))
|
| 1628 |
+
if not user_id: return jsonify({"status": "error", "message": "User ID is required"}), 400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1629 |
|
| 1630 |
+
with _data_lock:
|
| 1631 |
+
if user_id not in visitor_data_cache: return jsonify({"status": "error", "message": "User not found"}), 404
|
| 1632 |
+
if visitor_data_cache[user_id].get('telegram_id') is not None: return jsonify({"status": "error", "message": "Cannot delete a Telegram-linked user"}), 403
|
| 1633 |
+
|
| 1634 |
+
user_to_delete = visitor_data_cache[user_id]
|
| 1635 |
+
referrer_id = user_to_delete.get('referred_by')
|
| 1636 |
+
if referrer_id and referrer_id in visitor_data_cache:
|
| 1637 |
+
referrer = visitor_data_cache[referrer_id]
|
| 1638 |
+
if 'referrals' in referrer and user_id in referrer['referrals']:
|
| 1639 |
+
referrer['referrals'].remove(user_id)
|
| 1640 |
+
|
| 1641 |
+
del visitor_data_cache[user_id]
|
| 1642 |
+
save_visitor_data()
|
| 1643 |
return jsonify({"status": "ok", "message": "Client deleted successfully"}), 200
|
| 1644 |
except Exception as e:
|
| 1645 |
logging.exception("Error in /admin/delete_client endpoint")
|
|
|
|
| 1648 |
@app.route('/admin/organization_details', methods=['GET'])
|
| 1649 |
def get_organization_details():
|
| 1650 |
try:
|
| 1651 |
+
return jsonify(visitor_data_cache.get('organization_details', {})), 200
|
|
|
|
| 1652 |
except Exception as e:
|
| 1653 |
logging.exception("Error getting organization details")
|
| 1654 |
return jsonify({"status": "error", "message": str(e)}), 500
|
|
|
|
| 1657 |
def save_organization_details():
|
| 1658 |
try:
|
| 1659 |
data = request.get_json()
|
| 1660 |
+
with _data_lock:
|
| 1661 |
+
visitor_data_cache['organization_details'] = {
|
| 1662 |
+
"name": data.get("name", ""), "phone_numbers": data.get("phone_numbers", []),
|
| 1663 |
+
"address": data.get("address", ""), "whatsapp_link": data.get("whatsapp_link", ""),
|
| 1664 |
+
"telegram_link": data.get("telegram_link", "")
|
| 1665 |
+
}
|
| 1666 |
+
save_visitor_data()
|
|
|
|
| 1667 |
return jsonify({"status": "ok", "message": "Organization details saved successfully"}), 200
|
| 1668 |
except Exception as e:
|
| 1669 |
logging.exception("Error saving organization details")
|
|
|
|
| 1673 |
print("--- BONUS SYSTEM SERVER ---")
|
| 1674 |
print(f"Server starting on http://{HOST}:{PORT}")
|
| 1675 |
|
| 1676 |
+
print("Attempting to load local data file...")
|
| 1677 |
load_visitor_data()
|
| 1678 |
|
| 1679 |
+
if not visitor_data_cache or len(visitor_data_cache) <= 1:
|
| 1680 |
+
print("Local data file not found or is empty.")
|
| 1681 |
+
if HF_TOKEN_READ:
|
| 1682 |
+
print("Attempting to restore data from Hugging Face...")
|
| 1683 |
+
if not download_data_from_hf():
|
| 1684 |
+
print("Failed to restore from Hugging Face. Starting fresh.")
|
| 1685 |
+
else:
|
| 1686 |
+
print("HF_TOKEN_READ not set. Cannot restore from backup. Starting fresh.")
|
| 1687 |
else:
|
| 1688 |
+
user_count = len([k for k in visitor_data_cache if k != 'organization_details'])
|
| 1689 |
+
print(f"Successfully loaded data for {user_count} users from local file.")
|
| 1690 |
|
|
|
|
|
|
|
|
|
|
| 1691 |
print("WARNING: The /admin route is NOT protected. Implement proper authentication for production.")
|
| 1692 |
+
|
| 1693 |
if HF_TOKEN_WRITE:
|
| 1694 |
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
| 1695 |
backup_thread.start()
|
| 1696 |
print("Periodic backup thread started (every hour).")
|
| 1697 |
+
else:
|
| 1698 |
+
print("WARNING: HF_TOKEN_WRITE not set. Periodic backups to Hugging Face are disabled.")
|
| 1699 |
|
| 1700 |
print("--- Server Ready ---")
|
| 1701 |
app.run(host=HOST, port=PORT, debug=False)
|