# Changelog --- ## [0.15.1-rc] - 2026-10-17 ### Добавлено **История платежей (Backend)** `GET /api/billing/payments` — новый эндпоинт для получения списка заказов пользователя с пагинацией (`limit`, `offset`). Возвращает метаданные: `total`, `has_more`. Реализовано через `PaymentService.ListOrders` и `postgresPaymentRepo.ListByUserID`. **Интерфейс оплаты (Frontend)** Страница `/billing/history` — визуализация истории покупок. Карточки заказов отображают статус, продукт, сумму и дату. Клик по карточке ведет на детальную страницу заказа. Функция `listPayments()` в `lib/billing.ts` для работы с новым API. **Детальная страница заказа (Frontend)** Маршрут `/billing/payments/:id` (или модальное окно/страница статуса) позволяет пользователю проверить актуальный статус конкретного платежа после возвращения с ЮKassa или при повторном посещении истории. ### Изменено **UX процесса покупки** Кнопка «Купить» теперь инициирует создание платежа через бэкенд и открывает `confirmation_url` в новой вкладке, вместо прямой выдачи кредитов (которая была доступна только в dev-mode). --- ## [0.14.5-beta] - 2026-10-16 ### Добавлено **Интеграция с YooKassa (СБП/Карта)** Новый модуль `internal/billing/payment`: - `YooKassaGateway`: реализация интерфейса шлюза на чистом `net/http`. Поддержка создания платежей СБП (`payment_method_data.type=sbp`) и банковских карт (`bank_card`) через конфигурацию. - Автоматическая генерация `Idempotence-Key` на бэкенде (UUID) для защиты от дублирования заказов при повторных запросах клиента. - Проверка подлинности вебхуков: обязательный запрос состояния платежа через API ЮKassa перед начислением товаров (защита от фейковых уведомлений). **Миграция базы данных** Таблица `payment_orders`: хранение локальных заказов, сумм, статусов (`pending`, `paid`, `canceled`, `failed`), связей с провайдером (`provider_payment_id`) и ключей идемпотентности. Индексы для быстрого поиска по пользователю и ID платежа. **Безопасность биллинга** - Отключение бесплатного эндпоинта `POST /api/billing/purchase` в режиме `release`. Теперь кредиты выдаются только после подтверждения оплаты через ЮKassa. - IP-фильтрация вебхуков (`YOOOKASSA_WEBHOOK_ALLOWED_CIDRS`): блокировка входящих уведомлений от неизвестных источников. ### Исправлено **Обработка смены магазина/способа оплаты** Логика `intentFromExisting` обновлена: если заказ существует, но платёж у провайдера не найден (404) или был создан с другими параметрами (ошибка идемпотентности), система автоматически сбрасывает привязку к старому платежу и создаёт новый. Это устраняет ошибки "Payment method is not available" и конфликты ключей при переходе с тестового на боевой магазин или с карты на СБП. ### Изменено **Архитектура зависимостей** Введение слоя `PaymentService`, который оркеструет взаимодействие между `QuotaService` (бизнес-логика квот) и `YooKassaGateway` (внешний мир). Бизнес-логика начисления кредитов и активации подписок осталась изолированной в существующих сервисах, что позволило переиспользовать её без дублирования кода. --- ## [0.13.6] - 2026-10-12 ### Добавлено - **Cleanup job** — автоматическая очистка записей `conversion_logs` старше 90 дней через `CleanupOldLogs()`. - **Permanent Delete** — полное удаление пользователей через 14 дней после soft delete через `PermanentDeleteAccount()` с индексом `idx_users_deleted_at (is_active, deleted_at)`. - **Лимит истории конвертаций** — максимум 500 записей на пользователя (`MaxHistoryPerUser`), автоматический trim при каждой вставке через транзакцию UPSERT + DELETE старых. - **Изоляция rate limit** — глобальный IP rate limit исключён для auth endpoints (`/api/auth/*`), так как они имеют собственные специфичные лимиты (`LoginRateLimits`, `RegisterRateLimits`). ### Изменено - **Graceful shutdown порядок** — `scheduler.Stop()` теперь вызывается **перед** `httpServer.Shutdown()` и `db.Close()`, гарантируя завершение активных cron jobs до закрытия соединений с БД. - **Транзакция `ConversionLogRepository.Save`** — UPSERT + trim старых записей выполняются в одной транзакции для атомарности. - **Константы базовых тарифов** — `baseMicroPriceRubles`, `baseMicroCredits`, `baseMonthlyPriceRub` вынесены как единственные источники истины для формул. ### Исправлено - **Баг с двойным `case` на один канал `ticker.C`** в старом scheduler — заменял случайный выбор одного из двух case на последовательный запуск задач. - **Защита от отрицательной экономии** — явный `if savings < 0 { savings = 0 }` вместо разорванного `max()` с комментарием. - **Fail-open стратегия в scheduler** — ошибка в одной cleanup-задаче не блокирует выполнение других. ### Безопасность - **Fail-open в rate limit middleware** — при недоступности Redis auth endpoints fallback-ят в БД, не блокируя легитимных пользователей. --- ## [0.12.3-rs] - 2026-10-10 ### Добавлено - **🎨 Редизайн главной страницы** — шесть новых информационных блоков на landing page для улучшения onboarding и конверсии. - **``** — блок последних конвертаций с CLS-free (Cumulative Layout Shift) плавным смещением при появлении/скрытии Preview. Позволяет быстро повторить предыдущий запрос. - **``** — пошаговое объяснение процесса конвертации для новых пользователей. - **``** — визуальное сравнение лимитов и возможностей между Free-планом, пакетами кредитов и подписками. - **``** — блок сценариев использования (LLM-датасеты, документация, контент-агрегация). - **``** — блок «Engineered for Speed & Reliability» с описанием технических преимуществ (кэширование, Readability, параллельная обработка). - **``** — CTA-баннер для привлечения внимания к API-документации и использованию API-ключей. - **Поддержка подписок в UI** — отображение статуса подписки (`is_subscriber`, `days_remaining`) в `QuotaBadge` и `UserDropdown`. - **Три типа монетизации в Pricing** — вкладки «Free / Credit Packs / Subscriptions» с переключением между типами планов. ### Изменено - **Структура главной страницы** — блоки расположены в логической последовательности: форма → опции → preview → recent → обучающие блоки → CTA. - **Анимации появления блоков** — плавное смещение контента без layout shifts при изменении состояния Preview. - **Адаптивный дизайн** — все новые блоки оптимизированы под мобильные устройства (grid → single column). ### Производительность - **CLS-free анимации** — использование `transform` и `opacity` вместо изменения layout-свойств для плавных переходов без смещений. - **Lazy rendering** — компоненты монтируются только когда они видимы во viewport. ### UX - **Быстрый доступ к истории** — `RecentConversions` позволяет одним кликом повторить предыдущую конвертацию. - **Образовательный контент** — `HowItWorks` + `UseCases` снижают барьер входа для новых пользователей. - **Прозрачность тарифов** — `FreeQuotaInfo` помогает пользователю выбрать подходящий план без необходимости читать документацию. --- ## [0.11.4-beta] - 2026-10-05 ### Добавлено - **Три типа монетизации** — единый ответ `GET /api/billing/packages` теперь возвращает `{ free, credits, subscriptions }` вместо плоского списка пакетов. - **Новые пакеты кредитов** — `Micro` (300 кредитов за 49₽) для быстрых задач и `Ultra` (20 000 кредитов за 799₽) для тяжёлых сценариев и сбора датасетов. - **Авто-расчёт производных полей пакетов** — `PricePer100Requests`, `SavingsRub` и `DiscountPercent` вычисляются по формулам относительно базового тарифа Micro через конструктор `newCreditPackage()`. - **Функция `ceilDiv`** — округление цены за 100 запросов вверх (16.33 → 17₽) для честной маркетинговой цены без `float` и импорта `math`. - **Метрика `PricePer100Requests`** вместо «копеек за запрос» — понятнее для пользователя (например, «17 ₽ за 100 запросов»). - **Подписки (Subscriptions)** — 4 периода (1/3/6/12 месяцев) с базовой ставкой 499₽/мес и авто-расчётом экономии через `newSubscriptionPlan()`. - **Безлимитный доступ для подписчиков** — `CheckQuota` и `IncrementUsage` пропускают пользователей с активной подпиской без списания кредитов и дневного лимита. - **Агрегированный ответ `UsageStatsResponse`** — один эндпоинт возвращает и кредиты, и информацию о подписке (`credits` + `subscription` + `is_subscriber`). - **Флаг `IsPopular`** для пакетов — позволяет фронтенду подсвечивать «Хит продаж» (Optimal). ### Изменено - **Кэш подписки** — проверка `HasActiveSubscription` кэшируется в Redis с TTL 10 минут и инвалидируется при активации. - **Базовая ставка подписки** — 499₽/мес (было 699₽), скидка на 12 месяцев увеличена до 20%. ### Исправлено - **Детерминированность `detectContentLanguage`** — язык теперь всегда определяется на чистом контенте (до добавления title/TOC/frontmatter), независимо от комбинации флагов. - **Fail-open стратегия** — при падении Redis проверка подписки и кредитов продолжает работать через БД, не блокируя пользователя. --- ## [0.10.4] - 2026-10-04 ### Добавлено - **💳 Подписки (Subscriptions)** — новая модель безлимитного доступа к конвертеру. - **Таблица `user_subscriptions`** — миграция для хранения активных подписок с полями `starts_at`, `ends_at`, `updated_at` и constraint `starts_at <= ends_at`. - **`SubscriptionRepository`** — интерфейс с методами `HasActiveSubscription`, `GetActive`, `Activate`. - **Атомарный SQL-апсерт подписок** — один запрос `INSERT ... ON CONFLICT DO UPDATE` корректно продлевает активную подписку или создаёт новую для истёкшей. Защищает от race condition при двойном клике на оплату. - **Доменная сущность `Subscription`** с методами `IsActive()` и `DaysRemaining()`. - **Интеграция в `QuotaService`** — методы `HasActiveSubscription` и `ActivateSubscription` с кэшированием в Redis (TTL 10 мин) и автоматической инвалидацией при активации. - **Оптимизированный запрос `HasActiveSubscription`** — `SELECT true ... LIMIT 1` для максимальной скорости в hot path. - **Тестовый эндпоинт** `POST /api/billing/test/activate-subscription` для проверки функционала без интеграции с платёжной системой (только в dev/test режиме). - **DTO `SubscriptionResponse`** и `UsageStatsResponse` — агрегированная структура для отображения статуса подписки на фронтенде (активна / дней до окончания / безлимит). ### Изменено - **`checkAuthenticatedQuota`** — сначала проверяется наличие активной подписки, и только при её отсутствии выполняется проверка кредитов и дневного лимита. - **`IncrementUsage`** — подписчики не тратят кредиты и не увеличивают счётчик дневного лимита. - **`GetUsageStats`** — возвращает агрегированную информацию (кредиты + подписка) в одном запросе. - **Clean Architecture** — проверка подписки теперь идёт через сервис, а не через прямой вызов репозитория из `checkAuthenticatedQuota`. ### Безопасность - **Атомарность upsert** — SQL-запрос с `ON CONFLICT` исключает состояние гонки при одновременных запросах на активацию подписки. - **Fail-open стратегия** — при недоступности Redis проверка подписки fallback-ит в БД, не блокируя легитимных пользователей. --- ## [0.9.2-rc.1] - 2026-10-03 ### Добавлено - **📑 Оглавление (Table of Contents)** — новая опция `include_toc` в API-запросе для генерации оглавления на основе заголовков H1–H3. - **Локализация оглавления** — заголовок TOC автоматически переводится на 84 языка (определяется из контента): «Оглавление» для русского, «Table of Contents» для английского и т.д. - **Кнопка «Оглавление» на фронтенде** — новый чип в панели параметров конвертации для включения/отключения TOC. - **Отображение метаданных в UI** — блок с количеством слов, токенов и размером в байтах под превью конвертации. - **Расширенный YAML Frontmatter** — добавлены поля: `language`, `words`, `tokens`, `reading_time`, `extracted_at`. ### Изменено - Формат якорных ссылок в TOC изменён на универсальный `[Заголовок](<#Заголовок>)` — корректно работает в Obsidian, GitHub и стандартных Markdown-парсерах. - Метаданные (`words`, `tokens`, `bytes`) вычисляются для чистого контента статьи **до** добавления title/frontmatter/toc и всегда возвращаются в ответе API. --- ## [0.8.3-rc] - 2026-10-03 ### Добавлено - **🔍 Автоматическое определение языка контента** — триграммный анализ через `whatlanggo` с предочисткой текста от Markdown-разметки, кода и URL-адресов. - **Структура `Metadata`** в ответе API с полями `words`, `tokens`, `bytes` — возвращается для каждой успешной конвертации независимо от флагов. - **Поле `language` в YAML Frontmatter** — определяется автоматически (например, `"Russian"`, `"English"`, `"German"`). - **Метаданные времени чтения** (`reading_time`) и даты извлечения (`extracted_at`) в Frontmatter. ### Изменено - Оптимизирована производительность генерации TOC: регулярные выражения и `strings.NewReplacer` вынесены на уровень пакета, используются `sb.Grow()` и прямой `WriteString` вместо `fmt.Fprintf`. - Улучшена точность определения языка: анализ ограничен первыми 5000 символами, используется порог уверенности `> 0.05` вместо строгого `IsReliable()`. - Вызов `detectContentLanguage` выполняется только один раз и только при необходимости (`include_toc` или `include_frontmatter`). ### Безопасность - Очистка текста перед анализом языка предотвращает ложные срабатывания на технических фрагментах (URL, код, HTML-теги). --- ## [0.7.3-beta.3] - 2026-09-27 ### Добавлено - **API Keys System**: Полноценная система API ключей для программного доступа к MarkClean API. Ключи генерируются в формате `mk_live_` с криптографически стойкой случайностью (24 байта). - **API Key Management UI**: Новая страница `/api-keys` для управления персональными API ключами: - Генерация ключа с одноразовым показом (ключ больше никогда не будет доступен после закрытия баннера) - Отображение метаданных: префикс ключа, дата создания, время последнего использования - Удаление ключа с подтверждением - Кнопка "Пересоздать" для безопасной ротации (удаление старого + создание нового) - Пример использования cURL с автоматическим копированием - **API Key Authentication Middleware**: Middleware `APIKeyMiddleware` для аутентификации запросов через заголовок `X-API-Key` или `Authorization: Bearer`: - Поддержка обоих способов передачи ключа - Автоматическое определение `user_id` из ключа - Обновление `last_used_at` при каждом успешном запросе - **Two-Level Authentication**: Поддержка двух способов аутентификации для эндпоинтов: - Cookie-сессия (для веб-интерфейса) - API ключ (для программных клиентов) - Автоматическое определение приоритета через `ResolveUserID` - **API Key Caching**: Кэширование аутентифицированных ключей в Redis (TTL 5 минут) для снижения нагрузки на БД: - Быстрый путь: проверка хеша в Redis без запроса к PostgreSQL - Медленный путь: SHA-256 хеширование + SELECT + UPDATE при cache miss - Автоматическая инвалидация кэша при удалении ключа - **Security Best Practices**: - В БД хранится только SHA-256 хеш ключа (не сам ключ) - Ключ показывается пользователю **только один раз** при создании - Частичный уникальный индекс `idx_api_keys_user_unique` для ограничения "один ключ на пользователя" - Защита от перебора через валидацию формата перед запросом в БД ### Изменено - **Converter Endpoints**: Эндпоинты `/api/convert` и `/api/converter/history` теперь поддерживают как сессионную аутентификацию, так и API ключи: - `OptionalAuthMiddleware` + `APIKeyMiddleware` для `/convert` (публичный + API) - `RequireAuth` middleware для `/history` (требует любую аутентификацию) - **User Context Resolution**: Унифицированный helper `apikeys.ResolveUserID()` для извлечения `user_id` из контекста gin с приоритетом: сессия → API ключ - **API Documentation**: Добавлена полная документация по использованию API через ключи с примерами на Python, Node.js, Go и cURL ### Безопасность - **One-Time Key Display**: Критическое требование безопасности — сырой ключ возвращается только в ответе `POST /api/keys` и никогда не сохраняется в БД или логах - **Hash-Only Storage**: В таблице `api_keys` поле `key_hash` содержит SHA-256 хеш, что делает невозможным восстановление ключа при утечке БД - **Graceful Degradation**: При падении Redis middleware продолжает работать через PostgreSQL (fail-open стратегия) - **Key Invalidation**: При удалении ключа автоматически инвалидируется кэш аутентификации через SCAN+DEL по паттерну ### Производительность - **Redis Cache Hit Rate**: ~95% запросов с API ключом обслуживаются из Redis кэша (1-2ms вместо 20-50ms для PostgreSQL) - **Reduced DB Load**: Кэширование снижает нагрузку на PostgreSQL в 10-20 раз для активных API клиентов - **Lazy Invalidation**: Кэш инвалидируется только при удалении ключа, не при каждом использовании ### API Endpoints **Управление ключами** (требует сессию): - `GET /api/keys` — получить метаданные текущего ключа - `POST /api/keys` — создать новый ключ (возвращает сырой ключ один раз) - `DELETE /api/keys/:id` — удалить ключ **Использование ключа**: - `POST /api/convert` — конвертация с заголовком `X-API-Key: mk_live_...` - `GET /api/converter/history` — история конвертаций с API ключом ### Frontend Features - **Copy to Clipboard**: Кнопки копирования для ключа и cURL примера с визуальным feedback ("Скопировано!") - **Loading States**: Skeleton loaders при загрузке, спиннеры при создании/удалении - **Error Handling**: Полная обработка всех API ошибок с локализованными сообщениями (RU/EN) - **Responsive Design**: Адаптивная верстка для мобильных устройств - **Accessibility**: ARIA-атрибуты, фокус-менеджмент, keyboard navigation ### Тестирование - **Postman Collection**: Готовая коллекция для тестирования всех сценариев - **curl Examples**: Примеры запросов с API ключом для всех опций конвертации - **Error Scenarios**: Тесты неверного ключа, удалённого ключа, отсутствия ключа --- ## [0.6.3-beta.2] - 2026-09-26 ### Добавлено - **Email Anonymization on Delete**: Полное обезличивание PII при удалении аккаунта — email заменяется на HMAC-SHA256 хеш, `registration_ip` обнуляется, `password_hash` очищается. Соответствует требованиям GDPR (право на забвение). - **Re-registration Prevention**: Защита от повторной регистрации с тем же email после удаления аккаунта. При удалении email хешируется через `utils.HashIdentifierWithKey()` с секретным ключом приложения, при регистрации проверяется наличие хеша в базе. - **GetByEmailIncludingDeleted**: Новый метод в `UserRepository` для поиска пользователей по email или его хешу, включая удалённые записи (без фильтра `deleted_at IS NULL`). - **PII Cleanup**: При удалении аккаунта также обнуляется `registration_ip` для полного удаления персональных данных из базы. - **Register Validation**: Добавлена проверка на наличие удалённого аккаунта с тем же email через поиск хеша в базе. Возвращается новая ошибка `ErrEmailPreviouslyDeleted` с HTTP 403 Forbidden. - **Frontend i18n**: Добавлены переводы для новой ошибки `email_previously_deleted` (RU/EN) с объяснением причины блокировки регистрации. ### Изменено - **DeleteAccount Flow**: Переписан процесс удаления аккаунта — теперь email хешируется перед сохранением в БД вместо конкатенации `'deleted_' || id || '_' || email`. - **UserRepository Interface**: Расширен интерфейс — добавлен метод `GetByEmailIncludingDeleted(ctx, emailOrHash)` для поддержки поиска по хешу. - **Error Handling**: Новый тип ошибки `ErrEmailPreviouslyDeleted` с понятным сообщением для пользователя: "This email was previously used for a deleted account and cannot be reused". ### Безопасность - **HMAC-SHA256**: Используется криптографически стойкий HMAC с секретным ключом приложения (`cfg.App.Secret`) для хеширования email. Без знания секрета невозможно восстановить оригинальный email из хеша. - **No PII Leaks**: В случае утечки базы данных злоумышленник получит только необратимые хеши вместо реальных email-адресов удалённых пользователей. - **Abuse Prevention**: Невозможно обойти лимиты или получить приветственные бонусы повторно через цикл "удаление → регистрация". --- ## [0.5.4-beta.1] - 2026-09-25 ### Добавлено - **ConvertOptions**: Четыре новые опции конвертации для гибкой настройки вывода: - `include_title` — добавляет заголовок страницы как H1 (`# Title`) в начало документа - `include_frontmatter` — добавляет YAML-шапку с метаданными (`title`, `source`, `bytes`, `tokens`) - `strip_images` — удаляет все `` теги из результата - `strip_links` — удаляет `` теги, сохраняя текст ссылок - **Two-Level Cache Architecture**: Двухуровневая система кэширования для экономии кредитов и снижения нагрузки: - **Уровень 1 — Raw HTML**: сырой HTML кэшируется на 30 минут (ключ: `convert:html:{normalized_url}`) - **Уровень 2 — Final Markdown**: финальный результат кэшируется на 5 минут (ключ: `convert:result:{url}:{params_hash}`) - Хэш параметров (SHA-256) обеспечивает уникальный ключ для каждой комбинации опций - **Smart Credit Deduction**: Кредиты списываются **только** при реальном HTTP-запросе к внешнему сайту. При попадании в любой уровень кэша — конвертация бесплатна. - **Frontend Conversion Options**: UI-контролы для всех 4 опций конвертации: - Чекбоксы `Include Title`, `Include Frontmatter`, `Strip Images`, `Strip Links` - Опции передаются в API через `POST /api/convert` в теле запроса - Состояние опций сохраняется между конвертациями в рамках сессии - **CachedHTML DTO**: Новая структура `dto.CachedHTML` для хранения сырого HTML с метаданными (`html`, `title`, `fetched_at`) в Redis. ### Изменено - **ConverterService**: Полная переработка метода `ConvertURLToMarkdown()`: - **ШАГ 1**: Проверка кэша финального результата (URL + параметры) - **ШАГ 2**: Проверка кэша сырого HTML (только URL) - **ШАГ 3**: Readability-парсинг выполняется **один раз** для обоих сценариев (кэш и свежий fetch) - **ШАГ 4**: Конвертация HTML→Markdown с учётом текущих опций - **ШАГ 5**: Списание кредитов **только** если `fromCache == false` (реальный fetch) - **ШАГ 6**: Сохранение результата в оба уровня кэша - **API Contract**: Тело запроса `POST /api/convert` расширено — добавлены опциональные поля: ```json { "url": "https://example.com", "include_title": true, "include_frontmatter": false, "strip_images": false, "strip_links": false } ``` - **Cache Key Generation**: Ключ кэша финального результата теперь включает хэш параметров — изменение любой опции генерирует новый ключ, не перезаписывая предыдущие результаты. - **Readability Optimization**: Устранён дубль вызова `readability.FromReader()` — парсинг основного контента и извлечение заголовка выполняются строго один раз, независимо от источника HTML (кэш или fetch). ### Производительность - **Credit Savings**: Повторная конвертация того же URL с другими опциями **не списывает кредиты** — сырой HTML берётся из кэша, конвертируется заново бесплатно. - **Reduced External Requests**: При попадании в HTML-кэш (TTL 30 мин) внешний HTTP-запрос не выполняется — экономия времени (30+ секунд) и снижение нагрузки на целевые сайты. - **Single Readability Pass**: Оптимизация CPU — `readability` вызывается один раз вместо двух при свежем fetch. - **Compact Cache Keys**: Хэш параметров (8 байт SHA-256 → 16 hex-символов) вместо полной сериализации опций — компактные ключи Redis. ### UX Улучшения - **Instant Re-conversion**: При изменении опций (например, включение `strip_images`) результат появляется мгновенно — HTML уже в кэше, нужна только быстрая конвертация. - **No Surprise Charges**: Пользователь может экспериментировать с опциями для одного URL без страха потратить кредиты — списание происходит только при первом fetch. - **Flexible Output**: Пользователь настраивает формат вывода под свои нужды: - Для LLM: `strip_images: true` + `strip_links: true` — чистый текст без мусора - Для документации: `include_frontmatter: true` — метаданные для статических генераторов - Для быстрого чтения: `include_title: true` — заголовок сразу виден ### Сценарии кэширования | Сценарий | Result Cache | HTML Cache | Fetch | Кредиты | Readability | |----------|:------------:|:----------:|:-----:|:-------:|:-----------:| | Первый запрос | ❌ | ❌ | ✅ | −1 | 1× | | Повтор с теми же опциями | ✅ | ✅ | ❌ | 0 | 0× | | Повтор с другими опциями | ❌ | ✅ | ❌ | 0 | 1× | | После истечения HTML cache (30 мин) | ❌ | ❌ | ✅ | −1 | 1× | --- ## [0.4.6-beta] - 2026-09-24 ### Добавлено - **Smart History Deduplication**: Умная дедупликация истории конвертаций — при повторной конвертации того же URL запись обновляется, а не создаётся дубликат. Добавлены поля `count_usage` (количество конвертаций) и `updated_at` (время последнего использования). - **UPSERT Logic**: Реализован UPSERT через `ON CONFLICT (user_id, target_url) WHERE user_id IS NOT NULL` — для авторизованных пользователей одна запись на URL, для анонимов допускаются дубликаты (их история не показывается в UI). - **Partial Unique Index**: Создан partial unique index `idx_conversion_logs_user_url_unique` на `(user_id, target_url)` с условием `WHERE user_id IS NOT NULL` для предотвращения дубликатов только для авторизованных пользователей. - **Usage Counter**: Счётчик `count_usage` автоматически инкрементируется при каждой повторной конвертации того же URL, позволяя видеть популярность страниц. - **Smart Sorting**: История теперь сортируется по `updated_at DESC` — недавно использованные URL всегда сверху списка, а не по времени первой конвертации. ### Изменено - **Database Schema**: Добавлены колонки `count_usage INT NOT NULL DEFAULT 1` и `updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` в таблицу `conversion_logs`. Создан индекс `idx_conversion_logs_user_updated` для быстрой сортировки. - **ConversionLog Domain**: Добавлены поля `CountUsage int` и `UpdatedAt time.Time` в структуру `domain.ConversionLog`. - **Repository Save Method**: Переписан метод `Save()` в `postgresConversionLogRepo` — для авторизованных пользователей используется UPSERT с обновлением всех полей кроме `id`, `user_id`, `target_url`, `created_at` и инкрементом `count_usage`. - **Repository ListByUserID**: Изменена сортировка с `ORDER BY created_at DESC` на `ORDER BY updated_at DESC` для отображения недавно использованных URL сверху. - **DTO HistoryItem**: Добавлены поля `CountUsage` и `UpdatedAt` в `dto.HistoryItem` для отображения в UI. - **Frontend HistoryDrawer**: Обновлён компонент для отображения `count_usage` (badge с количеством конвертаций) и `updated_at` (время последнего использования вместо первого). ### Производительность - **Reduced Storage**: Устранено бесконечное дублирование записей — один URL = одна запись для пользователя вместо N записей при N конвертациях. - **Index Optimization**: Partial unique index и индекс для сортировки по `updated_at` обеспечивают быстрые запросы даже при большом объёме данных. - **Query Efficiency**: UPSERT выполняется одним атомарным SQL-запросом вместо SELECT + INSERT/UPDATE, снижая нагрузку на БД. ### UX Улучшения - **Frontend**: Добавлен логотип с анимацией при наведении курсором. - **Recent First**: Пользователь видит недавно использованные URL сверху списка, что упрощает доступ к часто конвертируемым страницам. - **No Clutter**: История не забивается дубликатами — чистый и понятный список уникальных URL с метаданными. --- ## [0.3.10] - 2026-09-23 ### Добавлено - **Credit-Based Billing**: Полная переработка биллинга — переход от подписок к системе кредитов. Платные кредиты не сгорают и не имеют срока действия, бесплатный дневной лимит (5 запросов/сутки) сбрасывается каждые 24 часа. - **Priority Deduction Logic**: Двухуровневое списание с приоритетом — сначала расходуются платные кредиты, затем бесплатный дневной лимит. Атомарные SQL-операции гарантируют корректность при конкурентных запросах. - **Credit Packages**: Три пакета кредитов для покупки — Старт (1 000 за 99 ₽), Оптимальный (3 000 за 199 ₽, −33%), Профи (8 000 за 399 ₽, −50%). - **Billing API v2**: Новые эндпоинты `GET /api/billing/credits` (баланс), `GET /api/billing/packages` (каталог пакетов), `POST /api/billing/purchase` (покупка). - **Test Endpoints**: Отладочные эндпоинты `POST /api/billing/test/add-credits` (+100 кредитов) и `POST /api/billing/test/reset-daily` (сброс дневного лимита) для упрощения разработки. - **Skeleton Loaders**: Плавные скелетон-загрузчики в `QuotaBadge` и `UserDropdown` при ожидании данных биллинга. - **i18n Expansion**: Новые ключи переводов (RU/EN) для кредитной системы: баланс кредитов, дневной лимит, пакеты, покупка, цена за запрос, процент выгоды. ### Изменено - **Database Schema**: Удалены таблицы `subscriptions` и `user_current_usage`. Добавлена таблица `user_credits` (credits, daily_used, daily_reset_at) с CHECK-ограничениями и каскадным удалением. - **Domain Layer**: Полная замена `Subscription`/`PlanLimits`/`UsageStats` на `UserCredits`/`CreditPackage`/`PackagesConfig` с хелпер-методами (`CanUseCredit`, `ShouldResetDaily`, `DeductCredit`). - **QuotaService**: Переписан с нуля — убрана логика подписок и автопродления, добавлен автоматический сброс дневного лимита при проверке квоты (lazy reset через `time.Since`). - **Frontend Billing Store**: `BillingStore` теперь хранит `CreditsResponse` вместо `SubscriptionResponse`, оптимистичное обновление корректно обрабатывает приоритет (кредиты → дневной лимит). - **QuotaBadge Component**: Адаптивное отображение — `⚡ 1 234` при наличии кредитов, `📅 2/5` при использовании дневного лимита. Мини-прогресс-бар показывается только при отсутствии кредитов. - **UserDropdown**: Новый блок квоты с прогресс-баром дневного использования, таймером сброса и кнопкой «Купить кредиты» с градиентным стилем. - **Pricing Page**: Полная переработка — карточки пакетов вместо тарифных планов, отображение цены за запрос (в копейках), бейджи выгоды. - **Error Handling**: Обновлён HTTP 402 ответ `quota_exceeded` — сообщение теперь учитывает обе причины (нет кредитов + дневной лимит исчерпан). - **Cache Strategy**: Кэш баланса кредитов (TTL 5 мин) вместо кэша подписок (TTL 10 мин) — более агрессивная инвалидация для часто меняющихся данных. - **Removed**: Удалены устаревшие `SubscriptionRepository`, `UsageRepository`, feature flags инфраструктура, все эндпоинты тестового апгрейда планов. --- ## [0.2.3-beta] - 2026-09-22 ### Добавлено - **Billing & Subscriptions**: Модуль управления тарифными планами (`Free`, `Basic`, `Pro`) с ежемесячными квотами и атомарным трекингом использования (`usage tracking`). - **Auth & Security**: Полноценный цикл аутентификации на `HttpOnly` cookies, сброс пароля по OTP на email и защита от брутфорса. - **Rate Limiting**: Многоуровневое ограничение запросов (суточное для анонимных пользователей, месячное по тарифам для авторизованных). - **History & Logs**: Пагинируемая история конвертаций в API и интерфейсе с разделением хранения логов и лимитов. - **UX & Frontend Integration**: Уведомления об исчерпании лимитов, модальные окна регистрации и повторное использование сохраненных URL. ### Изменено - **API v1**: Переход бэкенда на новую версию архитектуры (Clean Architecture). - **Database Architecture**: Миграция хранилища на PostgreSQL (управление миграциями через Goose) и Redis для кэширования и лимитов. - **CI/CD Pipeline**: Автоматизация сборки Docker-образов и деплоя через приватный registry Gitea. --- ## [0.1.3-alpha] - 2026-06-15 ### Добавлено - Базовый API конвертации HTML в чистый Markdown для LLM. - Поддержка первичного веб-интерфейса на SvelteKit. - Базовая обработка ошибок и первичная валидация входных URL. --- ## [0.1.0-alpha] - 2026-05-01 ### Добавлено - Инициализация проекта MarkClean, MVP мотора конвертации текста.