Учебный проект в рамках курса GO-advanced Яндекс.Практикума. Состоит из:
- сервиса расчета и аккумуляции бонусных баллов системы лояльности
- blackbox-сервиса accrual
Для получения полного списка доступных команд выполните:
make help
Сервер автоматически применит новые миграции при запуске.
Для работы с миграциями вручную можно установить утилиту golang-migrate:
go install -tags "postgres" github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# добавление новой миграции:
migrate create -ext sql -dir ./internal/storage/migrations -seq имя_миграции
# применить миграции:
migrate -database ${DATABASE_DSN} -path ./internal/storage/migrations up
# откатить миграции:
migrate -database ${DATABASE_DSN} -path ./internal/storage/migrations down -all
Сервер отвечает за взаимодействие с пользователем (получение списка накоплений, запрос расходования накоплений и т.п.)
./cmd/gophermart/gophermart -d${DATABASE_DSN}
Имеют приоритет перед конфигурационным файлом. Для вывода списка доступных опций и их значений по умолчанию выполните команду:
./cmd/gophermart/gophermart --help
-r, --accrual-address string address:port for accrual service (default "0.0.0.0:8181")
-d, --database string PostgreSQL database DSN
-a, --gophermart-address string address:port for HTTP API requests (default "0.0.0.0:8080")
-k, --secret string a key to sign data; will be generated automatically if empty
Имеют приоритет перед опциями командной строки.
# Адрес и порт для http-api:
export RUN_ADDRESS=0.0.0.0:8080
# Адрес и порт внешней системы начисления баллов:
export ACCRUAL_SYSTEM_ADDRESS=0.0.0.0:8080
# DSN для подключения к базе данных (postgres-only):
export DATABASE_URI=300
# Ключ для подписывания запросов, по умолчанию будет сгенерирован автоматически:
export APP_KEY=
Система представляет собой HTTP API со следующими требованиями к бизнес-логике:
- регистрация, аутентификация и авторизация пользователей;
- приём номеров заказов от зарегистрированных пользователей;
- учёт и ведение списка переданных номеров заказов зарегистрированного пользователя;
- учёт и ведение накопительного счёта зарегистрированного пользователя;
- проверка принятых номеров заказов через систему расчёта баллов лояльности;
- начисление за каждый подходящий номер заказа положенного вознаграждения на счёт лояльности пользователя.
- Пользователь регистрируется в системе лояльности «Гофермарт».
- Пользователь совершает покупку в интернет-магазине «Гофермарт».
- Заказ попадает в систему расчёта баллов лояльности.
- Пользователь передаёт номер совершённого заказа в систему лояльности.
- Система связывает номер заказа с пользователем и сверяет номер с системой расчёта баллов лояльности.
- При наличии положительного расчёта баллов лояльности производится начисление баллов лояльности на счёт пользователя.
- Пользователь списывает доступные баллы лояльности для частичной или полной оплаты последующих заказов в интернет-магазине «Гофермарт».
Примечания:
- пункт 2 представлен как гипотетический и не реализован в данном проекте;
- пункт 3 реализован в системе расчёта баллов лояльности и не реализован в данном проекте;
Система расчета баллов лояльности является внешним сервисом в доверенном контуре. Он работает по принципу чёрного ящика и недоступен для инспекции внешними клиентами. Система рассчитывает положенные баллы лояльности за совершённый заказ по сложным алгоритмам, которые могут меняться в любой момент времени.
Внешнему потребителю доступна только информация о количестве положенных за конкретный заказ баллов лояльности. Причины наличия или отсутствия начислений внешнему потребителю неизвестны.
Протокол взаимодействия с сервисом базы будет предоставлен в конце.
Накопительная система лояльности «Гофермарт» должна предоставлять следующие HTTP-хендлеры:
POST /api/user/register
— регистрация пользователя;POST /api/user/login
— аутентификация пользователя;POST /api/user/orders
— загрузка пользователем номера заказа для расчёта;GET /api/user/orders
— получение списка загруженных пользователем номеров заказов, статусов их обработки и информации о начислениях;GET /api/user/balance
— получение текущего баланса счёта баллов лояльности пользователя;POST /api/user/balance/withdraw
— запрос на списание баллов с накопительного счёта в счёт оплаты нового заказа;GET /api/user/withdrawals
— получение информации о выводе средств с накопительного счёта пользователем.
Хендлер: POST /api/user/register
.
Регистрация производится по паре логин/пароль. Каждый логин должен быть уникальным.
Формат запроса:
POST /api/user/register HTTP/1.1
Content-Type: application/json
...
{
"login": "<login>",
"password": "<password>"
}
Возможные коды ответа:
200
— пользователь успешно зарегистрирован и аутентифицирован;400
— неверный формат запроса;409
— логин уже занят;500
— внутренняя ошибка сервера.
Хендлер: POST /api/user/login
.
Аутентификация производится по паре логин/пароль.
Формат запроса:
POST /api/user/login HTTP/1.1
Content-Type: application/json
...
{
"login": "<login>",
"password": "<password>"
}
Возможные коды ответа:
200
— пользователь успешно аутентифицирован;400
— неверный формат запроса;401
— неверная пара логин/пароль;500
— внутренняя ошибка сервера.
Хендлер: POST /api/user/orders
.
Хендлер доступен только аутентифицированным пользователям. Номером заказа является последовательность цифр произвольной длины. Номер заказа проверяется на корректность ввода с помощью алгоритма Луна
Формат запроса:
POST /api/user/orders HTTP/1.1
Content-Type: text/plain
...
12345678903
Возможные коды ответа:
200
— номер заказа уже был загружен этим пользователем;202
— новый номер заказа принят в обработку;400
— неверный формат запроса;401
— пользователь не аутентифицирован;409
— номер заказа уже был загружен другим пользователем;422
— неверный формат номера заказа;500
— внутренняя ошибка сервера.
Хендлер: GET /api/user/orders
.
Хендлер доступен только авторизованному пользователю. Номера заказа в выдаче должны быть отсортированы по времени загрузки от самых новых к самым старым. Формат даты — RFC3339.
Доступные статусы обработки расчётов:
NEW
— заказ загружен в систему, но не попал в обработку;PROCESSING
— вознаграждение за заказ рассчитывается;INVALID
— система расчёта вознаграждений отказала в расчёте;PROCESSED
— данные по заказу проверены и информация о расчёте успешно получена.
Формат запроса:
GET /api/user/orders HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса.Формат ответа:
200 OK HTTP/1.1 Content-Type: application/json ... [ { "number": "9278923470", "status": "PROCESSED", "accrual": 500, "uploaded_at": "2020-12-10T15:15:45+03:00" }, { "number": "12345678903", "status": "PROCESSING", "uploaded_at": "2020-12-10T15:12:01+03:00" }, { "number": "346436439", "status": "INVALID", "uploaded_at": "2020-12-09T16:09:53+03:00" } ]
-
204
— нет данных для ответа. -
401
— пользователь не авторизован. -
500
— внутренняя ошибка сервера.
Хендлер: GET /api/user/balance
.
Хендлер доступен только авторизованному пользователю. В ответе должны содержаться данные о текущей сумме баллов лояльности, а также сумме использованных за весь период регистрации баллов.
Формат запроса:
GET /api/user/balance HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса.Формат ответа:
200 OK HTTP/1.1 Content-Type: application/json ... { "current": 500.5, "withdrawn": 42 }
-
401
— пользователь не авторизован. -
500
— внутренняя ошибка сервера.
Хендлер: POST /api/user/balance/withdraw
Хендлер доступен только авторизованному пользователю. Номер заказа представляет собой гипотетический номер нового заказа пользователя в счет оплаты которого списываются баллы.
Примечание: для успешного списания достаточно успешной регистрации запроса, никаких внешних систем начисления не предусмотрено и не требуется реализовывать.
Формат запроса:
POST /api/user/balance/withdraw HTTP/1.1
Content-Type: application/json
{
"order": "2377225624",
"sum": 751
}
Здесь order
— номер заказа, а sum
— сумма баллов к списанию в счёт оплаты.
Возможные коды ответа:
200
— успешная обработка запроса;401
— пользователь не авторизован;402
— на счету недостаточно средств;422
— неверный номер заказа;500
— внутренняя ошибка сервера.
Хендлер: GET /api/user/withdrawals
.
Хендлер доступен только авторизованному пользователю. Факты выводов в выдаче должны быть отсортированы по времени вывода от самых новых к самым старым. Формат даты — RFC3339.
Формат запроса:
GET /api/user/withdrawals HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса.Формат ответа:
200 OK HTTP/1.1 Content-Type: application/json ... [ { "order": "2377225624", "sum": 500, "processed_at": "2020-12-09T16:09:57+03:00" } ]
-
204
- нет ни одного списания. -
401
— пользователь не авторизован. -
500
— внутренняя ошибка сервера.
Для взаимодействия с системой доступен один хендлер:
GET /api/orders/{number}
— получение информации о расчёте начислений баллов лояльности.
Формат запроса:
GET /api/orders/{number} HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса.Формат ответа:
200 OK HTTP/1.1 Content-Type: application/json ... { "order": "<number>", "status": "PROCESSED", "accrual": 500 }
Поля объекта ответа:
-
order
— номер заказа; -
status
— статус расчёта начисления:REGISTERED
— заказ зарегистрирован, но не начисление не рассчитано;INVALID
— заказ не принят к расчёту, и вознаграждение не будет начислено;PROCESSING
— расчёт начисления в процессе;PROCESSED
— расчёт начисления окончен;
-
accrual
— рассчитанные баллы к начислению, при отсутствии начисления — поле отсутствует в ответе.
-
-
204
- заказ не зарегистрирован в системе расчета. -
429
— превышено количество запросов к сервису.Формат ответа:
429 Too Many Requests HTTP/1.1 Content-Type: text/plain Retry-After: 60 No more than N requests per minute allowed
-
500
— внутренняя ошибка сервера.