mod: планы по моделям, хранению данных, защите от накруток в голосовании.

This commit is contained in:
2026-08-17 23:24:08 +03:00
parent 6c23be1f4b
commit 4005373755
+96 -34
View File
@@ -327,48 +327,110 @@ pixels = image.quantize(colors=256) # или custom median-cut
HTMX: форма с `hx-post`, `hx-encoding="multipart/form-data"` → SVG вставляется в `#preview`. HTMX: форма с `hx-post`, `hx-encoding="multipart/form-data"` → SVG вставляется в `#preview`.
### 4. Политика хранения ### 4. Политика хранения, приватность и модели
**Исходные изображения:** удаляются сразу после генерации (не хранятся вообще). #### 4.1. Принципы приватности и Zero-PII (Без регистрации)
- **Полное отсутствие регистрации для посетителей:** Пользователи не создают аккаунты, не вводят email, пароли или личные данные. Единственный пользователь в `auth.User` — администратор сайта для входа в Django Admin.
- **Исходные растровые изображения (JPG/PNG/WEBP):** Обрабатываются исключительно в оперативной памяти (через буфер `BytesIO`) и **не сохраняются на диск сервера вообще**.
- **Лайки и учет голосов:**
- **Бэкенд:** Голоса учитываются анонимно через связку `(item_id, fingerprint)`. Поле `fingerprint` формируется как необратимый SHA-256 хэш: `sha256(IP + User-Agent + SECRET_KEY)`. Это защищает от примитивной накрутки через очистку кук или инкогнито и не сохраняет персональные данные (Zero-PII).
- **Фронтенд:** Для исключения переполнения cookies и сетевого оверхеда список лайкнутых картин хранится локально на клиенте в **`localStorage`** (до 5–10 МБ). На базе Alpine.js выполняется мгновенная индикация проголосованных работ.
- **Соответствие GDPR / 152-ФЗ:** Персональные данные не собираются и не хранятся.
**Публичные SVG (опционально, по желанию пользователя):** #### 4.2. Генерация названий (AI & Fallback Title)
``` У каждой сгенерированной картины обязательно должно быть название (title):
public/media/temp/{hash_16символов}.svg - **Основной способ:** Вызов через **OpenRouter API** с отправкой сильно сжатой миниатюры (thumbnail) исходного изображения в мультимодальную LLM (VLM) для распознавания сюжета и генерации забавного гипно-названия (в стиле Гипножабы/мемов).
``` - **Резервный способ (Fallback):** Если API OpenRouter недоступен, закончились квоты или таймаут — название генерируется локально из случайного набора гипнотических слов, эпитетов и фраз («Мерцающий транс #42», «Глаз Гипножабы», «Астральный вихрь» и т.п.).
- TTL: **14 дней**
- URL: `/view/{hash}` — прямая ссылка, без индексации
- Автоматическая очистка: `manage.py cleanup_temp --older-than 1209600` → crontab
**Галерея (FeaturedSVG):** #### 4.3. Стратегия хранения и умной очистки (Smart Retention)
- Администратор/promote: лучшие SVG переезжают из `temp/` в `gallery/` Проект не выступает постоянным файлообменником или бесплатным CDN. Пользователь скачивает сгенерированный SVG для личного использования.
- Хранятся вечно (лучшая коллекция халфтонов) Для оптимизации диска на сервере используется динамическая очистка на основе рейтинга популярности («гравитации»), а не слепой 14-дневный таймер:
- Прямая ссылка: `/gallery/{hash}`
- `/gallery` — страница галереи со всеми (пагинация) $$\text{Score} = \frac{\text{Likes} + \text{Bonus}_{\text{moderator}}}{(\text{Age in hours} + 2)^\gamma}$$
- **Квота хранилища:** Задается жесткий лимит объема (например, 500 МБ или N тысяч файлов).
- **Очистка (`manage.py cleanup_storage`):** При превышении лимита удаляются файлы с наименьшим `Score`.
- **Иммунитет к удалению:** Работы, отмеченные модератором (`is_curated=True` / `Bonus`), и закрепленные промо-картины сохраняются бессрочно.
#### 4.4. SEO, шеринг и коммерциализация
- **Вшивание ссылки в SVG (Zero-cost Branding):** В скачиваемый SVG на этапе генерации добавляется мета-комментарий со ссылкой на генератор (`<!-- Generated by Hypn0 Generator (https://hypn0.ru) -->` и `<metadata>`).
- **Страница просмотра (`/v/<hash>`):** Для внешнего шеринга и индексации поисковиками генерируется легкая HTML-страница с OpenGraph разметкой и кнопкой «Открыть настройки в генераторе».
- **Возможность промо/коммерциализации:** Поддержка опциональных полей (промо-ссылка, бейдж автора/спонсора, счетчик кликов).
#### 4.5. Блог, статические страницы и типографика (HTML + `etpgrf`)
- **Отказ от конвертации Markdown «на лету»:** Вместо оверхеда с парсингом Markdown статьи и инфо-страницы хранятся напрямую в формате **HTML**.
- **Очистка HTML в админке:** При вводе/сохранении в Django Admin контент проходит валидацию и санитизацию (очистку от вредоносного или мусорного HTML/инлайн-стилей).
- **Типографирование через `etpgrf`:** Для безупречной верстки текста (неразрывные пробелы, висячая пунктуация, кавычки-елочки, тире) используется библиотека типографирования **`etpgrf`**.
#### 4.6. Архитектура моделей
**Модель `FeaturedSVG`:**
```python ```python
class FeaturedSVG(models.Model): class HalftoneItem(models.Model):
svg_hash = models.CharField(max_length=16, unique=True) """
original_temp_path = models.CharField(max_length=512) Единая модель для генераций, публичных шеров и витрины галереи.
gallery_path = models.CharField(max_length=512) """
params_json = models.JSONField(default=dict) hash_id = models.CharField(max_length=16, unique=True, db_index=True)
votes = models.PositiveIntegerField(default=0) title = models.CharField(max_length=150, verbose_name="Название картины")
promoted_at = models.DateTimeField(auto_now_add=True) svg_file = models.FileField(upload_to="svg/%Y/%m/")
promoted_by = models.ForeignKey('auth.User', null=True, blank=True)
# Снимок параметров для возможного клонирования настроек
params_json = models.JSONField(default=dict, blank=True)
file_size = models.PositiveIntegerField(default=0, help_text="Размер файла в байтах")
# Популярность и модерация
likes_count = models.PositiveIntegerField(default=0, db_index=True)
views_count = models.PositiveIntegerField(default=0)
reports_count = models.PositiveIntegerField(default=0) reports_count = models.PositiveIntegerField(default=0)
score = models.FloatField(default=0.0, db_index=True, help_text="Рейтинг для Smart Retention")
is_curated = models.BooleanField(default=False, help_text="Выбор модератора / галерея (иммунитет к удалению)")
is_public = models.BooleanField(default=True, help_text="Доступность по прямой ссылке")
# Промо-блок (на перспективу)
promo_url = models.URLField(blank=True, default="", verbose_name="Промо-ссылка")
promo_title = models.CharField(max_length=100, blank=True, default="", verbose_name="Бейдж/автор")
promo_clicks = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
class HalftoneVote(models.Model):
"""
Анонимный учет голосов (лайков) без сохранения ПДн.
"""
item = models.ForeignKey(HalftoneItem, on_delete=models.CASCADE, related_name="votes")
fingerprint = models.CharField(
max_length=64,
db_index=True,
help_text="SHA256(IP + User-Agent + SECRET_KEY) для базовой защиты от накрутки"
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=["item", "fingerprint"], name="unique_item_fingerprint_vote")
]
class BlogPost(models.Model):
"""
Статьи блога, документация и инфо-страницы (Privacy Policy и др.).
+ виртуальные поля для управления типографом etpgrf
"""
title = models.CharField(max_length=200, help_text="HTML-заголовок, очищенный и типографированный etpgrf")
slug = models.SlugField(max_length=200, unique=True)
summary = models.TextField(max_length=500, blank=True, help_text="HTML-контент тизера, очищенный и типографированный etpgrf")
content = models.TextField(help_text="HTML-контент статьи, очищенный и типографированный etpgrf")
cover_image = models.ImageField(upload_to="blog/covers/%Y/", blank=True, null=True)
is_published = models.BooleanField(default=False)
published_at = models.DateTimeField(null=True, blank=True, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
``` ```
**Report / жалоба:**
- `/report/{hash}` — кнопка "Пожаловаться" на странице просмотра
- Просто счётчик `reports_count` в модели (или просто scan для ручного удаления)
- После жалобы — админ проверяет и удаляет вручную
**Публичный режим:**
Все генерации публичны:
- Любой SVG доступен к скачиванию
- SVG показываются в браузере по `temp/` или `/gallery`, но доступны только `/hypn0/` с hash-именем
- В браузере, показывается "Поделиться ссылкой"
## Шаги реализации ## Шаги реализации
### Шаг 1. Структура каталогов ### Шаг 1. Структура каталогов