YouVet — Project Overview

SaaS-платформа для ветеринарных клиник · Telegram Mini App + Веб-панель
GitHub: bospur/you_vet Admin Panel (prod) API (prod) Mini App (prod) Обновлено: 2026-04-04

Что за продукт

Клиника подключается к сервису и получает два инструмента:

Мультитенантность: одна инсталляция сервера обслуживает несколько клиник через clinic_id во всех таблицах. Добавить клинику = новая строка в clinics, никакого рефакторинга не требуется.

Структура монорепо

you_vet/
├── apps/
│ ├── server/ ← Go бэкенд + Telegram бот
│ ├── admin/ ← Веб-панель (React + MUI)
│ └── app/ ← Telegram Mini App (React + TG UI)
├── packages/
│ └── types/ ← @you-vet/types — общие TypeScript типы
├── docker-compose.yml ← локальный запуск (db + server)
├── turbo.json
└── package.json ← npm workspaces
Go не входит в npm workspaces. apps/server управляет зависимостями через go.mod независимо. Turborepo оркестрирует только admin и app.

Стек технологий

Backend Go

КомпонентРешениеЗачем
ЯзыкGo 1.25Скорость, простота деплоя (один бинарник)
HTTP роутерnet/http (stdlib)Без фреймворков — чисто и предсказуемо
База данныхPostgreSQL 16 + lib/pq
Миграцииgolang-migrateSQL-файлы, применяются при старте
AuthJWT (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 proxyNginx (SSL termination, SPA routing)
SSLLet's Encrypt (auto-renew)
КонтейнеризацияDocker + docker-compose (Go + PostgreSQL)
CI/CDGitHub Actions (path-based, 3 отдельных workflow)
МонорепоTurborepo + npm workspaces

Архитектура бэкенда

Слоёная архитектура: HTTP → Middleware → Handler → Repository → PostgreSQL

apps/server/
├── main.go ← инициализация, регистрация роутов
└── internal/
    ├── handler/ ← HTTP обработчики (animals, articles, doctors, grooming, admin)
    ├── repository/ ← SQL запросы (чистый database/sql, без ORM)
    ├── middleware/ ← JWT auth, CORS
    ├── bot/ ← Telegram бот (long polling, HTML→TG конвертация)
    └── db/ ← подключение к БД, запуск миграций

API — два контекста

PrefixAuthКто использует
/api/clinics/{slug}/...НетTelegram Mini App (публичный)
/api/admin/...JWT BearerAdmin 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 infoGET/PUT /api/admin/clinic-info, POST /logo, POST /banner
DoctorsCRUD + фото + расписание + исключения
ArticlesCRUD + статус + категории
GroomingПороды, шаблон недели, записи
SettingsGET/PATCH /api/admin/settings
UsersCRUD (только admin)

База данных — 8 миграций

#Что создаёт
001animals — животные (slug, icon, sort_order)
002categories — категории статей, привязанные к животному
003articles + M2M таблица article_categories
004clinics + clinic_id во все таблицы (мультитенантность)
005status у статей (draft / published)
006doctors, doctor_schedules, schedule_exceptions, clinic_settings
007grooming_breeds, grooming_template, grooming_appointments
008clinic_info — профиль клиники (name, description, phone, address, email, website, logo_url, banner_url)
Конвенция: day_of_week везде: 0 = Вс, 1 = Пн, … 6 = Сб (PostgreSQL стандарт).

Ролевая модель

admin

editor

groomer

Роль хранится в 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.

Локальная разработка

Требования

Запуск

# 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_URLpostgres://user:pass@db:5432/dbname?sslmode=disable
TELEGRAM_BOT_TOKENТокен бота от BotFather
CLINIC_SLUGSlug клиники по умолчанию (напр. vpract)
PORTПорт сервера (8080)
JWT_SECRETСлучайная строка для подписи токенов
ADMIN_LOGINЛогин первого администратора
ADMIN_PASSWORDПароль первого администратора

apps/admin/.env.local

ПеременнаяОписание
VITE_API_URLURL бэкенда (напр. http://localhost:8080)

apps/app/.env.local

ПеременнаяОписание
VITE_API_URLURL бэкенда
VITE_CLINIC_SLUGSlug клиники (напр. 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/*  ← рабочие ветки

Стандартный цикл:

  1. Создать ветку от dev: git checkout -b feature/my-feature dev
  2. Написать код, закоммитить
  3. PR → dev
  4. После тестирования на dev — PR devBospur
Никогда не пушить напрямую в dev или Bospur.

CI/CD

Три GitHub Actions workflow в .github/workflows/, каждый триггерится только при изменениях своего приложения:

WorkflowТриггер (paths)Что делает
deploy-server.ymlapps/server/**Сборка Docker образа → push в GHCR → SSH на VPS → docker compose pull && up -d
deploy-admin.ymlapps/admin/**, packages/types/**npm civite build → scp dist/ на VPS
deploy-app.ymlapps/app/**, packages/types/**npm civite 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 PanelNginx → /var/www/vp-bot-admin/
Mini AppNginx → /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 или напрямую