docs: инфраструктура VM Сервисы + архитектура pulse-api и pulse-web

This commit is contained in:
Cosmo
2026-04-02 10:43:44 +00:00
parent d4f3a10d0d
commit 20e1f3fa6c
31 changed files with 1843 additions and 0 deletions

View File

@@ -0,0 +1,337 @@
# pulse-api — Архитектура
**Репозиторий:** `https://git.digital-home.site/daniil/pulse-api`
**Go module:** `github.com/daniil/homelab-api`
**URL:** `https://api.digital-home.site`
**Dev:** `http://192.168.31.60:8081`
## Общая архитектура
Классический Go REST API с разделением на слои:
```
cmd/api/main.go ← точка входа, роутер, инициализация
internal/
config/ ← загрузка env-переменных
repository/ ← работа с БД (SQL-запросы)
service/ ← бизнес-логика
handler/ ← HTTP-хендлеры (request/response)
model/ ← структуры данных (Go structs + JSON)
middleware/ ← JWT-аутентификация
bot/ ← Telegram бот
scheduler/ ← cron-задачи (напоминания)
```
**Стек:**
- Router: `go-chi/chi v5`
- ORM: `jmoiron/sqlx` (raw SQL + named queries)
- БД: PostgreSQL 16
- JWT: `golang-jwt/jwt v5`
- Telegram: `go-telegram-bot-api v5`
- Cron: `robfig/cron v3`
- Email: Resend API (`service/email.go`)
- Крипто: `golang.org/x/crypto` (bcrypt для паролей)
## Структура папок
```
pulse-api/
├── cmd/api/main.go # Точка входа: роутер, DI, запуск
├── internal/
│ ├── bot/
│ │ ├── bot.go # Инициализация, Start(), SendMessage()
│ │ └── handlers.go # Команды и callback кнопки
│ ├── config/
│ │ └── config.go # Config struct + Load() из env
│ ├── handler/
│ │ ├── auth.go # Register, Login, Refresh, Me, VerifyEmail...
│ │ ├── tasks.go # CRUD задач + complete/uncomplete
│ │ ├── habits.go # CRUD привычек + логи + статистика
│ │ ├── habit_freeze.go # Заморозка привычек
│ │ ├── finance.go # Категории и транзакции финансов
│ │ ├── savings.go # Накопления (категории, участники, планы)
│ │ ├── interest.go # Начисление процентов
│ │ ├── profile.go # Профиль пользователя
│ │ └── health.go # GET /health
│ ├── middleware/
│ │ └── auth.go # JWT Bearer middleware
│ ├── model/
│ │ ├── user.go # User, RegisterRequest, LoginRequest...
│ │ ├── task.go # Task, CreateTaskRequest...
│ │ ├── habit.go # Habit, HabitLog, HabitStats...
│ │ ├── habit_freeze.go # HabitFreeze
│ │ ├── finance.go # FinanceCategory, FinanceTransaction...
│ │ ├── savings.go # SavingsCategory, SavingsTransaction...
│ │ └── email.go # Email templates
│ ├── repository/
│ │ ├── db.go # NewDB() + RunMigrations()
│ │ ├── user.go # UserRepository
│ │ ├── task.go # TaskRepository
│ │ ├── habit.go # HabitRepository
│ │ ├── habit_freeze.go # HabitFreezeRepository
│ │ ├── finance.go # FinanceRepository
│ │ ├── savings.go # SavingsRepository
│ │ └── email_token.go # EmailTokenRepository
│ ├── service/
│ │ ├── auth.go # AuthService: register/login/JWT
│ │ ├── habit.go # HabitService
│ │ ├── task.go # TaskService
│ │ ├── finance.go # FinanceService
│ │ ├── interest.go # Начисление процентов
│ │ └── email.go # EmailService (Resend)
│ └── scheduler/
│ └── scheduler.go # Cron-напоминания через Telegram
├── docs/
│ └── SAVINGS.md
├── go.mod
├── Dockerfile
└── docker-compose.yml
```
## API Эндпоинты
### Публичные (без авторизации)
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/health` | Health check |
| POST | `/auth/register` | Регистрация: `{email, username, password}` |
| POST | `/auth/login` | Логин: `{email, password}``{access_token, refresh_token, user}` |
| POST | `/auth/refresh` | Обновление токена: `{refresh_token}` |
| POST | `/auth/verify-email` | Подтверждение email: `{token}` |
| POST | `/auth/resend-verification` | Повторная отправка: `{email}` |
| POST | `/auth/forgot-password` | Сброс пароля: `{email}` |
| POST | `/auth/reset-password` | Новый пароль: `{token, new_password}` |
### Авторизованные (Bearer JWT)
#### Аутентификация/Профиль
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/auth/me` | Текущий пользователь |
| PUT | `/auth/me` | Обновить профиль |
| PUT | `/auth/password` | Сменить пароль: `{old_password, new_password}` |
| GET | `/profile` | Профиль |
| PUT | `/profile` | Обновить профиль |
#### Задачи
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/tasks` | Список задач. Query: `?completed=true/false` |
| GET | `/tasks/today` | Задачи на сегодня |
| POST | `/tasks` | Создать: `{title, description?, icon?, color?, due_date?, priority?, reminder_time?, is_recurring?, recurrence_type?, recurrence_interval?, recurrence_end_date?}` |
| GET | `/tasks/{id}` | Получить задачу |
| PUT | `/tasks/{id}` | Обновить задачу |
| DELETE | `/tasks/{id}` | Удалить задачу |
| POST | `/tasks/{id}/complete` | Отметить выполненной |
| POST | `/tasks/{id}/uncomplete` | Снять отметку |
#### Привычки
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/habits` | Список привычек |
| POST | `/habits` | Создать: `{name, description?, color?, icon?, frequency, target_days?, target_count?, reminder_time?, start_date?}` |
| GET | `/habits/{id}` | Получить привычку |
| PUT | `/habits/{id}` | Обновить |
| DELETE | `/habits/{id}` | Удалить |
| POST | `/habits/{id}/log` | Отметить: `{date?, count?, note?}` |
| GET | `/habits/{id}/logs` | История отметок |
| DELETE | `/habits/{id}/logs/{logId}` | Удалить отметку |
| GET | `/habits/stats` | Общая статистика |
| GET | `/habits/{id}/stats` | Статистика привычки |
| GET | `/habits/{id}/freezes` | Заморозки привычки |
| POST | `/habits/{id}/freezes` | Создать заморозку |
| DELETE | `/habits/{id}/freezes/{freezeId}` | Удалить заморозку |
#### Финансы
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/finance/categories` | Категории расходов/доходов |
| POST | `/finance/categories` | Создать: `{name, emoji?, type, budget?, color?, sort_order?}` |
| PUT | `/finance/categories/{id}` | Обновить |
| DELETE | `/finance/categories/{id}` | Удалить |
| GET | `/finance/transactions` | Транзакции. Query: `?month=&year=` |
| POST | `/finance/transactions` | Создать: `{category_id, type, amount, description?, date}` |
| PUT | `/finance/transactions/{id}` | Обновить |
| DELETE | `/finance/transactions/{id}` | Удалить |
| GET | `/finance/summary` | Сводка: баланс, доходы, расходы, по категориям |
| GET | `/finance/analytics` | Аналитика: тренды по месяцам |
#### Накопления
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/savings/categories` | Категории накоплений |
| POST | `/savings/categories` | Создать категорию |
| GET | `/savings/categories/{id}` | Получить |
| PUT | `/savings/categories/{id}` | Обновить |
| DELETE | `/savings/categories/{id}` | Удалить |
| GET | `/savings/categories/{id}/members` | Участники |
| POST | `/savings/categories/{id}/members` | Добавить участника |
| DELETE | `/savings/categories/{id}/members/{userId}` | Удалить участника |
| GET | `/savings/categories/{id}/recurring-plans` | Регулярные планы |
| POST | `/savings/categories/{id}/recurring-plans` | Создать план |
| PUT | `/savings/recurring-plans/{planId}` | Обновить план |
| DELETE | `/savings/recurring-plans/{planId}` | Удалить план |
| GET | `/savings/transactions` | Транзакции накоплений |
| POST | `/savings/transactions` | Создать транзакцию |
| GET | `/savings/transactions/{id}` | Получить |
| PUT | `/savings/transactions/{id}` | Обновить |
| DELETE | `/savings/transactions/{id}` | Удалить |
| GET | `/savings/stats` | Статистика |
## Модели данных
### User
```go
type User struct {
ID int64
Email string
Username string
PasswordHash string // bcrypt, в JSON скрыто
EmailVerified bool
TelegramChatID *int64 // nullable
NotificationsEnabled bool
Timezone string
MorningReminderTime string // "09:00"
EveningReminderTime string // "21:00"
CreatedAt, UpdatedAt time.Time
}
```
### Task
```go
type Task struct {
ID, UserID int64
Title, Description string
Icon, Color string
DueDate *string // "2026-01-01"
Priority int // 1=низкий, 2=средний, 3=высокий
ReminderTime *string // "19:00"
Completed bool // производное от CompletedAt
// Повторяющиеся задачи:
IsRecurring bool
RecurrenceType *string // "daily", "weekly", "monthly"
RecurrenceInterval int
RecurrenceEndDate *string
ParentTaskID *int64
CreatedAt, UpdatedAt time.Time
}
```
### Habit
```go
type Habit struct {
ID, UserID int64
Name, Description string
Color, Icon string
Frequency string // "daily", "weekly", "custom"
TargetDays []int // дни недели (0=вс...6=сб)
TargetCount int
ReminderTime *string // "19:00"
StartDate *string
IsArchived bool
CreatedAt, UpdatedAt time.Time
}
type HabitLog struct {
ID, HabitID, UserID int64
Date time.Time
Count int
Note string
CreatedAt time.Time
}
```
### FinanceCategory / FinanceTransaction
```go
type FinanceCategory struct {
ID, UserID int64
Name, Emoji string
Type string // "income" | "expense"
Budget *float64
Color string
SortOrder int
CreatedAt time.Time
}
type FinanceTransaction struct {
ID, UserID, CategoryID int64
Type string // "income" | "expense"
Amount float64
Description string
Date time.Time
CreatedAt time.Time
CategoryName, CategoryEmoji string // из JOIN
}
```
## Аутентификация
- **JWT HS256** с двумя типами токенов: `access` (короткий) и `refresh` (длинный)
- Middleware `Authenticate` парсит `Authorization: Bearer <token>`, проверяет `type == "access"`
- UserID извлекается из claims и помещается в context: `GetUserID(ctx)`
- Пароли хешируются bcrypt
- Email-верификация при регистрации через Resend API
- Password reset — одноразовые токены в таблице `email_tokens`
## Telegram Бот
**Команды:**
| Команда | Действие |
|---------|---------|
| `/start` | Показать Chat ID (для привязки к аккаунту Pulse) |
| `/tasks` | Список задач на сегодня с inline-кнопками |
| `/habits` | Привычки на сегодня с inline-кнопками |
| `/done <id>` или `/done_<id>` | Отметить задачу выполненной |
| `/check <id>` или `/check_<id>` | Отметить привычку |
| `/help` | Справка |
**Callback-кнопки:**
- `donetask_<id>` — выполнить задачу
- `deltask_<id>` — удалить задачу
- `checkhabit_<id>` — отметить привычку сегодня
- `checkhabit_<id>_yesterday` — отметить привычку за вчера
**Привязка:** пользователь вводит `/start`, получает Chat ID, вставляет его в настройки Pulse → `PUT /profile {telegram_chat_id: ...}`
## Scheduler (cron-напоминания)
`internal/scheduler/scheduler.go` — использует `robfig/cron`:
- Утренние напоминания (время из настроек пользователя)
- Вечерние напоминания
- Напоминания о задачах по `reminder_time`
- Напоминания о привычках по `reminder_time`
## Конфигурация (env переменные)
| Переменная | Описание | Default |
|-----------|----------|---------|
| `DATABASE_URL` | PostgreSQL DSN | `postgres://homelab:homelab@db:5432/homelab` |
| `JWT_SECRET` | Секрет для JWT | `change-me-in-production` |
| `PORT` | Порт сервера | `8080` |
| `RESEND_API_KEY` | API ключ Resend (email) | — |
| `FROM_EMAIL` | Email отправителя | `noreply@digital-home.site` |
| `FROM_NAME` | Имя отправителя | `Homelab` |
| `APP_URL` | Публичный URL приложения | `https://api.digital-home.site` |
| `TELEGRAM_BOT_TOKEN` | Токен Telegram бота | — |
## Где искать что
| Задача | Файл |
|--------|------|
| Добавить новый эндпоинт | `handler/<domain>.go` + роут в `cmd/api/main.go` |
| Изменить модель/таблицу | `model/<domain>.go` + `repository/db.go` (migrations) |
| Логика уведомлений | `internal/scheduler/scheduler.go` |
| Telegram команды | `internal/bot/handlers.go` |
| Финансы (категории/транзакции) | `handler/finance.go`, `service/finance.go`, `repository/finance.go` |
| Привычки | `handler/habits.go`, `service/habit.go`, `repository/habit.go` |
| Задачи | `handler/tasks.go`, `service/task.go`, `repository/task.go` |
| Накопления | `handler/savings.go`, `repository/savings.go` |
| Email отправка | `service/email.go` |
| JWT/Auth | `service/auth.go`, `middleware/auth.go` |
| Конфиг | `config/config.go` |