diff --git a/senkin_alexander_lab_3/.gitignore b/senkin_alexander_lab_3/.gitignore new file mode 100644 index 0000000..e0a1c00 --- /dev/null +++ b/senkin_alexander_lab_3/.gitignore @@ -0,0 +1 @@ +./idea \ No newline at end of file diff --git a/senkin_alexander_lab_3/README.md b/senkin_alexander_lab_3/README.md new file mode 100644 index 0000000..a570cd2 --- /dev/null +++ b/senkin_alexander_lab_3/README.md @@ -0,0 +1,52 @@ +# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами +Цель: изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API. + +Задачи: +- Создать 2 микросервиса, реализующих CRUD на связанных сущностях. +- Реализовать механизм синхронного обмена сообщениями между микросервисами. +- Реализовать шлюз на основе прозрачного прокси-сервера nginx. + +# Разработка двух распределенных приложений +Было решено на первом сервисе(service1) сделать реализацию рабочего(clerc), на втором сервисе(service2) сделать реализацию офиса(office), которая хранит uuid работников. +Было решено сделать как принято в микросервисной архитектуре бд на микросервис, поэтому реализовано 2 базы данных на PostgreSQL. +Также реализован nginx - proxy сервер на общения сервисов между друг другом, а также для доступа к сервам снаружи через ngnix. + +![img.png](img.png) + +# Запуск +Запуск контейнеров производится командой "docker-compose up -d" + +# Работа программы +- Создание репозиторий service1 и service2 для сервисов, bd1 и bd2 для образов баз данных +- Создание go.mod для подтягивания зависимостей. +- Описание Dockerfile для создания образов для сервисов, одинковые для обоих сервисов: +- ![img_1.png](img_1.png) +- Описание Dockerfile для создания образов для бд, одинаковые для обоих: +- ![img_2.png](img_2.png) +- Создание файлов server_run.go с логикой работ сервисов. +- Создаем файл nginx.conf с конфигурацией работы nginx: +- ![img_3.png](img_3.png) +- Создаем файл docker-compose.yml для описания запуска контейнеров в одной сети: +- ![img_4.png](img_4.png) +- ![img_5.png](img_5.png) +- Сборка и запуска контейнеров. +- ![img_6.png](img_6.png) +- ![img_7.png](img_7.png) +- Проверка работы докер сети и контейнеров, все работает стабильно: +- ![img_8.png](img_8.png) +- Делаем запросы через insomnia: +- ![img_9.png](img_9.png) +- ![img_10.png](img_10.png) +- ![img_11.png](img_11.png) +- ![img_12.png](img_12.png) +- ![img_13.png](img_13.png) +- ![img_14.png](img_14.png) +- ![img_15.png](img_15.png) +- ![img_16.png](img_16.png) +- ![img_17.png](img_17.png) +- ![img_18.png](img_18.png) +- Все запросы работают. + +# Видео +Видео с разбором лабораторной работы - https://youtu.be/RQCwqW9jwH4 + diff --git a/senkin_alexander_lab_3/bd1/Dockerfile b/senkin_alexander_lab_3/bd1/Dockerfile new file mode 100644 index 0000000..007401a --- /dev/null +++ b/senkin_alexander_lab_3/bd1/Dockerfile @@ -0,0 +1,3 @@ +FROM postgres:latest + +COPY init.sql /docker-entrypoint-initdb.d/init.sql diff --git a/senkin_alexander_lab_3/bd1/init.sql b/senkin_alexander_lab_3/bd1/init.sql new file mode 100644 index 0000000..a00827c --- /dev/null +++ b/senkin_alexander_lab_3/bd1/init.sql @@ -0,0 +1,2 @@ +-- init.sql +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/senkin_alexander_lab_3/bd2/Dockerfile b/senkin_alexander_lab_3/bd2/Dockerfile new file mode 100644 index 0000000..007401a --- /dev/null +++ b/senkin_alexander_lab_3/bd2/Dockerfile @@ -0,0 +1,3 @@ +FROM postgres:latest + +COPY init.sql /docker-entrypoint-initdb.d/init.sql diff --git a/senkin_alexander_lab_3/bd2/init.sql b/senkin_alexander_lab_3/bd2/init.sql new file mode 100644 index 0000000..a00827c --- /dev/null +++ b/senkin_alexander_lab_3/bd2/init.sql @@ -0,0 +1,2 @@ +-- init.sql +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/senkin_alexander_lab_3/docker-compose.yml b/senkin_alexander_lab_3/docker-compose.yml new file mode 100644 index 0000000..8ac552e --- /dev/null +++ b/senkin_alexander_lab_3/docker-compose.yml @@ -0,0 +1,53 @@ +services: + bd1: + build: + context: /bd1 + dockerfile: Dockerfile + environment: + POSTGRES_DB: "service1" + POSTGRES_USER: "postgres" + POSTGRES_PASSWORD: "159753" + networks: + - network + bd2: + build: + context: /bd2 + dockerfile: Dockerfile + environment: + POSTGRES_DB: "service2" + POSTGRES_USER: "postgres" + POSTGRES_PASSWORD: "159753" + networks: + - network + service1: + build: + context: /service1 + dockerfile: Dockerfile + networks: + - network + depends_on: + - bd1 + service2: + build: + context: /service2 + dockerfile: Dockerfile + networks: + - network + depends_on: + - bd2 + nginx: + image: nginx + ports: + - "80:80" + networks: + - network + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - service1 + - service2 + +networks: + network: + driver: bridge + diff --git a/senkin_alexander_lab_3/img.png b/senkin_alexander_lab_3/img.png new file mode 100644 index 0000000..ddc051d Binary files /dev/null and b/senkin_alexander_lab_3/img.png differ diff --git a/senkin_alexander_lab_3/img_1.png b/senkin_alexander_lab_3/img_1.png new file mode 100644 index 0000000..eb560aa Binary files /dev/null and b/senkin_alexander_lab_3/img_1.png differ diff --git a/senkin_alexander_lab_3/img_10.png b/senkin_alexander_lab_3/img_10.png new file mode 100644 index 0000000..71c09d7 Binary files /dev/null and b/senkin_alexander_lab_3/img_10.png differ diff --git a/senkin_alexander_lab_3/img_11.png b/senkin_alexander_lab_3/img_11.png new file mode 100644 index 0000000..359df9a Binary files /dev/null and b/senkin_alexander_lab_3/img_11.png differ diff --git a/senkin_alexander_lab_3/img_12.png b/senkin_alexander_lab_3/img_12.png new file mode 100644 index 0000000..d8c9602 Binary files /dev/null and b/senkin_alexander_lab_3/img_12.png differ diff --git a/senkin_alexander_lab_3/img_13.png b/senkin_alexander_lab_3/img_13.png new file mode 100644 index 0000000..e243880 Binary files /dev/null and b/senkin_alexander_lab_3/img_13.png differ diff --git a/senkin_alexander_lab_3/img_14.png b/senkin_alexander_lab_3/img_14.png new file mode 100644 index 0000000..494a6c3 Binary files /dev/null and b/senkin_alexander_lab_3/img_14.png differ diff --git a/senkin_alexander_lab_3/img_15.png b/senkin_alexander_lab_3/img_15.png new file mode 100644 index 0000000..4c8b4d9 Binary files /dev/null and b/senkin_alexander_lab_3/img_15.png differ diff --git a/senkin_alexander_lab_3/img_16.png b/senkin_alexander_lab_3/img_16.png new file mode 100644 index 0000000..ce7ffe3 Binary files /dev/null and b/senkin_alexander_lab_3/img_16.png differ diff --git a/senkin_alexander_lab_3/img_17.png b/senkin_alexander_lab_3/img_17.png new file mode 100644 index 0000000..f2a950f Binary files /dev/null and b/senkin_alexander_lab_3/img_17.png differ diff --git a/senkin_alexander_lab_3/img_18.png b/senkin_alexander_lab_3/img_18.png new file mode 100644 index 0000000..76ab51e Binary files /dev/null and b/senkin_alexander_lab_3/img_18.png differ diff --git a/senkin_alexander_lab_3/img_2.png b/senkin_alexander_lab_3/img_2.png new file mode 100644 index 0000000..6dd993c Binary files /dev/null and b/senkin_alexander_lab_3/img_2.png differ diff --git a/senkin_alexander_lab_3/img_3.png b/senkin_alexander_lab_3/img_3.png new file mode 100644 index 0000000..787722d Binary files /dev/null and b/senkin_alexander_lab_3/img_3.png differ diff --git a/senkin_alexander_lab_3/img_4.png b/senkin_alexander_lab_3/img_4.png new file mode 100644 index 0000000..3c3ec93 Binary files /dev/null and b/senkin_alexander_lab_3/img_4.png differ diff --git a/senkin_alexander_lab_3/img_5.png b/senkin_alexander_lab_3/img_5.png new file mode 100644 index 0000000..b5f30ba Binary files /dev/null and b/senkin_alexander_lab_3/img_5.png differ diff --git a/senkin_alexander_lab_3/img_6.png b/senkin_alexander_lab_3/img_6.png new file mode 100644 index 0000000..d8e30bc Binary files /dev/null and b/senkin_alexander_lab_3/img_6.png differ diff --git a/senkin_alexander_lab_3/img_7.png b/senkin_alexander_lab_3/img_7.png new file mode 100644 index 0000000..bfb0965 Binary files /dev/null and b/senkin_alexander_lab_3/img_7.png differ diff --git a/senkin_alexander_lab_3/img_8.png b/senkin_alexander_lab_3/img_8.png new file mode 100644 index 0000000..465ecb6 Binary files /dev/null and b/senkin_alexander_lab_3/img_8.png differ diff --git a/senkin_alexander_lab_3/img_9.png b/senkin_alexander_lab_3/img_9.png new file mode 100644 index 0000000..af89864 Binary files /dev/null and b/senkin_alexander_lab_3/img_9.png differ diff --git a/senkin_alexander_lab_3/nginx.conf b/senkin_alexander_lab_3/nginx.conf new file mode 100644 index 0000000..56d8632 --- /dev/null +++ b/senkin_alexander_lab_3/nginx.conf @@ -0,0 +1,20 @@ +events { + worker_connections 1024; +} + +http { + server { + listen 80; + listen [::]:80; + server_name localhost; + + location /service1/ { + proxy_pass http://service1:13999; + } + + location /service2/ { + proxy_pass http://service2:13998; + } + } +} + diff --git a/senkin_alexander_lab_3/service1/Dockerfile b/senkin_alexander_lab_3/service1/Dockerfile new file mode 100644 index 0000000..f9d966a --- /dev/null +++ b/senkin_alexander_lab_3/service1/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:latest + +WORKDIR /app + +COPY go.mod . +COPY go.sum . + +RUN go mod download + +COPY server_run.go . + +RUN go build -o myapp + +CMD ["/app/myapp"] \ No newline at end of file diff --git a/senkin_alexander_lab_3/service1/go.mod b/senkin_alexander_lab_3/service1/go.mod new file mode 100644 index 0000000..586caa5 --- /dev/null +++ b/senkin_alexander_lab_3/service1/go.mod @@ -0,0 +1,18 @@ +module DAS_2023_1/senkin_alexander_lab_3/service1 + +go 1.21.3 + +require ( + github.com/caarlos0/env/v8 v8.0.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/gorilla/mux v1.8.1 // 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 + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/text v0.13.0 // indirect + gorm.io/driver/postgres v1.5.4 // indirect + gorm.io/gorm v1.25.5 // indirect +) diff --git a/senkin_alexander_lab_3/service1/go.sum b/senkin_alexander_lab_3/service1/go.sum new file mode 100644 index 0000000..590a820 --- /dev/null +++ b/senkin_alexander_lab_3/service1/go.sum @@ -0,0 +1,31 @@ +github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= +github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/senkin_alexander_lab_3/service1/server_run.go b/senkin_alexander_lab_3/service1/server_run.go new file mode 100644 index 0000000..e921a89 --- /dev/null +++ b/senkin_alexander_lab_3/service1/server_run.go @@ -0,0 +1,304 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/caarlos0/env/v8" + "github.com/google/uuid" + "github.com/gorilla/mux" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "log" + "net/http" + "os" + "os/signal" + "syscall" +) + +type Сonfig struct { + Port string `env:"USER_HTTP_ADDR" envDefault:"13999"` + + PgPort string `env:"PG_PORT" envDefault:"5432"` + PgHost string `env:"PG_HOST" envDefault:"bd1"` + PgDBName string `env:"PG_DB_NAME" envDefault:"service1"` + PgUser string `env:"PG_USER" envDefault:"postgres"` + PgPwd string `env:"PG_PWD" envDefault:"159753"` +} + +func main() { + cfg := Сonfig{} + if err := env.Parse(&cfg); err != nil { + log.Fatalf("failed to retrieve env variables, %v", err) + } + if err := Run(cfg); err != nil { + log.Fatal("error running grpc server ", err) + } +} + +type Clerc struct { + Id uuid.UUID `json:"id" gorm:"type:uuid;default:uuid_generate_v4();primaryKey"` + Name string `json:"name" gorm:"name"` + Post string `json:"post" gorm:"post"` + OfficeId uuid.UUID `json:"office_id" gorm:"office_id"` +} + +func InitDb(cfg Сonfig) (*gorm.DB, error) { + dsn := fmt.Sprintf( + "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", + cfg.PgHost, cfg.PgUser, cfg.PgPwd, cfg.PgDBName, cfg.PgPort, + ) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + log.Fatal("Cannot to Connect DataBase", err) + } + db.AutoMigrate(&Clerc{}) + return gorm.Open(postgres.Open(dsn), &gorm.Config{}) +} + +type ClercRepository interface { + Create(ctx context.Context, c *Clerc) error + Get(ctx context.Context, id uuid.UUID) (*Clerc, error) + GetList(ctx context.Context) ([]*Clerc, error) + Update(ctx context.Context, c *Clerc) error + Delete(ctx context.Context, id uuid.UUID) error +} + +type ClercStorage struct { + db *gorm.DB +} + +func NewCs(db *gorm.DB) *ClercStorage { + return &ClercStorage{ + db: db, + } +} + +func (cs *ClercStorage) Create(ctx context.Context, clerc *Clerc) error { + return cs.db.WithContext(ctx).Create(clerc).Error +} + +func (cs *ClercStorage) Get(ctx context.Context, id uuid.UUID) (*Clerc, error) { + c := new(Clerc) + err := cs.db.WithContext(ctx).First(c, id).Error + return c, err +} + +func (cs *ClercStorage) GetList(ctx context.Context) ([]*Clerc, error) { + clercs := []*Clerc{} + err := cs.db.WithContext(ctx).Find(&clercs).Error + return clercs, err +} + +func (cs *ClercStorage) Update(ctx context.Context, clerc *Clerc) error { + return cs.db.WithContext(ctx).Save(clerc).Error +} + +func (cs *ClercStorage) Delete(ctx context.Context, id uuid.UUID) error { + c := new(Clerc) + return cs.db.WithContext(ctx).Where("id = ?", id).Delete(c).Error +} + +type Service struct { + clercrep ClercRepository + config Сonfig +} + +func NewServ(cfg Сonfig, clercrep ClercRepository) *Service { + return &Service{ + config: cfg, + clercrep: clercrep, + } +} + +func (s *Service) GetHandler() http.Handler { + router := mux.NewRouter() + router.HandleFunc("/service1/clerc/post", s.Post).Methods(http.MethodPost) + router.HandleFunc("/service1/clerc/get", s.Get).Methods(http.MethodGet) + router.HandleFunc("/service1/clerc/getall", s.GetAll).Methods(http.MethodGet) + router.HandleFunc("/service1/clerc/put", s.Put).Methods(http.MethodPut) + router.HandleFunc("/service1/clerc/delete", s.Delete).Methods(http.MethodDelete) + return router +} + +type PostRequest struct { + Name string `json:"name"` + Post string `json:"post"` + OfficeId string `json:"office_id"` +} + +func (s *Service) Post(w http.ResponseWriter, r *http.Request) { + req := &PostRequest{} + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + log.Println("Не удалось разкодировать запрос", err) + w.WriteHeader(http.StatusBadRequest) + return + } + officeUuid, err := uuid.Parse(req.OfficeId) + if err != nil { + log.Println("Не удалось конвертировать строку в uuid", err) + w.WriteHeader(http.StatusBadRequest) + return + } + c := Clerc{ + Name: req.Name, + Post: req.Post, + OfficeId: officeUuid, + } + err = s.clercrep.Create(r.Context(), &c) + if err != nil { + log.Println("Не удалось создать клерка", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + log.Println("Пытаемся передать id клерка на второй сервис") + url := "http://nginx/service2/office/patch" + data := map[string]interface{}{ + "office_id": c.OfficeId.String(), + "clerc_id": c.Id.String(), + } + log.Println(c.OfficeId.String(), c.Id.String()) + payload, err := json.Marshal(data) + if err != nil { + log.Println("Не удалось закодировать данные в json") + w.WriteHeader(http.StatusInternalServerError) + return + } + request, err := http.NewRequest("PATCH", url, bytes.NewBuffer(payload)) + if err != nil { + log.Println("Не удалось создать patch запрос") + w.WriteHeader(http.StatusInternalServerError) + return + } + client := &http.Client{} + resp, err := client.Do(request) + if err != nil { + log.Println("Не удалось отправить patch запрос") + return + } + defer resp.Body.Close() + w.WriteHeader(http.StatusCreated) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(c) +} + +func (s *Service) Get(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + uuid, err := uuid.Parse(id) + if err != nil { + log.Println("Не удалось конвертировать строку в uuid", err) + w.WriteHeader(http.StatusBadRequest) + return + } + c, err := s.clercrep.Get(r.Context(), uuid) + if err != nil { + log.Println("Не удалось получить запись клерка", err) + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(c) +} + +func (s *Service) GetAll(w http.ResponseWriter, r *http.Request) { + clercs, err := s.clercrep.GetList(r.Context()) + if err != nil { + log.Println("Не удалось получить записи клерков из бд", err) + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(clercs) +} + +func (s *Service) Put(w http.ResponseWriter, r *http.Request) { + req := &Clerc{} + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + log.Println("Не удалось разкодировать запрос", err) + w.WriteHeader(http.StatusBadRequest) + return + } + err := s.clercrep.Update(r.Context(), req) + if err != nil { + log.Println("Не удалось обновить запись о пользователе", err) + w.WriteHeader(http.StatusNotFound) + return + } + c, err := s.clercrep.Get(r.Context(), req.Id) + if err != nil { + log.Println("Не удалось получить запись пользователя из бд", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(c) +} + +type DeleteRequest struct { + Id string `json:"id"` +} + +func (s *Service) Delete(w http.ResponseWriter, r *http.Request) { + req := &DeleteRequest{} + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + log.Println("Не удалось разкодировать запрос", err) + w.WriteHeader(http.StatusBadRequest) + return + } + uuid, err := uuid.Parse(req.Id) + if err != nil { + log.Println("Не удалось конвертировать строку в uuid", err) + w.WriteHeader(http.StatusBadRequest) + return + } + err = s.clercrep.Delete(r.Context(), uuid) + if err != nil { + log.Println("Не удалось удалить запись о пользователе", err) + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) +} + +func Run(cfg Сonfig) error { + db, err := InitDb(cfg) + if err != nil { + return err + } + + serv := NewServ(cfg, NewCs(db)) + s := &http.Server{ + Addr: ":13999", //"0.0.0.0:%d", cfg.Port), + Handler: serv.GetHandler(), + } + s.SetKeepAlivesEnabled(true) + ctx, cancel := context.WithCancel(context.Background()) + + go func() { + log.Printf("starting http server at %d", cfg.Port) + if err := s.ListenAndServe(); err != nil { + log.Fatal(err) + } + + }() + + gracefullyShutdown(ctx, cancel, s) + + return nil + +} + +func gracefullyShutdown(ctx context.Context, cancel context.CancelFunc, server *http.Server) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(ch) + <-ch + if err := server.Shutdown(ctx); err != nil { + log.Print(err) + } + cancel() +} diff --git a/senkin_alexander_lab_3/service2/Dockerfile b/senkin_alexander_lab_3/service2/Dockerfile new file mode 100644 index 0000000..f9d966a --- /dev/null +++ b/senkin_alexander_lab_3/service2/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:latest + +WORKDIR /app + +COPY go.mod . +COPY go.sum . + +RUN go mod download + +COPY server_run.go . + +RUN go build -o myapp + +CMD ["/app/myapp"] \ No newline at end of file diff --git a/senkin_alexander_lab_3/service2/go.mod b/senkin_alexander_lab_3/service2/go.mod new file mode 100644 index 0000000..b5026a5 --- /dev/null +++ b/senkin_alexander_lab_3/service2/go.mod @@ -0,0 +1,18 @@ +module DAS_2023_1/senkin_alexander_lab_3/service2 + +go 1.21.3 + +require ( + github.com/caarlos0/env/v8 v8.0.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/gorilla/mux v1.8.1 // 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 + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/text v0.13.0 // indirect + gorm.io/driver/postgres v1.5.4 // indirect + gorm.io/gorm v1.25.5 // indirect +) diff --git a/senkin_alexander_lab_3/service2/go.sum b/senkin_alexander_lab_3/service2/go.sum new file mode 100644 index 0000000..590a820 --- /dev/null +++ b/senkin_alexander_lab_3/service2/go.sum @@ -0,0 +1,31 @@ +github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= +github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/senkin_alexander_lab_3/service2/server_run.go b/senkin_alexander_lab_3/service2/server_run.go new file mode 100644 index 0000000..d510875 --- /dev/null +++ b/senkin_alexander_lab_3/service2/server_run.go @@ -0,0 +1,464 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "github.com/caarlos0/env/v8" + "github.com/google/uuid" + "github.com/gorilla/mux" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "log" + "net/http" + "os" + "os/signal" + "syscall" +) + +type Config struct { + Port string `env:"USER_HTTP_ADDR" envDefault:"13998"` + + PgPort string `env:"PG_PORT" envDefault:"5432"` + PgHost string `env:"PG_HOST" envDefault:"bd2"` + PgDBName string `env:"PG_DB_NAME" envDefault:"service2"` + PgUser string `env:"PG_USER" envDefault:"postgres"` + PgPwd string `env:"PG_PWD" envDefault:"159753"` +} + +func main() { + cfg := Config{} + if err := env.Parse(&cfg); err != nil { + log.Fatalf("failed to retrieve env variables, %v", err) + } + if err := Run(cfg); err != nil { + log.Fatal("error running grpc server ", err) + } +} + +type Office struct { + Id uuid.UUID `json:"id" gorm:"type:uuid;default:uuid_generate_v4();primaryKey"` + Name string `json:"name" gorm:"name"` + Adress string `json:"adress" gorm:"adress"` + Clercs []Clerc `json:"clercs" gorm:"foreignKey:OfficeId;references:Id;constraint:OnDelete:CASCADE"` +} + +type Clerc struct { + Id uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` + Office Office `gorm:"foreignKey:OfficeId"` + OfficeId uuid.UUID +} + +func InitDb(cfg Config) (*gorm.DB, error) { + dsn := fmt.Sprintf( + "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", + cfg.PgHost, cfg.PgUser, cfg.PgPwd, cfg.PgDBName, cfg.PgPort, + ) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + log.Fatal("Cannot to Connect DataBase", err) + } + db.AutoMigrate(&Office{}, &Clerc{}) + return gorm.Open(postgres.Open(dsn), &gorm.Config{}) +} + +type OfficeRepository interface { + Create(ctx context.Context, o *Office) error + Get(ctx context.Context, id uuid.UUID) (*Office, error) + GetList(ctx context.Context) ([]*Office, error) + Update(ctx context.Context, o *Office) error + Delete(ctx context.Context, id uuid.UUID) error +} + +type ClercRepository interface { + Create(ctx context.Context, c *Clerc) error + GetList(ctx context.Context) ([]*Clerc, error) +} + +type ClercStorage struct { + db *gorm.DB +} + +func NewCs(db *gorm.DB) *ClercStorage { + return &ClercStorage{ + db: db, + } +} + +func (cs *ClercStorage) Create(ctx context.Context, clerc *Clerc) error { + return cs.db.WithContext(ctx).Create(clerc).Error +} + +func (cs *ClercStorage) GetList(ctx context.Context) ([]*Clerc, error) { + clercs := []*Clerc{} + err := cs.db.WithContext(ctx).Find(&clercs).Error + return clercs, err +} + +type OfficeStorage struct { + db *gorm.DB +} + +func NewOs(db *gorm.DB) *OfficeStorage { + return &OfficeStorage{ + db: db, + } +} + +func (os *OfficeStorage) Create(ctx context.Context, clerc *Office) error { + return os.db.WithContext(ctx).Create(clerc).Error +} + +func (os *OfficeStorage) Get(ctx context.Context, id uuid.UUID) (*Office, error) { + c := new(Office) + err := os.db.WithContext(ctx).First(c, id).Error + return c, err +} + +func (os *OfficeStorage) GetList(ctx context.Context) ([]*Office, error) { + offices := []*Office{} + err := os.db.WithContext(ctx).Find(&offices).Error + return offices, err +} + +func (os *OfficeStorage) Update(ctx context.Context, office *Office) error { + return os.db.WithContext(ctx).Save(office).Error +} + +func (os *OfficeStorage) Delete(ctx context.Context, id uuid.UUID) error { + c := new(Office) + return os.db.WithContext(ctx).Where("id = ?", id).Delete(c).Error +} + +type Service struct { + officerep OfficeRepository + clercrep ClercRepository + config Config +} + +func NewServ(cfg Config, officerep OfficeRepository, clercrep ClercRepository) *Service { + return &Service{ + config: cfg, + officerep: officerep, + clercrep: clercrep, + } +} + +func (s *Service) GetHandler() http.Handler { + router := mux.NewRouter() + router.HandleFunc("/service2/office/post", s.Post).Methods(http.MethodPost) + router.HandleFunc("/service2/office/get", s.Get).Methods(http.MethodGet) + router.HandleFunc("/service2/office/getall", s.GetAll).Methods(http.MethodGet) + router.HandleFunc("/service2/office/put", s.Put).Methods(http.MethodPut) + router.HandleFunc("/service2/office/delete", s.Delete).Methods(http.MethodDelete) + router.HandleFunc("/service2/office/patch", s.Patch).Methods(http.MethodPatch) + return router +} + +type PostRequest struct { + Name string `json:"name"` + Adress string `json:"adress"` +} + +func (s *Service) Post(w http.ResponseWriter, r *http.Request) { + req := &PostRequest{} + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + log.Println("Не удалось разкодировать запрос", err) + w.WriteHeader(http.StatusBadRequest) + return + } + o := Office{ + Name: req.Name, + Adress: req.Adress, + } + err := s.officerep.Create(r.Context(), &o) + if err != nil { + log.Println("Не удалось создать офис", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusCreated) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(o) +} + +type ClercResponse struct { + Clercs []ClercReq `json:"clerks"` +} + +type ClercReq struct { + Id string `json:"id"` + Name string `json:"name"` + Post string `json:"post"` + OfficeId string `json:"office_id"` +} + +type GetResponse struct { + Id string `json:"id"` + Name string `json:"name"` + Adress string `json:"adress"` + Clercs []ClercReq `json:"clercs"` +} + +func (s *Service) Get(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + uuid, err := uuid.Parse(id) + if err != nil { + log.Println("Не удалось конвертировать строку в uuid", err) + w.WriteHeader(http.StatusBadRequest) + return + } + o, err := s.officerep.Get(r.Context(), uuid) + if err != nil { + log.Println("Не удалось получить запись офиса", err) + w.WriteHeader(http.StatusNotFound) + return + } + url := "http://nginx/service1/clerc/getall" + response, err := http.Get(url) + if err != nil { + fmt.Println("Error:", err) + return + } + defer response.Body.Close() + /*body, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Println("Не удалось прочитать body из Get response с первого сервиса", err) + w.WriteHeader(http.StatusInternalServerError) + return + }*/ + var clercsreq []ClercReq + if err := json.NewDecoder(response.Body).Decode(&clercsreq); err != nil { + log.Println("Не удалось разпарсить ответ с 1 сервиса", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + //var clerksResponse ClercResponse + /*err = json.Unmarshal(body, clercsreq) + if err != nil { + log.Println("Не удалось разпарсить ответ с 1 сервиса", err) + w.WriteHeader(http.StatusInternalServerError) + return + }*/ + + cs, err := s.clercrep.GetList(r.Context()) + if err != nil { + log.Println("Не удалось получить записи клерков из бд", err) + w.WriteHeader(http.StatusNotFound) + return + } + clercsid := make([]string, len(cs)) + for _, c := range cs { + if o.Id == c.OfficeId { + clercsid = append(clercsid, c.Id.String()) + } + } + + clercs := make([]ClercReq, len(o.Clercs)) + for _, clercid := range clercsid { + for _, c := range clercsreq { + if clercid == c.Id { + clercs = append(clercs, c) + } + } + } + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(&GetResponse{ + Id: o.Id.String(), + Name: o.Name, + Adress: o.Adress, + Clercs: clercs, + }) +} + +func (s *Service) GetAll(w http.ResponseWriter, r *http.Request) { + offices, err := s.officerep.GetList(r.Context()) + if err != nil { + log.Println("Не удалось получить записи офисов из бд", err) + w.WriteHeader(http.StatusNotFound) + return + } + clercs, err := s.clercrep.GetList(r.Context()) + if err != nil { + log.Println("Не удалось получить записи клерков из бд", err) + w.WriteHeader(http.StatusNotFound) + return + } + //log.Println(clercs) + for i, o := range offices { + for _, c := range clercs { + if o.Id == c.OfficeId { + offices[i].Clercs = append(offices[i].Clercs, *c) + } + } + } + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(offices) +} + +type PatchRequest struct { + OfficeId string `json:"office_id"` + ClercId string `json:"clerc_id"` +} + +func (s *Service) Patch(w http.ResponseWriter, r *http.Request) { + log.Println("Использована функция добавления клерка в офис") + req := &PatchRequest{} + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + log.Println("Не удалось разкодировать запрос", err) + w.WriteHeader(http.StatusBadRequest) + return + } + log.Println(req) + uuidOffice, err := uuid.Parse(req.OfficeId) + if err != nil { + log.Println("Не удалось преобразовать строку в uuid", err) + w.WriteHeader(http.StatusBadRequest) + return + } + uuidClerc, err := uuid.Parse(req.ClercId) + if err != nil { + log.Println("Не удалось преобразовать строку в uuid", err) + w.WriteHeader(http.StatusBadRequest) + return + } + log.Println(uuidOffice) + o, err := s.officerep.Get(r.Context(), uuidOffice) + if err != nil { + log.Println("Не удалось получить запись офиса из бд", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + log.Println(*o) + clerc := &Clerc{ + Id: uuidClerc, + Office: *o, + } + /*o.Clercs = append(o.Clercs, *clerc) + err = s.officerep.Update(r.Context(), o) + if err != nil { + log.Println("Не удалось обновить информации об офисе") + w.WriteHeader(http.StatusInternalServerError) + return + }*/ + err = s.clercrep.Create(r.Context(), clerc) + if err != nil { + log.Println("Не удалось создать запись о клерке", err) + w.WriteHeader(http.StatusBadRequest) + return + } + log.Println(*clerc) + w.WriteHeader(http.StatusOK) +} + +type PutRequest struct { + Id string `json:"id"` + Name string `json:"name"` + Adress string `json:"adress"` +} + +func (s *Service) Put(w http.ResponseWriter, r *http.Request) { + req := &PutRequest{} + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + log.Println("Не удалось разкодировать запрос", err) + w.WriteHeader(http.StatusBadRequest) + return + } + uuid, err := uuid.Parse(req.Id) + if err != nil { + log.Println("Не удалось преобразовать строку в uuid", err) + w.WriteHeader(http.StatusBadRequest) + return + } + o, err := s.officerep.Get(r.Context(), uuid) + if err != nil { + log.Println("Не удалось получить запись офиса из бд", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + o.Name = req.Name + o.Adress = req.Adress + err = s.officerep.Update(r.Context(), o) + if err != nil { + log.Println("Не удалось обновить запись об офисе", err) + w.WriteHeader(http.StatusNotFound) + return + } + o2, err := s.officerep.Get(r.Context(), uuid) + if err != nil { + log.Println("Не удалось получить запись офиса из бд", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(o2) +} + +type DeleteRequest struct { + Id string `json:"id"` +} + +func (s *Service) Delete(w http.ResponseWriter, r *http.Request) { + req := &DeleteRequest{} + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + log.Println("Не удалось разкодировать запрос", err) + w.WriteHeader(http.StatusBadRequest) + return + } + uuid, err := uuid.Parse(req.Id) + if err != nil { + log.Println("Не удалось конвертировать строку в uuid", err) + w.WriteHeader(http.StatusBadRequest) + return + } + err = s.officerep.Delete(r.Context(), uuid) + if err != nil { + log.Println("Не удалось удалить запись об офисе", err) + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) +} + +func Run(cfg Config) error { + db, err := InitDb(cfg) + if err != nil { + return err + } + + serv := NewServ(cfg, NewOs(db), NewCs(db)) + s := &http.Server{ + Addr: ":13998", //"0.0.0.0:%d", cfg.Port), + Handler: serv.GetHandler(), + } + s.SetKeepAlivesEnabled(true) + ctx, cancel := context.WithCancel(context.Background()) + + go func() { + log.Printf("starting http server at %d", cfg.Port) + if err := s.ListenAndServe(); err != nil { + log.Fatal(err) + } + + }() + + gracefullyShutdown(ctx, cancel, s) + + return nil + +} + +func gracefullyShutdown(ctx context.Context, cancel context.CancelFunc, server *http.Server) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(ch) + <-ch + if err := server.Shutdown(ctx); err != nil { + log.Print(err) + } + cancel() +}