Eluza133 commited on
Commit
29dfb23
·
verified ·
1 Parent(s): ceccd92

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -51
app.py CHANGED
@@ -117,7 +117,7 @@ def register():
117
  flash('Пользователь уже существует!')
118
  return redirect(url_for('register'))
119
 
120
- data['users'][username] = {'password': password}
121
  save_data(data)
122
  logging.info(f"Пользователь {username} зарегистрирован")
123
  flash('Регистрация успешна! Войдите в систему.')
@@ -209,7 +209,7 @@ def login():
209
  username = session.get('username', None)
210
  html = '''
211
  <!DOCTYPE html>
212
- <html lang="ru">
213
  <head>
214
  <meta charset="UTF-8">
215
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -478,7 +478,7 @@ def post_page(post_id):
478
 
479
  html = '''
480
  <!DOCTYPE html>
481
- <html lang="ru">
482
  <head>
483
  <meta charset="UTF-8">
484
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -636,18 +636,56 @@ def profile():
636
  username = session['username']
637
  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)
638
  is_authenticated = 'username' in session
 
 
 
 
 
 
 
 
 
 
639
 
640
  if request.method == 'POST':
641
- post_id = request.form.get('post_id')
642
- if post_id:
643
- data['posts'] = [p for p in data['posts'] if p['id'] != post_id or p['uploader'] != username]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
  save_data(data)
645
- logging.info(f"Публикация {post_id} удалена пользователем {username}")
646
  return redirect(url_for('profile'))
647
 
648
  html = '''
649
  <!DOCTYPE html>
650
- <html lang="ru">
651
  <head>
652
  <meta charset="UTF-8">
653
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -669,13 +707,19 @@ def profile():
669
  .post-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; }
670
  .post-item:hover { transform: scale(1.02); }
671
  .post-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; cursor: pointer; }
672
- .btn { display: block; margin: 10px 0; padding: 12px 20px; background: rgba(59, 130, 246, 0.9); color: white; text-decoration: none; border-radius: 8px; border: none; cursor: pointer; transition: all 0.3s ease; }
673
  .btn:hover { background: rgba(59, 130, 246, 1); transform: scale(1.05); }
674
  .delete-btn { background: rgba(239, 68, 68, 0.9); }
675
  .delete-btn:hover { background: rgba(239, 68, 68, 1); }
676
  .stats { font-size: 0.9em; color: #666; margin-top: 5px; }
677
  .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 2000; justify-content: center; align-items: center; }
678
  .modal img { max-width: 90%; max-height: 90%; object-fit: contain; transform: scale(1); transition: transform 0.2s ease; }
 
 
 
 
 
 
679
  @media (max-width: 768px) {
680
  .sidebar { transform: translateX(-100%); width: 200px; }
681
  .sidebar.active { transform: translateX(0); }
@@ -686,6 +730,7 @@ def profile():
686
  .post-preview { height: 150px; }
687
  .btn { font-size: 14px; padding: 10px; }
688
  h1, h2 { font-size: 1.5em; }
 
689
  }
690
  </style>
691
  </head>
@@ -694,6 +739,24 @@ def profile():
694
  ''' + NAV_HTML + '''
695
  <div class="container">
696
  <h1>Профиль: {{ username }}</h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  <a href="{{ url_for('upload') }}" class="btn">Добавить публикацию</a>
698
  <h2>Ваши публикации</h2>
699
  <div class="post-grid">
@@ -715,7 +778,7 @@ def profile():
715
  <p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
716
  <form method="POST">
717
  <input type="hidden" name="post_id" value="{{ post['id'] }}">
718
- <button type="submit" class="btn delete-btn">Удалить</button>
719
  </form>
720
  </div>
721
  {% endfor %}
@@ -748,6 +811,13 @@ def profile():
748
  }
749
  }
750
 
 
 
 
 
 
 
 
751
  document.addEventListener('DOMContentLoaded', function() {
752
  const videos = document.querySelectorAll('.post-preview');
753
  videos.forEach(video => {
@@ -803,7 +873,7 @@ def profile():
803
  </body>
804
  </html>
805
  '''
806
- return render_template_string(html, username=username, user_posts=user_posts, repo_id=REPO_ID, is_authenticated=is_authenticated)
807
 
808
  # Страница профиля другого пользователя
809
  @app.route('/profile/<username>')
@@ -815,10 +885,20 @@ def user_profile(username):
815
  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)
816
  is_authenticated = 'username' in session
817
  current_user = session.get('username', None)
 
 
 
 
 
 
 
 
 
 
818
 
819
  html = '''
820
  <!DOCTYPE html>
821
- <html lang="ru">
822
  <head>
823
  <meta charset="UTF-8">
824
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -840,13 +920,17 @@ def user_profile(username):
840
  .post-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; }
841
  .post-item:hover { transform: scale(1.02); }
842
  .post-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; cursor: pointer; }
843
- .btn { display: block; margin: 10px 0; padding: 12px 20px; background: rgba(59, 130, 246, 0.9); color: white; text-decoration: none; border-radius: 8px; border: none; cursor: pointer; transition: all 0.3s ease; }
844
  .btn:hover { background: rgba(59, 130, 246, 1); transform: scale(1.05); }
845
  .stats { font-size: 0.9em; color: #666; margin-top: 5px; }
846
  .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 2000; justify-content: center; align-items: center; }
847
  .modal img { max-width: 90%; max-height: 90%; object-fit: contain; transform: scale(1); transition: transform 0.2s ease; }
848
  .username-link { color: #3b82f6; text-decoration: none; font-weight: 600; }
849
  .username-link:hover { text-decoration: underline; }
 
 
 
 
850
  @media (max-width: 768px) {
851
  .sidebar { transform: translateX(-100%); width: 200px; }
852
  .sidebar.active { transform: translateX(0); }
@@ -857,6 +941,7 @@ def user_profile(username):
857
  .post-preview { height: 150px; }
858
  .btn { font-size: 14px; padding: 10px; }
859
  h1, h2 { font-size: 1.5em; }
 
860
  }
861
  </style>
862
  </head>
@@ -865,6 +950,17 @@ def user_profile(username):
865
  ''' + NAV_HTML + '''
866
  <div class="container">
867
  <h1>Профиль: {{ username }}</h1>
 
 
 
 
 
 
 
 
 
 
 
868
  <h2>Публикации</h2>
869
  <div class="post-grid">
870
  {% if user_posts %}
@@ -914,6 +1010,13 @@ def user_profile(username):
914
  }
915
  }
916
 
 
 
 
 
 
 
 
917
  document.addEventListener('DOMContentLoaded', function() {
918
  const videos = document.querySelectorAll('.post-preview');
919
  videos.forEach(video => {
@@ -969,7 +1072,7 @@ def user_profile(username):
969
  </body>
970
  </html>
971
  '''
972
- return render_template_string(html, username=username, user_posts=user_posts, repo_id=REPO_ID, is_authenticated=is_authenticated, current_user=current_user)
973
 
974
  # Страница загрузки контента
975
  @app.route('/upload', methods=['GET', 'POST'])
@@ -1035,7 +1138,7 @@ def upload():
1035
  username = session.get('username', None)
1036
  html = '''
1037
  <!DOCTYPE html>
1038
- <html lang="ru">
1039
  <head>
1040
  <meta charset="UTF-8">
1041
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -1047,7 +1150,7 @@ def upload():
1047
  .sidebar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
1048
  .nav-brand { font-size: 1.5em; font-weight: 600; color: #3b82f6; }
1049
  .nav-links { display: flex; flex-direction: column; gap: 10px; }
1050
- .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; }
1051
  .nav-link:hover { background: rgba(59, 130, 246, 0.3); color: #2563eb; transform: translateX(5px); }
1052
  .logout-btn { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
1053
  .logout-btn:hover { background: rgba(239, 68, 68, 0.3); color: #dc2626; }
@@ -1124,37 +1227,37 @@ def upload():
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; }
@@ -1165,10 +1268,10 @@ def admin_panel():
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; }
@@ -1183,9 +1286,9 @@ def admin_panel():
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; }
@@ -1196,34 +1299,38 @@ def admin_panel():
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>
@@ -1233,20 +1340,22 @@ def admin_panel():
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)
 
117
  flash('Пользователь уже существует!')
118
  return redirect(url_for('register'))
119
 
120
+ data['users'][username] = {'password': password, 'bio': '', 'link': '', 'avatar': None}
121
  save_data(data)
122
  logging.info(f"Пользователь {username} зарегистрирован")
123
  flash('Регистрация успешна! Войдите в систему.')
 
209
  username = session.get('username', None)
210
  html = '''
211
  <!DOCTYPE html>
212
+ <html lang="ру">
213
  <head>
214
  <meta charset="UTF-8">
215
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
478
 
479
  html = '''
480
  <!DOCTYPE html>
481
+ <html lang="ру">
482
  <head>
483
  <meta charset="UTF-8">
484
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
636
  username = session['username']
637
  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)
638
  is_authenticated = 'username' in session
639
+
640
+ # Подсчет общего количества просмотров и лайков
641
+ total_views = sum(post.get('views', 0) for post in user_posts)
642
+ total_likes = sum(len(post.get('likes', [])) for post in user_posts)
643
+
644
+ # Получение текущих данных пользователя
645
+ user_data = data['users'].get(username, {})
646
+ bio = user_data.get('bio', '')
647
+ link = user_data.get('link', '')
648
+ avatar = user_data.get('avatar', None)
649
 
650
  if request.method == 'POST':
651
+ if 'delete_post' in request.form:
652
+ post_id = request.form.get('post_id')
653
+ if post_id:
654
+ data['posts'] = [p for p in data['posts'] if p['id'] != post_id or p['uploader'] != username]
655
+ save_data(data)
656
+ logging.info(f"Публикация {post_id} удалена пользователем {username}")
657
+ return redirect(url_for('profile'))
658
+ elif 'update_profile' in request.form:
659
+ bio = request.form.get('bio', '')
660
+ link = request.form.get('link', '')
661
+ avatar_file = request.files.get('avatar')
662
+ if avatar_file and avatar_file.filename:
663
+ filename = secure_filename(avatar_file.filename)
664
+ temp_path = os.path.join('uploads', filename)
665
+ os.makedirs('uploads', exist_ok=True)
666
+ avatar_file.save(temp_path)
667
+ api = HfApi()
668
+ avatar_path = f"avatars/{username}/{filename}"
669
+ api.upload_file(
670
+ path_or_fileobj=temp_path,
671
+ path_in_repo=avatar_path,
672
+ repo_id=REPO_ID,
673
+ repo_type="dataset",
674
+ token=HF_TOKEN_WRITE,
675
+ commit_message=f"Добавлен аватар для {username}"
676
+ )
677
+ data['users'][username]['avatar'] = avatar_path
678
+ if os.path.exists(temp_path):
679
+ os.remove(temp_path)
680
+ data['users'][username]['bio'] = bio
681
+ data['users'][username]['link'] = link
682
  save_data(data)
683
+ logging.info(f"Профиль {username} обновлен")
684
  return redirect(url_for('profile'))
685
 
686
  html = '''
687
  <!DOCTYPE html>
688
+ <html lang="ру">
689
  <head>
690
  <meta charset="UTF-8">
691
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
707
  .post-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; }
708
  .post-item:hover { transform: scale(1.02); }
709
  .post-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; cursor: pointer; }
710
+ .btn { display: inline-block; margin: 10px 0; padding: 12px 20px; background: rgba(59, 130, 246, 0.9); color: white; text-decoration: none; border-radius: 8px; border: none; cursor: pointer; transition: all 0.3s ease; }
711
  .btn:hover { background: rgba(59, 130, 246, 1); transform: scale(1.05); }
712
  .delete-btn { background: rgba(239, 68, 68, 0.9); }
713
  .delete-btn:hover { background: rgba(239, 68, 68, 1); }
714
  .stats { font-size: 0.9em; color: #666; margin-top: 5px; }
715
  .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 2000; justify-content: center; align-items: center; }
716
  .modal img { max-width: 90%; max-height: 90%; object-fit: contain; transform: scale(1); transition: transform 0.2s ease; }
717
+ .avatar { width: 100px; height: 100px; border-radius: 50%; object-fit: cover; margin-bottom: 10px; }
718
+ .profile-info { margin-bottom: 20px; }
719
+ textarea { width: 100%; padding: 12px; margin: 10px 0; border: 1px solid #e2e8f0; border-radius: 8px; resize: vertical; background: rgba(255, 255, 255, 0.8); }
720
+ input[type="text"] { width: 100%; padding: 12px; margin: 10px 0; border: 1px solid #e2e8f0; border-radius: 8px; box-sizing: border-box; background: rgba(255, 255, 255, 0.8); }
721
+ .share-btn { background: rgba(16, 185, 129, 0.9); }
722
+ .share-btn:hover { background: rgba(16, 185, 129, 1); }
723
  @media (max-width: 768px) {
724
  .sidebar { transform: translateX(-100%); width: 200px; }
725
  .sidebar.active { transform: translateX(0); }
 
730
  .post-preview { height: 150px; }
731
  .btn { font-size: 14px; padding: 10px; }
732
  h1, h2 { font-size: 1.5em; }
733
+ .avatar { width: 80px; height: 80px; }
734
  }
735
  </style>
736
  </head>
 
739
  ''' + NAV_HTML + '''
740
  <div class="container">
741
  <h1>Профиль: {{ username }}</h1>
742
+ <div class="profile-info">
743
+ {% if avatar %}
744
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ avatar }}" alt="Аватар" class="avatar">
745
+ {% endif %}
746
+ <p>{{ bio }}</p>
747
+ {% if link %}
748
+ <p><a href="{{ link }}" target="_blank">{{ link }}</a></p>
749
+ {% endif %}
750
+ <p>Всего просмотров: {{ total_views }} | Всего лайков: {{ total_likes }}</p>
751
+ <button class="btn share-btn" onclick="copyProfileLink()">Поделиться профилем</button>
752
+ </div>
753
+ <h2>Редактировать профиль</h2>
754
+ <form method="POST" enctype="multipart/form-data">
755
+ <textarea name="bio" placeholder="Описание профиля" rows="3">{{ bio }}</textarea>
756
+ <input type="text" name="link" placeholder="Ссылка" value="{{ link }}">
757
+ <input type="file" name="avatar" accept="image/*">
758
+ <button type="submit" name="update_profile" class="btn">Сохранить</button>
759
+ </form>
760
  <a href="{{ url_for('upload') }}" class="btn">Добавить публикацию</a>
761
  <h2>Ваши публикации</h2>
762
  <div class="post-grid">
 
778
  <p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
779
  <form method="POST">
780
  <input type="hidden" name="post_id" value="{{ post['id'] }}">
781
+ <button type="submit" name="delete_post" class="btn delete-btn">Удалить</button>
782
  </form>
783
  </div>
784
  {% endfor %}
 
811
  }
812
  }
813
 
814
+ function copyProfileLink() {
815
+ const url = window.location.href;
816
+ navigator.clipboard.writeText(url).then(() => {
817
+ alert('Ссылка на профиль скопирована в буфер обмена!');
818
+ });
819
+ }
820
+
821
  document.addEventListener('DOMContentLoaded', function() {
822
  const videos = document.querySelectorAll('.post-preview');
823
  videos.forEach(video => {
 
873
  </body>
874
  </html>
875
  '''
876
+ return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated)
877
 
878
  # Страница профиля другого пользователя
879
  @app.route('/profile/<username>')
 
885
  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)
886
  is_authenticated = 'username' in session
887
  current_user = session.get('username', None)
888
+
889
+ # Подсчет общего количества просмотров и лайков
890
+ total_views = sum(post.get('views', 0) for post in user_posts)
891
+ total_likes = sum(len(post.get('likes', [])) for post in user_posts)
892
+
893
+ # Получение данных пользователя
894
+ user_data = data['users'].get(username, {})
895
+ bio = user_data.get('bio', '')
896
+ link = user_data.get('link', '')
897
+ avatar = user_data.get('avatar', None)
898
 
899
  html = '''
900
  <!DOCTYPE html>
901
+ <html lang="ру">
902
  <head>
903
  <meta charset="UTF-8">
904
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
920
  .post-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; }
921
  .post-item:hover { transform: scale(1.02); }
922
  .post-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; cursor: pointer; }
923
+ .btn { display: inline-block; margin: 10px 0; padding: 12px 20px; background: rgba(59, 130, 246, 0.9); color: white; text-decoration: none; border-radius: 8px; border: none; cursor: pointer; transition: all 0.3s ease; }
924
  .btn:hover { background: rgba(59, 130, 246, 1); transform: scale(1.05); }
925
  .stats { font-size: 0.9em; color: #666; margin-top: 5px; }
926
  .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 2000; justify-content: center; align-items: center; }
927
  .modal img { max-width: 90%; max-height: 90%; object-fit: contain; transform: scale(1); transition: transform 0.2s ease; }
928
  .username-link { color: #3b82f6; text-decoration: none; font-weight: 600; }
929
  .username-link:hover { text-decoration: underline; }
930
+ .avatar { width: 100px; height: 100px; border-radius: 50%; object-fit: cover; margin-bottom: 10px; }
931
+ .profile-info { margin-bottom: 20px; }
932
+ .share-btn { background: rgba(16, 185, 129, 0.9); }
933
+ .share-btn:hover { background: rgba(16, 185, 129, 1); }
934
  @media (max-width: 768px) {
935
  .sidebar { transform: translateX(-100%); width: 200px; }
936
  .sidebar.active { transform: translateX(0); }
 
941
  .post-preview { height: 150px; }
942
  .btn { font-size: 14px; padding: 10px; }
943
  h1, h2 { font-size: 1.5em; }
944
+ .avatar { width: 80px; height: 80px; }
945
  }
946
  </style>
947
  </head>
 
950
  ''' + NAV_HTML + '''
951
  <div class="container">
952
  <h1>Профиль: {{ username }}</h1>
953
+ <div class="profile-info">
954
+ {% if avatar %}
955
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ avatar }}" alt="Аватар" class="avatar">
956
+ {% endif %}
957
+ <p>{{ bio }}</p>
958
+ {% if link %}
959
+ <p><a href="{{ link }}" target="_blank">{{ link }}</a></p>
960
+ {% endif %}
961
+ <p>Всего просмотров: {{ total_views }} | Всего лайков: {{ total_likes }}</p>
962
+ <button class="btn share-btn" onclick="copyProfileLink()">Поделиться профилем</button>
963
+ </div>
964
  <h2>Публикации</h2>
965
  <div class="post-grid">
966
  {% if user_posts %}
 
1010
  }
1011
  }
1012
 
1013
+ function copyProfileLink() {
1014
+ const url = window.location.href;
1015
+ navigator.clipboard.writeText(url).then(() => {
1016
+ alert('Ссылка на профиль скопирована в буфер обмена!');
1017
+ });
1018
+ }
1019
+
1020
  document.addEventListener('DOMContentLoaded', function() {
1021
  const videos = document.querySelectorAll('.post-preview');
1022
  videos.forEach(video => {
 
1072
  </body>
1073
  </html>
1074
  '''
1075
+ return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated, current_user=current_user)
1076
 
1077
  # Страница загрузки контента
1078
  @app.route('/upload', methods=['GET', 'POST'])
 
1138
  username = session.get('username', None)
1139
  html = '''
1140
  <!DOCTYPE html>
1141
+ <html lang="ру">
1142
  <head>
1143
  <meta charset="UTF-8">
1144
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
1150
  .sidebar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
1151
  .nav-brand { font-size: 1.5em; font-weight: 600; color: #3b82f6; }
1152
  .nav-links { display: flex; flex-direction: column; gap: 10px; }
1153
+ .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; }
1154
  .nav-link:hover { background: rgba(59, 130, 246, 0.3); color: #2563eb; transform: translateX(5px); }
1155
  .logout-btn { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
1156
  .logout-btn:hover { background: rgba(239, 68, 68, 0.3); color: #dc2626; }
 
1227
  '''
1228
  return render_template_string(html, username=username, is_authenticated=is_authenticated)
1229
 
1230
+ # Админ-панель (все посты: видео и фото)
1231
  @app.route('/admhosto', methods=['GET', 'POST'])
1232
  def admin_panel():
1233
  data = load_data()
1234
+ posts = sorted(data.get('posts', []), key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
1235
  is_authenticated = 'username' in session
1236
  username = session.get('username', None)
1237
 
1238
  search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' and 'search' in request.form else request.args.get('search', '').strip().lower()
1239
 
1240
  if search_query:
1241
+ posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()]
1242
 
1243
  if request.method == 'POST' and 'delete' in request.form:
1244
  post_id = request.form.get('post_id')
1245
  if post_id:
1246
  data['posts'] = [p for p in data['posts'] if p['id'] != post_id]
1247
  save_data(data)
1248
+ logging.info(f"Удален пост с ID {post_id} через админ-панель")
1249
  return redirect(url_for('admin_panel'))
1250
 
1251
  html = '''
1252
  <!DOCTYPE html>
1253
+ <html lang="ру">
1254
  <head>
1255
  <meta charset="UTF-8">
1256
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1257
  <title>Админ-панель</title>
1258
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
1259
  <style>
1260
+ body { font-family: 'Poppins', sans-serif; background: linear-gradient(135deg, #e0e7ff, #f3f4f6); margin: 0; padding: 0; min-height: 100vh; }
1261
  .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; }
1262
  .sidebar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
1263
  .nav-brand { font-size: 1.5em; font-weight: 600; color: #3b82f6; }
 
1268
  .logout-btn:hover { background: rgba(239, 68, 68, 0.3); color: #dc2626; }
1269
  .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); }
1270
  .container { max-width: 1200px; margin: 20px auto 20px 270px; padding: 20px; transition: margin-left 0.3s ease; }
1271
+ .post-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
1272
+ .post-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; }
1273
+ .post-item:hover { transform: scale(1.02); }
1274
+ .post-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; }
1275
  .delete-btn { padding: 10px; background: #ef4444; color: white; border: none; border-radius: 8px; cursor: pointer; width: 100%; transition: background 0.3s ease; }
1276
  .delete-btn:hover { background: #dc2626; }
1277
  .search-container { margin-bottom: 20px; }
 
1286
  .sidebar.active { transform: translateX(0); }
1287
  .menu-btn { display: block; }
1288
  .container { margin: 60px 10px 20px 10px; max-width: calc(100% - 20px); }
1289
+ .post-grid { grid-template-columns: 1fr; gap: 15px; }
1290
+ .post-item { padding: 10px; }
1291
+ .post-preview { height: 150px; }
1292
  .delete-btn, .search-input { padding: 10px; font-size: 14px; }
1293
  .search-btn { padding: 10px 15px; font-size: 14px; }
1294
  h1 { font-size: 1.5em; }
 
1299
  <button class="menu-btn" onclick="toggleSidebar()">☰</button>
1300
  ''' + NAV_HTML + '''
1301
  <div class="container">
1302
+ <h1>Админ-панель: Все посты</h1>
1303
  <div class="search-container">
1304
  <form method="POST">
1305
  <input type="text" name="search" class="search-input" placeholder="Поиск по названию или описанию" value="{{ search_query }}">
1306
  <button type="submit" class="search-btn">Искать</button>
1307
  </form>
1308
  </div>
1309
+ <div class="post-grid">
1310
+ {% if posts %}
1311
+ {% for post in posts %}
1312
+ <div class="post-item">
1313
+ <a href="{{ url_for('post_page', post_id=post['id']) }}">
1314
+ {% if post['type'] == 'video' %}
1315
+ <video class="post-preview" preload="metadata" muted>
1316
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
1317
+ </video>
1318
+ {% else %}
1319
+ <img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
1320
+ {% endif %}
1321
+ <h2>{{ post['title'] }}</h2>
1322
  </a>
1323
+ <p>{{ post['description'] }}</p>
1324
+ <p>Загрузил: <a href="{{ url_for('user_profile', username=post['uploader']) }}" class="username-link">{{ post['uploader'] }}</a> | {{ post['upload_date'] }}</p>
1325
+ <p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
1326
  <form method="POST">
1327
+ <input type="hidden" name="post_id" value="{{ post['id'] }}">
1328
  <button type="submit" name="delete" class="delete-btn">Удалить</button>
1329
  </form>
1330
  </div>
1331
  {% endfor %}
1332
  {% else %}
1333
+ <p>Посты не найдены.</p>
1334
  {% endif %}
1335
  </div>
1336
  </div>
 
1340
  }
1341
 
1342
  document.addEventListener('DOMContentLoaded', function() {
1343
+ const videos = document.querySelectorAll('.post-preview');
1344
  videos.forEach(video => {
1345
+ if (video.tagName === 'VIDEO') {
1346
+ video.addEventListener('loadedmetadata', function() {
1347
+ const duration = video.duration;
1348
+ const randomTime = Math.random() * duration;
1349
+ video.currentTime = randomTime;
1350
+ });
1351
+ }
1352
  });
1353
  });
1354
  </script>
1355
  </body>
1356
  </html>
1357
  '''
1358
+ return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query)
1359
 
1360
  if __name__ == '__main__':
1361
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)