Compare commits

...

3 Commits

Author SHA1 Message Date
3ecffa7779 Save state 2025-03-21 21:47:32 +03:00
Ivan Titov
788c02761d save 2025-03-21 16:05:06 +03:00
Ivan Titov
102413daaf save 2025-03-21 10:33:45 +03:00
22 changed files with 406 additions and 56 deletions

59
.dockerignore Normal file
View File

@ -0,0 +1,59 @@
# .dockerignore file for the project
# Language-specific patterns
/vendor/
*.test
.go-cache
# Development artifacts
.idea/
.vscode/
*.swp
*.swo
dist/
build/
out/
test/
tests/
*_test.go
debug/
*.log
# Version control
.git/
.gitignore
# Environment and secrets
.env*
*.env
*.pem
*.key
*.crt
config.local.*
*.local.yml
# Project-specific patterns
docs/
*.md
README*
Dockerfile*
docker-compose*
tmp/
temp/
*.tmp
.local/
local/
# Exclude the following files from being ignored
!go.mod
!go.sum
!cmd/main.go
!internal/config/config.go
!internal/domain/tasks/task.go
!internal/domain/users/user.go
!internal/persistance/task_manager.go
!internal/persistance/task_repository.go
!internal/persistance/user_repository.go
!database/init/00-users-init.sql
!database/init/01-tasks-init.sql
!deployments/task-manager.yaml

29
Dockerfile Normal file
View File

@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1.5
# Build stage
ARG GO_VERSION=1.24
FROM golang:${GO_VERSION} AS builder
# Set the working directory
WORKDIR /app
# Cache dependencies and build the application
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=bind,target=. \
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /task-manager ./cmd/main.go
# Final stage
FROM scratch AS final
# Set the working directory
WORKDIR /app
# Copy the built binary from the builder stage
COPY --from=builder /task-manager ./
# Expose the application port
EXPOSE 8080
# Set the default command to run the application
CMD ["./task-manager"]

35
README.md Normal file
View File

@ -0,0 +1,35 @@
# Running the Project with Docker
This section provides instructions to build and run the project using Docker.
## Requirements
- Docker version 20.10 or later
- Docker Compose version 1.29 or later
## Environment Variables
- `POSTGRES_USER`: Database username (default: `user`)
- `POSTGRES_PASSWORD`: Database password (default: `password`)
- `POSTGRES_DB`: Database name (default: `appdb`)
## Build and Run Instructions
1. Clone the repository and navigate to the project root directory.
2. Build and start the services using Docker Compose:
```bash
docker-compose up --build
```
3. Access the application at `http://localhost:8080`.
## Configuration
- The application binary is built using Go version 1.24.
- The database service uses the `postgres:latest` image.
## Exposed Ports
- Application: `8080` (mapped to `8080` on the host)
- Database: Not exposed to the host
For further details, refer to the project documentation or contact the maintainers.

20
compose.yaml Normal file
View File

@ -0,0 +1,20 @@
services:
app:
build:
context: .
ports:
- "8080:8080"
restart: unless-stopped
depends_on:
- database
database:
image: postgres:latest
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: appdb
ports:
- "5432:5432"
volumes:
- ./database/init:/docker-entrypoint-initdb.d:ro
restart: unless-stopped

View File

@ -6,9 +6,10 @@ CREATE DATABASE tasks;
CREATE SCHEMA task_manager; CREATE SCHEMA task_manager;
CREATE TABLE task_manager.users(
id serial PRIMARY KEY NOT NULL UNIQUE,
login TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
);
CREATE TABLE task_manager.user(
"id" SERIAL PRIMARY KEY UNIQUE,
"login" TEXT NOT NULL UNIQUE,
"password" TEXT NOT NULL,
"email" TEXT NOT NULL UNIQUE
);

View File

@ -0,0 +1,18 @@
\connect tasks;
CREATE TABLE task_manager.project (
"id" SERIAL PRIMARY KEY UNIQUE,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"date" TEXT NOT NULL
);
CREATE TABLE task_manager.user_project (
"users" INTEGER NOT NULL,
"projects" INTEGER NOT NULL,
PRIMARY KEY ("users", "projects"),
FOREIGN KEY ("projects") REFERENCES task_manager.project ("id") ON DELETE CASCADE,
FOREIGN KEY ("users") REFERENCES task_manager.user ("id") ON DELETE CASCADE
);

View File

@ -1,15 +0,0 @@
-- подключаемся к базе данных
\connect tasks;
CREATE TYPE task_manager.task_status AS ENUM('todo','in_progress','paused','test','review','done');
CREATE TABLE IF NOT EXISTS task_manager.tasks(
id serial PRIMARY KEY NOT NULL UNIQUE,
id_user integer,
name TEXT NOT NULL,
description TEXT,
status task_manager.task_status NOT NULL default 'todo',
creation_date date NOT NULL default Now(),
update_date date NOT NULL default Now(),
FOREIGN KEY (id_user) REFERENCES task_manager.users (id) ON DELETE CASCADE
);

View File

@ -0,0 +1,12 @@
\connect tasks;
CREATE TABLE task_manager.sprint(
"id" SERIAL PRIMARY KEY UNIQUE,
"id_project" INTEGER NOT NULL,
"name" TEXT NOT NULL UNIQUE,
"description" TEXT NOT NULL,
"date" TEXT NOT NULL,
"duration" INTEGER,
FOREIGN KEY (id_project) REFERENCES task_manager.project(id) ON DELETE CASCADE
);

View File

@ -0,0 +1,19 @@
-- подключаемся к базе данных
\connect tasks;
CREATE TYPE task_manager.task_status AS ENUM('todo','in_progress','paused','test','review','done');
CREATE TABLE task_manager.task(
"id" SERIAL PRIMARY KEY UNIQUE,
"id_sprint" INTEGER NOT NULL,
"id_user" INTEGER,
"id_project" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"date_start" DATE DEFAULT now(),
"duration" INTEGER,
"status" task_manager.task_status DEFAULT 'todo',
FOREIGN KEY (id_user) REFERENCES task_manager.user(id) ON DELETE SET NULL,
FOREIGN KEY (id_sprint) REFERENCES task_manager.sprint(id) ON DELETE SET NULL,
FOREIGN KEY (id_project) REFERENCES task_manager.project(id) ON DELETE CASCADE
);

View File

@ -0,0 +1,58 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-deployment
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:latest
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres-service
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
selector:
app: postgres

View File

@ -1,7 +1,7 @@
version: '3' version: '3'
services: services:
tasks-postgres: tasks-postgres:
image: postgres:13 image: docker.io/postgres:13
name: tasks-postgres name: tasks-postgres
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres

4
go.mod
View File

@ -1,8 +1,8 @@
module task_manager module task_manager
go 1.24 go 1.19
require ( require (
github.com/go-chi/chi/v5 v5.2.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/lib/pq v1.10.9 // indirect github.com/lib/pq v1.10.9 // indirect
) )

4
go.sum
View File

@ -1,4 +1,4 @@
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=

50
internal/app/handlers.go Normal file
View File

@ -0,0 +1,50 @@
package app
import (
"net/http"
gorilla "github.com/gorilla/mux"
)
const (
api_version string = "/api/v1"
)
type ErrorResponse struct {
Code int `json:"code"` // Http code
Message string `json:"message"` // Обычный текст ошибки
Error string `json:"error"` // Машинный текст ошибки
}
func (rm *TaskManager) RegisterRoutes(router *gorilla.Router) {
router.HandleFunc(api_version+"/user/login", rm.handleLogin).Methods(http.MethodPost)
router.HandleFunc(api_version+"/user/register", rm.handleRegistrateUser).Methods(http.MethodPost)
router.HandleFunc(api_version+"/user/tasks", rm.handleUserTasks).Methods(http.MethodGet)
router.HandleFunc(api_version+"/user/projects", rm.handleUserProjects).Methods(http.MethodGet)
router.HandleFunc(api_version+"/user/projects", rm.handleAssignUserToProject).Methods(http.MethodPost)
router.HandleFunc(api_version+"/projects", rm.handleCreateProject).Methods(http.MethodPost)
router.HandleFunc(api_version+"/projects", rm.handleGetProjects).Methods(http.MethodGet)
router.HandleFunc(api_version+"/projects/tasks", rm.handleCreateTask).Methods(http.MethodPost)
router.HandleFunc(api_version+"/projects/tasks/{task_id}", rm.handleUpdateTask).Methods(http.MethodPatch)
router.HandleFunc(api_version+"/projects/sprints", rm.handleCreateSprint).Methods(http.MethodPost)
router.HandleFunc(api_version+"/projects/users", rm.handleGetProjectUsers).Methods(http.MethodGet)
}
func (rm *TaskManager) handleLogin(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleRegistrateUser(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleUserTasks(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleUserProjects(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleAssignUserToProject(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleCreateProject(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleCreateTask(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleCreateSprint(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleGetProjectUsers(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleUpdateTask(w http.ResponseWriter, r *http.Request) {}
func (rm *TaskManager) handleGetProjects(w http.ResponseWriter, r *http.Request) {}

View File

@ -0,0 +1,13 @@
package app
import (
"task_manager/internal/persistance"
)
type TaskManager struct {
repo *persistance.TaskManagerRepository
}
func NewTaskManager(newRepo *persistance.TaskManagerRepository) *TaskManager {
return &TaskManager{repo: newRepo}
}

View File

@ -0,0 +1,11 @@
package project
import "task_manager/internal/domain/sprint"
type Project struct {
Id int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Date string `json:"date"`
Sprints []sprint.Sprint
}

View File

@ -0,0 +1,13 @@
package sprint
import "task_manager/internal/domain/tasks"
type Sprint struct {
Id int `json:"id"`
Id_project int `json:"id_project"`
Name string `json:"name"`
Description string `json:"description"`
Date string `json:"date"`
Duration int `json:"duration"`
Tasks []tasks.Task
}

View File

@ -1,11 +1,13 @@
package tasks package tasks
type Task struct { type Task struct {
Id int `json:"id"` Id int `json:"id"`
IdUser int `json:"id_user"` IdUser *int `json:"id_user"`
Name string `json:"name"` IdSprint *int `json:"id_sprint"`
Description string `json:"desrciption"` IdProject int `json:"id_project"`
Status string `json:"status"` Title string `json:"title"`
CreationDate string `json:"creation_date"` Description *string `json:"desrciption"`
UpdateDate string `json:"update_date"` DateStart string `jsong:"date_start"`
Status string `json:"status"`
Duration *int `json:"duration"`
} }

View File

@ -1,10 +1,11 @@
package users package users
import "task_manager/internal/domain/tasks" import "task_manager/internal/domain/project"
type User struct { type User struct {
Id int `json:"id"` Id int `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Password string `json:"password"` Password string `json:"password"`
Tasks []tasks.Task Email string `json:"email"`
Projects []project.Project
} }

View File

@ -6,12 +6,12 @@ import (
"encoding/hex" "encoding/hex"
) )
type TaskManager struct { type TaskManagerRepository struct {
db *sql.DB db *sql.DB
} }
func NewTaskManager(db *sql.DB) *TaskManager { func NewTaskManager(db *sql.DB) *TaskManagerRepository {
return &TaskManager{db: db} return &TaskManagerRepository{db: db}
} }
func GetMD5Hash(text string) string { func GetMD5Hash(text string) string {

View File

@ -7,23 +7,37 @@ import (
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
func (tm *TaskManager) AddTask(task tasks.Task) error { func (tm *TaskManagerRepository) AddTask(task tasks.Task) error {
err := tm.db.QueryRow("INSERT INTO task_manager.tasks(name, description) VALUES($1,$2) RETURNING id", task.Name, task.Description).Scan(&task.Id) err := tm.db.QueryRow(`INSERT INTO task_manager.tasks(id_sprint,id_user,id_project,title,description,duration,status)
VALUES($1,$2,$3,$4,$5,$6,$7)
RETURNING id`,
task.IdSprint,
task.IdUser,
task.IdProject,
task.Title,
task.Description,
task.Duration,
task.Status).Scan(&task.Id)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (tm *TaskManager) UpdateTask(task tasks.Task) error { func (tm *TaskManagerRepository) UpdateTask(task tasks.Task) error {
_, err := tm.db.Exec("UPDATE task_manager.tasks SET name=$1, description=$2, id_user=$3, status=$4 WHERE id=$3", task.Name, task.Description, task.IdUser, task.Status) _, err := tm.db.Exec(`UPDATE task_manager.tasks
SET name=$1,
description=$2,
id_user=$3,
status=$4
WHERE id=$3`, task.Title, task.Description, task.IdUser, task.Status)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (tm *TaskManager) DeleteTask(task tasks.Task) error { func (tm *TaskManagerRepository) DeleteTask(task tasks.Task) error {
_, err := tm.db.Exec("DELETE FROM task_manager.tasks WHERE id=$1", task.Id) _, err := tm.db.Exec("DELETE FROM task_manager.tasks WHERE id=$1", task.Id)
if err != nil { if err != nil {
return err return err
@ -31,10 +45,18 @@ func (tm *TaskManager) DeleteTask(task tasks.Task) error {
return nil return nil
} }
func (tm *TaskManager) GetTask(id int) (*tasks.Task, error) { func (tm *TaskManagerRepository) GetTask(id int) (*tasks.Task, error) {
task := tasks.Task{} task := tasks.Task{}
row := tm.db.QueryRow("SELECT (id,id_user,name,description,status,creation_date,update_date) FROM task_manager.tasks WHERE id=$1", id) row := tm.db.QueryRow("SELECT (id,id_sprint,id_user,id_project,title,description,date_start,duration,status) FROM task_manager.tasks WHERE id=$1", id)
err := row.Scan(task.Id, task.IdUser, task.Name, task.Description, task.Status, task.CreationDate, task.UpdateDate) err := row.Scan(&task.Id,
&task.IdSprint,
&task.IdUser,
&task.IdProject,
&task.Title,
&task.Description,
&task.DateStart,
&task.Duration,
&task.Status)
if err != nil { if err != nil {
log.Println("Ошибка получения пользователя:", err) log.Println("Ошибка получения пользователя:", err)
return nil, err return nil, err
@ -43,7 +65,7 @@ func (tm *TaskManager) GetTask(id int) (*tasks.Task, error) {
return &task, nil return &task, nil
} }
func (tm *TaskManager) GetUserTasks(id_user int) ([]tasks.Task, error) { func (tm *TaskManagerRepository) GetUserTasks(id_user int) ([]tasks.Task, error) {
user_tasks := []tasks.Task{} user_tasks := []tasks.Task{}
rows, err := tm.db.Query("SELECT (id,id_user,name,description,status,creation_date,update_date) FROM task_manager.tasks WHERE id_user=$1", id_user) rows, err := tm.db.Query("SELECT (id,id_user,name,description,status,creation_date,update_date) FROM task_manager.tasks WHERE id_user=$1", id_user)
@ -55,11 +77,10 @@ func (tm *TaskManager) GetUserTasks(id_user int) ([]tasks.Task, error) {
task := tasks.Task{} task := tasks.Task{}
err = rows.Scan(&task.Id, err = rows.Scan(&task.Id,
&task.IdUser, &task.IdUser,
&task.Name, &task.Title,
&task.Description, &task.Description,
&task.Status, &task.Status,
&task.CreationDate, &task.DateStart,
&task.UpdateDate,
) )
if err != nil { if err != nil {
return user_tasks, err return user_tasks, err

View File

@ -7,15 +7,18 @@ import (
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
func (tm *TaskManager) AddUser(user *users.User) error { func (tm *TaskManagerRepository) AddUser(user *users.User) error {
err := tm.db.QueryRow("INSERT INTO task_manager.users(name, password) VALUES($1,$2) RETURNING id", user.Name, GetMD5Hash(user.Password)).Scan(&user.Id) err := tm.db.QueryRow("INSERT INTO task_manager.users(name, password, email) VALUES($1,$2,$3) RETURNING id",
user.Name,
GetMD5Hash(user.Password),
user.Email).Scan(&user.Id)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (tm *TaskManager) DeleteUser(user *users.User) error { func (tm *TaskManagerRepository) DeleteUser(user *users.User) error {
_, err := tm.db.Exec("DELETE FROM task_manager.users WHERE name=$1", user.Name) _, err := tm.db.Exec("DELETE FROM task_manager.users WHERE name=$1", user.Name)
if err != nil { if err != nil {
return err return err
@ -23,7 +26,7 @@ func (tm *TaskManager) DeleteUser(user *users.User) error {
return nil return nil
} }
func (tm *TaskManager) UpdateUser(user *users.User) error { func (tm *TaskManagerRepository) UpdateUser(user *users.User) error {
_, err := tm.db.Exec("UPDATE task_manager.users SET name=$1, password=$2 WHERE id=$3", user.Name, user.Password, user.Id) _, err := tm.db.Exec("UPDATE task_manager.users SET name=$1, password=$2 WHERE id=$3", user.Name, user.Password, user.Id)
if err != nil { if err != nil {
return err return err
@ -31,10 +34,10 @@ func (tm *TaskManager) UpdateUser(user *users.User) error {
return nil return nil
} }
func (tm *TaskManager) GetUser(user_name string) (*users.User, error) { func (tm *TaskManagerRepository) GetUser(user_name string) (*users.User, error) {
user := users.User{} user := users.User{}
row := tm.db.QueryRow("SELECT (id,name,password) FROM task_manager.users WHERE name=$1", user_name) row := tm.db.QueryRow("SELECT (id,name,password,email) FROM task_manager.users WHERE name=$1", user_name)
err := row.Scan(user.Id, user.Name, user.Password) err := row.Scan(&user.Id, &user.Name, &user.Password, &user.Email)
if err != nil { if err != nil {
log.Println("Ошибка получения пользователя:", err) log.Println("Ошибка получения пользователя:", err)
return nil, err return nil, err