Соглашения проекта
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_ | User | account |
clnt_ | Client | client |
cprsn_ | ContactPerson | client |
cobj_ | ClientObject | client |
cinvt_ | ClientInvite | client |
sv_ | ServiceVisit | service_visit |
svm_ | ServiceVisitMember | service_visit |
rprt_ | Report | report |
rprts_ | ReportSurveyTemplate | report |
file_ | FileAttachment | report |
rprtdel_ | ReportDelivery | report |
ntfp_ | NotificationPreference | notification |
mtrl_ | Material | material |
srv_ | Service | service |
tglnk_ | TelegramLink | communication |
Базовая модель
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, форматирует в локальное время браузера на клиенте.
Интернационализация
| Сервис | Технология | Где живёт |
|---|---|---|
| Frontend | next-intl | frontend/src/locales/{en,ru}.json, маршруты /ru/... и /en/..., синхронизация через Crowdin |
| Telegram-бот | Fluent Translation List (FTL) | bot/assets/messages/{en,ru}/*.ftl |
| Email (Edge) | TSX-шаблоны с if-проверкой lang | supabase/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, defaultru). - Для контактного лица:
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 |
|---|---|---|
| Frontend | ESLint, Prettier | tsc |
| Garden | ruff | mypy (strict) |
| Bot | ruff | mypy (strict) |
| Supabase Edge | Deno fmt + lint | TypeScript (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,ClientInviteServiceVisit,ServiceVisitMemberReport,Material,Service
Каждое изменение пишется в зеркальную таблицу historical_<table> с полями history_user_id, history_date, history_change_reason. Это даёт полный аудит «кто, когда, что менял» без отдельной системы логирования.