28 KiB
28 KiB
[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)RequireAuthmiddleware для/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
Миграция БД
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) обеспечивает уникальный ключ для каждой комбинации опций
- Уровень 1 — Raw HTML: сырой HTML кэшируется на 30 минут (ключ:
- 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расширено — добавлены опциональные поля:{ "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— заголовок сразу виден
- Для LLM:
Сценарии кэширования
| Сценарий | 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: Полноценный цикл аутентификации на
HttpOnlycookies, сброс пароля по 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 мотора конвертации текста.