Files
2026-10-17 20:11:13 +03:00

441 lines
47 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 и конверсии.
- **`<RecentConversions />`** — блок последних конвертаций с CLS-free (Cumulative Layout Shift) плавным смещением при появлении/скрытии Preview. Позволяет быстро повторить предыдущий запрос.
- **`<HowItWorks />`** — пошаговое объяснение процесса конвертации для новых пользователей.
- **`<FreeQuotaInfo />`** — визуальное сравнение лимитов и возможностей между Free-планом, пакетами кредитов и подписками.
- **`<UseCases />`** — блок сценариев использования (LLM-датасеты, документация, контент-агрегация).
- **`<FeaturesOverview />`** — блок «Engineered for Speed & Reliability» с описанием технических преимуществ (кэширование, Readability, параллельная обработка).
- **`<ApiDocBanner />`** — 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_<base64url>` с криптографически стойкой случайностью (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` — удаляет все `<img>` теги из результата
- `strip_links` — удаляет `<a>` теги, сохраняя текст ссылок
- **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 мотора конвертации текста.