Files
markclean-doc/changelog.md
T
2026-09-26 14:53:34 +03:00

183 lines
20 KiB
Markdown
Raw 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.
## [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` — удаляет все `<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.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 мотора конвертации текста.