feature/task-1 #3
35
Dockerfile
35
Dockerfile
@ -1,29 +1,22 @@
|
|||||||
# syntax=docker/dockerfile:1.5
|
# Этап сборки
|
||||||
|
FROM golang:1.21-alpine AS builder
|
||||||
|
|
||||||
# Build stage
|
# Устанавливаем рабочую директорию
|
||||||
ARG GO_VERSION=1.24
|
|
||||||
FROM golang:${GO_VERSION} AS builder
|
|
||||||
|
|
||||||
# Set the working directory
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Cache dependencies and build the application
|
# Копируем файлы проекта
|
||||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
COPY go.mod go.sum ./
|
||||||
--mount=type=cache,target=/root/.cache/go-build \
|
RUN go mod download
|
||||||
--mount=type=bind,target=. \
|
|
||||||
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /task-manager ./cmd/main.go
|
|
||||||
|
|
||||||
# Final stage
|
COPY . ./
|
||||||
|
|
||||||
|
# Сборка бинарного файла
|
||||||
|
RUN go build -o /task_manager ./cmd/task_manager
|
||||||
|
|
||||||
|
# Финальный минимальный образ
|
||||||
FROM scratch AS final
|
FROM scratch AS final
|
||||||
|
|
||||||
# Set the working directory
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
COPY --from=builder /task_manager .
|
||||||
# Copy the built binary from the builder stage
|
|
||||||
COPY --from=builder /task-manager ./
|
|
||||||
|
|
||||||
# Expose the application port
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
CMD ["./task_manager"]
|
||||||
# Set the default command to run the application
|
|
||||||
CMD ["./task-manager"]
|
|
11
compose.yaml
11
compose.yaml
@ -16,4 +16,15 @@ services:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
volumes:
|
volumes:
|
||||||
- ./database/init:/docker-entrypoint-initdb.d
|
- ./database/init:/docker-entrypoint-initdb.d
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
swagger-ui:
|
||||||
|
image: swaggerapi/swagger-ui:latest
|
||||||
|
container_name: swagger-ui
|
||||||
|
ports:
|
||||||
|
- "8081:8080"
|
||||||
|
volumes:
|
||||||
|
- ./config/swagger.yaml:/tmp/swagger.yaml:ro
|
||||||
|
environment:
|
||||||
|
- SWAGGER_JSON=/tmp/swagger.yaml
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
254
config/swagger.yaml
Normal file
254
config/swagger.yaml
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
openapi: "3.0.0"
|
||||||
|
info:
|
||||||
|
title: "Task Manager API"
|
||||||
|
description: "API для управления задачами, проектами и пользователями"
|
||||||
|
version: "1.0.0"
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: "http://localhost:8080/api/v1"
|
||||||
|
description: "Локальный сервер для тестирования API"
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/user/login:
|
||||||
|
post:
|
||||||
|
summary: "Вход пользователя в систему"
|
||||||
|
description: "Авторизация пользователя по имени и паролю"
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: "johndoe"
|
||||||
|
description: "Имя пользователя"
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
example: "12345"
|
||||||
|
description: "Пароль пользователя"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Авторизация успешна"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
"401":
|
||||||
|
description: "Недействительный логин или пароль"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ErrorResponse"
|
||||||
|
/user/register:
|
||||||
|
post:
|
||||||
|
summary: "Регистрация нового пользователя"
|
||||||
|
description: "Создание нового аккаунта пользователя"
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/User"
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: "Пользователь успешно зарегистрирован"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/User"
|
||||||
|
"400":
|
||||||
|
description: "Ошибка в запросе"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ErrorResponse"
|
||||||
|
/user/tasks:
|
||||||
|
get:
|
||||||
|
summary: "Получить задачи пользователя"
|
||||||
|
description: "Возвращает список задач для указанного пользователя"
|
||||||
|
parameters:
|
||||||
|
- name: "id_user"
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
example: 1
|
||||||
|
description: "ID пользователя"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Список задач пользователя"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Task"
|
||||||
|
"400":
|
||||||
|
description: "Отсутствует ID пользователя"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ErrorResponse"
|
||||||
|
/user/projects:
|
||||||
|
get:
|
||||||
|
summary: "Получить проекты пользователя"
|
||||||
|
description: "Возвращает список проектов, связанных с пользователем"
|
||||||
|
parameters:
|
||||||
|
- name: "id_user"
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
example: 1
|
||||||
|
description: "ID пользователя"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Список проектов"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: "Отсутствует ID пользователя"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ErrorResponse"
|
||||||
|
/projects:
|
||||||
|
post:
|
||||||
|
summary: "Создать проект"
|
||||||
|
description: "Создание нового проекта (в разработке)"
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: "Не реализовано"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UnimplementedResponse"
|
||||||
|
get:
|
||||||
|
summary: "Получить проекты"
|
||||||
|
description: "Получение списка всех проектов (в разработке)"
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: "Не реализовано"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UnimplementedResponse"
|
||||||
|
/projects/tasks:
|
||||||
|
post:
|
||||||
|
summary: "Создать задачу"
|
||||||
|
description: "Создание новой задачи для проекта"
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Task"
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: "Задача успешно создана"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Task"
|
||||||
|
"400":
|
||||||
|
description: "Ошибка в запросе"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ErrorResponse"
|
||||||
|
/projects/tasks/{task_id}:
|
||||||
|
patch:
|
||||||
|
summary: "Обновить задачу"
|
||||||
|
description: "Изменение данных задачи по ID"
|
||||||
|
parameters:
|
||||||
|
- name: "task_id"
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
example: 42
|
||||||
|
description: "ID задачи"
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Task"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Задача успешно обновлена"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Task"
|
||||||
|
"400":
|
||||||
|
description: "Ошибка в запросе"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ErrorResponse"
|
||||||
|
/projects/sprints:
|
||||||
|
post:
|
||||||
|
summary: "Создать спринт"
|
||||||
|
description: "Создание спринта в проекте (в разработке)"
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: "Не реализовано"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UnimplementedResponse"
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
ErrorResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: "HTTP-код ошибки"
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
description: "Сообщение об ошибке"
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
description: "Машинное описание ошибки"
|
||||||
|
UnimplementedResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "Not implemented"
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
Task:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
@ -1,7 +1,13 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"task_manager/internal/domain/tasks"
|
||||||
|
"task_manager/internal/domain/users"
|
||||||
|
"task_manager/internal/persistance"
|
||||||
|
|
||||||
gorilla "github.com/gorilla/mux"
|
gorilla "github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
@ -35,16 +41,174 @@ func (rm *TaskManager) RegisterRoutes(router *gorilla.Router) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rm *TaskManager) handleLogin(w http.ResponseWriter, r *http.Request) {}
|
func (rm *TaskManager) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
func (rm *TaskManager) handleRegistrateUser(w http.ResponseWriter, r *http.Request) {}
|
var input struct {
|
||||||
func (rm *TaskManager) handleUserTasks(w http.ResponseWriter, r *http.Request) {}
|
Name string `json:"name"`
|
||||||
func (rm *TaskManager) handleUserProjects(w http.ResponseWriter, r *http.Request) {}
|
Password string `json:"password"`
|
||||||
func (rm *TaskManager) handleAssignUserToProject(w http.ResponseWriter, r *http.Request) {}
|
}
|
||||||
|
|
||||||
func (rm *TaskManager) handleCreateProject(w http.ResponseWriter, r *http.Request) {}
|
err := json.NewDecoder(r.Body).Decode(&input)
|
||||||
func (rm *TaskManager) handleCreateTask(w http.ResponseWriter, r *http.Request) {}
|
if err != nil {
|
||||||
func (rm *TaskManager) handleCreateSprint(w http.ResponseWriter, r *http.Request) {}
|
respondWithError(w, http.StatusBadRequest, "Invalid request payload", err)
|
||||||
func (rm *TaskManager) handleGetProjectUsers(w http.ResponseWriter, r *http.Request) {}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (rm *TaskManager) handleUpdateTask(w http.ResponseWriter, r *http.Request) {}
|
user, err := rm.repo.GetUser(input.Name)
|
||||||
func (rm *TaskManager) handleGetProjects(w http.ResponseWriter, r *http.Request) {}
|
if err != nil || user.Password != persistance.GetMD5Hash(input.Password) {
|
||||||
|
respondWithError(w, http.StatusUnauthorized, "Invalid username or password", errors.New("authentication failed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondWithJSON(w, http.StatusOK, user)
|
||||||
|
}
|
||||||
|
func (rm *TaskManager) handleRegistrateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var user users.User
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Invalid request payload", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rm.repo.AddUser(&user)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusInternalServerError, "Failed to create user", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondWithJSON(w, http.StatusCreated, user)
|
||||||
|
}
|
||||||
|
func (rm *TaskManager) handleUserTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID := r.URL.Query().Get("id_user")
|
||||||
|
if userID == "" {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Missing user ID", errors.New("id_user parameter required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(userID)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Invalid user ID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks, err := rm.repo.GetUserTasks(id)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusInternalServerError, "Failed to fetch user tasks", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondWithJSON(w, http.StatusOK, tasks)
|
||||||
|
}
|
||||||
|
func (rm *TaskManager) handleUserProjects(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID := r.URL.Query().Get("id_user")
|
||||||
|
if userID == "" {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Missing user ID", errors.New("id_user parameter required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := strconv.Atoi(userID)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Invalid user ID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Заглушка: здесь предполагается метод получения проектов пользователя
|
||||||
|
// Для реализации необходимо будет добавить соответствующий метод в репозиторий
|
||||||
|
projects := []string{"Project 1", "Project 2"} // Пример данных
|
||||||
|
|
||||||
|
respondWithJSON(w, http.StatusOK, projects)
|
||||||
|
}
|
||||||
|
func (rm *TaskManager) handleAssignUserToProject(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var input struct {
|
||||||
|
IdUser int `json:"id_user"`
|
||||||
|
IdProject int `json:"id_project"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&input)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Invalid request payload", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Заглушка: реализация привязки пользователя к проекту
|
||||||
|
// Для добавления необходимо доработать метод в репозитории
|
||||||
|
|
||||||
|
respondWithJSON(w, http.StatusOK, map[string]string{
|
||||||
|
"message": "User assigned to project successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *TaskManager) handleCreateProject(w http.ResponseWriter, r *http.Request) {
|
||||||
|
respondWithJSON(w, http.StatusNotImplemented, map[string]string{
|
||||||
|
"message": "Create project not implemented yet",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (rm *TaskManager) handleCreateTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var task tasks.Task
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&task)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Invalid request payload", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rm.repo.AddTask(task)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusInternalServerError, "Failed to create task", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondWithJSON(w, http.StatusCreated, task)
|
||||||
|
}
|
||||||
|
func (rm *TaskManager) handleCreateSprint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
taskID := gorilla.Vars(r)["task_id"]
|
||||||
|
id, err := strconv.Atoi(taskID)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Invalid task ID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var task tasks.Task
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&task)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusBadRequest, "Invalid request payload", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Id = id
|
||||||
|
err = rm.repo.UpdateTask(task)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, http.StatusInternalServerError, "Failed to update task", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondWithJSON(w, http.StatusOK, task)
|
||||||
|
}
|
||||||
|
func (rm *TaskManager) handleGetProjectUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
respondWithJSON(w, http.StatusNotImplemented, map[string]string{
|
||||||
|
"message": "Get projects not implemented yet",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *TaskManager) handleUpdateTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
respondWithJSON(w, http.StatusNotImplemented, map[string]string{
|
||||||
|
"message": "Create sprint not implemented yet",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (rm *TaskManager) handleGetProjects(w http.ResponseWriter, r *http.Request) {
|
||||||
|
respondWithJSON(w, http.StatusNotImplemented, map[string]string{
|
||||||
|
"message": "Get project users not implemented yet",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func respondWithError(w http.ResponseWriter, code int, message string, err error) {
|
||||||
|
w.WriteHeader(code)
|
||||||
|
json.NewEncoder(w).Encode(ErrorResponse{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
|
||||||
|
w.WriteHeader(code)
|
||||||
|
json.NewEncoder(w).Encode(payload)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user