first commit

This commit is contained in:
songl 2024-06-11 15:26:29 +05:00
commit 5460bf2019
29 changed files with 1313 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/test.iml" filepath="$PROJECT_DIR$/.idea/test.iml" />
</modules>
</component>
</project>

9
.idea/todolist.iml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

407
README.md Normal file
View File

@ -0,0 +1,407 @@
# Документация API
## Обзор
Этот проект представляет собой RESTful API для управления пользователями и Todo пользователей.
В качестве ORM используется GORM.
# Структура проекта
- ## cmd/
- ### main.go
- Запуск сервера
- ## config/
- ### config.go
- Инициализация и соединение с бд
- ## controllers/
- ### todo_controller.go
- контроллер для обработки HTTP-запросов с использованием фреймворка Gin. Он включает в себя несколько функций, каждая из которых отвечает за определённый CRUD (Create, Read, Update, Delete) функционал для задач (Todo).
1. **CreateTodo**:
- Получает JSON-запрос и пытается его привязать к структуре `Todo`.
- Валидирует структуру с использованием библиотеки `validator`.
- Вызывает сервис для создания новой задачи.
- Возвращает ответ с созданной задачей или ошибкой.
2. **GetTodos**:
- Вызывает сервис для получения всех задач.
- Возвращает список задач или ошибку.
3. **GetTodoByID**:
- Получает ID задачи из параметров URL.
- Вызывает сервис для получения задачи по этому ID.
- Возвращает найденную задачу или сообщение об ошибке, если задача не найдена.
4. **UpdateTodo**:
- Получает ID задачи из параметров URL.
- Получает JSON-запрос и пытается его привязать к структуре `Todo`.
- Валидирует структуру с использованием библиотеки `validator`.
- Вызывает сервис для обновления задачи по ID.
- Возвращает обновлённую задачу или ошибку.
5. **DeleteTodo**:
- Получает ID задачи из параметров URL.
- Вызывает сервис для удаления задачи по этому ID.
- Возвращает сообщение об успешном удалении или ошибку, если задача не найдена.
- ### user_controller.go
- контроллеры для управления пользователями с использованием фреймворка Gin
1. **Register**:
- Получает данные пользователя из тела запроса и преобразует их в структуру `User`.
- Проверяет корректность данных (валидацию).
- Вызывает сервис для регистрации пользователя.
- Возвращает ответ с соответствующим статусом (успешная регистрация или ошибка).
2. **Login**:
- Получает данные для входа (имя пользователя и пароль) из тела запроса.
- Вызывает сервис для аутентификации пользователя и получения токена.
- Возвращает токен или сообщение об ошибке.
3. **GetUserProfile**:
- Получает текущего пользователя из контекста запроса.
- Возвращает данные профиля пользователя или сообщение об ошибке, если пользователь не найден.
4. **GetUsers**:
- Получает всех пользователей с помощью сервиса.
- Возвращает список пользователей или сообщение об ошибке.
5. **GetUserByID**:
- Получает ID пользователя из параметров URL.
- Вызывает сервис для получения данных пользователя по ID.
- Возвращает данные пользователя или сообщение об ошибке, если пользователь не найден.
6. **UpdateUser**:
- Получает ID пользователя из параметров URL и новые данные пользователя из тела запроса.
- Проверяет корректность новых данных (валидацию).
- Вызывает сервис для обновления данных пользователя.
- Возвращает обновленные данные пользователя или сообщение об ошибке.
7. **DeleteUser**:
- Получает ID пользователя из параметров URL.
- Вызывает сервис для удаления пользователя по ID.
- Возвращает сообщение об успешном удалении или сообщение об ошибке, если пользователь не найден.
- *Основные компоненты*:
- **Gin**: веб-фреймворк для маршрутизации и обработки HTTP-запросов.
- **Validator**: библиотека для валидации структур данных.
- **Services**: пакет, содержащий бизнес-логику для работы с задачами.
- **Utils**: пакет для вспомогательных функций, таких как формирование ответов.
- ## database/
- ### migration.go
- Основная точку входа, которая использует пакет `gorm` для работы с базой данных.
1. **Main**:
- Инициализация базы данных:
- Получение объекта базы данных:
- Миграция таблиц:
- ## domain/
- ### error_response.go
- Структура ErrorResponse, которая используется для представления сообщений об ошибках в формате JSON.
- ### success_response.go
- Структура Response, которая используется для представления сообщений об успехах в формате JSON.
- ### todo.go
- Стуруктура Todo
- ### todoResponse
- Структура TodoResponse, которая используется для представлния сообщений с Todo
- ### user.go
- Структура User
- ### userResponse.go
- Структура UserResponse, которая используется для представлния сообщений с User
- ## middleware/
- ### auth_middleware.go
- Middleware проверяет наличие и валидность токена аутентификации в заголовке HTTP-запроса и выполняет соответствующие действия.
1. **Функция `AuthMiddleware`**:
- Определяется функция `AuthMiddleware`, возвращающая обработчик (handler) для использования в качестве middleware.
2. **Основная логика аутентификации**:
- *Получение токена*:
- Извлекается заголовок `Authorization` из HTTP-запроса.
- Если токен отсутствует, возвращается ответ с кодом `http.StatusUnauthorized` и сообщение об ошибке, после чего обработка запроса прекращается (`c.Abort()`).
- *Проверка токена*:
- Токен проверяется с помощью функции `utils.ValidateToken`, которая возвращает информацию о пользователе (`claims`) и ошибку (`err`).
- Если проверка не проходит, возвращается ответ с кодом `http.StatusUnauthorized` и сообщение об ошибке, после чего обработка запроса прекращается.
- *Загрузка пользователя*:
- Выполняется запрос к базе данных для получения пользователя по идентификатору из токена (`claims.UserID`).
- Если пользователь не найден, возвращается ответ с кодом `http.StatusUnauthorized` и сообщение об ошибке, после чего обработка запроса прекращается.
- *Установка пользователя в контекст*:
- Если пользователь успешно найден, он устанавливается в контекст запроса (`c.Set("user", user)`), чтобы быть доступным в последующих обработчиках.
3. **Продолжение обработки запроса**:
- Если все проверки прошли успешно, запрос передается дальше следующему обработчику в цепочке (`c.Next()`).
- ## repositories/
- ### todo_repository.go
- функции-репозитории для работы с задачами (Todo) в базе данных, используя GORM. Репозитории инкапсулируют логику доступа к данным и обеспечивают CRUD-операции (создание, чтение, обновление, удаление).
1. **CreateTodoRepository**:
- Создает новую задачу в базе данных.
- Получает соединение с базой данных через `config.GetDB()`.
- Использует метод `Create` для добавления новой задачи и возвращает возможную ошибку.
2. **GetTodosRepository**:
- Возвращает все задачи из базы данных.
- Инициализирует срез `todos` для хранения задач.
- Получает соединение с базой данных и использует метод `Find` для получения всех задач.
- Возвращает список задач и возможную ошибку.
3. **GetTodoByIDRepository**:
- Возвращает задачу по её идентификатору (ID).
- Инициализирует переменную `todo` для хранения задачи.
- Получает соединение с базой данных и использует метод `First` для поиска задачи по ID.
- Возвращает найденную задачу и возможную ошибку.
4. **UpdateTodoRepository**:
- Обновляет информацию о задаче в базе данных.
- Получает соединение с базой данных и использует метод `Save` для обновления задачи.
- Возвращает возможную ошибку.
5. **DeleteTodoRepository**:
- Удаляет задачу из базы данных по её идентификатору (ID).
- Получает соединение с базой данных и использует метод `Delete` для удаления задачи.
- Возвращает возможную ошибку.
- ### user_repository.go
- функции-репозитории для работы с пользователями в базе данных, используя GORM. Репозитории обеспечивают CRUD-операции (создание, чтение, обновление, удаление) для сущности пользователя (User).
1. **CreateUserRepository**:
- Создает нового пользователя в базе данных.
- Получает соединение с базой данных через `config.GetDB()`.
- Использует метод `Create` для добавления нового пользователя и возвращает возможную ошибку.
2. **GetUsersRepository**:
- Возвращает всех пользователей из базы данных.
- Инициализирует срез `users` для хранения пользователей.
- Получает соединение с базой данных и использует метод `Find` для получения всех пользователей, предварительно загружая связанные задачи (`Todos`).
- Возвращает список пользователей и возможную ошибку.
3. **GetUserByUsername**:
- Возвращает пользователя по его имени (username).
- Инициализирует переменную `user` для хранения пользователя.
- Получает соединение с базой данных и использует метод `Where` для поиска пользователя по имени и метод `First` для получения первого найденного результата.
- Возвращает найденного пользователя и возможную ошибку.
4. **GetUserByIDRepository**:
- Возвращает пользователя по его идентификатору (ID).
- Инициализирует переменную `user` для хранения пользователя.
- Получает соединение с базой данных и использует метод `First` для поиска пользователя по ID, предварительно загружая связанные задачи (`Todos`).
- Возвращает найденного пользователя и возможную ошибку.
5. **UpdateUserRepository**:
- Обновляет информацию о пользователе в базе данных.
- Получает соединение с базой данных и использует метод `Save` для обновления пользователя.
- Возвращает возможную ошибку.
6. **DeleteUserRepository**:
- Удаляет пользователя из базы данных по его идентификатору (ID).
- Получает соединение с базой данных и использует метод `Delete` для удаления пользователя.
- Возвращает возможную ошибку.
- ## routes/
- ### routes.go
- маршруты (routes) на основе Gin, задавая пути для операций с пользователями и задачами (Todo). Он также применяет middleware для аутентификации к определённым маршрутам.
1. **Пакеты и импорт**:
- Импортируются необходимые пакеты: `gin-gonic/gin` для работы с веб-фреймворком Gin, а также локальные пакеты `todolist/controllers` и `todolist/middleware`.
2. **Функция `RegisterRoutes`**:
- Функция `RegisterRoutes` регистрирует маршруты для приложения, принимая на вход экземпляр `gin.Engine`.
3. **Маршруты для пользователей**
- `r.POST("/register", controllers.Register)`: Маршрут для регистрации нового пользователя.
- `r.POST("/login", controllers.Login)`: Маршрут для входа пользователя.
- `r.GET("/users/:id", controllers.GetUserByID)`: Маршрут для получения пользователя по его ID.
- `r.GET("/users", controllers.GetUsers)`: Маршрут для получения всех пользователей.
- Группа маршрутов `userRoutes` с префиксом `/user`, к которой применяется middleware аутентификации `AuthMiddleware`:
- `userRoutes.GET("/profile", controllers.GetUserProfile)`: Маршрут для получения профиля текущего пользователя.
- `userRoutes.PATCH("/:id", controllers.UpdateUser)`: Маршрут для обновления пользователя по его ID.
- `userRoutes.DELETE("/:id", controllers.DeleteUser)`: Маршрут для удаления пользователя по его ID.
4. **Маршруты для задач (Todo)**:
- Группа маршрутов `todoRoutes` с префиксом `/todos`, к которой применяется middleware аутентификации `AuthMiddleware`:
- `todoRoutes.POST("/", controllers.CreateTodo)`: Маршрут для создания новой задачи.
- `todoRoutes.GET("/", controllers.GetTodos)`: Маршрут для получения всех задач.
- `todoRoutes.GET("/:id", controllers.GetTodoByID)`: Маршрут для получения задачи по её ID.
- `todoRoutes.PATCH("/:id", controllers.UpdateTodo)`: Маршрут для обновления задачи по её ID.
- `todoRoutes.DELETE("/:id", controllers.DeleteTodo)`: Маршрут для удаления задачи по её ID.
- ## services/
- ### todo_service.go
- сервисы для работы с задачами (Todo), используя репозитории для выполнения операций с базой данных. Сервисы предоставляют более высокий уровень абстракции для логики бизнес-процессов, инкапсулируя детали взаимодействия с репозиториями.
1. **CreateTodoService**:
- Создает новую задачу.
- Вызывает функцию репозитория `CreateTodoRepository` для добавления новой задачи в базу данных.
- Возвращает возможную ошибку.
2. **GetTodosService**:
- Получает все задачи из базы данных.
- Вызывает функцию репозитория `GetTodosRepository` для получения всех задач.
- Возвращает список задач и возможную ошибку.
3. **GetTodoByIDService**:
- Получает задачу по её идентификатору (ID).
- Вызывает функцию репозитория `GetTodoByIDRepository` для получения задачи по ID.
- Возвращает найденную задачу и возможную ошибку.
4. **UpdateTodoService**:
- Обновляет информацию о задаче.
- Сначала получает существующую задачу по её ID с помощью `GetTodoByIDRepository`.
- Если задача найдена, обновляет её поля (заголовок и статус выполнения) значениями из переданного объекта `todo`.
- Вызывает функцию репозитория `UpdateTodoRepository` для сохранения обновлённой задачи в базе данных.
- Возвращает обновлённую задачу и возможную ошибку.
5. **DeleteTodoService**:
- Удаляет задачу из базы данных по её идентификатору (ID).
- Вызывает функцию репозитория `DeleteTodoRepository` для удаления задачи.
- Возвращает возможную ошибку.
- ### user_service.go
- сервисы для работы с пользователями, используя репозитории и утилиты для выполнения операций. Сервисы предоставляют более высокий уровень абстракции для логики бизнес-процессов.
1. **RegisterService**:
- Регистрирует нового пользователя.
- Хэширует пароль пользователя с помощью `bcrypt`.
- Обновляет пароль пользователя на хэшированный.
- Вызывает функцию репозитория `CreateUserRepository` для добавления нового пользователя в базу данных.
- Возвращает возможную ошибку.
2. **LoginService**:
- Выполняет вход пользователя.
- Ищет пользователя по его имени с помощью `GetUserByUsername`.
- Если пользователь не найден, возвращает ошибку "user not found".
- Сравнивает хэшированный пароль пользователя с введенным паролем с помощью `bcrypt.CompareHashAndPassword`.
- Если пароли не совпадают, возвращает ошибку "invalid credentials".
- Генерирует токен для пользователя с помощью `utils.GenerateToken`.
- Обновляет пользователя с новым токеном в базе данных с помощью `UpdateUserRepository`.
- Возвращает токен и возможную ошибку.
3. **GetUsersService**:
- Получает всех пользователей из базы данных.
- Вызывает функцию репозитория `GetUsersRepository` для получения всех пользователей.
- Возвращает список пользователей и возможную ошибку.
4. **GetUserByIDService**:
- Получает пользователя по его идентификатору (ID).
- Вызывает функцию репозитория `GetUserByIDRepository` для получения пользователя по ID.
- Возвращает найденного пользователя и возможную ошибку.
5. **UpdateUserService**:
- Обновляет информацию о пользователе.
- Получает существующего пользователя по его ID с помощью `GetUserByIDRepository`.
- Хэширует пароль пользователя с помощью `bcrypt`.
- Обновляет поля пользователя (имя и пароль) значениями из переданного объекта `user`.
- Вызывает функцию репозитория `UpdateUserRepository` для сохранения обновленного пользователя в базе данных.
- Возвращает обновленного пользователя и возможную ошибку.
6. **DeleteUserService**:
- Удаляет пользователя из базы данных по его идентификатору (ID).
- Вызывает функцию репозитория `DeleteUserRepository` для удаления пользователя.
- Возвращает возможную ошибку.
- ## utils/
- ### todo_response_utils.go
- утилитная функция для создания ответа на основе списка задач (Todo).
**Функция `CreateTodoResponse`**:
- Принимает срез задач (`todos []domain.Todo`) в качестве входного параметра.
- Создаёт пустой срез `todosModel` типа `[]domain.TodoResponse` для хранения преобразованных задач.
- Использует функцию `lo.ForEach` для итерации по каждой задаче в списке `todos`.
- Для каждой задачи создаёт объект `domain.TodoResponse`, копируя значения полей `Title` и `Completed` из оригинальной задачи.
- Добавляет созданный объект `domain.TodoResponse` в `todosModel`.
- Возвращает срез `todosModel`, содержащий преобразованные задачи.
- ### token_utils.go
- утилиты для работы с JSON Web Tokens (JWT). Основные функции включают генерацию и валидацию JWT.
1. **Функция GenerateToken**:
- Принимает идентификатор пользователя (`userID`) в качестве параметра.
- Устанавливает время истечения токена на 24 часа с текущего момента (`expirationTime`).
- Создаёт объект `Claims` с `UserID` и `ExpiresAt`.
- Создаёт новый JWT с помощью метода `jwt.NewWithClaims`, используя алгоритм HMAC SHA256 (`jwt.SigningMethodHS256`) и claims.
- Подписывает токен с использованием `jwtKey` и возвращает подписанную строку токена или ошибку.
2. **Функция ValidateToken**:
- Принимает строку токена (`tokenString`) в качестве параметра.
- Инициализирует объект `Claims`.
- Использует метод `jwt.ParseWithClaims` для парсинга и валидации токена, передавая функцию, которая возвращает секретный ключ (`jwtKey`).
- Если при парсинге произошла ошибка или токен недействителен (`!token.Valid`), возвращает соответствующую ошибку.
- Если токен валиден, возвращает claims и `nil` в качестве ошибки.
- ### user_response_utils.go
- утилитная функция для создания ответа на основе списка пользователей, включающего их задачи (Todos).
1. **Функция CreateUserResponse**:
- Принимает срез пользователей (`users []domain.User`) в качестве входного параметра.
- Инициализирует пустые срезы `usersModel` типа `[]domain.UserResponse` для хранения преобразованных пользователей и `todosModel` типа `[]domain.TodoResponse` для хранения преобразованных задач.
- Использует функцию `lo.ForEach` для итерации по каждому пользователю в списке `users`.
- Внутри первой итерации использует ещё одну итерацию `lo.ForEach` для каждого задания (Todo) пользователя.
- Для каждой задачи создаёт объект `domain.TodoResponse`, копируя значения полей `Title` и `Completed` из оригинальной задачи, и добавляет его в `todosModel`.
- После итерации по задачам пользователя, создаёт объект `domain.UserResponse`, копируя значения поля `Username` из оригинального пользователя и добавляя список задач `todosModel`.
- Добавляет созданный объект `domain.UserResponse` в `usersModel`.
- Возвращает срез `usersModel`, содержащий преобразованных пользователей и их задачи.
## Endpoints
### Todos
- **POST /todos/** - Создание нового Todo. Требуется JSON с данными Todo. Требуется токен пользователя.
- **GET /todos/** - Получение списка всех Todos. Требуется токен пользователя.
- **GET /todos/:id** - Получение информации о конкретном Todo. Требуется токен пользователя.
- **PATCH /todos/:id** - Изменение Todo по идентификатору. Требуется JSON с данными Todo. Требуется токен пользователя.
- **DELETE /todos/:id** - Удаление Todo по идентификатору. Требуется токен пользователя.
Пример JSON для создания Todo
``` json
{
"title": "newtitle",
"completed": false,
"title": 1,
}
```
### Пользователи
- **POST /register** - Регистрация нового пользователя. Требуется JSON с данными пользователя.
- **POST /login** - Вход пользователя. Требуется JSON с данными для входа.
- **GET /user/profile** - Получение данных текущего пользователя. Требуется токен пользователя.
- **GET /users** - Получение списка всех пользователей.
- **GET /users/:id** - Получение пользователя.
- **PATCH /user/:id** - Редактирование имени и/или пароля пользователя. Требуется JSON с новым именем и/или паролем. Требуется токен пользователя.
- **DELETE /user/:id** - Удаление пользователя. Требуется JSON с суммой. Требуется токен пользователя.
## Запуск сервера
- При запуске впервый раз:
``` go run database/migration.go ```
- Затем можно запускать контейнер вместе с БД Postgresql командой:
``` docker-compose up -d ```
- Затем запуск самого сервера
``` go run cmd/main.go ```

9
bruno/bruno.json Normal file
View File

@ -0,0 +1,9 @@
{
"version": "1",
"name": "bruno",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}

11
bruno/dd.bru Normal file
View File

@ -0,0 +1,11 @@
meta {
name: dd
type: http
seq: 2
}
post {
url:
body: none
auth: none
}

18
cmd/main.go Normal file
View File

@ -0,0 +1,18 @@
package main
import (
"github.com/gin-gonic/gin"
"log"
"todolist/config"
"todolist/routes"
)
func main() {
r := gin.Default()
config.InitDatabase()
routes.RegisterRoutes(r)
err := r.Run(":8081")
if err != nil {
log.Fatalf("failed to start server: %v", err)
}
}

26
config/config.go Normal file
View File

@ -0,0 +1,26 @@
package config
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
"log"
)
var db *gorm.DB
func InitDatabase() {
dsn := "host=localhost user=postgres password=example dbname=database port=5432 sslmode=disable TimeZone=Asia/Yekaterinburg"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
}
func GetDB() *gorm.DB {
if db == nil {
// если база данных не инициализирована
log.Fatalf("database connection is not initialized")
}
return db
}

View File

@ -0,0 +1,84 @@
package controllers
import (
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"net/http"
"strconv"
"todolist/domain"
"todolist/services"
"todolist/utils"
)
var validate = validator.New()
func CreateTodo(c *gin.Context) {
var todo domain.Todo
if err := c.ShouldBindJSON(&todo); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
if err := validate.Struct(todo); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
if err := services.CreateTodoService(&todo); err != nil {
c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})
return
}
c.JSON(http.StatusCreated, utils.CreateTodoResponse([]domain.Todo{todo}))
}
func GetTodos(c *gin.Context) {
todos, err := services.GetTodosService()
if err != nil {
c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})
return
}
c.JSON(http.StatusOK, utils.CreateTodoResponse(todos))
}
func GetTodoByID(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
todo, err := services.GetTodoByIDService(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "Todo not found"})
return
}
c.JSON(http.StatusOK, utils.CreateTodoResponse([]domain.Todo{*todo}))
}
func UpdateTodo(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var todo domain.Todo
if err := c.ShouldBindJSON(&todo); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
if err := validate.Struct(todo); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
updatedTodo, err := services.UpdateTodoService(uint(id), &todo)
if err != nil {
c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})
return
}
c.JSON(http.StatusOK, utils.CreateTodoResponse([]domain.Todo{*updatedTodo}))
}
func DeleteTodo(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
err := services.DeleteTodoService(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "Todo not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Todo deleted successfully"})
}

View File

@ -0,0 +1,109 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"todolist/domain"
"todolist/services"
"todolist/utils"
)
func Register(c *gin.Context) {
var user domain.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
if err := validate.Struct(user); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
if err := services.RegisterService(&user); err != nil {
c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})
return
}
c.JSON(http.StatusCreated, domain.Response{Message: "User created successfully!"})
}
func Login(c *gin.Context) {
var user domain.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
token, err := services.LoginService(user.Username, user.Password)
if err != nil {
c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"token": token})
}
func GetUserProfile(c *gin.Context) {
user, exists := c.Get("user")
if !exists {
c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "User not found"})
return
}
userModel, _ := user.(domain.User)
c.JSON(http.StatusOK, utils.CreateUserResponse([]domain.User{userModel}))
}
func GetUsers(c *gin.Context) {
users, err := services.GetUsersService()
if err != nil {
c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})
return
}
c.JSON(http.StatusOK, utils.CreateUserResponse(users))
}
func GetUserByID(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
user, err := services.GetUserByIDService(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "User not found"})
return
}
c.JSON(http.StatusOK, user)
}
func UpdateUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var user domain.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
if err := validate.Struct(user); err != nil {
c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})
return
}
updatedUser, err := services.UpdateUserService(uint(id), &user)
if err != nil {
c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})
return
}
c.JSON(http.StatusOK, utils.CreateUserResponse([]domain.User{*updatedUser}))
}
func DeleteUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
err := services.DeleteUserService(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "User not found"})
return
}
c.JSON(http.StatusOK, domain.Response{Message: "User deleted successfully!"})
}

20
database/migration.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"log"
"todolist/config"
"todolist/domain"
)
func main() {
// Инициализация базы данных
config.InitDatabase()
db := config.GetDB()
// Миграции таблиц
err := db.AutoMigrate(&domain.User{}, &domain.Todo{})
if err != nil {
log.Fatalf("failed to migrate database: %v", err)
}
}

28
docker-compose.yml Normal file
View File

@ -0,0 +1,28 @@
# Use postgres/example user/password credentials
version: '3.9'
services:
db:
image: postgres
restart: always
# set shared memory limit when using docker-compose
shm_size: 128mb
# or set shared memory limit when deploy via swarm stack
#volumes:
# - type: tmpfs
# target: /dev/shm
# tmpfs:
# size: 134217728 # 128*2^20 bytes = 128Mb
environment:
POSTGRES_PASSWORD: example
POSTGRES_DB: database
POSTGRES_USER: postgres
ports:
- "127.0.0.1:5432:5432"
adminer:
image: adminer
restart: always
ports:
- 8000:8080

5
domain/error_response.go Normal file
View File

@ -0,0 +1,5 @@
package domain
type ErrorResponse struct {
Message string `json:"message"`
}

View File

@ -0,0 +1,5 @@
package domain
type Response struct {
Message string `json:"message"`
}

12
domain/todo.go Normal file
View File

@ -0,0 +1,12 @@
package domain
import (
"gorm.io/gorm"
)
type Todo struct {
gorm.Model
Title string `json:"title" validate:"required"`
Completed bool `json:"completed"`
UserID uint `json:"user_id" gorm:"foreignKey:UserID;references:ID:"`
}

6
domain/todoResponse.go Normal file
View File

@ -0,0 +1,6 @@
package domain
type TodoResponse struct {
Title string `json:"title"`
Completed bool `json:"completed"`
}

13
domain/user.go Normal file
View File

@ -0,0 +1,13 @@
package domain
import (
"gorm.io/gorm"
)
type User struct {
gorm.Model
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Token string `json:"token"`
Todos []Todo `json:"todos"`
}

6
domain/userResponse.go Normal file
View File

@ -0,0 +1,6 @@
package domain
type UserResponse struct {
Username any `json:"username"`
Todos []TodoResponse `json:"todos"`
}

48
go.mod Normal file
View File

@ -0,0 +1,48 @@
module todolist
go 1.22
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/validator/v10 v10.20.0
github.com/samber/lo v1.39.0
golang.org/x/crypto v0.23.0
gorm.io/driver/postgres v1.5.7
gorm.io/gorm v1.25.10
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

117
go.sum Normal file
View File

@ -0,0 +1,117 @@
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
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/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -0,0 +1,38 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
"todolist/config"
"todolist/domain"
"todolist/utils"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Authorization token required"})
c.Abort()
return
}
claims, err := utils.ValidateToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: err.Error()})
c.Abort()
return
}
var user domain.User
if err := config.GetDB().Preload("Todos").Where("id = ?", claims.UserID).First(&user).Error; err != nil {
//c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Invalid token"})
c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: err.Error()})
c.Abort()
return
}
c.Set("user", user)
c.Next()
}
}

View File

@ -0,0 +1,40 @@
package repositories
import (
"todolist/config"
"todolist/domain"
)
// CreateTodoRepository создает новую задачу в базе данных.
func CreateTodoRepository(todo *domain.Todo) error {
db := config.GetDB()
return db.Create(todo).Error
}
// GetTodosRepository возвращает все задачи из базы данных.
func GetTodosRepository() ([]domain.Todo, error) {
var todos []domain.Todo
db := config.GetDB()
err := db.Find(&todos).Error
return todos, err
}
// GetTodoByIDRepository возвращает задачу по ее ID.
func GetTodoByIDRepository(id uint) (*domain.Todo, error) {
var todo domain.Todo
db := config.GetDB()
err := db.First(&todo, id).Error
return &todo, err
}
// UpdateTodoRepository обновляет информацию о задаче в базе данных.
func UpdateTodoRepository(todo *domain.Todo) error {
db := config.GetDB()
return db.Save(todo).Error
}
// DeleteTodoRepository удаляет задачу из базы данных.
func DeleteTodoRepository(id uint) error {
db := config.GetDB()
return db.Delete(&domain.Todo{}, id).Error
}

View File

@ -0,0 +1,48 @@
package repositories
import (
"todolist/config"
"todolist/domain"
)
// CreateUserRepository создает нового пользователя в базе данных.
func CreateUserRepository(user *domain.User) error {
db := config.GetDB()
return db.Create(user).Error
}
// GetUsersRepository возвращает всех пользователей из базы данных.
func GetUsersRepository() ([]domain.User, error) {
var users []domain.User
db := config.GetDB()
err := db.Preload("Todos").Find(&users).Error
return users, err
}
// GetUserByUsername возвращает пользователя по его имени.
func GetUserByUsername(username string) (*domain.User, error) {
var user domain.User
db := config.GetDB()
err := db.Where("username = ?", username).First(&user).Error
return &user, err
}
// GetUserByIDRepository возвращает пользователя по его ID.
func GetUserByIDRepository(userID uint) (*domain.User, error) {
var user domain.User
db := config.GetDB()
err := db.Preload("Todos").First(&user, userID).Error
return &user, err
}
// UpdateUserRepository обновляет информацию о пользователе в базе данных.
func UpdateUserRepository(user *domain.User) error {
db := config.GetDB()
return db.Save(user).Error
}
// DeleteUserRepository удаляет пользователя из базы данных.
func DeleteUserRepository(id uint) error {
db := config.GetDB()
return db.Delete(&domain.User{}, id).Error
}

35
routes/routes.go Normal file
View File

@ -0,0 +1,35 @@
package routes
import (
"github.com/gin-gonic/gin"
"todolist/controllers"
"todolist/middleware"
)
func RegisterRoutes(r *gin.Engine) {
// User routes
r.POST("/register", controllers.Register)
r.POST("/login", controllers.Login)
r.GET("/users/:id", controllers.GetUserByID)
r.GET("/users", controllers.GetUsers)
userRoutes := r.Group("/user")
userRoutes.Use(middleware.AuthMiddleware())
{
userRoutes.GET("/profile", controllers.GetUserProfile)
userRoutes.PATCH("/:id", controllers.UpdateUser)
userRoutes.DELETE("/:id", controllers.DeleteUser)
}
// 1todo routes
todoRoutes := r.Group("/todos")
todoRoutes.Use(middleware.AuthMiddleware())
{
todoRoutes.POST("/", controllers.CreateTodo)
todoRoutes.GET("/", controllers.GetTodos)
todoRoutes.GET("/:id", controllers.GetTodoByID)
todoRoutes.PATCH("/:id", controllers.UpdateTodo)
todoRoutes.DELETE("/:id", controllers.DeleteTodo)
// Add other routes such as PUT /:id, DELETE /:id, and GET /:id
}
}

43
services/todo_service.go Normal file
View File

@ -0,0 +1,43 @@
package services
import (
"todolist/domain"
"todolist/repositories"
)
// CreateTodoService создает новую задачу.
func CreateTodoService(todo *domain.Todo) error {
return repositories.CreateTodoRepository(todo)
}
// GetTodosService получает все задачи из базы данных.
func GetTodosService() ([]domain.Todo, error) {
return repositories.GetTodosRepository()
}
// GetTodoByIDService получает задачу по ее ID.
func GetTodoByIDService(id uint) (*domain.Todo, error) {
return repositories.GetTodoByIDRepository(id)
}
// UpdateTodoService обновляет информацию о задаче.
func UpdateTodoService(id uint, todo *domain.Todo) (*domain.Todo, error) {
existingTodo, err := repositories.GetTodoByIDRepository(id)
if err != nil {
return nil, err
}
existingTodo.Title = todo.Title
existingTodo.Completed = todo.Completed
if err := repositories.UpdateTodoRepository(existingTodo); err != nil {
return nil, err
}
return existingTodo, nil
}
// DeleteTodoService удаляет задачу из базы данных.
func DeleteTodoService(id uint) error {
return repositories.DeleteTodoRepository(id)
}

73
services/user_service.go Normal file
View File

@ -0,0 +1,73 @@
package services
import (
"errors"
"golang.org/x/crypto/bcrypt"
"todolist/domain"
"todolist/repositories"
"todolist/utils"
)
func RegisterService(user *domain.User) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
user.Password = string(hashedPassword)
return repositories.CreateUserRepository(user)
}
func LoginService(username, password string) (string, error) {
user, err := repositories.GetUserByUsername(username)
if err != nil {
return "", errors.New("user not found")
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
return "", errors.New("invalid credentials")
}
token, err := utils.GenerateToken(user.ID)
if err != nil {
return "", err
}
user.Token = token
if err := repositories.UpdateUserRepository(user); err != nil {
return "", err
}
return token, nil
}
func GetUsersService() ([]domain.User, error) {
return repositories.GetUsersRepository()
}
func GetUserByIDService(id uint) (*domain.User, error) {
return repositories.GetUserByIDRepository(id)
}
func UpdateUserService(id uint, user *domain.User) (*domain.User, error) {
existingUser, err := repositories.GetUserByIDRepository(id)
if err != nil {
return nil, err
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(existingUser.Password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
existingUser.Username = user.Username
existingUser.Password = string(hashedPassword)
if err := repositories.UpdateUserRepository(existingUser); err != nil {
return nil, err
}
return existingUser, nil
}
func DeleteUserService(id uint) error {
return repositories.DeleteUserRepository(id)
}

View File

@ -0,0 +1,18 @@
package utils
import (
"github.com/samber/lo"
"todolist/domain"
)
func CreateTodoResponse(todos []domain.Todo) []domain.TodoResponse {
var todosModel []domain.TodoResponse
lo.ForEach(todos, func(todo domain.Todo, _ int) {
todosModel = append(todosModel, domain.TodoResponse{
Title: todo.Title,
Completed: todo.Completed,
})
})
return todosModel
}

45
utils/token_utils.go Normal file
View File

@ -0,0 +1,45 @@
package utils
import (
"errors"
"github.com/dgrijalva/jwt-go"
"time"
)
var jwtKey = []byte("secret_key")
type Claims struct {
UserID uint `json:"user_id"`
jwt.StandardClaims
}
func GenerateToken(userID uint) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &Claims{
UserID: userID,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}
func ValidateToken(tokenString string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
return nil, err
}
if !token.Valid {
return nil, errors.New("invalid token")
}
return claims, nil
}

View File

@ -0,0 +1,24 @@
package utils
import (
"github.com/samber/lo"
"todolist/domain"
)
func CreateUserResponse(users []domain.User) []domain.UserResponse {
var usersModel []domain.UserResponse
var todosModel []domain.TodoResponse
lo.ForEach(users, func(user domain.User, _ int) {
lo.ForEach(user.Todos, func(todo domain.Todo, _ int) {
todosModel = append(todosModel, domain.TodoResponse{
Title: todo.Title,
Completed: todo.Completed,
})
})
usersModel = append(usersModel, domain.UserResponse{
Username: user.Username,
Todos: todosModel,
})
})
return usersModel
}