KNOTTA research & development

Соглашения проекта

KSUID-идентификаторы, базовая модель, часовые пояса, интернационализация, структура модулей.

Соглашения проекта

Идентификаторы — KSUID с префиксом

Все доменные сущности используют идентификаторы вида <prefix>_<ksuid>:

acc_2TCXd9h4jKLNKnPGQMdqFfW7QkN
clnt_2TFD8h7kKLPMNlQHQNexFfW8ZkQ
sv_2THK1c2lLPNORnQIRMfyFfW9AkR
rprt_2TJM4d5mMQOSPoQJSNgzFfWAEkS

Преимущества:

  • Уникальность без CSPRNG-трюков (KSUID — 27-символьный URL-safe).
  • Сортировка по времени — KSUID кодирует timestamp в первых 4 байтах.
  • Читаемость — префикс мгновенно говорит, какая это модель: acc_ (User), clnt_ (Client), sv_ (ServiceVisit) и т. д.

Префиксы доменных моделей

ПрефиксМодельМодуль
acc_Useraccount
clnt_Clientclient
cprsn_ContactPersonclient
cobj_ClientObjectclient
cinvt_ClientInviteclient
sv_ServiceVisitservice_visit
svm_ServiceVisitMemberservice_visit
rprt_Reportreport
rprts_ReportSurveyTemplatereport
file_FileAttachmentreport
rprtdel_ReportDeliveryreport
ntfp_NotificationPreferencenotification
mtrl_Materialmaterial
srv_Serviceservice
tglnk_TelegramLinkcommunication

Базовая модель

modules/shared/db/models.BaseModel — абстрактный родитель для всех доменных моделей. Даёт:

  • id — KSUID с префиксом (через дескриптор KsuidField).
  • created_at, updated_at — автоматические таймстемпы.
  • Метод __str__ по умолчанию.

Дочерний класс задаёт свой префикс через атрибут класса:

class Report(BaseModel):
    prefix = "rprt_"

Часовые пояса

Хранение — UTC. Все DateTimeField в БД лежат в UTC.

Отображение — Europe/Moscow. В Django настроен TIME_ZONE = "Europe/Moscow" плюс FixedTimezoneMiddleware, который активирует MSK-зону на каждый запрос.

Фронтенд работает в UTC, форматирует в локальное время браузера на клиенте.

Интернационализация

СервисТехнологияГде живёт
Frontendnext-intlfrontend/src/locales/{en,ru}.json, маршруты /ru/... и /en/..., синхронизация через Crowdin
Telegram-ботFluent Translation List (FTL)bot/assets/messages/{en,ru}/*.ftl
Email (Edge)TSX-шаблоны с if-проверкой langsupabase/functions/send-email/templates/*.tsx
Email (Django)MJML по языкуgarden/modules/notification/templates/notification/email/<topic>/{body,subject}_{ru,en}.{mjml,txt}

Язык подписчика:

  • Для пользователя: User.preferred_language (ru / en, default ru).
  • Для контактного лица: ContactPerson.preferred_language.
  • Для Supabase Auth: user_metadata.lang.

Структура Django-модуля

Стандартная раскладка модуля в garden/modules/:

modules/<name>/
├── __init__.py
├── apps.py            # AppConfig
├── models.py          # или models/ — пакет
├── admin.py           # Django Admin
├── api/
│   ├── urls.py        # router.urls
│   ├── views.py       # ViewSet'ы
│   ├── serializers.py
│   ├── filters.py     # django-filter
│   └── permissions.py
├── services.py        # или services/ — бизнес-логика
├── selectors.py       # (опционально) read-only запросы
├── signals.py         # post_save / pre_delete
├── tasks.py           # (опционально) Celery
├── migrations/
└── tests/

Services и selectors — паттерн «views тонкие, бизнес-логика в сервисах». Views/ViewSets делают только сериализацию и проверку прав; реальная работа — в services.py/selectors.py. Это упрощает тестирование и переиспользование.

REST API

OpenAPI-схема генерируется через drf-spectacular:

  • GET /api/schema/ — JSON-схема (OpenAPI 3.0).
  • GET /api/schema/swagger-ui/ — интерактивный Swagger.
  • GET /api/schema/redoc/ — ReDoc.

Группы эндпоинтов перечислены в Для разработчиков → API.

Безопасность

  • JWT — только Supabase, ключи валидируются в SupabaseAuthentication.
  • Webhook-подписи — HMAC-SHA256 для Telegram-бот, Svix для Resend, standardwebhooks для Supabase Auth-хуков.
  • Токены приглашенийsecrets.token_urlsafe(32) (~256 бит).
  • Подписанные ссылки на файлы — TTL 3600 сек по умолчанию, генерируются на лету.
  • Токены отписки — подписанные itsdangerous-токены, одноразовые.

Стиль кода

СервисЛинтерType-checker
FrontendESLint, Prettiertsc
Gardenruffmypy (strict)
Botruffmypy (strict)
Supabase EdgeDeno fmt + lintTypeScript (Deno)

Единые правила: type hints везде, без Any, докстринги для публичных функций и сервисов, тесты на критичные сервисы.

Деплой и секреты

  • Все секреты — в Infisical. Локально — .env.local / .env.example.
  • Прод — single-host deployment через Ansible. Инфра-as-code: вся конфигурация в репозитории ansible/.
  • Никаких git push origin prod — деплой только через Ansible-плейбук.

История изменений (audit trail)

Ключевые модели подключают simple_history.HistoricalRecords():

  • Client, ContactPerson, ClientObject, ClientInvite
  • ServiceVisit, ServiceVisitMember
  • Report, Material, Service

Каждое изменение пишется в зеркальную таблицу historical_<table> с полями history_user_id, history_date, history_change_reason. Это даёт полный аудит «кто, когда, что менял» без отдельной системы логирования.

На странице