CloudShare/bank.py

3754 lines
148 KiB
Python

from flask import Flask, request, redirect, session, Response, jsonify
import json, os, random, time, uuid
from datetime import datetime
from functools import wraps
app = Flask(__name__)
app.secret_key = "bank_secret_key_2025"
app.config['SESSION_PERMANENT'] = True
app.config['PERMANENT_SESSION_LIFETIME'] = 3600
USERS_FILE = "users.json"
HISTORY_FILE = "history.json"
CARDS_FILE = "cards.json"
SHOP_FILE = "shop.json"
PAYMENTS_FILE = "payments.json" # Новый файл для платежей
# ---------- ИНИЦИАЛИЗАЦИЯ ФАЙЛОВ ----------
def init_files():
"""Инициализация файлов (без удаления существующих)"""
# Создаем файлы если они не существуют
files_to_init = [
(USERS_FILE, {
"admin": {
"password": "admin123",
"balance": 100000,
"deposit": 0,
"credit": 0,
"role": "admin",
"full_name": "Администратор Системы",
"email": "admin@bank.com",
"api_key": None
}
}),
(HISTORY_FILE, []),
(CARDS_FILE, {}),
(SHOP_FILE, {"items": [], "ads": []}),
(PAYMENTS_FILE, []) # Инициализация файла платежей
]
for filename, default_data in files_to_init:
if not os.path.exists(filename):
with open(filename, "w", encoding='utf-8') as f:
json.dump(default_data, f, indent=4, ensure_ascii=False)
# Инициализируем файлы при запуске
init_files()
# ---------- ФУНКЦИИ ДЛЯ РАБОТЫ С ДАННЫМИ ----------
def load_users():
try:
with open(USERS_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
# Убеждаемся, что данные в правильном формате
if not isinstance(data, dict):
print("Warning: users.json is not a dictionary, resetting to default")
data = {
"admin": {
"password": "admin123",
"balance": 100000,
"deposit": 0,
"credit": 0,
"role": "admin",
"full_name": "Администратор Системы",
"email": "admin@bank.com",
"api_key": None
}
}
save_users(data)
return data
# Проверяем, что все значения являются словарями
cleaned_data = {}
for username, user_data in data.items():
if isinstance(user_data, dict):
cleaned_data[username] = user_data
else:
print(f"Warning: User {username} data is not a dict, resetting")
cleaned_data[username] = {
"password": "reset123",
"balance": 1000,
"deposit": 0,
"credit": 0,
"role": "client",
"full_name": "Пользователь",
"email": "user@example.com",
"api_key": None
}
if cleaned_data != data:
save_users(cleaned_data)
return cleaned_data
except Exception as e:
print(f"Error loading users: {e}")
data = {
"admin": {
"password": "admin123",
"balance": 100000,
"deposit": 0,
"credit": 0,
"role": "admin",
"full_name": "Администратор Системы",
"email": "admin@bank.com",
"api_key": None
}
}
save_users(data)
return data
def save_users(data):
with open(USERS_FILE, "w", encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def load_history():
try:
with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return []
def save_history(data):
with open(HISTORY_FILE, "w", encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def load_cards():
try:
with open(CARDS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return {}
def save_cards(data):
with open(CARDS_FILE, "w", encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
# ---------- ФУНКЦИИ ДЛЯ ПЛАТЕЖЕЙ ----------
def load_payments():
"""Загрузка платежей"""
try:
with open(PAYMENTS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return []
def save_payments(data):
"""Сохранение платежей"""
with open(PAYMENTS_FILE, "w", encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def create_payment(payer, receiver, amount, description="", external_id=""):
"""Создание платежа"""
payments = load_payments()
payment_id = str(uuid.uuid4())[:12]
payment_data = {
"id": payment_id,
"payer": payer,
"receiver": receiver,
"amount": int(amount),
"description": description,
"external_id": external_id,
"created": int(time.time()),
"status": "pending", # pending, completed, failed, cancelled
"completed_at": None
}
payments.append(payment_data)
save_payments(payments)
return payment_id, payment_data
def process_payment(payment_id, payer_username=None):
"""Обработка платежа"""
payments = load_payments()
users = load_users()
payment = None
for p in payments:
if p["id"] == payment_id:
payment = p
break
if not payment:
return False, "Платеж не найден"
if payment["status"] != "pending":
return False, f"Платеж уже обработан (статус: {payment['status']})"
# Если передан payer_username, проверяем что это тот же плательщик
if payer_username and payment["payer"] != payer_username:
return False, "Неверный плательщик"
payer = payment["payer"]
receiver = payment["receiver"]
amount = payment["amount"]
# Проверяем существование пользователей
if payer not in users:
return False, "Плательщик не найден"
if receiver not in users:
return False, "Получатель не найден"
# Проверяем баланс плательщика
if users[payer]["balance"] < amount:
return False, "Недостаточно средств"
# Выполняем перевод
users[payer]["balance"] -= amount
users[receiver]["balance"] += amount
save_users(users)
# Обновляем статус платежа
for p in payments:
if p["id"] == payment_id:
p["status"] = "completed"
p["completed_at"] = int(time.time())
break
save_payments(payments)
# Добавляем в историю
add_history(payer, "payment_sent", amount, f"Платеж #{payment_id} для {receiver}")
add_history(receiver, "payment_received", amount, f"Платеж #{payment_id} от {payer}")
return True, "Платеж успешно выполнен"
def cancel_payment(payment_id):
"""Отмена платежа"""
payments = load_payments()
for payment in payments:
if payment["id"] == payment_id and payment["status"] == "pending":
payment["status"] = "cancelled"
save_payments(payments)
return True, "Платеж отменен"
return False, "Платеж не найден или уже обработан"
# ---------- ФУНКЦИИ ДЛЯ МАГАЗИНА ----------
def load_shop():
"""Загрузка данных магазина"""
try:
with open(SHOP_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
# Обеспечиваем наличие всех ключей
if "items" not in data:
data["items"] = []
if "ads" not in data:
data["ads"] = []
return data
except:
return {"items": [], "ads": []}
def save_shop(data):
"""Сохранение данных магазина"""
with open(SHOP_FILE, "w", encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def create_shop_item(seller_username, title, description, price, category="other"):
"""Создание товара в магазине"""
shop_data = load_shop()
item_id = str(uuid.uuid4())[:8]
new_item = {
"id": item_id,
"seller": seller_username,
"title": title,
"description": description,
"price": int(price),
"category": category,
"created": int(time.time()),
"status": "available",
"buyer": None
}
shop_data["items"].append(new_item)
save_shop(shop_data)
# Добавляем в историю
add_history(seller_username, "create_shop_item", 0, f"Товар: {title}")
return item_id
def create_advertisement(author, title, content, contact_info):
"""Создание объявления"""
shop_data = load_shop()
ad_id = str(uuid.uuid4())[:8]
new_ad = {
"id": ad_id,
"author": author,
"title": title,
"content": content,
"contact_info": contact_info,
"created": int(time.time()),
"status": "active"
}
shop_data["ads"].append(new_ad)
save_shop(shop_data)
# Добавляем в историю
add_history(author, "create_advertisement", 0, f"Объявление: {title}")
return ad_id
def buy_item(item_id, buyer_username):
"""Покупка товара"""
shop_data = load_shop()
users = load_users()
# Находим товар
item_to_buy = None
for item in shop_data["items"]:
if item["id"] == item_id and item["status"] == "available":
item_to_buy = item
break
if not item_to_buy:
return False, "Товар не найден или уже продан"
seller = item_to_buy["seller"]
price = item_to_buy["price"]
# Проверяем, есть ли у покупателя достаточно средств
if buyer_username not in users:
return False, "Покупатель не найден"
if users[buyer_username]["balance"] < price:
return False, "Недостаточно средств"
# Проверяем, что покупатель не покупает у себя
if buyer_username == seller:
return False, "Нельзя купить собственный товар"
# Проверяем, существует ли продавец
if seller not in users:
return False, "Продавец не найден"
# Выполняем транзакцию
users[buyer_username]["balance"] -= price
users[seller]["balance"] += price
save_users(users)
# Обновляем статус товара
for item in shop_data["items"]:
if item["id"] == item_id:
item["status"] = "sold"
item["buyer"] = buyer_username
item["sold_date"] = int(time.time())
break
save_shop(shop_data)
# Добавляем в историю
add_history(buyer_username, "buy_item", price, f"Товар: {item_to_buy['title']} от {seller}")
add_history(seller, "sell_item", price, f"Товар: {item_to_buy['title']} покупателю {buyer_username}")
return True, "Покупка успешно завершена"
# ---------- API КЛЮЧИ ----------
def generate_api_key():
"""Генерация API ключа"""
return f"bank_api_{uuid.uuid4().hex[:16]}"
def get_user_by_api_key(api_key):
"""Получение пользователя по API ключу - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
if not api_key:
return None, None
users = load_users()
for username, user_data in users.items():
# Проверяем, что user_data является словарем
if isinstance(user_data, dict) and user_data.get('api_key') == api_key:
return username, user_data
return None, None
# ---------- БАНКОВСКИЕ КАРТЫ ----------
def generate_card_number(user_id, card_type="standard"):
"""Генерация номера банковской карты"""
prefixes = {
"standard": ["5469", "4276", "5375", "4856"],
"gold": ["5469", "5278", "5375"],
"platinum": ["4475", "4876", "4999"]
}
prefix = random.choice(prefixes.get(card_type, prefixes["standard"]))
middle = ''.join([str(random.randint(0, 9)) for _ in range(8)])
last_digits = ''.join([str(random.randint(0, 9)) for _ in range(4)])
card_number = f"{prefix}{middle}{last_digits}"
formatted = ' '.join([card_number[i:i+4] for i in range(0, len(card_number), 4)])
return formatted
def generate_cvv():
"""Генерация CVV кода"""
return str(random.randint(100, 999))
def generate_expiry_date():
"""Генерация срока действия карты"""
from datetime import datetime, timedelta
expiry = datetime.now() + timedelta(days=365*3)
return expiry.strftime("%m/%y")
def get_user_cards(user_id):
"""Получение всех карт пользователя"""
cards = load_cards()
return cards.get(user_id, [])
def create_user_card(user_id, user_data, card_type="standard"):
"""Создание новой карты для пользователя"""
cards = load_cards()
if user_id not in cards:
cards[user_id] = []
# Проверяем, не слишком ли много карт у пользователя
if len(cards[user_id]) >= 5:
return None
card_id = str(uuid.uuid4())[:8]
new_card = {
"id": card_id,
"number": generate_card_number(user_id, card_type),
"holder": user_data.get('full_name', user_id.upper()),
"type": card_type,
"cvv": generate_cvv(),
"expiry": generate_expiry_date(),
"balance": user_data.get('balance', 0),
"currency": "USD",
"created": int(time.time()),
"active": True,
"limit": 10000 if card_type == "standard" else 50000 if card_type == "gold" else 100000,
"color": {
"standard": "#4361ee",
"gold": "#ffd166",
"platinum": "#e5e5e5"
}.get(card_type, "#4361ee")
}
cards[user_id].append(new_card)
save_cards(cards)
# Добавляем в историю
add_history(user_id, f"create_{card_type}_card", 0, f"Card {new_card['number'][-4:]}")
return new_card
# ---------- БАНКОВСКИЕ ОПЕРАЦИИ ----------
def daily_update():
"""Ежедневное обновление депозитов и кредитов"""
users = load_users()
updated = False
for user_id, u in users.items():
if isinstance(u, dict): # Проверяем, что это словарь
if u.get('deposit', 0) > 0:
u['deposit'] = int(u['deposit'] * 1.01)
updated = True
if u.get('credit', 0) > 0:
u['credit'] = int(u['credit'] * 1.05)
updated = True
if updated:
save_users(users)
def add_history(user, action, amount, target=None):
"""Добавление записи в историю"""
hist = load_history()
hist.append({
"time": int(time.time()),
"user": user,
"action": action,
"amount": amount,
"target": target,
"id": str(uuid.uuid4())[:8]
})
save_history(hist)
# ---------- ДЕКОРАТОРЫ ----------
def login_required(f):
"""Декоратор для проверки авторизации"""
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user' not in session:
return redirect('/')
return f(*args, **kwargs)
return decorated_function
def admin_required(f):
"""Декоратор для проверки прав администратора"""
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user' not in session:
return redirect('/')
users = load_users()
user_data = users.get(session['user'])
if not user_data or not isinstance(user_data, dict) or user_data.get('role') != 'admin':
return redirect('/dashboard')
return f(*args, **kwargs)
return decorated_function
# ИСПРАВЛЕННЫЙ ДЕКОРАТОР API
def api_key_required(f):
"""Декоратор для проверки API ключа - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
@wraps(f)
def decorated_function(*args, **kwargs):
api_key = None
# 1. Проверяем заголовок X-API-Key
if 'X-API-Key' in request.headers:
api_key = request.headers.get('X-API-Key')
# 2. Проверяем параметр запроса api_key
if not api_key and request.args.get('api_key'):
api_key = request.args.get('api_key')
# 3. Проверяем JSON тело
if not api_key and request.is_json:
try:
data = request.get_json()
if data and 'api_key' in data:
api_key = data.get('api_key')
except:
pass
# 4. Проверяем форму
if not api_key and request.form.get('api_key'):
api_key = request.form.get('api_key')
if not api_key:
return jsonify({'success': False, 'error': 'API key required'}), 401
# Получаем пользователя по API ключу
username, user_data = get_user_by_api_key(api_key)
if not username:
return jsonify({'success': False, 'error': 'Invalid API key'}), 401
# Сохраняем данные пользователя в объекте request
request.username = username
request.user_data = user_data
return f(*args, **kwargs)
return decorated_function
# ---------- HTML ШАБЛОНЫ ----------
def render_login():
return '''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Банк | Вход в систему</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {
--primary: #4361ee;
--secondary: #3a0ca3;
--gradient: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
}
body {
background: var(--gradient);
min-height: 100vh;
display: flex;
align-items: center;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.login-card {
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.login-header {
background: var(--gradient);
color: white;
padding: 30px;
text-align: center;
}
.login-body {
padding: 40px;
}
.form-control {
border: 2px solid #e9ecef;
border-radius: 10px;
padding: 12px 15px;
transition: all 0.3s;
}
.form-control:focus {
border-color: var(--primary);
box-shadow: 0 0 0 0.25rem rgba(67, 97, 238, 0.25);
}
.btn-primary {
background: var(--gradient);
border: none;
border-radius: 10px;
padding: 12px;
font-weight: 600;
transition: all 0.3s;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(67, 97, 238, 0.3);
}
.alert {
border-radius: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-5">
<div class="login-card">
<div class="login-header">
<i class="fas fa-university fa-3x mb-3"></i>
<h2>Добро пожаловать в Банк</h2>
<p class="mb-0">Вход в систему</p>
</div>
<div class="login-body">
<form method="post">
<div class="mb-3">
<label class="form-label">Логин</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" name="username" class="form-control" placeholder="Введите логин" required>
</div>
</div>
<div class="mb-4">
<label class="form-label">Пароль</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="password" name="password" class="form-control" placeholder="Введите пароль" required>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 mb-3">
<i class="fas fa-sign-in-alt me-2"></i>Войти
</button>
<div class="text-center">
<p class="mb-0">Нет аккаунта? <a href="/register" class="text-decoration-none">Зарегистрироваться</a></p>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
'''
def render_register():
return '''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Банк | Регистрация</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {
--primary: #4361ee;
--secondary: #3a0ca3;
--gradient: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
}
body {
background: var(--gradient);
min-height: 100vh;
display: flex;
align-items: center;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.register-card {
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.register-header {
background: var(--gradient);
color: white;
padding: 30px;
text-align: center;
}
.register-body {
padding: 40px;
}
.form-control {
border: 2px solid #e9ecef;
border-radius: 10px;
padding: 12px 15px;
transition: all 0.3s;
}
.form-control:focus {
border-color: var(--primary);
box-shadow: 0 0 0 0.25rem rgba(67, 97, 238, 0.25);
}
.btn-primary {
background: var(--gradient);
border: none;
border-radius: 10px;
padding: 12px;
font-weight: 600;
transition: all 0.3s;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(67, 97, 238, 0.3);
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="register-card">
<div class="register-header">
<i class="fas fa-user-plus fa-3x mb-3"></i>
<h2>Регистрация аккаунта</h2>
<p class="mb-0">Создайте свой банковский аккаунт</p>
</div>
<div class="register-body">
<form method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Логин</label>
<input type="text" name="username" class="form-control" placeholder="Придумайте логин" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Пароль</label>
<input type="password" name="password" class="form-control" placeholder="Придумайте пароль" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Имя</label>
<input type="text" name="first_name" class="form-control" placeholder="Ваше имя" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Фамилия</label>
<input type="text" name="last_name" class="form-control" placeholder="Ваша фамилия" required>
</div>
</div>
<div class="mb-4">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control" placeholder="email@example.com" required>
</div>
<button type="submit" class="btn btn-primary w-100 mb-3">
<i class="fas fa-user-plus me-2"></i>Создать аккаунт
</button>
<div class="text-center">
<p class="mb-0">Уже есть аккаунт? <a href="/" class="text-decoration-none">Войти</a></p>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
'''
def render_card_html(card, width="400px"):
"""Генерация HTML для банковской карты"""
colors = {
"standard": "#4361ee",
"gold": "#ffd166",
"platinum": "#e5e5e5"
}
bg_color = card.get('color', colors.get(card['type'], "#4361ee"))
text_color = "#ffffff" if card['type'] == 'standard' else "#000000"
return f'''
<div class="bank-card" style="width: {width}; background: linear-gradient(135deg, {bg_color} 0%, {bg_color}dd 100%); color: {text_color};">
<div class="bank-logo">
<span>BANK CARD</span>
<div class="chip"></div>
</div>
<div class="card-number">
{card['number']}
</div>
<div class="card-holder">
{card['holder']}
</div>
</div>
'''
def render_dashboard(user_data, history_data, user_cards, shop_data=None):
"""Генерация HTML личного кабинета"""
# Форматирование времени истории
for h in history_data:
h['formatted_time'] = datetime.fromtimestamp(h['time']).strftime('%d.%m.%Y %H:%M')
# HTML для истории
history_html = ""
for h in history_data[-10:][::-1]:
icon = "fa-history"
color = "text-primary"
if "deposit" in h['action']:
icon = "fa-piggy-bank"
color = "text-info"
elif "credit" in h['action']:
icon = "fa-credit-card"
color = "text-warning"
elif "transfer" in h['action']:
icon = "fa-exchange-alt"
color = "text-primary"
elif "card" in h['action']:
icon = "fa-credit-card"
color = "text-success"
elif "api" in h['action']:
icon = "fa-key"
color = "text-secondary"
elif "shop" in h['action'] or "item" in h['action'] or "advertisement" in h['action']:
icon = "fa-shopping-cart"
color = "text-success"
elif "payment" in h['action']:
icon = "fa-credit-card"
color = "text-success"
history_html += f'''
<tr>
<td><i class="fas {icon} {color} me-2"></i>{h['formatted_time']}</td>
<td><span class="badge bg-light text-dark">{h['user']}</span></td>
<td>{h['action']}</td>
<td class="fw-bold">{h['amount']:,} $</td>
<td>{h.get('target', '-')}</td>
</tr>
'''
# HTML для банковских карт
cards_html = ""
if user_cards:
for card in user_cards:
width = "350px" if len(card['number']) < 24 else "400px" if len(card['number']) < 28 else "450px"
cards_html += f'''
<div class="col-md-6 col-lg-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-credit-card me-2"></i>
Карта {card['type'].upper()}
</h5>
{render_card_html(card, width)}
<div class="mt-3">
<p class="mb-1"><small>CVV: <strong>{card['cvv']}</strong></small></p>
<p class="mb-1"><small>Срок: <strong>{card['expiry']}</strong></small></p>
<p class="mb-0"><small>Баланс: <strong>${card['balance']:,}</strong></small></p>
<p class="mb-0"><small>Лимит: <strong>${card['limit']:,}</strong></small></p>
<form action="/delete_card/{card['id']}" method="post" class="mt-2">
<button type="submit" class="btn btn-danger btn-sm w-100">
<i class="fas fa-trash me-1"></i> Удалить
</button>
</form>
</div>
</div>
</div>
</div>
'''
else:
cards_html = '''
<div class="col-12">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
У вас еще нет банковских карт. Создайте свою первую карту!
</div>
</div>
'''
# HTML для API ключа
api_key_html = ""
if user_data.get('api_key'):
api_key_html = f'''
<div class="alert alert-success">
<h6><i class="fas fa-key me-2"></i>Ваш API ключ</h6>
<div class="input-group">
<input type="text" id="apiKey" class="form-control" value="{user_data['api_key']}" readonly>
<button class="btn btn-outline-secondary" type="button" onclick="copyApiKey()">
<i class="fas fa-copy"></i>
</button>
</div>
<small class="text-muted">Используйте этот ключ для доступа к API</small>
</div>
'''
else:
api_key_html = '''
<div class="alert alert-warning">
<h6><i class="fas fa-key me-2"></i>API ключ не создан</h6>
<form action="/generate_api_key" method="post">
<button type="submit" class="btn btn-primary btn-sm">
<i class="fas fa-plus me-1"></i>Создать API ключ
</button>
</form>
</div>
'''
# HTML для магазина
shop_tab_content = ""
if shop_data:
# Товары
shop_items_html = ""
available_items = [item for item in shop_data["items"] if item["status"] == "available"]
if available_items:
for item in available_items[-10:][::-1]: # Последние 10 товаров
created_date = datetime.fromtimestamp(item["created"]).strftime('%d.%m.%Y')
shop_items_html += f'''
<div class="col-md-6 col-lg-4">
<div class="card h-100 shop-item-card">
<div class="card-body">
<h5 class="card-title">{item["title"]}</h5>
<p class="card-text">{item["description"]}</p>
<p class="card-text">
<small class="text-muted">Продавец: {item["seller"]}</small><br>
<small class="text-muted">Категория: {item["category"]}</small><br>
<small class="text-muted">Дата: {created_date}</small>
</p>
<div class="d-flex justify-content-between align-items-center">
<span class="h4 text-success">${item["price"]:,}</span>
<form action="/buy_item/{item['id']}" method="post">
<button type="submit" class="btn btn-primary">
<i class="fas fa-shopping-cart me-1"></i> Купить
</button>
</form>
</div>
</div>
</div>
</div>
'''
else:
shop_items_html = '''
<div class="col-12">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
В магазине пока нет товаров. Будьте первым, кто добавит товар!
</div>
</div>
'''
# Объявления
ads_html = ""
active_ads = [ad for ad in shop_data["ads"] if ad["status"] == "active"]
if active_ads:
for ad in active_ads[-10:][::-1]: # Последние 10 объявлений
created_date = datetime.fromtimestamp(ad["created"]).strftime('%d.%m.%Y')
ads_html += f'''
<div class="col-md-6">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">{ad["title"]}</h5>
<p class="card-text">{ad["content"]}</p>
<p class="card-text">
<small class="text-muted">Автор: {ad["author"]}</small><br>
<small class="text-muted">Контакты: {ad["contact_info"]}</small><br>
<small class="text-muted">Дата: {created_date}</small>
</p>
</div>
</div>
</div>
'''
shop_tab_content = f'''
<div class="tab-pane fade" id="shop">
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between mb-3">
<h4><i class="fas fa-store me-2"></i>Магазин</h4>
<div>
<button class="btn btn-success me-2" data-bs-toggle="modal" data-bs-target="#addItemModal">
<i class="fas fa-plus me-1"></i>Добавить товар
</button>
<button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#addAdModal">
<i class="fas fa-bullhorn me-1"></i>Добавить объявление
</button>
</div>
</div>
</div>
</div>
<h5><i class="fas fa-shopping-bag me-2"></i>Товары в магазине</h5>
<div class="row g-4 mb-4">
{shop_items_html}
</div>
<h5><i class="fas fa-bullhorn me-2"></i>Объявления</h5>
<div class="row g-4">
{ads_html}
</div>
</div>
'''
# HTML для админ-панели
admin_tab = ""
if user_data.get('role') == 'admin':
admin_tab = '''
<li class="nav-item">
<a class="nav-link" href="/admin">
<i class="fas fa-cogs me-2"></i>Админ-панель
</a>
</li>
'''
return f'''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Банк | Личный кабинет</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {{
--primary: #4361ee;
--secondary: #3a0ca3;
--gradient: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
}}
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f5f7fb;
}}
.navbar-custom {{
background: var(--gradient) !important;
box-shadow: 0 4px 20px rgba(67, 97, 238, 0.3);
}}
.sidebar {{
background: white;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
padding: 20px;
height: fit-content;
}}
.main-content {{
background: white;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
padding: 25px;
}}
.balance-card {{
background: var(--gradient);
color: white;
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
}}
.stat-card {{
background: white;
border-radius: 15px;
padding: 15px;
box-shadow: 0 3px 10px rgba(0,0,0,0.05);
border-left: 4px solid var(--primary);
margin-bottom: 15px;
}}
.bank-card {{
border-radius: 12px;
padding: 20px;
color: white;
display: flex;
flex-direction: column;
justify-content: space-between;
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
position: relative;
overflow: hidden;
min-height: 180px;
margin: 10px 0;
}}
.bank-card::before {{
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at top right, rgba(255,255,255,0.1) 0%, transparent 50%);
}}
.bank-logo {{
font-size: 18px;
font-weight: bold;
letter-spacing: 1px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}}
.chip {{
width: 35px;
height: 25px;
background: linear-gradient(135deg, #ffd166 0%, #ff9e00 100%);
border-radius: 5px;
position: relative;
}}
.chip::before {{
content: "";
position: absolute;
top: 3px;
left: 3px;
right: 3px;
bottom: 3px;
border: 2px solid rgba(0,0,0,0.2);
border-radius: 3px;
}}
.card-number {{
font-size: 18px;
letter-spacing: 2px;
font-weight: bold;
text-align: center;
margin: 15px 0;
word-break: break-all;
}}
.card-holder {{
font-size: 14px;
letter-spacing: 1px;
text-transform: uppercase;
}}
.nav-tabs .nav-link {{
border: none;
border-radius: 10px;
padding: 10px 15px;
margin: 0 5px;
color: #666;
font-weight: 500;
}}
.nav-tabs .nav-link.active {{
background: var(--gradient);
color: white;
}}
.table-custom {{
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}}
.table-custom thead {{
background: var(--gradient);
color: white;
}}
.btn-primary {{
background: var(--gradient);
border: none;
border-radius: 10px;
padding: 8px 15px;
font-weight: 600;
}}
.btn-primary:hover {{
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(67, 97, 238, 0.3);
}}
.modal-header {{
background: var(--gradient);
color: white;
}}
.create-card-btn {{
background: linear-gradient(135deg, #ffd166 0%, #ff9e00 100%);
border: none;
color: #000;
font-weight: 600;
}}
.create-card-btn:hover {{
background: linear-gradient(135deg, #ffc145 0%, #e68900 100%);
color: #000;
}}
.services-grid {{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}}
.service-card {{
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 3px 10px rgba(0,0,0,0.05);
border-left: 4px solid var(--primary);
transition: all 0.3s;
}}
.service-card:hover {{
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}}
.api-docs {{
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
margin-top: 20px;
}}
.code-block {{
background: #1e1e1e;
color: #d4d4d4;
padding: 15px;
border-radius: 5px;
font-family: "Courier New", monospace;
overflow-x: auto;
margin: 10px 0;
}}
.shop-item-card {{
transition: all 0.3s;
}}
.shop-item-card:hover {{
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-custom navbar-dark">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/dashboard">
<i class="fas fa-university fa-2x me-2"></i>
<div>
<h4 class="mb-0">Банк</h4>
<small class="opacity-75">Финансовая система</small>
</div>
</a>
<div class="d-flex align-items-center">
<span class="badge bg-light text-dark me-3">
<i class="fas fa-user-circle me-1"></i>
{session['user']} ({user_data.get('role', 'client')})
</span>
<a href="/logout" class="btn btn-outline-light btn-sm">
<i class="fas fa-sign-out-alt me-1"></i>Выход
</a>
</div>
</div>
</nav>
<div class="container mt-4 mb-5">
<div class="row g-4">
<div class="col-lg-3">
<div class="sidebar">
<h5 class="mb-3"><i class="fas fa-chart-line me-2"></i>Финансы</h5>
<div class="balance-card">
<h6 class="opacity-75">Текущий баланс</h6>
<h2 class="{'text-success' if user_data['balance'] >= 0 else 'text-danger'}">
${user_data['balance']:,}
</h2>
<small>Доступные средства</small>
</div>
<div class="stat-card">
<div class="d-flex justify-content-between">
<div>
<h6>Вклад</h6>
<h5 class="text-success">${user_data['deposit']:,}</h5>
</div>
<i class="fas fa-piggy-bank fa-2x text-success"></i>
</div>
<small class="text-muted">+1% ежедневно</small>
</div>
<div class="stat-card">
<div class="d-flex justify-content-between">
<div>
<h6>Кредит</h6>
<h5 class="text-danger">${user_data['credit']:,}</h5>
</div>
<i class="fas fa-credit-card fa-2x text-danger"></i>
</div>
<small class="text-muted">+5% ежедневно</small>
</div>
<div class="mt-3">
<h6><i class="fas fa-wallet me-2"></i>Операции</h6>
<div class="d-grid gap-2 mt-2">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#depositModal">
<i class="fas fa-plus-circle me-1"></i>Пополнить вклад
</button>
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#creditModal">
<i class="fas fa-hand-holding-usd me-1"></i>Взять кредит
</button>
<button class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#transferModal">
<i class="fas fa-exchange-alt me-1"></i>Перевод
</button>
<button class="btn create-card-btn" data-bs-toggle="modal" data-bs-target="#cardModal">
<i class="fas fa-credit-card me-1"></i>Создать карту
</button>
<a href="/payment_page" class="btn btn-warning">
<i class="fas fa-credit-card me-1"></i>Оплата
</a>
</div>
</div>
<div class="mt-4">
{api_key_html}
</div>
</div>
</div>
<div class="col-lg-9">
<div class="main-content">
<ul class="nav nav-tabs mb-4">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#services">
<i class="fas fa-concierge-bell me-1"></i>Услуги
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#cards">
<i class="fas fa-credit-card me-1"></i>Мои карты
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#shop">
<i class="fas fa-store me-1"></i>Магазин
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#history">
<i class="fas fa-history me-1"></i>История
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#api">
<i class="fas fa-code me-1"></i>API
</a>
</li>
{admin_tab}
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="services">
<div class="services-grid">
<div class="service-card">
<h5><i class="fas fa-piggy-bank text-primary me-2"></i>Вклады</h5>
<p class="text-muted">Откройте вклад под 1% ежедневно и получайте пассивный доход.</p>
</div>
<div class="service-card">
<h5><i class="fas fa-credit-card text-success me-2"></i>Кредиты</h5>
<p class="text-muted">Получите кредит на любые цели. Ставка 5% ежедневно.</p>
</div>
<div class="service-card">
<h5><i class="fas fa-exchange-alt text-info me-2"></i>Переводы</h5>
<p class="text-muted">Быстрые переводы между клиентами банка без комиссий.</p>
</div>
<div class="service-card">
<h5><i class="fas fa-credit-card text-warning me-2"></i>Банковские карты</h5>
<p class="text-muted">Выпустите банковскую карту для удобных платежей.</p>
</div>
<div class="service-card">
<h5><i class="fas fa-store text-danger me-2"></i>Магазин</h5>
<p class="text-muted">Покупайте и продавайте товары, размещайте объявления.</p>
</div>
<div class="service-card">
<h5><i class="fas fa-credit-card text-secondary me-2"></i>Платежи</h5>
<p class="text-muted">Принимайте платежи от клиентов через API или ссылки.</p>
</div>
<div class="service-card">
<h5><i class="fas fa-code text-dark me-2"></i>API Доступ</h5>
<p class="text-muted">Интегрируйте банковские услуги в ваши приложения через API.</p>
</div>
</div>
</div>
<div class="tab-pane fade" id="cards">
<div class="row g-4">
{cards_html}
</div>
</div>
{shop_tab_content}
<div class="tab-pane fade" id="history">
<div class="table-responsive">
<table class="table table-custom table-hover">
<thead>
<tr>
<th><i class="fas fa-clock"></i> Время</th>
<th><i class="fas fa-user"></i> Пользователь</th>
<th><i class="fas fa-tasks"></i> Операция</th>
<th><i class="fas fa-dollar-sign"></i> Сумма</th>
<th><i class="fas fa-user-friends"></i>Получатель</th>
</tr>
</thead>
<tbody>
{history_html}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade" id="api">
<div class="api-docs">
<h4><i class="fas fa-code me-2"></i>Документация API</h4>
<p>Используйте ваш API ключ для доступа к банковским операциям через REST API.</p>
<h5 class="mt-4">Базовый URL</h5>
<div class="code-block">
http://ваш-домен/api/v1/
</div>
<h5 class="mt-4">Аутентификация</h5>
<p>Добавьте заголовок с вашим API ключом:</p>
<div class="code-block">
X-API-Key: ваш_api_ключ
</div>
<p>Или передайте как параметр:</p>
<div class="code-block">
?api_key=ваш_api_ключ
</div>
<h5 class="mt-4">Доступные методы</h5>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Метод</th>
<th>Эндпоинт</th>
<th>Описание</th>
<th>Параметры</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="badge bg-info">GET</span></td>
<td>/api/v1/balance</td>
<td>Получить информацию о балансе</td>
<td>-</td>
</tr>
<tr>
<td><span class="badge bg-info">GET</span></td>
<td>/api/v1/history</td>
<td>Получить историю операций</td>
<td>limit (опционально)</td>
</tr>
<tr>
<td><span class="badge bg-info">GET</span></td>
<td>/api/v1/cards</td>
<td>Получить список карт</td>
<td>-</td>
</tr>
<tr>
<td><span class="badge bg-success">POST</span></td>
<td>/api/v1/deposit</td>
<td>Открыть вклад</td>
<td>amount (int)</td>
</tr>
<tr>
<td><span class="badge bg-success">POST</span></td>
<td>/api/v1/credit</td>
<td>Взять кредит</td>
<td>amount (int)</td>
</tr>
<tr>
<td><span class="badge bg-success">POST</span></td>
<td>/api/v1/transfer</td>
<td>Сделать перевод</td>
<td>target (str), amount (int)</td>
</tr>
<tr>
<td><span class="badge bg-success">POST</span></td>
<td>/api/v1/cards/create</td>
<td>Создать карту</td>
<td>card_type (standard|gold|platinum)</td>
</tr>
<tr>
<td><span class="badge bg-danger">POST</span></td>
<td>/api/v1/cards/delete</td>
<td>Удалить карту</td>
<td>card_id (str)</td>
</tr>
<tr>
<td><span class="badge bg-info">GET</span></td>
<td>/api/v1/shop/items</td>
<td>Получить список товаров</td>
<td>-</td>
</tr>
<tr>
<td><span class="badge bg-info">GET</span></td>
<td>/api/v1/shop/ads</td>
<td>Получить список объявлений</td>
<td>-</td>
</tr>
<tr>
<td><span class="badge bg-success">POST</span></td>
<td>/api/v1/payment/create</td>
<td>Создать платеж</td>
<td>receiver, amount, description</td>
</tr>
<tr>
<td><span class="badge bg-success">POST</span></td>
<td>/api/v1/payment/process</td>
<td>Выполнить платеж</td>
<td>payment_id</td>
</tr>
</tbody>
</table>
</div>
<h5 class="mt-4">Примеры использования</h5>
<p>Python с requests:</p>
<div class="code-block">
import requests<br>
<br>
api_key = "ваш_api_ключ"<br>
base_url = "http://ваш-домен/api/v1"<br>
<br>
# Получить баланс<br>
headers = {{"X-API-Key": api_key}}<br>
response = requests.get(f"{{base_url}}/balance", headers=headers)<br>
print(response.json())<br>
<br>
# Сделать перевод<br>
data = {{"target": "получатель", "amount": 100}}<br>
response = requests.post(f"{{base_url}}/transfer", headers=headers, json=data)<br>
print(response.json())
</div>
<p>cURL:</p>
<div class="code-block">
# Получить баланс<br>
curl -H "X-API-Key: ваш_api_ключ" http://ваш-домен/api/v1/balance<br>
<br>
# Сделать перевод<br>
curl -X POST -H "X-API-Key: ваш_api_ключ" \\<br>
-H "Content-Type: application/json" \\<br>
-d '{{"target": "получатель", "amount": 100}}' \\<br>
http://ваш-домен/api/v1/transfer
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Модальные окна -->
<div class="modal fade" id="depositModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-piggy-bank me-2"></i>Вклад</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="/deposit" method="post">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Сумма вклада ($)</label>
<input type="number" name="amount" class="form-control" min="1" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Подтвердить</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="creditModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-credit-card me-2"></i>Кредит</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="/credit" method="post">
<div class="modal-body">
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
Кредит выдается под 5% ежедневно
</div>
<div class="mb-3">
<label class="form-label">Сумма кредита ($)</label>
<input type="number" name="amount" class="form-control" min="1" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Взять кредит</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="transferModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-exchange-alt me-2"></i>Перевод</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="/transfer" method="post">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Получатель</label>
<input type="text" name="target" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Сумма ($)</label>
<input type="number" name="amount" class="form-control" min="1" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Отправить</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="cardModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-credit-card me-2"></i>Создание карты</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="/create_card" method="post">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Тип карты</label>
<select name="card_type" class="form-select">
<option value="standard">Стандартная (бесплатно)</option>
<option value="gold">Золотая ($50)</option>
<option value="platinum">Платиновая ($100)</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Создать карту</button>
</div>
</form>
</div>
</div>
</div>
<!-- Модальные окна для магазина -->
<div class="modal fade" id="addItemModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-plus me-2"></i>Добавить товар</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="/create_shop_item" method="post">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Название товара</label>
<input type="text" name="title" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Описание</label>
<textarea name="description" class="form-control" rows="3" required></textarea>
</div>
<div class="mb-3">
<label class="form-label">Цена ($)</label>
<input type="number" name="price" class="form-control" min="1" required>
</div>
<div class="mb-3">
<label class="form-label">Категория</label>
<select name="category" class="form-select">
<option value="electronics">Электроника</option>
<option value="clothing">Одежда</option>
<option value="books">Книги</option>
<option value="other">Другое</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Добавить товар</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="addAdModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-bullhorn me-2"></i>Добавить объявление</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="/create_advertisement" method="post">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Заголовок</label>
<input type="text" name="title" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Содержание</label>
<textarea name="content" class="form-control" rows="3" required></textarea>
</div>
<div class="mb-3">
<label class="form-label">Контактная информация</label>
<input type="text" name="contact_info" class="form-control" placeholder="Email или телефон" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Добавить объявление</button>
</div>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Автообновление каждые 60 секунд
setInterval(() => {{
window.location.reload();
}}, 60000);
// Валидация форм
document.querySelectorAll("form").forEach(form => {{
form.addEventListener("submit", function(e) {{
const inputs = this.querySelectorAll("input[type=\"number\"]");
inputs.forEach(input => {{
if (input.value && parseFloat(input.value) <= 0) {{
e.preventDefault();
alert("Пожалуйста, введите корректное значение");
}}
}});
}});
}});
// Анимация карт
document.querySelectorAll(".bank-card").forEach(card => {{
card.addEventListener("mouseenter", function() {{
this.style.transform = "translateY(-5px)";
this.style.boxShadow = "0 15px 35px rgba(0,0,0,0.3)";
}});
card.addEventListener("mouseleave", function() {{
this.style.transform = "translateY(0)";
this.style.boxShadow = "0 10px 25px rgba(0,0,0,0.2)";
}});
}});
// Копирование API ключа
function copyApiKey() {{
const apiKeyInput = document.getElementById("apiKey");
apiKeyInput.select();
apiKeyInput.setSelectionRange(0, 99999);
document.execCommand("copy");
alert("API ключ скопирован в буфер обмена!");
}}
</script>
</body>
</html>
'''
def render_admin_panel(users):
"""Генерация HTML админ-панели - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
users_html = ""
current_user = session.get('user')
valid_users_count = 0
total_balance = 0
total_deposit = 0
total_credit = 0
for username, user in users.items():
# Пропускаем текущего пользователя
if username == current_user:
continue
# Проверяем, что пользователь - словарь (корректные данные)
if not isinstance(user, dict):
continue
# Безопасно получаем данные пользователя
role = user.get('role', 'client')
balance = user.get('balance', 0)
deposit = user.get('deposit', 0)
credit = user.get('credit', 0)
email = user.get('email', 'нет')
# Обновляем статистику
valid_users_count += 1
total_balance += balance
total_deposit += deposit
total_credit += credit
# Определяем цвет бейджа роли
badge_color = "success" if role == 'admin' else "primary" if role == 'manager' else "secondary"
users_html += f'''
<div class="col-md-6 col-lg-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">{username}</h5>
<p class="card-text">
<small>Роль: <span class="badge bg-{badge_color}">{role}</span></small><br>
<small>Баланс: <strong>${balance:,}</strong></small><br>
<small>Вклад: <strong>${deposit:,}</strong></small><br>
<small>Кредит: <strong>${credit:,}</strong></small><br>
<small>Email: {email}</small>
</p>
<form action="/admin/change_role" method="post" class="mb-2">
<input type="hidden" name="username" value="{username}">
<select name="role" class="form-select form-select-sm mb-2">
<option value="client" {'selected' if role == 'client' else ''}>Клиент</option>
<option value="admin" {'selected' if role == 'admin' else ''}>Админ</option>
<option value="manager" {'selected' if role == 'manager' else ''}>Менеджер</option>
</select>
<button type="submit" class="btn btn-warning btn-sm w-100">Изменить роль</button>
</form>
<form action="/admin/add_money" method="post">
<input type="hidden" name="username" value="{username}">
<div class="input-group input-group-sm mb-2">
<input type="number" name="amount" class="form-control" placeholder="Сумма" min="1" required>
<button class="btn btn-success" type="submit">+</button>
</div>
</form>
</div>
</div>
</div>
'''
return f'''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Админ-панель</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {{
--primary: #4361ee;
--gradient: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
}}
body {{
background: #f5f7fb;
padding: 20px;
}}
.admin-header {{
background: var(--gradient);
color: white;
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
}}
.card {{
border-radius: 12px;
box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}}
.stat-card {{
background: white;
border-radius: 12px;
padding: 15px;
box-shadow: 0 3px 10px rgba(0,0,0,0.05);
border-left: 4px solid var(--primary);
margin-bottom: 15px;
}}
</style>
</head>
<body>
<div class="container">
<div class="admin-header">
<div class="d-flex justify-content-between align-items-center">
<div>
<h2><i class="fas fa-cogs me-2"></i>Админ-панель</h2>
<p class="mb-0">Управление пользователями</p>
</div>
<a href="/dashboard" class="btn btn-light">
<i class="fas fa-arrow-left me-1"></i>Назад
</a>
</div>
</div>
<div class="row g-4">
{users_html if users_html else '''
<div class="col-12">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
Нет других пользователей для управления.
</div>
</div>
'''}
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title"><i class="fas fa-chart-bar me-2"></i>Статистика системы</h5>
<div class="row">
<div class="col-md-3">
<div class="stat-card">
<h6>Всего пользователей</h6>
<h4>{valid_users_count + 1}</h4>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<h6>Общий баланс</h6>
<h4 class="text-success">${total_balance:,}</h4>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<h6>Общие вклады</h6>
<h4 class="text-info">${total_deposit:,}</h4>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<h6>Общие кредиты</h6>
<h4 class="text-danger">${total_credit:,}</h4>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Валидация форм админ-панели
document.querySelectorAll("form").forEach(form => {{
form.addEventListener("submit", function(e) {{
const amountInput = this.querySelector('input[type="number"]');
if (amountInput && amountInput.value) {{
if (parseInt(amountInput.value) <= 0) {{
e.preventDefault();
alert("Сумма должна быть положительной!");
}}
}}
}});
}});
</script>
</body>
</html>
'''
# ---------- СТРАНИЦА ОПЛАТЫ ----------
def render_payment_page():
"""Страница оплаты"""
return '''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Оплата | Банковская система</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {
--primary: #4361ee;
--secondary: #3a0ca3;
--gradient: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
}
body {
background: #f5f7fb;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.payment-card {
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
}
.payment-header {
background: var(--gradient);
color: white;
padding: 25px;
text-align: center;
}
.payment-body {
padding: 30px;
}
.form-control {
border: 2px solid #e9ecef;
border-radius: 10px;
padding: 12px 15px;
transition: all 0.3s;
}
.form-control:focus {
border-color: var(--primary);
box-shadow: 0 0 0 0.25rem rgba(67, 97, 238, 0.25);
}
.btn-primary {
background: var(--gradient);
border: none;
border-radius: 10px;
padding: 12px;
font-weight: 600;
transition: all 0.3s;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(67, 97, 238, 0.3);
}
.api-example {
background: #f8f9fa;
border-radius: 10px;
padding: 15px;
font-family: 'Courier New', monospace;
font-size: 14px;
margin: 10px 0;
}
.qr-code {
width: 200px;
height: 200px;
background: #f8f9fa;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin: 20px auto;
border: 1px solid #dee2e6;
}
.payment-link {
background: #e9ecef;
border-radius: 8px;
padding: 10px;
word-break: break-all;
font-family: 'Courier New', monospace;
margin: 10px 0;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark" style="background: var(--gradient);">
<div class="container">
<a class="navbar-brand" href="/dashboard">
<i class="fas fa-university me-2"></i>
Банк | Оплата
</a>
<div>
<a href="/dashboard" class="btn btn-outline-light btn-sm">
<i class="fas fa-arrow-left me-1"></i> Назад
</a>
</div>
</div>
</nav>
<div class="container mt-4 mb-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="payment-card">
<div class="payment-header">
<h3><i class="fas fa-credit-card me-2"></i>Платежная система</h3>
<p class="mb-0">Принимайте платежи от клиентов через ссылки или API</p>
</div>
<div class="payment-body">
<div class="row">
<div class="col-md-6">
<h4><i class="fas fa-link me-2"></i>Создать ссылку для оплаты</h4>
<form action="/create_payment_link" method="post" id="paymentForm">
<div class="mb-3">
<label class="form-label">Получатель (логин)</label>
<input type="text" name="receiver" class="form-control" required
placeholder="Введите логин получателя">
</div>
<div class="mb-3">
<label class="form-label">Сумма ($)</label>
<input type="number" name="amount" class="form-control" min="1" required
placeholder="Введите сумму">
</div>
<div class="mb-3">
<label class="form-label">Описание платежа</label>
<input type="text" name="description" class="form-control"
placeholder="Оплата за товар/услугу">
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-link me-2"></i>Создать ссылку
</button>
</form>
<div class="mt-4">
<h5><i class="fas fa-qrcode me-2"></i>QR-код для оплаты</h5>
<div class="qr-code" id="qrCode">
<span class="text-muted">QR-код появится после создания ссылки</span>
</div>
</div>
</div>
<div class="col-md-6">
<h4><i class="fas fa-code me-2"></i>API для оплаты</h4>
<p>Используйте API для интеграции платежей в ваши приложения:</p>
<h6>1. Создать платеж:</h6>
<div class="api-example">
POST /api/v1/payment/create<br>
Headers: X-API-Key: ваш_ключ<br>
Body: {<br>
"receiver": "username",<br>
"amount": 100,<br>
"description": "Оплата за услугу"<br>
}
</div>
<h6>2. Получить статус платежа:</h6>
<div class="api-example">
GET /api/v1/payment/status/{payment_id}<br>
Headers: X-API-Key: ваш_ключ
</div>
<h6>3. Пример ссылки для оплаты:</h6>
<div class="payment-link" id="paymentLinkExample">
http://ваш-домен/pay/{payment_id}
</div>
<div class="alert alert-info mt-3">
<h6><i class="fas fa-lightbulb me-2"></i>Как это работает:</h6>
<ol class="mb-0">
<li>Создаете платеж через форму или API</li>
<li>Получаете уникальную ссылку или payment_id</li>
<li>Отправляете ссылку клиенту</li>
<li>Клиент переходит по ссылке и оплачивает</li>
<li>Вы получаете деньги на счет</li>
</ol>
</div>
</div>
</div>
<div class="mt-5">
<h4><i class="fas fa-history me-2"></i>Последние платежи</h4>
<div class="table-responsive">
<table class="table table-hover" id="paymentsTable">
<thead>
<tr>
<th>ID</th>
<th>Сумма</th>
<th>Получатель</th>
<th>Статус</th>
<th>Дата</th>
</tr>
</thead>
<tbody>
<!-- Данные будут загружены через JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Обработка формы создания платежа
document.getElementById('paymentForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
try {
const response = await fetch('/api/v1/payment/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
receiver: formData.get('receiver'),
amount: parseInt(formData.get('amount')),
description: formData.get('description')
})
});
const result = await response.json();
if (result.success) {
// Обновляем пример ссылки
const paymentLink = window.location.origin + '/pay/' + result.payment_id;
document.getElementById('paymentLinkExample').textContent = paymentLink;
// Показываем QR-код (простая визуализация)
const qrCodeDiv = document.getElementById('qrCode');
qrCodeDiv.innerHTML = `
<div class="text-center">
<div style="font-size: 80px;">💰</div>
<small class="text-muted">Ссылка: ${result.payment_id}</small>
</div>
`;
alert('Платеж создан! ID: ' + result.payment_id);
// Обновляем список платежей
loadPayments();
} else {
alert('Ошибка: ' + result.error);
}
} catch (error) {
alert('Ошибка соединения: ' + error.message);
}
});
// Загрузка списка платежей
async function loadPayments() {
try {
const response = await fetch('/api/v1/payment/list');
const result = await response.json();
if (result.success) {
const tbody = document.querySelector('#paymentsTable tbody');
tbody.innerHTML = '';
result.payments.forEach(payment => {
const row = document.createElement('tr');
// Определяем цвет статуса
let statusClass = 'warning';
if (payment.status === 'completed') statusClass = 'success';
if (payment.status === 'failed') statusClass = 'danger';
if (payment.status === 'cancelled') statusClass = 'secondary';
const date = new Date(payment.created * 1000).toLocaleString();
row.innerHTML = `
<td><small>${payment.id}</small></td>
<td><strong>$${payment.amount}</strong></td>
<td>${payment.receiver}</td>
<td><span class="badge bg-${statusClass}">${payment.status}</span></td>
<td><small>${date}</small></td>
`;
tbody.appendChild(row);
});
}
} catch (error) {
console.error('Ошибка загрузки платежей:', error);
}
}
// Загружаем платежи при загрузке страницы
document.addEventListener('DOMContentLoaded', loadPayments);
</script>
</body>
</html>
'''
def render_pay_page(payment_id):
"""Страница оплаты для клиента"""
payments = load_payments()
payment = None
for p in payments:
if p["id"] == payment_id:
payment = p
break
if not payment:
return '''
<!DOCTYPE html>
<html>
<head>
<title>Платеж не найден</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="alert alert-danger">
<h4>Платеж не найден</h4>
<p>Проверьте правильность ссылки или обратитесь к отправителю.</p>
<a href="/" class="btn btn-primary">На главную</a>
</div>
</div>
</body>
</html>
'''
if payment["status"] != "pending":
status_message = {
"completed": "Оплачен",
"failed": "Не удался",
"cancelled": "Отменен"
}.get(payment["status"], "Неизвестен")
return f'''
<!DOCTYPE html>
<html>
<head>
<title>Статус платежа</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body text-center">
<h2>Платеж {status_message}</h2>
<div class="my-4">
<i class="fas fa-{ 'check-circle text-success' if payment['status'] == 'completed' else 'times-circle text-danger' } fa-5x"></i>
</div>
<p>ID платежа: <strong>{payment_id}</strong></p>
<p>Сумма: <strong>${payment['amount']}</strong></p>
<p>Получатель: <strong>{payment['receiver']}</strong></p>
<p>Описание: {payment['description']}</p>
<a href="/" class="btn btn-primary mt-3">На главную</a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
'''
return f'''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Оплата #{payment_id}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {{
--primary: #4361ee;
--gradient: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
}}
body {{
background: #f5f7fb;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}}
.payment-container {{
max-width: 500px;
margin: 50px auto;
}}
.payment-card {{
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
}}
.payment-header {{
background: var(--gradient);
color: white;
padding: 25px;
text-align: center;
}}
.payment-body {{
padding: 30px;
}}
.amount-display {{
font-size: 48px;
font-weight: bold;
color: var(--primary);
text-align: center;
margin: 20px 0;
}}
.btn-pay {{
background: var(--gradient);
border: none;
border-radius: 10px;
padding: 15px;
font-size: 18px;
font-weight: 600;
width: 100%;
margin-top: 20px;
}}
</style>
</head>
<body>
<div class="payment-container">
<div class="payment-card">
<div class="payment-header">
<h3><i class="fas fa-credit-card me-2"></i>Оплата</h3>
<p class="mb-0">ID: {payment_id}</p>
</div>
<div class="payment-body">
<div class="text-center mb-4">
<i class="fas fa-shopping-cart fa-4x text-primary"></i>
</div>
<div class="amount-display">
${payment['amount']}
</div>
<div class="payment-details">
<div class="d-flex justify-content-between mb-2">
<span>Получатель:</span>
<strong>{payment['receiver']}</strong>
</div>
<div class="d-flex justify-content-between mb-2">
<span>Описание:</span>
<strong>{payment['description'] or 'Нет описания'}</strong>
</div>
<div class="d-flex justify-content-between mb-4">
<span>Статус:</span>
<span class="badge bg-warning">Ожидает оплаты</span>
</div>
</div>
<form action="/process_payment/{payment_id}" method="post" id="paymentForm">
<div class="mb-3">
<label class="form-label">Ваш логин</label>
<input type="text" name="username" class="form-control" placeholder="Введите ваш логин" required>
</div>
<div class="mb-3">
<label class="form-label">Пароль</label>
<input type="password" name="password" class="form-control" placeholder="Введите пароль" required>
</div>
<button type="submit" class="btn btn-pay">
<i class="fas fa-lock me-2"></i>Оплатить ${payment['amount']}
</button>
<div class="text-center mt-3">
<small class="text-muted">После оплаты средства будут переведены получателю</small>
</div>
</form>
<div class="alert alert-info mt-4">
<h6><i class="fas fa-info-circle me-2"></i>Безопасная оплата</h6>
<p class="mb-0 small">Все платежи защищены банковской системой. Для оплаты требуется авторизация.</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById('paymentForm').addEventListener('submit', function(e) {{
const submitBtn = this.querySelector('button[type="submit"]');
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Обработка...';
}});
</script>
</body>
</html>
'''
# ---------- ИСПРАВЛЕННЫЕ API МАРШРУТЫ ----------
@app.route('/api/v1/balance', methods=['GET'])
@api_key_required
def api_balance():
"""API: Получить информацию о балансе - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
user_data = request.user_data
return jsonify({
'success': True,
'username': request.username,
'balance': user_data.get('balance', 0),
'deposit': user_data.get('deposit', 0),
'credit': user_data.get('credit', 0),
'total': user_data.get('balance', 0) + user_data.get('deposit', 0) - user_data.get('credit', 0)
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/history', methods=['GET'])
@api_key_required
def api_history():
"""API: Получить историю операций - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
history = load_history()
user_history = [h for h in history if h['user'] == request.username]
limit = request.args.get('limit', type=int, default=50)
if limit > 0 and limit < len(user_history):
user_history = user_history[-limit:]
for h in user_history:
h['time_formatted'] = datetime.fromtimestamp(h['time']).strftime('%Y-%m-%d %H:%M:%S')
return jsonify({
'success': True,
'count': len(user_history),
'history': user_history[::-1]
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/cards', methods=['GET'])
@api_key_required
def api_cards():
"""API: Получить список карт - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
cards = get_user_cards(request.username)
safe_cards = []
for card in cards:
safe_card = card.copy()
if 'number' in safe_card and len(safe_card['number']) > 4:
safe_card['number_masked'] = '**** **** **** ' + safe_card['number'][-4:]
safe_card['cvv'] = '***'
safe_cards.append(safe_card)
return jsonify({
'success': True,
'count': len(safe_cards),
'cards': safe_cards
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/deposit', methods=['POST'])
@api_key_required
def api_deposit():
"""API: Открыть вклад - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
amount = data.get('amount')
if not amount:
return jsonify({'success': False, 'error': 'Amount required'}), 400
amount = int(amount)
if amount <= 0:
return jsonify({'success': False, 'error': 'Amount must be positive'}), 400
users = load_users()
user = users.get(request.username)
if not user or user['balance'] < amount:
return jsonify({'success': False, 'error': 'Insufficient funds'}), 400
user['balance'] -= amount
user['deposit'] += amount
save_users(users)
add_history(request.username, 'api_deposit', amount)
return jsonify({
'success': True,
'message': f'Deposit of ${amount} created successfully',
'new_balance': user['balance'],
'new_deposit': user['deposit']
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/credit', methods=['POST'])
@api_key_required
def api_credit():
"""API: Взять кредит - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
amount = data.get('amount')
if not amount:
return jsonify({'success': False, 'error': 'Amount required'}), 400
amount = int(amount)
if amount <= 0:
return jsonify({'success': False, 'error': 'Amount must be positive'}), 400
users = load_users()
user = users.get(request.username)
if not user:
return jsonify({'success': False, 'error': 'User not found'}), 404
user['balance'] += amount
user['credit'] += amount
save_users(users)
add_history(request.username, 'api_credit', amount)
return jsonify({
'success': True,
'message': f'Credit of ${amount} taken successfully',
'new_balance': user['balance'],
'new_credit': user['credit']
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/transfer', methods=['POST'])
@api_key_required
def api_transfer():
"""API: Сделать перевод - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
target = data.get('target', '').strip()
amount = data.get('amount')
if not target:
return jsonify({'success': False, 'error': 'Target username required'}), 400
if not amount:
return jsonify({'success': False, 'error': 'Amount required'}), 400
amount = int(amount)
if amount <= 0:
return jsonify({'success': False, 'error': 'Amount must be positive'}), 400
users = load_users()
if target not in users:
return jsonify({'success': False, 'error': 'Target user not found'}), 404
if target == request.username:
return jsonify({'success': False, 'error': 'Cannot transfer to yourself'}), 400
sender = users.get(request.username)
if not sender:
return jsonify({'success': False, 'error': 'Sender not found'}), 404
if sender['balance'] < amount:
return jsonify({'success': False, 'error': 'Insufficient funds'}), 400
sender['balance'] -= amount
users[target]['balance'] += amount
save_users(users)
add_history(request.username, 'api_transfer', amount, target)
return jsonify({
'success': True,
'message': f'Transfer of ${amount} to {target} successful',
'new_balance': sender['balance']
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/cards/create', methods=['POST'])
@api_key_required
def api_create_card():
"""API: Создать карту - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
card_type = data.get('card_type', 'standard')
if card_type not in ['standard', 'gold', 'platinum']:
return jsonify({'success': False, 'error': 'Invalid card type'}), 400
users = load_users()
user = users.get(request.username)
if not user:
return jsonify({'success': False, 'error': 'User not found'}), 404
costs = {'standard': 0, 'gold': 50, 'platinum': 100}
cost = costs.get(card_type, 0)
if user['balance'] < cost:
return jsonify({'success': False, 'error': 'Insufficient funds'}), 400
if cost > 0:
user['balance'] -= cost
save_users(users)
card = create_user_card(request.username, user, card_type)
if not card:
if cost > 0:
user['balance'] += cost
save_users(users)
return jsonify({'success': False, 'error': 'Card creation failed. Maximum cards limit reached?'}), 400
safe_card = card.copy()
if 'number' in safe_card and len(safe_card['number']) > 4:
safe_card['number_masked'] = '**** **** **** ' + safe_card['number'][-4:]
safe_card['cvv'] = '***'
return jsonify({
'success': True,
'message': f'{card_type.capitalize()} card created successfully',
'card': safe_card,
'cost': cost,
'new_balance': user['balance']
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/cards/delete', methods=['POST'])
@api_key_required
def api_delete_card():
"""API: Удалить карту - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
card_id = data.get('card_id')
if not card_id:
return jsonify({'success': False, 'error': 'Card ID required'}), 400
cards = load_cards()
user_cards = cards.get(request.username, [])
card_to_delete = None
for card in user_cards:
if card['id'] == card_id:
card_to_delete = card
break
if not card_to_delete:
return jsonify({'success': False, 'error': 'Card not found'}), 404
cards[request.username] = [c for c in user_cards if c['id'] != card_id]
save_cards(cards)
add_history(request.username, 'api_delete_card', 0, f"Card {card_to_delete['number'][-4:]}")
return jsonify({
'success': True,
'message': f"Card ending with {card_to_delete['number'][-4:]} deleted successfully"
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
# ---------- API ДЛЯ ПЛАТЕЖЕЙ ----------
@app.route('/api/v1/payment/create', methods=['POST'])
@api_key_required
def api_payment_create():
"""API: Создать платеж"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
receiver = data.get('receiver')
amount = data.get('amount')
description = data.get('description', '')
external_id = data.get('external_id', '')
if not receiver:
return jsonify({'success': False, 'error': 'Receiver required'}), 400
if not amount:
return jsonify({'success': False, 'error': 'Amount required'}), 400
try:
amount_int = int(amount)
if amount_int <= 0:
return jsonify({'success': False, 'error': 'Amount must be positive'}), 400
except:
return jsonify({'success': False, 'error': 'Invalid amount'}), 400
# Проверяем существование получателя
users = load_users()
if receiver not in users:
return jsonify({'success': False, 'error': 'Receiver not found'}), 404
# Создаем платеж
payment_id, payment_data = create_payment(
payer=request.username,
receiver=receiver,
amount=amount_int,
description=description,
external_id=external_id
)
return jsonify({
'success': True,
'message': 'Payment created successfully',
'payment_id': payment_id,
'payment': payment_data,
'payment_url': f'/pay/{payment_id}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/payment/process', methods=['POST'])
@api_key_required
def api_payment_process():
"""API: Выполнить платеж"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
payment_id = data.get('payment_id')
if not payment_id:
return jsonify({'success': False, 'error': 'Payment ID required'}), 400
success, message = process_payment(payment_id, request.username)
return jsonify({
'success': success,
'message': message
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/payment/status/<payment_id>', methods=['GET'])
@api_key_required
def api_payment_status(payment_id):
"""API: Получить статус платежа"""
try:
payments = load_payments()
payment = None
for p in payments:
if p["id"] == payment_id:
payment = p
break
if not payment:
return jsonify({'success': False, 'error': 'Payment not found'}), 404
# Проверяем, имеет ли пользователь доступ к этому платежу
if payment["payer"] != request.username and payment["receiver"] != request.username:
users = load_users()
if users.get(request.username, {}).get('role') != 'admin':
return jsonify({'success': False, 'error': 'Access denied'}), 403
return jsonify({
'success': True,
'payment': payment
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/payment/list', methods=['GET'])
@api_key_required
def api_payment_list():
"""API: Получить список платежей пользователя"""
try:
payments = load_payments()
# Фильтруем платежи по пользователю
user_payments = []
for payment in payments:
if payment["payer"] == request.username or payment["receiver"] == request.username:
user_payments.append(payment)
# Сортируем по дате (новые сверху)
user_payments.sort(key=lambda x: x['created'], reverse=True)
# Ограничиваем количество
limit = min(20, len(user_payments))
user_payments = user_payments[:limit]
return jsonify({
'success': True,
'count': len(user_payments),
'payments': user_payments
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/shop/items', methods=['GET'])
@api_key_required
def api_shop_items():
"""API: Получить список товаров - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
shop_data = load_shop()
available_items = [item for item in shop_data["items"] if item["status"] == "available"]
for item in available_items:
item['created_formatted'] = datetime.fromtimestamp(item['created']).strftime('%Y-%m-%d %H:%M:%S')
return jsonify({
'success': True,
'count': len(available_items),
'items': available_items[::-1]
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/shop/ads', methods=['GET'])
@api_key_required
def api_shop_ads():
"""API: Получить список объявлений - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
shop_data = load_shop()
active_ads = [ad for ad in shop_data["ads"] if ad["status"] == "active"]
for ad in active_ads:
ad['created_formatted'] = datetime.fromtimestamp(ad['created']).strftime('%Y-%m-%d %H:%M:%S')
return jsonify({
'success': True,
'count': len(active_ads),
'ads': active_ads[::-1]
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/system/status', methods=['GET'])
@api_key_required
def api_system_status():
"""API: Получить статус системы - ИСПРАВЛЕННАЯ ВЕРСИЯ"""
try:
users = load_users()
valid_users = [u for u in users.values() if isinstance(u, dict)]
total_users = len(valid_users)
total_balance = sum(u.get('balance', 0) for u in valid_users)
total_deposit = sum(u.get('deposit', 0) for u in valid_users)
total_credit = sum(u.get('credit', 0) for u in valid_users)
return jsonify({
'success': True,
'system_status': 'operational',
'statistics': {
'total_users': total_users,
'total_balance': total_balance,
'total_deposit': total_deposit,
'total_credit': total_credit,
'net_worth': total_balance + total_deposit - total_credit
},
'timestamp': int(time.time())
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
# ДОБАВЛЕННЫЕ API ЭНДПОИНТЫ
@app.route('/api/v1/users', methods=['GET'])
@api_key_required
def api_users():
"""API: Получить список пользователей (только для админов)"""
try:
users = load_users()
if not isinstance(request.user_data, dict) or request.user_data.get('role') != 'admin':
return jsonify({'success': False, 'error': 'Admin access required'}), 403
safe_users = {}
for username, user in users.items():
if isinstance(user, dict):
safe_user = user.copy()
safe_user.pop('password', None)
safe_users[username] = safe_user
return jsonify({
'success': True,
'count': len(safe_users),
'users': safe_users
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/health', methods=['GET'])
def api_health():
"""API: Проверка здоровья системы (не требует API ключа)"""
try:
files = [USERS_FILE, HISTORY_FILE, CARDS_FILE, SHOP_FILE, PAYMENTS_FILE]
file_status = {}
for file in files:
file_status[file] = os.path.exists(file)
return jsonify({
'status': 'healthy',
'timestamp': int(time.time()),
'files': file_status,
'message': 'System is operational'
})
except Exception as e:
return jsonify({'status': 'unhealthy', 'error': str(e)}), 500
@app.route('/api/v1/shop/create_item', methods=['POST'])
@api_key_required
def api_create_shop_item():
"""API: Создать товар в магазине"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
title = data.get('title')
description = data.get('description')
price = data.get('price')
category = data.get('category', 'other')
if not title or not description or not price:
return jsonify({'success': False, 'error': 'Title, description and price required'}), 400
try:
price_int = int(price)
if price_int <= 0:
return jsonify({'success': False, 'error': 'Price must be positive'}), 400
except:
return jsonify({'success': False, 'error': 'Invalid price'}), 400
item_id = create_shop_item(request.username, title, description, price_int, category)
return jsonify({
'success': True,
'message': f'Item "{title}" created successfully',
'item_id': item_id
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/v1/shop/buy_item', methods=['POST'])
@api_key_required
def api_buy_item():
"""API: Купить товар"""
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'JSON data required'}), 400
data = request.get_json()
item_id = data.get('item_id')
if not item_id:
return jsonify({'success': False, 'error': 'Item ID required'}), 400
success, message = buy_item(item_id, request.username)
return jsonify({
'success': success,
'message': message
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
# ---------- МАГАЗИН МАРШРУТЫ ----------
@app.route('/create_shop_item', methods=['POST'])
@login_required
def create_shop_item_route():
"""Создание товара в магазине"""
title = request.form.get('title')
description = request.form.get('description')
price = request.form.get('price')
category = request.form.get('category', 'other')
if not title or not description or not price:
return redirect('/dashboard')
try:
price_int = int(price)
if price_int <= 0:
return redirect('/dashboard')
except:
return redirect('/dashboard')
item_id = create_shop_item(session['user'], title, description, price_int, category)
return redirect('/dashboard')
@app.route('/create_advertisement', methods=['POST'])
@login_required
def create_advertisement_route():
"""Создание объявления"""
title = request.form.get('title')
content = request.form.get('content')
contact_info = request.form.get('contact_info')
if not title or not content or not contact_info:
return redirect('/dashboard')
ad_id = create_advertisement(session['user'], title, content, contact_info)
return redirect('/dashboard')
@app.route('/buy_item/<item_id>', methods=['POST'])
@login_required
def buy_item_route(item_id):
"""Покупка товара"""
success, message = buy_item(item_id, session['user'])
return redirect('/dashboard')
# ---------- ПЛАТЕЖНЫЕ МАРШРУТЫ ----------
@app.route('/payment_page')
@login_required
def payment_page():
"""Страница управления платежами"""
return render_payment_page()
@app.route('/create_payment_link', methods=['POST'])
@login_required
def create_payment_link():
"""Создание ссылки для оплаты"""
receiver = request.form.get('receiver')
amount = request.form.get('amount')
description = request.form.get('description', '')
if not receiver or not amount:
return redirect('/payment_page')
try:
amount_int = int(amount)
if amount_int <= 0:
return redirect('/payment_page')
except:
return redirect('/payment_page')
payment_id, payment_data = create_payment(
payer=session['user'],
receiver=receiver,
amount=amount_int,
description=description
)
return redirect(f'/payment_page')
@app.route('/pay/<payment_id>', methods=['GET'])
def pay_page(payment_id):
"""Страница оплаты для клиента"""
return render_pay_page(payment_id)
@app.route('/process_payment/<payment_id>', methods=['POST'])
def process_payment_route(payment_id):
"""Обработка платежа (для неавторизованных пользователей)"""
username = request.form.get('username')
password = request.form.get('password')
if not username or not password:
return "Неверные данные", 400
# Проверяем логин и пароль
users = load_users()
user_data = users.get(username)
if not user_data or user_data['password'] != password:
return "Неверный логин или пароль", 401
# Проверяем, что пользователь является плательщиком
payments = load_payments()
payment = None
for p in payments:
if p["id"] == payment_id:
payment = p
break
if not payment:
return "Платеж не найден", 404
if payment["payer"] != username:
return "Вы не являетесь плательщиком по этому платежу", 403
# Выполняем платеж
success, message = process_payment(payment_id, username)
if success:
return redirect(f'/pay/{payment_id}')
else:
return f"Ошибка: {message}", 400
# ---------- ОСНОВНЫЕ МАРШРУТЫ ----------
@app.route('/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
users = load_users()
username = request.form.get('username')
password = request.form.get('password')
user_data = users.get(username)
if user_data and isinstance(user_data, dict) and user_data['password'] == password:
session['user'] = username
return redirect('/dashboard')
else:
return render_login().replace('</form>',
'<div class="alert alert-danger mt-3">Неверный логин или пароль</div></form>')
return render_login()
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
users = load_users()
username = request.form.get('username')
if username in users:
return render_register().replace('</form>',
'<div class="alert alert-danger mt-3">Пользователь уже существует</div></form>')
user_data = {
'password': request.form.get('password'),
'balance': 1000,
'deposit': 0,
'credit': 0,
'role': 'client',
'full_name': f"{request.form.get('first_name')} {request.form.get('last_name')}",
'email': request.form.get('email'),
'api_key': None
}
users[username] = user_data
save_users(users)
session['user'] = username
return redirect('/dashboard')
return render_register()
@app.route('/dashboard')
@login_required
def dashboard():
users = load_users()
history = load_history()
if session['user'] not in users:
session.clear()
return redirect('/')
user_data = users[session['user']]
user_history = [h for h in history if h['user'] == session['user']]
user_cards = get_user_cards(session['user'])
shop_data = load_shop()
return render_dashboard(user_data, user_history[-20:], user_cards, shop_data)
@app.route('/generate_api_key', methods=['POST'])
@login_required
def generate_api_key_route():
users = load_users()
user = users.get(session['user'])
if user and isinstance(user, dict):
user['api_key'] = generate_api_key()
save_users(users)
add_history(session['user'], 'generate_api_key', 0)
return redirect('/dashboard')
@app.route('/deposit', methods=['POST'])
@login_required
def deposit():
users = load_users()
user = users.get(session['user'])
try:
amount = int(request.form.get('amount', 0))
if amount > 0 and user and user['balance'] >= amount:
user['balance'] -= amount
user['deposit'] += amount
save_users(users)
add_history(session['user'], 'deposit', amount)
except:
pass
return redirect('/dashboard')
@app.route('/credit', methods=['POST'])
@login_required
def credit():
users = load_users()
user = users.get(session['user'])
try:
amount = int(request.form.get('amount', 0))
if amount > 0 and user:
user['balance'] += amount
user['credit'] += amount
save_users(users)
add_history(session['user'], 'credit', amount)
except:
pass
return redirect('/dashboard')
@app.route('/transfer', methods=['POST'])
@login_required
def transfer():
users = load_users()
sender = session['user']
try:
target = request.form.get('target')
amount = int(request.form.get('amount', 0))
sender_data = users.get(sender)
target_data = users.get(target)
if (amount > 0 and target_data and target != sender and
sender_data and sender_data['balance'] >= amount):
sender_data['balance'] -= amount
target_data['balance'] += amount
save_users(users)
add_history(sender, 'transfer', amount, target)
except Exception as e:
print(f"Transfer error: {e}")
pass
return redirect('/dashboard')
@app.route('/create_card', methods=['POST'])
@login_required
def create_card():
users = load_users()
user = users.get(session['user'])
card_type = request.form.get('card_type', 'standard')
costs = {
'standard': 0,
'gold': 50,
'platinum': 100
}
cost = costs.get(card_type, 0)
if user and user['balance'] >= cost:
user['balance'] -= cost
save_users(users)
card = create_user_card(session['user'], user, card_type)
if card:
add_history(session['user'], 'create_card', cost, f"{card_type}_{card['number'][-4:]}")
return redirect('/dashboard')
@app.route('/delete_card/<card_id>', methods=['POST'])
@login_required
def delete_card(card_id):
cards = load_cards()
user_cards = cards.get(session['user'], [])
cards[session['user']] = [c for c in user_cards if c['id'] != card_id]
save_cards(cards)
return redirect('/dashboard')
# ---------- АДМИН МАРШРУТЫ (ИСПРАВЛЕННЫЕ) ----------
@app.route('/admin')
@admin_required
def admin():
users = load_users()
return render_admin_panel(users)
@app.route('/admin/change_role', methods=['POST'])
@admin_required
def admin_change_role():
users = load_users()
username = request.form.get('username')
new_role = request.form.get('role')
if username in users and username != session['user'] and isinstance(users[username], dict):
users[username]['role'] = new_role
save_users(users)
add_history(session['user'], 'change_role', 0, f"{username}->{new_role}")
return redirect('/admin')
@app.route('/admin/add_money', methods=['POST'])
@admin_required
def admin_add_money():
users = load_users()
username = request.form.get('username')
amount = int(request.form.get('amount', 0))
if username in users and amount > 0 and isinstance(users[username], dict):
users[username]['balance'] += amount
save_users(users)
add_history('admin', 'add_money', amount, username)
return redirect('/admin')
@app.route('/logout')
def logout():
session.clear()
return redirect('/')
# ---------- API ДОКУМЕНТАЦИЯ ----------
@app.route('/api/docs')
def api_docs():
"""Страница с документацией API"""
return '''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Документация</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background: #f5f7fb;
padding: 20px;
}
.code-block {
background: #1e1e1e;
color: #d4d4d4;
padding: 15px;
border-radius: 5px;
font-family: "Courier New", monospace;
overflow-x: auto;
margin: 10px 0;
}
.endpoint {
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 3px 10px rgba(0,0,0,0.05);
border-left: 4px solid #4361ee;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-12">
<div class="card mb-4">
<div class="card-body">
<h1 class="card-title"><i class="fas fa-code me-2"></i>Документация API</h1>
<p class="card-text">Используйте API для интеграции банковских услуг в ваши приложения.</p>
<h3 class="mt-4">Базовый URL</h3>
<div class="code-block">
http://localhost:5000/api/v1/
</div>
<h3 class="mt-4">Аутентификация</h3>
<p>Для использования API необходим API ключ. Получите его в личном кабинете.</p>
<p>Передавайте ключ одним из способов:</p>
<h5>Заголовок HTTP:</h5>
<div class="code-block">
X-API-Key: ваш_api_ключ
</div>
<h5>Параметр запроса:</h5>
<div class="code-block">
?api_key=ваш_api_ключ
</div>
<h5>JSON тело запроса:</h5>
<div class="code-block">
{
"api_key": "ваш_api_ключ",
...
}
</div>
<h3 class="mt-4">Доступные эндпоинты</h3>
<div class="endpoint">
<h5><span class="badge bg-success">GET</span> /api/v1/health</h5>
<p>Проверка здоровья системы (не требует API ключа)</p>
<div class="code-block">
curl http://localhost:5000/api/v1/health
</div>
</div>
<div class="endpoint">
<h5><span class="badge bg-info">GET</span> /api/v1/balance</h5>
<p>Получить информацию о балансе пользователя</p>
<div class="code-block">
curl -H "X-API-Key: ваш_ключ" http://localhost:5000/api/v1/balance
</div>
</div>
<div class="endpoint">
<h5><span class="badge bg-info">GET</span> /api/v1/history?limit=50</h5>
<p>Получить историю операций (опциональный параметр limit)</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-success">POST</span> /api/v1/deposit</h5>
<p>Открыть вклад. Требуется JSON: {"amount": 100}</p>
<div class="code-block">
curl -X POST -H "X-API-Key: ваш_ключ" -H "Content-Type: application/json"<br>
-d "{\\"amount\\": 100}" http://localhost:5000/api/v1/deposit
</div>
</div>
<div class="endpoint">
<h5><span class="badge bg-success">POST</span> /api/v1/credit</h5>
<p>Взять кредит. Требуется JSON: {"amount": 100}</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-success">POST</span> /api/v1/transfer</h5>
<p>Сделать перевод. Требуется JSON: {"target": "username", "amount": 100}</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-info">GET</span> /api/v1/cards</h5>
<p>Получить список карт пользователя</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-success">POST</span> /api/v1/cards/create</h5>
<p>Создать карту. Требуется JSON: {"card_type": "standard"}</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-info">GET</span> /api/v1/shop/items</h5>
<p>Получить список товаров в магазине</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-success">POST</span> /api/v1/payment/create</h5>
<p>Создать платеж. Требуется JSON: {"receiver": "...", "amount": 100, "description": "..."}</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-success">POST</span> /api/v1/payment/process</h5>
<p>Выполнить платеж. Требуется JSON: {"payment_id": "..."}</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-info">GET</span> /api/v1/payment/status/{payment_id}</h5>
<p>Получить статус платежа</p>
</div>
<div class="endpoint">
<h5><span class="badge bg-info">GET</span> /api/v1/system/status</h5>
<p>Получить статус системы</p>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
'''
# ================= ДЕКОРАТОР УЧИТЕЛЯ =================
from functools import wraps
def teacher_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'user' not in session:
return redirect("/")
users = load_users()
user = users.get(session['user'])
if not user:
return redirect("/")
if user.get("role") not in ["teacher", "admin"]:
return redirect("/dashboard")
return f(*args, **kwargs)
return decorated
# ============================================================
# ========= ГРАФИК ДИНАМИКИ СРЕДНЕГО БАЛЛА ==================
# ============================================================
@app.route("/student_avg_graph/<student>")
@login_required
@teacher_required
def student_avg_graph(student):
journal = load_journal()
if student not in journal:
return "Нет данных"
subjects = journal[student]
# собираем оценки по порядку добавления
all_grades = []
for subject, grades in subjects.items():
all_grades += grades
averages = []
current = []
for g in all_grades:
current.append(g)
averages.append(sum(current) / len(current))
plt.figure()
plt.plot(range(1, len(averages)+1), averages)
plt.xlabel("Количество оценок")
plt.ylabel("Средний балл")
plt.title(f"Динамика среднего балла {student}")
img = BytesIO()
plt.savefig(img, format="png")
plt.close()
img.seek(0)
graph_url = base64.b64encode(img.getvalue()).decode()
return f"""
<h2>📈 Динамика среднего балла: {student}</h2>
<a href="/journal">Назад</a>
<br><br>
<img src="data:image/png;base64,{graph_url}">
"""
# ============================================================
# ========= ФИНАНСОВЫЙ ГРАФИК УЧЕНИКА ========================
# ============================================================
@app.route("/student_finance_graph/<student>")
@login_required
@teacher_required
def student_finance_graph(student):
journal = load_journal()
users = load_users()
if student not in journal:
return "Нет данных"
subjects = journal[student]
# считаем баланс на основе оценок
balance_history = []
balance = 0
for subject, grades in subjects.items():
for g in grades:
balance += calculate_money_for_grade(g)
balance_history.append(balance)
if not balance_history:
return "Нет оценок"
plt.figure()
plt.plot(range(1, len(balance_history)+1), balance_history)
plt.xlabel("Количество оценок")
plt.ylabel("Баланс ($)")
plt.title(f"Финансовая динамика {student}")
img = BytesIO()
plt.savefig(img, format="png")
plt.close()
img.seek(0)
graph_url = base64.b64encode(img.getvalue()).decode()
return f"""
<h2>💰 Финансовый график: {student}</h2>
<a href="/journal">Назад</a>
<br><br>
<img src="data:image/png;base64,{graph_url}">
"""
# ============================================================
# ОБНОВЛЕНИЕ ТАБЛИЦЫ В ЖУРНАЛЕ (добавление ссылок)
# ============================================================
@app.route("/journal")
@login_required
@teacher_required
def journal_page():
journal = load_journal()
table = ""
for student, subjects in journal.items():
total = []
for grades in subjects.values():
total += grades
avg = round(sum(total)/len(total),2) if total else 0
table += f"""
<tr>
<td>{student}</td>
<td>{avg}</td>
<td>
<a href="/student_graph/{student}">📊 Успеваемость</a><br>
<a href="/student_avg_graph/{student}">📈 Средний</a><br>
<a href="/student_finance_graph/{student}">💰 Финансы</a>
</td>
</tr>
"""
return f"""
<h2>Электронный журнал PRO MAX</h2>
<a href="/dashboard">Назад</a>
<hr>
<h3>Создать класс</h3>
<form method="post" action="/add_class">
<input name="class_name" placeholder="" required>
<button>Создать</button>
</form>
<h3>Добавить ученика в класс</h3>
<form method="post" action="/add_student_to_class">
<input name="student" placeholder="Логин ученика" required>
<input name="class_name" placeholder="Класс" required>
<button>Добавить</button>
</form>
<h3>Поставить оценку</h3>
<form method="post" action="/add_grade">
<input name="student" placeholder="Логин ученика" required>
<input name="subject" placeholder="Предмет" required>
<input name="grade" type="number" min="2" max="5" required>
<button>Поставить</button>
</form>
<hr>
<h3>Ученики</h3>
<table border="1" cellpadding="10">
<tr>
<th>Ученик</th>
<th>Средний балл</th>
<th>Графики</th>
</tr>
{table}
</table>
<br>
<a href="/rating">🏆 Смотреть рейтинг</a>
"""
if __name__ == '__main__':
import threading
def daily_update_thread():
while True:
time.sleep(86400)
daily_update()
thread = threading.Thread(target=daily_update_thread, daemon=True)
thread.start()
app.run(host='0.0.0.0', port=5000, debug=True)