Update app.py
Browse files
app.py
CHANGED
|
@@ -27,18 +27,20 @@ def load_data():
|
|
| 27 |
data = json.load(file)
|
| 28 |
if not isinstance(data, dict):
|
| 29 |
logging.warning("Данные не в формате dict, инициализация пустой базы")
|
| 30 |
-
return {'posts': [], 'users': {}, 'pending_organizations': []}
|
| 31 |
if 'posts' not in data:
|
| 32 |
data['posts'] = []
|
| 33 |
if 'users' not in data:
|
| 34 |
data['users'] = {}
|
| 35 |
if 'pending_organizations' not in data:
|
| 36 |
data['pending_organizations'] = []
|
|
|
|
|
|
|
| 37 |
logging.info("Данные успешно загружены")
|
| 38 |
return data
|
| 39 |
except Exception as e:
|
| 40 |
logging.error(f"Ошибка загрузки данных: {e}")
|
| 41 |
-
return {'posts': [], 'users': {}, 'pending_organizations': []}
|
| 42 |
|
| 43 |
def save_data(data):
|
| 44 |
try:
|
|
@@ -80,7 +82,7 @@ def download_db_from_hf():
|
|
| 80 |
logging.error(f"Ошибка скачивания базы: {e}")
|
| 81 |
if not os.path.exists(DATA_FILE):
|
| 82 |
with open(DATA_FILE, 'w', encoding='utf-8') as f:
|
| 83 |
-
json.dump({'posts': [], 'users': {}, 'pending_organizations': []}, f)
|
| 84 |
|
| 85 |
def periodic_backup():
|
| 86 |
while True:
|
|
@@ -101,6 +103,7 @@ BASE_STYLE = '''
|
|
| 101 |
--shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
| 102 |
--glass-bg: rgba(255, 255, 255, 0.15);
|
| 103 |
--transition: all 0.4s ease;
|
|
|
|
| 104 |
}
|
| 105 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 106 |
body {
|
|
@@ -208,6 +211,12 @@ BASE_STYLE = '''
|
|
| 208 |
transform: scale(1.08);
|
| 209 |
background: #5439cc;
|
| 210 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
input, textarea, select {
|
| 212 |
width: 100%;
|
| 213 |
padding: 14px;
|
|
@@ -296,6 +305,9 @@ NAV_HTML = '''
|
|
| 296 |
{% if is_authenticated %}
|
| 297 |
<a href="{{ url_for('profile') }}" class="nav-link"><span>👤</span> Профиль ({{ username }})</a>
|
| 298 |
<a href="{{ url_for('upload') }}" class="nav-link"><span>⬆️</span> Загрузить</a>
|
|
|
|
|
|
|
|
|
|
| 299 |
<a href="{{ url_for('logout') }}" class="nav-link logout-btn"><span>🚪</span> Выйти</a>
|
| 300 |
{% else %}
|
| 301 |
<a href="{{ url_for('login') }}" class="nav-link"><span>🔑</span> Войти</a>
|
|
@@ -622,6 +634,8 @@ def feed():
|
|
| 622 |
posts = sorted(data.get('posts', []), key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
|
| 623 |
is_authenticated = 'username' in session
|
| 624 |
username = session.get('username', None)
|
|
|
|
|
|
|
| 625 |
|
| 626 |
search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else request.args.get('search', '').strip().lower()
|
| 627 |
if search_query:
|
|
@@ -745,16 +759,21 @@ def feed():
|
|
| 745 |
</div>
|
| 746 |
<div class="post-grid">
|
| 747 |
{% for post in posts %}
|
| 748 |
-
<
|
| 749 |
-
<
|
| 750 |
-
<
|
| 751 |
-
|
| 752 |
-
|
|
|
|
|
|
|
| 753 |
<p>{{ post['description'] }}</p>
|
| 754 |
<p class="price">Цена: {{ post['price'] }} {{ post['currency'] }}</p>
|
| 755 |
<p>Загрузил: <a href="{{ url_for('user_profile', username=post['uploader']) }}" class="username-link">{{ post['uploader'] }}</a> | {{ post['upload_date'] }}</p>
|
| 756 |
<p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
|
| 757 |
-
|
|
|
|
|
|
|
|
|
|
| 758 |
{% endfor %}
|
| 759 |
</div>
|
| 760 |
</div>
|
|
@@ -781,6 +800,16 @@ def feed():
|
|
| 781 |
document.getElementById('imageModal').style.display = 'none';
|
| 782 |
}
|
| 783 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 784 |
window.onload = () => {
|
| 785 |
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
|
| 786 |
const videos = document.querySelectorAll('.post-preview');
|
|
@@ -794,7 +823,7 @@ def feed():
|
|
| 794 |
</body>
|
| 795 |
</html>
|
| 796 |
'''
|
| 797 |
-
return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query)
|
| 798 |
|
| 799 |
# Страница публикации
|
| 800 |
@app.route('/post/<post_id>', methods=['GET', 'POST'])
|
|
@@ -806,6 +835,8 @@ def post_page(post_id):
|
|
| 806 |
|
| 807 |
is_authenticated = 'username' in session
|
| 808 |
username = session.get('username', None)
|
|
|
|
|
|
|
| 809 |
post['views'] = post.get('views', 0) + 1
|
| 810 |
save_data(data)
|
| 811 |
|
|
@@ -912,6 +943,7 @@ def post_page(post_id):
|
|
| 912 |
{% if username in post['likes'] %}Убрать лайк{% else %}Лайк{% endif %}
|
| 913 |
</button>
|
| 914 |
</form>
|
|
|
|
| 915 |
<form method="POST" class="comment-section">
|
| 916 |
<textarea name="comment" placeholder="Оставьте комментарий" rows="4"></textarea>
|
| 917 |
<button type="submit" class="btn">Отправить</button>
|
|
@@ -936,6 +968,16 @@ def post_page(post_id):
|
|
| 936 |
document.body.classList.toggle('dark');
|
| 937 |
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
|
| 938 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 939 |
window.onload = () => {
|
| 940 |
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
|
| 941 |
};
|
|
@@ -943,7 +985,7 @@ def post_page(post_id):
|
|
| 943 |
</body>
|
| 944 |
</html>
|
| 945 |
'''
|
| 946 |
-
return render_template_string(html, post=post, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username)
|
| 947 |
|
| 948 |
# Профиль пользователя
|
| 949 |
@app.route('/profile', methods=['GET', 'POST'])
|
|
@@ -999,6 +1041,27 @@ def profile():
|
|
| 999 |
data['users'][username]['link'] = link
|
| 1000 |
save_data(data)
|
| 1001 |
return redirect(url_for('profile'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1002 |
|
| 1003 |
html = '''
|
| 1004 |
<!DOCTYPE html>
|
|
@@ -1046,12 +1109,12 @@ def profile():
|
|
| 1046 |
font-size: 1.2em;
|
| 1047 |
margin-bottom: 15px;
|
| 1048 |
}
|
| 1049 |
-
.post-grid {
|
| 1050 |
display: grid;
|
| 1051 |
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
| 1052 |
gap: 30px;
|
| 1053 |
}
|
| 1054 |
-
.post-item {
|
| 1055 |
background: var(--card-bg-light);
|
| 1056 |
backdrop-filter: blur(20px);
|
| 1057 |
padding: 25px;
|
|
@@ -1059,10 +1122,10 @@ def profile():
|
|
| 1059 |
box-shadow: var(--shadow);
|
| 1060 |
transition: var(--transition);
|
| 1061 |
}
|
| 1062 |
-
body.dark .post-item {
|
| 1063 |
background: var(--card-bg-dark);
|
| 1064 |
}
|
| 1065 |
-
.post-item:hover {
|
| 1066 |
transform: translateY(-15px);
|
| 1067 |
}
|
| 1068 |
.post-preview {
|
|
@@ -1072,7 +1135,7 @@ def profile():
|
|
| 1072 |
border-radius: 15px;
|
| 1073 |
margin-bottom: 20px;
|
| 1074 |
}
|
| 1075 |
-
.post-item h3 {
|
| 1076 |
font-size: 1.5em;
|
| 1077 |
margin-bottom: 15px;
|
| 1078 |
}
|
|
@@ -1099,6 +1162,12 @@ def profile():
|
|
| 1099 |
font-weight: 600;
|
| 1100 |
color: var(--primary);
|
| 1101 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1102 |
</style>
|
| 1103 |
</head>
|
| 1104 |
<body>
|
|
@@ -1131,6 +1200,9 @@ def profile():
|
|
| 1131 |
{% if user_type == 'seller' and verified %}
|
| 1132 |
<a href="{{ url_for('upload') }}" class="btn">Добавить видео</a>
|
| 1133 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 1134 |
<h2>Ваши видео</h2>
|
| 1135 |
<div class="post-grid">
|
| 1136 |
{% if user_posts %}
|
|
@@ -1169,8 +1241,54 @@ def profile():
|
|
| 1169 |
alert('Ссылка скопирована!');
|
| 1170 |
});
|
| 1171 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1172 |
window.onload = () => {
|
| 1173 |
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
|
|
|
|
| 1174 |
const videos = document.querySelectorAll('.post-preview');
|
| 1175 |
videos.forEach(video => {
|
| 1176 |
video.addEventListener('loadedmetadata', () => {
|
|
@@ -1193,6 +1311,7 @@ def user_profile(username):
|
|
| 1193 |
|
| 1194 |
user_posts = sorted([p for p in data['posts'] if p['uploader'] == username], key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
|
| 1195 |
is_authenticated = 'username' in session
|
|
|
|
| 1196 |
total_views = sum(post.get('views', 0) for post in user_posts)
|
| 1197 |
total_likes = sum(len(post.get('likes', [])) for post in user_posts)
|
| 1198 |
user_data = data['users'].get(username, {})
|
|
@@ -1437,6 +1556,8 @@ def upload():
|
|
| 1437 |
|
| 1438 |
is_authenticated = 'username' in session
|
| 1439 |
username = session.get('username', None)
|
|
|
|
|
|
|
| 1440 |
html = '''
|
| 1441 |
<!DOCTYPE html>
|
| 1442 |
<html lang="ru">
|
|
@@ -1566,7 +1687,150 @@ def upload():
|
|
| 1566 |
</body>
|
| 1567 |
</html>
|
| 1568 |
'''
|
| 1569 |
-
return render_template_string(html, username=username, is_authenticated=is_authenticated)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1570 |
|
| 1571 |
# Админ-панель
|
| 1572 |
@app.route('/admhosto', methods=['GET', 'POST'])
|
|
@@ -1576,6 +1840,8 @@ def admin_panel():
|
|
| 1576 |
pending_orgs = data.get('pending_organizations', [])
|
| 1577 |
is_authenticated = 'username' in session
|
| 1578 |
username = session.get('username', None)
|
|
|
|
|
|
|
| 1579 |
|
| 1580 |
if request.method == 'POST':
|
| 1581 |
if 'delete' in request.form:
|
|
@@ -1603,7 +1869,7 @@ def admin_panel():
|
|
| 1603 |
posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()]
|
| 1604 |
|
| 1605 |
html = '''
|
| 1606 |
-
<!DOCTYPE html
|
| 1607 |
<html lang="ru">
|
| 1608 |
<head>
|
| 1609 |
<meta charset="UTF-8">
|
|
@@ -1797,7 +2063,7 @@ def admin_panel():
|
|
| 1797 |
</body>
|
| 1798 |
</html>
|
| 1799 |
'''
|
| 1800 |
-
return render_template_string(html, posts=posts, pending_orgs=pending_orgs, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query)
|
| 1801 |
|
| 1802 |
if __name__ == '__main__':
|
| 1803 |
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
|
|
|
| 27 |
data = json.load(file)
|
| 28 |
if not isinstance(data, dict):
|
| 29 |
logging.warning("Данные не в формате dict, инициализация пустой базы")
|
| 30 |
+
return {'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}}
|
| 31 |
if 'posts' not in data:
|
| 32 |
data['posts'] = []
|
| 33 |
if 'users' not in data:
|
| 34 |
data['users'] = {}
|
| 35 |
if 'pending_organizations' not in data:
|
| 36 |
data['pending_organizations'] = []
|
| 37 |
+
if 'orders' not in data:
|
| 38 |
+
data['orders'] = {}
|
| 39 |
logging.info("Данные успешно загружены")
|
| 40 |
return data
|
| 41 |
except Exception as e:
|
| 42 |
logging.error(f"Ошибка загрузки данных: {e}")
|
| 43 |
+
return {'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}}
|
| 44 |
|
| 45 |
def save_data(data):
|
| 46 |
try:
|
|
|
|
| 82 |
logging.error(f"Ошибка скачивания базы: {e}")
|
| 83 |
if not os.path.exists(DATA_FILE):
|
| 84 |
with open(DATA_FILE, 'w', encoding='utf-8') as f:
|
| 85 |
+
json.dump({'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}}, f)
|
| 86 |
|
| 87 |
def periodic_backup():
|
| 88 |
while True:
|
|
|
|
| 103 |
--shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
| 104 |
--glass-bg: rgba(255, 255, 255, 0.15);
|
| 105 |
--transition: all 0.4s ease;
|
| 106 |
+
--cart-btn: #10b981;
|
| 107 |
}
|
| 108 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 109 |
body {
|
|
|
|
| 211 |
transform: scale(1.08);
|
| 212 |
background: #5439cc;
|
| 213 |
}
|
| 214 |
+
.cart-btn {
|
| 215 |
+
background: var(--cart-btn);
|
| 216 |
+
}
|
| 217 |
+
.cart-btn:hover {
|
| 218 |
+
background: #0d9f6e;
|
| 219 |
+
}
|
| 220 |
input, textarea, select {
|
| 221 |
width: 100%;
|
| 222 |
padding: 14px;
|
|
|
|
| 305 |
{% if is_authenticated %}
|
| 306 |
<a href="{{ url_for('profile') }}" class="nav-link"><span>👤</span> Профиль ({{ username }})</a>
|
| 307 |
<a href="{{ url_for('upload') }}" class="nav-link"><span>⬆️</span> Загрузить</a>
|
| 308 |
+
{% if user_type == 'seller' and verified %}
|
| 309 |
+
<a href="{{ url_for('orders') }}" class="nav-link"><span>🛒</span> Заказы</a>
|
| 310 |
+
{% endif %}
|
| 311 |
<a href="{{ url_for('logout') }}" class="nav-link logout-btn"><span>🚪</span> Выйти</a>
|
| 312 |
{% else %}
|
| 313 |
<a href="{{ url_for('login') }}" class="nav-link"><span>🔑</span> Войти</a>
|
|
|
|
| 634 |
posts = sorted(data.get('posts', []), key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
|
| 635 |
is_authenticated = 'username' in session
|
| 636 |
username = session.get('username', None)
|
| 637 |
+
user_type = data['users'].get(username, {}).get('type') if username else None
|
| 638 |
+
verified = data['users'].get(username, {}).get('verified', False) if username else False
|
| 639 |
|
| 640 |
search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else request.args.get('search', '').strip().lower()
|
| 641 |
if search_query:
|
|
|
|
| 759 |
</div>
|
| 760 |
<div class="post-grid">
|
| 761 |
{% for post in posts %}
|
| 762 |
+
<div class="post-item">
|
| 763 |
+
<a href="{{ url_for('post_page', post_id=post['id']) }}">
|
| 764 |
+
<video class="post-preview" preload="metadata" muted>
|
| 765 |
+
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/videos/{{ post['filename'] }}" type="video/mp4">
|
| 766 |
+
</video>
|
| 767 |
+
<h2>{{ post['title'] }}</h2>
|
| 768 |
+
</a>
|
| 769 |
<p>{{ post['description'] }}</p>
|
| 770 |
<p class="price">Цена: {{ post['price'] }} {{ post['currency'] }}</p>
|
| 771 |
<p>Загрузил: <a href="{{ url_for('user_profile', username=post['uploader']) }}" class="username-link">{{ post['uploader'] }}</a> | {{ post['upload_date'] }}</p>
|
| 772 |
<p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
|
| 773 |
+
{% if is_authenticated %}
|
| 774 |
+
<button class="btn cart-btn" onclick="addToCart('{{ post['id'] }}', '{{ post['title'] }}', '{{ post['price'] }}', '{{ post['currency'] }}', '{{ post['uploader'] }}')">В корзину</button>
|
| 775 |
+
{% endif %}
|
| 776 |
+
</div>
|
| 777 |
{% endfor %}
|
| 778 |
</div>
|
| 779 |
</div>
|
|
|
|
| 800 |
document.getElementById('imageModal').style.display = 'none';
|
| 801 |
}
|
| 802 |
}
|
| 803 |
+
function addToCart(postId, title, price, currency, uploader) {
|
| 804 |
+
let cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 805 |
+
if (!cart.some(item => item.postId === postId)) {
|
| 806 |
+
cart.push({ postId, title, price, currency, uploader });
|
| 807 |
+
localStorage.setItem('cart', JSON.stringify(cart));
|
| 808 |
+
alert('Добавлено в корзину!');
|
| 809 |
+
} else {
|
| 810 |
+
alert('Этот товар уже в корзине!');
|
| 811 |
+
}
|
| 812 |
+
}
|
| 813 |
window.onload = () => {
|
| 814 |
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
|
| 815 |
const videos = document.querySelectorAll('.post-preview');
|
|
|
|
| 823 |
</body>
|
| 824 |
</html>
|
| 825 |
'''
|
| 826 |
+
return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query, user_type=user_type, verified=verified)
|
| 827 |
|
| 828 |
# Страница публикации
|
| 829 |
@app.route('/post/<post_id>', methods=['GET', 'POST'])
|
|
|
|
| 835 |
|
| 836 |
is_authenticated = 'username' in session
|
| 837 |
username = session.get('username', None)
|
| 838 |
+
user_type = data['users'].get(username, {}).get('type') if username else None
|
| 839 |
+
verified = data['users'].get(username, {}).get('verified', False) if username else False
|
| 840 |
post['views'] = post.get('views', 0) + 1
|
| 841 |
save_data(data)
|
| 842 |
|
|
|
|
| 943 |
{% if username in post['likes'] %}Убрать лайк{% else %}Лайк{% endif %}
|
| 944 |
</button>
|
| 945 |
</form>
|
| 946 |
+
<button class="btn cart-btn" onclick="addToCart('{{ post['id'] }}', '{{ post['title'] }}', '{{ post['price'] }}', '{{ post['currency'] }}', '{{ post['uploader'] }}')">В корзину</button>
|
| 947 |
<form method="POST" class="comment-section">
|
| 948 |
<textarea name="comment" placeholder="Оставьте комментарий" rows="4"></textarea>
|
| 949 |
<button type="submit" class="btn">Отправить</button>
|
|
|
|
| 968 |
document.body.classList.toggle('dark');
|
| 969 |
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
|
| 970 |
}
|
| 971 |
+
function addToCart(postId, title, price, currency, uploader) {
|
| 972 |
+
let cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 973 |
+
if (!cart.some(item => item.postId === postId)) {
|
| 974 |
+
cart.push({ postId, title, price, currency, uploader });
|
| 975 |
+
localStorage.setItem('cart', JSON.stringify(cart));
|
| 976 |
+
alert('Добавлено в корзину!');
|
| 977 |
+
} else {
|
| 978 |
+
alert('Этот товар уже в корзине!');
|
| 979 |
+
}
|
| 980 |
+
}
|
| 981 |
window.onload = () => {
|
| 982 |
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
|
| 983 |
};
|
|
|
|
| 985 |
</body>
|
| 986 |
</html>
|
| 987 |
'''
|
| 988 |
+
return render_template_string(html, post=post, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username, user_type=user_type, verified=verified)
|
| 989 |
|
| 990 |
# Профиль пользователя
|
| 991 |
@app.route('/profile', methods=['GET', 'POST'])
|
|
|
|
| 1041 |
data['users'][username]['link'] = link
|
| 1042 |
save_data(data)
|
| 1043 |
return redirect(url_for('profile'))
|
| 1044 |
+
elif 'checkout' in request.form:
|
| 1045 |
+
cart = json.loads(request.form.get('cart_data', '[]'))
|
| 1046 |
+
if cart:
|
| 1047 |
+
for item in cart:
|
| 1048 |
+
seller = item['uploader']
|
| 1049 |
+
if seller not in data['orders']:
|
| 1050 |
+
data['orders'][seller] = []
|
| 1051 |
+
order_id = f"order_{len(data['orders'].get(seller, [])) + 1}_{int(time.time())}"
|
| 1052 |
+
data['orders'][seller].append({
|
| 1053 |
+
'order_id': order_id,
|
| 1054 |
+
'buyer': username,
|
| 1055 |
+
'post_id': item['postId'],
|
| 1056 |
+
'title': item['title'],
|
| 1057 |
+
'price': item['price'],
|
| 1058 |
+
'currency': item['currency'],
|
| 1059 |
+
'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
| 1060 |
+
'status': 'pending'
|
| 1061 |
+
})
|
| 1062 |
+
save_data(data)
|
| 1063 |
+
flash('Заказ успешно оформлен!', 'success')
|
| 1064 |
+
return redirect(url_for('profile'))
|
| 1065 |
|
| 1066 |
html = '''
|
| 1067 |
<!DOCTYPE html>
|
|
|
|
| 1109 |
font-size: 1.2em;
|
| 1110 |
margin-bottom: 15px;
|
| 1111 |
}
|
| 1112 |
+
.post-grid, .cart-grid {
|
| 1113 |
display: grid;
|
| 1114 |
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
| 1115 |
gap: 30px;
|
| 1116 |
}
|
| 1117 |
+
.post-item, .cart-item {
|
| 1118 |
background: var(--card-bg-light);
|
| 1119 |
backdrop-filter: blur(20px);
|
| 1120 |
padding: 25px;
|
|
|
|
| 1122 |
box-shadow: var(--shadow);
|
| 1123 |
transition: var(--transition);
|
| 1124 |
}
|
| 1125 |
+
body.dark .post-item, body.dark .cart-item {
|
| 1126 |
background: var(--card-bg-dark);
|
| 1127 |
}
|
| 1128 |
+
.post-item:hover, .cart-item:hover {
|
| 1129 |
transform: translateY(-15px);
|
| 1130 |
}
|
| 1131 |
.post-preview {
|
|
|
|
| 1135 |
border-radius: 15px;
|
| 1136 |
margin-bottom: 20px;
|
| 1137 |
}
|
| 1138 |
+
.post-item h3, .cart-item h3 {
|
| 1139 |
font-size: 1.5em;
|
| 1140 |
margin-bottom: 15px;
|
| 1141 |
}
|
|
|
|
| 1162 |
font-weight: 600;
|
| 1163 |
color: var(--primary);
|
| 1164 |
}
|
| 1165 |
+
.remove-cart-btn {
|
| 1166 |
+
background: #ef4444;
|
| 1167 |
+
}
|
| 1168 |
+
.remove-cart-btn:hover {
|
| 1169 |
+
background: #dc2626;
|
| 1170 |
+
}
|
| 1171 |
</style>
|
| 1172 |
</head>
|
| 1173 |
<body>
|
|
|
|
| 1200 |
{% if user_type == 'seller' and verified %}
|
| 1201 |
<a href="{{ url_for('upload') }}" class="btn">Добавить видео</a>
|
| 1202 |
{% endif %}
|
| 1203 |
+
<h2>Корзина</h2>
|
| 1204 |
+
<div class="cart-grid" id="cartGrid"></div>
|
| 1205 |
+
<button class="btn" onclick="checkout()" style="margin-top: 20px;">Оформить заказ</button>
|
| 1206 |
<h2>Ваши видео</h2>
|
| 1207 |
<div class="post-grid">
|
| 1208 |
{% if user_posts %}
|
|
|
|
| 1241 |
alert('Ссылка скопирована!');
|
| 1242 |
});
|
| 1243 |
}
|
| 1244 |
+
function renderCart() {
|
| 1245 |
+
const cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 1246 |
+
const cartGrid = document.getElementById('cartGrid');
|
| 1247 |
+
cartGrid.innerHTML = '';
|
| 1248 |
+
cart.forEach(item => {
|
| 1249 |
+
const div = document.createElement('div');
|
| 1250 |
+
div.className = 'cart-item';
|
| 1251 |
+
div.innerHTML = `
|
| 1252 |
+
<h3>${item.title}</h3>
|
| 1253 |
+
<p class="price">Цена: ${item.price} ${item.currency}</p>
|
| 1254 |
+
<p>Продавец: <a href="/profile/${item.uploader}" class="username-link">${item.uploader}</a></p>
|
| 1255 |
+
<button class="btn remove-cart-btn" onclick="removeFromCart('${item.postId}')">Удалить</button>
|
| 1256 |
+
`;
|
| 1257 |
+
cartGrid.appendChild(div);
|
| 1258 |
+
});
|
| 1259 |
+
}
|
| 1260 |
+
function removeFromCart(postId) {
|
| 1261 |
+
let cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 1262 |
+
cart = cart.filter(item => item.postId !== postId);
|
| 1263 |
+
localStorage.setItem('cart', JSON.stringify(cart));
|
| 1264 |
+
renderCart();
|
| 1265 |
+
}
|
| 1266 |
+
function checkout() {
|
| 1267 |
+
const cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 1268 |
+
if (cart.length === 0) {
|
| 1269 |
+
alert('Корзина пуста!');
|
| 1270 |
+
return;
|
| 1271 |
+
}
|
| 1272 |
+
const form = document.createElement('form');
|
| 1273 |
+
form.method = 'POST';
|
| 1274 |
+
form.action = '/profile';
|
| 1275 |
+
const input = document.createElement('input');
|
| 1276 |
+
input.type = 'hidden';
|
| 1277 |
+
input.name = 'checkout';
|
| 1278 |
+
input.value = 'true';
|
| 1279 |
+
const cartData = document.createElement('input');
|
| 1280 |
+
cartData.type = 'hidden';
|
| 1281 |
+
cartData.name = 'cart_data';
|
| 1282 |
+
cartData.value = JSON.stringify(cart);
|
| 1283 |
+
form.appendChild(input);
|
| 1284 |
+
form.appendChild(cartData);
|
| 1285 |
+
document.body.appendChild(form);
|
| 1286 |
+
form.submit();
|
| 1287 |
+
localStorage.removeItem('cart');
|
| 1288 |
+
}
|
| 1289 |
window.onload = () => {
|
| 1290 |
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
|
| 1291 |
+
renderCart();
|
| 1292 |
const videos = document.querySelectorAll('.post-preview');
|
| 1293 |
videos.forEach(video => {
|
| 1294 |
video.addEventListener('loadedmetadata', () => {
|
|
|
|
| 1311 |
|
| 1312 |
user_posts = sorted([p for p in data['posts'] if p['uploader'] == username], key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
|
| 1313 |
is_authenticated = 'username' in session
|
| 1314 |
+
current_user = session.get('username')
|
| 1315 |
total_views = sum(post.get('views', 0) for post in user_posts)
|
| 1316 |
total_likes = sum(len(post.get('likes', [])) for post in user_posts)
|
| 1317 |
user_data = data['users'].get(username, {})
|
|
|
|
| 1556 |
|
| 1557 |
is_authenticated = 'username' in session
|
| 1558 |
username = session.get('username', None)
|
| 1559 |
+
user_type = data['users'].get(username, {}).get('type')
|
| 1560 |
+
verified = data['users'].get(username, {}).get('verified', False)
|
| 1561 |
html = '''
|
| 1562 |
<!DOCTYPE html>
|
| 1563 |
<html lang="ru">
|
|
|
|
| 1687 |
</body>
|
| 1688 |
</html>
|
| 1689 |
'''
|
| 1690 |
+
return render_template_string(html, username=username, is_authenticated=is_authenticated, user_type=user_type, verified=verified)
|
| 1691 |
+
|
| 1692 |
+
# Заказы продавца
|
| 1693 |
+
@app.route('/orders', methods=['GET', 'POST'])
|
| 1694 |
+
def orders():
|
| 1695 |
+
if 'username' not in session:
|
| 1696 |
+
flash('Войдите, чтобы просмотреть заказы!', 'error')
|
| 1697 |
+
return redirect(url_for('login'))
|
| 1698 |
+
|
| 1699 |
+
data = load_data()
|
| 1700 |
+
username = session['username']
|
| 1701 |
+
user_data = data['users'].get(username, {})
|
| 1702 |
+
if user_data.get('type') != 'seller' or not user_data.get('verified'):
|
| 1703 |
+
flash('Только проверенные продавцы могут просматривать заказы!', 'error')
|
| 1704 |
+
return redirect(url_for('profile'))
|
| 1705 |
+
|
| 1706 |
+
orders = data['orders'].get(username, [])
|
| 1707 |
+
is_authenticated = 'username' in session
|
| 1708 |
+
user_type = user_data.get('type')
|
| 1709 |
+
verified = user_data.get('verified', False)
|
| 1710 |
+
|
| 1711 |
+
if request.method == 'POST':
|
| 1712 |
+
if 'update_status' in request.form:
|
| 1713 |
+
order_id = request.form.get('order_id')
|
| 1714 |
+
new_status = request.form.get('status')
|
| 1715 |
+
for order in orders:
|
| 1716 |
+
if order['order_id'] == order_id:
|
| 1717 |
+
order['status'] = new_status
|
| 1718 |
+
break
|
| 1719 |
+
save_data(data)
|
| 1720 |
+
return redirect(url_for('orders'))
|
| 1721 |
+
|
| 1722 |
+
html = '''
|
| 1723 |
+
<!DOCTYPE html>
|
| 1724 |
+
<html lang="ru">
|
| 1725 |
+
<head>
|
| 1726 |
+
<meta charset="UTF-8">
|
| 1727 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 1728 |
+
<title>Заказы - {{ username }}</title>
|
| 1729 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet">
|
| 1730 |
+
<style>
|
| 1731 |
+
''' + BASE_STYLE + '''
|
| 1732 |
+
.container {
|
| 1733 |
+
max-width: 950px;
|
| 1734 |
+
}
|
| 1735 |
+
h1 {
|
| 1736 |
+
font-size: 2.5em;
|
| 1737 |
+
font-weight: 700;
|
| 1738 |
+
margin-bottom: 40px;
|
| 1739 |
+
background: linear-gradient(45deg, var(--primary), var(--secondary));
|
| 1740 |
+
-webkit-background-clip: text;
|
| 1741 |
+
color: transparent;
|
| 1742 |
+
}
|
| 1743 |
+
.order-grid {
|
| 1744 |
+
display: grid;
|
| 1745 |
+
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
| 1746 |
+
gap: 30px;
|
| 1747 |
+
}
|
| 1748 |
+
.order-item {
|
| 1749 |
+
background: var(--card-bg-light);
|
| 1750 |
+
backdrop-filter: blur(20px);
|
| 1751 |
+
padding: 25px;
|
| 1752 |
+
border-radius: 20px;
|
| 1753 |
+
box-shadow: var(--shadow);
|
| 1754 |
+
transition: var(--transition);
|
| 1755 |
+
}
|
| 1756 |
+
body.dark .order-item {
|
| 1757 |
+
background: var(--card-bg-dark);
|
| 1758 |
+
}
|
| 1759 |
+
.order-item:hover {
|
| 1760 |
+
transform: translateY(-15px);
|
| 1761 |
+
}
|
| 1762 |
+
.order-item h3 {
|
| 1763 |
+
font-size: 1.5em;
|
| 1764 |
+
margin-bottom: 15px;
|
| 1765 |
+
}
|
| 1766 |
+
.order-item p {
|
| 1767 |
+
margin-bottom: 15px;
|
| 1768 |
+
font-size: 1.1em;
|
| 1769 |
+
}
|
| 1770 |
+
.price {
|
| 1771 |
+
font-weight: 600;
|
| 1772 |
+
color: var(--primary);
|
| 1773 |
+
}
|
| 1774 |
+
.username-link {
|
| 1775 |
+
color: var(--primary);
|
| 1776 |
+
font-weight: 600;
|
| 1777 |
+
text-decoration: none;
|
| 1778 |
+
}
|
| 1779 |
+
.username-link:hover {
|
| 1780 |
+
text-decoration: underline;
|
| 1781 |
+
}
|
| 1782 |
+
select {
|
| 1783 |
+
width: auto;
|
| 1784 |
+
display: inline-block;
|
| 1785 |
+
}
|
| 1786 |
+
</style>
|
| 1787 |
+
</head>
|
| 1788 |
+
<body>
|
| 1789 |
+
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
|
| 1790 |
+
''' + NAV_HTML + '''
|
| 1791 |
+
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
|
| 1792 |
+
<div class="container">
|
| 1793 |
+
<h1>Ваши заказы</h1>
|
| 1794 |
+
<div class="order-grid">
|
| 1795 |
+
{% if orders %}
|
| 1796 |
+
{% for order in orders %}
|
| 1797 |
+
<div class="order-item">
|
| 1798 |
+
<h3>{{ order['title'] }}</h3>
|
| 1799 |
+
<p class="price">Цена: {{ order['price'] }} {{ order['currency'] }}</p>
|
| 1800 |
+
<p>Покупатель: <a href="{{ url_for('user_profile', username=order['buyer']) }}" class="username-link">{{ order['buyer'] }}</a></p>
|
| 1801 |
+
<p>Дата: {{ order['date'] }}</p>
|
| 1802 |
+
<form method="POST">
|
| 1803 |
+
<input type="hidden" name="order_id" value="{{ order['order_id'] }}">
|
| 1804 |
+
<select name="status" onchange="this.form.submit()">
|
| 1805 |
+
<option value="pending" {% if order['status'] == 'pending' %}selected{% endif %}>В ожидании</option>
|
| 1806 |
+
<option value="processing" {% if order['status'] == 'processing' %}selected{% endif %}>В обработке</option>
|
| 1807 |
+
<option value="completed" {% if order['status'] == 'completed' %}selected{% endif %}>Завершено</option>
|
| 1808 |
+
</select>
|
| 1809 |
+
<input type="hidden" name="update_status" value="true">
|
| 1810 |
+
</form>
|
| 1811 |
+
</div>
|
| 1812 |
+
{% endfor %}
|
| 1813 |
+
{% else %}
|
| 1814 |
+
<p style="font-size: 1.2em;">У вас пока нет заказов.</p>
|
| 1815 |
+
{% endif %}
|
| 1816 |
+
</div>
|
| 1817 |
+
</div>
|
| 1818 |
+
<script>
|
| 1819 |
+
function toggleSidebar() {
|
| 1820 |
+
document.getElementById('sidebar').classList.toggle('active');
|
| 1821 |
+
}
|
| 1822 |
+
function toggleTheme() {
|
| 1823 |
+
document.body.classList.toggle('dark');
|
| 1824 |
+
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
|
| 1825 |
+
}
|
| 1826 |
+
window.onload = () => {
|
| 1827 |
+
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
|
| 1828 |
+
};
|
| 1829 |
+
</script>
|
| 1830 |
+
</body>
|
| 1831 |
+
</html>
|
| 1832 |
+
'''
|
| 1833 |
+
return render_template_string(html, username=username, orders=orders, is_authenticated=is_authenticated, user_type=user_type, verified=verified)
|
| 1834 |
|
| 1835 |
# Админ-панель
|
| 1836 |
@app.route('/admhosto', methods=['GET', 'POST'])
|
|
|
|
| 1840 |
pending_orgs = data.get('pending_organizations', [])
|
| 1841 |
is_authenticated = 'username' in session
|
| 1842 |
username = session.get('username', None)
|
| 1843 |
+
user_type = data['users'].get(username, {}).get('type') if username else None
|
| 1844 |
+
verified = data['users'].get(username, {}).get('verified', False) if username else False
|
| 1845 |
|
| 1846 |
if request.method == 'POST':
|
| 1847 |
if 'delete' in request.form:
|
|
|
|
| 1869 |
posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()]
|
| 1870 |
|
| 1871 |
html = '''
|
| 1872 |
+
<!DOCTYPE html
|
| 1873 |
<html lang="ru">
|
| 1874 |
<head>
|
| 1875 |
<meta charset="UTF-8">
|
|
|
|
| 2063 |
</body>
|
| 2064 |
</html>
|
| 2065 |
'''
|
| 2066 |
+
return render_template_string(html, posts=posts, pending_orgs=pending_orgs, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query, user_type=user_type, verified=verified)
|
| 2067 |
|
| 2068 |
if __name__ == '__main__':
|
| 2069 |
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|