From 940cc6757faab7cd7389edf8a739aea802f607ab Mon Sep 17 00:00:00 2001 From: AnnZhimol Date: Sat, 5 Oct 2024 21:14:57 +0300 Subject: [PATCH] lab 3 complete --- zhimolostnova_anna_lab_3/.env | 9 + zhimolostnova_anna_lab_3/README.md | 90 ++++++ zhimolostnova_anna_lab_3/docker-compose.yml | 64 +++++ .../messageService/Dockerfile | 14 + .../messageService/cmd/message_main.go | 64 +++++ .../messageService/docs/doc.json | 244 ++++++++++++++++ .../messageService/docs/docs.go | 269 ++++++++++++++++++ .../messageService/docs/swagger.json | 244 ++++++++++++++++ .../messageService/docs/swagger.yaml | 162 +++++++++++ .../messageService/go.mod | 39 +++ .../internal/app/database/db.go | 59 ++++ .../internal/app/handlers/message_handler.go | 244 ++++++++++++++++ .../internal/app/models/message.go | 12 + .../internal/app/models/message_response.go | 10 + .../internal/app/models/stream_response.go | 8 + .../app/repositories/message_repository.go | 64 +++++ .../internal/app/services/message_service.go | 47 +++ .../internal/app/services/stream_client.go | 62 ++++ zhimolostnova_anna_lab_3/nginx.conf | 61 ++++ .../streamService/Dockerfile | 14 + .../streamService/cmd/stream_main.go | 59 ++++ .../streamService/docs/doc.json | 191 +++++++++++++ .../streamService/docs/docs.go | 216 ++++++++++++++ .../streamService/docs/swagger.json | 191 +++++++++++++ .../streamService/docs/swagger.yaml | 127 +++++++++ zhimolostnova_anna_lab_3/streamService/go.mod | 39 +++ .../streamService/internal/app/database/db.go | 31 ++ .../internal/app/handlers/stream_handler.go | 168 +++++++++++ .../internal/app/models/stream.go | 12 + .../app/repositories/stream_repository.go | 55 ++++ .../internal/app/services/stream_service.go | 42 +++ 31 files changed, 2911 insertions(+) create mode 100644 zhimolostnova_anna_lab_3/.env create mode 100644 zhimolostnova_anna_lab_3/README.md create mode 100644 zhimolostnova_anna_lab_3/docker-compose.yml create mode 100644 zhimolostnova_anna_lab_3/messageService/Dockerfile create mode 100644 zhimolostnova_anna_lab_3/messageService/cmd/message_main.go create mode 100644 zhimolostnova_anna_lab_3/messageService/docs/doc.json create mode 100644 zhimolostnova_anna_lab_3/messageService/docs/docs.go create mode 100644 zhimolostnova_anna_lab_3/messageService/docs/swagger.json create mode 100644 zhimolostnova_anna_lab_3/messageService/docs/swagger.yaml create mode 100644 zhimolostnova_anna_lab_3/messageService/go.mod create mode 100644 zhimolostnova_anna_lab_3/messageService/internal/app/database/db.go create mode 100644 zhimolostnova_anna_lab_3/messageService/internal/app/handlers/message_handler.go create mode 100644 zhimolostnova_anna_lab_3/messageService/internal/app/models/message.go create mode 100644 zhimolostnova_anna_lab_3/messageService/internal/app/models/message_response.go create mode 100644 zhimolostnova_anna_lab_3/messageService/internal/app/models/stream_response.go create mode 100644 zhimolostnova_anna_lab_3/messageService/internal/app/repositories/message_repository.go create mode 100644 zhimolostnova_anna_lab_3/messageService/internal/app/services/message_service.go create mode 100644 zhimolostnova_anna_lab_3/messageService/internal/app/services/stream_client.go create mode 100644 zhimolostnova_anna_lab_3/nginx.conf create mode 100644 zhimolostnova_anna_lab_3/streamService/Dockerfile create mode 100644 zhimolostnova_anna_lab_3/streamService/cmd/stream_main.go create mode 100644 zhimolostnova_anna_lab_3/streamService/docs/doc.json create mode 100644 zhimolostnova_anna_lab_3/streamService/docs/docs.go create mode 100644 zhimolostnova_anna_lab_3/streamService/docs/swagger.json create mode 100644 zhimolostnova_anna_lab_3/streamService/docs/swagger.yaml create mode 100644 zhimolostnova_anna_lab_3/streamService/go.mod create mode 100644 zhimolostnova_anna_lab_3/streamService/internal/app/database/db.go create mode 100644 zhimolostnova_anna_lab_3/streamService/internal/app/handlers/stream_handler.go create mode 100644 zhimolostnova_anna_lab_3/streamService/internal/app/models/stream.go create mode 100644 zhimolostnova_anna_lab_3/streamService/internal/app/repositories/stream_repository.go create mode 100644 zhimolostnova_anna_lab_3/streamService/internal/app/services/stream_service.go diff --git a/zhimolostnova_anna_lab_3/.env b/zhimolostnova_anna_lab_3/.env new file mode 100644 index 0000000..65f17a9 --- /dev/null +++ b/zhimolostnova_anna_lab_3/.env @@ -0,0 +1,9 @@ +# Переменные для базы данных Stream +DB_HOST=postgres-db +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=password +DB_STREAM_NAME=streamdb + +# Переменные для базы данных Message +DB_MESSAGE_NAME=messagedb diff --git a/zhimolostnova_anna_lab_3/README.md b/zhimolostnova_anna_lab_3/README.md new file mode 100644 index 0000000..88b17f2 --- /dev/null +++ b/zhimolostnova_anna_lab_3/README.md @@ -0,0 +1,90 @@ +# Отчет по лабораторной работе №3 + +## Поставленные задачи + +1. Создать 2 микросервиса, реализующих CRUD на связанных сущностях. +2. Реализовать механизм синхронного обмена сообщениями между микросервисами. +3. Реализовать шлюз на основе прозрачного прокси-сервера nginx. + +## Предметная область + +Проект основан на системе управления стримами (трансляциями) и сообщениями. +Каждый стрим представляет собой событие с такими параметрами, как название, +время начала и окончания (окончание может быть null). +Сообщения связаны с конкретными стримами и содержат следующие атрибуты: содержание, +дата публикации, идентификатор стрима, к которому имеет отношение комментарий. Связь между сущностями один ко многим +Два микросервиса выполняют раздельные функции: один управляет стримами, другой — сообщениями. +Взаимодействие между ними происходит через HTTP-запросы. + +## Запуск работы + +1. Убедиться, что установлены необходимые технологии: + - Docker: Платформа для контейнеризации приложений. + - Docker Compose: Инструмент для запуска многоконтейнерных приложений на основе `docker-compose.yaml`. Обычно поставляется вместе с Docker. Чтобы проверить, установлена ли утилита, нужно запустить команду: + ```bash + docker-compose --version + ``` + +2. В директории, где находится файл `docker-compose.yaml`, выполнить следующую команду для запуска всех сервисов: + ```bash + docker-compose up --build + ``` + Эта команда сначала выполнит сборку, а затем запустит контейнеры. + +3. После успешного запуска можно перейти к Swagger UI: + - Message Swagger UI: [http://localhost/message-service/swagger/index.html](http://localhost/message-service/swagger/index.html#/). + - Stream Swagger UI: [http://localhost/stream-service/swagger/index.html](http://localhost/message-service/swagger/index.html#/). + +4. Stream сервис предоставляет следующие эндпоинты: + - GET /streams — получить все стримы + - POST /streams — создать новый стрим + - GET /streams/{id} — получить стрим по ID + - PUT /streams/{id} — обновить стрим по ID + - DELETE /streams/{id} — удалить стрим по ID + + Message сервис предоставляет: + - GET /messages — получить все сообщения + - POST /messages — создать новое сообщение + - GET /messages/{id} — получить сообщение по ID + - GET /messages/all/{streamId} — получить все сообщения, которые относятся к streamId + - PUT /messages/{id} — обновить сообщение по ID + - DELETE /messages/{id} — удалить сообщение по ID + +## Технологии + +1. Golang: основной язык программирования для микросервисов. +2. PostgreSQL: база данных для хранения данных о стримах и сообщениях. +3. Docker & Docker Compose: для контейнеризации сервисов и удобного развертывания. +4. Swagger UI: для документации и тестирования API. +5. Nginx: для проксирования запросов между клиентом и микросервисами. +6. Gorilla Mux: маршрутизация запросов в Go. +7. Zerolog: логирование ошибок и действий. + +## Архитектура + +Проект реализован на основе микросервисной архитектуры. Каждый сервис (Stream и Message) +использует свою собственную базу данных (всего их две и в каждой БД по одной таблице) и предоставляет +свои эндпоинты для работы с данными. Используются Docker и Docker Compose для управления окружением и +запуска сервисов. + +## Ход работы + +1. **Микросервисная архитектура**: Проект разделен на два микросервиса: один для управления стримами, + другой — для управления сообщениями. Каждый сервис имеет свой собственный набор функций, таблицы + в базе данных и собственные эндпоинты для взаимодействия с клиентом. + +2. **Реализация взаимодействия между микросервисами**: Stream-сервис предоставляет заголовки для стримов, + которые затем использует Message-сервис для работы с сообщениями, привязанными к стримам. + Взаимодействие реализовано через HTTP-запросы между сервисами. + +3. **Использование Swagger**: Для каждого сервиса настроена Swagger-документация. Это позволяет + визуализировать и тестировать API прямо в браузере, что упрощает разработку и тестирование. + +4. **Докеризация проекта**: Все микросервисы и вспомогательные сервисы, такие как база данных и Nginx, + запускаются через Docker Compose. Это упрощает управление зависимостями и разворачивание проекта. + +В коде присутствуют пояснительные комментарии. + +## Демонстрационное видео + +Видеозапись доступна по адресу: [https://vk.com/video193898050_456240870](https://vk.com/video193898050_456240870) \ No newline at end of file diff --git a/zhimolostnova_anna_lab_3/docker-compose.yml b/zhimolostnova_anna_lab_3/docker-compose.yml new file mode 100644 index 0000000..c155e82 --- /dev/null +++ b/zhimolostnova_anna_lab_3/docker-compose.yml @@ -0,0 +1,64 @@ +services: + # Общая база данных для обоих сервисов + postgres-db: + image: postgres:13-alpine + environment: + POSTGRES_USER: ${DB_USER} # Общий пользователь базы данных + POSTGRES_PASSWORD: ${DB_PASSWORD} # Пароль для базы данных + POSTGRES_DB: ${DB_STREAM_NAME} # База данных для сервиса Stream + ports: + - "5432:5432" # Порт базы данных + volumes: + - postgres-db-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"] + interval: 20s + timeout: 20s + retries: 10 + networks: + - network + + # Сервис Stream + stream-service: + build: ./streamService + env_file: + - .env + networks: + - network + ports: + - "8000:8000" + depends_on: + postgres-db: + condition: service_healthy + + # Сервис Message + message-service: + build: ./messageService + env_file: + - .env + networks: + - network + ports: + - "8080:8080" + depends_on: + postgres-db: + condition: service_healthy + + nginx: + image: nginx + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + networks: + - network + depends_on: + - stream-service + - message-service + +networks: + network: + driver: bridge + +volumes: + postgres-db-data: diff --git a/zhimolostnova_anna_lab_3/messageService/Dockerfile b/zhimolostnova_anna_lab_3/messageService/Dockerfile new file mode 100644 index 0000000..2953720 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.23-alpine + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN go build -o message_main ./cmd/message_main.go + +EXPOSE 8080 + +CMD ["./message_main"] diff --git a/zhimolostnova_anna_lab_3/messageService/cmd/message_main.go b/zhimolostnova_anna_lab_3/messageService/cmd/message_main.go new file mode 100644 index 0000000..96758fd --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/cmd/message_main.go @@ -0,0 +1,64 @@ +package main + +import ( + "github.com/gorilla/mux" + "github.com/rs/zerolog" + httpSwagger "github.com/swaggo/http-swagger" + "log" + _ "messageService/docs" + "messageService/internal/app/database" + "messageService/internal/app/handlers" + "messageService/internal/app/repositories" + "messageService/internal/app/services" + "net/http" + "os" + "time" +) + +func main() { + // Настройка логирования + logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("Could not open log file: %v", err) + } + defer func(logFile *os.File) { + err := logFile.Close() + if err != nil { + + } + }(logFile) + + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + logger := zerolog.New(logFile).With().Timestamp().Logger() + + // Инициализация базы данных + db, err := database.InitMessageDB() + if err != nil { + log.Fatalf("Could not connect to the database: %v", err) + } + + // Инициализация репозитория и сервиса + messageRepo := repositories.NewMessageRepository(db) + messageService := services.NewMessageService(messageRepo) + + // Инициализация маршрутизатора + router := mux.NewRouter() + + // Создаем StreamClient + streamClient := services.NewStreamClient("http://stream-service:8000", 5*time.Second) + + // Регистрация хендлеров + handlers.RegisterMessageRoutes(router, messageService, streamClient) + + // Swagger + router.HandleFunc("/doc.json", func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "docs/doc.json") + }).Methods("GET") + + // Route for Swagger UI + router.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler) + + // Запуск сервера + logger.Info().Msg("Server started at http://localhost:8080") + logger.Fatal().Err(http.ListenAndServe(":8080", router)).Msg("Server crashed") +} diff --git a/zhimolostnova_anna_lab_3/messageService/docs/doc.json b/zhimolostnova_anna_lab_3/messageService/docs/doc.json new file mode 100644 index 0000000..19186f7 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/docs/doc.json @@ -0,0 +1,244 @@ +{ + "swagger": "2.0", + "info": { + "contact": {}, + "version": "", + "title": "" + }, + "paths": { + "/message-service/messages": { + "get": { + "description": "Возвращает список всех сообщений", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить все сообщения", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, + "post": { + "description": "Создает новое сообщение", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Создать сообщение", + "parameters": [ + { + "description": "Данные сообщения", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Message" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, + "/message-service/messages/all/{streamId}": { + "get": { + "description": "Возвращает список сообщений с указанным StreamID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить список сообщений по StreamID", + "parameters": [ + { + "type": "integer", + "description": "StreamID сообщения", + "name": "streamId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MessageResponse" + } + } + } + } + } + }, + "/message-service/messages/{id}": { + "get": { + "description": "Возвращает сообщение с указанным ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить сообщение по ID", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + }, + "put": { + "description": "Обновляет данные сообщения по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Обновить данные сообщения", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Обновленные данные сообщения", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Message" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + }, + "delete": { + "description": "Удаляет сообщение по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Удалить сообщение", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "models.Message": { + "description": "Сообщение", + "type": "object", + "properties": { + "content": { + "description": "Содержание комментария", + "type": "string" + }, + "created_at": { + "description": "Время отправления комментария", + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "id": { + "description": "Идентификатор сообщения", + "type": "integer" + }, + "stream_id": { + "description": "Идентификатор стрима", + "type": "integer" + } + } + }, + "models.MessageResponse": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "id": { + "type": "integer" + }, + "stream_title": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/zhimolostnova_anna_lab_3/messageService/docs/docs.go b/zhimolostnova_anna_lab_3/messageService/docs/docs.go new file mode 100644 index 0000000..b3a0bdb --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/docs/docs.go @@ -0,0 +1,269 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/message-service/messages": { + "get": { + "description": "Возвращает список всех сообщений", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить все сообщения", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, + "post": { + "description": "Создает новое сообщение", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Создать сообщение", + "parameters": [ + { + "description": "Данные сообщения", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Message" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, + "/message-service/messages/all/{streamId}": { + "get": { + "description": "Возвращает список сообщений с указанным StreamID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить список сообщений по StreamID", + "parameters": [ + { + "type": "integer", + "description": "StreamID сообщения", + "name": "streamId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MessageResponse" + } + } + } + } + } + }, + "/message-service/messages/{id}": { + "get": { + "description": "Возвращает сообщение с указанным ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить сообщение по ID", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + }, + "put": { + "description": "Обновляет данные сообщения по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Обновить данные сообщения", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Обновленные данные сообщения", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Message" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + }, + "delete": { + "description": "Удаляет сообщение по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Удалить сообщение", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "models.Message": { + "description": "Сообщение", + "type": "object", + "properties": { + "content": { + "description": "Содержание комментария", + "type": "string" + }, + "created_at": { + "description": "Время отправления комментария", + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "id": { + "description": "Идентификатор сообщения", + "type": "integer" + }, + "stream_id": { + "description": "Идентификатор стрима", + "type": "integer" + } + } + }, + "models.MessageResponse": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "id": { + "type": "integer" + }, + "stream_title": { + "type": "string" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/zhimolostnova_anna_lab_3/messageService/docs/swagger.json b/zhimolostnova_anna_lab_3/messageService/docs/swagger.json new file mode 100644 index 0000000..1083770 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/docs/swagger.json @@ -0,0 +1,244 @@ +{ + "swagger": "2.0", + "info": { + "contact": {}, + "version": "", + "title": "" + }, + "paths": { + "/messages": { + "get": { + "description": "Возвращает список всех сообщений", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить все сообщения", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, + "post": { + "description": "Создает новое сообщение", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Создать сообщение", + "parameters": [ + { + "description": "Данные сообщения", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Message" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, + "/messages/all/{streamId}": { + "get": { + "description": "Возвращает список сообщений с указанным StreamID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить список сообщений по StreamID", + "parameters": [ + { + "type": "integer", + "description": "StreamID сообщения", + "name": "streamId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.MessageResponse" + } + } + } + } + } + }, + "/messages/{id}": { + "get": { + "description": "Возвращает сообщение с указанным ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Получить сообщение по ID", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + }, + "put": { + "description": "Обновляет данные сообщения по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Обновить данные сообщения", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Обновленные данные сообщения", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Message" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + }, + "delete": { + "description": "Удаляет сообщение по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "messages" + ], + "summary": "Удалить сообщение", + "parameters": [ + { + "type": "integer", + "description": "ID сообщения", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "models.Message": { + "description": "Сообщение", + "type": "object", + "properties": { + "content": { + "description": "Содержание комментария", + "type": "string" + }, + "created_at": { + "description": "Время отправления комментария", + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "id": { + "description": "Идентификатор сообщения", + "type": "integer" + }, + "stream_id": { + "description": "Идентификатор стрима", + "type": "integer" + } + } + }, + "models.MessageResponse": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "id": { + "type": "integer" + }, + "stream_title": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/zhimolostnova_anna_lab_3/messageService/docs/swagger.yaml b/zhimolostnova_anna_lab_3/messageService/docs/swagger.yaml new file mode 100644 index 0000000..9c5941a --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/docs/swagger.yaml @@ -0,0 +1,162 @@ +definitions: + models.Message: + description: Сообщение + properties: + content: + description: Содержание комментария + type: string + created_at: + description: Время отправления комментария + example: "2024-10-04T14:48:00Z" + type: string + id: + description: Идентификатор сообщения + type: integer + stream_id: + description: Идентификатор стрима + type: integer + type: object + models.MessageResponse: + properties: + content: + type: string + created_at: + example: "2024-10-04T14:48:00Z" + type: string + id: + type: integer + stream_title: + type: string + type: object +info: + contact: { } + version: '2.0' + title: 'API' +paths: + /messages: + get: + consumes: + - application/json + description: Возвращает список всех сообщений + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Message' + type: array + summary: Получить все сообщения + tags: + - messages + post: + consumes: + - application/json + description: Создает новое сообщение + parameters: + - description: Данные сообщения + in: body + name: message + required: true + schema: + $ref: '#/definitions/models.Message' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Message' + summary: Создать сообщение + tags: + - messages + /messages/{id}: + delete: + consumes: + - application/json + description: Удаляет сообщение по ID + parameters: + - description: ID сообщения + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "204": + description: No Content + summary: Удалить сообщение + tags: + - messages + get: + consumes: + - application/json + description: Возвращает сообщение с указанным ID + parameters: + - description: ID сообщения + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Message' + summary: Получить сообщение по ID + tags: + - messages + put: + consumes: + - application/json + description: Обновляет данные сообщения по ID + parameters: + - description: ID сообщения + in: path + name: id + required: true + type: integer + - description: Обновленные данные сообщения + in: body + name: message + required: true + schema: + $ref: '#/definitions/models.Message' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Message' + summary: Обновить данные сообщения + tags: + - messages + /messages/all/{streamId}: + get: + consumes: + - application/json + description: Возвращает список сообщений с указанным StreamID + parameters: + - description: StreamID сообщения + in: path + name: streamId + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.MessageResponse' + type: array + summary: Получить список сообщений по StreamID + tags: + - messages +swagger: "2.0" diff --git a/zhimolostnova_anna_lab_3/messageService/go.mod b/zhimolostnova_anna_lab_3/messageService/go.mod new file mode 100644 index 0000000..143c3c0 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/go.mod @@ -0,0 +1,39 @@ +module messageService + +go 1.23.2 + +require ( + github.com/gorilla/mux v1.8.1 + github.com/rs/zerolog v1.33.0 + github.com/swaggo/http-swagger v1.3.4 + github.com/swaggo/swag v1.8.1 + gorm.io/driver/postgres v1.5.9 + gorm.io/gorm v1.25.12 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.6 // indirect + github.com/go-openapi/swag v0.19.15 // 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.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/zhimolostnova_anna_lab_3/messageService/internal/app/database/db.go b/zhimolostnova_anna_lab_3/messageService/internal/app/database/db.go new file mode 100644 index 0000000..f7f6fc8 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/internal/app/database/db.go @@ -0,0 +1,59 @@ +package database + +import ( + "fmt" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "messageService/internal/app/models" + "os" +) + +// InitMessageDB инициализирует подключение к базе данных для микросервиса Message +func InitMessageDB() (*gorm.DB, error) { + host := os.Getenv("DB_HOST") + port := os.Getenv("DB_PORT") + user := os.Getenv("DB_USER") + password := os.Getenv("DB_PASSWORD") + dbname := os.Getenv("DB_MESSAGE_NAME") + + // Строка подключения без указания имени базы данных + dsn := fmt.Sprintf("host=%s user=%s password=%s port=%s sslmode=disable", host, user, password, port) + + // Подключаемся к PostgreSQL без указания базы данных + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), // Включаем логирование для отладки + }) + if err != nil { + return nil, err + } + + // Проверяем существование базы данных + var exists bool + if err := db.Raw("SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = ?)", dbname).Scan(&exists).Error; err != nil { + return nil, err + } + + // Если база данных не существует, создаем ее + if !exists { + if err := db.Exec(fmt.Sprintf("CREATE DATABASE %s;", dbname)).Error; err != nil { + return nil, err + } + } + + // Подключаемся к только что созданной базе данных + dsn = fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", host, user, password, dbname, port) + db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + if err != nil { + return nil, err + } + + // Автоматическая миграция для Message + if err := db.AutoMigrate(&models.Message{}); err != nil { + return nil, err + } + + return db, nil +} diff --git a/zhimolostnova_anna_lab_3/messageService/internal/app/handlers/message_handler.go b/zhimolostnova_anna_lab_3/messageService/internal/app/handlers/message_handler.go new file mode 100644 index 0000000..199edec --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/internal/app/handlers/message_handler.go @@ -0,0 +1,244 @@ +package handlers + +import ( + "encoding/json" + "log" + "messageService/internal/app/models" + "messageService/internal/app/services" + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +func RegisterMessageRoutes(router *mux.Router, svc services.MessageService, streamSvc services.StreamClient) { + router.HandleFunc("/messages", GetAllMessages(svc, streamSvc)).Methods("GET") + router.HandleFunc("/messages/{id}", GetMessage(svc, streamSvc)).Methods("GET") + router.HandleFunc("/messages/all/{streamId}", GetMessagesByStreamID(svc, streamSvc)).Methods("GET") + router.HandleFunc("/messages", CreateMessage(svc)).Methods("POST") + router.HandleFunc("/messages/{id}", UpdateMessage(svc)).Methods("PUT") + router.HandleFunc("/messages/{id}", DeleteMessage(svc)).Methods("DELETE") +} + +// GetAllMessages godoc +// @Summary Получить все сообщения +// @Description Возвращает список всех сообщений +// @Tags messages +// @Accept json +// @Produce json +// @Success 200 {array} models.Message +// @Router /messages [get] +func GetAllMessages(svc services.MessageService, streamSvc services.StreamClient) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + messages, err := svc.GetAllMessages() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var response []models.MessageResponse + for _, message := range messages { + stream, err := streamSvc.GetStreamTitleByID(message.StreamID) + log.Printf("Result: %s", stream) + if err != nil { + http.Error(w, "Stream not found", http.StatusNotFound) + return + } + response = append(response, models.MessageResponse{ + ID: message.ID, + Content: message.Content, + StreamTitle: stream, + CreatedAt: message.CreatedAt, + }) + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// GetMessagesByStreamID godoc +// @Summary Получить список сообщений по StreamID +// @Description Возвращает список сообщений с указанным StreamID +// @Tags messages +// @Accept json +// @Produce json +// @Param streamId path int true "StreamID сообщения" +// @Success 200 {array} models.MessageResponse +// @Router /messages/all/{streamId} [get] +func GetMessagesByStreamID(svc services.MessageService, streamSvc services.StreamClient) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + idStr := mux.Vars(r)["streamId"] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid StreamID", http.StatusBadRequest) + return + } + + messages, err := svc.GetMessagesByStreamID(uint(id)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var response []models.MessageResponse + for _, message := range messages { + stream, err := streamSvc.GetStreamTitleByID(message.StreamID) + if err != nil { + http.Error(w, "Stream not found", http.StatusNotFound) + return + } + response = append(response, models.MessageResponse{ + ID: message.ID, + Content: message.Content, + StreamTitle: stream, + CreatedAt: message.CreatedAt, + }) + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// GetMessage godoc +// @Summary Получить сообщение по ID +// @Description Возвращает сообщение с указанным ID +// @Tags messages +// @Accept json +// @Produce json +// @Param id path int true "ID сообщения" +// @Success 200 {object} models.Message +// @Router /messages/{id} [get] +func GetMessage(svc services.MessageService, streamSvc services.StreamClient) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + idStr := mux.Vars(r)["id"] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid ID", http.StatusBadRequest) + return + } + + message, err := svc.GetMessageByID(uint(id)) + if err != nil { + http.Error(w, "Message not found", http.StatusNotFound) + return + } + + var response models.MessageResponse + stream, err := streamSvc.GetStreamTitleByID(message.StreamID) + if err != nil { + http.Error(w, "Stream not found", http.StatusNotFound) + return + } + + response = models.MessageResponse{ + ID: message.ID, + Content: message.Content, + StreamTitle: stream, + CreatedAt: message.CreatedAt, + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// CreateMessage godoc +// @Summary Создать сообщение +// @Description Создает новое сообщение +// @Tags messages +// @Accept json +// @Produce json +// @Param message body models.Message true "Данные сообщения" +// @Success 200 {object} models.Message +// @Router /messages [post] +func CreateMessage(svc services.MessageService) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var message models.Message + err := json.NewDecoder(r.Body).Decode(&message) + if err != nil { + http.Error(w, "Failed to decode request body", http.StatusBadRequest) + return + } + err = svc.CreateMessage(&message) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(message) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// UpdateMessage godoc +// @Summary Обновить данные сообщения +// @Description Обновляет данные сообщения по ID +// @Tags messages +// @Accept json +// @Produce json +// @Param id path int true "ID сообщения" +// @Param message body models.Message true "Обновленные данные сообщения" +// @Success 200 {object} models.Message +// @Router /messages/{id} [put] +func UpdateMessage(svc services.MessageService) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var message models.Message + idStr := mux.Vars(r)["id"] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid ID", http.StatusBadRequest) + return + } + + if err := json.NewDecoder(r.Body).Decode(&message); err != nil { + http.Error(w, "Failed to decode request body", http.StatusBadRequest) + return + } + message.ID = uint(id) + err = svc.UpdateMessage(&message) + if err != nil { + http.Error(w, "Message not found", http.StatusNotFound) + return + } + err = json.NewEncoder(w).Encode(message) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// DeleteMessage godoc +// @Summary Удалить сообщение +// @Description Удаляет сообщение по ID +// @Tags messages +// @Accept json +// @Produce json +// @Param id path int true "ID сообщения" +// @Success 204 "No Content" +// @Router /messages/{id} [delete] +func DeleteMessage(svc services.MessageService) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + idStr := mux.Vars(r)["id"] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid ID", http.StatusBadRequest) + return + } + err = svc.DeleteMessage(uint(id)) + if err != nil { + http.Error(w, "Message not found", http.StatusNotFound) + return + } + w.WriteHeader(http.StatusNoContent) + } +} diff --git a/zhimolostnova_anna_lab_3/messageService/internal/app/models/message.go b/zhimolostnova_anna_lab_3/messageService/internal/app/models/message.go new file mode 100644 index 0000000..8bb9270 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/internal/app/models/message.go @@ -0,0 +1,12 @@ +package models + +import "time" + +// Message представляет сообщение в системе +// @Description Сообщение +type Message struct { + ID uint `gorm:"primaryKey" json:"id"` // Идентификатор сообщения + StreamID uint `json:"stream_id"` // Идентификатор стрима + Content string `json:"content"` // Содержание комментария + CreatedAt time.Time `json:"created_at" example:"2024-10-04T14:48:00Z"` // Время отправления комментария +} diff --git a/zhimolostnova_anna_lab_3/messageService/internal/app/models/message_response.go b/zhimolostnova_anna_lab_3/messageService/internal/app/models/message_response.go new file mode 100644 index 0000000..2b3b8a1 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/internal/app/models/message_response.go @@ -0,0 +1,10 @@ +package models + +import "time" + +type MessageResponse struct { + ID uint `json:"id"` + Content string `json:"content"` + StreamTitle string `json:"stream_title"` + CreatedAt time.Time `json:"created_at" example:"2024-10-04T14:48:00Z"` +} diff --git a/zhimolostnova_anna_lab_3/messageService/internal/app/models/stream_response.go b/zhimolostnova_anna_lab_3/messageService/internal/app/models/stream_response.go new file mode 100644 index 0000000..8122540 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/internal/app/models/stream_response.go @@ -0,0 +1,8 @@ +package models + +type StreamResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + StartedAt string `json:"started_at"` + EndedAt *string `json:"ended_at"` +} diff --git a/zhimolostnova_anna_lab_3/messageService/internal/app/repositories/message_repository.go b/zhimolostnova_anna_lab_3/messageService/internal/app/repositories/message_repository.go new file mode 100644 index 0000000..318d707 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/internal/app/repositories/message_repository.go @@ -0,0 +1,64 @@ +package repositories + +import ( + "gorm.io/gorm" + "messageService/internal/app/models" +) + +type MessageRepository interface { + GetAllMessages() ([]models.Message, error) + GetMessageByID(id uint) (*models.Message, error) + GetMessagesByStreamID(streamID uint) ([]models.Message, error) + CreateMessage(message *models.Message) error + UpdateMessage(message *models.Message) error + DeleteMessage(id uint) error +} + +type messageRepository struct { + db *gorm.DB +} + +func NewMessageRepository(db *gorm.DB) MessageRepository { + return &messageRepository{db: db} +} + +func (r *messageRepository) GetAllMessages() ([]models.Message, error) { + var messages []models.Message + if err := r.db.Find(&messages).Error; err != nil { + return nil, err + } + return messages, nil +} + +func (r *messageRepository) GetMessageByID(id uint) (*models.Message, error) { + var message models.Message + if err := r.db.First(&message, id).Error; err != nil { + return nil, err + } + return &message, nil +} + +func (r *messageRepository) GetMessagesByStreamID(streamID uint) ([]models.Message, error) { + var messages []models.Message + if err := r.db.Where("stream_id = ?", streamID).Find(&messages).Error; err != nil { + return nil, err + } + return messages, nil +} + +func (r *messageRepository) CreateMessage(message *models.Message) error { + return r.db.Create(message).Error +} + +func (r *messageRepository) UpdateMessage(message *models.Message) error { + var existingMessage models.Message + if err := r.db.First(&existingMessage, message.ID).Error; err != nil { + return err + } + + return r.db.Model(&existingMessage).Updates(message).Error +} + +func (r *messageRepository) DeleteMessage(id uint) error { + return r.db.Delete(&models.Message{}, id).Error +} diff --git a/zhimolostnova_anna_lab_3/messageService/internal/app/services/message_service.go b/zhimolostnova_anna_lab_3/messageService/internal/app/services/message_service.go new file mode 100644 index 0000000..cd17919 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/internal/app/services/message_service.go @@ -0,0 +1,47 @@ +package services + +import ( + "messageService/internal/app/models" + "messageService/internal/app/repositories" +) + +type MessageService interface { + GetAllMessages() ([]models.Message, error) + GetMessagesByStreamID(streamId uint) ([]models.Message, error) + GetMessageByID(id uint) (*models.Message, error) + CreateMessage(message *models.Message) error + UpdateMessage(message *models.Message) error + DeleteMessage(id uint) error +} + +type messageService struct { + repo repositories.MessageRepository +} + +func NewMessageService(repo repositories.MessageRepository) MessageService { + return &messageService{repo} +} + +func (s *messageService) GetAllMessages() ([]models.Message, error) { + return s.repo.GetAllMessages() +} + +func (s *messageService) GetMessagesByStreamID(streamId uint) ([]models.Message, error) { + return s.repo.GetMessagesByStreamID(streamId) +} + +func (s *messageService) GetMessageByID(id uint) (*models.Message, error) { + return s.repo.GetMessageByID(id) +} + +func (s *messageService) CreateMessage(message *models.Message) error { + return s.repo.CreateMessage(message) +} + +func (s *messageService) UpdateMessage(message *models.Message) error { + return s.repo.UpdateMessage(message) +} + +func (s *messageService) DeleteMessage(id uint) error { + return s.repo.DeleteMessage(id) +} diff --git a/zhimolostnova_anna_lab_3/messageService/internal/app/services/stream_client.go b/zhimolostnova_anna_lab_3/messageService/internal/app/services/stream_client.go new file mode 100644 index 0000000..35f69b4 --- /dev/null +++ b/zhimolostnova_anna_lab_3/messageService/internal/app/services/stream_client.go @@ -0,0 +1,62 @@ +package services + +import ( + "encoding/json" + "fmt" + "io" + "log" + "messageService/internal/app/models" + "net/http" + "time" +) + +type StreamClient interface { + GetStreamTitleByID(streamId uint) (string, error) +} + +type streamClient struct { + BaseURL string + Timeout time.Duration +} + +func NewStreamClient(baseURL string, timeout time.Duration) StreamClient { + return &streamClient{ + BaseURL: baseURL, + Timeout: timeout, + } +} + +func (c *streamClient) GetStreamTitleByID(streamID uint) (string, error) { + client := &http.Client{Timeout: c.Timeout} + url := fmt.Sprintf("%s/streams/%d", c.BaseURL, streamID) + log.Printf(url) + + resp, err := client.Get(url) + if err != nil { + return "", err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + + } + }(resp.Body) + + log.Printf("Response Status: %s", resp.Status) + body, _ := io.ReadAll(resp.Body) + log.Printf("Response Body: %s", body) + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to get stream title: %s", resp.Status) + } + + var stream models.StreamResponse + if err := json.Unmarshal(body, &stream); err != nil { + log.Printf("Unmarshal error: %s", err) + return "", err + } + + log.Printf("Name: %s", stream.Title) + + return stream.Title, nil +} diff --git a/zhimolostnova_anna_lab_3/nginx.conf b/zhimolostnova_anna_lab_3/nginx.conf new file mode 100644 index 0000000..640561d --- /dev/null +++ b/zhimolostnova_anna_lab_3/nginx.conf @@ -0,0 +1,61 @@ +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name localhost; + + # Прокси для Stream-сервиса + location /stream-service/ { + proxy_pass http://stream-service: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'; + + } + + # Прокси для Message-сервиса + location /message-service/ { + proxy_pass http://message-service: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'; + } + + # Прокси для 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; + } + } +} diff --git a/zhimolostnova_anna_lab_3/streamService/Dockerfile b/zhimolostnova_anna_lab_3/streamService/Dockerfile new file mode 100644 index 0000000..17ed0bf --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.23-alpine + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN go build -o stream_main ./cmd/stream_main.go + +EXPOSE 8000 + +CMD ["./stream_main"] diff --git a/zhimolostnova_anna_lab_3/streamService/cmd/stream_main.go b/zhimolostnova_anna_lab_3/streamService/cmd/stream_main.go new file mode 100644 index 0000000..7966449 --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/cmd/stream_main.go @@ -0,0 +1,59 @@ +package main + +import ( + "github.com/gorilla/mux" + "github.com/rs/zerolog" + httpSwagger "github.com/swaggo/http-swagger" + "log" + "net/http" + "os" + _ "streamService/docs" + "streamService/internal/app/database" + "streamService/internal/app/handlers" + "streamService/internal/app/repositories" + "streamService/internal/app/services" +) + +func main() { + // Настройка логирования + logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("Could not open log file: %v", err) + } + defer func(logFile *os.File) { + err := logFile.Close() + if err != nil { + + } + }(logFile) + + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + logger := zerolog.New(logFile).With().Timestamp().Logger() + + // Инициализация базы данных + db, err := database.InitStreamDB() + if err != nil { + log.Fatalf("Could not connect to the database: %v", err) + } + + // Инициализация репозитория и сервиса + streamRepo := repositories.NewStreamRepository(db) + streamService := services.NewStreamService(streamRepo) + + // Инициализация маршрутизатора + router := mux.NewRouter() + + // Регистрация хендлеров + handlers.RegisterStreamRoutes(router, streamService) + + // Swagger + router.HandleFunc("/doc.json", func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "docs/doc.json") + }).Methods("GET") + + // Route for Swagger UI + router.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler) + + logger.Info().Msg("Server started at http://localhost:8000") + logger.Fatal().Err(http.ListenAndServe(":8000", router)).Msg("Server crashed") +} diff --git a/zhimolostnova_anna_lab_3/streamService/docs/doc.json b/zhimolostnova_anna_lab_3/streamService/docs/doc.json new file mode 100644 index 0000000..89b303c --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/docs/doc.json @@ -0,0 +1,191 @@ +{ + "swagger": "2.0", + "info": { + "contact": {}, + "version": "", + "title": "" + }, + "paths": { + "/stream-service/streams": { + "get": { + "description": "Возвращает список всех стримов", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Получить все стримы", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Stream" + } + } + } + } + }, + "post": { + "description": "Создает новый стрим", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Создать стрим", + "parameters": [ + { + "description": "Данные стримы", + "name": "stream", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + } + }, + "/stream-service/streams/{id}": { + "get": { + "description": "Возвращает стрим с указанным ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Получить стрим по ID", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + }, + "put": { + "description": "Обновляет данные стрима по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Обновить данные стрима", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Обновленные данные стрима", + "name": "stream", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + }, + "delete": { + "description": "Удаляет стрим по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Удалить стрим", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "models.Stream": { + "description": "Стрим", + "type": "object", + "properties": { + "ended_at": { + "description": "Время окончания стрима", + "type": "string" + }, + "id": { + "description": "Идентификатор стрима", + "type": "integer" + }, + "started_at": { + "description": "Время запуска стрима", + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "title": { + "description": "Название стрима", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/zhimolostnova_anna_lab_3/streamService/docs/docs.go b/zhimolostnova_anna_lab_3/streamService/docs/docs.go new file mode 100644 index 0000000..d942c93 --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/docs/docs.go @@ -0,0 +1,216 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/stream-service/streams": { + "get": { + "description": "Возвращает список всех стримов", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Получить все стримы", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Stream" + } + } + } + } + }, + "post": { + "description": "Создает новый стрим", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Создать стрим", + "parameters": [ + { + "description": "Данные стримы", + "name": "stream", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + } + }, + "/stream-service/streams/{id}": { + "get": { + "description": "Возвращает стрим с указанным ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Получить стрим по ID", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + }, + "put": { + "description": "Обновляет данные стрима по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Обновить данные стрима", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Обновленные данные стрима", + "name": "stream", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + }, + "delete": { + "description": "Удаляет стрим по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Удалить стрим", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "models.Stream": { + "description": "Стрим", + "type": "object", + "properties": { + "ended_at": { + "description": "Время окончания стрима", + "type": "string" + }, + "id": { + "description": "Идентификатор стрима", + "type": "integer" + }, + "started_at": { + "description": "Время запуска стрима", + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "title": { + "description": "Название стрима", + "type": "string" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/zhimolostnova_anna_lab_3/streamService/docs/swagger.json b/zhimolostnova_anna_lab_3/streamService/docs/swagger.json new file mode 100644 index 0000000..f00738c --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/docs/swagger.json @@ -0,0 +1,191 @@ +{ + "swagger": "2.0", + "info": { + "contact": {}, + "version": "", + "title": "" + }, + "paths": { + "/streams": { + "get": { + "description": "Возвращает список всех стримов", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Получить все стримы", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Stream" + } + } + } + } + }, + "post": { + "description": "Создает новый стрим", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Создать стрим", + "parameters": [ + { + "description": "Данные стримы", + "name": "stream", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + } + }, + "/streams/{id}": { + "get": { + "description": "Возвращает стрим с указанным ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Получить стрим по ID", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + }, + "put": { + "description": "Обновляет данные стрима по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Обновить данные стрима", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Обновленные данные стрима", + "name": "stream", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Stream" + } + } + } + }, + "delete": { + "description": "Удаляет стрим по ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "streams" + ], + "summary": "Удалить стрим", + "parameters": [ + { + "type": "integer", + "description": "ID стрима", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "models.Stream": { + "description": "Стрим", + "type": "object", + "properties": { + "ended_at": { + "description": "Время окончания стрима", + "type": "string" + }, + "id": { + "description": "Идентификатор стрима", + "type": "integer" + }, + "started_at": { + "description": "Время запуска стрима", + "type": "string", + "example": "2024-10-04T14:48:00Z" + }, + "title": { + "description": "Название стрима", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/zhimolostnova_anna_lab_3/streamService/docs/swagger.yaml b/zhimolostnova_anna_lab_3/streamService/docs/swagger.yaml new file mode 100644 index 0000000..9f64256 --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/docs/swagger.yaml @@ -0,0 +1,127 @@ +definitions: + models.Stream: + description: Стрим + properties: + ended_at: + description: Время окончания стрима + type: string + id: + description: Идентификатор стрима + type: integer + started_at: + description: Время запуска стрима + example: "2024-10-04T14:48:00Z" + type: string + title: + description: Название стрима + type: string + type: object +info: + contact: { } + version: '2.0' + title: 'API' +paths: + /streams: + get: + consumes: + - application/json + description: Возвращает список всех стримов + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Stream' + type: array + summary: Получить все стримы + tags: + - streams + post: + consumes: + - application/json + description: Создает новый стрим + parameters: + - description: Данные стримы + in: body + name: stream + required: true + schema: + $ref: '#/definitions/models.Stream' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Stream' + summary: Создать стрим + tags: + - streams + /streams/{id}: + delete: + consumes: + - application/json + description: Удаляет стрим по ID + parameters: + - description: ID стрима + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "204": + description: No Content + summary: Удалить стрим + tags: + - streams + get: + consumes: + - application/json + description: Возвращает стрим с указанным ID + parameters: + - description: ID стрима + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Stream' + summary: Получить стрим по ID + tags: + - streams + put: + consumes: + - application/json + description: Обновляет данные стрима по ID + parameters: + - description: ID стрима + in: path + name: id + required: true + type: integer + - description: Обновленные данные стрима + in: body + name: stream + required: true + schema: + $ref: '#/definitions/models.Stream' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Stream' + summary: Обновить данные стрима + tags: + - streams +swagger: "2.0" diff --git a/zhimolostnova_anna_lab_3/streamService/go.mod b/zhimolostnova_anna_lab_3/streamService/go.mod new file mode 100644 index 0000000..042a199 --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/go.mod @@ -0,0 +1,39 @@ +module streamService + +go 1.23.2 + +require ( + github.com/gorilla/mux v1.8.1 + github.com/rs/zerolog v1.33.0 + github.com/swaggo/http-swagger v1.3.4 + github.com/swaggo/swag v1.8.1 + gorm.io/driver/postgres v1.5.9 + gorm.io/gorm v1.25.12 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.6 // indirect + github.com/go-openapi/swag v0.19.15 // 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.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/zhimolostnova_anna_lab_3/streamService/internal/app/database/db.go b/zhimolostnova_anna_lab_3/streamService/internal/app/database/db.go new file mode 100644 index 0000000..a047b85 --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/internal/app/database/db.go @@ -0,0 +1,31 @@ +package database + +import ( + "fmt" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "os" + "streamService/internal/app/models" +) + +// InitStreamDB инициализирует подключение к базе данных для микросервиса Stream +func InitStreamDB() (*gorm.DB, error) { + host := os.Getenv("DB_HOST") + port := os.Getenv("DB_PORT") + user := os.Getenv("DB_USER") + password := os.Getenv("DB_PASSWORD") + dbname := os.Getenv("DB_STREAM_NAME") + + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", host, user, password, dbname, port) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + return nil, err + } + + // Автоматическая миграция для Stream + if err := db.AutoMigrate(&models.Stream{}); err != nil { + return nil, err + } + + return db, nil +} diff --git a/zhimolostnova_anna_lab_3/streamService/internal/app/handlers/stream_handler.go b/zhimolostnova_anna_lab_3/streamService/internal/app/handlers/stream_handler.go new file mode 100644 index 0000000..00920ef --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/internal/app/handlers/stream_handler.go @@ -0,0 +1,168 @@ +package handlers + +import ( + "encoding/json" + "io" + "log" + "net/http" + "strconv" + "streamService/internal/app/models" + "streamService/internal/app/services" + + "github.com/gorilla/mux" +) + +func RegisterStreamRoutes(router *mux.Router, svc services.StreamService) { + router.HandleFunc("/streams", GetAllStreams(svc)).Methods("GET") + router.HandleFunc("/streams/{id}", GetStream(svc)).Methods("GET") + router.HandleFunc("/streams", CreateStream(svc)).Methods("POST") + router.HandleFunc("/streams/{id}", UpdateStream(svc)).Methods("PUT") + router.HandleFunc("/streams/{id}", DeleteStream(svc)).Methods("DELETE") +} + +// GetAllStreams godoc +// @Summary Получить все стримы +// @Description Возвращает список всех стримов +// @Tags streams +// @Accept json +// @Produce json +// @Success 200 {array} models.Stream +// @Router /streams [get] +func GetAllStreams(svc services.StreamService) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + streams, err := svc.GetAllStreams() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + err = json.NewEncoder(w).Encode(streams) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// GetStream godoc +// @Summary Получить стрим по ID +// @Description Возвращает стрим с указанным ID +// @Tags streams +// @Accept json +// @Produce json +// @Param id path int true "ID стрима" +// @Success 200 {object} models.Stream +// @Router /streams/{id} [get] +func GetStream(svc services.StreamService) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + log.Printf("Response Body: %s", body) + idStr := mux.Vars(r)["id"] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid ID", http.StatusBadRequest) + return + } + + stream, err := svc.GetStreamByID(uint(id)) + if err != nil { + http.Error(w, "Stream not found", http.StatusNotFound) + return + } + err = json.NewEncoder(w).Encode(stream) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// CreateStream godoc +// @Summary Создать стрим +// @Description Создает новый стрим +// @Tags streams +// @Accept json +// @Produce json +// @Param stream body models.Stream true "Данные стримы" +// @Success 200 {object} models.Stream +// @Router /streams [post] +func CreateStream(svc services.StreamService) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var stream models.Stream + err := json.NewDecoder(r.Body).Decode(&stream) + if err != nil { + http.Error(w, "Failed to decode request body", http.StatusBadRequest) + return + } + err = svc.CreateStream(&stream) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(stream) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// UpdateStream godoc +// @Summary Обновить данные стрима +// @Description Обновляет данные стрима по ID +// @Tags streams +// @Accept json +// @Produce json +// @Param id path int true "ID стрима" +// @Param stream body models.Stream true "Обновленные данные стрима" +// @Success 200 {object} models.Stream +// @Router /streams/{id} [put] +func UpdateStream(svc services.StreamService) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var stream models.Stream + idStr := mux.Vars(r)["id"] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid ID", http.StatusBadRequest) + return + } + + if err := json.NewDecoder(r.Body).Decode(&stream); err != nil { + http.Error(w, "Failed to decode request body", http.StatusBadRequest) + return + } + stream.ID = uint(id) + err = svc.UpdateStream(&stream) + if err != nil { + http.Error(w, "Stream not found", http.StatusNotFound) + return + } + err = json.NewEncoder(w).Encode(stream) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +// DeleteStream godoc +// @Summary Удалить стрим +// @Description Удаляет стрим по ID +// @Tags streams +// @Accept json +// @Produce json +// @Param id path int true "ID стрима" +// @Success 204 "No Content" +// @Router /streams/{id} [delete] +func DeleteStream(svc services.StreamService) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + idStr := mux.Vars(r)["id"] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid ID", http.StatusBadRequest) + return + } + err = svc.DeleteStream(uint(id)) + if err != nil { + http.Error(w, "Stream not found", http.StatusNotFound) + return + } + w.WriteHeader(http.StatusNoContent) + } +} diff --git a/zhimolostnova_anna_lab_3/streamService/internal/app/models/stream.go b/zhimolostnova_anna_lab_3/streamService/internal/app/models/stream.go new file mode 100644 index 0000000..2808d0a --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/internal/app/models/stream.go @@ -0,0 +1,12 @@ +package models + +import "time" + +// Stream представляет стрим в системе +// @Description Стрим +type Stream struct { + ID uint `gorm:"primaryKey" json:"id"` // Идентификатор стрима + Title string `json:"title"` // Название стрима + StartedAt time.Time `json:"started_at" example:"2024-10-04T14:48:00Z"` // Время запуска стрима + EndedAt *time.Time `json:"ended_at"` // Время окончания стрима +} diff --git a/zhimolostnova_anna_lab_3/streamService/internal/app/repositories/stream_repository.go b/zhimolostnova_anna_lab_3/streamService/internal/app/repositories/stream_repository.go new file mode 100644 index 0000000..7e1f58c --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/internal/app/repositories/stream_repository.go @@ -0,0 +1,55 @@ +package repositories + +import ( + "gorm.io/gorm" + "streamService/internal/app/models" +) + +type StreamRepository interface { + GetAllStreams() ([]models.Stream, error) + GetStreamByID(id uint) (*models.Stream, error) + CreateStream(stream *models.Stream) error + UpdateStream(stream *models.Stream) error + DeleteStream(id uint) error +} + +type streamRepository struct { + db *gorm.DB +} + +func NewStreamRepository(db *gorm.DB) StreamRepository { + return &streamRepository{db: db} +} + +func (r *streamRepository) GetAllStreams() ([]models.Stream, error) { + var streams []models.Stream + if err := r.db.Find(&streams).Error; err != nil { + return nil, err + } + return streams, nil +} + +func (r *streamRepository) GetStreamByID(id uint) (*models.Stream, error) { + var stream models.Stream + if err := r.db.First(&stream, id).Error; err != nil { + return nil, err + } + return &stream, nil +} + +func (r *streamRepository) CreateStream(stream *models.Stream) error { + return r.db.Create(stream).Error +} + +func (r *streamRepository) UpdateStream(stream *models.Stream) error { + var existingStream models.Stream + if err := r.db.First(&existingStream, stream.ID).Error; err != nil { + return err + } + + return r.db.Model(&existingStream).Updates(stream).Error +} + +func (r *streamRepository) DeleteStream(id uint) error { + return r.db.Delete(&models.Stream{}, id).Error +} diff --git a/zhimolostnova_anna_lab_3/streamService/internal/app/services/stream_service.go b/zhimolostnova_anna_lab_3/streamService/internal/app/services/stream_service.go new file mode 100644 index 0000000..21a4c32 --- /dev/null +++ b/zhimolostnova_anna_lab_3/streamService/internal/app/services/stream_service.go @@ -0,0 +1,42 @@ +package services + +import ( + "streamService/internal/app/models" + "streamService/internal/app/repositories" +) + +type StreamService interface { + GetAllStreams() ([]models.Stream, error) + GetStreamByID(id uint) (*models.Stream, error) + CreateStream(stream *models.Stream) error + UpdateStream(stream *models.Stream) error + DeleteStream(id uint) error +} + +type streamService struct { + repo repositories.StreamRepository +} + +func NewStreamService(repo repositories.StreamRepository) StreamService { + return &streamService{repo} +} + +func (s *streamService) GetAllStreams() ([]models.Stream, error) { + return s.repo.GetAllStreams() +} + +func (s *streamService) GetStreamByID(id uint) (*models.Stream, error) { + return s.repo.GetStreamByID(id) +} + +func (s *streamService) CreateStream(stream *models.Stream) error { + return s.repo.CreateStream(stream) +} + +func (s *streamService) UpdateStream(stream *models.Stream) error { + return s.repo.UpdateStream(stream) +} + +func (s *streamService) DeleteStream(id uint) error { + return s.repo.DeleteStream(id) +}