Init commit
This commit is contained in:
commit
6c37504c06
31
.dockerignore
Normal file
31
.dockerignore
Normal file
@ -0,0 +1,31 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
docker-compose.yaml
|
||||
.dockerignore
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below if you want to include vendored dependencies)
|
||||
# vendor/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# IDE specific files
|
||||
.idea
|
||||
.vscode
|
||||
6
.env.example
Normal file
6
.env.example
Normal file
@ -0,0 +1,6 @@
|
||||
TELEGRAM_BOT_TOKEN=123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ
|
||||
TELEGRAM_APP_URL=https://wish-list-bot.telegram.bot
|
||||
|
||||
MONGO_USER=mongo_user
|
||||
MONGO_PASSWORD=mongo_password
|
||||
MONGO_DATABASE=books
|
||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
46
Dockerfile
Normal file
46
Dockerfile
Normal file
@ -0,0 +1,46 @@
|
||||
FROM golang:1.23-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go.mod and go.sum first to leverage Docker cache
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy the rest of the code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN go build -o app ./cmd/main.go
|
||||
|
||||
# Use a minimal alpine image for the final container
|
||||
FROM alpine:latest
|
||||
|
||||
# Install necessary packages
|
||||
RUN apk --no-cache add ca-certificates netcat-openbsd
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the pre-built binary from the builder stage
|
||||
COPY --from=builder /app/app .
|
||||
|
||||
# Copy source code and tests for testing
|
||||
COPY --from=builder /app/pkg ./pkg
|
||||
COPY --from=builder /app/api ./api
|
||||
COPY --from=builder /app/tests ./tests
|
||||
COPY --from=builder /app/scripts ./scripts
|
||||
COPY --from=builder /app/go.mod ./go.mod
|
||||
COPY --from=builder /app/go.sum ./go.sum
|
||||
|
||||
# Copy the Go binary and environment for tests
|
||||
COPY --from=builder /usr/local/go /usr/local/go
|
||||
ENV PATH="/usr/local/go/bin:${PATH}"
|
||||
ENV GOPATH="/go"
|
||||
|
||||
# Set environment variables
|
||||
ENV GO_ENV=production
|
||||
|
||||
# Expose the application port
|
||||
EXPOSE 8080
|
||||
|
||||
# Command to run the application
|
||||
CMD ["./app"]
|
||||
168
README.md
Normal file
168
README.md
Normal file
@ -0,0 +1,168 @@
|
||||
# Wish List API
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
API-сервер для приложения списка желаний с использованием принципов чистой архитектуры.
|
||||
|
||||
## Описание
|
||||
|
||||
Wish List API - это RESTful API-сервис, который позволяет пользователям создавать, управлять и делиться своими списками желаемых подарков. Приложение разработано с использованием чистой архитектуры для обеспечения масштабируемости, тестируемости и поддержки в долгосрочной перспективе.
|
||||
|
||||
## Основные возможности
|
||||
|
||||
- Регистрация и аутентификация пользователей
|
||||
- Авторизация через Telegram
|
||||
- Создание, просмотр, редактирование и удаление списков желаний
|
||||
- Добавление, просмотр, редактирование и удаление элементов в списке желаний
|
||||
- Настройка приватности списков желаний (публичный/приватный доступ)
|
||||
- Подробная документация API через Swagger
|
||||
|
||||
## Технологический стек
|
||||
|
||||
- **Go** - язык программирования
|
||||
- **Fiber** - веб-фреймворк
|
||||
- **MongoDB** - база данных
|
||||
- **JWT** - токены для аутентификации
|
||||
- **Swagger** - документация API
|
||||
- **Docker** - контейнеризация приложения
|
||||
|
||||
## Архитектура проекта
|
||||
|
||||
Проект следует принципам чистой архитектуры, разделяя код на следующие слои:
|
||||
|
||||
- `api/` - HTTP обработчики, маршрутизация и представления
|
||||
- `handlers/` - HTTP обработчики запросов
|
||||
- `middleware/` - промежуточные обработчики
|
||||
- `presenter/` - форматирование ответов
|
||||
- `routes/` - маршрутизация API
|
||||
- `cmd/` - точка входа в приложение
|
||||
- `docs/` - автоматически сгенерированная Swagger документация
|
||||
- `pkg/` - основная бизнес-логика и сущности
|
||||
- `auth/` - аутентификация и авторизация
|
||||
- `entities/` - основные сущности (модели данных)
|
||||
- `user/` - сервисы для работы с пользователями
|
||||
- `wish-list/` - сервисы для работы со списками желаний
|
||||
- `tests/` - тесты
|
||||
- `unit/` - модульные тесты
|
||||
- `integration/` - интеграционные тесты
|
||||
|
||||
## Установка и запуск
|
||||
|
||||
### Требования
|
||||
|
||||
- [Go](https://golang.org/dl/) 1.18 или выше
|
||||
- [Docker](https://www.docker.com/get-started) и Docker Compose
|
||||
- [MongoDB](https://www.mongodb.com/try/download/community) (при локальном запуске без Docker)
|
||||
|
||||
### Переменные окружения
|
||||
|
||||
Создайте файл `.env` на основе `.env.example`:
|
||||
|
||||
```env
|
||||
TELEGRAM_BOT_TOKEN=123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ
|
||||
TELEGRAM_APP_URL=https://wish-list-bot.telegram.bot
|
||||
|
||||
MONGO_USER=mongo_user
|
||||
MONGO_PASSWORD=mongo_password
|
||||
MONGO_DATABASE=books
|
||||
```
|
||||
|
||||
### Запуск с использованием Docker
|
||||
|
||||
1. Клонируйте репозиторий:
|
||||
```bash
|
||||
git clone https://github.com/yourusername/wish-list-api.git
|
||||
cd wish-list-api
|
||||
```
|
||||
|
||||
2. Запустите приложение с помощью Docker Compose:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
3. API будет доступен по адресу `http://localhost:8080`
|
||||
4. Документация Swagger доступна по адресу `http://localhost:8080/docs/index.html`
|
||||
|
||||
### Локальный запуск для разработки
|
||||
|
||||
1. Клонируйте репозиторий:
|
||||
```bash
|
||||
git clone https://github.com/yourusername/wish-list-api.git
|
||||
cd wish-list-api
|
||||
```
|
||||
|
||||
2. Установите зависимости:
|
||||
```bash
|
||||
go mod download
|
||||
```
|
||||
|
||||
3. Запустите приложение:
|
||||
```bash
|
||||
go run cmd/main.go
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Основные эндпоинты API:
|
||||
|
||||
### Аутентификация
|
||||
|
||||
- **POST /api/auth/register** - Регистрация нового пользователя
|
||||
- **POST /api/auth/login** - Авторизация пользователя
|
||||
- **GET /api/auth/telegram-login** - Авторизация через Telegram
|
||||
|
||||
### Пользователи
|
||||
|
||||
- **GET /api/users/:id** - Получение информации о пользователе
|
||||
- **PUT /api/users/:id** - Обновление информации пользователя
|
||||
|
||||
### Списки желаний
|
||||
|
||||
- **POST /api/wishlist** - Создание нового списка желаний
|
||||
- **GET /api/wishlist/:id** - Получение списка желаний по ID
|
||||
- **GET /api/wishlist/user/:userId** - Получение всех списков желаний пользователя
|
||||
- **PUT /api/wishlist/:id** - Обновление списка желаний
|
||||
- **DELETE /api/wishlist/:id** - Удаление списка желаний
|
||||
|
||||
### Элементы списка желаний
|
||||
|
||||
- **POST /api/wishlist/item** - Добавление элемента в список желаний
|
||||
- **GET /api/wishlist/:wishlistId/items** - Получение всех элементов списка желаний
|
||||
- **GET /api/wishlist/item/:id** - Получение элемента списка по ID
|
||||
- **PUT /api/wishlist/item/:id** - Обновление элемента списка
|
||||
- **DELETE /api/wishlist/item/:id** - Удаление элемента из списка
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Запуск модульных тестов
|
||||
|
||||
```bash
|
||||
go test ./tests/unit/...
|
||||
```
|
||||
|
||||
### Запуск интеграционных тестов
|
||||
|
||||
```bash
|
||||
go test ./tests/integration/...
|
||||
```
|
||||
|
||||
### Запуск всех тестов с покрытием
|
||||
|
||||
```bash
|
||||
./run-docker-tests.sh
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Форкните репозиторий
|
||||
2. Создайте новую ветку для вашей функциональности (`git checkout -b feature/amazing-feature`)
|
||||
3. Зафиксируйте изменения (`git commit -m 'Add some amazing feature'`)
|
||||
4. Отправьте изменения в ветку (`git push origin feature/amazing-feature`)
|
||||
5. Создайте Pull Request
|
||||
|
||||
## Лицензия
|
||||
|
||||
Распространяется под лицензией MIT. Смотрите `LICENSE` для получения дополнительной информации.
|
||||
161
api/handlers/auth_handler.go
Normal file
161
api/handlers/auth_handler.go
Normal file
@ -0,0 +1,161 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/auth"
|
||||
"wish-list-api/pkg/entities"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// @Summary Вход пользователя
|
||||
// @Description Аутентифицирует пользователя и выдает JWT токены
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param credentials body entities.LoginRequest true "Учетные данные пользователя"
|
||||
// @Success 200 {object} presenter.AuthResponse
|
||||
// @Failure 400 {object} presenter.AuthResponse
|
||||
// @Failure 401 {object} presenter.AuthResponse
|
||||
// @Failure 500 {object} presenter.AuthResponse
|
||||
// @Router /auth/login [post]
|
||||
func Login(service auth.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var requestBody entities.LoginRequest
|
||||
err := c.BodyParser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
if requestBody.Email == "" || requestBody.Password == "" {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.AuthErrorResponse(fiber.ErrBadRequest))
|
||||
}
|
||||
|
||||
tokens, err := service.Login(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusUnauthorized)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.JSON(presenter.AuthSuccessResponse(tokens))
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Регистрация пользователя
|
||||
// @Description Регистрирует нового пользователя и выдает JWT токены
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user body entities.RegisterRequest true "Данные нового пользователя"
|
||||
// @Success 200 {object} presenter.AuthResponse
|
||||
// @Failure 400 {object} presenter.AuthResponse
|
||||
// @Failure 409 {object} presenter.AuthResponse
|
||||
// @Failure 500 {object} presenter.AuthResponse
|
||||
// @Router /auth/register [post]
|
||||
func Register(service auth.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var requestBody entities.RegisterRequest
|
||||
err := c.BodyParser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
if requestBody.Email == "" || requestBody.Password == "" {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.AuthErrorResponse(fiber.ErrBadRequest))
|
||||
}
|
||||
|
||||
user, err := service.Register(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusConflict)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
tokens, err := service.Login(&entities.LoginRequest{
|
||||
Email: requestBody.Email,
|
||||
Password: requestBody.Password,
|
||||
})
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.JSON(presenter.AuthSuccessResponseWithUser(tokens, user))
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Обновление токенов
|
||||
// @Description Обновляет JWT токены с помощью refresh токена
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param refreshToken body entities.TokenRequest true "Refresh токен"
|
||||
// @Success 200 {object} presenter.AuthResponse
|
||||
// @Failure 400 {object} presenter.AuthResponse
|
||||
// @Failure 401 {object} presenter.AuthResponse
|
||||
// @Failure 500 {object} presenter.AuthResponse
|
||||
// @Router /auth/refresh [post]
|
||||
func RefreshToken(service auth.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var requestBody entities.TokenRequest
|
||||
err := c.BodyParser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
if requestBody.RefreshToken == "" {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.AuthErrorResponse(fiber.ErrBadRequest))
|
||||
}
|
||||
|
||||
tokens, err := service.RefreshToken(requestBody.RefreshToken)
|
||||
if err != nil {
|
||||
c.Status(http.StatusUnauthorized)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.JSON(presenter.AuthSuccessResponse(tokens))
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Вход пользователя через Telegram
|
||||
// @Description Аутентифицирует пользователя через Telegram и выдает JWT токены
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param credentials body entities.TelegramAuthRequest true "Данные аутентификации Telegram"
|
||||
// @Success 200 {object} presenter.AuthResponse
|
||||
// @Failure 400 {object} presenter.AuthResponse
|
||||
// @Failure 401 {object} presenter.AuthResponse
|
||||
// @Failure 500 {object} presenter.AuthResponse
|
||||
// @Router /auth/telegram [post]
|
||||
func LoginWithTelegram(telegramService auth.TelegramAuthService) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var requestBody entities.TelegramAuthRequest
|
||||
err := c.BodyParser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
if requestBody.TelegramID == 0 || requestBody.AuthDate == 0 || requestBody.Hash == "" {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.AuthErrorResponse(fiber.ErrBadRequest))
|
||||
}
|
||||
|
||||
tokens, err := telegramService.AuthenticateWithTelegram(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusUnauthorized)
|
||||
return c.JSON(presenter.AuthErrorResponse(err))
|
||||
}
|
||||
|
||||
c.Status(http.StatusOK)
|
||||
return c.JSON(presenter.AuthSuccessResponse(tokens))
|
||||
}
|
||||
}
|
||||
177
api/handlers/user_handler.go
Normal file
177
api/handlers/user_handler.go
Normal file
@ -0,0 +1,177 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/entities"
|
||||
"wish-list-api/pkg/user"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// @Summary Добавить нового пользователя
|
||||
// @Description Создает нового пользователя в системе
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user body entities.User true "Информация о пользователе"
|
||||
// @Success 200 {object} presenter.UserResponse
|
||||
// @Failure 400 {object} presenter.UserResponse
|
||||
// @Failure 500 {object} presenter.UserResponse
|
||||
// @Router /users [post]
|
||||
func CreateUser(service user.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var requestBody entities.User
|
||||
err := c.BodyParser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
if requestBody.Email == "" || requestBody.Password == "" {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return c.JSON(presenter.UserErrorResponse(errors.New(
|
||||
"Please specify email and password")))
|
||||
}
|
||||
result, err := service.CreateUser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
return c.JSON(presenter.UserSuccessResponse(result))
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Обновить пользователя
|
||||
// @Description Обновляет информацию о существующем пользователе
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param user body entities.User true "Информация о пользователе для обновления"
|
||||
// @Success 200 {object} presenter.UserResponse
|
||||
// @Failure 400 {object} presenter.UserResponse
|
||||
// @Failure 500 {object} presenter.UserResponse
|
||||
// @Router /users [put]
|
||||
func UpdateUser(service user.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var requestBody entities.User
|
||||
err := c.BodyParser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
result, err := service.UpdateUser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
return c.JSON(presenter.UserSuccessResponse(result))
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Удалить пользователя
|
||||
// @Description Удаляет пользователя из системы по ID
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body entities.DeleteUserRequest true "ID пользователя для удаления"
|
||||
// @Success 200 {object} presenter.UserResponse
|
||||
// @Failure 400 {object} presenter.UserResponse
|
||||
// @Failure 500 {object} presenter.UserResponse
|
||||
// @Router /users [delete]
|
||||
func DeleteUser(service user.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var requestBody entities.DeleteUserRequest
|
||||
err := c.BodyParser(&requestBody)
|
||||
if err != nil {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
userID := requestBody.ID
|
||||
err = service.DeleteUser(userID)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
return c.JSON(presenter.UserSuccessResponse(nil))
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Получить всех пользователей
|
||||
// @Description Возвращает список всех пользователей в системе
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} presenter.UsersResponse
|
||||
// @Failure 500 {object} presenter.UserResponse
|
||||
// @Router /users [get]
|
||||
func GetAllUsers(service user.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
fetched, err := service.GetAllUsers()
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
return c.JSON(presenter.UsersSuccessResponse(fetched))
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Получить пользователя по ID
|
||||
// @Description Возвращает информацию о пользователе по его ID
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "ID пользователя"
|
||||
// @Success 200 {object} presenter.UserResponse
|
||||
// @Failure 400 {object} presenter.UserResponse
|
||||
// @Failure 500 {object} presenter.UserResponse
|
||||
// @Router /users/{id} [get]
|
||||
func GetUserByID(service user.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
if id == "" {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.UserErrorResponse(errors.New("id is required")))
|
||||
}
|
||||
|
||||
result, err := service.GetUser(id)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
return c.JSON(presenter.UserSuccessResponse(result))
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Получить пользователя по Email
|
||||
// @Description Возвращает информацию о пользователе по его Email
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param email path string true "Email пользователя"
|
||||
// @Success 200 {object} presenter.UserResponse
|
||||
// @Failure 400 {object} presenter.UserResponse
|
||||
// @Failure 500 {object} presenter.UserResponse
|
||||
// @Router /users/email/{email} [get]
|
||||
func GetUserByEmail(service user.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
email := c.Params("email")
|
||||
if email == "" {
|
||||
c.Status(http.StatusBadRequest)
|
||||
return c.JSON(presenter.UserErrorResponse(errors.New("email is required")))
|
||||
}
|
||||
|
||||
result, err := service.GetUserByEmail(email)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return c.JSON(presenter.UserErrorResponse(err))
|
||||
}
|
||||
return c.JSON(presenter.UserSuccessResponse(result))
|
||||
}
|
||||
}
|
||||
456
api/handlers/wish_list_handler.go
Normal file
456
api/handlers/wish_list_handler.go
Normal file
@ -0,0 +1,456 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/auth"
|
||||
"wish-list-api/pkg/entities"
|
||||
wishlist "wish-list-api/pkg/wish-list"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type WishListHandler struct {
|
||||
wishListService wishlist.Service
|
||||
authService auth.Service
|
||||
}
|
||||
|
||||
func NewWishListHandler(wishListService wishlist.Service, authService auth.Service) *WishListHandler {
|
||||
return &WishListHandler{
|
||||
wishListService: wishListService,
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Create a new wishlist
|
||||
// @Description Create a new wishlist for the authenticated user
|
||||
// @Tags wishlist
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string true "Bearer token"
|
||||
// @Param wishlist body entities.WishList true "Wishlist data"
|
||||
// @Success 201 {object} presenter.WishListResponse
|
||||
// @Failure 400 {object} presenter.WishListResponse
|
||||
// @Failure 401 {object} presenter.WishListResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist [post]
|
||||
func (h *WishListHandler) CreateWishList(c *fiber.Ctx) error {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
wishList := new(entities.WishList)
|
||||
if err := c.BodyParser(wishList); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
wishList.UserID = userID
|
||||
|
||||
result, err := h.wishListService.CreateWishList(wishList)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(presenter.WishListSuccessResponse(result))
|
||||
}
|
||||
|
||||
// @Summary Get a wishlist
|
||||
// @Description Get a wishlist by its ID
|
||||
// @Tags wishlist
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Wishlist ID"
|
||||
// @Success 200 {object} presenter.WishListResponse
|
||||
// @Failure 404 {object} presenter.WishListResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/{id} [get]
|
||||
func (h *WishListHandler) GetWishList(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
result, err := h.wishListService.GetWishList(id)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if !result.IsPublic {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil || userID != result.UserID {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(presenter.WishListSuccessResponse(result))
|
||||
}
|
||||
|
||||
// @Summary Get user wishlists
|
||||
// @Description Get all wishlists for a specific user
|
||||
// @Tags wishlist
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param userId path string true "User ID"
|
||||
// @Success 200 {object} presenter.WishListsResponse
|
||||
// @Failure 404 {object} presenter.WishListResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/user/{userId} [get]
|
||||
func (h *WishListHandler) GetUserWishLists(c *fiber.Ctx) error {
|
||||
userID := c.Params("userId")
|
||||
|
||||
isOwner := false
|
||||
token := c.Get("Authorization")
|
||||
if token != "" {
|
||||
requestorID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err == nil && requestorID == userID {
|
||||
isOwner = true
|
||||
}
|
||||
}
|
||||
|
||||
var result *[]presenter.WishList
|
||||
var err error
|
||||
|
||||
result, err = h.wishListService.GetAllWishLists(userID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if !isOwner {
|
||||
publicLists := []presenter.WishList{}
|
||||
for _, list := range *result {
|
||||
if list.IsPublic {
|
||||
publicLists = append(publicLists, list)
|
||||
}
|
||||
}
|
||||
filteredResult := publicLists
|
||||
result = &filteredResult
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(presenter.WishListsSuccessResponse(result))
|
||||
}
|
||||
|
||||
// @Summary Update a wishlist
|
||||
// @Description Update an existing wishlist
|
||||
// @Tags wishlist
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string true "Bearer token"
|
||||
// @Param id path string true "Wishlist ID"
|
||||
// @Param wishlist body entities.WishList true "Updated wishlist data"
|
||||
// @Success 200 {object} presenter.WishListResponse
|
||||
// @Failure 400 {object} presenter.WishListResponse
|
||||
// @Failure 401 {object} presenter.WishListResponse
|
||||
// @Failure 404 {object} presenter.WishListResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/{id} [put]
|
||||
func (h *WishListHandler) UpdateWishList(c *fiber.Ctx) error {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
currentWishList, err := h.wishListService.GetWishList(id)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if currentWishList.UserID != userID {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
wishList := new(entities.WishList)
|
||||
if err := c.BodyParser(wishList); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
wishList.ID = id
|
||||
wishList.UserID = userID
|
||||
|
||||
result, err := h.wishListService.UpdateWishList(wishList)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(presenter.WishListSuccessResponse(result))
|
||||
}
|
||||
|
||||
// @Summary Delete a wishlist
|
||||
// @Description Delete a wishlist and all its items
|
||||
// @Tags wishlist
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string true "Bearer token"
|
||||
// @Param id path string true "Wishlist ID"
|
||||
// @Success 200 {object} presenter.WishListResponse
|
||||
// @Failure 401 {object} presenter.WishListResponse
|
||||
// @Failure 404 {object} presenter.WishListResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/{id} [delete]
|
||||
func (h *WishListHandler) DeleteWishList(c *fiber.Ctx) error {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
currentWishList, err := h.wishListService.GetWishList(id)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if currentWishList.UserID != userID {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
if err := h.wishListService.DeleteWishList(id); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"status": true,
|
||||
"data": "Wishlist deleted successfully",
|
||||
"error": nil,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Create a wishlist item
|
||||
// @Description Create a new item for a wishlist
|
||||
// @Tags wishlist-items
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string true "Bearer token"
|
||||
// @Param item body entities.WishListItem true "Wishlist item data"
|
||||
// @Success 201 {object} presenter.WishListItemResponse
|
||||
// @Failure 400 {object} presenter.WishListItemResponse
|
||||
// @Failure 401 {object} presenter.WishListItemResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/item [post]
|
||||
func (h *WishListHandler) CreateWishListItem(c *fiber.Ctx) error {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
item := new(entities.WishListItem)
|
||||
if err := c.BodyParser(item); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
wishList, err := h.wishListService.GetWishList(item.WishListID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if wishList.UserID != userID {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
result, err := h.wishListService.CreateWishListItem(item)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(presenter.WishListItemSuccessResponse(result))
|
||||
}
|
||||
|
||||
// @Summary Get a wishlist item
|
||||
// @Description Get a wishlist item by its ID
|
||||
// @Tags wishlist-items
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Success 200 {object} presenter.WishListItemResponse
|
||||
// @Failure 404 {object} presenter.WishListItemResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/item/{id} [get]
|
||||
func (h *WishListHandler) GetWishListItem(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
result, err := h.wishListService.GetWishListItem(id)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
wishList, err := h.wishListService.GetWishList(result.WishListID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if !wishList.IsPublic {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil || userID != wishList.UserID {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(presenter.WishListItemSuccessResponse(result))
|
||||
}
|
||||
|
||||
// @Summary Get wishlist items
|
||||
// @Description Get all items in a wishlist
|
||||
// @Tags wishlist-items
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param wishlistId path string true "Wishlist ID"
|
||||
// @Success 200 {object} presenter.WishListItemsResponse
|
||||
// @Failure 404 {object} presenter.WishListItemsResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/{wishlistId}/items [get]
|
||||
func (h *WishListHandler) GetWishListItems(c *fiber.Ctx) error {
|
||||
wishListID := c.Params("wishlistId")
|
||||
|
||||
wishList, err := h.wishListService.GetWishList(wishListID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if !wishList.IsPublic {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil || userID != wishList.UserID {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.wishListService.GetAllWishListItems(wishListID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(presenter.WishListItemsSuccessResponse(result))
|
||||
}
|
||||
|
||||
// @Summary Update a wishlist item
|
||||
// @Description Update an existing wishlist item
|
||||
// @Tags wishlist-items
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string true "Bearer token"
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param item body entities.WishListItem true "Updated item data"
|
||||
// @Success 200 {object} presenter.WishListItemResponse
|
||||
// @Failure 400 {object} presenter.WishListItemResponse
|
||||
// @Failure 401 {object} presenter.WishListItemResponse
|
||||
// @Failure 404 {object} presenter.WishListItemResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/item/{id} [put]
|
||||
func (h *WishListHandler) UpdateWishListItem(c *fiber.Ctx) error {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
currentItem, err := h.wishListService.GetWishListItem(id)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
wishList, err := h.wishListService.GetWishList(currentItem.WishListID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if wishList.UserID != userID {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
item := new(entities.WishListItem)
|
||||
if err := c.BodyParser(item); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
item.ID = id
|
||||
item.WishListID = currentItem.WishListID
|
||||
|
||||
result, err := h.wishListService.UpdateWishListItem(item)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(presenter.WishListItemSuccessResponse(result))
|
||||
}
|
||||
|
||||
// @Summary Delete a wishlist item
|
||||
// @Description Delete a wishlist item
|
||||
// @Tags wishlist-items
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string true "Bearer token"
|
||||
// @Param id path string true "Item ID"
|
||||
// @Success 200 {object} presenter.WishListItemResponse
|
||||
// @Failure 401 {object} presenter.WishListItemResponse
|
||||
// @Failure 404 {object} presenter.WishListItemResponse
|
||||
// @Security BearerAuth
|
||||
// @Router /wishlist/item/{id} [delete]
|
||||
func (h *WishListHandler) DeleteWishListItem(c *fiber.Ctx) error {
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
userID, err := h.authService.GetUserIDFromToken(token)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
currentItem, err := h.wishListService.GetWishListItem(id)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
wishList, err := h.wishListService.GetWishList(currentItem.WishListID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
if wishList.UserID != userID {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(presenter.WishListErrorResponse(fiber.ErrUnauthorized))
|
||||
}
|
||||
|
||||
if err := h.wishListService.DeleteWishListItem(id); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(presenter.WishListErrorResponse(err))
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"status": true,
|
||||
"data": "Wishlist item deleted successfully",
|
||||
"error": nil,
|
||||
})
|
||||
}
|
||||
46
api/middleware/auth_middleware.go
Normal file
46
api/middleware/auth_middleware.go
Normal file
@ -0,0 +1,46 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"wish-list-api/pkg/auth"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func Protected(authService auth.Service) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"status": false,
|
||||
"error": "требуется авторизация",
|
||||
})
|
||||
}
|
||||
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
|
||||
token, err := authService.ValidateToken(tokenString)
|
||||
if err != nil || !token.Valid {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"status": false,
|
||||
"error": "недействительный токен",
|
||||
})
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"status": false,
|
||||
"error": "недействительный токен",
|
||||
})
|
||||
}
|
||||
|
||||
c.Locals("userID", claims["user_id"])
|
||||
c.Locals("email", claims["email"])
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
46
api/presenter/auth.go
Normal file
46
api/presenter/auth.go
Normal file
@ -0,0 +1,46 @@
|
||||
package presenter
|
||||
|
||||
import (
|
||||
"wish-list-api/pkg/entities"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type AuthResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data *entities.TokenPair `json:"data,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
func AuthSuccessResponse(tokens *entities.TokenPair) *fiber.Map {
|
||||
return &fiber.Map{
|
||||
"status": true,
|
||||
"data": tokens,
|
||||
"error": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func AuthSuccessResponseWithUser(tokens *entities.TokenPair, user *entities.User) *fiber.Map {
|
||||
userResponse := User{
|
||||
ID: user.ID,
|
||||
Email: user.Email,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
return &fiber.Map{
|
||||
"status": true,
|
||||
"data": tokens,
|
||||
"user": userResponse,
|
||||
"error": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func AuthErrorResponse(err error) *fiber.Map {
|
||||
return &fiber.Map{
|
||||
"status": false,
|
||||
"data": nil,
|
||||
"error": err.Error(),
|
||||
}
|
||||
}
|
||||
59
api/presenter/user.go
Normal file
59
api/presenter/user.go
Normal file
@ -0,0 +1,59 @@
|
||||
package presenter
|
||||
|
||||
import (
|
||||
"wish-list-api/pkg/entities"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data User `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
type UsersResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data []User `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
func UserSuccessResponse(data *entities.User) *fiber.Map {
|
||||
user := User{
|
||||
ID: data.ID,
|
||||
Email: data.Email,
|
||||
CreatedAt: data.CreatedAt,
|
||||
UpdatedAt: data.UpdatedAt,
|
||||
}
|
||||
return &fiber.Map{
|
||||
"status": true,
|
||||
"data": user,
|
||||
"error": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func UsersSuccessResponse(data *[]User) *fiber.Map {
|
||||
return &fiber.Map{
|
||||
"status": true,
|
||||
"data": data,
|
||||
"error": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func UserErrorResponse(err error) *fiber.Map {
|
||||
return &fiber.Map{
|
||||
"status": false,
|
||||
"data": "",
|
||||
"error": err.Error(),
|
||||
}
|
||||
}
|
||||
120
api/presenter/wish-list.go
Normal file
120
api/presenter/wish-list.go
Normal file
@ -0,0 +1,120 @@
|
||||
package presenter
|
||||
|
||||
import (
|
||||
"wish-list-api/pkg/entities"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type WishList struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
|
||||
Title string `json:"title"`
|
||||
UserID string `json:"user_id"`
|
||||
Description string `json:"description"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
PhotoURL string `json:"photo_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type WishListItem struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Cost float64 `json:"cost"`
|
||||
WishListID string `json:"wish_list_id"`
|
||||
Description string `json:"description"`
|
||||
PhotoURL string `json:"photo_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type WishListResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data WishList `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
type WishListsResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data []WishList `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
type WishListItemResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data WishListItem `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
type WishListItemsResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data []WishListItem `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
func WishListSuccessResponse(data *entities.WishList) *fiber.Map {
|
||||
id, _ := primitive.ObjectIDFromHex(data.ID)
|
||||
wishList := WishList{
|
||||
ID: id,
|
||||
Title: data.Title,
|
||||
UserID: data.UserID,
|
||||
Description: data.Description,
|
||||
IsPublic: data.IsPublic,
|
||||
PhotoURL: data.PhotoURL,
|
||||
CreatedAt: data.CreatedAt,
|
||||
UpdatedAt: data.UpdatedAt,
|
||||
}
|
||||
return &fiber.Map{
|
||||
"status": true,
|
||||
"data": wishList,
|
||||
"error": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func WishListsSuccessResponse(data *[]WishList) *fiber.Map {
|
||||
return &fiber.Map{
|
||||
"status": true,
|
||||
"data": data,
|
||||
"error": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func WishListItemSuccessResponse(data *entities.WishListItem) *fiber.Map {
|
||||
id, _ := primitive.ObjectIDFromHex(data.ID)
|
||||
wishListItem := WishListItem{
|
||||
ID: id,
|
||||
Title: data.Title,
|
||||
URL: data.URL,
|
||||
Cost: data.Cost,
|
||||
WishListID: data.WishListID,
|
||||
Description: data.Description,
|
||||
PhotoURL: data.PhotoURL,
|
||||
CreatedAt: data.CreatedAt,
|
||||
UpdatedAt: data.UpdatedAt,
|
||||
}
|
||||
return &fiber.Map{
|
||||
"status": true,
|
||||
"data": wishListItem,
|
||||
"error": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func WishListItemsSuccessResponse(data *[]WishListItem) *fiber.Map {
|
||||
return &fiber.Map{
|
||||
"status": true,
|
||||
"data": data,
|
||||
"error": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func WishListErrorResponse(err error) *fiber.Map {
|
||||
return &fiber.Map{
|
||||
"status": false,
|
||||
"data": "",
|
||||
"error": err.Error(),
|
||||
}
|
||||
}
|
||||
15
api/routes/auth.go
Normal file
15
api/routes/auth.go
Normal file
@ -0,0 +1,15 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"wish-list-api/api/handlers"
|
||||
"wish-list-api/pkg/auth"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func AuthRouter(app fiber.Router, service auth.Service, telegramService auth.TelegramAuthService) {
|
||||
app.Post("/auth/login", handlers.Login(service))
|
||||
app.Post("/auth/register", handlers.Register(service))
|
||||
app.Post("/auth/refresh", handlers.RefreshToken(service))
|
||||
app.Post("/auth/telegram", handlers.LoginWithTelegram(telegramService))
|
||||
}
|
||||
21
api/routes/user.go
Normal file
21
api/routes/user.go
Normal file
@ -0,0 +1,21 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"wish-list-api/api/handlers"
|
||||
"wish-list-api/api/middleware"
|
||||
"wish-list-api/pkg/auth"
|
||||
"wish-list-api/pkg/user"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func UserRouter(app fiber.Router, userService user.Service, authService auth.Service) {
|
||||
app.Post("/users", handlers.CreateUser(userService))
|
||||
|
||||
app.Use("/users", middleware.Protected(authService))
|
||||
app.Get("/users", handlers.GetAllUsers(userService))
|
||||
app.Get("/users/:id", handlers.GetUserByID(userService))
|
||||
app.Get("/users/email/:email", handlers.GetUserByEmail(userService))
|
||||
app.Put("/users", handlers.UpdateUser(userService))
|
||||
app.Delete("/users", handlers.DeleteUser(userService))
|
||||
}
|
||||
32
api/routes/wish_list.go
Normal file
32
api/routes/wish_list.go
Normal file
@ -0,0 +1,32 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"wish-list-api/api/handlers"
|
||||
"wish-list-api/api/middleware"
|
||||
"wish-list-api/pkg/auth"
|
||||
wishlist "wish-list-api/pkg/wish-list"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func WishListRouter(api fiber.Router, wishListService wishlist.Service, authService auth.Service) {
|
||||
wishListHandler := handlers.NewWishListHandler(wishListService, authService)
|
||||
|
||||
wishList := api.Group("/wishlist")
|
||||
|
||||
wishList.Get("/:id", wishListHandler.GetWishList)
|
||||
|
||||
wishList.Get("/user/:userId", wishListHandler.GetUserWishLists)
|
||||
|
||||
wishList.Get("/:wishlistId/items", wishListHandler.GetWishListItems)
|
||||
|
||||
wishList.Get("/item/:id", wishListHandler.GetWishListItem)
|
||||
|
||||
wishList.Post("/", middleware.Protected(authService), wishListHandler.CreateWishList)
|
||||
wishList.Put("/:id", middleware.Protected(authService), wishListHandler.UpdateWishList)
|
||||
wishList.Delete("/:id", middleware.Protected(authService), wishListHandler.DeleteWishList)
|
||||
|
||||
wishList.Post("/item", middleware.Protected(authService), wishListHandler.CreateWishListItem)
|
||||
wishList.Put("/item/:id", middleware.Protected(authService), wishListHandler.UpdateWishListItem)
|
||||
wishList.Delete("/item/:id", middleware.Protected(authService), wishListHandler.DeleteWishListItem)
|
||||
}
|
||||
109
cmd/main.go
Normal file
109
cmd/main.go
Normal file
@ -0,0 +1,109 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"wish-list-api/api/routes"
|
||||
_ "wish-list-api/docs"
|
||||
"wish-list-api/pkg/auth"
|
||||
"wish-list-api/pkg/user"
|
||||
wishlist "wish-list-api/pkg/wish-list"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/redirect"
|
||||
|
||||
swagger "github.com/swaggo/fiber-swagger"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// @title Wish List API
|
||||
// @version 1.0
|
||||
// @description API-сервер для приложения списка желаний
|
||||
// @securityDefinitions.apikey BearerAuth
|
||||
// @in header
|
||||
// @name Authorization
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
// @host localhost:8080
|
||||
// @BasePath /api
|
||||
func main() {
|
||||
db, cancel, err := databaseConnection()
|
||||
defer cancel()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Database Connection Error $s", err)
|
||||
}
|
||||
|
||||
fmt.Println("Database connection success!")
|
||||
|
||||
userCollection := db.Collection("users")
|
||||
userRepo := user.NewRepo(userCollection)
|
||||
userService := user.NewService(userRepo)
|
||||
|
||||
wishListCollection := db.Collection("wish_lists")
|
||||
wishListItemCollection := db.Collection("wish_list_items")
|
||||
wishListRepo := wishlist.NewRepo(wishListCollection, wishListItemCollection)
|
||||
wishListService := wishlist.NewService(wishListRepo)
|
||||
|
||||
authService := auth.NewService(auth.ServiceConfig{
|
||||
UserService: userService,
|
||||
})
|
||||
|
||||
telegramAuthService := auth.NewTelegramAuthService(auth.TelegramAuthConfig{
|
||||
UserService: userService,
|
||||
AuthService: authService,
|
||||
BotToken: os.Getenv("TELEGRAM_BOT_TOKEN"),
|
||||
TelegramAppUrl: os.Getenv("TELEGRAM_APP_URL"),
|
||||
})
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(cors.New())
|
||||
app.Use(logger.New())
|
||||
|
||||
app.Use(redirect.New(redirect.Config{
|
||||
Rules: map[string]string{
|
||||
"/": "/docs/index.html",
|
||||
},
|
||||
StatusCode: 301,
|
||||
}))
|
||||
|
||||
app.Get("/docs/*", swagger.FiberWrapHandler())
|
||||
|
||||
api := app.Group("/api")
|
||||
|
||||
routes.AuthRouter(api, authService, telegramAuthService)
|
||||
routes.UserRouter(api, userService, authService)
|
||||
routes.WishListRouter(api, wishListService, authService)
|
||||
|
||||
log.Fatal(app.Listen(":8080"))
|
||||
}
|
||||
|
||||
func databaseConnection() (*mongo.Database, context.CancelFunc, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
|
||||
mongoURI := os.Getenv("MONGODB_URI")
|
||||
|
||||
fmt.Println("MongoDB URI: ", mongoURI)
|
||||
|
||||
if mongoURI == "" {
|
||||
mongoURI = "mongodb://mongo_user:mongo_password@localhost:27017/admin"
|
||||
}
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI).
|
||||
SetServerSelectionTimeout(5*time.Second))
|
||||
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
db := client.Database("books")
|
||||
return db, cancel, nil
|
||||
}
|
||||
108
coverage/coverage_integration.html
Normal file
108
coverage/coverage_integration.html
Normal file
@ -0,0 +1,108 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Go Coverage Report</title>
|
||||
<style>
|
||||
body {
|
||||
background: black;
|
||||
color: rgb(80, 80, 80);
|
||||
}
|
||||
body, pre, #legend span {
|
||||
font-family: Menlo, monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
#topbar {
|
||||
background: black;
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 42px;
|
||||
border-bottom: 1px solid rgb(80, 80, 80);
|
||||
}
|
||||
#content {
|
||||
margin-top: 50px;
|
||||
}
|
||||
#nav, #legend {
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#legend {
|
||||
margin-top: 12px;
|
||||
}
|
||||
#nav {
|
||||
margin-top: 10px;
|
||||
}
|
||||
#legend span {
|
||||
margin: 0 5px;
|
||||
}
|
||||
.cov0 { color: rgb(192, 0, 0) }
|
||||
.cov1 { color: rgb(128, 128, 128) }
|
||||
.cov2 { color: rgb(116, 140, 131) }
|
||||
.cov3 { color: rgb(104, 152, 134) }
|
||||
.cov4 { color: rgb(92, 164, 137) }
|
||||
.cov5 { color: rgb(80, 176, 140) }
|
||||
.cov6 { color: rgb(68, 188, 143) }
|
||||
.cov7 { color: rgb(56, 200, 146) }
|
||||
.cov8 { color: rgb(44, 212, 149) }
|
||||
.cov9 { color: rgb(32, 224, 152) }
|
||||
.cov10 { color: rgb(20, 236, 155) }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="topbar">
|
||||
<div id="nav">
|
||||
<select id="files">
|
||||
|
||||
</select>
|
||||
</div>
|
||||
<div id="legend">
|
||||
<span>not tracked</span>
|
||||
|
||||
<span class="cov0">no coverage</span>
|
||||
<span class="cov1">low coverage</span>
|
||||
<span class="cov2">*</span>
|
||||
<span class="cov3">*</span>
|
||||
<span class="cov4">*</span>
|
||||
<span class="cov5">*</span>
|
||||
<span class="cov6">*</span>
|
||||
<span class="cov7">*</span>
|
||||
<span class="cov8">*</span>
|
||||
<span class="cov9">*</span>
|
||||
<span class="cov10">high coverage</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
(function() {
|
||||
var files = document.getElementById('files');
|
||||
var visible;
|
||||
files.addEventListener('change', onChange, false);
|
||||
function select(part) {
|
||||
if (visible)
|
||||
visible.style.display = 'none';
|
||||
visible = document.getElementById(part);
|
||||
if (!visible)
|
||||
return;
|
||||
files.value = part;
|
||||
visible.style.display = 'block';
|
||||
location.hash = part;
|
||||
}
|
||||
function onChange() {
|
||||
select(files.value);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
if (location.hash != "") {
|
||||
select(location.hash.substr(1));
|
||||
}
|
||||
if (!visible) {
|
||||
select("file0");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</html>
|
||||
429
coverage/coverage_unit.html
Normal file
429
coverage/coverage_unit.html
Normal file
@ -0,0 +1,429 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>unit: Go Coverage Report</title>
|
||||
<style>
|
||||
body {
|
||||
background: black;
|
||||
color: rgb(80, 80, 80);
|
||||
}
|
||||
body, pre, #legend span {
|
||||
font-family: Menlo, monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
#topbar {
|
||||
background: black;
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 42px;
|
||||
border-bottom: 1px solid rgb(80, 80, 80);
|
||||
}
|
||||
#content {
|
||||
margin-top: 50px;
|
||||
}
|
||||
#nav, #legend {
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#legend {
|
||||
margin-top: 12px;
|
||||
}
|
||||
#nav {
|
||||
margin-top: 10px;
|
||||
}
|
||||
#legend span {
|
||||
margin: 0 5px;
|
||||
}
|
||||
.cov0 { color: rgb(192, 0, 0) }
|
||||
.cov1 { color: rgb(128, 128, 128) }
|
||||
.cov2 { color: rgb(116, 140, 131) }
|
||||
.cov3 { color: rgb(104, 152, 134) }
|
||||
.cov4 { color: rgb(92, 164, 137) }
|
||||
.cov5 { color: rgb(80, 176, 140) }
|
||||
.cov6 { color: rgb(68, 188, 143) }
|
||||
.cov7 { color: rgb(56, 200, 146) }
|
||||
.cov8 { color: rgb(44, 212, 149) }
|
||||
.cov9 { color: rgb(32, 224, 152) }
|
||||
.cov10 { color: rgb(20, 236, 155) }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="topbar">
|
||||
<div id="nav">
|
||||
<select id="files">
|
||||
|
||||
<option value="file0">wish-list-api/tests/unit/mock_auth.go (22.5%)</option>
|
||||
|
||||
<option value="file1">wish-list-api/tests/unit/mock_repository.go (56.5%)</option>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
<div id="legend">
|
||||
<span>not tracked</span>
|
||||
|
||||
<span class="cov0">not covered</span>
|
||||
<span class="cov8">covered</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
|
||||
<pre class="file" id="file0" style="display: none">package unit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"wish-list-api/pkg/auth"
|
||||
"wish-list-api/pkg/entities"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// MockAuthService реализует интерфейс auth.Service
|
||||
type MockAuthService struct {
|
||||
users map[primitive.ObjectID]*entities.User
|
||||
// Токен -> ID пользователя
|
||||
tokens map[string]primitive.ObjectID
|
||||
}
|
||||
|
||||
// NewMockAuthService создает новый mock сервис аутентификации
|
||||
func NewMockAuthService() auth.Service <span class="cov8" title="1">{
|
||||
return &MockAuthService{
|
||||
users: make(map[primitive.ObjectID]*entities.User),
|
||||
tokens: make(map[string]primitive.ObjectID),
|
||||
}
|
||||
}</span>
|
||||
|
||||
// AddMockUser добавляет мок-пользователя в сервис
|
||||
func (m *MockAuthService) AddMockUser(user *entities.User, token string) <span class="cov8" title="1">{
|
||||
m.users[user.ID] = user
|
||||
m.tokens[token] = user.ID
|
||||
}</span>
|
||||
|
||||
// Login реализация для теста
|
||||
func (m *MockAuthService) Login(credentials *entities.LoginRequest) (*entities.TokenPair, error) <span class="cov0" title="0">{
|
||||
for _, user := range m.users </span><span class="cov0" title="0">{
|
||||
if user.Email == credentials.Email && m.ComparePasswords(user.Password, credentials.Password) </span><span class="cov0" title="0">{
|
||||
token := "mock-token-for-" + user.ID.Hex()
|
||||
m.tokens[token] = user.ID
|
||||
return &entities.TokenPair{
|
||||
AccessToken: token,
|
||||
RefreshToken: "refresh-" + token,
|
||||
}, nil
|
||||
}</span>
|
||||
}
|
||||
<span class="cov0" title="0">return nil, errors.New("invalid credentials")</span>
|
||||
}
|
||||
|
||||
// Register реализация для теста
|
||||
func (m *MockAuthService) Register(userData *entities.RegisterRequest) (*entities.User, error) <span class="cov0" title="0">{
|
||||
// Проверяем, что email не занят
|
||||
for _, existingUser := range m.users </span><span class="cov0" title="0">{
|
||||
if existingUser.Email == userData.Email </span><span class="cov0" title="0">{
|
||||
return nil, errors.New("email already exists")
|
||||
}</span>
|
||||
}
|
||||
|
||||
// Создаем нового пользователя
|
||||
<span class="cov0" title="0">hashedPassword, _ := m.HashPassword(userData.Password)
|
||||
user := &entities.User{
|
||||
ID: primitive.NewObjectID(),
|
||||
Email: userData.Email,
|
||||
Password: hashedPassword,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Сохраняем пользователя
|
||||
m.users[user.ID] = user
|
||||
|
||||
return user, nil</span>
|
||||
}
|
||||
|
||||
// RefreshToken реализация для теста
|
||||
func (m *MockAuthService) RefreshToken(refreshToken string) (*entities.TokenPair, error) <span class="cov0" title="0">{
|
||||
// Предполагаем, что refresh token имеет вид "refresh-<access-token>"
|
||||
if len(refreshToken) < 8 </span><span class="cov0" title="0">{
|
||||
return nil, errors.New("invalid refresh token")
|
||||
}</span>
|
||||
|
||||
<span class="cov0" title="0">accessToken := refreshToken[8:] // Получаем access token из refresh token
|
||||
userID, ok := m.tokens[accessToken]
|
||||
if !ok </span><span class="cov0" title="0">{
|
||||
return nil, errors.New("invalid refresh token")
|
||||
}</span>
|
||||
|
||||
<span class="cov0" title="0">newToken := "mock-token-for-" + userID.Hex() + "-refreshed"
|
||||
m.tokens[newToken] = userID
|
||||
|
||||
return &entities.TokenPair{
|
||||
AccessToken: newToken,
|
||||
RefreshToken: "refresh-" + newToken,
|
||||
}, nil</span>
|
||||
}
|
||||
|
||||
// ValidateToken реализация для теста
|
||||
func (m *MockAuthService) ValidateToken(tokenString string) (*jwt.Token, error) <span class="cov0" title="0">{
|
||||
// Проверяем, имеет ли токен префикс "Bearer "
|
||||
if len(tokenString) > 7 && tokenString[:7] == "Bearer " </span><span class="cov0" title="0">{
|
||||
tokenString = tokenString[7:]
|
||||
}</span>
|
||||
|
||||
<span class="cov0" title="0">userID, ok := m.tokens[tokenString]
|
||||
if !ok </span><span class="cov0" title="0">{
|
||||
return nil, errors.New("invalid token")
|
||||
}</span>
|
||||
|
||||
// Создаем mock JWT token
|
||||
<span class="cov0" title="0">token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"user_id": userID.Hex(),
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
})
|
||||
|
||||
return token, nil</span>
|
||||
}
|
||||
|
||||
// GetUserIDFromToken реализация для теста
|
||||
func (m *MockAuthService) GetUserIDFromToken(tokenString string) (string, error) <span class="cov8" title="1">{
|
||||
// Проверяем, имеет ли токен префикс "Bearer "
|
||||
if len(tokenString) > 7 && tokenString[:7] == "Bearer " </span><span class="cov8" title="1">{
|
||||
tokenString = tokenString[7:]
|
||||
}</span>
|
||||
|
||||
<span class="cov8" title="1">userID, ok := m.tokens[tokenString]
|
||||
if !ok </span><span class="cov8" title="1">{
|
||||
return "", errors.New("invalid token")
|
||||
}</span>
|
||||
<span class="cov8" title="1">return userID.Hex(), nil</span>
|
||||
}
|
||||
|
||||
// HashPassword реализация для теста
|
||||
func (m *MockAuthService) HashPassword(password string) (string, error) <span class="cov0" title="0">{
|
||||
// Просто возвращаем пароль с префиксом для имитации хеширования
|
||||
return "hashed_" + password, nil
|
||||
}</span>
|
||||
|
||||
// ComparePasswords реализация для теста
|
||||
func (m *MockAuthService) ComparePasswords(hashedPassword string, plainPassword string) bool <span class="cov0" title="0">{
|
||||
// Проверяем, соответствует ли "хешированный" пароль нашей простой схеме
|
||||
return hashedPassword == "hashed_"+plainPassword
|
||||
}</span>
|
||||
</pre>
|
||||
|
||||
<pre class="file" id="file1" style="display: none">package unit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/entities"
|
||||
wishlist "wish-list-api/pkg/wish-list"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// MockWishListRepository реализует интерфейс Repository для тестирования
|
||||
type MockWishListRepository struct {
|
||||
wishLists map[string]*entities.WishList
|
||||
wishListItems map[string]*entities.WishListItem
|
||||
}
|
||||
|
||||
// NewMockWishListRepository создает новый экземпляр мок-репозитория
|
||||
func NewMockWishListRepository() wishlist.Repository <span class="cov8" title="1">{
|
||||
return &MockWishListRepository{
|
||||
wishLists: make(map[string]*entities.WishList),
|
||||
wishListItems: make(map[string]*entities.WishListItem),
|
||||
}
|
||||
}</span>
|
||||
|
||||
// CreateWishList реализует создание списка желаний
|
||||
func (r *MockWishListRepository) CreateWishList(wishList *entities.WishList) (*entities.WishList, error) <span class="cov8" title="1">{
|
||||
if wishList.ID == "" </span><span class="cov8" title="1">{
|
||||
wishList.ID = "mock-id-" + time.Now().String()
|
||||
}</span>
|
||||
<span class="cov8" title="1">wishList.CreatedAt = time.Now()
|
||||
wishList.UpdatedAt = time.Now()
|
||||
r.wishLists[wishList.ID] = wishList
|
||||
return wishList, nil</span>
|
||||
}
|
||||
|
||||
// ReadWishList реализует чтение списка желаний по ID
|
||||
func (r *MockWishListRepository) ReadWishList(ID string) (*entities.WishList, error) <span class="cov8" title="1">{
|
||||
if wishList, ok := r.wishLists[ID]; ok </span><span class="cov8" title="1">{
|
||||
return wishList, nil
|
||||
}</span>
|
||||
<span class="cov8" title="1">return nil, errors.New("wishlist not found")</span>
|
||||
}
|
||||
|
||||
// ReadAllWishLists реализует чтение всех списков желаний пользователя
|
||||
func (r *MockWishListRepository) ReadAllWishLists(userID string) (*[]presenter.WishList, error) <span class="cov0" title="0">{
|
||||
var result []presenter.WishList
|
||||
for _, wl := range r.wishLists </span><span class="cov0" title="0">{
|
||||
if wl.UserID == userID </span><span class="cov0" title="0">{
|
||||
objID, _ := primitive.ObjectIDFromHex(wl.ID)
|
||||
result = append(result, presenter.WishList{
|
||||
ID: objID,
|
||||
Title: wl.Title,
|
||||
UserID: wl.UserID,
|
||||
Description: wl.Description,
|
||||
IsPublic: wl.IsPublic,
|
||||
PhotoURL: wl.PhotoURL,
|
||||
CreatedAt: wl.CreatedAt,
|
||||
UpdatedAt: wl.UpdatedAt,
|
||||
})
|
||||
}</span>
|
||||
}
|
||||
<span class="cov0" title="0">return &result, nil</span>
|
||||
}
|
||||
|
||||
// ReadPublicWishLists реализует чтение всех публичных списков желаний
|
||||
func (r *MockWishListRepository) ReadPublicWishLists() (*[]presenter.WishList, error) <span class="cov0" title="0">{
|
||||
var result []presenter.WishList
|
||||
for _, wl := range r.wishLists </span><span class="cov0" title="0">{
|
||||
if wl.IsPublic </span><span class="cov0" title="0">{
|
||||
objID, _ := primitive.ObjectIDFromHex(wl.ID)
|
||||
result = append(result, presenter.WishList{
|
||||
ID: objID,
|
||||
Title: wl.Title,
|
||||
UserID: wl.UserID,
|
||||
Description: wl.Description,
|
||||
IsPublic: wl.IsPublic,
|
||||
PhotoURL: wl.PhotoURL,
|
||||
CreatedAt: wl.CreatedAt,
|
||||
UpdatedAt: wl.UpdatedAt,
|
||||
})
|
||||
}</span>
|
||||
}
|
||||
<span class="cov0" title="0">return &result, nil</span>
|
||||
}
|
||||
|
||||
// UpdateWishList реализует обновление списка желаний
|
||||
func (r *MockWishListRepository) UpdateWishList(wishList *entities.WishList) (*entities.WishList, error) <span class="cov8" title="1">{
|
||||
if _, ok := r.wishLists[wishList.ID]; !ok </span><span class="cov8" title="1">{
|
||||
return nil, errors.New("wishlist not found")
|
||||
}</span>
|
||||
<span class="cov8" title="1">wishList.UpdatedAt = time.Now()
|
||||
r.wishLists[wishList.ID] = wishList
|
||||
return wishList, nil</span>
|
||||
}
|
||||
|
||||
// DeleteWishList реализует удаление списка желаний
|
||||
func (r *MockWishListRepository) DeleteWishList(ID string) error <span class="cov8" title="1">{
|
||||
if _, ok := r.wishLists[ID]; !ok </span><span class="cov8" title="1">{
|
||||
return errors.New("wishlist not found")
|
||||
}</span>
|
||||
<span class="cov8" title="1">delete(r.wishLists, ID)
|
||||
|
||||
// Удаляем все элементы, связанные с этим списком
|
||||
for id, item := range r.wishListItems </span><span class="cov0" title="0">{
|
||||
if item.WishListID == ID </span><span class="cov0" title="0">{
|
||||
delete(r.wishListItems, id)
|
||||
}</span>
|
||||
}
|
||||
<span class="cov8" title="1">return nil</span>
|
||||
}
|
||||
|
||||
// CreateWishListItem реализует создание элемента списка желаний
|
||||
func (r *MockWishListRepository) CreateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error) <span class="cov8" title="1">{
|
||||
if _, ok := r.wishLists[item.WishListID]; !ok </span><span class="cov8" title="1">{
|
||||
return nil, errors.New("wishlist not found")
|
||||
}</span>
|
||||
|
||||
<span class="cov8" title="1">if item.ID == "" </span><span class="cov8" title="1">{
|
||||
item.ID = "mock-item-id-" + time.Now().String()
|
||||
}</span>
|
||||
<span class="cov8" title="1">item.CreatedAt = time.Now()
|
||||
item.UpdatedAt = time.Now()
|
||||
r.wishListItems[item.ID] = item
|
||||
return item, nil</span>
|
||||
}
|
||||
|
||||
// ReadWishListItem реализует чтение элемента списка желаний по ID
|
||||
func (r *MockWishListRepository) ReadWishListItem(ID string) (*entities.WishListItem, error) <span class="cov0" title="0">{
|
||||
if item, ok := r.wishListItems[ID]; ok </span><span class="cov0" title="0">{
|
||||
return item, nil
|
||||
}</span>
|
||||
<span class="cov0" title="0">return nil, errors.New("wishlist item not found")</span>
|
||||
}
|
||||
|
||||
// ReadAllWishListItems реализует чтение всех элементов списка желаний
|
||||
func (r *MockWishListRepository) ReadAllWishListItems(wishListID string) (*[]presenter.WishListItem, error) <span class="cov8" title="1">{
|
||||
if _, ok := r.wishLists[wishListID]; !ok </span><span class="cov0" title="0">{
|
||||
return nil, errors.New("wishlist not found")
|
||||
}</span>
|
||||
|
||||
<span class="cov8" title="1">var result []presenter.WishListItem
|
||||
for _, item := range r.wishListItems </span><span class="cov8" title="1">{
|
||||
if item.WishListID == wishListID </span><span class="cov8" title="1">{
|
||||
objID, _ := primitive.ObjectIDFromHex(item.ID)
|
||||
result = append(result, presenter.WishListItem{
|
||||
ID: objID,
|
||||
Title: item.Title,
|
||||
URL: item.URL,
|
||||
Cost: item.Cost,
|
||||
WishListID: item.WishListID,
|
||||
Description: item.Description,
|
||||
PhotoURL: item.PhotoURL,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
}</span>
|
||||
}
|
||||
<span class="cov8" title="1">return &result, nil</span>
|
||||
}
|
||||
|
||||
// UpdateWishListItem реализует обновление элемента списка желаний
|
||||
func (r *MockWishListRepository) UpdateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error) <span class="cov0" title="0">{
|
||||
if _, ok := r.wishListItems[item.ID]; !ok </span><span class="cov0" title="0">{
|
||||
return nil, errors.New("wishlist item not found")
|
||||
}</span>
|
||||
<span class="cov0" title="0">item.UpdatedAt = time.Now()
|
||||
r.wishListItems[item.ID] = item
|
||||
return item, nil</span>
|
||||
}
|
||||
|
||||
// DeleteWishListItem реализует удаление элемента списка желаний
|
||||
func (r *MockWishListRepository) DeleteWishListItem(ID string) error <span class="cov0" title="0">{
|
||||
if _, ok := r.wishListItems[ID]; !ok </span><span class="cov0" title="0">{
|
||||
return errors.New("wishlist item not found")
|
||||
}</span>
|
||||
<span class="cov0" title="0">delete(r.wishListItems, ID)
|
||||
return nil</span>
|
||||
}
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
(function() {
|
||||
var files = document.getElementById('files');
|
||||
var visible;
|
||||
files.addEventListener('change', onChange, false);
|
||||
function select(part) {
|
||||
if (visible)
|
||||
visible.style.display = 'none';
|
||||
visible = document.getElementById(part);
|
||||
if (!visible)
|
||||
return;
|
||||
files.value = part;
|
||||
visible.style.display = 'block';
|
||||
location.hash = part;
|
||||
}
|
||||
function onChange() {
|
||||
select(files.value);
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
if (location.hash != "") {
|
||||
select(location.hash.substr(1));
|
||||
}
|
||||
if (!visible) {
|
||||
select("file0");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</html>
|
||||
62
docker-compose.yaml
Normal file
62
docker-compose.yaml
Normal file
@ -0,0 +1,62 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: wish-list-api
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- MONGODB_URI=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo:27017/${MONGO_DATABASE}
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
- mongo
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
tests:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: wish-list-api-tests
|
||||
environment:
|
||||
- MONGODB_URI=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongo:27017/${MONGO_DATABASE}
|
||||
- RUN_INTEGRATION_TESTS=true
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
- mongo
|
||||
volumes:
|
||||
- ./coverage:/app/coverage
|
||||
command: sh -c "chmod +x /app/scripts/run_tests.sh && /app/scripts/run_tests.sh"
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
mongo:
|
||||
image: mongo:latest
|
||||
container_name: mongodb
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
|
||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
|
||||
- MONGO_INITDB_DATABASE=${MONGO_DATABASE}
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- mongo_data:/data/db
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mongo_data:
|
||||
driver: local
|
||||
1478
docs/docs.go
Normal file
1478
docs/docs.go
Normal file
File diff suppressed because it is too large
Load Diff
1410
docs/swagger.json
Normal file
1410
docs/swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
909
docs/swagger.yaml
Normal file
909
docs/swagger.yaml
Normal file
@ -0,0 +1,909 @@
|
||||
basePath: /api
|
||||
definitions:
|
||||
entities.DeleteUserRequest:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
type: object
|
||||
entities.LoginRequest:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
type: object
|
||||
entities.RegisterRequest:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
type: object
|
||||
entities.TelegramAuthRequest:
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
auth_date:
|
||||
type: integer
|
||||
first_name:
|
||||
type: string
|
||||
hash:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
photo_url:
|
||||
type: string
|
||||
refresh_token:
|
||||
type: string
|
||||
telegram_id:
|
||||
type: integer
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
entities.TokenPair:
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
refresh_token:
|
||||
type: string
|
||||
type: object
|
||||
entities.TokenRequest:
|
||||
properties:
|
||||
refresh_token:
|
||||
type: string
|
||||
type: object
|
||||
entities.User:
|
||||
properties:
|
||||
created_at:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
first_name:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
last_login_date:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
photo_url:
|
||||
type: string
|
||||
telegram_id:
|
||||
type: integer
|
||||
telegram_username:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
entities.WishList:
|
||||
properties:
|
||||
created_at:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
is_public:
|
||||
type: boolean
|
||||
photo_url:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
entities.WishListItem:
|
||||
properties:
|
||||
cost:
|
||||
type: number
|
||||
created_at:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
photo_url:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
wish_list_id:
|
||||
type: string
|
||||
type: object
|
||||
presenter.AuthResponse:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/entities.TokenPair'
|
||||
error:
|
||||
type: string
|
||||
status:
|
||||
type: boolean
|
||||
user:
|
||||
$ref: '#/definitions/presenter.User'
|
||||
type: object
|
||||
presenter.User:
|
||||
properties:
|
||||
created_at:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
presenter.UserResponse:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/presenter.User'
|
||||
error:
|
||||
type: string
|
||||
status:
|
||||
type: boolean
|
||||
type: object
|
||||
presenter.UsersResponse:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/presenter.User'
|
||||
type: array
|
||||
error:
|
||||
type: string
|
||||
status:
|
||||
type: boolean
|
||||
type: object
|
||||
presenter.WishList:
|
||||
properties:
|
||||
created_at:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
is_public:
|
||||
type: boolean
|
||||
photo_url:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
presenter.WishListItem:
|
||||
properties:
|
||||
cost:
|
||||
type: number
|
||||
created_at:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
photo_url:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
wish_list_id:
|
||||
type: string
|
||||
type: object
|
||||
presenter.WishListItemResponse:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/presenter.WishListItem'
|
||||
error:
|
||||
type: string
|
||||
status:
|
||||
type: boolean
|
||||
type: object
|
||||
presenter.WishListItemsResponse:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/presenter.WishListItem'
|
||||
type: array
|
||||
error:
|
||||
type: string
|
||||
status:
|
||||
type: boolean
|
||||
type: object
|
||||
presenter.WishListResponse:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/presenter.WishList'
|
||||
error:
|
||||
type: string
|
||||
status:
|
||||
type: boolean
|
||||
type: object
|
||||
presenter.WishListsResponse:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/presenter.WishList'
|
||||
type: array
|
||||
error:
|
||||
type: string
|
||||
status:
|
||||
type: boolean
|
||||
type: object
|
||||
host: localhost:8080
|
||||
info:
|
||||
contact: {}
|
||||
description: API-сервер для приложения списка желаний
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
title: Wish List API
|
||||
version: "1.0"
|
||||
paths:
|
||||
/auth/login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Аутентифицирует пользователя и выдает JWT токены
|
||||
parameters:
|
||||
- description: Учетные данные пользователя
|
||||
in: body
|
||||
name: credentials
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.LoginRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
summary: Вход пользователя
|
||||
tags:
|
||||
- auth
|
||||
/auth/refresh:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Обновляет JWT токены с помощью refresh токена
|
||||
parameters:
|
||||
- description: Refresh токен
|
||||
in: body
|
||||
name: refreshToken
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.TokenRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
summary: Обновление токенов
|
||||
tags:
|
||||
- auth
|
||||
/auth/register:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Регистрирует нового пользователя и выдает JWT токены
|
||||
parameters:
|
||||
- description: Данные нового пользователя
|
||||
in: body
|
||||
name: user
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.RegisterRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"409":
|
||||
description: Conflict
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
summary: Регистрация пользователя
|
||||
tags:
|
||||
- auth
|
||||
/auth/telegram:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Аутентифицирует пользователя через Telegram и выдает JWT токены
|
||||
parameters:
|
||||
- description: Данные аутентификации Telegram
|
||||
in: body
|
||||
name: credentials
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.TelegramAuthRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.AuthResponse'
|
||||
summary: Вход пользователя через Telegram
|
||||
tags:
|
||||
- auth
|
||||
/users:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Удаляет пользователя из системы по ID
|
||||
parameters:
|
||||
- description: ID пользователя для удаления
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.DeleteUserRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Удалить пользователя
|
||||
tags:
|
||||
- users
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает список всех пользователей в системе
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UsersResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Получить всех пользователей
|
||||
tags:
|
||||
- users
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Создает нового пользователя в системе
|
||||
parameters:
|
||||
- description: Информация о пользователе
|
||||
in: body
|
||||
name: user
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.User'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
summary: Добавить нового пользователя
|
||||
tags:
|
||||
- users
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Обновляет информацию о существующем пользователе
|
||||
parameters:
|
||||
- description: Информация о пользователе для обновления
|
||||
in: body
|
||||
name: user
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.User'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Обновить пользователя
|
||||
tags:
|
||||
- users
|
||||
/users/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает информацию о пользователе по его ID
|
||||
parameters:
|
||||
- description: ID пользователя
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Получить пользователя по ID
|
||||
tags:
|
||||
- users
|
||||
/users/email/{email}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Возвращает информацию о пользователе по его Email
|
||||
parameters:
|
||||
- description: Email пользователя
|
||||
in: path
|
||||
name: email
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.UserResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Получить пользователя по Email
|
||||
tags:
|
||||
- users
|
||||
/wishlist:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create a new wishlist for the authenticated user
|
||||
parameters:
|
||||
- description: Bearer token
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
- description: Wishlist data
|
||||
in: body
|
||||
name: wishlist
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.WishList'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Create a new wishlist
|
||||
tags:
|
||||
- wishlist
|
||||
/wishlist/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Delete a wishlist and all its items
|
||||
parameters:
|
||||
- description: Bearer token
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
- description: Wishlist ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Delete a wishlist
|
||||
tags:
|
||||
- wishlist
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a wishlist by its ID
|
||||
parameters:
|
||||
- description: Wishlist ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get a wishlist
|
||||
tags:
|
||||
- wishlist
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update an existing wishlist
|
||||
parameters:
|
||||
- description: Bearer token
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
- description: Wishlist ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Updated wishlist data
|
||||
in: body
|
||||
name: wishlist
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.WishList'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Update a wishlist
|
||||
tags:
|
||||
- wishlist
|
||||
/wishlist/{wishlistId}/items:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get all items in a wishlist
|
||||
parameters:
|
||||
- description: Wishlist ID
|
||||
in: path
|
||||
name: wishlistId
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemsResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemsResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get wishlist items
|
||||
tags:
|
||||
- wishlist-items
|
||||
/wishlist/item:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Create a new item for a wishlist
|
||||
parameters:
|
||||
- description: Bearer token
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
- description: Wishlist item data
|
||||
in: body
|
||||
name: item
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.WishListItem'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Create a wishlist item
|
||||
tags:
|
||||
- wishlist-items
|
||||
/wishlist/item/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Delete a wishlist item
|
||||
parameters:
|
||||
- description: Bearer token
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
- description: Item ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Delete a wishlist item
|
||||
tags:
|
||||
- wishlist-items
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a wishlist item by its ID
|
||||
parameters:
|
||||
- description: Item ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get a wishlist item
|
||||
tags:
|
||||
- wishlist-items
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update an existing wishlist item
|
||||
parameters:
|
||||
- description: Bearer token
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
- description: Item ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Updated item data
|
||||
in: body
|
||||
name: item
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/entities.WishListItem'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListItemResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Update a wishlist item
|
||||
tags:
|
||||
- wishlist-items
|
||||
/wishlist/user/{userId}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get all wishlists for a specific user
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: userId
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListsResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/presenter.WishListResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get user wishlists
|
||||
tags:
|
||||
- wishlist
|
||||
securityDefinitions:
|
||||
BearerAuth:
|
||||
in: header
|
||||
name: Authorization
|
||||
type: apiKey
|
||||
swagger: "2.0"
|
||||
54
go.mod
Normal file
54
go.mod
Normal file
@ -0,0 +1,54 @@
|
||||
module wish-list-api
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.7
|
||||
|
||||
require (
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/swaggo/fiber-swagger v1.1.0
|
||||
github.com/swaggo/swag v1.7.8
|
||||
go.mongodb.org/mongo-driver v1.16.1
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
||||
)
|
||||
185
go.sum
Normal file
185
go.sum
Normal file
@ -0,0 +1,185 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/gofiber/fiber/v2 v2.24.0/go.mod h1:MR1usVH3JHYRyQwMe2eZXRSZHRX38fkV+A7CPB+DlDQ=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
|
||||
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/swaggo/fiber-swagger v1.1.0 h1:3yeJeStLYtUvj9zZg0/COwEESIxPBKM+OohOxB0tJ68=
|
||||
github.com/swaggo/fiber-swagger v1.1.0/go.mod h1:DaSh3M3So+l/lrdpnAHllOOmTsKZT4flCYjz+jbUIfo=
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/swag v1.7.8 h1:w249t0l/kc/DKMGlS0fppNJQxKyJ8heNaUWB6nsH3zc=
|
||||
github.com/swaggo/swag v1.7.8/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8=
|
||||
go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
237
pkg/auth/service.go
Normal file
237
pkg/auth/service.go
Normal file
@ -0,0 +1,237 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"wish-list-api/pkg/entities"
|
||||
"wish-list-api/pkg/user"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Login(credentials *entities.LoginRequest) (*entities.TokenPair, error)
|
||||
Register(userData *entities.RegisterRequest) (*entities.User, error)
|
||||
RefreshToken(refreshToken string) (*entities.TokenPair, error)
|
||||
ValidateToken(tokenString string) (*jwt.Token, error)
|
||||
GetUserIDFromToken(tokenString string) (string, error)
|
||||
HashPassword(password string) (string, error)
|
||||
ComparePasswords(hashedPassword string, plainPassword string) bool
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultAccessTokenExpiry = 30 * 60
|
||||
DefaultRefreshTokenExpiry = 7 * 24 * 3600
|
||||
DefaultSecretKey = "94cb4ff3-2396-4903-9266-9cdd9b885767"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
userService user.Service
|
||||
accessTokenExp int64
|
||||
refreshTokenExp int64
|
||||
accessTokenSecret string
|
||||
refreshTokenSecret string
|
||||
}
|
||||
|
||||
type ServiceConfig struct {
|
||||
UserService user.Service
|
||||
AccessTokenExp int64
|
||||
RefreshTokenExp int64
|
||||
AccessTokenSecret string
|
||||
RefreshTokenSecret string
|
||||
}
|
||||
|
||||
func NewService(config ServiceConfig) Service {
|
||||
if config.AccessTokenExp == 0 {
|
||||
if envExp := os.Getenv("ACCESS_TOKEN_EXPIRY"); envExp != "" {
|
||||
if exp, err := strconv.ParseInt(envExp, 10, 64); err == nil {
|
||||
config.AccessTokenExp = exp
|
||||
} else {
|
||||
config.AccessTokenExp = DefaultAccessTokenExpiry
|
||||
}
|
||||
} else {
|
||||
config.AccessTokenExp = DefaultAccessTokenExpiry
|
||||
}
|
||||
}
|
||||
|
||||
if config.RefreshTokenExp == 0 {
|
||||
if envExp := os.Getenv("REFRESH_TOKEN_EXPIRY"); envExp != "" {
|
||||
if exp, err := strconv.ParseInt(envExp, 10, 64); err == nil {
|
||||
config.RefreshTokenExp = exp
|
||||
} else {
|
||||
config.RefreshTokenExp = DefaultRefreshTokenExpiry
|
||||
}
|
||||
} else {
|
||||
config.RefreshTokenExp = DefaultRefreshTokenExpiry
|
||||
}
|
||||
}
|
||||
|
||||
if config.AccessTokenSecret == "" {
|
||||
if secret := os.Getenv("ACCESS_TOKEN_SECRET"); secret != "" {
|
||||
config.AccessTokenSecret = secret
|
||||
} else {
|
||||
config.AccessTokenSecret = DefaultSecretKey
|
||||
}
|
||||
}
|
||||
|
||||
if config.RefreshTokenSecret == "" {
|
||||
if secret := os.Getenv("REFRESH_TOKEN_SECRET"); secret != "" {
|
||||
config.RefreshTokenSecret = secret
|
||||
} else {
|
||||
config.RefreshTokenSecret = DefaultSecretKey + "_refresh"
|
||||
}
|
||||
}
|
||||
|
||||
return &service{
|
||||
userService: config.UserService,
|
||||
accessTokenExp: config.AccessTokenExp,
|
||||
refreshTokenExp: config.RefreshTokenExp,
|
||||
accessTokenSecret: config.AccessTokenSecret,
|
||||
refreshTokenSecret: config.RefreshTokenSecret,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Login(credentials *entities.LoginRequest) (*entities.TokenPair, error) {
|
||||
user, err := s.userService.GetUserByEmail(credentials.Email)
|
||||
if err != nil {
|
||||
return nil, errors.New("неверный email или пароль")
|
||||
}
|
||||
|
||||
if !s.ComparePasswords(user.Password, credentials.Password) {
|
||||
return nil, errors.New("неверный email или пароль")
|
||||
}
|
||||
|
||||
return s.generateTokenPair(user.ID.Hex(), user.Email)
|
||||
}
|
||||
|
||||
func (s *service) Register(userData *entities.RegisterRequest) (*entities.User, error) {
|
||||
existingUser, _ := s.userService.GetUserByEmail(userData.Email)
|
||||
if existingUser != nil {
|
||||
return nil, errors.New("пользователь с таким email уже существует")
|
||||
}
|
||||
|
||||
hashedPassword, err := s.HashPassword(userData.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newUser := &entities.User{
|
||||
Email: userData.Email,
|
||||
Password: hashedPassword,
|
||||
}
|
||||
|
||||
return s.userService.CreateUser(newUser)
|
||||
}
|
||||
|
||||
func (s *service) RefreshToken(refreshToken string) (*entities.TokenPair, error) {
|
||||
token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("неожиданный метод подписи")
|
||||
}
|
||||
return []byte(s.refreshTokenSecret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.New("недействительный refresh token")
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
if exp, ok := claims["exp"].(float64); ok {
|
||||
if time.Now().Unix() > int64(exp) {
|
||||
return nil, errors.New("refresh token истек")
|
||||
}
|
||||
}
|
||||
|
||||
userID, ok := claims["user_id"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("недействительный refresh token")
|
||||
}
|
||||
|
||||
user, err := s.userService.GetUser(userID)
|
||||
if err != nil {
|
||||
return nil, errors.New("пользователь не найден")
|
||||
}
|
||||
|
||||
return s.generateTokenPair(user.ID.Hex(), user.Email)
|
||||
}
|
||||
|
||||
return nil, errors.New("недействительный refresh token")
|
||||
}
|
||||
|
||||
func (s *service) ValidateToken(tokenString string) (*jwt.Token, error) {
|
||||
return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("неожиданный метод подписи")
|
||||
}
|
||||
return []byte(s.accessTokenSecret), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) GetUserIDFromToken(tokenString string) (string, error) {
|
||||
if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
|
||||
tokenString = tokenString[7:]
|
||||
}
|
||||
|
||||
token, err := s.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return "", errors.New("invalid token claims")
|
||||
}
|
||||
|
||||
userID, ok := claims["user_id"].(string)
|
||||
if !ok {
|
||||
return "", errors.New("user_id not found in token")
|
||||
}
|
||||
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func (s *service) generateTokenPair(userID, email string) (*entities.TokenPair, error) {
|
||||
now := time.Now().Unix()
|
||||
|
||||
accessClaims := jwt.MapClaims{
|
||||
"user_id": userID,
|
||||
"email": email,
|
||||
"exp": now + s.accessTokenExp,
|
||||
}
|
||||
|
||||
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
|
||||
accessTokenString, err := accessToken.SignedString([]byte(s.accessTokenSecret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refreshClaims := jwt.MapClaims{
|
||||
"user_id": userID,
|
||||
"exp": now + s.refreshTokenExp,
|
||||
}
|
||||
|
||||
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
|
||||
refreshTokenString, err := refreshToken.SignedString([]byte(s.refreshTokenSecret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entities.TokenPair{
|
||||
AccessToken: accessTokenString,
|
||||
RefreshToken: refreshTokenString,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func (s *service) ComparePasswords(hashedPassword string, plainPassword string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainPassword))
|
||||
return err == nil
|
||||
}
|
||||
211
pkg/auth/telegram.go
Normal file
211
pkg/auth/telegram.go
Normal file
@ -0,0 +1,211 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"wish-list-api/pkg/entities"
|
||||
"wish-list-api/pkg/user"
|
||||
)
|
||||
|
||||
const MaxAuthAge = 86400
|
||||
|
||||
type TelegramAuthService interface {
|
||||
AuthenticateWithTelegram(data *entities.TelegramAuthRequest) (*entities.TokenPair, error)
|
||||
ValidateTelegramAuthData(data *entities.TelegramAuthRequest) error
|
||||
}
|
||||
|
||||
type telegramAuthService struct {
|
||||
userService user.Service
|
||||
authService Service
|
||||
botToken string
|
||||
telegramAppUrl string
|
||||
}
|
||||
|
||||
type TelegramAuthConfig struct {
|
||||
UserService user.Service
|
||||
AuthService Service
|
||||
BotToken string
|
||||
TelegramAppUrl string
|
||||
}
|
||||
|
||||
func NewTelegramAuthService(config TelegramAuthConfig) TelegramAuthService {
|
||||
if config.BotToken == "" {
|
||||
config.BotToken = os.Getenv("TELEGRAM_BOT_TOKEN")
|
||||
}
|
||||
|
||||
if config.TelegramAppUrl == "" {
|
||||
config.TelegramAppUrl = os.Getenv("TELEGRAM_APP_URL")
|
||||
}
|
||||
|
||||
return &telegramAuthService{
|
||||
userService: config.UserService,
|
||||
authService: config.AuthService,
|
||||
botToken: config.BotToken,
|
||||
telegramAppUrl: config.TelegramAppUrl,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *telegramAuthService) AuthenticateWithTelegram(data *entities.TelegramAuthRequest) (*entities.TokenPair, error) {
|
||||
err := s.ValidateTelegramAuthData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := s.userService.GetUserByTelegramID(data.TelegramID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
randomPassword, err := generateRandomPassword(16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
email := fmt.Sprintf("telegram_%d@%s", data.TelegramID, s.telegramAppUrl)
|
||||
|
||||
user = &entities.User{
|
||||
Email: email,
|
||||
Password: randomPassword,
|
||||
TelegramID: data.TelegramID,
|
||||
TelegramUsername: data.Username,
|
||||
FirstName: data.FirstName,
|
||||
LastName: data.LastName,
|
||||
PhotoURL: data.PhotoURL,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
LastLoginDate: time.Now(),
|
||||
}
|
||||
|
||||
hashedPassword, err := s.authService.HashPassword(randomPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Password = hashedPassword
|
||||
|
||||
user, err = s.userService.CreateUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
updated := false
|
||||
|
||||
if user.TelegramUsername != data.Username {
|
||||
user.TelegramUsername = data.Username
|
||||
updated = true
|
||||
}
|
||||
|
||||
if user.FirstName != data.FirstName {
|
||||
user.FirstName = data.FirstName
|
||||
updated = true
|
||||
}
|
||||
|
||||
if user.LastName != data.LastName {
|
||||
user.LastName = data.LastName
|
||||
updated = true
|
||||
}
|
||||
|
||||
if user.PhotoURL != data.PhotoURL && data.PhotoURL != "" {
|
||||
user.PhotoURL = data.PhotoURL
|
||||
updated = true
|
||||
}
|
||||
|
||||
user.LastLoginDate = time.Now()
|
||||
updated = true
|
||||
|
||||
if updated {
|
||||
_, err = s.userService.UpdateUserTelegramData(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loginRequest := &entities.LoginRequest{
|
||||
Email: user.Email,
|
||||
}
|
||||
|
||||
return s.authService.Login(loginRequest)
|
||||
}
|
||||
|
||||
func (s *telegramAuthService) ValidateTelegramAuthData(data *entities.TelegramAuthRequest) error {
|
||||
now := time.Now().Unix()
|
||||
if now-data.AuthDate > MaxAuthAge {
|
||||
return errors.New("данные аутентификации Telegram устарели")
|
||||
}
|
||||
|
||||
secretKey := generateSecretKey(s.botToken)
|
||||
|
||||
dataCheckString := buildDataCheckString(data)
|
||||
hash := generateHash(dataCheckString, secretKey)
|
||||
|
||||
if hash != data.Hash {
|
||||
return errors.New("неверная подпись данных аутентификации Telegram")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateSecretKey(botToken string) []byte {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(botToken))
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func buildDataCheckString(data *entities.TelegramAuthRequest) string {
|
||||
params := map[string]string{
|
||||
"auth_date": strconv.FormatInt(data.AuthDate, 10),
|
||||
"first_name": data.FirstName,
|
||||
"telegram_id": strconv.FormatInt(data.TelegramID, 10),
|
||||
}
|
||||
|
||||
if data.LastName != "" {
|
||||
params["last_name"] = data.LastName
|
||||
}
|
||||
|
||||
if data.Username != "" {
|
||||
params["username"] = data.Username
|
||||
}
|
||||
|
||||
if data.PhotoURL != "" {
|
||||
params["photo_url"] = data.PhotoURL
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for k := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var pairs []string
|
||||
for _, k := range keys {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, params[k]))
|
||||
}
|
||||
|
||||
return strings.Join(pairs, "\n")
|
||||
}
|
||||
|
||||
func generateHash(data string, secretKey []byte) string {
|
||||
h := hmac.New(sha256.New, secretKey)
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func generateRandomPassword(length int) (string, error) {
|
||||
bytes := make([]byte, length)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(bytes)[:length], nil
|
||||
}
|
||||
43
pkg/entities/auth.go
Normal file
43
pkg/entities/auth.go
Normal file
@ -0,0 +1,43 @@
|
||||
package entities
|
||||
|
||||
type LoginRequest struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type TokenPair struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
type AccessTokenClaims struct {
|
||||
UserID string `json:"user_id"`
|
||||
Email string `json:"email"`
|
||||
Exp int64 `json:"exp"`
|
||||
}
|
||||
|
||||
type RefreshTokenClaims struct {
|
||||
UserID string `json:"user_id"`
|
||||
Exp int64 `json:"exp"`
|
||||
}
|
||||
|
||||
type TokenRequest struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
type TelegramAuthRequest struct {
|
||||
TelegramID int64 `json:"telegram_id"`
|
||||
Username string `json:"username"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
AuthDate int64 `json:"auth_date"`
|
||||
Hash string `json:"hash"`
|
||||
PhotoURL string `json:"photo_url,omitempty"`
|
||||
AccessToken string `json:"access_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
}
|
||||
25
pkg/entities/user.go
Normal file
25
pkg/entities/user.go
Normal file
@ -0,0 +1,25 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
|
||||
Email string `json:"email" bson:"email"`
|
||||
Password string `json:"password" bson:"password"`
|
||||
TelegramID int64 `json:"telegram_id,omitempty" bson:"telegram_id,omitempty"`
|
||||
TelegramUsername string `json:"telegram_username,omitempty" bson:"telegram_username,omitempty"`
|
||||
FirstName string `json:"first_name,omitempty" bson:"first_name,omitempty"`
|
||||
LastName string `json:"last_name,omitempty" bson:"last_name,omitempty"`
|
||||
PhotoURL string `json:"photo_url,omitempty" bson:"photo_url,omitempty"`
|
||||
LastLoginDate time.Time `json:"last_login_date,omitempty" bson:"last_login_date,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at" bson:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
|
||||
}
|
||||
|
||||
type DeleteUserRequest struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
36
pkg/entities/wish-list.go
Normal file
36
pkg/entities/wish-list.go
Normal file
@ -0,0 +1,36 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type WishList struct {
|
||||
ID string `json:"id" bson:"_id,omitempty"`
|
||||
Title string `json:"title" bson:"title"`
|
||||
UserID string `json:"user_id" bson:"user_id"`
|
||||
Description string `json:"description" bson:"description"`
|
||||
IsPublic bool `json:"is_public" bson:"is_public"`
|
||||
PhotoURL string `json:"photo_url" bson:"photo_url"`
|
||||
CreatedAt time.Time `json:"created_at" bson:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
|
||||
}
|
||||
|
||||
type WishListItem struct {
|
||||
ID string `json:"id" bson:"_id,omitempty"`
|
||||
Title string `json:"title" bson:"title"`
|
||||
URL string `json:"url" bson:"url"`
|
||||
Cost float64 `json:"cost" bson:"cost"`
|
||||
WishListID string `json:"wish_list_id" bson:"wish_list_id"`
|
||||
Description string `json:"description" bson:"description"`
|
||||
PhotoURL string `json:"photo_url" bson:"photo_url"`
|
||||
CreatedAt time.Time `json:"created_at" bson:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
|
||||
}
|
||||
|
||||
type DeleteWishListRequest struct {
|
||||
ID string `json:"id" bson:"_id"`
|
||||
}
|
||||
|
||||
type DeleteWishListItemRequest struct {
|
||||
ID string `json:"id" bson:"_id"`
|
||||
}
|
||||
168
pkg/user/repository.go
Normal file
168
pkg/user/repository.go
Normal file
@ -0,0 +1,168 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/entities"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
CreateUser(user *entities.User) (*entities.User, error)
|
||||
ReadUser(ID string) (*entities.User, error)
|
||||
ReadUserByEmail(email string) (*entities.User, error)
|
||||
ReadAllUsers() (*[]presenter.User, error)
|
||||
UpdateUser(user *entities.User) (*entities.User, error)
|
||||
DeleteUser(ID string) error
|
||||
|
||||
ReadUserByTelegramID(telegramID int64) (*entities.User, error)
|
||||
UpdateUserTelegramData(user *entities.User) (*entities.User, error)
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
Collection *mongo.Collection
|
||||
}
|
||||
|
||||
func NewRepo(collection *mongo.Collection) Repository {
|
||||
return &repository{
|
||||
Collection: collection,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMongoRepository(collection *mongo.Collection) Repository {
|
||||
return NewRepo(collection)
|
||||
}
|
||||
|
||||
func (r *repository) CreateUser(user *entities.User) (*entities.User, error) {
|
||||
user.ID = primitive.NewObjectID()
|
||||
user.CreatedAt = time.Now()
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
_, err := r.Collection.InsertOne(context.Background(), user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadUser(ID string) (*entities.User, error) {
|
||||
var user entities.User
|
||||
|
||||
objectID, err := primitive.ObjectIDFromHex(ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r.Collection.FindOne(context.Background(), bson.M{"_id": objectID}).Decode(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadUserByEmail(email string) (*entities.User, error) {
|
||||
var user entities.User
|
||||
|
||||
err := r.Collection.FindOne(context.Background(), bson.M{"email": email}).Decode(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadAllUsers() (*[]presenter.User, error) {
|
||||
var users []presenter.User
|
||||
|
||||
cursor, err := r.Collection.Find(context.Background(), bson.M{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for cursor.Next(context.TODO()) {
|
||||
var user presenter.User
|
||||
_ = cursor.Decode(&user)
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
return &users, nil
|
||||
}
|
||||
|
||||
func (r *repository) UpdateUser(user *entities.User) (*entities.User, error) {
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
_, err := r.Collection.UpdateOne(
|
||||
context.Background(),
|
||||
bson.M{"_id": user.ID},
|
||||
bson.M{"$set": user},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *repository) DeleteUser(ID string) error {
|
||||
objectID, err := primitive.ObjectIDFromHex(ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.Collection.DeleteOne(context.Background(), bson.M{"_id": objectID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadUserByTelegramID(telegramID int64) (*entities.User, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var user entities.User
|
||||
filter := bson.M{"telegram_id": telegramID}
|
||||
|
||||
err := r.Collection.FindOne(ctx, filter).Decode(&user)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *repository) UpdateUserTelegramData(user *entities.User) (*entities.User, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
user.UpdatedAt = time.Now()
|
||||
user.LastLoginDate = time.Now()
|
||||
|
||||
filter := bson.M{"_id": user.ID}
|
||||
update := bson.M{"$set": bson.M{
|
||||
"telegram_id": user.TelegramID,
|
||||
"telegram_username": user.TelegramUsername,
|
||||
"first_name": user.FirstName,
|
||||
"last_name": user.LastName,
|
||||
"photo_url": user.PhotoURL,
|
||||
"last_login_date": user.LastLoginDate,
|
||||
"updated_at": user.UpdatedAt,
|
||||
}}
|
||||
|
||||
_, err := r.Collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
60
pkg/user/service.go
Normal file
60
pkg/user/service.go
Normal file
@ -0,0 +1,60 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/entities"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
CreateUser(user *entities.User) (*entities.User, error)
|
||||
GetUser(ID string) (*entities.User, error)
|
||||
GetUserByEmail(email string) (*entities.User, error)
|
||||
GetAllUsers() (*[]presenter.User, error)
|
||||
UpdateUser(user *entities.User) (*entities.User, error)
|
||||
DeleteUser(ID string) error
|
||||
|
||||
GetUserByTelegramID(telegramID int64) (*entities.User, error)
|
||||
UpdateUserTelegramData(user *entities.User) (*entities.User, error)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
repository Repository
|
||||
}
|
||||
|
||||
func NewService(r Repository) Service {
|
||||
return &service{
|
||||
repository: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) CreateUser(user *entities.User) (*entities.User, error) {
|
||||
return s.repository.CreateUser(user)
|
||||
}
|
||||
|
||||
func (s *service) GetUser(ID string) (*entities.User, error) {
|
||||
return s.repository.ReadUser(ID)
|
||||
}
|
||||
|
||||
func (s *service) GetUserByEmail(email string) (*entities.User, error) {
|
||||
return s.repository.ReadUserByEmail(email)
|
||||
}
|
||||
|
||||
func (s *service) GetAllUsers() (*[]presenter.User, error) {
|
||||
return s.repository.ReadAllUsers()
|
||||
}
|
||||
|
||||
func (s *service) UpdateUser(user *entities.User) (*entities.User, error) {
|
||||
return s.repository.UpdateUser(user)
|
||||
}
|
||||
|
||||
func (s *service) DeleteUser(ID string) error {
|
||||
return s.repository.DeleteUser(ID)
|
||||
}
|
||||
|
||||
func (s *service) GetUserByTelegramID(telegramID int64) (*entities.User, error) {
|
||||
return s.repository.ReadUserByTelegramID(telegramID)
|
||||
}
|
||||
|
||||
func (s *service) UpdateUserTelegramData(user *entities.User) (*entities.User, error) {
|
||||
return s.repository.UpdateUserTelegramData(user)
|
||||
}
|
||||
194
pkg/wish-list/repository.go
Normal file
194
pkg/wish-list/repository.go
Normal file
@ -0,0 +1,194 @@
|
||||
package wishlist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/entities"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
CreateWishList(wishList *entities.WishList) (*entities.WishList, error)
|
||||
ReadWishList(ID string) (*entities.WishList, error)
|
||||
ReadAllWishLists(userID string) (*[]presenter.WishList, error)
|
||||
ReadPublicWishLists() (*[]presenter.WishList, error)
|
||||
UpdateWishList(wishList *entities.WishList) (*entities.WishList, error)
|
||||
DeleteWishList(ID string) error
|
||||
|
||||
CreateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error)
|
||||
ReadWishListItem(ID string) (*entities.WishListItem, error)
|
||||
ReadAllWishListItems(wishListID string) (*[]presenter.WishListItem, error)
|
||||
UpdateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error)
|
||||
DeleteWishListItem(ID string) error
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
WishListCollection *mongo.Collection
|
||||
WishListItemCollection *mongo.Collection
|
||||
}
|
||||
|
||||
func NewRepo(wishListCollection, wishListItemCollection *mongo.Collection) Repository {
|
||||
return &repository{
|
||||
WishListCollection: wishListCollection,
|
||||
WishListItemCollection: wishListItemCollection,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMongoRepository(wishListCollection, wishListItemCollection *mongo.Collection) Repository {
|
||||
return NewRepo(wishListCollection, wishListItemCollection)
|
||||
}
|
||||
|
||||
func (r *repository) CreateWishList(wishList *entities.WishList) (*entities.WishList, error) {
|
||||
wishList.ID = primitive.NewObjectID().Hex()
|
||||
wishList.CreatedAt = time.Now()
|
||||
wishList.UpdatedAt = time.Now()
|
||||
|
||||
_, err := r.WishListCollection.InsertOne(context.Background(), wishList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wishList, nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadWishList(ID string) (*entities.WishList, error) {
|
||||
var wishList entities.WishList
|
||||
|
||||
err := r.WishListCollection.FindOne(context.Background(), bson.M{"_id": ID}).Decode(&wishList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wishList, nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadAllWishLists(userID string) (*[]presenter.WishList, error) {
|
||||
var wishLists []presenter.WishList
|
||||
|
||||
cursor, err := r.WishListCollection.Find(context.Background(), bson.M{"user_id": userID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for cursor.Next(context.TODO()) {
|
||||
var wishList presenter.WishList
|
||||
_ = cursor.Decode(&wishList)
|
||||
wishLists = append(wishLists, wishList)
|
||||
}
|
||||
|
||||
return &wishLists, nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadPublicWishLists() (*[]presenter.WishList, error) {
|
||||
var wishLists []presenter.WishList
|
||||
|
||||
cursor, err := r.WishListCollection.Find(context.Background(), bson.M{"is_public": true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for cursor.Next(context.TODO()) {
|
||||
var wishList presenter.WishList
|
||||
_ = cursor.Decode(&wishList)
|
||||
wishLists = append(wishLists, wishList)
|
||||
}
|
||||
|
||||
return &wishLists, nil
|
||||
}
|
||||
|
||||
func (r *repository) UpdateWishList(wishList *entities.WishList) (*entities.WishList, error) {
|
||||
wishList.UpdatedAt = time.Now()
|
||||
|
||||
_, err := r.WishListCollection.UpdateOne(
|
||||
context.Background(),
|
||||
bson.M{"_id": wishList.ID},
|
||||
bson.M{"$set": wishList},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wishList, nil
|
||||
}
|
||||
|
||||
func (r *repository) DeleteWishList(ID string) error {
|
||||
_, err := r.WishListCollection.DeleteOne(context.Background(), bson.M{"_id": ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.WishListItemCollection.DeleteMany(context.Background(), bson.M{"wish_list_id": ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *repository) CreateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error) {
|
||||
item.ID = primitive.NewObjectID().Hex()
|
||||
item.CreatedAt = time.Now()
|
||||
item.UpdatedAt = time.Now()
|
||||
|
||||
_, err := r.WishListItemCollection.InsertOne(context.Background(), item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadWishListItem(ID string) (*entities.WishListItem, error) {
|
||||
var item entities.WishListItem
|
||||
|
||||
err := r.WishListItemCollection.FindOne(context.Background(), bson.M{"_id": ID}).Decode(&item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *repository) ReadAllWishListItems(wishListID string) (*[]presenter.WishListItem, error) {
|
||||
var items []presenter.WishListItem
|
||||
|
||||
cursor, err := r.WishListItemCollection.Find(context.Background(), bson.M{"wish_list_id": wishListID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for cursor.Next(context.TODO()) {
|
||||
var item presenter.WishListItem
|
||||
_ = cursor.Decode(&item)
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &items, nil
|
||||
}
|
||||
|
||||
func (r *repository) UpdateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error) {
|
||||
item.UpdatedAt = time.Now()
|
||||
|
||||
_, err := r.WishListItemCollection.UpdateOne(
|
||||
context.Background(),
|
||||
bson.M{"_id": item.ID},
|
||||
bson.M{"$set": item},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (r *repository) DeleteWishListItem(ID string) error {
|
||||
_, err := r.WishListItemCollection.DeleteOne(context.Background(), bson.M{"_id": ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
75
pkg/wish-list/service.go
Normal file
75
pkg/wish-list/service.go
Normal file
@ -0,0 +1,75 @@
|
||||
package wishlist
|
||||
|
||||
import (
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/entities"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
CreateWishList(wishList *entities.WishList) (*entities.WishList, error)
|
||||
GetWishList(ID string) (*entities.WishList, error)
|
||||
GetAllWishLists(userID string) (*[]presenter.WishList, error)
|
||||
GetPublicWishLists() (*[]presenter.WishList, error)
|
||||
UpdateWishList(wishList *entities.WishList) (*entities.WishList, error)
|
||||
DeleteWishList(ID string) error
|
||||
|
||||
CreateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error)
|
||||
GetWishListItem(ID string) (*entities.WishListItem, error)
|
||||
GetAllWishListItems(wishListID string) (*[]presenter.WishListItem, error)
|
||||
UpdateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error)
|
||||
DeleteWishListItem(ID string) error
|
||||
}
|
||||
|
||||
type service struct {
|
||||
repository Repository
|
||||
}
|
||||
|
||||
func NewService(r Repository) Service {
|
||||
return &service{
|
||||
repository: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) CreateWishList(wishList *entities.WishList) (*entities.WishList, error) {
|
||||
return s.repository.CreateWishList(wishList)
|
||||
}
|
||||
|
||||
func (s *service) GetWishList(ID string) (*entities.WishList, error) {
|
||||
return s.repository.ReadWishList(ID)
|
||||
}
|
||||
|
||||
func (s *service) GetAllWishLists(userID string) (*[]presenter.WishList, error) {
|
||||
return s.repository.ReadAllWishLists(userID)
|
||||
}
|
||||
|
||||
func (s *service) GetPublicWishLists() (*[]presenter.WishList, error) {
|
||||
return s.repository.ReadPublicWishLists()
|
||||
}
|
||||
|
||||
func (s *service) UpdateWishList(wishList *entities.WishList) (*entities.WishList, error) {
|
||||
return s.repository.UpdateWishList(wishList)
|
||||
}
|
||||
|
||||
func (s *service) DeleteWishList(ID string) error {
|
||||
return s.repository.DeleteWishList(ID)
|
||||
}
|
||||
|
||||
func (s *service) CreateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error) {
|
||||
return s.repository.CreateWishListItem(item)
|
||||
}
|
||||
|
||||
func (s *service) GetWishListItem(ID string) (*entities.WishListItem, error) {
|
||||
return s.repository.ReadWishListItem(ID)
|
||||
}
|
||||
|
||||
func (s *service) GetAllWishListItems(wishListID string) (*[]presenter.WishListItem, error) {
|
||||
return s.repository.ReadAllWishListItems(wishListID)
|
||||
}
|
||||
|
||||
func (s *service) UpdateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error) {
|
||||
return s.repository.UpdateWishListItem(item)
|
||||
}
|
||||
|
||||
func (s *service) DeleteWishListItem(ID string) error {
|
||||
return s.repository.DeleteWishListItem(ID)
|
||||
}
|
||||
61
run-docker-tests.sh
Executable file
61
run-docker-tests.sh
Executable file
@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Цвета для вывода
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Используем -e для интерпретации escape-последовательностей
|
||||
echo -e "${YELLOW}===== Запуск тестов в Docker =====${NC}"
|
||||
|
||||
# Проверка существования директории для отчетов о покрытии
|
||||
if [ ! -d "./coverage" ]; then
|
||||
mkdir -p ./coverage
|
||||
echo -e "Создана директория ./coverage для хранения отчетов о покрытии тестами"
|
||||
fi
|
||||
|
||||
# Проверка запущен ли MongoDB, если нет - запускаем
|
||||
if ! docker ps | grep -q mongodb; then
|
||||
echo -e "${YELLOW}MongoDB не запущена, запускаем контейнер...${NC}"
|
||||
docker compose up -d mongo
|
||||
|
||||
# Даем больше времени на запуск MongoDB
|
||||
echo -e "Ожидаем запуск MongoDB..."
|
||||
sleep 10
|
||||
echo -e "Проверяем готовность MongoDB..."
|
||||
# Попытка подключения к MongoDB для проверки её готовности
|
||||
docker exec mongodb mongosh --quiet --eval "db.stats()"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}MongoDB успешно запущена и готова к работе${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Ожидаем дополнительное время для полной инициализации MongoDB...${NC}"
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
|
||||
# Запускаем тесты в Docker
|
||||
echo -e "${YELLOW}Запускаем контейнер с тестами...${NC}"
|
||||
docker compose up --build tests
|
||||
|
||||
# Получаем код выхода контейнера с тестами
|
||||
exit_code=$(docker inspect -f '{{.State.ExitCode}}' wish-list-api-tests)
|
||||
|
||||
# Останавливаем контейнер с тестами
|
||||
docker compose stop tests
|
||||
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Все тесты успешно пройдены${NC}"
|
||||
echo -e "${YELLOW}Отчеты о покрытии тестами сохранены в директории ./coverage${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Некоторые тесты содержат ошибки (код выхода: $exit_code)${NC}"
|
||||
fi
|
||||
|
||||
# Спрашиваем пользователя, хочет ли он остановить MongoDB
|
||||
read -p "Остановить MongoDB? (y/n): " stop_mongo
|
||||
if [ "$stop_mongo" = "y" ] || [ "$stop_mongo" = "Y" ]; then
|
||||
docker compose stop mongo
|
||||
echo -e "MongoDB остановлена"
|
||||
fi
|
||||
|
||||
exit $exit_code
|
||||
77
scripts/run_tests.sh
Executable file
77
scripts/run_tests.sh
Executable file
@ -0,0 +1,77 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Цвета для вывода
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${YELLOW}===== Запуск всех тестов =====${NC}"
|
||||
|
||||
# Создаем директорию для отчетов о покрытии, если ее нет
|
||||
mkdir -p ./coverage
|
||||
|
||||
# Функция для выполнения и отчета о результатах тестов
|
||||
run_tests() {
|
||||
test_type=$1
|
||||
test_path=$2
|
||||
|
||||
echo -e "${YELLOW}===== Запуск $test_type тестов =====${NC}"
|
||||
|
||||
# Запускаем тесты с генерацией отчета о покрытии
|
||||
if go test -v -coverprofile=./coverage/coverage_$test_type.out $test_path; then
|
||||
echo -e "${GREEN}✓ $test_type тесты успешно пройдены${NC}"
|
||||
# Генерация HTML-отчета о покрытии
|
||||
go tool cover -html=./coverage/coverage_$test_type.out -o ./coverage/coverage_$test_type.html
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗ $test_type тесты содержат ошибки${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Запускаем unit-тесты
|
||||
run_tests "unit" "./tests/unit/..."
|
||||
UNIT_RESULT=$?
|
||||
|
||||
# Функция для проверки доступности MongoDB
|
||||
check_mongodb() {
|
||||
# В Docker контейнере MongoDB доступен по имени хоста 'mongo'
|
||||
if [ -n "$MONGODB_URI" ]; then
|
||||
# Если задана переменная окружения MONGODB_URI, считаем что MongoDB доступен
|
||||
return 0
|
||||
else
|
||||
# Проверяем доступность MongoDB
|
||||
if nc -z mongo 27017 2>/dev/null; then
|
||||
return 0
|
||||
elif nc -z localhost 27017 2>/dev/null; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Если настроена переменная окружения для запуска интеграционных тестов
|
||||
if [ "$RUN_INTEGRATION_TESTS" = "true" ]; then
|
||||
# Запускаем интеграционные тесты, если MongoDB доступна
|
||||
if check_mongodb; then
|
||||
run_tests "integration" "./tests/integration/..."
|
||||
INTEGRATION_RESULT=$?
|
||||
else
|
||||
echo -e "${YELLOW}MongoDB не доступна, интеграционные тесты пропущены${NC}"
|
||||
INTEGRATION_RESULT=0
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}Интеграционные тесты пропущены (RUN_INTEGRATION_TESTS не установлена)${NC}"
|
||||
INTEGRATION_RESULT=0
|
||||
fi
|
||||
|
||||
# Проверяем общий результат
|
||||
if [ $UNIT_RESULT -eq 0 ] && [ $INTEGRATION_RESULT -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Все тесты успешно пройдены${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ Некоторые тесты содержат ошибки${NC}"
|
||||
exit 1
|
||||
fi
|
||||
398
tests/integration/api_test.go
Normal file
398
tests/integration/api_test.go
Normal file
@ -0,0 +1,398 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"wish-list-api/api/handlers"
|
||||
"wish-list-api/api/middleware"
|
||||
"wish-list-api/pkg/auth"
|
||||
"wish-list-api/pkg/entities"
|
||||
"wish-list-api/pkg/user"
|
||||
wishlist "wish-list-api/pkg/wish-list"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
var (
|
||||
app *fiber.App
|
||||
testDB *mongo.Database
|
||||
client *mongo.Client
|
||||
authSvc auth.Service
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setup()
|
||||
|
||||
exitCode := m.Run()
|
||||
|
||||
teardown()
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func setup() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var err error
|
||||
mongoURI := os.Getenv("MONGODB_URI")
|
||||
if mongoURI == "" {
|
||||
mongoURI = "mongodb://mongo_user:mongo_password@localhost:27017/admin"
|
||||
}
|
||||
|
||||
client, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
if err != nil {
|
||||
log.Fatalf("Error connecting to MongoDB: %v", err)
|
||||
}
|
||||
|
||||
err = client.Ping(ctx, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not connect to MongoDB: %v", err)
|
||||
}
|
||||
|
||||
dbName := fmt.Sprintf("wishlist_test_%d", time.Now().UnixNano())
|
||||
testDB = client.Database(dbName)
|
||||
|
||||
userCollection := testDB.Collection("users")
|
||||
wishlistCollection := testDB.Collection("wishlists")
|
||||
wishlistItemCollection := testDB.Collection("wishlist_items")
|
||||
|
||||
userRepo := user.NewMongoRepository(userCollection)
|
||||
wishlistRepo := wishlist.NewMongoRepository(wishlistCollection, wishlistItemCollection)
|
||||
|
||||
userService := user.NewService(userRepo)
|
||||
|
||||
authSvc = auth.NewService(auth.ServiceConfig{
|
||||
UserService: userService,
|
||||
})
|
||||
|
||||
wishlistSvc := wishlist.NewService(wishlistRepo)
|
||||
|
||||
app = fiber.New()
|
||||
|
||||
api := app.Group("/api")
|
||||
|
||||
api.Post("/auth/register", handlers.Register(authSvc))
|
||||
api.Post("/auth/login", handlers.Login(authSvc))
|
||||
api.Post("/auth/refresh", handlers.RefreshToken(authSvc))
|
||||
|
||||
wishListHandler := handlers.NewWishListHandler(wishlistSvc, authSvc)
|
||||
|
||||
wishList := api.Group("/wishlist")
|
||||
|
||||
wishList.Get("/:id", wishListHandler.GetWishList)
|
||||
|
||||
wishList.Get("/user/:userId", wishListHandler.GetUserWishLists)
|
||||
|
||||
wishList.Get("/:wishlistId/items", wishListHandler.GetWishListItems)
|
||||
|
||||
wishList.Get("/item/:id", wishListHandler.GetWishListItem)
|
||||
|
||||
wishList.Post("/", middleware.Protected(authSvc), wishListHandler.CreateWishList)
|
||||
wishList.Put("/:id", middleware.Protected(authSvc), wishListHandler.UpdateWishList)
|
||||
wishList.Delete("/:id", middleware.Protected(authSvc), wishListHandler.DeleteWishList)
|
||||
|
||||
wishList.Post("/item", middleware.Protected(authSvc), wishListHandler.CreateWishListItem)
|
||||
wishList.Put("/item/:id", middleware.Protected(authSvc), wishListHandler.UpdateWishListItem)
|
||||
wishList.Delete("/item/:id", middleware.Protected(authSvc), wishListHandler.DeleteWishListItem)
|
||||
}
|
||||
|
||||
func teardown() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := testDB.Drop(ctx); err != nil {
|
||||
log.Printf("Error dropping test database: %v", err)
|
||||
}
|
||||
|
||||
if err := client.Disconnect(ctx); err != nil {
|
||||
log.Printf("Error disconnecting from MongoDB: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func registerTestUser(t *testing.T) (string, primitive.ObjectID) {
|
||||
userData := entities.RegisterRequest{
|
||||
Email: fmt.Sprintf("test%d@example.com", time.Now().UnixNano()),
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(userData)
|
||||
req := httptest.NewRequest("POST", "/api/auth/register", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var response struct {
|
||||
Status bool `json:"status"`
|
||||
Data entities.TokenPair `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&response)
|
||||
assert.NoError(t, err)
|
||||
|
||||
claims, err := authSvc.ValidateToken(response.Data.AccessToken)
|
||||
assert.NoError(t, err)
|
||||
|
||||
mapClaims := claims.Claims.(jwt.MapClaims)
|
||||
userIDStr := mapClaims["user_id"].(string)
|
||||
userID, err := primitive.ObjectIDFromHex(userIDStr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return response.Data.AccessToken, userID
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
userData := entities.RegisterRequest{
|
||||
Email: fmt.Sprintf("test%d@example.com", time.Now().UnixNano()),
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(userData)
|
||||
req := httptest.NewRequest("POST", "/api/auth/register", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
req = httptest.NewRequest("POST", "/api/auth/register", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusConflict, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
email := fmt.Sprintf("test%d@example.com", time.Now().UnixNano())
|
||||
userData := entities.RegisterRequest{
|
||||
Email: email,
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(userData)
|
||||
req := httptest.NewRequest("POST", "/api/auth/register", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
loginData := entities.LoginRequest{
|
||||
Email: email,
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
jsonBody, _ = json.Marshal(loginData)
|
||||
req = httptest.NewRequest("POST", "/api/auth/login", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
loginData.Password = "wrongpassword"
|
||||
jsonBody, _ = json.Marshal(loginData)
|
||||
req = httptest.NewRequest("POST", "/api/auth/login", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestWishListCRUD(t *testing.T) {
|
||||
token, userID := registerTestUser(t)
|
||||
|
||||
wishlistData := entities.WishList{
|
||||
Title: "Test Wishlist",
|
||||
Description: "Integration test wishlist",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(wishlistData)
|
||||
req := httptest.NewRequest("POST", "/api/wishlist", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
resp, err := app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
|
||||
var createResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data entities.WishList `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&createResponse)
|
||||
assert.NoError(t, err)
|
||||
wishlistID := createResponse.Data.ID
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/wishlist/"+wishlistID, nil)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
updatedData := entities.WishList{
|
||||
Title: "Updated Title",
|
||||
Description: "Updated description",
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
jsonBody, _ = json.Marshal(updatedData)
|
||||
req = httptest.NewRequest("PUT", "/api/wishlist/"+wishlistID, bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/wishlist/"+wishlistID, nil)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/wishlist/"+wishlistID, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/wishlist/user/"+userID.Hex(), nil)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
req = httptest.NewRequest("DELETE", "/api/wishlist/"+wishlistID, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/wishlist/"+wishlistID, nil)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestWishListItemsCRUD(t *testing.T) {
|
||||
token, _ := registerTestUser(t)
|
||||
|
||||
wishlistData := entities.WishList{
|
||||
Title: "Test Wishlist for Items",
|
||||
Description: "Testing wishlist items",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(wishlistData)
|
||||
req := httptest.NewRequest("POST", "/api/wishlist", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
resp, err := app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
|
||||
var createResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data entities.WishList `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&createResponse)
|
||||
assert.NoError(t, err)
|
||||
wishlistID := createResponse.Data.ID
|
||||
|
||||
itemData := entities.WishListItem{
|
||||
Title: "Test Item",
|
||||
Description: "Test item description",
|
||||
URL: "https://example.com",
|
||||
Cost: 99.99,
|
||||
WishListID: wishlistID,
|
||||
}
|
||||
|
||||
jsonBody, _ = json.Marshal(itemData)
|
||||
req = httptest.NewRequest("POST", "/api/wishlist/item", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
|
||||
var itemResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data entities.WishListItem `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&itemResponse)
|
||||
assert.NoError(t, err)
|
||||
itemID := itemResponse.Data.ID
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/wishlist/item/"+itemID, nil)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
updatedItemData := entities.WishListItem{
|
||||
Title: "Updated Item Title",
|
||||
Description: "Updated item description",
|
||||
URL: "https://example.com",
|
||||
Cost: 149.99,
|
||||
}
|
||||
|
||||
jsonBody, _ = json.Marshal(updatedItemData)
|
||||
req = httptest.NewRequest("PUT", "/api/wishlist/item/"+itemID, bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/wishlist/"+wishlistID+"/items", nil)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var itemsResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Data []entities.WishListItem `json:"data"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&itemsResponse)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(itemsResponse.Data))
|
||||
|
||||
req = httptest.NewRequest("DELETE", "/api/wishlist/item/"+itemID, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
req = httptest.NewRequest("GET", "/api/wishlist/item/"+itemID, nil)
|
||||
resp, err = app.Test(req, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
45
tests/integration/mongodb_test.go
Normal file
45
tests/integration/mongodb_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func TestMongoDBConnection(t *testing.T) {
|
||||
mongoURI := os.Getenv("MONGODB_URI")
|
||||
if mongoURI == "" {
|
||||
mongoURI = "mongodb://mongo_user:mongo_password@localhost:27017/admin"
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
assert.NoError(t, err, "Ошибка подключения к MongoDB")
|
||||
defer client.Disconnect(ctx)
|
||||
|
||||
err = client.Ping(ctx, nil)
|
||||
assert.NoError(t, err, "Не удается пропинговать MongoDB")
|
||||
|
||||
dbName := fmt.Sprintf("test_db_%d", time.Now().UnixNano())
|
||||
collection := client.Database(dbName).Collection("test_collection")
|
||||
|
||||
doc := bson.M{"name": "test", "value": "success"}
|
||||
result, err := collection.InsertOne(ctx, doc)
|
||||
assert.NoError(t, err, "Ошибка при вставке документа")
|
||||
assert.NotNil(t, result.InsertedID, "ID вставленного документа не должен быть nil")
|
||||
|
||||
err = client.Database(dbName).Drop(ctx)
|
||||
assert.NoError(t, err, "Ошибка при удалении тестовой БД")
|
||||
|
||||
log.Println("MongoDB подключение успешно проверено!")
|
||||
}
|
||||
347
tests/unit/auth_service_test.go
Normal file
347
tests/unit/auth_service_test.go
Normal file
@ -0,0 +1,347 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/auth"
|
||||
"wish-list-api/pkg/entities"
|
||||
"wish-list-api/pkg/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
var ErrUserNotFound = errors.New("user not found")
|
||||
|
||||
type MockUserRepository struct {
|
||||
users map[string]*entities.User
|
||||
email map[string]*entities.User
|
||||
telegram map[int64]*entities.User
|
||||
}
|
||||
|
||||
func NewMockUserRepository() user.Repository {
|
||||
return &MockUserRepository{
|
||||
users: make(map[string]*entities.User),
|
||||
email: make(map[string]*entities.User),
|
||||
telegram: make(map[int64]*entities.User),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MockUserRepository) CreateUser(user *entities.User) (*entities.User, error) {
|
||||
if user.ID.IsZero() {
|
||||
user.ID = primitive.NewObjectID()
|
||||
}
|
||||
user.CreatedAt = time.Now()
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
r.users[user.ID.Hex()] = user
|
||||
r.email[user.Email] = user
|
||||
if user.TelegramID != 0 {
|
||||
r.telegram[user.TelegramID] = user
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *MockUserRepository) ReadUser(id string) (*entities.User, error) {
|
||||
if user, ok := r.users[id]; ok {
|
||||
return user, nil
|
||||
}
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
func (r *MockUserRepository) ReadUserByEmail(email string) (*entities.User, error) {
|
||||
if user, ok := r.email[email]; ok {
|
||||
return user, nil
|
||||
}
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
func (r *MockUserRepository) ReadAllUsers() (*[]presenter.User, error) {
|
||||
var users []presenter.User
|
||||
for _, u := range r.users {
|
||||
users = append(users, presenter.User{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
})
|
||||
}
|
||||
return &users, nil
|
||||
}
|
||||
|
||||
func (r *MockUserRepository) UpdateUser(user *entities.User) (*entities.User, error) {
|
||||
if _, ok := r.users[user.ID.Hex()]; !ok {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
r.users[user.ID.Hex()] = user
|
||||
|
||||
for email, u := range r.email {
|
||||
if u.ID == user.ID && email != user.Email {
|
||||
delete(r.email, email)
|
||||
break
|
||||
}
|
||||
}
|
||||
r.email[user.Email] = user
|
||||
|
||||
if user.TelegramID != 0 {
|
||||
r.telegram[user.TelegramID] = user
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *MockUserRepository) DeleteUser(id string) error {
|
||||
user, ok := r.users[id]
|
||||
if !ok {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
delete(r.users, id)
|
||||
delete(r.email, user.Email)
|
||||
if user.TelegramID != 0 {
|
||||
delete(r.telegram, user.TelegramID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MockUserRepository) ReadUserByTelegramID(telegramID int64) (*entities.User, error) {
|
||||
if user, ok := r.telegram[telegramID]; ok {
|
||||
return user, nil
|
||||
}
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
func (r *MockUserRepository) UpdateUserTelegramData(user *entities.User) (*entities.User, error) {
|
||||
if _, ok := r.users[user.ID.Hex()]; !ok {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
existingUser := r.users[user.ID.Hex()]
|
||||
existingUser.TelegramID = user.TelegramID
|
||||
existingUser.TelegramUsername = user.TelegramUsername
|
||||
existingUser.FirstName = user.FirstName
|
||||
existingUser.LastName = user.LastName
|
||||
existingUser.PhotoURL = user.PhotoURL
|
||||
existingUser.UpdatedAt = time.Now()
|
||||
|
||||
r.users[user.ID.Hex()] = existingUser
|
||||
|
||||
if user.TelegramID != 0 {
|
||||
for tgID, u := range r.telegram {
|
||||
if u.ID == user.ID && tgID != user.TelegramID {
|
||||
delete(r.telegram, tgID)
|
||||
break
|
||||
}
|
||||
}
|
||||
r.telegram[user.TelegramID] = existingUser
|
||||
}
|
||||
|
||||
return existingUser, nil
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
mockRepo := NewMockUserRepository()
|
||||
mockUserService := user.NewService(mockRepo)
|
||||
|
||||
serviceConfig := auth.ServiceConfig{
|
||||
UserService: mockUserService,
|
||||
}
|
||||
|
||||
service := auth.NewService(serviceConfig)
|
||||
|
||||
t.Run("Register_Success", func(t *testing.T) {
|
||||
userData := &entities.RegisterRequest{
|
||||
Email: "test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
user, err := service.Register(userData)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, user)
|
||||
assert.Equal(t, userData.Email, user.Email)
|
||||
assert.NotEqual(t, userData.Password, user.Password)
|
||||
})
|
||||
|
||||
t.Run("Register_DuplicateEmail", func(t *testing.T) {
|
||||
userData := &entities.RegisterRequest{
|
||||
Email: "test@example.com",
|
||||
Password: "different_password",
|
||||
}
|
||||
|
||||
user, err := service.Register(userData)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, user)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
mockRepo := NewMockUserRepository()
|
||||
mockUserService := user.NewService(mockRepo)
|
||||
|
||||
serviceConfig := auth.ServiceConfig{
|
||||
UserService: mockUserService,
|
||||
}
|
||||
|
||||
service := auth.NewService(serviceConfig)
|
||||
|
||||
registerData := &entities.RegisterRequest{
|
||||
Email: "login_test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
_, err := service.Register(registerData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("Login_Success", func(t *testing.T) {
|
||||
loginData := &entities.LoginRequest{
|
||||
Email: "login_test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
tokens, err := service.Login(loginData)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tokens)
|
||||
assert.NotEmpty(t, tokens.AccessToken)
|
||||
assert.NotEmpty(t, tokens.RefreshToken)
|
||||
})
|
||||
|
||||
t.Run("Login_InvalidEmail", func(t *testing.T) {
|
||||
loginData := &entities.LoginRequest{
|
||||
Email: "nonexistent@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
tokens, err := service.Login(loginData)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, tokens)
|
||||
})
|
||||
|
||||
t.Run("Login_InvalidPassword", func(t *testing.T) {
|
||||
loginData := &entities.LoginRequest{
|
||||
Email: "login_test@example.com",
|
||||
Password: "wrong_password",
|
||||
}
|
||||
|
||||
tokens, err := service.Login(loginData)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, tokens)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRefreshToken(t *testing.T) {
|
||||
mockRepo := NewMockUserRepository()
|
||||
mockUserService := user.NewService(mockRepo)
|
||||
|
||||
serviceConfig := auth.ServiceConfig{
|
||||
UserService: mockUserService,
|
||||
}
|
||||
|
||||
service := auth.NewService(serviceConfig)
|
||||
|
||||
registerData := &entities.RegisterRequest{
|
||||
Email: "refresh_test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
_, err := service.Register(registerData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
loginData := &entities.LoginRequest{
|
||||
Email: "refresh_test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
tokens, err := service.Login(loginData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("RefreshToken_Success", func(t *testing.T) {
|
||||
newTokens, err := service.RefreshToken(tokens.RefreshToken)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, newTokens)
|
||||
assert.NotEmpty(t, newTokens.AccessToken)
|
||||
assert.NotEmpty(t, newTokens.RefreshToken)
|
||||
})
|
||||
|
||||
t.Run("RefreshToken_Invalid", func(t *testing.T) {
|
||||
newTokens, err := service.RefreshToken("invalid_refresh_token")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, newTokens)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateToken(t *testing.T) {
|
||||
mockRepo := NewMockUserRepository()
|
||||
mockUserService := user.NewService(mockRepo)
|
||||
|
||||
serviceConfig := auth.ServiceConfig{
|
||||
UserService: mockUserService,
|
||||
}
|
||||
|
||||
service := auth.NewService(serviceConfig)
|
||||
|
||||
registerData := &entities.RegisterRequest{
|
||||
Email: "validate_test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
_, err := service.Register(registerData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
loginData := &entities.LoginRequest{
|
||||
Email: "validate_test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
tokens, err := service.Login(loginData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("ValidateToken_Success", func(t *testing.T) {
|
||||
token, err := service.ValidateToken(tokens.AccessToken)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, token)
|
||||
})
|
||||
|
||||
t.Run("ValidateToken_Invalid", func(t *testing.T) {
|
||||
token, err := service.ValidateToken("invalid_token")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, token)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetUserIDFromToken(t *testing.T) {
|
||||
mockRepo := NewMockUserRepository()
|
||||
mockUserService := user.NewService(mockRepo)
|
||||
|
||||
serviceConfig := auth.ServiceConfig{
|
||||
UserService: mockUserService,
|
||||
}
|
||||
|
||||
service := auth.NewService(serviceConfig)
|
||||
|
||||
registerData := &entities.RegisterRequest{
|
||||
Email: "userid_test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
user, err := service.Register(registerData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
loginData := &entities.LoginRequest{
|
||||
Email: "userid_test@example.com",
|
||||
Password: "password123",
|
||||
}
|
||||
tokens, err := service.Login(loginData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("GetUserIDFromToken_Success", func(t *testing.T) {
|
||||
userID, err := service.GetUserIDFromToken(tokens.AccessToken)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID.Hex(), userID)
|
||||
})
|
||||
|
||||
t.Run("GetUserIDFromToken_Invalid", func(t *testing.T) {
|
||||
userID, err := service.GetUserIDFromToken("invalid_token")
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, userID)
|
||||
})
|
||||
}
|
||||
121
tests/unit/mock_auth.go
Normal file
121
tests/unit/mock_auth.go
Normal file
@ -0,0 +1,121 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"wish-list-api/pkg/auth"
|
||||
"wish-list-api/pkg/entities"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type MockAuthService struct {
|
||||
users map[primitive.ObjectID]*entities.User
|
||||
tokens map[string]primitive.ObjectID
|
||||
}
|
||||
|
||||
func NewMockAuthService() auth.Service {
|
||||
return &MockAuthService{
|
||||
users: make(map[primitive.ObjectID]*entities.User),
|
||||
tokens: make(map[string]primitive.ObjectID),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockAuthService) AddMockUser(user *entities.User, token string) {
|
||||
m.users[user.ID] = user
|
||||
m.tokens[token] = user.ID
|
||||
}
|
||||
|
||||
func (m *MockAuthService) Login(credentials *entities.LoginRequest) (*entities.TokenPair, error) {
|
||||
for _, user := range m.users {
|
||||
if user.Email == credentials.Email && m.ComparePasswords(user.Password, credentials.Password) {
|
||||
token := "mock-token-for-" + user.ID.Hex()
|
||||
m.tokens[token] = user.ID
|
||||
return &entities.TokenPair{
|
||||
AccessToken: token,
|
||||
RefreshToken: "refresh-" + token,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
func (m *MockAuthService) Register(userData *entities.RegisterRequest) (*entities.User, error) {
|
||||
for _, existingUser := range m.users {
|
||||
if existingUser.Email == userData.Email {
|
||||
return nil, errors.New("email already exists")
|
||||
}
|
||||
}
|
||||
|
||||
hashedPassword, _ := m.HashPassword(userData.Password)
|
||||
user := &entities.User{
|
||||
ID: primitive.NewObjectID(),
|
||||
Email: userData.Email,
|
||||
Password: hashedPassword,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
m.users[user.ID] = user
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m *MockAuthService) RefreshToken(refreshToken string) (*entities.TokenPair, error) {
|
||||
if len(refreshToken) < 8 {
|
||||
return nil, errors.New("invalid refresh token")
|
||||
}
|
||||
|
||||
accessToken := refreshToken[8:]
|
||||
userID, ok := m.tokens[accessToken]
|
||||
if !ok {
|
||||
return nil, errors.New("invalid refresh token")
|
||||
}
|
||||
|
||||
newToken := "mock-token-for-" + userID.Hex() + "-refreshed"
|
||||
m.tokens[newToken] = userID
|
||||
|
||||
return &entities.TokenPair{
|
||||
AccessToken: newToken,
|
||||
RefreshToken: "refresh-" + newToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockAuthService) ValidateToken(tokenString string) (*jwt.Token, error) {
|
||||
if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
|
||||
tokenString = tokenString[7:]
|
||||
}
|
||||
|
||||
userID, ok := m.tokens[tokenString]
|
||||
if !ok {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"user_id": userID.Hex(),
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
})
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (m *MockAuthService) GetUserIDFromToken(tokenString string) (string, error) {
|
||||
if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
|
||||
tokenString = tokenString[7:]
|
||||
}
|
||||
|
||||
userID, ok := m.tokens[tokenString]
|
||||
if !ok {
|
||||
return "", errors.New("invalid token")
|
||||
}
|
||||
return userID.Hex(), nil
|
||||
}
|
||||
|
||||
func (m *MockAuthService) HashPassword(password string) (string, error) {
|
||||
return "hashed_" + password, nil
|
||||
}
|
||||
|
||||
func (m *MockAuthService) ComparePasswords(hashedPassword string, plainPassword string) bool {
|
||||
return hashedPassword == "hashed_"+plainPassword
|
||||
}
|
||||
167
tests/unit/mock_repository.go
Normal file
167
tests/unit/mock_repository.go
Normal file
@ -0,0 +1,167 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"wish-list-api/api/presenter"
|
||||
"wish-list-api/pkg/entities"
|
||||
wishlist "wish-list-api/pkg/wish-list"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type MockWishListRepository struct {
|
||||
wishLists map[string]*entities.WishList
|
||||
wishListItems map[string]*entities.WishListItem
|
||||
}
|
||||
|
||||
func NewMockWishListRepository() wishlist.Repository {
|
||||
return &MockWishListRepository{
|
||||
wishLists: make(map[string]*entities.WishList),
|
||||
wishListItems: make(map[string]*entities.WishListItem),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) CreateWishList(wishList *entities.WishList) (*entities.WishList, error) {
|
||||
if wishList.ID == "" {
|
||||
wishList.ID = "mock-id-" + time.Now().String()
|
||||
}
|
||||
wishList.CreatedAt = time.Now()
|
||||
wishList.UpdatedAt = time.Now()
|
||||
r.wishLists[wishList.ID] = wishList
|
||||
return wishList, nil
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) ReadWishList(ID string) (*entities.WishList, error) {
|
||||
if wishList, ok := r.wishLists[ID]; ok {
|
||||
return wishList, nil
|
||||
}
|
||||
return nil, errors.New("wishlist not found")
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) ReadAllWishLists(userID string) (*[]presenter.WishList, error) {
|
||||
var result []presenter.WishList
|
||||
for _, wl := range r.wishLists {
|
||||
if wl.UserID == userID {
|
||||
objID, _ := primitive.ObjectIDFromHex(wl.ID)
|
||||
result = append(result, presenter.WishList{
|
||||
ID: objID,
|
||||
Title: wl.Title,
|
||||
UserID: wl.UserID,
|
||||
Description: wl.Description,
|
||||
IsPublic: wl.IsPublic,
|
||||
PhotoURL: wl.PhotoURL,
|
||||
CreatedAt: wl.CreatedAt,
|
||||
UpdatedAt: wl.UpdatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) ReadPublicWishLists() (*[]presenter.WishList, error) {
|
||||
var result []presenter.WishList
|
||||
for _, wl := range r.wishLists {
|
||||
if wl.IsPublic {
|
||||
objID, _ := primitive.ObjectIDFromHex(wl.ID)
|
||||
result = append(result, presenter.WishList{
|
||||
ID: objID,
|
||||
Title: wl.Title,
|
||||
UserID: wl.UserID,
|
||||
Description: wl.Description,
|
||||
IsPublic: wl.IsPublic,
|
||||
PhotoURL: wl.PhotoURL,
|
||||
CreatedAt: wl.CreatedAt,
|
||||
UpdatedAt: wl.UpdatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) UpdateWishList(wishList *entities.WishList) (*entities.WishList, error) {
|
||||
if _, ok := r.wishLists[wishList.ID]; !ok {
|
||||
return nil, errors.New("wishlist not found")
|
||||
}
|
||||
wishList.UpdatedAt = time.Now()
|
||||
r.wishLists[wishList.ID] = wishList
|
||||
return wishList, nil
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) DeleteWishList(ID string) error {
|
||||
if _, ok := r.wishLists[ID]; !ok {
|
||||
return errors.New("wishlist not found")
|
||||
}
|
||||
delete(r.wishLists, ID)
|
||||
|
||||
for id, item := range r.wishListItems {
|
||||
if item.WishListID == ID {
|
||||
delete(r.wishListItems, id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) CreateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error) {
|
||||
if _, ok := r.wishLists[item.WishListID]; !ok {
|
||||
return nil, errors.New("wishlist not found")
|
||||
}
|
||||
|
||||
if item.ID == "" {
|
||||
item.ID = "mock-item-id-" + time.Now().String()
|
||||
}
|
||||
item.CreatedAt = time.Now()
|
||||
item.UpdatedAt = time.Now()
|
||||
r.wishListItems[item.ID] = item
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) ReadWishListItem(ID string) (*entities.WishListItem, error) {
|
||||
if item, ok := r.wishListItems[ID]; ok {
|
||||
return item, nil
|
||||
}
|
||||
return nil, errors.New("wishlist item not found")
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) ReadAllWishListItems(wishListID string) (*[]presenter.WishListItem, error) {
|
||||
if _, ok := r.wishLists[wishListID]; !ok {
|
||||
return nil, errors.New("wishlist not found")
|
||||
}
|
||||
|
||||
var result []presenter.WishListItem
|
||||
for _, item := range r.wishListItems {
|
||||
if item.WishListID == wishListID {
|
||||
objID, _ := primitive.ObjectIDFromHex(item.ID)
|
||||
result = append(result, presenter.WishListItem{
|
||||
ID: objID,
|
||||
Title: item.Title,
|
||||
URL: item.URL,
|
||||
Cost: item.Cost,
|
||||
WishListID: item.WishListID,
|
||||
Description: item.Description,
|
||||
PhotoURL: item.PhotoURL,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) UpdateWishListItem(item *entities.WishListItem) (*entities.WishListItem, error) {
|
||||
if _, ok := r.wishListItems[item.ID]; !ok {
|
||||
return nil, errors.New("wishlist item not found")
|
||||
}
|
||||
item.UpdatedAt = time.Now()
|
||||
r.wishListItems[item.ID] = item
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (r *MockWishListRepository) DeleteWishListItem(ID string) error {
|
||||
if _, ok := r.wishListItems[ID]; !ok {
|
||||
return errors.New("wishlist item not found")
|
||||
}
|
||||
delete(r.wishListItems, ID)
|
||||
return nil
|
||||
}
|
||||
283
tests/unit/wish_list_handler_test.go
Normal file
283
tests/unit/wish_list_handler_test.go
Normal file
@ -0,0 +1,283 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"wish-list-api/api/handlers"
|
||||
"wish-list-api/pkg/entities"
|
||||
wishlist "wish-list-api/pkg/wish-list"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func setupTestApp() (*fiber.App, *handlers.WishListHandler, wishlist.Repository, *MockAuthService) {
|
||||
app := fiber.New()
|
||||
mockRepo := NewMockWishListRepository()
|
||||
mockAuth := NewMockAuthService().(*MockAuthService)
|
||||
|
||||
userID := primitive.NewObjectID()
|
||||
user := &entities.User{
|
||||
ID: userID,
|
||||
Email: "test@example.com",
|
||||
}
|
||||
mockAuth.AddMockUser(user, "valid-token")
|
||||
|
||||
service := wishlist.NewService(mockRepo)
|
||||
handler := handlers.NewWishListHandler(service, mockAuth)
|
||||
|
||||
return app, handler, mockRepo, mockAuth
|
||||
}
|
||||
|
||||
func TestCreateWishListHandler(t *testing.T) {
|
||||
app, handler, _, _ := setupTestApp()
|
||||
|
||||
app.Post("/wishlist", handler.CreateWishList)
|
||||
|
||||
t.Run("CreateWishList_Success", func(t *testing.T) {
|
||||
wishList := entities.WishList{
|
||||
Title: "Test Wishlist",
|
||||
Description: "Test Description",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(wishList)
|
||||
req := httptest.NewRequest("POST", "/wishlist", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer valid-token")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 201, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("CreateWishList_Unauthorized", func(t *testing.T) {
|
||||
wishList := entities.WishList{
|
||||
Title: "Test Wishlist",
|
||||
Description: "Test Description",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(wishList)
|
||||
req := httptest.NewRequest("POST", "/wishlist", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("CreateWishList_InvalidToken", func(t *testing.T) {
|
||||
wishList := entities.WishList{
|
||||
Title: "Test Wishlist",
|
||||
Description: "Test Description",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(wishList)
|
||||
req := httptest.NewRequest("POST", "/wishlist", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer invalid-token")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetWishListHandler(t *testing.T) {
|
||||
app, handler, repo, mockAuth := setupTestApp()
|
||||
|
||||
app.Get("/wishlist/:id", handler.GetWishList)
|
||||
|
||||
userObjID := primitive.NewObjectID()
|
||||
userID := userObjID.Hex()
|
||||
wishList := &entities.WishList{
|
||||
ID: "test-wishlist-id",
|
||||
Title: "Test Public Wishlist",
|
||||
UserID: userID,
|
||||
IsPublic: true,
|
||||
}
|
||||
repo.CreateWishList(wishList)
|
||||
|
||||
privateWishList := &entities.WishList{
|
||||
ID: "private-wishlist-id",
|
||||
Title: "Test Private Wishlist",
|
||||
UserID: userID,
|
||||
IsPublic: false,
|
||||
}
|
||||
repo.CreateWishList(privateWishList)
|
||||
|
||||
mockAuth.tokens["valid-token"] = userObjID
|
||||
|
||||
t.Run("GetWishList_Public_Success", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/wishlist/test-wishlist-id", nil)
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("GetWishList_Private_Unauthorized", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/wishlist/private-wishlist-id", nil)
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("GetWishList_Private_AuthorizedOwner", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/wishlist/private-wishlist-id", nil)
|
||||
req.Header.Set("Authorization", "Bearer valid-token")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("GetWishList_NotFound", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/wishlist/non-existent-id", nil)
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 404, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateWishListHandler(t *testing.T) {
|
||||
app, handler, repo, mockAuth := setupTestApp()
|
||||
|
||||
app.Put("/wishlist/:id", handler.UpdateWishList)
|
||||
|
||||
userID := primitive.NewObjectID()
|
||||
|
||||
wishList := &entities.WishList{
|
||||
ID: "test-wishlist-id",
|
||||
Title: "Original Title",
|
||||
UserID: userID.Hex(),
|
||||
IsPublic: true,
|
||||
}
|
||||
repo.CreateWishList(wishList)
|
||||
|
||||
otherUserWishList := &entities.WishList{
|
||||
ID: "other-user-wishlist-id",
|
||||
Title: "Other User's Wishlist",
|
||||
UserID: "other-user-id",
|
||||
IsPublic: true,
|
||||
}
|
||||
repo.CreateWishList(otherUserWishList)
|
||||
|
||||
mockAuth.tokens["valid-token"] = userID
|
||||
|
||||
t.Run("UpdateWishList_Success", func(t *testing.T) {
|
||||
updatedWishList := entities.WishList{
|
||||
Title: "Updated Title",
|
||||
Description: "Updated Description",
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(updatedWishList)
|
||||
req := httptest.NewRequest("PUT", "/wishlist/test-wishlist-id", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer valid-token")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
updatedList, _ := repo.ReadWishList("test-wishlist-id")
|
||||
assert.Equal(t, "Updated Title", updatedList.Title)
|
||||
assert.Equal(t, "Updated Description", updatedList.Description)
|
||||
assert.Equal(t, false, updatedList.IsPublic)
|
||||
})
|
||||
|
||||
t.Run("UpdateWishList_Unauthorized", func(t *testing.T) {
|
||||
updatedWishList := entities.WishList{
|
||||
Title: "Updated Title",
|
||||
Description: "Updated Description",
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(updatedWishList)
|
||||
req := httptest.NewRequest("PUT", "/wishlist/test-wishlist-id", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("UpdateWishList_NotOwner", func(t *testing.T) {
|
||||
updatedWishList := entities.WishList{
|
||||
Title: "Updated Title",
|
||||
Description: "Updated Description",
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(updatedWishList)
|
||||
req := httptest.NewRequest("PUT", "/wishlist/other-user-wishlist-id", bytes.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer valid-token")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteWishListHandler(t *testing.T) {
|
||||
app, handler, repo, mockAuth := setupTestApp()
|
||||
|
||||
app.Delete("/wishlist/:id", handler.DeleteWishList)
|
||||
|
||||
userID := primitive.NewObjectID()
|
||||
|
||||
wishList := &entities.WishList{
|
||||
ID: "test-wishlist-id",
|
||||
Title: "Test Wishlist",
|
||||
UserID: userID.Hex(),
|
||||
IsPublic: true,
|
||||
}
|
||||
repo.CreateWishList(wishList)
|
||||
|
||||
otherUserWishList := &entities.WishList{
|
||||
ID: "other-user-wishlist-id",
|
||||
Title: "Other User's Wishlist",
|
||||
UserID: "other-user-id",
|
||||
IsPublic: true,
|
||||
}
|
||||
repo.CreateWishList(otherUserWishList)
|
||||
|
||||
mockAuth.tokens["valid-token"] = userID
|
||||
|
||||
t.Run("DeleteWishList_Success", func(t *testing.T) {
|
||||
req := httptest.NewRequest("DELETE", "/wishlist/test-wishlist-id", nil)
|
||||
req.Header.Set("Authorization", "Bearer valid-token")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
_, err = repo.ReadWishList("test-wishlist-id")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("DeleteWishList_Unauthorized", func(t *testing.T) {
|
||||
req := httptest.NewRequest("DELETE", "/wishlist/other-user-wishlist-id", nil)
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("DeleteWishList_NotOwner", func(t *testing.T) {
|
||||
req := httptest.NewRequest("DELETE", "/wishlist/other-user-wishlist-id", nil)
|
||||
req.Header.Set("Authorization", "Bearer valid-token")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
282
tests/unit/wish_list_service_test.go
Normal file
282
tests/unit/wish_list_service_test.go
Normal file
@ -0,0 +1,282 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"wish-list-api/pkg/entities"
|
||||
wishlist "wish-list-api/pkg/wish-list"
|
||||
)
|
||||
|
||||
func TestCreateWishList(t *testing.T) {
|
||||
mockRepo := NewMockWishListRepository()
|
||||
service := wishlist.NewService(mockRepo)
|
||||
|
||||
t.Run("CreateWishList_Success", func(t *testing.T) {
|
||||
wishList := &entities.WishList{
|
||||
Title: "Test WishList",
|
||||
UserID: "user123",
|
||||
Description: "Test Description",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
result, err := service.CreateWishList(wishList)
|
||||
if err != nil {
|
||||
t.Errorf("Ошибка при создании списка желаний: %v", err)
|
||||
}
|
||||
|
||||
if result.Title != wishList.Title {
|
||||
t.Errorf("Ожидалось название '%s', получено '%s'", wishList.Title, result.Title)
|
||||
}
|
||||
|
||||
if result.UserID != wishList.UserID {
|
||||
t.Errorf("Ожидался UserID '%s', получен '%s'", wishList.UserID, result.UserID)
|
||||
}
|
||||
|
||||
if result.ID == "" {
|
||||
t.Error("ID не должен быть пустым")
|
||||
}
|
||||
|
||||
if result.CreatedAt.IsZero() {
|
||||
t.Error("CreatedAt не должен быть пустым")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetWishList(t *testing.T) {
|
||||
mockRepo := NewMockWishListRepository()
|
||||
service := wishlist.NewService(mockRepo)
|
||||
|
||||
wishList := &entities.WishList{
|
||||
ID: "test-id-123",
|
||||
Title: "Test WishList",
|
||||
UserID: "user123",
|
||||
Description: "Test Description",
|
||||
IsPublic: true,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
createdWishList, _ := service.CreateWishList(wishList)
|
||||
|
||||
t.Run("GetWishList_Success", func(t *testing.T) {
|
||||
result, err := service.GetWishList(createdWishList.ID)
|
||||
if err != nil {
|
||||
t.Errorf("Ошибка при получении списка желаний: %v", err)
|
||||
}
|
||||
|
||||
if result.ID != createdWishList.ID {
|
||||
t.Errorf("Ожидался ID '%s', получен '%s'", createdWishList.ID, result.ID)
|
||||
}
|
||||
|
||||
if result.Title != wishList.Title {
|
||||
t.Errorf("Ожидалось название '%s', получено '%s'", wishList.Title, result.Title)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetWishList_NotFound", func(t *testing.T) {
|
||||
_, err := service.GetWishList("non-existent-id")
|
||||
if err == nil {
|
||||
t.Error("Должна быть ошибка при поиске несуществующего списка")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateWishList(t *testing.T) {
|
||||
mockRepo := NewMockWishListRepository()
|
||||
service := wishlist.NewService(mockRepo)
|
||||
|
||||
wishList := &entities.WishList{
|
||||
Title: "Original Title",
|
||||
UserID: "user123",
|
||||
Description: "Original Description",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
createdWishList, _ := service.CreateWishList(wishList)
|
||||
|
||||
t.Run("UpdateWishList_Success", func(t *testing.T) {
|
||||
updatedWishList := &entities.WishList{
|
||||
ID: createdWishList.ID,
|
||||
Title: "Updated Title",
|
||||
UserID: "user123",
|
||||
Description: "Updated Description",
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
result, err := service.UpdateWishList(updatedWishList)
|
||||
if err != nil {
|
||||
t.Errorf("Ошибка при обновлении списка желаний: %v", err)
|
||||
}
|
||||
|
||||
if result.Title != "Updated Title" {
|
||||
t.Errorf("Название не обновилось. Ожидалось 'Updated Title', получено '%s'", result.Title)
|
||||
}
|
||||
|
||||
if result.Description != "Updated Description" {
|
||||
t.Errorf("Описание не обновилось. Ожидалось 'Updated Description', получено '%s'", result.Description)
|
||||
}
|
||||
|
||||
if result.IsPublic != false {
|
||||
t.Error("Флаг IsPublic не обновился. Ожидалось false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UpdateWishList_NotFound", func(t *testing.T) {
|
||||
nonExistentWishList := &entities.WishList{
|
||||
ID: "non-existent-id",
|
||||
Title: "Test Title",
|
||||
UserID: "user123",
|
||||
Description: "Test Description",
|
||||
}
|
||||
|
||||
_, err := service.UpdateWishList(nonExistentWishList)
|
||||
if err == nil {
|
||||
t.Error("Должна быть ошибка при обновлении несуществующего списка")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteWishList(t *testing.T) {
|
||||
mockRepo := NewMockWishListRepository()
|
||||
service := wishlist.NewService(mockRepo)
|
||||
|
||||
wishList := &entities.WishList{
|
||||
Title: "Test WishList",
|
||||
UserID: "user123",
|
||||
Description: "Test Description",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
createdWishList, _ := service.CreateWishList(wishList)
|
||||
|
||||
t.Run("DeleteWishList_Success", func(t *testing.T) {
|
||||
err := service.DeleteWishList(createdWishList.ID)
|
||||
if err != nil {
|
||||
t.Errorf("Ошибка при удалении списка желаний: %v", err)
|
||||
}
|
||||
|
||||
_, err = service.GetWishList(createdWishList.ID)
|
||||
if err == nil {
|
||||
t.Error("Список желаний не был удален")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DeleteWishList_NotFound", func(t *testing.T) {
|
||||
err := service.DeleteWishList("non-existent-id")
|
||||
if err == nil {
|
||||
t.Error("Должна быть ошибка при удалении несуществующего списка")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateWishListItem(t *testing.T) {
|
||||
mockRepo := NewMockWishListRepository()
|
||||
service := wishlist.NewService(mockRepo)
|
||||
|
||||
wishList := &entities.WishList{
|
||||
Title: "Test WishList",
|
||||
UserID: "user123",
|
||||
Description: "Test Description",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
createdWishList, _ := service.CreateWishList(wishList)
|
||||
|
||||
t.Run("CreateWishListItem_Success", func(t *testing.T) {
|
||||
item := &entities.WishListItem{
|
||||
Title: "Test Item",
|
||||
URL: "https://example.com",
|
||||
Cost: 100.50,
|
||||
WishListID: createdWishList.ID,
|
||||
Description: "Test Item Description",
|
||||
}
|
||||
|
||||
result, err := service.CreateWishListItem(item)
|
||||
if err != nil {
|
||||
t.Errorf("Ошибка при создании элемента списка желаний: %v", err)
|
||||
}
|
||||
|
||||
if result.Title != item.Title {
|
||||
t.Errorf("Ожидалось название '%s', получено '%s'", item.Title, result.Title)
|
||||
}
|
||||
|
||||
if result.WishListID != createdWishList.ID {
|
||||
t.Errorf("Ожидался WishListID '%s', получен '%s'", createdWishList.ID, result.WishListID)
|
||||
}
|
||||
|
||||
if result.ID == "" {
|
||||
t.Error("ID не должен быть пустым")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreateWishListItem_InvalidWishListID", func(t *testing.T) {
|
||||
item := &entities.WishListItem{
|
||||
Title: "Test Item",
|
||||
URL: "https://example.com",
|
||||
Cost: 100.50,
|
||||
WishListID: "non-existent-id",
|
||||
Description: "Test Item Description",
|
||||
}
|
||||
|
||||
_, err := service.CreateWishListItem(item)
|
||||
if err == nil {
|
||||
t.Error("Должна быть ошибка при создании элемента для несуществующего списка")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetWishListItems(t *testing.T) {
|
||||
mockRepo := NewMockWishListRepository()
|
||||
service := wishlist.NewService(mockRepo)
|
||||
|
||||
wishList := &entities.WishList{
|
||||
Title: "Test WishList",
|
||||
UserID: "user123",
|
||||
Description: "Test Description",
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
createdWishList, _ := service.CreateWishList(wishList)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
item := &entities.WishListItem{
|
||||
Title: "Item",
|
||||
URL: "https://example.com",
|
||||
Cost: 100.50,
|
||||
WishListID: createdWishList.ID,
|
||||
Description: "Item Description",
|
||||
}
|
||||
service.CreateWishListItem(item)
|
||||
}
|
||||
|
||||
t.Run("GetAllWishListItems_Success", func(t *testing.T) {
|
||||
items, err := service.GetAllWishListItems(createdWishList.ID)
|
||||
if err != nil {
|
||||
t.Errorf("Ошибка при получении элементов списка желаний: %v", err)
|
||||
}
|
||||
|
||||
if len(*items) != 3 {
|
||||
t.Errorf("Ожидалось 3 элемента, получено %d", len(*items))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetAllWishListItems_EmptyList", func(t *testing.T) {
|
||||
emptyWishList := &entities.WishList{
|
||||
Title: "Empty WishList",
|
||||
UserID: "user123",
|
||||
Description: "Empty List",
|
||||
IsPublic: true,
|
||||
}
|
||||
createdEmptyWishList, _ := service.CreateWishList(emptyWishList)
|
||||
|
||||
items, err := service.GetAllWishListItems(createdEmptyWishList.ID)
|
||||
if err != nil {
|
||||
t.Errorf("Ошибка при получении элементов пустого списка: %v", err)
|
||||
}
|
||||
|
||||
if len(*items) != 0 {
|
||||
t.Errorf("Ожидалось 0 элементов, получено %d", len(*items))
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user