Skip to content

Latest commit

 

History

History
471 lines (321 loc) · 12 KB

File metadata and controls

471 lines (321 loc) · 12 KB

Актуальное состояние проекта для code review

1. Что делает сервис

Сервис предоставляет:

  • регистрацию пользователя;
  • логин с выдачей access и refresh токенов;
  • проверку токена через /validate;
  • обновление access токена через /refresh;
  • CRUD для задач текущего авторизованного пользователя;
  • внутренние метрики через /metrics.

2. Что изменилось архитектурно

Изначально проект работал только на in-memory хранилищах, что давало типичные ограничения:

  • данные терялись при рестарте;
  • между инстансами не было общего состояния;
  • нельзя было нормально масштабировать сервис;
  • решение не подходило даже для базового production-like сценария.

Сейчас проект поддерживает две реализации хранения:

  • memory
  • postgres

Сервисный слой работает через интерфейсы, поэтому бизнес-логика не зависит от конкретного storage backend.

Ключевые интерфейсы:

  • UserStore
  • TaskStore

Файлы:

  • internal/storage/interfaces.go
  • internal/storage/storage.go
  • internal/storage/task.go
  • internal/storage/postgres/user_store.go
  • internal/storage/postgres/task_store.go

3. Конфигурация

В Config сейчас используются:

  • Port
  • JWTSecret
  • LogLevel
  • Storage
  • AccessTokenTTL
  • RefreshTokenTTL
  • DatabaseURL

Что важно:

  • JWT_SECRET обязателен;
  • DATABASE_URL обязателен, если STORAGE=postgres;
  • TTL токенов вынесены в конфиг;
  • access и refresh токены имеют разные сроки жизни.

Файл:

  • internal/config/config.go

Пример .env:

PORT=8081
JWT_SECRET=secret
STORAGE=postgres
DATABASE_URL=postgres://postgres:postgres@localhost:5432/task_tracker?sslmode=disable
ACCESS_TOKEN_TTL=15m
REFRESH_TOKEN_TTL=168h
LOG_LEVEL=info

4. Аутентификация и токены

Что реализовано

  • регистрация нового пользователя;
  • логин с проверкой пароля через bcrypt;
  • выдача двух токенов:
    • access
    • refresh
  • обновление access токена через /refresh;
  • различение типа токена через token_type в claims;
  • в JWT claims хранятся:
    • login
    • user_id
    • token_type

Файлы:

  • internal/service/auth.go
  • internal/transport/http/handlers/auth.go
  • internal/dto/auth.go

Что изменилось по сравнению с прошлой версией

  • TTL токенов больше не захардкожен;
  • появился refresh flow;
  • после перехода на нормализованную модель задач в claims добавлен user_id.

5. Middleware и HTTP-слой

Auth middleware

AuthMiddleware:

  • читает Authorization: Bearer <token>;
  • валидирует access token;
  • кладет в request context:
    • login
    • user_id

Файлы:

  • internal/transport/http/middleware/auth.go
  • internal/transport/http/requestctx/context.go

Формат ответов

Ошибки и JSON-ответы унифицированы через helper:

  • DecodeBody(...)
  • WriteJSON(...)
  • WriteError(...)

Что дополнительно есть:

  • DisallowUnknownFields();
  • ограничение размера request body;
  • проверка, что body содержит только один JSON объект.

Файл:

  • internal/transport/http/response/json.go

Logging middleware

Для каждого запроса логируются:

  • HTTP method;
  • path;
  • status code;
  • duration.

Файл:

  • internal/transport/http/middleware/logging.go

6. Хранилище задач

In-memory

In-memory storage теперь привязан не к login, а к user_id.

Текущая модель Task:

type Task struct {
	ID          string
	Title       string
	Description string
	Status      TaskStatus
	UserID      int64
}

В памяти задачи сейчас хранятся как:

map[int64][]Task

То есть группировка идет по user_id.

Файл:

  • internal/storage/task.go

PostgreSQL

В Postgres-реализации есть все CRUD операции:

  • CreateTask
  • GetTasks
  • UpdateTask
  • DeleteTask

Особенности:

  • выборка задач идет по user_id;
  • update собирается динамически;
  • обновленная задача возвращается через RETURNING;
  • delete проверяет RowsAffected.

Файл:

  • internal/storage/postgres/task_store.go

7. Модель пользователей

Пользователь теперь имеет ID:

type User struct {
	ID       int64
	Login    string
	Password string
}

In-memory

В in-memory user storage добавлен автоинкрементный nextID, чтобы auth service мог работать с user_id единообразно и для memory, и для postgres.

Файл:

  • internal/storage/storage.go

PostgreSQL

GetUser(...) теперь возвращает:

  • id
  • login
  • password

Файл:

  • internal/storage/postgres/user_store.go

8. Миграции

Текущая init-схема

migrations/001_init.sql уже создает нормализованную схему:

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    login TEXT NOT NULL UNIQUE,
    password TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE tasks (
    id UUID PRIMARY KEY,
    user_id BIGINT NOT NULL,
    title TEXT NOT NULL,
    description TEXT NOT NULL DEFAULT '',
    status TEXT NOT NULL DEFAULT 'todo',
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    CONSTRAINT fk_tasks_user_id
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    CONSTRAINT chk_tasks_status
        CHECK (status IN ('todo', 'in_progress', 'done'))
);

Также создаются индексы:

  • idx_tasks_user_id
  • idx_tasks_user_id_status

Миграция для старой схемы

Добавлена отдельная миграция:

  • migrations/002_migrate_tasks_user_login_to_user_id.sql

Ее задача:

  • добавить user_id в старую таблицу tasks;
  • перенести данные из user_login;
  • перестроить FK и индексы;
  • удалить старый user_login.

9. Rate limiting

Для логина реализован in-memory rate limiter:

  • ключ: login + ip;
  • при превышении лимита возвращается 429 Too Many Requests;
  • при успешном логине счетчик сбрасывается.

Файлы:

  • internal/security/ratelimiter.go
  • internal/transport/http/handlers/auth.go

10. Метрики

Сейчас в проекте есть простые in-memory counters:

  • успешные логины;
  • неуспешные логины;
  • количество запросов к tasks;
  • количество 4xx;
  • количество 5xx.

Endpoint:

  • /metrics

Он защищен LocalOnlyMiddleware, то есть доступен только с localhost.

Файлы:

  • internal/metrics/metrics.go
  • internal/transport/http/handlers/metrics.go
  • internal/transport/http/middleware/local_only.go

11. Интеграционные тесты PostgreSQL

Добавлены integration tests для Postgres storage:

  • internal/storage/postgres/user_store_integration_test.go
  • internal/storage/postgres/task_store_integration_test.go

Важный момент:

  • обычный go test ./... теперь не должен падать без локального Postgres;
  • integration tests запускаются только при явном RUN_POSTGRES_INTEGRATION_TESTS=1;
  • URL можно переопределить через TEST_DATABASE_URL;
  • если БД недоступна, тесты корректно Skip, а не ломают весь suite.

Файл инфраструктуры тестов:

  • internal/storage/postgres/postgres_test.go

Пример запуска:

RUN_POSTGRES_INTEGRATION_TESTS=1 \
TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5433/task_tracker_test?sslmode=disable \
GOCACHE=$(pwd)/.gocache \
go test ./internal/storage/postgres -v

12. Ручной запуск PostgreSQL

Пример через Docker:

docker run -d \
  --name task-tracker-postgres \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=task_tracker \
  -p 5432:5432 \
  postgres:16

Применение init-схемы:

docker exec -i task-tracker-postgres \
  psql -U postgres -d task_tracker < migrations/001_init.sql

Если поднимается legacy-схема со старым user_login, после этого потребуется отдельно применить:

docker exec -i task-tracker-postgres \
  psql -U postgres -d task_tracker < migrations/002_migrate_tasks_user_login_to_user_id.sql

13. Быстрый smoke test API

Регистрация

curl -X POST http://localhost:8081/register \
  -H "Content-Type: application/json" \
  -d '{"login":"alice","password":"password123"}'

Логин

TOKENS=$(curl -s -X POST http://localhost:8081/login \
  -H "Content-Type: application/json" \
  -d '{"login":"alice","password":"password123"}')

ACCESS_TOKEN=$(echo "$TOKENS" | jq -r .access_token)
REFRESH_TOKEN=$(echo "$TOKENS" | jq -r .refresh_token)

Обновление access token

curl -X POST http://localhost:8081/refresh \
  -H "Content-Type: application/json" \
  -d "{\"refresh_token\":\"$REFRESH_TOKEN\"}"

Создание задачи

CREATE_RESPONSE=$(curl -s -X POST http://localhost:8081/tasks \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"first task","description":"postgres works"}')

TASK_ID=$(echo "$CREATE_RESPONSE" | jq -r .id)

Получение задач

curl -X GET http://localhost:8081/tasks \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Обновление задачи

curl -X PUT http://localhost:8081/tasks/$TASK_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status":"done"}'

Удаление задачи

curl -X DELETE http://localhost:8081/tasks/$TASK_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"

14. Что еще остается техническим долгом

На текущем этапе логично обсуждать в review:

  1. миграционную стратегию между 001 и 002, чтобы цепочка была безопасной;
  2. проверку обязательности user_id в JWT claims после перехода на новую модель;
  3. connection pool настройки для Postgres;
  4. использование настоящего migration tool вместо ручного SQL применения;
  5. возможный переход с in-memory metrics на Prometheus/OpenTelemetry;
  6. дальнейшую чистку API-контрактов и структуры ошибок.