Pull to refresh

От хаоса к порядку: Как Peakline превратился в профессиональный инструмент за неделю

Level of difficultyEasy
Reading time11 min
Views844

Неделя назад мой проект был "швейцарским ножом в картонной коробке". Сегодня это настоящая платформа с PWA, AI-анализом по зонам, системой защиты от ботов, отслеживанием износа снаряжения, прогнозом погоды для маршрутов и детальной аналитикой клубов. Рассказываю, что изменилось под капотом и почему это важно.

Привет, Хабр! Снова на связи Александр, создатель Peakline. Прошла неделя с момента публикации первой статьи о проекте, и я обещал рассказывать о развитии. Но даже я не ожидал такого темпа изменений.

Что случилось за эти 7 дней? Проект получил такой отклик от сообщества, что пришлось буквально строить его заново. Не интерфейс — он остался узнаваемым. Но архитектуру, безопасность, производительность — всё пересмотрел с нуля. И добавил функций, о которых раньше только мечтал.

📈 Невероятный рост: цифры, которые удивили даже меня

Наше сообщество в цифрах

  • 🌐 1,709 уникальных посетителей

  • 👥 185 счастливых атлетов

  • 📊 342 тренировки проанализировано

  • 📁 71 FIT-файл создан

Для сравнения: неделю назад у нас было 8 пользователей и 27 проанализированных тренировок. Рост в 20+ раз — это космос!

🛡️ Безопасность: авось пронесет к "enterprise-уровню"

Honeypot против армии WordPress-ботов

Как только IP стал публичным, началось веселье. В логах появились тысячи запросов к несуществующим путям:

/wp-admin/
/xmlrpc.php  
/.env
/.git/config
/phpmyadmin/

Проблема: Боты ищут уязвимости WordPress на FastAPI-сервере. Это как искать карбюратор в Tesla.

Решение: Написал простую "медовую ловушку" (Honeypot). Вместо скучной 404 ошибки, боты получают киберпанк-манифест:

@app.get("/{full_path:path}", response_class=PlainTextResponse)
async def troll_booth(request: Request, full_path: str):
    scanner_keywords = [".php", "wp-", "/.env", "/.git", "shell"]
    is_scanner = any(keyword in full_path.lower() for keyword in scanner_keywords)
    
    if is_scanner:
        logger.warning(f"HONEYPOT HIT: Bot [{client_ip}] looking for '{full_path}'")
        return PlainTextResponse(content=cyber_memorandum, status_code=200)

Результат: Боты получают красивый ASCII-арт с кибер-глазом и объяснение, что "этот сервер говорит на языке асинхронных воркеров и Pydantic, а не плагинов и тем". А я получаю детальные логи для анализа атак, в планах подключение fail2ban.

Rate Limiter: защита от DoS и контроль ресурсов

Создал универсальную систему ограничения частоты запросов:

class RateLimiter:
    def check(self, scope: str, limit: int, period: int):
        async def dependency(user: dict = Depends(get_current_user_required)):
            cache_key = f"ratelimit:{scope}:{user['strava_id']}"
            # Логика проверки временных меток
            if len(valid_timestamps) >= limit:
                raise HTTPException(status_code=429, 
                    detail=f"Rate limit exceeded. Try again in {wait_time} seconds.")

Применение:

  • AI-анализ: 5 запросов в час

  • Скачивание FIT-файлов: 10 в час

  • GPX Fixer: 10 исправлений в час

  • Поиск создателя сегмента: 7 запросов в час

Достижение лимита запросов
Достижение лимита запросов

Зачем это нужно? AI-анализ чего то стоит. Strava API так же имеет лимиты. Сервер не резиновый. Лучше ограничить заранее, чем падать при наплыве пользователей.

Усиление против XSS

После обсуждения потенциальной уязвимости на Хабре провел аудит и добавил дополнительную санацию всего пользовательского контента. Лучше перебдеть, чем недобдеть.

🚀 Progressive Web App: от сайта к приложению

Почему PWA?

Некоторые пользователи просили мобильное приложение. Нативная разработка под iOS/Android — это месяцы работы и дублирование логики. PWA позволяет превратить веб-сайт в полноценное приложение за выходные.

Что добавил:

1. Web App Manifest (manifest.json):

{
  "name": "Peakline",
  "short_name": "Peakline", 
  "display": "standalone",
  "background_color": "#0c0c0f",
  "theme_color": "#fc5200",
  "start_url": "/?source=pwa"
}

2. Service Worker (sw.js):

// Кэширование ключевых ресурсов
const urlsToCache = ['/', '/static/css/home.css', '/offline'];

// Обработка запросов в офлайне
self.addEventListener('fetch', event => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => caches.match('/offline'))
    );
  }
});

3. Офлайн-страница:
Когда сеть недоступна, вместо браузерной ошибки пользователь видит красивую страницу с объяснением.

Результат: Peakline теперь можно установить на домашний экран телефона как обычное приложение. Запускается в отдельном окне, работает быстрее, есть базовая офлайн-поддержка.

> Честно про мобильную версию: Верстка на мобильных пока не идеальна — это в приоритетах на ближайшие недели.

🌤️ Прогноз погоды для маршрутов: мечта стала реальностью

От идеи к реализации

Теперь можно выбрать любимый маршрут, указать дату и время тренировки, и получить детальный прогноз.

Что реализовано:

async def get_route_forecast(lat: float, lon: float):
    params = {
        "hourly": "temperature_2m,windspeed_10m,winddirection_10m",
        "daily": "temperature_2m_max,temperature_2m_min"
    }
    # Двухуровневый fallback для максимальной надежности

Фишка: Направление ветра визуализируется прямо на карте маршрута! Видишь, что на возвращении будет попутный ветер? Самое время ехать на рекорд.

Пример использования: Планируешь 50-километровую покатушку на субботу. Открываешь маршрут в Peakline, видишь прогноз: утром +12°C, ветер 8 км/ч с запада. Значит, лучше ехать против ветра в первой половине, чтобы домой лететь по попутному.

🏃‍♂️ Страницы клубов: социальная аналитика

В Strava у клубов есть лента активностей, но нет детальной аналитики. Хочется знать: кто самый активный участник? Какие сегменты популярны в клубе? Кто показывает лучший прогресс?

Что реализовал:

1. Детальная страница клуба:

@app.get("/club/{club_id}")
async def club_detail_page(club_id: str, user: dict = Depends(get_current_user_required)):
    club_details = await api_get_club_details(club_id, access_token)
    return templates.TemplateResponse("club_detail.html", {...})

Что реализую:

  • Лента активностей с пагинацией

  • Статистика участников

  • Популярные маршруты и сегменты

  • Топ атлетов по различным метрикам

3. Интеграция с дашбордом:
Теперь карточки клубов в дашборде кликабельны — можно сразу перейти к детальному анализу.

Результат: Руководители клубов получили инструмент для анализа активности сообщества, а участники — мотивацию соревноваться друг с другом в дружеской атмосфере.

Что еще добавилось:

4. Просмотр недавних тренировок участников:
Теперь на странице клуба можно не только видеть общую статистику, но и просматривать недавние тренировки конкретных спортсменов. Это добавляет социальный элемент и помогает участникам клуба вдохновляться друг другом.

5. Интеграция с навигацией:
Добавил кнопки "Все маршруты" и "Все снаряжение" для быстрого доступа к соответствующим разделам прямо из дашборда клуба.

🧠 AI-анализ нового уровня: от общих фраз к конкретике

Что было плохо

AI видел только общие цифры:

prompt = f"Средний пульс: {avg_hr}, средняя мощность: {avg_power}"

Ответ был соответствующий: "У вас был высокий средний пульс". Спасибо, кэп!

Что изменилось

Теперь AI получает структурированные данные о времени в каждой пульсовой зоне:

prompt = f"""
Анализируй тренировку:
- Время в зоне 1 (восстановление): {time_z1} мин
- Время в зоне 2 (аэробная): {time_z2} мин  
- Время в зоне 3 (темповая): {time_z3} мин
- Время в зоне 4 (анаэробная): {time_z4} мин
- Время в зоне 5 (нейромышечная): {time_z5} мин
"""

Результат: AI теперь дает конкретные рекомендации:
> "Отличная темповая работа! Ты провел 40% времени в 3-й пульсовой зоне, что идеально для развития выносливости. Однако 10% времени в 5-й зоне могли быть излишними для этой цели."

⚙️ Система отслеживания износа снаряжения

Зачем это нужно?

Велосипедисты знают: цепь нужно менять каждые 3000 км, покрышки — каждые 5000 км, тормозные колодки — по ощущениям или каждые 1000 км в зависимости от стиля езды. Но кто это помнит?

Архитектура решения

1. Новые таблицы в БД:

-- Основное снаряжение
CREATE TABLE user_gear (
    gear_id TEXT PRIMARY KEY,
    distance_strava INTEGER,     -- Текущий пробег из Strava
    max_distance INTEGER,        -- Ресурс до замены  
    initial_distance INTEGER,    -- Начальный пробег
    retired BOOLEAN DEFAULT FALSE
);

-- Компоненты снаряжения  
CREATE TABLE gear_components (
    component_id INTEGER PRIMARY KEY,
    gear_id TEXT,
    name TEXT,                   -- "Цепь Shimano XT"
    max_distance INTEGER,        -- Ресурс компонента
    distance_at_install INTEGER, -- Пробег при установке
    retired_at TIMESTAMP
);

2. Алгоритм расчета износа:

# Пробег компонента = (Текущий пробег - Пробег при установке) + Начальный износ
component_wear = (current_distance - install_distance) + initial_wear

# Процент износа с цветовой индикацией
wear_percentage = min(100, (component_wear / max_distance) * 100)

3. Визуализация:

  • Зеленый прогресс-бар: < 70% износа

  • Желтый: 70-90% (пора планировать замену)

  • Красный: > 90% (срочно менять!)

    Да, имеются перспективы для роста и изменений - к примеру для амортизации не километры, а часы.

Что получил пользователь?

  1. Автоматическая синхронизация снаряжения из Strava

  2. Настройка ресурса для каждого элемента (велосипед, кроссовки)

  3. Добавление компонентов к снаряжению (цепь, покрышки, колодки)

  4. Визуальные индикаторы износа на всех страницах

  5. История замен с возможностью пометить компонент как "снятый"

В планах: Telegram-уведомления

Сейчас тестирую систему алертов в Telegram. Когда компонент достигнет 85% износа, бот пришлет уведомление:
> "🔧 Пора менять цепь на велосипеде Trek! Пробег: 2,800 км из 3,000 км (93% износа)"

Это поможет не забывать о техобслуживании и продлит жизнь дорогому снаряжению.

🎨 Полное обновление главной страницы

Зачем нужен был редизайн?

Старая главная страница была скучной и не показывала весь потенциал Peakline. Новые пользователи не понимали, что именно они получат, зарегистрировавшись.

Что изменилось:

1. Новые информационные блоки:


<section class="features-showcase">
  <div class="feature-card">
    <h3>🌤️ Погодный контекст</h3>
    <p>Исторические данные и прогноз для планирования</p>
  </div>
  <div class="feature-card">
    <h3>⚙️ Pro-инструменты</h3>
    <p>Кастомные зоны и гонка с целью в FIT-файлах</p>
  </div>
</section>

2. Живые примеры функций:
Вместо текстовых описаний добавил интерактивные демо-блоки, показывающие реальную аналитику.

3. Социальное доказательство:
Блок со статистикой сообщества теперь обновляется в реальном времени и показывает реальную активность пользователей.

Результат: Конверсия с главной страницы в регистрацию выросла, а время на сайте увеличилось в среднем на 40%.

📊 Детальный анализ сегментов: от базового к профессиональному

Что было раньше?

Раньше страница сегмента показывала только основную информацию: название, расстояние, набор высоты. Этого мало для серьезной подготовки.

Что добавлю:

1. Подробную страницу анализа сегмента:

@app.get("/segment/{segment_id}/analysis")
async def segment_analysis_page(segment_id: str, user: dict = Depends(get_current_user_required)):
    # Загружаем детальную аналитику
    segment_stats = await get_segment_detailed_stats(segment_id, user["strava_id"])
    return templates.TemplateResponse("segment_analysis.html", {
        "segment": segment_stats,
        "personal_attempts": segment_stats["attempts_history"],
        "weather_correlation": segment_stats["weather_data"]
    })

2. История попыток пользователя:
Таблица со всеми вашими заездами на сегменте, с возможностью сравнения результатов и анализа прогресса.

3. Корреляция с погодой:
Анализ того, как погодные условия влияли на ваши результаты на этом сегменте.

4. Оптимальная стратегия:
AI-рекомендации по тактике прохождения сегмента на основе профиля высот и ваших сильных сторон.

🔧 Системные улучшения

Многоязычная FAQ-система для SEO

Вместо статичных FAQ создал динамическую систему с поддержкой 5 языков (русский, английский, немецкий, французский, словацкий). Главная цель: захват органического трафика из поисковиков.

def load_faq_content(lang_code: str) -&gt; List[Dict[str, Any]]:
    faq_dir = Path(f"faq_content/{lang_code}")
    categories = []
    for file_path in sorted(faq_dir.glob("*.md")):
        # Парсинг Markdown и группировка по категориям

Что дает:

  • 14 детальных FAQ-статей на каждом языке

  • Темы: от базовых ("Что такое GPX?") до продвинутых ("API интеграции")

  • SEO-оптимизированный контент для привлечения новых пользователей

  • Легкое добавление новых вопросов через Markdown-файлы

Система уведомлений для разработки

Добавил умную систему уведомлений для параллельной разработки и исправления багов:

{
  "maintenance_mode": false,
  "message_ru": "Система обновляется. Некоторые функции могут работать нестабильно.",
  "message_en": "The system is being updated. Some features may be unstable."
}

Зачем это нужно: Когда разворачиваю новую версию или исправляю критический баг, пользователи получают предупреждение о возможной нестабильности. Это улучшает пользовательский опыт и снижает количество негативных отзывов.

Логика работы:

  • Уведомление показывается один раз в день

  • Поддержка многих языков

  • Мгновенная активация через изменение конфига

  • Два варианта: "Продолжить" (запомнить) или "Вернуться позже"

Улучшения навигации

Новые кнопки быстрого доступа:
Добавил кнопки "Все снаряжение" и "Все маршруты" для мгновенного перехода к соответствующим разделам. Это особенно удобно для пользователей с большим количеством велосипедов или любимых маршрутов.

<div class="quick-navigation">
  <a class="nav-button" href="/gear">⚙️ Все снаряжение</a>
  <a class="nav-button" href="/routes">🗺️ Все маршруты</a>
</div>

Результат: Время поиска нужной информации сократилось, пользователи стали чаще использовать продвинутые функции.

🤔 Выводы и размышления

Что я понял за эту неделю?

  1. Сообщество — лучший QA-тестер. Ни один автоматизированный тест не найдет столько багов, сколько находят реальные пользователи за день активного использования.

  2. Безопасность — не роскошь, а необходимость. Как только сервис становится публичным, атакуют все: от скрипт-кидди до автоматизированных ботов.

  3. Rate limiting спасает жизнь. Лучше ограничить пользователей заранее, чем упасть при первом же наплыве трафика.

  4. PWA — это магия. За выходные превратил веб-сайт в полноценное приложение. Пользователи счастливы, затраты минимальны.

  5. AI без контекста — бесполезен. Но с правильными данными он становится настоящим тренером.

  6. SEO работает. Многоязычная FAQ-система уже дает первые результаты в органическом поиске.

  7. Пользователи хотят больше аналитики. Чем глубже данные, тем больше инсайтов можно извлечь для улучшения результатов.

Главный урок

Не бойтесь показывать сырой продукт. Моя первая версия была далека от идеала, но именно благодаря открытой публикации на Хабре проект получил импульс для развития. Сообщество не только находит баги, но и предлагает идеи, которые я сам никогда бы не придумал.

🚀 Что дальше? Амбициозные планы развития

Ближайшие месяцы:

1. Продвинутая аналитика нового уровня:

# Планируемые метрики
class AdvancedAnalytics:
    power_curve_analysis: bool     # Анализ кривой мощности  
    fatigue_modeling: bool         # Моделирование усталости
    optimal_pacing: bool           # Оптимальная тактика гонки
    performance_prediction: bool   # Прогноз результатов
    seasonal_trends: bool          # Сезонные тренды формы

2. Локальные аккаунты без привязки к Strava:
Многие пользователи просят возможность использовать Peakline без регистрации в Strava. Планирую добавить:

  • Регистрацию по email

  • Ручную загрузку файлов GPX/FIT

  • Собственную базу сегментов

  • Экспорт данных в различные форматы

3. Мобильная оптимизация:
Переписываю верстку с нуля под mobile-first подход. Планирую добавить жесты, улучшенную навигацию и оптимизацию для небольших экранов.

Средне-долгосрочные планы:

1. Машинное обучение для персонализации:

  • Алгоритмы рекомендации тренировок

  • Персональные зоны на основе истории

  • Автоматическое выявление паттернов в данных

2. Социальные функции:

  • Приватные группы тренировок

  • Соревнования между друзьями

  • Система достижений и наград

3. API для интеграций:
Открытый API для интеграции с другими сервисами и приложениями.

Почему я продолжаю развивать проект?

Каждая новая функция рождается из реальной потребности пользователей. Я не добавляю фичи ради фич — каждое улучшение решает конкретную проблему, с которой сталкиваются атлеты в своих тренировках.

Проект растет органически, и это его главная сила. Вместо попыток угодить всем, я сосредотачиваюсь на создании лучшего инструмента для серьезных спортсменов, которые хотят понимать свои данные глубже.


🔗 Ссылки и контакты

Попробовать: www.thepeakline.com

GitHub: github.com/cyberscoper — здесь можно посмотреть на мои другие проекты

Telegram-канал: @peakline_official — новости и обновления проекта (давненько не обновлялся, но планирую вернуться к регулярным постам)

🙏 Благодарности

Спасибо всем, кто тестировал, находил баги, предлагал идеи и просто пользовался Peakline. Отдельная благодарность тем, кто написал развернутые отзывы — именно они помогли расставить приоритеты в разработке.

Продолжаю работать для вас!

Peakline развивается каждый день. За время написания этой статьи уже добавил еще несколько мелких, но полезных улучшений. Это живой проект, который растет вместе со своим сообществом.

Следующая статья будет через 2-3 недели, когда доделаю мобильную верстку, запущу Telegram-уведомления о техобслуживании и, возможно, представлю первые результаты работы продвинутой аналитики. А пока — добро пожаловать тестировать новые функции!


P.S. Если заметите баги или у вас есть идеи — пишите в комментариях. Каждый отзыв помогает сделать проект лучше.

Tags:
Hubs:
+3
Comments1

Articles