## [0.4.6-beta] - 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.4.5-beta] - 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.4-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.4.3-beta] - 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.3.2-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 v2**: Переход бэкенда на новую версию архитектуры (Clean Architecture).
- **Database Architecture**: Миграция хранилища на PostgreSQL (управление миграциями через Goose) и Redis для кэширования и лимитов.
- **CI/CD Pipeline**: Автоматизация сборки Docker-образов и деплоя через приватный registry Gitea.
---
## [0.2.1-alpha] - 2026-06-15
### Добавлено
- Базовый API конвертации HTML в чистый Markdown для LLM.
- Поддержка первичного веб-интерфейса на SvelteKit.
- Базовая обработка ошибок и первичная валидация входных URL.
---
## [0.1.0-alpha] - 2026-03-01
### Добавлено
- Инициализация проекта MarkClean, MVP мотора конвертации текста.