{"id":29231034,"url":"https://github.com/stacenko-developer/link-tracker","last_synced_at":"2025-07-03T14:10:43.077Z","repository":{"id":301733290,"uuid":"1010140610","full_name":"stacenko-developer/link-tracker","owner":"stacenko-developer","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-28T14:04:49.000Z","size":424,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-28T14:20:28.468Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stacenko-developer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-28T12:39:35.000Z","updated_at":"2025-06-28T14:04:52.000Z","dependencies_parsed_at":"2025-06-28T14:20:42.672Z","dependency_job_id":null,"html_url":"https://github.com/stacenko-developer/link-tracker","commit_stats":null,"previous_names":["stacenko-developer/link-tracker"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/stacenko-developer/link-tracker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2Flink-tracker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2Flink-tracker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2Flink-tracker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2Flink-tracker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stacenko-developer","download_url":"https://codeload.github.com/stacenko-developer/link-tracker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2Flink-tracker/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263340027,"owners_count":23451521,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-03T14:10:38.487Z","updated_at":"2025-07-03T14:10:43.069Z","avatar_url":"https://github.com/stacenko-developer.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"![tbank-logo](img/tbank-logo.svg)\n# Академия бэкенда от Т-Банка 2025, 2 семестр, Проект: LinkTracker: приложение для отслеживания обновлений контента\n\n## Описание\nПроект представляет собой реализацию сервиса LinkTracker для отслеживания обновлений на веб-ресурсах. Основной функционал включает подписку на изменения контента по URL-адресам, мониторинг обновлений и отправку уведомлений пользователям через Telegram-бота.\n\nСистема позволяет:\n\n* Добавлять и удалять отслеживаемые ссылки через чат-бота\n* Автоматически проверять обновления на подписанных страницах\n* Получать уведомления о новых ответах, комментариях и изменениях контента\n* Управлять подписками с помощью тегов и фильтров\n\n## Функциональные требования\n* Бот должен поддерживать следующие команды:\n   * /start - регистрация пользователя.\n   * /help - вывод списка доступных команд.\n   * /track - начать отслеживание ссылки.\n   * /untrack - прекратить отслеживание ссылки.\n   * /list - показать список отслеживаемых ссылок (cписок ссылок, полученных при /track)\n* Команда /list должна выводить специальное сообщение, если список отслеживаемых ссылок пуст.\n* Неизвестные команды должны игнорироваться с уведомлением пользователю.\n* При попытке добавить ссылку, которая уже отслеживается, выводится сообщение: \"Ссылка уже отслеживается\"\n* Все endpoint'ы соответствуют OpenAPI-контракту\n   * Разрешено модифицировать LinkUpdate в Bot API для расширения и добавления новой информации (для упрощения форматирования)\n   * Ссылка на контракт OpenAPI: https://gist.github.com/sanyarnd/e35dc3d4e0c8000205ec5029dac38f5a (2 файла)\n* Реализован планировщик: бот присылает уведомление в случае обнаружения изменений\n* Общение между приложениями bot и scrapper происходит по HTTP или Apache Kafka\n* Бот должен регистировать доступные команды через метод setmycommands при запуске\n* Scrapper (планировщик) при отправке сообщения с изменениями включает детализацию данных:\n   * Для StackOverflow новый ответ или комментарий, сообщение включает:\n      * текст темы вопроса\n      * имя пользователя\n      * время создания\n      * превью ответа или комментария (первые 200 символов)\n   * Для GitHub новый PR или Issue, сообщение включает:\n      * название PR или Issue\n      * имя пользователя\n      * время создания\n      * превью описания (первые 200 символов)\n* Планировщик должен работать многопоточно\n* Возможность тэгирования ссылок: создание тэгов по темам или проектам позволит группировать подписки. Например, можно создать тэг \"Работа\", в который войдут ссылки на профессиональные ресурсы, и тэг \"Хобби\" для развлечений. Соответственно должны появиться операции управления тэгами.\n* Некорректные (не парсится и т.п.) сообщения отправляются в отдельную очередь dead letter queue\n* Все имена топиков задаются в конфигурационном файле \n* Бот кэширует запросы типа `/list` в Redis\n* Функционал фильтрации: реализовать возможность не получать уведомления от определенных пользователей\n* Батчинг уведомлений: реализовать отправку уведомлений не сразу при обнаружении, а по настраиваемому расписанию, например, утром в 10:00 единым сообщением (дайджест)\n   * Время отправки должно быть конфигурируемым\n   * Должна быть возможность выбрать режим уведомлений в настройках бота: \"сразу\" или \"дайджест раз в сутки\"\n   * Для накопления состояния следует использовать Redis\n* Все HTTP-запросы поддерживают Timeout\n* Все HTTP-запросы поддерживают Retry\n* У каждого публичного endpoint'а есть выставленный Rate Limiting на основе IP-адреса клиента\n* В случае недоступности сервиса продолжительное время вместо Retry соединение разрывается при помощи Circuit Breaker\n* В случае отказа HTTP или Kafka при отправке уведомлений происходит fallback на альтернативный транспорт\n* В приложении доступен эндпоинт `/metrics` на выделенном порте (порт основного приложения != порт `/metrics`)\n* compose-файл содержит настроенные сервисы `prom/prometheus` и `grafana/grafana-oss`\n* Создан параметризованный (`Dashboard Settings -\u003e Variables`) дашборд на основе стандартных метрик, параметризация сделана по имени приложения:\n   * RED-метрики\n   * количество используемой памяти в единицу времени с разбивкой по типу (если применимо)\n   * в PR прикреплены pql-запросы и скриншот\n* Создан дашборд на основе кастомных бизнес-метрик:\n   * Количество пользовательских сообщений в секунду\n   * График количества активных ссылок в БД по типу (github, stackoverflow)\n   * p50, p95, p99 времени работы одного scrape по типу (github, stackoverflow)\n* В проекте присутствуют `bot.Dockerfile` и `scrapper.Dockerfile`, которые собирают приложение в образ, сборка включена в CI/CD\n\n\n## Нефункциональные требования\n* Проект должен быть написан на `Java 23` с использованием `Spring Boot 3`.\n* Токен авторизации должен храниться в конфигурационном файле, недоступном для общего доступа \n* Запрещено подключать дополнительные библиотеки в шаблон\n* Поддерживается скреппинг вопросов StackOverflow и репозиториев GitHub\n* Не нужно парсить страницу (HTML), нужно работать с API: GitHub, StackOverflow\n* Запрещается использовать готовые SDK для доступа к API для HTTP-клиентов (GitHub, StackOverflow)\n   * Клиентов нужно написать руками\n   * По возможности используйте декларативные клиенты\n* Бот должен реализовывать концепт машины состояний (создание /track должно идти в формате диалога, а не команды):\n   * неверно: /track tag=work tag=hobby https://foo.bar/baz\n   * верно:\n      * \u003e /track https://foo.bar/baz (можно раздельно /track и отдельно ссылка)\n      * \u003c введите тэги (опционально)\n      * \u003e work hobby\n      * \u003c настройте фильтры (опционально)\n      * \u003e user:dummy type:comment\n* В тестах запрещено делать реальные вызовы к API внешних систем, нужно использовать заглушки (mocks)\n* Используйте типобезопасную конфигурацию\n* Используйте структурное логирование (добавление key-value значений к логу, вместо зашивания данных в поле message) в коде приложения\n* При проверке обновлений запрещено загружать все ссылки в память сразу\n* Логика планировщика (проверка ссылок) и отправки (уведомления) разнесены по разным сервисам \n   * Объявлен интерфейс для сервиса отправки уведомлений scrapper -\u003e bot\n   * Пока что будет одна реализация -- HTTP (в следующем ДЗ появится Kafka)\n* Запуск Postgres осуществляется\n   * через compose-файл для локальной разработки\n   * через testcontainers для автоматического тестирования\n* Миграции должны быть написаны на языке SQL\n* Для миграций используется Liquibase \n* Схема БД должна находиться в каталоге migrations/ в корне проекта\n* Запуск миграций должен выполняться собственноручно написанной функцией\n* Реализуйте 2 способа работы с БД: \"голый\" SQL и ORM. Например, у вас есть LinkService, тогда у него будет 2 имплементации: OrmLinkService и SqlLinkService. \n* Выбор способа работы с БД осуществляется через конфигурацию: access-type=SQL или access-type=ORM\n* Выбор транспорта происходит в конфигурационном файле при помощи свойства `app.message-transport: {Kafka, HTTP}`\n* Отправка уведомлений реализована как абстракция (сервис)\n* Уведомления в Kafka передаются в формате JSON \n* Бот делает инвалидацию кэша, если пользователь сделал изменение в списке своих отслеживаемых ссылок (добавил, удалил, изменил)\n* Параметры Timeout настраиваются в конфигурации\n* Параметры Rate Limiting настраиваются в конфигурации\n* Политика Retry задаётся в конфигурации и позволяет задать количество повторов и время между попытками (constant backoff)\n* Retry происходит только в случае если это имеет смысл: вам нужен настраиваемый список кодов, на которые происходит retry\n* Параметры Circuit Breaker настраиваются в конфигурации\n* Circuit Breaker настроен в режиме скользящего окна\n\n## Стандартные метрики\n![standard metrics red](img/metrics/standard-metrics-red.png)\n![standard metrics jvm](img/metrics/standard-metrics-jvm.png)\n\nФайл для импорта дашборда со стандартными метриками (также содержит все pql запросы):  [link-tracker-standard-metrics.json](files/link-tracker-standard-metrics.json)\n\n\n## Бизнес метрики\n![business metrics](img/metrics/business-metrics.png)\n\nФайл для импорта дашборда с бизнес метриками (также содержит все pql запросы):\n[link-tracker-business-metrics.json](files/link-tracker-business-metrics.json)\n\n\n## Публикация в k8s кластер:\n### Bot и Scrapper в пространстве имен application\n![application namespace](img/kubernetes/application-namespace.png)\n\n### Postgres, Redis и Liquebase Migration в пространстве имен db\n![db namespace](img/kubernetes/db-namespace.png)\n\n### Kafka в пространстве имен message-broker\n![message broker](img/kubernetes/message-broker.png)\n\n### Prometheus и Grafana в пространстве имен monitoring\n![monitoring-namespace](img/kubernetes/monitoring-namespace.png)\n\n## Справочная информация\n* [Запуск и использование](./docs/2.%20Разработка/setup-dev-env.md)\n* [Архитектура](./docs/1.%20Архитектура/high-level-design.md)\n* [Шаблон проекта](./HELP.md)\n* Диаграммы:\n   * [/help](./docs/1.%20Архитектура/diagrams/help-command.md)\n   * [/list](./docs/1.%20Архитектура/diagrams/list-command.md)\n   * [/notificationmode](./docs/1.%20Архитектура/diagrams/notification-mode-command.md)\n   * [/track](./docs/1.%20Архитектура/diagrams/track-command.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacenko-developer%2Flink-tracker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstacenko-developer%2Flink-tracker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacenko-developer%2Flink-tracker/lists"}