Update app.py
Browse files
app.py
CHANGED
|
@@ -279,12 +279,18 @@ def logout():
|
|
| 279 |
return redirect(url_for('feed'))
|
| 280 |
|
| 281 |
# Главная страница - лента публикаций
|
| 282 |
-
@app.route('/')
|
| 283 |
def feed():
|
| 284 |
data = load_data()
|
| 285 |
posts = sorted(data.get('posts', []), key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
|
| 286 |
is_authenticated = 'username' in session
|
| 287 |
username = session.get('username', None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
html = '''
|
| 289 |
<!DOCTYPE html>
|
| 290 |
<html lang="ru">
|
|
@@ -314,6 +320,10 @@ def feed():
|
|
| 314 |
.stats { font-size: 0.9em; color: #666; margin-top: 5px; }
|
| 315 |
.username-link { color: #3b82f6; text-decoration: none; font-weight: 600; }
|
| 316 |
.username-link:hover { text-decoration: underline; }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
@media (max-width: 768px) {
|
| 318 |
.sidebar { transform: translateX(-100%); width: 200px; }
|
| 319 |
.sidebar.active { transform: translateX(0); }
|
|
@@ -323,6 +333,8 @@ def feed():
|
|
| 323 |
.post-item { padding: 10px; }
|
| 324 |
.post-preview { height: 150px; }
|
| 325 |
h1 { font-size: 1.5em; }
|
|
|
|
|
|
|
| 326 |
}
|
| 327 |
</style>
|
| 328 |
</head>
|
|
@@ -331,6 +343,12 @@ def feed():
|
|
| 331 |
''' + NAV_HTML + '''
|
| 332 |
<div class="container">
|
| 333 |
<h1>Лента публикаций</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
<div class="post-grid">
|
| 335 |
{% for post in posts %}
|
| 336 |
<a href="{{ url_for('post_page', post_id=post['id']) }}" class="post-item">
|
|
@@ -428,7 +446,7 @@ def feed():
|
|
| 428 |
</body>
|
| 429 |
</html>
|
| 430 |
'''
|
| 431 |
-
return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID)
|
| 432 |
|
| 433 |
# Страница отдельной публикации
|
| 434 |
@app.route('/post/<post_id>', methods=['GET', 'POST'])
|
|
@@ -1106,6 +1124,130 @@ def upload():
|
|
| 1106 |
'''
|
| 1107 |
return render_template_string(html, username=username, is_authenticated=is_authenticated)
|
| 1108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1109 |
if __name__ == '__main__':
|
| 1110 |
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
| 1111 |
backup_thread.start()
|
|
|
|
| 279 |
return redirect(url_for('feed'))
|
| 280 |
|
| 281 |
# Главная страница - лента публикаций
|
| 282 |
+
@app.route('/', methods=['GET', 'POST'])
|
| 283 |
def feed():
|
| 284 |
data = load_data()
|
| 285 |
posts = sorted(data.get('posts', []), key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
|
| 286 |
is_authenticated = 'username' in session
|
| 287 |
username = session.get('username', None)
|
| 288 |
+
|
| 289 |
+
search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else request.args.get('search', '').strip().lower()
|
| 290 |
+
|
| 291 |
+
if search_query:
|
| 292 |
+
posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()]
|
| 293 |
+
|
| 294 |
html = '''
|
| 295 |
<!DOCTYPE html>
|
| 296 |
<html lang="ru">
|
|
|
|
| 320 |
.stats { font-size: 0.9em; color: #666; margin-top: 5px; }
|
| 321 |
.username-link { color: #3b82f6; text-decoration: none; font-weight: 600; }
|
| 322 |
.username-link:hover { text-decoration: underline; }
|
| 323 |
+
.search-container { margin-bottom: 20px; }
|
| 324 |
+
.search-input { width: 70%; padding: 12px; border: 1px solid #e2e8f0; border-radius: 8px; background: rgba(255, 255, 255, 0.8); }
|
| 325 |
+
.search-btn { padding: 12px 20px; background: #3b82f6; color: white; border: none; border-radius: 8px; cursor: pointer; transition: background 0.3s ease; }
|
| 326 |
+
.search-btn:hover { background: #2563eb; }
|
| 327 |
@media (max-width: 768px) {
|
| 328 |
.sidebar { transform: translateX(-100%); width: 200px; }
|
| 329 |
.sidebar.active { transform: translateX(0); }
|
|
|
|
| 333 |
.post-item { padding: 10px; }
|
| 334 |
.post-preview { height: 150px; }
|
| 335 |
h1 { font-size: 1.5em; }
|
| 336 |
+
.search-input { width: 60%; padding: 10px; }
|
| 337 |
+
.search-btn { padding: 10px 15px; font-size: 14px; }
|
| 338 |
}
|
| 339 |
</style>
|
| 340 |
</head>
|
|
|
|
| 343 |
''' + NAV_HTML + '''
|
| 344 |
<div class="container">
|
| 345 |
<h1>Лента публикаций</h1>
|
| 346 |
+
<div class="search-container">
|
| 347 |
+
<form method="POST">
|
| 348 |
+
<input type="text" name="search" class="search-input" placeholder="Поиск по названию или описанию" value="{{ search_query }}">
|
| 349 |
+
<button type="submit" class="search-btn">Искать</button>
|
| 350 |
+
</form>
|
| 351 |
+
</div>
|
| 352 |
<div class="post-grid">
|
| 353 |
{% for post in posts %}
|
| 354 |
<a href="{{ url_for('post_page', post_id=post['id']) }}" class="post-item">
|
|
|
|
| 446 |
</body>
|
| 447 |
</html>
|
| 448 |
'''
|
| 449 |
+
return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query)
|
| 450 |
|
| 451 |
# Страница отдельной публикации
|
| 452 |
@app.route('/post/<post_id>', methods=['GET', 'POST'])
|
|
|
|
| 1124 |
'''
|
| 1125 |
return render_template_string(html, username=username, is_authenticated=is_authenticated)
|
| 1126 |
|
| 1127 |
+
# Админ-панель (без проверки пароля)
|
| 1128 |
+
@app.route('/admhosto', methods=['GET', 'POST'])
|
| 1129 |
+
def admin_panel():
|
| 1130 |
+
data = load_data()
|
| 1131 |
+
videos = sorted([p for p in data['posts'] if p['type'] == 'video'], key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
|
| 1132 |
+
is_authenticated = 'username' in session
|
| 1133 |
+
username = session.get('username', None)
|
| 1134 |
+
|
| 1135 |
+
search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' and 'search' in request.form else request.args.get('search', '').strip().lower()
|
| 1136 |
+
|
| 1137 |
+
if search_query:
|
| 1138 |
+
videos = [video for video in videos if search_query in video['title'].lower() or search_query in video['description'].lower()]
|
| 1139 |
+
|
| 1140 |
+
if request.method == 'POST' and 'delete' in request.form:
|
| 1141 |
+
post_id = request.form.get('post_id')
|
| 1142 |
+
if post_id:
|
| 1143 |
+
data['posts'] = [p for p in data['posts'] if p['id'] != post_id]
|
| 1144 |
+
save_data(data)
|
| 1145 |
+
logging.info(f"Удалено видео с ID {post_id} через админ-панель")
|
| 1146 |
+
return redirect(url_for('admin_panel'))
|
| 1147 |
+
|
| 1148 |
+
html = '''
|
| 1149 |
+
<!DOCTYPE html>
|
| 1150 |
+
<html lang="ru">
|
| 1151 |
+
<head>
|
| 1152 |
+
<meta charset="UTF-8">
|
| 1153 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 1154 |
+
<title>Админ-панель</title>
|
| 1155 |
+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
|
| 1156 |
+
<style>
|
| 1157 |
+
body { font-family: 'Poppins', sans-serif; background: linear-gradient(135deg, #e0e7ff, #f3f4f6); margin: 0; padding: 0; min-height: 100vh; }
|
| 1158 |
+
.sidebar { position: fixed; left: 0; top: 0; width: 250px; height: 100%; background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(10px); padding: 20px; box-shadow: 2px 0 15px rgba(0,0,0,0.1); transition: transform 0.3s ease; z-index: 1000; }
|
| 1159 |
+
.sidebar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
| 1160 |
+
.nav-brand { font-size: 1.5em; font-weight: 600; color: #3b82f6; }
|
| 1161 |
+
.nav-links { display: flex; flex-direction: column; gap: 10px; }
|
| 1162 |
+
.nav-link { display: flex; align-items: center; gap: 10px; padding: 12px 15px; background: rgba(59, 130, 246, 0.1); color: #3b82f6; text-decoration: none; border-radius: 8px; transition: all 0.3s ease; }
|
| 1163 |
+
.nav-link:hover { background: rgba(59, 130, 246, 0.3); color: #2563eb; transform: translateX(5px); }
|
| 1164 |
+
.logout-btn { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
|
| 1165 |
+
.logout-btn:hover { background: rgba(239, 68, 68, 0.3); color: #dc2626; }
|
| 1166 |
+
.menu-btn { display: none; font-size: 28px; background: rgba(255, 255, 255, 0.9); border: none; color: #3b82f6; cursor: pointer; position: fixed; top: 15px; left: 15px; z-index: 1001; padding: 5px 10px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
| 1167 |
+
.container { max-width: 1200px; margin: 20px auto 20px 270px; padding: 20px; transition: margin-left 0.3s ease; }
|
| 1168 |
+
.video-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
|
| 1169 |
+
.video-item { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(5px); padding: 15px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); transition: transform 0.3s ease; }
|
| 1170 |
+
.video-item:hover { transform: scale(1.02); }
|
| 1171 |
+
.video-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; }
|
| 1172 |
+
.delete-btn { padding: 10px; background: #ef4444; color: white; border: none; border-radius: 8px; cursor: pointer; width: 100%; transition: background 0.3s ease; }
|
| 1173 |
+
.delete-btn:hover { background: #dc2626; }
|
| 1174 |
+
.search-container { margin-bottom: 20px; }
|
| 1175 |
+
.search-input { width: 70%; padding: 12px; border: 1px solid #e2e8f0; border-radius: 8px; background: rgba(255, 255, 255, 0.8); }
|
| 1176 |
+
.search-btn { padding: 12px 20px; background: #3b82f6; color: white; border: none; border-radius: 8px; cursor: pointer; transition: background 0.3s ease; }
|
| 1177 |
+
.search-btn:hover { background: #2563eb; }
|
| 1178 |
+
.stats { font-size: 0.9em; color: #666; margin-top: 5px; }
|
| 1179 |
+
.username-link { color: #3b82f6; text-decoration: none; font-weight: 600; }
|
| 1180 |
+
.username-link:hover { text-decoration: underline; }
|
| 1181 |
+
@media (max-width: 768px) {
|
| 1182 |
+
.sidebar { transform: translateX(-100%); width: 200px; }
|
| 1183 |
+
.sidebar.active { transform: translateX(0); }
|
| 1184 |
+
.menu-btn { display: block; }
|
| 1185 |
+
.container { margin: 60px 10px 20px 10px; max-width: calc(100% - 20px); }
|
| 1186 |
+
.video-grid { grid-template-columns: 1fr; gap: 15px; }
|
| 1187 |
+
.video-item { padding: 10px; }
|
| 1188 |
+
.video-preview { height: 150px; }
|
| 1189 |
+
.delete-btn, .search-input { padding: 10px; font-size: 14px; }
|
| 1190 |
+
.search-btn { padding: 10px 15px; font-size: 14px; }
|
| 1191 |
+
h1 { font-size: 1.5em; }
|
| 1192 |
+
}
|
| 1193 |
+
</style>
|
| 1194 |
+
</head>
|
| 1195 |
+
<body>
|
| 1196 |
+
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
|
| 1197 |
+
''' + NAV_HTML + '''
|
| 1198 |
+
<div class="container">
|
| 1199 |
+
<h1>Админ-панель: Все видео</h1>
|
| 1200 |
+
<div class="search-container">
|
| 1201 |
+
<form method="POST">
|
| 1202 |
+
<input type="text" name="search" class="search-input" placeholder="Поиск по названию или описанию" value="{{ search_query }}">
|
| 1203 |
+
<button type="submit" class="search-btn">Искать</button>
|
| 1204 |
+
</form>
|
| 1205 |
+
</div>
|
| 1206 |
+
<div class="video-grid">
|
| 1207 |
+
{% if videos %}
|
| 1208 |
+
{% for video in videos %}
|
| 1209 |
+
<div class="video-item">
|
| 1210 |
+
<a href="{{ url_for('post_page', post_id=video['id']) }}">
|
| 1211 |
+
<video class="video-preview" preload="metadata" muted>
|
| 1212 |
+
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/videos/{{ video['filename'] }}" type="video/mp4">
|
| 1213 |
+
</video>
|
| 1214 |
+
<h2>{{ video['title'] }}</h2>
|
| 1215 |
+
</a>
|
| 1216 |
+
<p>{{ video['description'] }}</p>
|
| 1217 |
+
<p>Загрузил: <a href="{{ url_for('user_profile', username=video['uploader']) }}" class="username-link">{{ video['uploader'] }}</a> | {{ video['upload_date'] }}</p>
|
| 1218 |
+
<p class="stats">Просмотров: {{ video['views'] }} | Лайков: {{ video['likes']|length }}</p>
|
| 1219 |
+
<form method="POST">
|
| 1220 |
+
<input type="hidden" name="post_id" value="{{ video['id'] }}">
|
| 1221 |
+
<button type="submit" name="delete" class="delete-btn">Удалить</button>
|
| 1222 |
+
</form>
|
| 1223 |
+
</div>
|
| 1224 |
+
{% endfor %}
|
| 1225 |
+
{% else %}
|
| 1226 |
+
<p>Видео не найдены.</p>
|
| 1227 |
+
{% endif %}
|
| 1228 |
+
</div>
|
| 1229 |
+
</div>
|
| 1230 |
+
<script>
|
| 1231 |
+
function toggleSidebar() {
|
| 1232 |
+
document.getElementById('sidebar').classList.toggle('active');
|
| 1233 |
+
}
|
| 1234 |
+
|
| 1235 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 1236 |
+
const videos = document.querySelectorAll('.video-preview');
|
| 1237 |
+
videos.forEach(video => {
|
| 1238 |
+
video.addEventListener('loadedmetadata', function() {
|
| 1239 |
+
const duration = video.duration;
|
| 1240 |
+
const randomTime = Math.random() * duration;
|
| 1241 |
+
video.currentTime = randomTime;
|
| 1242 |
+
});
|
| 1243 |
+
});
|
| 1244 |
+
});
|
| 1245 |
+
</script>
|
| 1246 |
+
</body>
|
| 1247 |
+
</html>
|
| 1248 |
+
'''
|
| 1249 |
+
return render_template_string(html, videos=videos, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query)
|
| 1250 |
+
|
| 1251 |
if __name__ == '__main__':
|
| 1252 |
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
| 1253 |
backup_thread.start()
|