# 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` |
