Files

15 KiB
Raw Permalink Blame History

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

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

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

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

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