diff --git a/create_test_data.py b/create_test_data.py new file mode 100644 index 0000000..91129b4 --- /dev/null +++ b/create_test_data.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +""" +Скрипт для создания тестовых групп и ссылок через API +""" + +import requests +import json +import random +import time + +API_BASE = "https://links.shareon.kr/api" +# Конфигурация - попробуем разные варианты +API_ENDPOINTS = [ + + "https://links.shareon.kr/api", + +] +USERNAME = "trevor" +PASSWORD = "Cl0ud1985!" + +# Данные для групп и ссылок +GROUPS_DATA = [ + { + "name": "Социальные сети", + "description": "Популярные социальные платформы и мессенджеры", + "links": [ + {"title": "Facebook", "url": "https://facebook.com", "description": "Социальная сеть Facebook"}, + {"title": "Instagram", "url": "https://instagram.com", "description": "Фото и видео платформа"}, + {"title": "Twitter", "url": "https://twitter.com", "description": "Микроблоггинг платформа"}, + {"title": "LinkedIn", "url": "https://linkedin.com", "description": "Профессиональная сеть"}, + {"title": "YouTube", "url": "https://youtube.com", "description": "Видеохостинг"}, + {"title": "TikTok", "url": "https://tiktok.com", "description": "Короткие видео"}, + {"title": "WhatsApp", "url": "https://whatsapp.com", "description": "Мессенджер"}, + {"title": "Telegram", "url": "https://telegram.org", "description": "Облачный мессенджер"}, + {"title": "Discord", "url": "https://discord.com", "description": "Голосовой чат для геймеров"}, + {"title": "Reddit", "url": "https://reddit.com", "description": "Социальные новости"}, + {"title": "Pinterest", "url": "https://pinterest.com", "description": "Визуальные идеи"}, + {"title": "Snapchat", "url": "https://snapchat.com", "description": "Мгновенные фото"}, + {"title": "Clubhouse", "url": "https://clubhouse.com", "description": "Голосовые чаты"}, + {"title": "VKontakte", "url": "https://vk.com", "description": "Российская соцсеть"}, + {"title": "Odnoklassniki", "url": "https://ok.ru", "description": "Социальная сеть"} + ] + }, + { + "name": "Разработка и IT", + "description": "Инструменты и ресурсы для программистов", + "links": [ + {"title": "GitHub", "url": "https://github.com", "description": "Хостинг кода"}, + {"title": "GitLab", "url": "https://gitlab.com", "description": "DevOps платформа"}, + {"title": "Stack Overflow", "url": "https://stackoverflow.com", "description": "Q&A для программистов"}, + {"title": "MDN Web Docs", "url": "https://developer.mozilla.org", "description": "Документация веб-технологий"}, + {"title": "Visual Studio Code", "url": "https://code.visualstudio.com", "description": "Редактор кода"}, + {"title": "Docker Hub", "url": "https://hub.docker.com", "description": "Контейнеры Docker"}, + {"title": "npm", "url": "https://npmjs.com", "description": "Пакетный менеджер Node.js"}, + {"title": "Python.org", "url": "https://python.org", "description": "Официальный сайт Python"}, + {"title": "Django", "url": "https://djangoproject.com", "description": "Веб-фреймворк Python"}, + {"title": "React", "url": "https://reactjs.org", "description": "JavaScript библиотека"}, + {"title": "Vue.js", "url": "https://vuejs.org", "description": "Прогрессивный фреймворк"}, + {"title": "Angular", "url": "https://angular.io", "description": "TypeScript фреймворк"}, + {"title": "Next.js", "url": "https://nextjs.org", "description": "React фреймворк"}, + {"title": "Tailwind CSS", "url": "https://tailwindcss.com", "description": "Utility-first CSS"}, + {"title": "Bootstrap", "url": "https://getbootstrap.com", "description": "CSS фреймворк"} + ] + }, + { + "name": "Онлайн сервисы", + "description": "Полезные веб-сервисы и инструменты", + "links": [ + {"title": "Google Drive", "url": "https://drive.google.com", "description": "Облачное хранилище"}, + {"title": "Dropbox", "url": "https://dropbox.com", "description": "Синхронизация файлов"}, + {"title": "Notion", "url": "https://notion.so", "description": "Рабочее пространство"}, + {"title": "Trello", "url": "https://trello.com", "description": "Управление проектами"}, + {"title": "Slack", "url": "https://slack.com", "description": "Корпоративный мессенджер"}, + {"title": "Zoom", "url": "https://zoom.us", "description": "Видеоконференции"}, + {"title": "Figma", "url": "https://figma.com", "description": "Дизайн интерфейсов"}, + {"title": "Canva", "url": "https://canva.com", "description": "Графический дизайн"}, + {"title": "Unsplash", "url": "https://unsplash.com", "description": "Бесплатные фотографии"}, + {"title": "Pixabay", "url": "https://pixabay.com", "description": "Стоковые изображения"}, + {"title": "Google Translate", "url": "https://translate.google.com", "description": "Переводчик"}, + {"title": "DeepL", "url": "https://deepl.com", "description": "Продвинутый переводчик"}, + {"title": "Grammarly", "url": "https://grammarly.com", "description": "Проверка грамматики"}, + {"title": "LastPass", "url": "https://lastpass.com", "description": "Менеджер паролей"}, + {"title": "1Password", "url": "https://1password.com", "description": "Безопасность паролей"} + ] + }, + { + "name": "Развлечения", + "description": "Игры, фильмы и развлекательный контент", + "links": [ + {"title": "Netflix", "url": "https://netflix.com", "description": "Стриминг видео"}, + {"title": "Spotify", "url": "https://spotify.com", "description": "Музыкальный стриминг"}, + {"title": "Twitch", "url": "https://twitch.tv", "description": "Стриминг игр"}, + {"title": "Steam", "url": "https://store.steampowered.com", "description": "Игровая платформа"}, + {"title": "Epic Games", "url": "https://epicgames.com", "description": "Игровой магазин"}, + {"title": "PlayStation Store", "url": "https://store.playstation.com", "description": "Игры для PS"}, + {"title": "Xbox Store", "url": "https://xbox.com", "description": "Игры для Xbox"}, + {"title": "IMDb", "url": "https://imdb.com", "description": "База данных фильмов"}, + {"title": "Rotten Tomatoes", "url": "https://rottentomatoes.com", "description": "Рейтинги фильмов"}, + {"title": "Kinopoisk", "url": "https://kinopoisk.ru", "description": "Российский кинопоиск"}, + {"title": "Apple Music", "url": "https://music.apple.com", "description": "Музыка от Apple"}, + {"title": "Amazon Prime", "url": "https://primevideo.com", "description": "Видео от Amazon"}, + {"title": "Disney+", "url": "https://disneyplus.com", "description": "Контент Disney"}, + {"title": "Hulu", "url": "https://hulu.com", "description": "Американский стриминг"}, + {"title": "Paramount+", "url": "https://paramountplus.com", "description": "Стриминг Paramount"} + ] + }, + { + "name": "Образование", + "description": "Платформы для обучения и саморазвития", + "links": [ + {"title": "Coursera", "url": "https://coursera.org", "description": "Онлайн курсы университетов"}, + {"title": "Udemy", "url": "https://udemy.com", "description": "Практические курсы"}, + {"title": "edX", "url": "https://edx.org", "description": "Курсы от ведущих вузов"}, + {"title": "Khan Academy", "url": "https://khanacademy.org", "description": "Бесплатное образование"}, + {"title": "Codecademy", "url": "https://codecademy.com", "description": "Обучение программированию"}, + {"title": "FreeCodeCamp", "url": "https://freecodecamp.org", "description": "Бесплатные курсы кода"}, + {"title": "Pluralsight", "url": "https://pluralsight.com", "description": "Технические курсы"}, + {"title": "LinkedIn Learning", "url": "https://linkedin.com/learning", "description": "Профессиональные навыки"}, + {"title": "Skillshare", "url": "https://skillshare.com", "description": "Творческие курсы"}, + {"title": "MasterClass", "url": "https://masterclass.com", "description": "Курсы от экспертов"}, + {"title": "Duolingo", "url": "https://duolingo.com", "description": "Изучение языков"}, + {"title": "Babbel", "url": "https://babbel.com", "description": "Языковые курсы"}, + {"title": "TED", "url": "https://ted.com", "description": "Вдохновляющие лекции"}, + {"title": "MIT OpenCourseWare", "url": "https://ocw.mit.edu", "description": "Курсы MIT"}, + {"title": "Stanford Online", "url": "https://online.stanford.edu", "description": "Курсы Стэнфорда"} + ] + } +] + +class APIClient: + def __init__(self, base_url, username, password): + self.base_url = base_url + self.session = requests.Session() + self.session.verify = False # Отключаем проверку SSL для локального тестирования + self.token = None + self.user_id = None + self.username = username + self.password = password + self.groups_endpoint = None # Сохраняем рабочий endpoint для групп + + def login(self): + """Получение токена авторизации""" + print(f"🔐 Авторизация пользователя {self.username} на {self.base_url}...") + + login_data = { + "username": self.username, + "password": self.password + } + + try: + response = self.session.post(f"{self.base_url}/auth/login/", json=login_data, timeout=10) + + if response.status_code == 200: + data = response.json() + + # Пробуем разные поля для токена + self.token = (data.get('access') or + data.get('access_token') or + data.get('token')) + + # Пробуем разные поля для user_id + user_data = data.get('user', {}) + if isinstance(user_data, dict): + self.user_id = user_data.get('id') + else: + self.user_id = data.get('user_id') + + print(f"Debug: Response data keys: {list(data.keys())}") + print(f"Debug: Token: {self.token}") + + if self.token: + self.session.headers.update({ + 'Authorization': f'Bearer {self.token}', + 'Content-Type': 'application/json' + }) + print(f"✅ Успешная авторизация! User ID: {self.user_id}") + return True + else: + print("❌ Токен не найден в ответе") + return False + + print(f"❌ Ошибка авторизации: {response.status_code}") + if response.status_code != 502: # Не показываем HTML для 502 + print(f"Response: {response.text[:200]}") + return False + + except requests.exceptions.RequestException as e: + print(f"❌ Ошибка подключения: {e}") + return False + + def create_group(self, group_data): + """Создание группы ссылок""" + print(f"📁 Создание группы '{group_data['name']}'...") + + payload = { + "name": group_data["name"], + "description": group_data.get("description", ""), + "is_favorite": random.choice([True, False]), + "header_color": f"#{random.randint(0, 0xFFFFFF):06x}" + } + + # Пробуем разные endpoint'ы для групп + endpoints_to_try = [ + f"{self.base_url}/link-groups/", + f"{self.base_url}/linkgroups/", + f"{self.base_url}/groups/", + f"{self.base_url}/link_groups/" + ] + + for endpoint in endpoints_to_try: + try: + response = self.session.post(endpoint, json=payload, timeout=10) + if response.status_code == 201: + group = response.json() + print(f"✅ Группа '{group_data['name']}' создана с ID: {group['id']}") + self.groups_endpoint = endpoint # Запоминаем рабочий endpoint + return group + elif response.status_code == 404: + continue # Пробуем следующий endpoint + else: + print(f"❌ Ошибка создания группы через {endpoint}: {response.status_code}") + if response.status_code != 502: + print(f"Response: {response.text[:200]}") + continue + except Exception as e: + print(f"❌ Ошибка подключения к {endpoint}: {e}") + continue + + print(f"❌ Не удалось создать группу '{group_data['name']}' через все endpoints") + return None + + def create_link(self, group_id, link_data): + """Создание ссылки в группе""" + payload = { + "title": link_data["title"], + "url": link_data["url"], + "description": link_data.get("description", ""), + "group": group_id + } + + # Пробуем разные endpoint'ы для ссылок + endpoints_to_try = [ + f"{self.base_url}/links/", + f"{self.base_url}/link/", + f"{self.base_url}/api/links/" + ] + + for endpoint in endpoints_to_try: + try: + response = self.session.post(endpoint, json=payload, timeout=10) + if response.status_code == 201: + link = response.json() + print(f" ✅ Ссылка '{link_data['title']}' добавлена") + return link + elif response.status_code == 404: + continue # Пробуем следующий endpoint + else: + print(f" ❌ Ошибка создания ссылки '{link_data['title']}' через {endpoint}: {response.status_code}") + continue + except Exception as e: + print(f" ❌ Ошибка подключения к {endpoint}: {e}") + continue + + print(f" ❌ Не удалось создать ссылку '{link_data['title']}' через все endpoints") + return None + + def create_groups_and_links(self): + """Создание всех групп и ссылок""" + if not self.login(): + return False + + print(f"\n🚀 Начинаем создание {len(GROUPS_DATA)} групп...") + + created_groups = [] + total_links = 0 + + for group_data in GROUPS_DATA: + # Создаем группу + group = self.create_group(group_data) + if not group: + continue + + created_groups.append(group) + + # Создаем ссылки в группе + print(f" 📎 Добавляем {len(group_data['links'])} ссылок в группу...") + for link_data in group_data['links']: + link = self.create_link(group['id'], link_data) + if link: + total_links += 1 + + print(f"\n🎉 Готово!") + print(f"📁 Создано групп: {len(created_groups)}") + print(f"📎 Создано ссылок: {total_links}") + + return True + +def main(): + print("🔗 Создание тестовых данных для профиля trevor") + print("=" * 50) + + # Пробуем разные API endpoints + for api_base in API_ENDPOINTS: + print(f"\n🔍 Пробуем подключиться к: {api_base}") + client = APIClient(api_base, USERNAME, PASSWORD) + + try: + success = client.create_groups_and_links() + if success: + print("\n✨ Все данные успешно созданы!") + return + else: + print(f"\n💥 Не удалось создать данные через {api_base}") + continue + except Exception as e: + print(f"\n💥 Ошибка с {api_base}: {e}") + continue + + print("\n💥 Не удалось подключиться ни к одному API endpoint") + print("🔄 Попробуйте позже, когда сервер будет доступен") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/DEPLOYMENT_INSTRUCTIONS.md b/docs/DEPLOYMENT_INSTRUCTIONS.md similarity index 100% rename from DEPLOYMENT_INSTRUCTIONS.md rename to docs/DEPLOYMENT_INSTRUCTIONS.md diff --git a/TEMPLATES_AND_GROUPS_REPORT.md b/docs/TEMPLATES_AND_GROUPS_REPORT.md similarity index 100% rename from TEMPLATES_AND_GROUPS_REPORT.md rename to docs/TEMPLATES_AND_GROUPS_REPORT.md diff --git a/frontend/linktree-frontend/src/app/[username]/page.tsx b/frontend/linktree-frontend/src/app/[username]/page.tsx index d70717d..b1ac072 100644 --- a/frontend/linktree-frontend/src/app/[username]/page.tsx +++ b/frontend/linktree-frontend/src/app/[username]/page.tsx @@ -617,11 +617,17 @@ export default function UserPage({

{group.description}

)} ({ + id: link.id, + title: link.title, + url: link.url, + description: link.description, + image: designSettings.show_link_icons ? link.icon_url : undefined + }))} layout="cards" initialShowCount={8} - overlayColor={designSettings.group_overlay_enabled ? designSettings.group_overlay_color : undefined} - overlayOpacity={designSettings.group_overlay_enabled ? designSettings.group_overlay_opacity : undefined} + overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined} + overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined} /> @@ -694,7 +700,13 @@ export default function UserPage({

)} ({ + id: link.id, + title: link.title, + url: link.url, + description: link.description, + image: designSettings.show_link_icons ? link.icon_url : undefined + }))} layout="timeline" initialShowCount={5} overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined} @@ -780,9 +792,15 @@ export default function UserPage({

({ + id: link.id, + title: link.title, + url: link.url, + description: link.description, + image: designSettings.show_link_icons ? link.icon_url : undefined + }))} layout="magazine" - initialShowCount={index === 0 ? 5 : 3} + initialShowCount={index === 0 ? 8 : 6} overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined} overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined} /> diff --git a/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx b/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx index 79681cc..460586e 100644 --- a/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx +++ b/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx @@ -37,7 +37,11 @@ export function ExpandableGroup({ opacity: overlayOpacity } : undefined + // Добавляем отладочную информацию + console.log(`ExpandableGroup: ${links.length} links, initialShowCount: ${initialShowCount}, layout: ${layout}`) + if (links.length <= initialShowCount) { + console.log(`No expand button needed: ${links.length} <= ${initialShowCount}`) return (
{links.map((link) => ( @@ -57,6 +61,8 @@ export function ExpandableGroup({ const visibleLinks = isExpanded ? links : links.slice(0, initialShowCount) const hiddenCount = links.length - initialShowCount + console.log(`Showing expand button: visibleLinks: ${visibleLinks.length}, hiddenCount: ${hiddenCount}`) + return (
{visibleLinks.map((link) => (