Что за продукт
Клиника подключается к сервису и получает два инструмента:
- Telegram Mini App — для клиентов. Владельцы питомцев видят информацию о клинике (лого, баннер, телефон, адрес), читают статьи по первой помощи, смотрят врачей и расписание, записываются на груминг.
- Веб-панель (admin) — для персонала клиники. Редактор ведёт контент, администратор управляет врачами, пользователями и информацией о клинике, грумер — своими записями.
Мультитенантность: одна инсталляция сервера обслуживает несколько клиник через clinic_id во всех таблицах. Добавить клинику = новая строка в clinics, никакого рефакторинга не требуется.
Структура монорепо
you_vet/
├── apps/
│ ├── server/
│ ├── admin/
│ └── app/
├── packages/
│ └── types/
├── docker-compose.yml
├── turbo.json
└── package.json
Go не входит в npm workspaces. apps/server управляет зависимостями через go.mod независимо. Turborepo оркестрирует только admin и app.
Стек технологий
Backend Go
| Компонент | Решение | Зачем |
| Язык | Go 1.25 | Скорость, простота деплоя (один бинарник) |
| HTTP роутер | net/http (stdlib) | Без фреймворков — чисто и предсказуемо |
| База данных | PostgreSQL 16 + lib/pq | — |
| Миграции | golang-migrate | SQL-файлы, применяются при старте |
| Auth | JWT (golang-jwt) | 24ч токены, роль в claims |
| Telegram бот | Telebot (long polling) | Отправка статей, ссылки на Mini App |
Admin Panel React 19 TypeScript
| Компонент | Решение |
| Сборка | Vite 8 |
| UI-компоненты | MUI v7 (Material UI) |
| Роутинг | React Router v7 (Data Router) |
| Серверное состояние | TanStack Query v5 |
| Формы + валидация | React Hook Form + Valibot |
| WYSIWYG редактор | TipTap v2 |
| HTTP-клиент | Axios (interceptor для Bearer токена) |
Telegram Mini App React 18 TypeScript
| Компонент | Решение |
| Сборка | Vite 8 |
| Telegram SDK | @telegram-apps/sdk-react v3 |
| UI-компоненты | @telegram-apps/telegram-ui v2 (нативный TG-стиль) |
| Серверное состояние | TanStack Query v5 |
| Роутинг | React Router v7 |
Инфраструктура
| Компонент | Решение |
| Сервер | VPS Ubuntu (194.87.0.94) |
| Reverse proxy | Nginx (SSL termination, SPA routing) |
| SSL | Let's Encrypt (auto-renew) |
| Контейнеризация | Docker + docker-compose (Go + PostgreSQL) |
| CI/CD | GitHub Actions (path-based, 3 отдельных workflow) |
| Монорепо | Turborepo + npm workspaces |
Архитектура бэкенда
Слоёная архитектура: HTTP → Middleware → Handler → Repository → PostgreSQL
apps/server/
├── main.go
└── internal/
├── handler/
├── repository/
├── middleware/
├── bot/
└── db/
API — два контекста
| Prefix | Auth | Кто использует |
/api/clinics/{slug}/... | Нет | Telegram Mini App (публичный) |
/api/admin/... | JWT Bearer | Admin Panel |
Публичные эндпоинты Mini App
| Метод | Путь | Описание |
| GET | /api/clinics/{slug}/clinic-info | Профиль клиники (лого, баннер, контакты) |
| GET | /api/clinics/{slug}/doctors | Список врачей |
| GET | /api/clinics/{slug}/schedule | Расписание приёма |
| GET | /api/clinics/{slug}/grooming/breeds | Породы и цены груминга |
| GET | /api/clinics/{slug}/grooming/schedule | Расписание груминга |
| GET | /api/clinics/{slug}/animals | Виды животных |
| GET | /api/clinics/{slug}/articles/{slug} | Статья по первой помощи |
Admin эндпоинты (JWT)
| Ресурс | Эндпоинты |
| Clinic info | GET/PUT /api/admin/clinic-info, POST /logo, POST /banner |
| Doctors | CRUD + фото + расписание + исключения |
| Articles | CRUD + статус + категории |
| Grooming | Породы, шаблон недели, записи |
| Settings | GET/PATCH /api/admin/settings |
| Users | CRUD (только admin) |
База данных — 8 миграций
| # | Что создаёт |
| 001 | animals — животные (slug, icon, sort_order) |
| 002 | categories — категории статей, привязанные к животному |
| 003 | articles + M2M таблица article_categories |
| 004 | clinics + clinic_id во все таблицы (мультитенантность) |
| 005 | status у статей (draft / published) |
| 006 | doctors, doctor_schedules, schedule_exceptions, clinic_settings |
| 007 | grooming_breeds, grooming_template, grooming_appointments |
| 008 | clinic_info — профиль клиники (name, description, phone, address, email, website, logo_url, banner_url) |
Конвенция: day_of_week везде: 0 = Вс, 1 = Пн, … 6 = Сб (PostgreSQL стандарт).
Ролевая модель
admin
- Полный доступ
- Публикация статей и врачей
- Управление пользователями
- Настройки клиники
editor
- Создание и редактирование черновиков
- Нельзя публиковать
- Нельзя редактировать опубликованное
groomer
- Доступ только к
/grooming
- Управление породами, шаблоном, записями
Роль хранится в JWT claim и проверяется на бэкенде. Фронтенд только скрывает UI-элементы для удобства — это не единственная защита.
Пакет @you-vet/types
Общий TypeScript пакет (packages/types/) — единый источник моделей данных для admin и app.
import type { Doctor, GroomingBreed, Article } from '@you-vet/types';
Файлы: animals.ts, articles.ts, doctors.ts, grooming.ts, users.ts, index.ts
Tygo (кодогенерация из Go) удалён. Типы поддерживаются вручную. При изменении Go структур — обновляй packages/types.
Локальная разработка
Требования
- Node.js 22+, npm
- Go 1.25+
- Docker + docker-compose
Запуск
# 1. Клонировать и поставить зависимости
git clone https://github.com/bospur/you_vet.git
cd you_vet
npm install
# 2. Настроить окружение для сервера
cp apps/server/.env.example apps/server/.env
# Отредактировать .env: задать BOT_TOKEN, JWT_SECRET, пароли
# 3. Запустить базу данных и сервер
docker compose up -d
# Go сервер автоматически применит миграции при старте
# 4. Запустить фронтенд (в отдельном терминале)
cp apps/admin/.env.example apps/admin/.env.local # задать VITE_API_URL=http://localhost:8080
cp apps/app/.env.example apps/app/.env.local # задать VITE_CLINIC_SLUG
npm run dev # turbo запустит admin и app параллельно
Переменные окружения
apps/server/.env
| Переменная | Описание |
DATABASE_URL | postgres://user:pass@db:5432/dbname?sslmode=disable |
TELEGRAM_BOT_TOKEN | Токен бота от BotFather |
CLINIC_SLUG | Slug клиники по умолчанию (напр. vpract) |
PORT | Порт сервера (8080) |
JWT_SECRET | Случайная строка для подписи токенов |
ADMIN_LOGIN | Логин первого администратора |
ADMIN_PASSWORD | Пароль первого администратора |
apps/admin/.env.local
| Переменная | Описание |
VITE_API_URL | URL бэкенда (напр. http://localhost:8080) |
apps/app/.env.local
| Переменная | Описание |
VITE_API_URL | URL бэкенда |
VITE_CLINIC_SLUG | Slug клиники (напр. vpract) |
Telegram Mini App требует HTTPS. Для локального тестирования используй ngrok или аналог, чтобы пробросить localhost через HTTPS туннель.
Turborepo команды
# Из корня монорепо
npm run dev # запускает admin и app параллельно (turbo dev)
npm run build # сборка всех приложений с учётом зависимостей
npm run lint # линтинг всех приложений
Git workflow
Bospur ← prod, только через PR (никогда не пушить напрямую)
dev ← интеграция, только через PR
feature/*, fix/*, chore/* ← рабочие ветки
Стандартный цикл:
- Создать ветку от
dev: git checkout -b feature/my-feature dev
- Написать код, закоммитить
- PR →
dev
- После тестирования на dev — PR
dev → Bospur
Никогда не пушить напрямую в dev или Bospur.
CI/CD
Три GitHub Actions workflow в .github/workflows/, каждый триггерится только при изменениях своего приложения:
| Workflow | Триггер (paths) | Что делает |
deploy-server.yml | apps/server/** | Сборка Docker образа → push в GHCR → SSH на VPS → docker compose pull && up -d |
deploy-admin.yml | apps/admin/**, packages/types/** | npm ci → vite build → scp dist/ на VPS |
deploy-app.yml | apps/app/**, packages/types/** | npm ci → vite build → scp dist/ на VPS |
Server деплой использует GHCR (GitHub Container Registry). Образ собирается в CI и пушится как ghcr.io/bospur/you_vet-server:latest. VPS только тянет готовый образ — никакого git на сервере не нужно. Аутентификация через GITHUB_TOKEN (автоматический, не истекает).
Секреты в GitHub Repository Settings: VPS_HOST, VPS_USER, VPS_SSH_KEY, VITE_API_URL, VITE_CLINIC_SLUG
Деплой на VPS
| Компонент | Расположение |
| Go сервер | Docker (/home/deploy/you_vet/apps/server) |
| Admin Panel | Nginx → /var/www/vp-bot-admin/ |
| Mini App | Nginx → /var/www/vp-bot-app/ |
| Домены | api.*, admin.*, app.* .snzbeachvolleyball25.ru |
Паттерны в коде
Backend (Go) — типичный handler
func (h *Handler) GetDoctors(w http.ResponseWriter, r *http.Request) {
claims := middleware.ClaimsFromContext(r.Context()) // JWT из контекста
doctors, err := h.repo.GetDoctors(r.Context(), claims.ClinicID)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(doctors)
}
Admin (React) — useQuery + useMutation
const { data: doctors } = useQuery({
queryKey: ['doctors'],
queryFn: getDoctors,
});
const deleteMutation = useMutation({
mutationFn: deleteDoctor,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['doctors'] }),
});
Mini App (React) — публичный API без авторизации
// Данные из публичного API
const { data } = useQuery({
queryKey: ['doctors'],
queryFn: () => api.get(`/api/clinics/${CLINIC_SLUG}/doctors`),
});
// Telegram BackButton — управляется нативно через Telegram.WebApp
// показывается на всех экранах кроме "/"
Mini App — архитектура экранов
Все экраны обёрнуты в AppLayout (React Router nested route) — постоянный хедер с лого клиники, названием и кнопкой звонка. Данные клиники фетчатся один раз в AppLayout и передаются дочерним экранам через useOutletContext().
// AppLayout.tsx — лейаут верхнего уровня
<Route element={<AppLayout />}>
<Route path="/" element={<HomeScreen />} />
<Route path="/doctors" element={<DoctorsScreen />} />
...
</Route>
// HomeScreen.tsx — получает данные клиники от лейаута
const info = useOutletContext<ClinicInfo | null>();
Статус готовности к запуску
| Функция | Статус | Примечание |
| Профили врачей + расписание | ✅ Готово | |
| Статьи по первой помощи (WYSIWYG) | ✅ Готово | |
| Груминг (породы, цены, расписание) | ✅ Готово | |
| Информация о клинике (лого, баннер, контакты) | ✅ Готово | |
| Кнопка "Позвонить" в хедере | ✅ Готово | |
| Персистентный хедер во всём приложении | ✅ Готово | AppLayout |
| Скрытие груминга если раздел не настроен | 🔴 Не сделано | Блокирует запуск |
| Telegram initData валидация | 🔴 Не проверено | Безопасность |
| Запись на приём (pending/confirm flow) | 🔴 Не сделано | Ключевая фича |
| Аналитика посещений | 🟡 Не начато | После записи |
Документация в репо
| Файл | Описание |
docs/architecture.md | Системная архитектура, схема взаимодействия |
docs/api.md | Полный справочник API (все эндпоинты) |
docs/data-model.md | Схема БД, конвенции полей |
docs/roles.md | Ролевая матрица прав |
docs/deployment.md | Деплой, VPS, Nginx, SSL |
docs/development.md | Локальная разработка, git workflow |
docs/monorepo.md | Устройство монорепо, Turborepo |
YouVet · github.com/bospur/you_vet · вопросы — в Issues или напрямую