mod: про модели и хранение данных...
This commit is contained in:
+110
-81
@@ -51,21 +51,21 @@
|
|||||||
│ │ ├── 404.html
|
│ │ ├── 404.html
|
||||||
│ │ └── 500.html
|
│ │ └── 500.html
|
||||||
│ │
|
│ │
|
||||||
│ └── halftone/ # Django app
|
│ └── hypn0_site/ # Основное Django app
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ ├── urls.py
|
│ ├── urls.py
|
||||||
│ ├── views.py # главная + генерация
|
│ ├── views.py # главная + генерация
|
||||||
│ ├── forms.py # формы параметров
|
│ ├── forms.py # формы параметров
|
||||||
│ ├── models.py # FeaturedSVG + votes
|
│ ├── models.py # TbHypn0Item, TbVote, TbBlogPost
|
||||||
│ ├── services/
|
│ ├── services/
|
||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ └── halftone.py # ядро из main.py
|
│ │ └── halftone.py # ядро генерации
|
||||||
│ ├── templates/
|
│ ├── templates/
|
||||||
│ │ └── halftone/
|
│ │ └── halftone/
|
||||||
│ │ └── index.html
|
│ │ └── index.html
|
||||||
│ └── management/
|
│ └── management/
|
||||||
│ └── commands/
|
│ └── commands/
|
||||||
│ ├── cleanup_temp.py # очистка temp (14 дней)
|
│ ├── cleanup_storage.py # очистка по рейтингу Smart Retention
|
||||||
│ └── cleanup_reports.py # удаление по жалобам
|
│ └── cleanup_reports.py # удаление по жалобам
|
||||||
│
|
│
|
||||||
├── .gitea/workflows/ # CI/CD
|
├── .gitea/workflows/ # CI/CD
|
||||||
@@ -115,12 +115,12 @@ def generate_halftone_svg(
|
|||||||
```python
|
```python
|
||||||
# Главная — GET → рендерим index.html
|
# Главная — GET → рендерим index.html
|
||||||
# Генерация — POST (HTMX) → возвращаем SVG фрагмент
|
# Генерация — POST (HTMX) → возвращаем SVG фрагмент
|
||||||
# Скачивание — GET /download/{temp_id} → отдаём файл + удаляем исходник
|
# Скачивание — GET /download/{hash_id} → отдаём файл
|
||||||
# Просмотр публичного — GET /view/{hash} → отдаём SVG из temp/
|
# Просмотр публичного — GET /view/{hash_id} → отдаём страницу просмотра
|
||||||
# Галерея — GET /gallery → список FeaturedSVG
|
# Галерея — GET /gallery → список TbHypn0Item
|
||||||
# Просмотр в галерее — GET /gallery/{hash}
|
# Просмотр в галерее — GET /gallery/{hash_id}
|
||||||
# Report — POST /report/{hash} → increment reports_count
|
# Vote/Report — POST /gallery/{hash_id}/vote → учет голоса в TbVote (Like/Claim)
|
||||||
# Promote (admin) — админ переносит SVG из temp → gallery
|
# Promote (admin) — модератор повышает i_level (Level.LEVEL_2 или Level.IMMORTAL)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Фронтенд (`hypn0/halftone/templates/halftone/index.html`)
|
### 3. Фронтенд (`hypn0/halftone/templates/halftone/index.html`)
|
||||||
@@ -335,8 +335,17 @@ HTMX: форма с `hx-post`, `hx-encoding="multipart/form-data"` → SVG вс
|
|||||||
- **Куки-согласие и идентификация устройства (Cookie-токен):**
|
- **Куки-согласие и идентификация устройства (Cookie-токен):**
|
||||||
- При согласии с правилами («Подчиниться Гипножабе!» в `allow-tracking.html`) клиенту выставляется долгоживущая анонимная кука устройства `hypn0_vid` со случайным UUID (`Max-Age=2 года`, `SameSite=Lax`, `HttpOnly`).
|
- При согласии с правилами («Подчиниться Гипножабе!» в `allow-tracking.html`) клиенту выставляется долгоживущая анонимная кука устройства `hypn0_vid` со случайным UUID (`Max-Age=2 года`, `SameSite=Lax`, `HttpOnly`).
|
||||||
- **Голосование доступно только при наличии согласия на куки:** Если посетитель не принял соглашение (кука отсутствует), голосование заблокировано интерфейсом и отклоняется бэкендом (с требованием подчиниться Гипножабе).
|
- **Голосование доступно только при наличии согласия на куки:** Если посетитель не принял соглашение (кука отсутствует), голосование заблокировано интерфейсом и отклоняется бэкендом (с требованием подчиниться Гипножабе).
|
||||||
- **Лайки и учет голосов:**
|
- **Учет голосов, защита от накрутки и определение авторства (`TbVote`):**
|
||||||
- **Бэкенд:** Голоса учитываются анонимно через связку `(item_id, fingerprint)`. Поле `fingerprint` формируется как необратимый хэш: `sha256(visitor_uuid + SECRET_KEY)`. Это надежно разделяет разные компьютеры в одной корпоративной сети/NAT и не содержит открытых ПДн (Zero-PII).
|
- **Бэкенд:** Голоса учитываются анонимно через модель `TbVote` со связкой `(k_item, s_fingerprint)`. Поле `s_fingerprint` формируется как необратимый хэш: `sha256(visitor_uuid + SECRET_KEY)`. Это надежно разделяет разные компьютеры в одной корпоративной сети/NAT и не содержит открытых ПДн (Zero-PII).
|
||||||
|
- **Направление голоса (`i_direction` / `Direction`):**
|
||||||
|
- `Direction.LIKE` (`+1`) — обычный лайк посетителя.
|
||||||
|
- `Direction.CLAIM` (`-2`) — жалоба / клейм на неподобающий контент.
|
||||||
|
- `Direction.AUTHOR` (`+2`) — голос создателя картины.
|
||||||
|
- **Идентификация автора через `Direction.AUTHOR`:**
|
||||||
|
- При генерации картины в `TbVote` автоматически создается запись с `i_direction=Direction.AUTHOR`.
|
||||||
|
- Это исключает необходимость хранить отдельное поле `s_author_fingerprint` в `TbHypn0Item` (нет дублирования данных).
|
||||||
|
- Благодаря `UniqueConstraint(fields=["k_item", "s_fingerprint"])` автор автоматически защищен от «самолайка» (повторный лайк со своего браузера не пройдет).
|
||||||
|
- Свежая генерация сразу получает стартовый вес (`+2`), предотвращающий мгновенное удаление при низком `f_score`.
|
||||||
- **Фронтенд:** Для быстродействия UI и исключения лишней нагрузки список лайкнутых картин дублируется локально на клиенте в **`localStorage`** (`hypn0_votes`). На базе Alpine.js выполняется мгновенная индикация проголосованных работ.
|
- **Фронтенд:** Для быстродействия UI и исключения лишней нагрузки список лайкнутых картин дублируется локально на клиенте в **`localStorage`** (`hypn0_votes`). На базе Alpine.js выполняется мгновенная индикация проголосованных работ.
|
||||||
- **Соответствие GDPR / 152-ФЗ:** Персональные данные не собираются и не хранятся.
|
- **Соответствие GDPR / 152-ФЗ:** Персональные данные не собираются и не хранятся.
|
||||||
|
|
||||||
@@ -347,91 +356,111 @@ HTMX: форма с `hx-post`, `hx-encoding="multipart/form-data"` → SVG вс
|
|||||||
|
|
||||||
#### 4.3. Стратегия хранения и умной очистки (Smart Retention)
|
#### 4.3. Стратегия хранения и умной очистки (Smart Retention)
|
||||||
Проект не выступает постоянным файлообменником или бесплатным CDN. Пользователь скачивает сгенерированный SVG для личного использования.
|
Проект не выступает постоянным файлообменником или бесплатным CDN. Пользователь скачивает сгенерированный SVG для личного использования.
|
||||||
Для оптимизации диска на сервере используется динамическая очистка на основе рейтинга популярности («гравитации»), а не слепой 14-дневный таймер:
|
Для оптимизации диска на сервере используется динамическая очистка на основе рейтинга популярности («гравитации»), а не слепой таймер:
|
||||||
|
|
||||||
$$\text{Score} = \frac{\text{Likes} + \text{Bonus}_{\text{moderator}}}{(\text{Age in hours} + 2)^\gamma}$$
|
$$\text{Score} = \frac{\text{Likes} + \text{Bonus}_{\text{moderator}}}{(\text{Age in hours} + 2)^\gamma}$$
|
||||||
|
|
||||||
- **Квота хранилища:** Задается жесткий лимит объема (например, 500 МБ или N тысяч файлов).
|
- **Квота хранилища:** Задается жесткий лимит объема (например, 500 МБ или N тысяч файлов).
|
||||||
- **Очистка (`manage.py cleanup_storage`):** При превышении лимита удаляются файлы с наименьшим `Score`.
|
- **Уровни хранения (`i_level` / `TbHypn0Item.Level`):**
|
||||||
- **Иммунитет к удалению:** Работы, отмеченные модератором (`is_curated=True` / `Bonus`), и закрепленные промо-картины сохраняются бессрочно.
|
- `CANDIDATE` (`LVL_CANDIDATE`): свежая генерация, защита от удаления на начальный период.
|
||||||
|
- `LEVEL_1` (`LVL_MIN_VOTES`): получен минимум лайков («Лёгкий транс»).
|
||||||
|
- `LEVEL_2` (`LVL_MODERATED`): проверено и одобрено («Одобрено Мозговым Слизнем»), удаление возможно при низком `f_score`.
|
||||||
|
- `IMMORTAL` (`LVL_LOCK_FOR_DELETION`): полный иммунитет к удалению даже при низком рейтинге («Глубокий транс»).
|
||||||
|
- **Очистка (`manage.py cleanup_storage`):** При превышении квоты диска удаляются файлы с наименьшим `f_score` (не затрагивая защищенные уровни).
|
||||||
|
|
||||||
#### 4.4. SEO, шеринг и коммерциализация
|
#### 4.4. SEO, шеринг и коммерциализация
|
||||||
- **Вшивание ссылки в SVG (Zero-cost Branding):** В скачиваемый SVG на этапе генерации добавляется мета-комментарий со ссылкой на генератор (`<!-- Generated by Hypn0 Generator (https://hypn0.ru) -->` и `<metadata>`).
|
- **Вшивание ссылки в SVG (Zero-cost Branding):** В скачиваемый SVG на этапе генерации добавляется мета-комментарий со ссылкой на генератор (`<!-- Generated by Hypn0 Generator (https://hypn0.ru) -->` и `<metadata>`).
|
||||||
- **Страница просмотра (`/v/<hash>`):** Для внешнего шеринга и индексации поисковиками генерируется легкая HTML-страница с OpenGraph разметкой и кнопкой «Открыть настройки в генераторе».
|
- **Страница просмотра (`/v/<hash>`):** Для внешнего шеринга и индексации поисковиками генерируется легкая HTML-страница с OpenGraph разметкой и кнопкой «Открыть настройки в генераторе».
|
||||||
- **Возможность промо/коммерциализации:** Поддержка опциональных полей (промо-ссылка, бейдж автора/спонсора, счетчик кликов).
|
- **Возможность промо/коммерциализации:** Поддержка опциональных полей (`s_promo_url`, `s_promo_title`, `i_promo_clicks`).
|
||||||
|
|
||||||
#### 4.5. Блог, статические страницы и типографика (HTML + `etpgrf`)
|
#### 4.5. Блог, статические страницы и типографика (HTML + `etpgrf`)
|
||||||
- **Отказ от конвертации Markdown «на лету»:** Вместо оверхеда с парсингом Markdown статьи и инфо-страницы хранятся напрямую в формате **HTML**.
|
- **Отказ от конвертации Markdown «на лету»:** Вместо оверхеда с парсингом Markdown статьи и инфо-страницы хранятся напрямую в формате **HTML**.
|
||||||
- **Очистка HTML в админке:** При вводе/сохранении в Django Admin контент проходит валидацию и санитизацию (очистку от вредоносного или мусорного HTML/инлайн-стилей).
|
- **Очистка HTML в админке:** При вводе/сохранении в Django Admin контент проходит валидацию и санитизацию (очистку от вредоносного или мусорного HTML/инлайн-стилей).
|
||||||
- **Типографирование через `etpgrf`:** Для безупречной верстки текста (неразрывные пробелы, висячая пунктуация, кавычки-елочки, тире) используется библиотека типографирования **`etpgrf`**.
|
- **Типографирование через `etpgrf`:** Для безупречной верстки текста (неразрывные пробелы, висячая пунктуация, кавычки-елочки, тире) используется библиотека типографирования **`etpgrf`**.
|
||||||
|
|
||||||
#### 4.6. Архитектура моделей
|
#### 4.6. Архитектура моделей (Венгерская нотация и типизация)
|
||||||
|
|
||||||
|
В моделях используется венгерская нотация префиксов полей (`s_` — string/text/url, `i_` — integer, `f_` — float, `j_` — json, `k_` — foreign key, `d_` — datetime, `is_` — boolean, `file_` — file), а также оптимизированные типы первичных ключей (`SmallAutoField`, `AutoField`, `BigAutoField`).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class HalftoneItem(models.Model):
|
class TbHypn0Item(models.Model):
|
||||||
"""
|
"""
|
||||||
Единая модель для генераций, публичных шеров и витрины галереи.
|
Единая модель для генераций, публичных шеров и витрины галереи.
|
||||||
"""
|
"""
|
||||||
hash_id = models.CharField(max_length=16, unique=True, db_index=True)
|
class Level(models.IntegerChoices):
|
||||||
title = models.CharField(max_length=150, verbose_name="Название картины")
|
CANDIDATE = LVL_CANDIDATE, 'Candidate: Шум сознания' # Свежая генерация
|
||||||
svg_file = models.FileField(upload_to="svg/%Y/%m/")
|
LEVEL_1 = LVL_MIN_VOTES, 'Level 1: Лёгкий транс' # Есть первичные голоса
|
||||||
|
LEVEL_2 = LVL_MODERATED, 'Moderated: Одобрено Мозговым Слизнем' # Модерировано
|
||||||
|
IMMORTAL = LVL_LOCK_FOR_DELETION, 'Locked: Глубокий транс' # Иммунитет к удалению
|
||||||
|
|
||||||
# Снимок параметров для возможного клонирования настроек
|
id = models.AutoField(primary_key=True, verbose_name="ID") # 4 байта, до ~2.14 млрд
|
||||||
params_json = models.JSONField(default=dict, blank=True)
|
s_hash_id = models.CharField(max_length=16, unique=True, verbose_name="ID-Хэш")
|
||||||
file_size = models.PositiveIntegerField(default=0, help_text="Размер файла в байтах")
|
s_title = models.CharField(max_length=255, verbose_name="Заголовок")
|
||||||
|
file_svg = models.FileField(upload_to="svg/%Y/%m/", verbose_name="SVG-файл")
|
||||||
|
i_file_size = models.PositiveIntegerField(default=0, verbose_name="Размер файла (байт)")
|
||||||
|
|
||||||
# Популярность и модерация
|
# Метаданные и снимок параметров алгоритма генерации
|
||||||
likes_count = models.PositiveIntegerField(default=0, db_index=True)
|
j_metadata = models.JSONField(default=dict, blank=True, verbose_name="Метаданные")
|
||||||
views_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="Выбор модератора / галерея (иммунитет к удалению)")
|
# Популярность, модерация и рейтинг Smart Retention
|
||||||
is_public = models.BooleanField(default=True, help_text="Доступность по прямой ссылке")
|
i_likes_count = models.PositiveIntegerField(default=0, db_index=True, verbose_name="Лайки")
|
||||||
|
i_views_count = models.PositiveIntegerField(default=0, verbose_name="Просмотры")
|
||||||
|
i_claims_count = models.PositiveIntegerField(default=0, verbose_name="Жалобы")
|
||||||
|
f_score = models.FloatField(default=0.0, db_index=True, verbose_name="Рейтинг Smart Retention")
|
||||||
|
i_level = models.IntegerField(choices=Level.choices, default=Level.CANDIDATE, verbose_name="Уровень хранения")
|
||||||
|
|
||||||
|
is_public = models.BooleanField(default=True, verbose_name="Публичный доступ")
|
||||||
|
|
||||||
# Промо-блок (на перспективу)
|
# Промо-блок (на перспективу)
|
||||||
promo_url = models.URLField(blank=True, default="", verbose_name="Промо-ссылка")
|
s_promo_url = models.URLField(blank=True, default="", verbose_name="Промо-ссылка")
|
||||||
promo_title = models.CharField(max_length=100, blank=True, default="", verbose_name="Бейдж/автор")
|
s_promo_title = models.CharField(max_length=100, blank=True, default="", verbose_name="Бейдж/автор")
|
||||||
promo_clicks = models.PositiveIntegerField(default=0)
|
i_promo_clicks = models.PositiveIntegerField(default=0, verbose_name="Клики по промо")
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
d_created_at = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name="Дата создания")
|
||||||
|
d_updated_at = models.DateTimeField(auto_now=True, db_index=True, verbose_name="Дата обновления")
|
||||||
|
|
||||||
|
|
||||||
class HalftoneVote(models.Model):
|
class TbVote(models.Model):
|
||||||
"""
|
"""
|
||||||
Анонимный учет голосов (лайков) без сохранения ПДн.
|
Анонимный учет голосов (лайков, жалоб, авторства) без сохранения ПДн.
|
||||||
"""
|
"""
|
||||||
item = models.ForeignKey(HalftoneItem, on_delete=models.CASCADE, related_name="votes")
|
class Direction(models.IntegerChoices):
|
||||||
fingerprint = models.CharField(
|
LIKE = VOTE_LIKE, 'Like (+1)'
|
||||||
|
CLAIM = VOTE_CLAIM, 'Claim (-2)'
|
||||||
|
AUTHOR = VOTE_AUTHOR, 'Author Like (+2)'
|
||||||
|
|
||||||
|
id = models.BigAutoField(primary_key=True, verbose_name="ID") # 8 байт, до ~9×10¹⁸
|
||||||
|
k_item = models.ForeignKey(TbHypn0Item, on_delete=models.CASCADE, related_name="votes", verbose_name="Картина")
|
||||||
|
i_direction = models.IntegerField(choices=Direction.choices, default=Direction.LIKE, verbose_name="Направление")
|
||||||
|
s_fingerprint = models.CharField(
|
||||||
max_length=64,
|
max_length=64,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
help_text="SHA256(visitor_uuid + SECRET_KEY) для защиты от накрутки и разделения устройств"
|
verbose_name="Хэш устройства",
|
||||||
|
help_text="SHA256(visitor_uuid + SECRET_KEY) для защиты от накрутки"
|
||||||
)
|
)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
d_created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата голосования")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(fields=["item", "fingerprint"], name="unique_item_fingerprint_vote")
|
models.UniqueConstraint(fields=["k_item", "s_fingerprint"], name="unique_item_fingerprint_vote")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BlogPost(models.Model):
|
class TbBlogPost(models.Model):
|
||||||
"""
|
"""
|
||||||
Статьи блога, документация и инфо-страницы (Privacy Policy и др.).
|
Статьи блога, документация и инфо-страницы (Privacy Policy и др.).
|
||||||
|
|
||||||
+ виртуальные поля для управления типографом etpgrf
|
|
||||||
"""
|
"""
|
||||||
title = models.CharField(max_length=200, help_text="HTML-заголовок, очищенный и типографированный etpgrf")
|
id = models.SmallAutoField(primary_key=True, verbose_name="ID") # 2 байта, до 32 767 записей
|
||||||
slug = models.SlugField(max_length=200, unique=True)
|
s_title = models.CharField(max_length=255, help_text="HTML-заголовок, очищенный и типографированный etpgrf")
|
||||||
summary = models.TextField(max_length=500, blank=True, help_text="HTML-контент тизера, очищенный и типографированный etpgrf")
|
slug = models.SlugField(max_length=200, unique=True, verbose_name="URL-слаг")
|
||||||
content = models.TextField(help_text="HTML-контент статьи, очищенный и типографированный etpgrf")
|
s_teaser = models.TextField(max_length=1024, blank=True, help_text="HTML-тизер, очищенный и типографированный etpgrf")
|
||||||
cover_image = models.ImageField(upload_to="blog/covers/%Y/", blank=True, null=True)
|
s_content = models.TextField(help_text="HTML-контент статьи, очищенный и типографированный etpgrf")
|
||||||
|
f_cover_img = models.ImageField(upload_to="blog/covers/%Y/", blank=True, null=True, verbose_name="Обложка")
|
||||||
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)
|
|
||||||
|
|
||||||
|
is_published = models.BooleanField(default=False, verbose_name="Опубликовано")
|
||||||
|
d_published_at = models.DateTimeField(null=True, blank=True, db_index=True, verbose_name="Дата публикации")
|
||||||
|
d_created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||||
|
d_updated_at = models.DateTimeField(auto_now=True, verbose_name="Дата обновления")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Шаги реализации
|
## Шаги реализации
|
||||||
@@ -521,32 +550,32 @@ CLI больше не нужен.
|
|||||||
|
|
||||||
## Файлы для создания (итого)
|
## Файлы для создания (итого)
|
||||||
|
|
||||||
| Файл | Назначение |
|
| Файл | Назначение |
|
||||||
|---------------------------------------------------------|------------------------|
|
|-----------------------------------------------------------|------------------------|
|
||||||
| `hypn0/manage.py` | Django CLI |
|
| `hypn0/manage.py` | Django CLI |
|
||||||
| `hypn0/hypn0/__init__.py` | Settings pkg |
|
| `hypn0/hypn0/__init__.py` | Settings pkg |
|
||||||
| `hypn0/hypn0/settings.py` | Конфиг |
|
| `hypn0/hypn0/settings.py` | Конфиг |
|
||||||
| `hypn0/hypn0/urls.py` | URL routing |
|
| `hypn0/hypn0/urls.py` | URL routing |
|
||||||
| `hypn0/hypn0/wsgi.py` | WSGI entry |
|
| `hypn0/hypn0/wsgi.py` | WSGI entry |
|
||||||
| `hypn0/hypn0/templates/base.html` | Базовый шаблон |
|
| `hypn0/hypn0/templates/base.html` | Базовый шаблон |
|
||||||
| `hypn0/halftone/__init__.py` | App pkg |
|
| `hypn0/hypn0_site/__init__.py` | App pkg |
|
||||||
| `hypn0/halftone/urls.py` | URL app |
|
| `hypn0/hypn0_site/urls.py` | URL app |
|
||||||
| `hypn0/halftone/views.py` | Views |
|
| `hypn0/hypn0_site/views.py` | Views |
|
||||||
| `hypn0/halftone/forms.py` | Forms |
|
| `hypn0/hypn0_site/forms.py` | Forms |
|
||||||
| `hypn0/halftone/models.py` | FeaturedSVG |
|
| `hypn0/hypn0_site/models.py` | TbHypn0Item, TbVote... |
|
||||||
| `hypn0/halftone/services/halftone.py` | Ядро |
|
| `hypn0/hypn0_site/services/halftone.py` | Ядро |
|
||||||
| `hypn0/halftone/templates/halftone/index.html` | Главная |
|
| `hypn0/hypn0_site/templates/halftone/index.html` | Главная |
|
||||||
| `hypn0/halftone/management/commands/cleanup_temp.py` | Очистка temp (14 дней) |
|
| `hypn0/hypn0_site/management/commands/cleanup_storage.py` | Очистка SmartRetention |
|
||||||
| `hypn0/halftone/management/commands/cleanup_reports.py` | Очистка по жалобам |
|
| `hypn0/hypn0_site/management/commands/cleanup_reports.py` | Очистка по жалобам |
|
||||||
| `public/static/css/site.css` | Стили |
|
| `public/static/css/site.css` | Стили |
|
||||||
| `public/static/js/site.js` | JS |
|
| `public/static/js/site.js` | JS |
|
||||||
| `.env.sample` | Env template |
|
| `.env.sample` | Env template |
|
||||||
| `Dockerfile` | Сборка |
|
| `Dockerfile` | Сборка |
|
||||||
| `docker-compose.local.yml` | Dev |
|
| `docker-compose.local.yml` | Dev |
|
||||||
| `docker-compose.prod.yml` | Prod |
|
| `docker-compose.prod.yml` | Prod |
|
||||||
| `config/nginx/hypn0-app--external-nginx.conf` | Nginx |
|
| `config/nginx/hypn0-app--external-nginx.conf` | Nginx |
|
||||||
| `.gitea/workflows/docker-publish.yaml` | CI/CD |
|
| `.gitea/workflows/docker-publish.yaml` | CI/CD |
|
||||||
| `database/.gitignore` | Игнор БД |
|
| `database/.gitignore` | Игнор БД |
|
||||||
|
|
||||||
## Файлы для модификации
|
## Файлы для модификации
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user