diff --git a/bazunov_andrew_lab_3/PersonApp/.DS_Store b/bazunov_andrew_lab_3/PersonApp/.DS_Store new file mode 100644 index 0000000..b997599 Binary files /dev/null and b/bazunov_andrew_lab_3/PersonApp/.DS_Store differ diff --git a/bazunov_andrew_lab_3/PersonApp/.env b/bazunov_andrew_lab_3/PersonApp/.env new file mode 100644 index 0000000..70f865e --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/.env @@ -0,0 +1,4 @@ +PORT=8080 +TASK_APP_URL=http://task-app:8000 +TIMEOUT=15 +DATABASE=./database.db \ No newline at end of file diff --git a/bazunov_andrew_lab_3/PersonApp/Dockerfile b/bazunov_andrew_lab_3/PersonApp/Dockerfile new file mode 100644 index 0000000..c8c0804 --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.23 + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN go build -o /bin/PersonApp + +EXPOSE 8080 + +CMD ["/bin/PersonApp"] \ No newline at end of file diff --git a/bazunov_andrew_lab_3/PersonApp/database.db b/bazunov_andrew_lab_3/PersonApp/database.db new file mode 100644 index 0000000..ac97971 Binary files /dev/null and b/bazunov_andrew_lab_3/PersonApp/database.db differ diff --git a/bazunov_andrew_lab_3/PersonApp/go.mod b/bazunov_andrew_lab_3/PersonApp/go.mod new file mode 100644 index 0000000..e899c54 --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/go.mod @@ -0,0 +1,10 @@ +module PersonApp + +go 1.23.1 + +require ( + github.com/gorilla/mux v1.8.1 + github.com/mattn/go-sqlite3 v1.14.24 +) + +require github.com/joho/godotenv v1.5.1 // indirect diff --git a/bazunov_andrew_lab_3/PersonApp/go.sum b/bazunov_andrew_lab_3/PersonApp/go.sum new file mode 100644 index 0000000..b62a289 --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/go.sum @@ -0,0 +1,6 @@ +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/bazunov_andrew_lab_3/PersonApp/handlers/handlers.go b/bazunov_andrew_lab_3/PersonApp/handlers/handlers.go new file mode 100644 index 0000000..43e2a2b --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/handlers/handlers.go @@ -0,0 +1,157 @@ +package handlers + +import ( + "PersonApp/httpClient" + "PersonApp/models" + "PersonApp/repository" + "encoding/json" + "fmt" + "github.com/gorilla/mux" + "net/http" + "strconv" +) + +func InitRoutes(r *mux.Router, rep repository.PersonRepository, cln httpClient.Client) { + r.HandleFunc("/", GetPersons(rep, cln)).Methods("GET") + r.HandleFunc("/{id:[0-9]+}", GetPersonById(rep, cln)).Methods("GET") + r.HandleFunc("/", CreatePerson(rep)).Methods("POST") + r.HandleFunc("/{id:[0-9]+}", UpdatePerson(rep)).Methods("PUT") + r.HandleFunc("/{id:[0-9]+}", DeletePerson(rep)).Methods("DELETE") +} + +func GetPersons(rep repository.PersonRepository, cln httpClient.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Println("GET PERSONS") + + persons, err := rep.GetAllPersons() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + for i := 0; i < len(persons); i++ { + tasks, _ := cln.GetPersonTasks(persons[i].Id) + persons[i].Tasks = tasks + } + + err = json.NewEncoder(w).Encode(persons) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func GetPersonById(rep repository.PersonRepository, cln httpClient.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + id, err := strconv.Atoi(mux.Vars(r)["id"]) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + person, err := rep.GetPersonById(id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tasks, err := cln.GetPersonTasks(id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + person.Tasks = tasks + } + + err = json.NewEncoder(w).Encode(person) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func CreatePerson(rep repository.PersonRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var person *models.Person + + err := json.NewDecoder(r.Body).Decode(&person) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + person, err = rep.CreatePerson(*person) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(person) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func UpdatePerson(rep repository.PersonRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + id, err := strconv.Atoi(mux.Vars(r)["id"]) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var person *models.Person + err = json.NewDecoder(r.Body).Decode(&person) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + person, err = rep.UpdatePerson(models.Person{ + Id: id, + Name: person.Name, + Tasks: nil, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusAccepted) + err = json.NewEncoder(w).Encode(person) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +func DeletePerson(rep repository.PersonRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + id, err := strconv.Atoi(mux.Vars(r)["id"]) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = rep.DeletePerson(id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + } +} diff --git a/bazunov_andrew_lab_3/PersonApp/httpClient/client.go b/bazunov_andrew_lab_3/PersonApp/httpClient/client.go new file mode 100644 index 0000000..29471fd --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/httpClient/client.go @@ -0,0 +1,72 @@ +package httpClient + +import ( + "PersonApp/models" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +type Client interface { + GetPersonTasks(id int) ([]models.Task, error) + TestConnection() (bool, error) +} + +type client struct { + BaseUrl string + Timeout time.Duration +} + +func (c *client) TestConnection() (bool, error) { + client := &http.Client{Timeout: c.Timeout} + url := fmt.Sprintf("%s/", c.BaseUrl) + resp, err := client.Get(url) + + if err != nil { + return false, err + } + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + return + } + }(resp.Body) + + if resp.StatusCode != http.StatusOK { + return false, fmt.Errorf("bad status code: %d", resp.StatusCode) + } + return true, nil +} + +func (c *client) GetPersonTasks(id int) ([]models.Task, error) { + client := &http.Client{Timeout: c.Timeout * time.Second} + url := fmt.Sprintf("%s/f/%d", c.BaseUrl, id) + + resp, err := client.Get(url) + if err != nil { + return nil, err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + + } + }(resp.Body) + + body, _ := io.ReadAll(resp.Body) + + var tasks []models.Task + if err := json.Unmarshal(body, &tasks); err != nil { + fmt.Printf("Unmarshal error: %s", err) + return []models.Task{}, err + } + + return tasks, nil +} + +func NewClient(baseUrl string, timeout time.Duration) Client { + return &client{BaseUrl: baseUrl, Timeout: timeout} +} diff --git a/bazunov_andrew_lab_3/PersonApp/httpTests/test.http b/bazunov_andrew_lab_3/PersonApp/httpTests/test.http new file mode 100644 index 0000000..6e0f95d --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/httpTests/test.http @@ -0,0 +1,34 @@ +GET http://localhost/person-app/ +Accept: application/json + +### + +GET http://localhost/person-app/1 +Accept: application/json + +### + +POST http://localhost/person-app/ +Accept: application/json +Content-Type: application/json + +{ + "name": "TEST3" +} + +### + +PUT http://localhost/person-app/3 +Accept: application/json +Content-Type: application/json + +{ + "name": "TEST11" +} + +### + +DELETE http://localhost/person-app/3 +Accept: application/json + +### \ No newline at end of file diff --git a/bazunov_andrew_lab_3/PersonApp/main.go b/bazunov_andrew_lab_3/PersonApp/main.go new file mode 100644 index 0000000..2de6525 --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "PersonApp/handlers" + "PersonApp/httpClient" + "PersonApp/repository" + "PersonApp/storage" + "github.com/gorilla/mux" + "github.com/joho/godotenv" + "net/http" + "os" + "strconv" + "time" +) + +func main() { + err := godotenv.Load(".env") + if err != nil { + panic("Error loading .env file") + } + + url := os.Getenv("TASK_APP_URL") + port := os.Getenv("PORT") + databasePath := os.Getenv("DATABASE") + timeout, err := strconv.Atoi(os.Getenv("TIMEOUT")) + + if err != nil { + panic("Error converting timeout to int") + } + + database, err := storage.Init(databasePath) + if err != nil { + panic(err) + } + + cln := httpClient.NewClient(url, time.Duration(timeout)) + rep := repository.NewPersonRepository(database) + router := mux.NewRouter() + handlers.InitRoutes(router, rep, cln) + + err = http.ListenAndServe(":"+port, router) + if err != nil { + panic(err) + } + + storage.Close(database) +} diff --git a/bazunov_andrew_lab_3/PersonApp/models/models.go b/bazunov_andrew_lab_3/PersonApp/models/models.go new file mode 100644 index 0000000..a518460 --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/models/models.go @@ -0,0 +1,24 @@ +package models + +type Person struct { + Id int `json:"id"` + Name string `json:"name"` + Tasks []Task `json:"tasks"` +} + +type PersonCreate struct { + Name string `json:"name"` +} + +type Task struct { + Id int `json:"id"` + PersonId int `json:"person_id"` + Name string `json:"name"` + Date string `json:"date"` +} + +type TaskCreate struct { + PersonId int `json:"person_id"` + Name string `json:"name"` + Date string `json:"date"` +} diff --git a/bazunov_andrew_lab_3/PersonApp/repository/personRepository.go b/bazunov_andrew_lab_3/PersonApp/repository/personRepository.go new file mode 100644 index 0000000..37de8ee --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/repository/personRepository.go @@ -0,0 +1,99 @@ +package repository + +import ( + "PersonApp/models" + "database/sql" +) + +type PersonRepository interface { + GetAllPersons() ([]models.Person, error) + GetPersonById(id int) (*models.Person, error) + CreatePerson(person models.Person) (*models.Person, error) + UpdatePerson(person models.Person) (*models.Person, error) + DeletePerson(id int) error +} + +type personRepository struct { + DB *sql.DB +} + +func NewPersonRepository(db *sql.DB) PersonRepository { + return &personRepository{DB: db} +} + +func (pr *personRepository) GetAllPersons() ([]models.Person, error) { + rows, err := pr.DB.Query("select * from Persons") + if err != nil { + return nil, err + } + + defer func(rows *sql.Rows) { + err := rows.Close() + if err != nil { + panic(err) + } + }(rows) + + var persons []models.Person + + for rows.Next() { + p := models.Person{} + err := rows.Scan(&p.Id, &p.Name) + if err != nil { + panic(err) + } + + persons = append(persons, p) + } + + return persons, err +} + +func (pr *personRepository) GetPersonById(id int) (*models.Person, error) { + row := pr.DB.QueryRow("select * from Persons where id=?", id) + + person := models.Person{} + err := row.Scan(&person.Id, &person.Name) + if err != nil { + return nil, err + } + + return &person, err +} + +func (pr *personRepository) CreatePerson(p models.Person) (*models.Person, error) { + res, err := pr.DB.Exec("INSERT INTO Persons (name) values (?)", p.Name) + + if err != nil { + return nil, err + } + + if res == nil { + return nil, nil + } + + return &p, err +} + +func (pr *personRepository) UpdatePerson(p models.Person) (*models.Person, error) { + res, err := pr.DB.Exec("UPDATE Persons SET name = ? WHERE id = ?", p.Name, p.Id) + + if err != nil { + return nil, err + } + + if res == nil { + return nil, nil + } + + return &p, err +} + +func (pr *personRepository) DeletePerson(id int) error { + _, err := pr.DB.Exec("DELETE FROM Persons WHERE id = ?", id) + + if err != nil { + return err + } + return nil +} diff --git a/bazunov_andrew_lab_3/PersonApp/storage/database.db b/bazunov_andrew_lab_3/PersonApp/storage/database.db new file mode 100644 index 0000000..e69de29 diff --git a/bazunov_andrew_lab_3/PersonApp/storage/db.go b/bazunov_andrew_lab_3/PersonApp/storage/db.go new file mode 100644 index 0000000..13c0501 --- /dev/null +++ b/bazunov_andrew_lab_3/PersonApp/storage/db.go @@ -0,0 +1,36 @@ +package storage + +import ( + "database/sql" + _ "github.com/mattn/go-sqlite3" +) + +func Init(databasePath string) (*sql.DB, error) { + db, err := sql.Open("sqlite3", databasePath) + + if err != nil || db == nil { + return nil, err + } + + if err := createTableIfNotExists(db); err != nil { + return nil, err + } + + return db, nil +} + +func Close(db *sql.DB) { + err := db.Close() + if err != nil { + return + } +} + +func createTableIfNotExists(db *sql.DB) error { + if result, err := db.Exec( + "CREATE TABLE IF NOT EXISTS `Persons`(Id integer primary key autoincrement, Name text not null);", + ); err != nil || result == nil { + return err + } + return nil +} diff --git a/bazunov_andrew_lab_2/README.md b/bazunov_andrew_lab_3/README.md similarity index 68% rename from bazunov_andrew_lab_2/README.md rename to bazunov_andrew_lab_3/README.md index 650ebf0..1e5788f 100644 --- a/bazunov_andrew_lab_2/README.md +++ b/bazunov_andrew_lab_3/README.md @@ -1,11 +1,6 @@ -# Распределенные вычисления и приложения Л2 +# Распределенные вычисления и приложения Л3 ## _Автор Базунов Андрей Игревич ПИбд-42_ -Сервисы ( _порядок исполнения сервисов соблюден_ ): -- 1.FileCreator - (_Создание тестовых данных_) -- 2.FirstService - (_Выполнение 1.4 варианта задания_) -- 3.SecondService - (_Выполнение 2.2 варианта задания_) - В качестве основного языка был выбран GoLang. Для каждого сервиса был создан DOCKERFILE где были прописаны условия и действия для сборки каждого из модулей # Docker @@ -27,4 +22,4 @@ docker-compose up -d --build docker-compose down ``` -[Демонстрация работы](https://vk.com/video236673313_456239575) +[Демонстрация работы](https://vk.com/video/@viltskaa?z=video236673313_456239577%2Fpl_236673313_-2) diff --git a/bazunov_andrew_lab_3/TaskApp/.env b/bazunov_andrew_lab_3/TaskApp/.env new file mode 100644 index 0000000..a664223 --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/.env @@ -0,0 +1,4 @@ +PORT=8000 +PERSON_APP_URL=http://person-app:8080 +TIMEOUT=15 +DATABASE=./database.db \ No newline at end of file diff --git a/bazunov_andrew_lab_3/TaskApp/Dockerfile b/bazunov_andrew_lab_3/TaskApp/Dockerfile new file mode 100644 index 0000000..2fef0f1 --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.23 + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN go build -o /bin/TaskApp + +EXPOSE 8000 + +CMD ["/bin/TaskApp"] \ No newline at end of file diff --git a/bazunov_andrew_lab_3/TaskApp/database.db b/bazunov_andrew_lab_3/TaskApp/database.db new file mode 100644 index 0000000..9da5d21 Binary files /dev/null and b/bazunov_andrew_lab_3/TaskApp/database.db differ diff --git a/bazunov_andrew_lab_3/TaskApp/go.mod b/bazunov_andrew_lab_3/TaskApp/go.mod new file mode 100644 index 0000000..041dfbe --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/go.mod @@ -0,0 +1,10 @@ +module TaskApp + +go 1.23.1 + +require ( + github.com/gorilla/mux v1.8.1 + github.com/mattn/go-sqlite3 v1.14.24 +) + +require github.com/joho/godotenv v1.5.1 diff --git a/bazunov_andrew_lab_3/TaskApp/go.sum b/bazunov_andrew_lab_3/TaskApp/go.sum new file mode 100644 index 0000000..b62a289 --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/go.sum @@ -0,0 +1,6 @@ +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/bazunov_andrew_lab_3/TaskApp/handlers/handlers.go b/bazunov_andrew_lab_3/TaskApp/handlers/handlers.go new file mode 100644 index 0000000..9841d7c --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/handlers/handlers.go @@ -0,0 +1,177 @@ +package handlers + +import ( + "TaskApp/httpClient" + "TaskApp/models" + "TaskApp/repository" + "encoding/json" + "fmt" + "github.com/gorilla/mux" + "net/http" + "strconv" +) + +func InitRoutes(r *mux.Router, rep repository.TaskRepository, cln httpClient.Client) { + r.HandleFunc("/", GetTasks(rep)).Methods("GET") + r.HandleFunc("/{id:[0-9]+}", GetTaskById(rep)).Methods("GET") + r.HandleFunc("/", CreateTask(rep, cln)).Methods("POST") + r.HandleFunc("/{id:[0-9]+}", UpdateTask(rep)).Methods("PUT") + r.HandleFunc("/{id:[0-9]+}", DeleteTask(rep)).Methods("DELETE") + r.HandleFunc("/f/{id:[0-9]+}", GetPersonTasks(rep)).Methods("GET") +} + +func GetTasks(rep repository.TaskRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + tasks, err := rep.GetAllTasks() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + err = json.NewEncoder(w).Encode(tasks) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } +} + +func GetTaskById(rep repository.TaskRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + id, err := strconv.Atoi(mux.Vars(r)["id"]) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + person, err := rep.GetTaskById(id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = json.NewEncoder(w).Encode(person) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func GetPersonTasks(rep repository.TaskRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + id, err := strconv.Atoi(mux.Vars(r)["id"]) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + tasks, err := rep.GetUserTasks(id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = json.NewEncoder(w).Encode(tasks) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func CreateTask(rep repository.TaskRepository, cln httpClient.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var task *models.TaskCreate + + err := json.NewDecoder(r.Body).Decode(&task) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if &task.Name == nil || &task.PersonId == nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + person, err := cln.GetPerson(task.PersonId) + + if err != nil { + fmt.Println(err) + http.Error(w, "Connection to PersonApp is confused.", http.StatusInternalServerError) + return + } + + if person == nil { + http.Error(w, fmt.Sprintf("Person with id=%d is't founded.", person.Id), http.StatusBadGateway) + return + } + + newTask, err := rep.CreateTask(*task) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(newTask) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func UpdateTask(rep repository.TaskRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + id, err := strconv.Atoi(mux.Vars(r)["id"]) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + var task *models.TaskCreate + + err = json.NewDecoder(r.Body).Decode(&task) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + newTask, err := rep.UpdateTask(models.Task{Id: id, Name: task.Name, Date: task.Date}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + err = json.NewEncoder(w).Encode(newTask) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +func DeleteTask(rep repository.TaskRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + id, err := strconv.Atoi(mux.Vars(r)["id"]) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + err = rep.DeleteTask(id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + } +} diff --git a/bazunov_andrew_lab_3/TaskApp/httpClient/client.go b/bazunov_andrew_lab_3/TaskApp/httpClient/client.go new file mode 100644 index 0000000..680fc2e --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/httpClient/client.go @@ -0,0 +1,73 @@ +package httpClient + +import ( + "TaskApp/models" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "time" +) + +type Client interface { + GetPerson(id int) (*models.Person, error) + TestConnection() (bool, error) +} + +type client struct { + BaseUrl string + Timeout time.Duration +} + +func (c *client) TestConnection() (bool, error) { + client := &http.Client{Timeout: c.Timeout} + url := fmt.Sprintf("%s/", c.BaseUrl) + resp, err := client.Get(url) + + if err != nil { + return false, err + } + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + return + } + }(resp.Body) + + if resp.StatusCode != http.StatusOK { + return false, fmt.Errorf("bad status code: %d", resp.StatusCode) + } + return true, nil +} + +func (c *client) GetPerson(id int) (*models.Person, error) { + client := &http.Client{Timeout: c.Timeout * time.Second} + url := fmt.Sprintf("%s/%d", c.BaseUrl, id) + + resp, err := client.Get(url) + if err != nil { + return nil, err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + + } + }(resp.Body) + + body, _ := io.ReadAll(resp.Body) + + var person models.Person + if err := json.Unmarshal(body, &person); err != nil { + log.Printf("Unmarshal error: %s", err) + return nil, err + } + + return &person, nil +} + +func NewClient(baseUrl string, timeout time.Duration) Client { + return &client{BaseUrl: baseUrl, Timeout: timeout} +} diff --git a/bazunov_andrew_lab_3/TaskApp/httpTests/tests.http b/bazunov_andrew_lab_3/TaskApp/httpTests/tests.http new file mode 100644 index 0000000..e022851 --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/httpTests/tests.http @@ -0,0 +1,37 @@ +GET http://localhost/task-app/ +Accept: application/json + +### + +GET http://localhost/task-app/4 +Accept: application/json + +### + +POST http://localhost/task-app/ +Accept: application/json +Content-Type: application/json + +{ + "name": "TEST2", + "person_id": 1, + "date": "19.02.2202" +} + +### + +PUT http://localhost/task-app/4 +Accept: application/json +Content-Type: application/json + +{ + "name": "TEST5", + "date": "19.02.2202" +} + +### + +DELETE http://localhost/task-app/4 +Accept: application/json + +### \ No newline at end of file diff --git a/bazunov_andrew_lab_3/TaskApp/main.go b/bazunov_andrew_lab_3/TaskApp/main.go new file mode 100644 index 0000000..9ccd4ee --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "TaskApp/handlers" + "TaskApp/httpClient" + "TaskApp/repository" + "TaskApp/storage" + "github.com/gorilla/mux" + "github.com/joho/godotenv" + "net/http" + "os" + "strconv" + "time" +) + +func main() { + err := godotenv.Load(".env") + if err != nil { + panic("Error loading .env file") + } + + url := os.Getenv("PERSON_APP_URL") + port := os.Getenv("PORT") + databasePath := os.Getenv("DATABASE") + timeout, err := strconv.Atoi(os.Getenv("TIMEOUT")) + + if err != nil { + panic("Error converting timeout to int") + } + + database, err := storage.Init(databasePath) + if err != nil { + panic(err) + } + + cln := httpClient.NewClient(url, time.Duration(timeout)) + rep := repository.NewTaskRepository(database) + router := mux.NewRouter() + handlers.InitRoutes(router, rep, cln) + + err = http.ListenAndServe(":"+port, router) + if err != nil { + panic(err) + } + + storage.Close(database) +} diff --git a/bazunov_andrew_lab_3/TaskApp/models/models.go b/bazunov_andrew_lab_3/TaskApp/models/models.go new file mode 100644 index 0000000..a518460 --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/models/models.go @@ -0,0 +1,24 @@ +package models + +type Person struct { + Id int `json:"id"` + Name string `json:"name"` + Tasks []Task `json:"tasks"` +} + +type PersonCreate struct { + Name string `json:"name"` +} + +type Task struct { + Id int `json:"id"` + PersonId int `json:"person_id"` + Name string `json:"name"` + Date string `json:"date"` +} + +type TaskCreate struct { + PersonId int `json:"person_id"` + Name string `json:"name"` + Date string `json:"date"` +} diff --git a/bazunov_andrew_lab_3/TaskApp/repository/taskRepository.go b/bazunov_andrew_lab_3/TaskApp/repository/taskRepository.go new file mode 100644 index 0000000..41d1ccf --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/repository/taskRepository.go @@ -0,0 +1,121 @@ +package repository + +import ( + "TaskApp/models" + "database/sql" +) + +type TaskRepository interface { + GetAllTasks() ([]models.Task, error) + GetTaskById(id int) (*models.Task, error) + GetUserTasks(id int) ([]models.Task, error) + CreateTask(task models.TaskCreate) (*models.Task, error) + UpdateTask(task models.Task) (*models.Task, error) + DeleteTask(id int) error +} + +type taskRepository struct { + DB *sql.DB +} + +func (t taskRepository) GetUserTasks(id int) ([]models.Task, error) { + rows, err := t.DB.Query("select * from Tasks where PersonId = ?", id) + if err != nil { + return nil, err + } + + defer func(rows *sql.Rows) { + err := rows.Close() + if err != nil { + panic(err) + } + }(rows) + + var tasks []models.Task + + for rows.Next() { + p := models.Task{} + err := rows.Scan(&p.Id, &p.Name, &p.PersonId, &p.Date) + if err != nil { + panic(err) + } + + tasks = append(tasks, p) + } + + return tasks, err +} + +func (t taskRepository) GetAllTasks() ([]models.Task, error) { + rows, err := t.DB.Query("select * from Tasks") + if err != nil { + return nil, err + } + + defer func(rows *sql.Rows) { + err := rows.Close() + if err != nil { + panic(err) + } + }(rows) + + var tasks []models.Task + + for rows.Next() { + p := models.Task{} + err := rows.Scan(&p.Id, &p.Name, &p.PersonId, &p.Date) + if err != nil { + panic(err) + } + + tasks = append(tasks, p) + } + + return tasks, err +} + +func (t taskRepository) GetTaskById(id int) (*models.Task, error) { + row := t.DB.QueryRow("select * from Tasks where id=?", id) + + task := models.Task{} + err := row.Scan(&task.Id, &task.Name, &task.PersonId, &task.Date) + if err != nil { + return nil, err + } + + return &task, err +} + +func (t taskRepository) CreateTask(task models.TaskCreate) (*models.Task, error) { + _, err := t.DB.Exec("INSERT INTO Tasks(Name, PersonId, Date) VALUES (?, ?, ?)", task.Name, task.PersonId, task.Date) + if err != nil { + return nil, err + } + + return &models.Task{ + Id: 0, + PersonId: task.PersonId, + Name: task.Name, + Date: task.Date, + }, err +} + +func (t taskRepository) UpdateTask(task models.Task) (*models.Task, error) { + _, err := t.DB.Exec("UPDATE Tasks SET name = ?, date = ? WHERE id = ?", task.Name, task.Date, task.Id) + if err != nil { + return nil, err + } + return &task, err +} + +func (t taskRepository) DeleteTask(id int) error { + _, err := t.DB.Exec("DELETE FROM Tasks WHERE id = ?", id) + if err != nil { + return err + } + return nil +} + +func NewTaskRepository(db *sql.DB) TaskRepository { + return &taskRepository{DB: db} +} diff --git a/bazunov_andrew_lab_3/TaskApp/storage/db.go b/bazunov_andrew_lab_3/TaskApp/storage/db.go new file mode 100644 index 0000000..8d8c0a3 --- /dev/null +++ b/bazunov_andrew_lab_3/TaskApp/storage/db.go @@ -0,0 +1,36 @@ +package storage + +import ( + "database/sql" + _ "github.com/mattn/go-sqlite3" +) + +func Init(databasePath string) (*sql.DB, error) { + db, err := sql.Open("sqlite3", databasePath) + + if err != nil || db == nil { + return nil, err + } + + if err := createTableIfNotExists(db); err != nil { + return nil, err + } + + return db, nil +} + +func Close(db *sql.DB) { + err := db.Close() + if err != nil { + return + } +} + +func createTableIfNotExists(db *sql.DB) error { + if result, err := db.Exec( + "CREATE TABLE IF NOT EXISTS `Tasks`(Id integer primary key autoincrement, Name text not null, PersonId integer not null, Date text not null);", + ); err != nil || result == nil { + return err + } + return nil +} diff --git a/bazunov_andrew_lab_3/docker-compose.yaml b/bazunov_andrew_lab_3/docker-compose.yaml new file mode 100644 index 0000000..00d914d --- /dev/null +++ b/bazunov_andrew_lab_3/docker-compose.yaml @@ -0,0 +1,34 @@ +services: + person-app: + build: + context: ./PersonApp + dockerfile: Dockerfile + networks: + - network + ports: + - "8080:8080" + + task-app: + build: + context: ./TaskApp + dockerfile: Dockerfile + networks: + - network + ports: + - "8000:8000" + + nginx: + image: nginx + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + networks: + - network + depends_on: + - person-app + - task-app + +networks: + network: + driver: bridge \ No newline at end of file diff --git a/bazunov_andrew_lab_3/nginx.conf b/bazunov_andrew_lab_3/nginx.conf new file mode 100644 index 0000000..2c4bf8a --- /dev/null +++ b/bazunov_andrew_lab_3/nginx.conf @@ -0,0 +1,59 @@ +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name localhost; + + location /person-app/ { + proxy_pass http://person-app:8080/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization'; + + } + + location /task-app/ { + proxy_pass http://task-app:8000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization'; + } + + # Прокси для Swagger (Stream-сервис) + #location /stream-service/swagger/ { + # proxy_pass http://stream-service:8000/swagger/; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + #} + + # Прокси для Swagger (Message-сервис) + #location /message-service/swagger/ { + # proxy_pass http://message-service:8080/swagger/; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + #} + + #location /stream-service/doc.json { + # proxy_pass http://stream-service:8000/doc.json; + #} + + #location /message-service/doc.json { + # proxy_pass http://message-service:8080/doc.json; + #} + } +} \ No newline at end of file