Files
markclean-doc/changelog.md
T
2026-10-02 22:23:56 +03:00

320 lines
31 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.
# Changelog
## [0.5.8-beta] - 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.5.7-beta] - 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.5.6-beta] - 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
### Миграция БД
```sql
CREATE TABLE api_keys (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL DEFAULT 'Default Key',
key_prefix VARCHAR(50) NOT NULL,
key_hash TEXT NOT NULL,
last_used_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_api_keys_user_unique
ON api_keys(user_id)
WHERE is_active = TRUE;
CREATE INDEX idx_api_keys_hash
ON api_keys(key_hash)
WHERE is_active = TRUE;
```
### Тестирование
- **Postman Collection**: Готовая коллекция для тестирования всех сценариев
- **curl Examples**: Примеры запросов с API ключом для всех опций конвертации
- **Error Scenarios**: Тесты неверного ключа, удалённого ключа, отсутствия ключа
---
## [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 v1**: Переход бэкенда на новую версию архитектуры (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 мотора конвертации текста.