diff --git a/.gitignore b/.gitignore
index e69de29..723ef36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
\ No newline at end of file
diff --git a/README.md b/README.md
index 0ad9f7b..04c3347 100644
--- a/README.md
+++ b/README.md
@@ -29,5 +29,5 @@
`$ docker run --name postgres2 -p 5432:5432 -e POSTGRES_USER=auth -e POSTGRES_PASSWORD=123 -e POSTGRES_DB=auth -d postgres:16`
### RabbitMQ
-`$ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management`
+`$ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=myuser -e RABBITMQ_DEFAULT_PASS=mypassword rabbitmq:3-management`
diff --git a/cmd/agent_service.go b/cmd/agent_service.go
deleted file mode 100644
index 48faf32..0000000
--- a/cmd/agent_service.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package main
-
-import (
- "encoding/base64"
- "encoding/json"
- "fmt"
- "log"
- "os"
- "os/exec"
-
- "github.com/streadway/amqp"
-)
-
-func main() {
-
- type Code struct {
- IDTask string `json:"id_task"`
- TpRunner string `json:"tp_runner"`
- Code string `json:"code"`
- Test string `json:"test"`
- }
-
- // Define RabbitMQ server URL.
- amqpServerURL := os.Getenv("AMQP_SERVER_URL")
- if amqpServerURL == "" {
- amqpServerURL = "amqp://guest:guest@localhost:5672/" // Default URL if not set
- }
-
- // Create a new RabbitMQ connection.
- connectRabbitMQ, err := amqp.Dial(amqpServerURL)
- if err != nil {
- panic(err)
- }
- defer connectRabbitMQ.Close()
-
- // Opening a channel to our RabbitMQ instance over
- // the connection we have already established.
- channelRabbitMQ, err := connectRabbitMQ.Channel()
- if err != nil {
- panic(err)
- }
- defer channelRabbitMQ.Close()
-
- // Subscribing to QueueService1 for getting messages.
- messages, err := channelRabbitMQ.Consume(
- "QueueService1", // queue name
- "", // consumer
- true, // auto-ack
- false, // exclusive
- false, // no local
- false, // no wait
- nil, // arguments
- )
- if err != nil {
- log.Println(err)
- }
-
- // Build a welcome message.
- log.Println("Successfully connected to RabbitMQ")
- log.Println("Waiting for messages")
-
- // Make a channel to receive messages into infinite loop.
- forever := make(chan bool)
-
- go func() {
- for message := range messages {
- // For example, show received message in a console.
- log.Printf(" > Received message: %s\n", message.Body)
- // Создание экземпляра структуры Task
-
- var code_obj Code
-
- // Разбор JSON строки в структуру
- err := json.Unmarshal([]byte(message.Body), &code_obj)
- if err != nil {
- log.Fatalf("Ошибка при разборе JSON: %v", err)
- }
- mkdir(code_obj.IDTask)
- mkfile(code_obj.IDTask, code_obj.Code, "main.py")
- mkfile(code_obj.IDTask, code_obj.Test, "test_hello.py")
- run(code_obj.IDTask)
-
- fmt.Println(code_obj.Code)
-
- }
- }()
-
- <-forever
-}
-
-func mkdir(dirName string) {
- // Имя директории, которую нужно создать
-
- // Права доступа к директории (0755 - чтение, запись, выполнение для владельца, чтение и выполнение для группы и остальных)
- permissions := os.ModeDir | 0755
-
- // Создание директории
- err := os.Mkdir(dirName, permissions)
- if err != nil {
- log.Printf("Ошибка при создании директории: %v", err)
- }
-
- fmt.Printf("Директория '%s' успешно создана.\n", dirName)
-}
-
-func mkfile(dirName string, base64String string, fName string) {
-
- // Декодирование Base64 строки
- decodedBytes, er := base64.StdEncoding.DecodeString(base64String)
- if er != nil {
- log.Fatalf("Ошибка при декодировании Base64: %v", er)
- }
-
- // Преобразование байтов в строку
- fileContent := string(decodedBytes)
-
- // Права доступа к файлу (0644 - чтение и запись для владельца, чтение для группы и остальных)
- fileName := "./" + dirName + "/" + fName
- permissions := 0644
-
- // Преобразование содержимого в слайс байтов
- data := []byte(fileContent)
-
- // Создание файла и запись содержимого
- err := os.WriteFile(fileName, data, os.FileMode(permissions))
- if err != nil {
- log.Fatalf("Ошибка при создании файла: %v", err)
- }
-
- fmt.Printf("Файл '%s' успешно создан с содержимым.\n", fileName)
-
-}
-
-func run(idTask string) {
-
- // Получение текущей рабочей директории
- currentDir, err := os.Getwd()
- if err != nil {
- log.Fatalf("Ошибка при получении текущей директории: %v", err)
- }
-
- dir := currentDir + "/" + idTask
-
- // Изменение текущей рабочей директории
- err = os.Chdir(dir)
- if err != nil {
- log.Fatalf("Ошибка при изменении директории: %v", err)
- }
-
- // Команда для запуска Python с кодом
- fileName := "test_hello.py"
-
-
- cmd := exec.Command("python", "-m", "unittest", fileName)
-
- // Запуск команды и получение вывода
- output, err := cmd.CombinedOutput()
- if err != nil {
- log.Fatalf("Ошибка при выполнении Python кода: %v\nВывод: %s", err, string(output))
- }
-
- // Вывод результата
- // todo: записать в топик RMQ id_user_id_UUID
- // { data: output}
- fmt.Printf("Вывод Python:\n%s\n", string(output))
-
-}
diff --git a/cmd/frontend_service.go b/cmd/frontend_service.go
deleted file mode 100644
index d67be89..0000000
--- a/cmd/frontend_service.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package main
-
-import (
- "context"
- "database/sql"
- "fmt"
- "log"
- "net/http"
-
- "github.com/gin-gonic/gin"
- _ "github.com/lib/pq" // Import the PostgreSQL driver
- "github.com/redis/go-redis/v9"
-)
-
-const (
- dbHost = "localhost"
- dbPort = 5432
- dbUser = "postgres"
- dbPassword = "123" // Замените на свой пароль
- dbName = "auth"
-)
-
-func main() {
-
- // Создание клиента Redis
- rdb := redis.NewClient(&redis.Options{
- Addr: "localhost:6379", // Адрес Redis сервера
- Password: "", // Пароль, если требуется
- DB: 0, // Номер базы данных
- })
-
- // Проверка подключения
- ctx := context.Background()
- pong, err := rdb.Ping(ctx).Result()
- if err != nil {
- fmt.Println("Ошибка подключения к Redis:", err)
- return
- }
-
- fmt.Println("Успешное подключение к Redis! Ответ:", pong)
-
- // Строка подключения к базе данных
- connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
- dbHost, dbPort, dbUser, dbPassword, dbName)
-
- // to-do: написать обращение в кеш Redis
- // Подключение к базе данных
- db, err := sql.Open("postgres", connStr)
- if err != nil {
- log.Fatal(err)
- }
- defer db.Close()
-
- // Проверка подключения
- err = db.Ping()
- if err != nil {
- log.Fatal(err)
- }
-
- fmt.Println("Connected to the database!")
-
- router := gin.Default()
- router.LoadHTMLGlob("./templates/*")
-
- // Обработчик для получения фрагмента кода
- router.GET("/task/:id", func(c *gin.Context) {
- var code string
-
- id := c.Param("id")
- code, err := rdb.Get(ctx, id).Result()
- if err != nil {
- err := db.QueryRow("SELECT json_text FROM task WHERE id = $1", id).Scan(&code)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- return
- }
- // Пример использования: установка и получение значения
- err = rdb.Set(ctx, id, code, 0).Err()
- if err != nil {
- fmt.Println("Ошибка при установке значения:", err)
- return
- }
- fmt.Println("Установили значение по ключу:", err)
- }
- c.JSON(http.StatusOK, gin.H{"code": code})
- })
-
- // Обработчик для обновления фрагмента кода
- router.POST("/task/:id", func(c *gin.Context) {
- id := c.Param("id")
- var requestBody struct {
- Code string `json:"code" binding:"required"`
- }
-
- if err := c.BindJSON(&requestBody); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
-
- _, err := db.Exec("UPDATE snippets SET code = $1 WHERE id = $2", requestBody.Code, id)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- return
- }
-
- c.JSON(http.StatusOK, gin.H{"message": "Snippet updated successfully"})
- })
-
- router.GET("/", func(c *gin.Context) {
- c.HTML(http.StatusOK, "index.html", gin.H{})
- })
-
- // Запуск сервера
- router.Run(":8080")
-}
diff --git a/cmd/push_service.go b/cmd/push_service.go
deleted file mode 100644
index 27b2416..0000000
--- a/cmd/push_service.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "log"
- "net/http"
-
- "os"
- "github.com/streadway/amqp"
- "github.com/gorilla/websocket"
-)
-
-
-
-var upgrader = websocket.Upgrader{
- CheckOrigin: func(r *http.Request) bool {
- return true // Allow all origins (for development). In production, specify allowed origins.
- },
-}
-
-func handleConnections(w http.ResponseWriter, r *http.Request) {
-
- type Code struct {
- IDTask string `json:"id_task"`
- TpRunner string `json:"tp_runner"`
- Code string `json:"code"`
- Test string `json:"test"`
- }
-
- // Define RabbitMQ server URL.
- amqpServerURL := os.Getenv("AMQP_SERVER_URL")
- if amqpServerURL == "" {
- amqpServerURL = "amqp://guest:guest@localhost:5672/" // Default URL if not set
- }
-
- // Create a new RabbitMQ connection.
- connectRabbitMQ, err := amqp.Dial(amqpServerURL)
- if err != nil {
- panic(err)
- }
- defer connectRabbitMQ.Close()
-
- // Opening a channel to our RabbitMQ instance over
- // the connection we have already established.
- channelRabbitMQ, err := connectRabbitMQ.Channel()
- if err != nil {
- panic(err)
- }
- defer channelRabbitMQ.Close()
-
-
- // todo: заменен QueueService1 на динамический
- // id_user_id_task
- // Subscribing to QueueService1 for getting messages.
- messages, err := channelRabbitMQ.Consume(
- "QueueService1", // queue name
- "", // consumer
- true, // auto-ack
- false, // exclusive
- false, // no local
- false, // no wait
- nil, // arguments
- )
- if err != nil {
- log.Println(err)
- }
-
- // Build a welcome message.
- log.Println("Successfully connected to RabbitMQ")
- log.Println("Waiting for messages")
-
- // Upgrade initial GET request to a websocket
- ws, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- log.Fatal(err)
- }
- // Make sure we close the connection when the function returns
- defer ws.Close()
-
-
- // Make a channel to receive messages into infinite loop.
- forever := make(chan bool)
-
- go func() {
- for message := range messages {
- // For example, show received message in a console.
- log.Printf(" > Received message: %s\n", message.Body)
- // Создание экземпляра структуры Task
-
- var code_obj Code
-
- // Разбор JSON строки в структуру
- err := json.Unmarshal([]byte(message.Body), &code_obj)
- if err != nil {
- log.Fatalf("Ошибка при разборе JSON: %v", err)
- }
- // Write message back to browser
- err = ws.WriteMessage(websocket.TextMessage, []byte(code_obj.Code))
- if err != nil {
- log.Println(err)
- return
- }
- fmt.Println(code_obj.Code)
-
- }
- }()
-
- <-forever
-
-
- // for {
- // // Read message from browser
- // _, msg, err := ws.ReadMessage()
- // if err != nil {
- // log.Println(err)
- // return
- // }
-
- // // Print the message to the console
- // fmt.Printf("Received: %s\n", msg)
-
- // // Write message back to browser
- // err = ws.WriteMessage(websocket.TextMessage, msg)
- // if err != nil {
- // log.Println(err)
- // return
- // }
- // }
-}
-
-func main() {
-
- fmt.Println("Starting WebSocket server...")
- http.HandleFunc("/ws", handleConnections)
-
- log.Fatal(http.ListenAndServe(":8081", nil))
-}
\ No newline at end of file
diff --git a/cmd/rest_service.go b/cmd/rest_service.go
deleted file mode 100644
index 9fbf493..0000000
--- a/cmd/rest_service.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "os"
-
- "encoding/json"
-
- "github.com/gofiber/fiber/v2"
- "github.com/gofiber/fiber/v2/middleware/cors"
- "github.com/gofiber/fiber/v2/middleware/logger"
- "github.com/streadway/amqp"
-)
-
-// Структура для JSON данных
-type Code struct {
- Id_task string `json:"id_task"`
- Tp_runner string `json:"tp_runner"`
- Code string `json:"code"`
- Test string `json:"test"`
-}
-
-func main() {
- // Define RabbitMQ server URL.
- amqpServerURL := os.Getenv("AMQP_SERVER_URL")
- if amqpServerURL == "" {
- amqpServerURL = "amqp://guest:guest@localhost:5672/" // Default URL if not set
- }
-
- // Create a new RabbitMQ connection.
- connectRabbitMQ, err := amqp.Dial(amqpServerURL)
- if err != nil {
- panic(err)
- }
- defer connectRabbitMQ.Close()
-
- // Let's start by opening a channel to our RabbitMQ
- // instance over the connection we have already
- // established.
- channelRabbitMQ, err := connectRabbitMQ.Channel()
- if err != nil {
- panic(err)
- }
- defer channelRabbitMQ.Close()
-
- // With the instance and declare Queues that we can
- // publish and subscribe to.
- _, err = channelRabbitMQ.QueueDeclare(
- "QueueService1", // queue name
- true, // durable
- false, // auto delete
- false, // exclusive
- false, // no wait
- nil, // arguments
- )
- if err != nil {
- panic(err)
- }
-
- // Create a new Fiber instance.
- app := fiber.New()
-
- app.Use(cors.New(cors.Config{
- AllowOrigins: "*",
- AllowMethods: "GET,POST,HEAD,PUT,DELETE,PATCH",
- AllowHeaders: "Origin, Content-Type, Accept, Authorization",
- }))
-
- // Add middleware.
- app.Use(
- logger.New(), // add simple logger
- )
-
- app.Get("/", func(c *fiber.Ctx) error {
- return c.SendString("Hello, CORS is enabled!")
- })
- // Add route. ?msg=bla
-
- app.Post("/run", func(c *fiber.Ctx) error {
- //todo: Тестирование ручки на взаимодействие
- // c клиентом. Расширить json добавив ID_USER
- // из AUTH SERVICE
-
- // Create a message to publish.
- // to-do: дописать приемку json
- mess := new(Code)
-
- // Разбираем JSON из тела запроса и заполняем структуру
- _ = c.BodyParser(mess)
- jsonBody, err := json.Marshal(mess)
- if err != nil {
- log.Fatalf("Error encoding JSON: %v", err)
- }
-
- fmt.Println(mess.Code)
- message := amqp.Publishing{
- ContentType: "application/json",
- Body: jsonBody,
- }
-
- // message := amqp.Publishing{
- // ContentType: "text/plain",
- // Body: []byte("Hello"),
- // }
-
- // Attempt to publish a message to the queue.
- if err := channelRabbitMQ.Publish(
- "", // exchange
- "QueueService1", // queue name
- false, // mandatory
- false, // immediate
- message, // message to publish
- ); err != nil {
- return err
- }
-
- return nil
- })
-
- // Start Fiber API server.
- log.Fatal(app.Listen(":3000"))
-}
diff --git a/cmd/service_agent/s_agent.go b/cmd/service_agent/s_agent.go
new file mode 100644
index 0000000..adde684
--- /dev/null
+++ b/cmd/service_agent/s_agent.go
@@ -0,0 +1,222 @@
+package main
+
+import (
+ "code_runner/config"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "github.com/google/uuid"
+ "github.com/streadway/amqp"
+)
+
+
+// State - раннер может находится в нескольких состояниях в зависимости
+// от исполняемого языка программирования.
+// State - интерфейс состояния
+type State interface {
+ Handle(context *Context)
+}
+
+// ConcretStatePy - конкретное состояние A
+type ConcretStatePy struct{}
+
+func (a *ConcretStatePy) Handle(context *Context) {
+ fmt.Println("ConcretStatePy: Обработка запроса и изменение состояния на ConcretStateGo")
+ context.SetState(&ConcretStateGo{})
+}
+
+// ConcretStateGo - конкретное состояние B
+type ConcretStateGo struct{}
+
+func (b *ConcretStateGo) Handle(context *Context) {
+ fmt.Println("ConcretStateGo: Обработка запроса и изменение состояния на ConcretStatePy")
+ context.SetState(&ConcretStatePy{})
+}
+
+// Context - контекст, который хранит текущее состояние
+type Context struct {
+ state State
+}
+
+// NewContext - конструктор для Context
+func NewContext() *Context {
+ return &Context{
+ state: &ConcretStatePy{}, // Начальное состояние
+ }
+}
+
+// SetState - устанавливает новое состояние
+func (c *Context) SetState(state State) {
+ c.state = state
+}
+
+// Request - выполняет запрос, делегируя его текущему состоянию
+func (c *Context) Request() {
+ fmt.Println("Context: Запрос")
+ c.state.Handle(c)
+}
+
+
+func main() {
+ amqpServerURL := config.AmqpServerURL
+ // os.Getenv("AMQP_SERVER_URL")
+
+ fmt.Print(amqpServerURL)
+ if amqpServerURL == "" {
+ panic("Дискриптор для RabbitMQ не указан!")
+ }
+
+ connectRabbitMQ, err := amqp.Dial(amqpServerURL)
+ if err != nil {
+ panic(err)
+ }
+ defer connectRabbitMQ.Close()
+
+ channelRabbitMQ, err := connectRabbitMQ.Channel()
+ if err != nil {
+ panic(err)
+ }
+ defer channelRabbitMQ.Close()
+
+ _, err = channelRabbitMQ.QueueDeclare(
+ config.TaskQueueCallbackName,
+ true,
+ false,
+ false,
+ false,
+ nil,
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ messages, err := channelRabbitMQ.Consume(
+ config.TaskQueueName,
+ "",
+ true,
+ false,
+ false,
+ false,
+ nil,
+ )
+ if err != nil {
+ log.Println(err)
+ }
+ log.Println("Successfully connected to RabbitMQ")
+ log.Println("Waiting for messages")
+
+ forever := make(chan bool)
+
+ go func() {
+
+ for message := range messages {
+ var codeObj config.MessageCode
+
+ err := json.Unmarshal([]byte(message.Body), &codeObj)
+ if err != nil {
+ log.Printf("Error unmarshaling message: %v", err)
+ continue
+ }
+ log.Print(codeObj)
+ //todo: в зависимости от типа tp_lang вызываем Runner.
+ // context := NewContext()
+
+ uid := uuid.New()
+
+ fileName := uid.String() + ".py"
+ dirName := "code"
+
+ // Создаем директорию, если её нет
+ if err := os.MkdirAll(dirName, 0755); err != nil {
+ log.Printf("Error creating directory: %v", err)
+ continue
+ }
+
+ if err := makeFile(dirName, fileName, codeObj.Code); err != nil {
+ // if err := makeFile(dirName, fileName, codeObj.Unit_test); err != nil {
+ log.Printf("Error creating file: %v", err)
+ continue
+ }
+
+ success, runMessage := runCode(dirName, fileName)
+
+ // Удаляем файл в любом случае
+ if err := deleteFile(dirName, fileName); err != nil {
+ log.Printf("Error deleting file: %v", err)
+ }
+
+ response := config.SuccessResponse{
+ Success: success,
+ Message: runMessage,
+ Queue: config.TaskQueueCallbackName,
+ }
+
+ jsonBody, err := json.Marshal(response)
+ if err != nil {
+ log.Printf("Error marshaling response: %v", err)
+ continue
+ }
+
+ err = channelRabbitMQ.Publish(
+ "",
+ config.TaskQueueCallbackName,
+ false,
+ false,
+ amqp.Publishing{
+ ContentType: "application/json",
+ Body: jsonBody,
+ },
+ )
+ if err != nil {
+ log.Printf("Error publishing message: %v", err)
+ }
+ }
+ }()
+
+ <-forever
+}
+
+
+
+
+func makeFile(dirName string, fileName string, base64String string) error {
+ decodedBytes, err := base64.StdEncoding.DecodeString(base64String)
+ if err != nil {
+ return fmt.Errorf("base64 decode error: %v", err)
+ }
+
+ fullPath := filepath.Join(dirName, fileName)
+ err = os.WriteFile(fullPath, decodedBytes, 0644)
+ if err != nil {
+ return fmt.Errorf("write file error: %v", err)
+ }
+ return nil
+}
+
+func deleteFile(dirName string, fileName string) error {
+ fullPath := filepath.Join(dirName, fileName)
+ err := os.Remove(fullPath)
+ if err != nil {
+ return fmt.Errorf("remove file error: %v", err)
+ }
+ return nil
+}
+
+
+
+func runCode(dirName string, fileName string) (bool, string) {
+ fullPath := filepath.Join(dirName, fileName)
+
+ // cmd := exec.Command("python", "-m", "unittest", fullPath)
+
+ cmd := exec.Command("python3", fullPath)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ return false, fmt.Sprintf("%s\nError: %v", output, err)
+ }
+ return true, string(output)
+}
diff --git a/cmd/service_back/s_back.go b/cmd/service_back/s_back.go
new file mode 100644
index 0000000..9a1b8da
--- /dev/null
+++ b/cmd/service_back/s_back.go
@@ -0,0 +1,115 @@
+package main
+
+import (
+ "code_runner/config"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/gofiber/fiber/v2"
+ _ "github.com/gofiber/fiber/v2"
+
+ "encoding/json"
+
+ "github.com/gofiber/fiber/v2/middleware/cors"
+ "github.com/gofiber/fiber/v2/middleware/logger"
+ "github.com/streadway/amqp"
+)
+
+func main() {
+ amqpServer := os.Getenv(config.AmqpServerKey)
+ if amqpServer == "" {
+ amqpServer = config.AmqpServerURL
+ }
+
+ connectRabbitMQ, err := amqp.Dial(amqpServer)
+ if err != nil {
+ panic(err)
+ }
+ defer connectRabbitMQ.Close()
+
+ channelRabbitMQ, err := connectRabbitMQ.Channel()
+ if err != nil {
+ panic(err)
+ }
+ defer channelRabbitMQ.Close()
+
+ _, err = channelRabbitMQ.QueueDeclare(
+ config.TaskQueueName,
+ true,
+ false,
+ false,
+ false,
+ nil,
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ app := fiber.New()
+
+ app.Use(cors.New(cors.Config{
+ AllowOrigins: "*",
+ AllowMethods: "GET,POST,HEAD,PUT,DELETE,PATCH",
+ AllowHeaders: "Origin, Content-Type, Accept, Authorization",
+ }))
+
+ app.Use(
+ logger.New(),
+ )
+
+ app.Get("/", func(c *fiber.Ctx) error {
+ return c.SendString("Hello, CORS is enabled!")
+ })
+
+ app.Post("/run", func(c *fiber.Ctx) error {
+ mess := new(config.MessageCode)
+
+ if err := c.BodyParser(mess); err != nil {
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
+ "success": false,
+ "message": "Invalid request body",
+ })
+ }
+
+ jsonBody, err := json.Marshal(mess)
+ log.Println("FrontEnd JsonBody:", string( jsonBody))
+ if err != nil {
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "success": false,
+ "message": "Error encoding message",
+ })
+ }
+ fmt.Println("Received message:", mess)
+
+ message := amqp.Publishing{
+ ContentType: "application/json",
+ Body: jsonBody,
+ }
+
+ err = channelRabbitMQ.Publish(
+ "",
+ config.TaskQueueName,
+ false,
+ false,
+ message,
+ )
+
+ if err != nil {
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "success": false,
+ "message": "Failed to publish message to queue",
+ })
+ }
+
+ response := config.SuccessResponse{
+ Success: true,
+ Message: "Code successfully added to queue",
+ Queue: config.TaskQueueName,
+ }
+
+ return c.Status(fiber.StatusOK).JSON(response)
+ })
+
+ log.Fatal(app.Listen(":3000"))
+}
diff --git a/cmd/service_front/s_front.go b/cmd/service_front/s_front.go
new file mode 100644
index 0000000..f51561b
--- /dev/null
+++ b/cmd/service_front/s_front.go
@@ -0,0 +1,482 @@
+package main
+
+import (
+ "code_runner/config"
+ "code_runner/pkg/database"
+
+ // "code_runner/pkg/database"
+ "code_runner/internal/handler"
+
+ "context"
+ "database/sql"
+ "errors"
+ "fmt"
+ "log"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/golang-jwt/jwt/v5"
+ _ "github.com/lib/pq"
+ "github.com/redis/go-redis/v9"
+ "golang.org/x/crypto/bcrypt"
+)
+
+
+
+type Task struct {
+ ID int `json:"id"`
+ Text string `json:"text"`
+ IdLang string `json:"tp_lang"`
+}
+
+var user struct {
+ ID int
+ Email string
+}
+
+type User struct {
+ Name string `json:"name" binding:"required"`
+ Email string `json:"email" binding:"required,email"`
+ Password string `json:"password" binding:"required,min=6"`
+}
+
+type LoginRequest struct {
+ Email string `json:"email" binding:"required,email"`
+ Password string `json:"password" binding:"required"`
+}
+
+type UserResponse struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+}
+
+type AuthResponse struct {
+ User UserResponse `json:"user"`
+ Token string `json:"token"`
+}
+
+var (
+ postgresDb *sql.DB
+ redisDb *redis.Client
+)
+
+type SolutionRequest struct {
+ TaskID string `json:"task_id" binding:"required"`
+ Code string `json:"code" binding:"required"`
+}
+
+type Claims struct {
+ UserID int `json:"user_id"`
+ jwt.RegisteredClaims
+}
+
+func initDb() error {
+ connStr := config.GetDSN()
+ var err error
+ postgresDb, err = sql.Open("postgres", connStr)
+ if err != nil {
+ return err
+ }
+ return postgresDb.Ping()
+}
+
+func initRedis() error {
+ redisDb = redis.NewClient(&redis.Options{
+ Addr: config.RedisHost,
+ Password: config.RedisPass,
+ DB: config.RedisDb,
+ })
+
+ ctx := context.Background()
+ _, err := redisDb.Ping(ctx).Result()
+ return err
+}
+
+func generateToken(userID int) (string, error) {
+ expirationTime := time.Now().Add(config.JWTExpiration)
+
+ claims := &Claims{
+ UserID: userID,
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(expirationTime),
+ IssuedAt: jwt.NewNumericDate(time.Now()),
+ NotBefore: jwt.NewNumericDate(time.Now()),
+ Issuer: "your-app-name",
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return token.SignedString([]byte(config.JWTSecretKey))
+}
+
+func extractTokenFromHeader(c *gin.Context) (string, error) {
+ tokenString := c.GetHeader("Authorization")
+ if tokenString == "" {
+ return "", errors.New("authorization header is required")
+ }
+
+ if len(tokenString) > 7 && strings.HasPrefix(tokenString, "Bearer ") {
+ tokenString = tokenString[7:]
+ }
+
+ return tokenString, nil
+}
+
+func extractTokenFromCookie(c *gin.Context) (string, error) {
+ tokenString, err := c.Cookie("jwtToken")
+ if err != nil {
+ return "", errors.New("jwt cookie is required")
+ }
+ return tokenString, nil
+}
+
+func parseToken(tokenString string) (*Claims, error) {
+ claims := &Claims{}
+ token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
+ return []byte(config.JWTSecretKey), nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ if !token.Valid {
+ return nil, errors.New("invalid token")
+ }
+
+ return claims, nil
+}
+
+func authMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ tokenString, err := extractTokenFromHeader(c)
+ if err != nil {
+ tokenString, err = extractTokenFromCookie(c)
+ if err != nil {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization token is required"})
+ c.Abort()
+ return
+ }
+ }
+
+ claims, err := parseToken(tokenString)
+ if err != nil {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token: " + err.Error()})
+ c.Abort()
+ return
+ }
+
+ log.Print(claims.UserID)
+
+ err = postgresDb.QueryRow(`
+ SELECT id, login
+ FROM "account"
+ WHERE id = $1`, claims.UserID).
+ Scan(&user.ID, &user.Email)
+
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"})
+ } else {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
+ }
+ c.Abort()
+ return
+ }
+
+ c.Set("id_user", user.ID)
+ log.Println("id_user_middle:" , user.ID)
+ c.Set("user_name", user.Email)
+ c.Set("user_email", user.Email)
+ c.Next()
+ }
+}
+
+func registerUser(c *gin.Context) {
+ var user User
+ if err := c.ShouldBindJSON(&user); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
+ return
+ }
+
+ var id int
+ err = postgresDb.QueryRow(`
+ INSERT INTO "account" ( login, pswd, is_block, dt_create)
+ VALUES ($1, $2, $3, now())
+ RETURNING id`, user.Email, string(hashedPassword), false).Scan(&id)
+ if err != nil {
+ fmt.Println(err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user: " + err.Error()})
+ return
+ }
+
+ token, err := generateToken(id)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
+ return
+ }
+
+ c.SetCookie("jwtToken", token, int(config.JWTExpiration.Seconds()), "/", "", false, true)
+
+ response := AuthResponse{
+ User: UserResponse{
+ ID: id,
+ Name: user.Name,
+ Email: user.Email,
+ },
+ Token: token,
+ }
+
+ c.JSON(http.StatusCreated, gin.H{
+ "message": "User registered successfully",
+ "data": response,
+ })
+}
+
+func loginUser(c *gin.Context) {
+ var req LoginRequest
+
+ fmt.Println(req.Email)
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+ //log.Print(req)
+ var user struct {
+ ID int
+ Login string
+ Password string
+ }
+
+ err := postgresDb.QueryRow(`
+ SELECT id, login, pswd
+ FROM "account"
+ WHERE login like $1`, req.Email).
+ Scan(&user.ID, &user.Login, &user.Password)
+
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
+ return
+ }
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
+ return
+ }
+
+ if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
+ return
+ }
+
+ token, err := generateToken(user.ID)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
+ return
+ }
+
+ c.SetCookie("jwtToken", token, int(config.JWTExpiration.Seconds()), "/", "", false, true)
+
+ c.JSON(http.StatusOK, gin.H{
+ "message": "Login successful",
+ "data": AuthResponse{
+ User: UserResponse{
+ ID: user.ID,
+ Name: user.Login,
+ Email: user.Login,
+ },
+ Token: token,
+ },
+ })
+}
+
+func SaveSolution(c *gin.Context) {
+ userID, exists := c.Get("user_id")
+ if !exists {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
+ return
+ }
+
+ var req SolutionRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{
+ "error": "Invalid request body",
+ "details": err.Error(),
+ })
+ return
+ }
+
+ _, err := postgresDb.Exec(`
+ INSERT INTO solution (id_task, id_user, code)
+ VALUES ($1, $2, $3)
+ ON CONFLICT (id_task, id_user)
+ DO UPDATE SET code = EXCLUDED.code`,
+ req.TaskID, userID, req.Code)
+
+ if err != nil {
+ log.Printf("Database error: %v", err)
+ c.JSON(http.StatusInternalServerError, gin.H{
+ "error": "Failed to save solution",
+ "details": err.Error(),
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Solution saved successfully"})
+}
+
+func getSolution(c *gin.Context) {
+ userID, exists := c.Get("user_id")
+ if !exists {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
+ return
+ }
+
+ taskID, err := strconv.Atoi(c.Param("task_id"))
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
+ return
+ }
+ var code string
+ err = postgresDb.QueryRow(`
+ SELECT code
+ FROM solution
+ WHERE id_task = $1
+ AND id_user = $2`,
+ taskID, userID.(int)).Scan(&code)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ c.JSON(http.StatusNotFound, gin.H{"error": "Solution not found"})
+ } else {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get solution"})
+ }
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"code": code})
+}
+
+func createTask(c *gin.Context) {
+ userID, exists := c.Get("user_id")
+ if !exists {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
+ return
+ }
+
+ var req SolutionRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{
+ "error": "Invalid request body",
+ "details": err.Error(),
+ })
+ return
+ }
+
+ _, err := postgresDb.Exec(`
+ INSERT INTO solution (id_task, id_user, code)
+ VALUES ($1, $2, $3)
+ ON CONFLICT (id_task, id_user)
+ DO UPDATE SET code = EXCLUDED.code`,
+ req.TaskID, userID, req.Code)
+
+ if err != nil {
+ log.Printf("Database error: %v", err)
+ c.JSON(http.StatusInternalServerError, gin.H{
+ "error": "Failed to save solution",
+ "details": err.Error(),
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Solution saved successfully"})
+}
+
+func Logout(c *gin.Context) {
+ c.SetCookie("jwtToken", "", -1, "/", "", false, true)
+ c.JSON(http.StatusOK, gin.H{
+ "message": "Successfully logged out",
+ })
+}
+
+func main() {
+ log.SetFlags(log.Lshortfile)
+
+ // postgresDb, err := database.NewConnection()
+ // if err != nil {
+ // log.Fatalf("Failed to connect to database: %v", err)
+ // }
+ // defer postgresDb.Close()
+ // init DB
+ database.ConnectDatabasePostgres()
+ postgresDb = database.DB
+
+ // if err := initDb();
+ // err != nil {
+ // log.Fatalf("PostgreSQL init error: %v", err)
+ // }
+ defer postgresDb.Close()
+
+ router := gin.Default()
+ router.LoadHTMLGlob("./templates/*")
+
+ // Обработчик для 404 ошибок
+ router.NoRoute(func(c *gin.Context) {
+ c.HTML(http.StatusNotFound, "404.html", gin.H{
+ "requestedPath": c.Request.URL.Path,
+ })
+ })
+
+ router.GET("/", func(c *gin.Context) {
+ c.HTML(http.StatusOK, "auth.html", gin.H{})
+ })
+
+ router.GET("/profile", authMiddleware(), func(c *gin.Context) {
+ userID, existId := c.Get("user_id")
+ log.Print(userID)
+ userName, existName := c.Get("user_name")
+ log.Print(userName)
+ userEmail, existEmail := c.Get("user_email")
+ log.Print(userEmail)
+ if !existId || !existName || !existEmail {
+ return
+ }
+
+ c.HTML(http.StatusOK, "profile.html", gin.H{
+ "userId": userID,
+ "userName": userName,
+ "userEmail": userEmail,
+ })
+ })
+
+ // TEST SYSYTEM ROUTER
+
+ router.GET("/task", authMiddleware(), handler.GetAllTask)
+ router.GET("/task/:id", authMiddleware(), handler.GetTask)
+ // todo: Добавить страницу
+ router.POST("/task", handler.CreateTask)
+
+ authGroup := router.Group("/")
+ authGroup.Use(authMiddleware())
+ {
+ authGroup.POST("/solution", SaveSolution)
+ authGroup.GET("/solution/:task_id", getSolution)
+ }
+
+ // AUTH ROUTER
+ router.POST("/register", registerUser)
+ router.POST("/login", loginUser)
+ router.POST("/logout", authMiddleware(), Logout)
+
+ // RUN SERVER
+ if err := router.Run(":8081"); err != nil {
+ log.Fatalf("Failed to start server: %v", err)
+ }
+}
diff --git a/cmd/service_front/templates/404.html b/cmd/service_front/templates/404.html
new file mode 100644
index 0000000..934ea72
--- /dev/null
+++ b/cmd/service_front/templates/404.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+ Страница не найдена
+
+
+
+
+
404
+
Упс! Страница не найдена
+
Возможно, она была перемещена или удалена.
+
На главную
+
+
+
\ No newline at end of file
diff --git a/cmd/service_front/templates/auth.html b/cmd/service_front/templates/auth.html
new file mode 100644
index 0000000..580ad2f
--- /dev/null
+++ b/cmd/service_front/templates/auth.html
@@ -0,0 +1,257 @@
+
+
+
+
+
+ Регистрация/Авторизация
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/service_front/templates/profile.html b/cmd/service_front/templates/profile.html
new file mode 100644
index 0000000..ec9a4f3
--- /dev/null
+++ b/cmd/service_front/templates/profile.html
@@ -0,0 +1,174 @@
+
+
+
+
+
+ Профиль пользователя
+
+
+
+
+
+
+
+
+
+
+
+
Имя пользователя:
+
{{ .userName }}
+
+
+
ID пользователя:
+
{{ .userId }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/service_front/templates/task_list.html b/cmd/service_front/templates/task_list.html
new file mode 100644
index 0000000..9dde9f4
--- /dev/null
+++ b/cmd/service_front/templates/task_list.html
@@ -0,0 +1,141 @@
+
+
+
+
+
+ Список задач Python
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/service_front/templates/task_solution.html b/cmd/service_front/templates/task_solution.html
new file mode 100644
index 0000000..c5db29c
--- /dev/null
+++ b/cmd/service_front/templates/task_solution.html
@@ -0,0 +1,300 @@
+
+
+
+
+
+ Задача #{{ .task.ID }}
+
+
+
+
+
+
+
+
+
+
+
+
Условие задачи:
+
{{ .task.Description }}
+
+
+
+
Ваше решение:
+
+
+
+
+
+
+
+
+
+
+
Результат выполнения:
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/service_push/s_push.go b/cmd/service_push/s_push.go
new file mode 100644
index 0000000..bc937e1
--- /dev/null
+++ b/cmd/service_push/s_push.go
@@ -0,0 +1,108 @@
+package main
+
+import (
+ "code_runner/config"
+ "encoding/json"
+ "fmt"
+ "github.com/gorilla/websocket"
+ "github.com/streadway/amqp"
+ "log"
+ "net/http"
+)
+
+var upgrader = websocket.Upgrader{
+ CheckOrigin: func(r *http.Request) bool {
+ return true
+ },
+}
+
+func handleConnections(w http.ResponseWriter, r *http.Request) {
+ type CallbackMessage struct {
+ Success bool `json:"success"`
+ Message string `json:"message"`
+ Queue string `json:"queue"`
+ }
+
+ ws, _ := upgrader.Upgrade(w, r, nil)
+ amqpServerURL := config.AmqpServerURL
+ // amqpServerURL := os.Getenv("AMQP_SERVER_URL")
+ if amqpServerURL == "" {
+ panic("Дискриптор для RabbitMQ не указан!")
+ }
+
+ connectRabbitMQ, err := amqp.Dial(amqpServerURL)
+ if err != nil {
+ panic(err)
+ }
+ defer connectRabbitMQ.Close()
+
+ channelRabbitMQ, err := connectRabbitMQ.Channel()
+ if err != nil {
+ panic(err)
+ }
+ defer channelRabbitMQ.Close()
+
+ messages, err := channelRabbitMQ.Consume(
+ config.TaskQueueCallbackName,
+ "",
+ true,
+ false,
+ false,
+ false,
+ nil,
+ )
+ if err != nil {
+ log.Println(err)
+ }
+
+ log.Println("Successfully connected to RabbitMQ")
+ log.Println("Waiting for messages")
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ defer ws.Close()
+
+ forever := make(chan bool)
+
+ go func() {
+ for message := range messages {
+ log.Printf(" > Received message: %s\n", message.Body)
+ var messageObj CallbackMessage
+
+ err := json.Unmarshal([]byte(message.Body), &messageObj)
+ if err != nil {
+ log.Fatalf("Ошибка при разборе JSON: %v", err)
+ }
+ err = ws.WriteMessage(websocket.TextMessage, []byte(messageObj.Message))
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ }
+ }()
+
+ for {
+ _, msg, err := ws.ReadMessage()
+ fmt.Println(err)
+
+ if err != nil {
+ return
+ }
+ fmt.Println(err)
+
+ err = ws.WriteMessage(websocket.TextMessage, msg)
+ if err != nil {
+ return
+ }
+ }
+
+ <-forever
+}
+
+func main() {
+ fmt.Println("Starting WebSocket server...")
+ http.HandleFunc("/ws", handleConnections)
+ log.Fatal(http.ListenAndServe(":8082", nil))
+}
diff --git a/cmd/templates/chat.html b/cmd/templates/chat.html
deleted file mode 100644
index e6edc58..0000000
--- a/cmd/templates/chat.html
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
- WebSocket Client
-
-
- WebSocket Client
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/cmd/templates/index.html b/cmd/templates/index.html
deleted file mode 100644
index 484ece0..0000000
--- a/cmd/templates/index.html
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
-
- Code Editor
-
-
-
-
-
-
- Code Editor
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..35ae713
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,50 @@
+package config
+
+import (
+ "fmt"
+ "time"
+)
+
+const TaskQueueCallbackName = "task_queue_callback"
+const TaskQueueName = "task_queue"
+const AmqpServerURL = "amqp://guest:guest@localhost:5672/"
+const AmqpServerKey = "AMQP_SERVER_URL"
+
+type MessageCode struct {
+ IdUser string `json:"id_user"`
+ IdTask string `json:"id_task"`
+ Code string `json:"code"`
+ Unit_test string `json:"unit_test"`
+ Tp_lang string `json:"tp_lang"`
+
+}
+
+type SuccessResponse struct {
+ Success bool `json:"success"`
+ Message string `json:"message"`
+ Queue string `json:"queue"`
+}
+
+const (
+ dbHost = "localhost"
+ dbPort = 5432
+ dbUser = "code_ru"
+ dbPassword = "code_ru"
+ dbName = "code_ru"
+)
+
+const (
+ RedisHost = "localhost:6379"
+ RedisPass = "5432"
+ RedisDb = 0
+)
+
+var (
+ JWTSecretKey = "your-very-secret-key"
+ JWTExpiration = 24 * time.Hour
+)
+
+func GetDSN() string {
+ return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
+ dbHost, dbPort, dbUser, dbPassword, dbName)
+}
diff --git a/doc/code.json b/doc/code.json
new file mode 100644
index 0000000..6cb0662
--- /dev/null
+++ b/doc/code.json
@@ -0,0 +1,8 @@
+{
+ "id_user": 100,
+ "id_task": "01975b05-647e-7ed5-abdd-f412a8a4882b",
+ "tp_runner": "python",
+ "code": "CmRlZiBoZWxsb193b3JsZCgpOgogICAgcHJpbnQoIkhlbGxvLCBXb3JsZCEiKQogICAg",
+ "test": "CmltcG9ydCB1bml0dGVzdAppbXBvcnQgaW8KaW1wb3J0IHN5cwpmcm9tIGNvbnRleHRsaWIgaW1wb3J0IHJlZGlyZWN0X3N0ZG91dApmcm9tIG1haW4gaW1wb3J0IGhlbGxvX3dvcmxkCgpjbGFzcyBUZXN0SGVsbG9Xb3JsZCh1bml0dGVzdC5UZXN0Q2FzZSk6CgogICAgZGVmIHRlc3RfaGVsbG9fd29ybGRfb3V0cHV0KHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFRlc3QgdGhhdCBoZWxsb193b3JsZCgpIHByaW50cyAiSGVsbG8sIFdvcmxkISIgdG8gc3Rkb3V0LgogICAgICAgICIiIgogICAgICAgIGYgPSBpby5TdHJpbmdJTygpCiAgICAgICAgd2l0aCByZWRpcmVjdF9zdGRvdXQoZik6CiAgICAgICAgICAgIGhlbGxvX3dvcmxkKCkKICAgICAgICBzZWxmLmFzc2VydEVxdWFsKGYuZ2V0dmFsdWUoKSwgIkhlbGxvLCBXb3JsZCFcbiIpCgppZiBfX25hbWVfXyA9PSAnX19tYWluX18nOgogICAgdW5pdHRlc3QubWFpbigpCiAgICA="
+}
+
diff --git a/go.mod b/go.mod
index a066529..30bca0d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,53 @@
-module git.gocommunity.ru/chertkov/code_runner
+module code_runner
-go 1.23.6
+go 1.23.10
+
+require (
+ github.com/gin-gonic/gin v1.10.1
+ github.com/gofiber/fiber/v2 v2.52.8
+ github.com/google/uuid v1.6.0
+ github.com/gorilla/websocket v1.5.3
+ github.com/lib/pq v1.10.9
+ github.com/redis/go-redis/v9 v9.10.0
+ github.com/streadway/amqp v1.1.0
+)
+
+require (
+ github.com/andybalholm/brotli v1.1.0 // indirect
+ github.com/bytedance/sonic v1.11.6 // indirect
+ github.com/bytedance/sonic/loader v0.1.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/cloudwego/base64x v0.1.4 // indirect
+ github.com/cloudwego/iasm v0.2.0 // indirect
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+ github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.20.0 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/compress v1.17.9 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+ github.com/rivo/uniseg v0.2.0 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.12 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasthttp v1.51.0 // indirect
+ github.com/valyala/tcplisten v1.0.0 // indirect
+ golang.org/x/arch v0.8.0 // indirect
+ golang.org/x/crypto v0.23.0 // indirect
+ golang.org/x/net v0.25.0 // indirect
+ golang.org/x/sys v0.28.0 // indirect
+ golang.org/x/text v0.15.0 // indirect
+ google.golang.org/protobuf v1.34.1 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..5ba9957
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,129 @@
+github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
+github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
+github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
+github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
+github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
+github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
+github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
+github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
+github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
+github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
+github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM=
+github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+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=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
+github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
+github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
+golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+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=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/internal/handler/task.go b/internal/handler/task.go
new file mode 100644
index 0000000..f8dc190
--- /dev/null
+++ b/internal/handler/task.go
@@ -0,0 +1,137 @@
+package handler
+
+import (
+ "code_runner/pkg/database"
+ "errors"
+ "log"
+ "net/http"
+ "strconv"
+
+ "database/sql"
+ "encoding/base64"
+
+ "github.com/gin-gonic/gin"
+)
+
+func CreateTask(c *gin.Context) {
+
+ var task tpTask
+ if err := c.ShouldBindJSON(&task); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{
+ "error": "Invalid request body",
+ "details": err.Error(),
+ })
+ log.Print("Invalid request body")
+ }
+ log.Print(task)
+ query := `INSERT INTO task (id_category, id_profile, tp_level, tp_lang, "name", description, code, unit_test, dt_create)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, now()) RETURNING id`
+
+ err := database.DB.QueryRow(
+ query,
+ task.Id_category,
+ task.Id_profile,
+ task.Tp_level,
+ task.Tp_lang,
+ task.Name,
+ task.Description,
+ task.Code,
+ task.Unit_test,
+ ).Scan(&task.ID)
+ log.Print(task.ID)
+ log.Print(err)
+
+}
+
+func GetTask(c *gin.Context) {
+ log.SetFlags(log.Lshortfile)
+
+ idStr := c.Param("id")
+ id_user, ok := c.Get("id_user")
+ if !ok {
+ log.Fatalf("Ошибка получения пользователя!")
+ }
+
+ log.Print("id_user", id_user )
+ log.Print("id:", c.Param("id"))
+
+
+ id, err := strconv.Atoi(idStr)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
+ return
+ }
+
+ var task tpTask
+ err = database.DB.QueryRow(`
+ SELECT id, name, description, code, tp_lang, unit_test
+ FROM task
+ WHERE id = $1`, id).
+ Scan(
+ &task.ID,
+ &task.Name,
+ &task.Description,
+ &task.Code,
+ &task.Tp_lang,
+ &task.Unit_test,
+ )
+
+ log.Println("task.Unit_test = ", task.Unit_test)
+ // Декодирование Base64 строки
+ decodedBytes, er := base64.StdEncoding.DecodeString(task.Code)
+
+ if er != nil {
+ log.Fatalf("Ошибка при декодировании Base64: %v", er)
+ }
+ task.Code = string(decodedBytes)
+
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ c.JSON(http.StatusNotFound, gin.H{"error": "Task not found: " + err.Error()})
+ } else {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error: " + err.Error()})
+ }
+ return
+ }
+
+ c.HTML(http.StatusOK, "task_solution.html", gin.H{
+ "task": task,
+ "id_user": id_user,
+ })
+}
+
+func GetAllTask(c *gin.Context) {
+ rows, err := database.DB.Query(`
+ SELECT id, description, tp_lang
+ FROM task`)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tasks: " + err.Error()})
+ return
+ }
+ defer rows.Close()
+
+ var taskList []tpTask
+
+ for rows.Next() {
+ var task tpTask
+ err := rows.Scan(
+ &task.ID,
+ &task.Description,
+ &task.Tp_lang,
+ )
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan task: " + err.Error()})
+ return
+ }
+ taskList = append(taskList, task)
+ }
+
+ if err = rows.Err(); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Error after scanning rows: " + err.Error()})
+ return
+ }
+
+ c.HTML(http.StatusOK, "task_list.html", gin.H{
+ "task_list": taskList,
+ })
+}
diff --git a/internal/handler/types.go b/internal/handler/types.go
new file mode 100644
index 0000000..6004325
--- /dev/null
+++ b/internal/handler/types.go
@@ -0,0 +1,21 @@
+package handler
+
+import "time"
+
+type tpTask struct {
+ ID int `json:"id"`
+ Id_category int `json:"id_category"`
+ Id_profile int `json:"id_profile"`
+ Tp_level string `json:"tp_level"`
+ Tp_lang string `json:"tp_lang"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Code string `json:"code"`
+ Unit_test string `json:"unit_test"`
+ Dt_create time.Time `json:"dt_create"`
+}
+
+
+
+
+
diff --git a/internal/model/task_model.go b/internal/model/task_model.go
new file mode 100644
index 0000000..92d52fb
--- /dev/null
+++ b/internal/model/task_model.go
@@ -0,0 +1,58 @@
+package model
+
+import (
+ "database/sql"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+type Repository interface {
+ Create(task *Task) error
+}
+
+type PostgresRepository struct {
+ db *sql.DB
+}
+
+func NewPostgresRepository(db *sql.DB) *PostgresRepository {
+ return &PostgresRepository{db: db}
+}
+
+func (r *PostgresRepository) Create(task *Task) error {
+
+ query := `INSERT INTO task (id_category, id_profile, tp_level, tp_lang, "name", description, code, unit_test, dt_create)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, now()) RETURNING id`
+
+ err := r.db.QueryRow(
+ query,
+ task.Id_category,
+ task.Id_profile,
+ task.Tp_level,
+ task.Tp_lang,
+ task.Name,
+ task.Description,
+ task.Code,
+ task.Unit_test,
+ task.Dt_create,
+
+ ).Scan(&task.ID)
+
+ return err
+}
+
+
+func CreateTask(c *gin.Context) {
+ id := c.Param("id") // Получаем ID пользователя из параметров URL.
+
+ // Здесь должна быть логика получения информации о пользователе из базы данных или другого источника.
+ // В этом примере мы просто возвращаем фиктивные данные.
+
+ user := map[string]interface{}{
+ "id": id,
+ "name": "John Doe",
+ "email": "john.doe@example.com",
+ }
+
+ c.JSON(http.StatusOK, user) // Отправляем JSON-ответ с информацией о пользователе.
+}
diff --git a/migration/create_db.go b/migration/create_db.go
new file mode 100644
index 0000000..57a7b5e
--- /dev/null
+++ b/migration/create_db.go
@@ -0,0 +1,52 @@
+package main
+
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "os"
+
+ _ "github.com/lib/pq" // PostgreSQL driver
+ "code_runner/config"
+)
+
+
+func main() {
+ // Connection string
+ connStr := config.GetDSN()
+
+ // Open database connection
+ db, err := sql.Open("postgres", connStr)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer db.Close()
+
+ // Test the connection
+ err = db.Ping()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println("Connected to the database!")
+ // Read DDL SQL statements to create DB
+ stmt, err := os.ReadFile("db.sql")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("Executed statement: %s\n", string(stmt))
+ _, err = db.Exec(string(stmt))
+ if err != nil {
+ log.Printf("Error executing statement: %s\nError: %v", stmt, err)
+ log.Fatal(err)
+ return // Exit if any statement fails
+ }
+
+ fmt.Println("- Database schema created successfully!")
+ fmt.Println("- Migration completed successfully")
+ fmt.Println("- Added 10 languages")
+ fmt.Println("- Added 5 Python tasks")
+
+}
\ No newline at end of file
diff --git a/migration/create_task_table.go b/migration/create_task_table.go
new file mode 100644
index 0000000..a6e1c27
--- /dev/null
+++ b/migration/create_task_table.go
@@ -0,0 +1,110 @@
+package main
+
+import (
+ "code_runner/config"
+ "database/sql"
+ "fmt"
+ _ "github.com/lib/pq"
+ "log"
+)
+
+var (
+ postgresDb *sql.DB
+)
+
+func main() {
+ connStr := config.GetDSN()
+ var err error
+ postgresDb, err = sql.Open("postgres", connStr)
+ if err != nil {
+ log.Fatalf("Failed to connect to database: %v", err)
+ }
+ defer postgresDb.Close()
+
+ err = postgresDb.Ping()
+ if err != nil {
+ log.Fatalf("Failed to ping database: %v", err)
+ }
+
+ createLangTableSQL := `
+ CREATE TABLE IF NOT EXISTS lang (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR(50) NOT NULL UNIQUE
+ );`
+
+ _, err = postgresDb.Exec(createLangTableSQL)
+ if err != nil {
+ log.Fatalf("Failed to create lang table: %v", err)
+ }
+
+ _, err = postgresDb.Exec(`
+ INSERT INTO lang (name)
+ VALUES ('Python')
+ ON CONFLICT (name) DO NOTHING`)
+ if err != nil {
+ log.Fatalf("Failed to insert Python language: %v", err)
+ }
+
+ var pythonID int
+ err = postgresDb.QueryRow("SELECT id FROM lang WHERE name = 'Python'").Scan(&pythonID)
+ if err != nil {
+ log.Fatalf("Failed to get Python language ID: %v", err)
+ }
+
+ createTaskTableSQL := `
+ CREATE TABLE IF NOT EXISTS task (
+ id SERIAL PRIMARY KEY,
+ text TEXT NOT NULL,
+ id_lang INTEGER REFERENCES lang(id) ON DELETE SET NULL
+ );
+
+ CREATE INDEX IF NOT EXISTS id_task_id_lang ON task(id_lang);
+ `
+
+ _, err = postgresDb.Exec(createTaskTableSQL)
+ if err != nil {
+ log.Fatalf("Failed to create tasks table: %v", err)
+ }
+
+ createSolutionTableSQL := `
+ CREATE TABLE IF NOT EXISTS solution (
+ id SERIAL PRIMARY KEY,
+ id_task INTEGER NOT NULL REFERENCES task(id) ON DELETE CASCADE,
+ id_user INTEGER NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
+ code TEXT NOT NULL,
+ UNIQUE(id_task, id_user)
+ );
+
+ CREATE INDEX IF NOT EXISTS id_solution_id_task ON solution(id_task);
+ CREATE INDEX IF NOT EXISTS id_solution_id_user ON solution(id_user);
+ `
+
+ _, err = postgresDb.Exec(createSolutionTableSQL)
+ if err != nil {
+ log.Fatalf("Failed to create solutions table: %v", err)
+ }
+
+ tasks := []string{
+ "найдите сумму чисел от 1 до 10",
+ "напишите проверку на палиндром",
+ "напишите генератор чисел Фибоначчи до 10 числа",
+ "подсчитайте количество гласных в строке",
+ "напишите код который переносит строку в массив побуквенно",
+ }
+
+ for _, task := range tasks {
+ _, err = postgresDb.Exec(`
+ INSERT INTO task (text, id_lang)
+ VALUES ($1, $2)`, task, pythonID)
+ if err != nil {
+ log.Printf("Failed to insert task '%s': %v", task, err)
+ continue
+ }
+ }
+
+ fmt.Println("- Migration completed successfully")
+ fmt.Println("- Created lang, task and solution tables")
+ fmt.Println("- Added Python language")
+ fmt.Println("- Added 5 Python tasks")
+ fmt.Println("- Created solution table with foreign keys to task and user")
+}
diff --git a/migration/db.sql b/migration/db.sql
new file mode 100644
index 0000000..0dff3fb
--- /dev/null
+++ b/migration/db.sql
@@ -0,0 +1,159 @@
+-- Удалить сперва таблицы, затем типы.
+--DROP TYPE prog_lang;
+--DROP TYPE prog_level;
+
+
+CREATE TYPE prog_level AS ENUM (
+ 'Нулевой',
+ 'Начальный',
+ 'Базовый',
+ 'Средний',
+ 'Продвинутый',
+ 'Эксперт',
+ 'Архитектор'
+);
+
+
+CREATE TYPE prog_lang AS ENUM (
+ 'Go',
+ 'Js',
+ 'Ts',
+ 'Python',
+ 'Java',
+ 'C++',
+ 'C',
+ 'PHP',
+ 'SQL'
+ );
+
+
+DROP TABLE "role_rules";
+CREATE TABLE "role_rules" (
+ "id_roles" INTEGER NOT NULL,
+ "id_rules" INTEGER NOT NULL,
+ PRIMARY KEY ("id_roles", "id_rules")
+);
+
+
+DROP TABLE "rules";
+CREATE TABLE "rules" (
+ "id" SERIAL PRIMARY KEY,
+ "id_rules" INTEGER,
+ "name" VARCHAR(255) NOT NULL,
+ "body" JSONB NOT NULL
+);
+
+
+DROP TABLE "role_account";
+CREATE TABLE "role_account" (
+ "id_role" INTEGER NOT NULL,
+ "id_accont" INTEGER NOT NULL,
+ PRIMARY KEY ("id_role", "id_accont")
+);
+
+DROP TABLE "role";
+CREATE TABLE "role" (
+ "id" SERIAL PRIMARY KEY,
+ "name" TEXT NOT NULL,
+ "description" VARCHAR(255) NOT NULL
+);
+
+
+DROP TABLE "solution";
+CREATE TABLE "solution" (
+ "id_tasks" INTEGER NOT NULL,
+ "id_students" INTEGER NOT NULL,
+ "message" TEXT NOT NULL,
+ "status" INTEGER,
+ "dt_check" TIMESTAMP,
+ PRIMARY KEY ("id_tasks", "id_students")
+);
+
+
+DROP TABLE "task_for_student";
+CREATE TABLE "task_for_student" (
+ "id_task" INTEGER NOT NULL,
+ "id_student" INTEGER NOT NULL,
+ "dt_of_delivery" DATE,
+ PRIMARY KEY ("id_task", "id_student")
+);
+
+
+
+DROP TABLE "task";
+CREATE TABLE "task" (
+ "id" SERIAL PRIMARY KEY,
+ "id_category" INTEGER,
+ "id_profile" INTEGER,
+ "tp_level" PROG_LEVEL NOT NULL,
+ "tp_lang" PROG_LANG NOT NULL,
+ "name" VARCHAR(255),
+ "description" TEXT NOT NULL,
+ "code" TEXT NOT NULL,
+ "unit_test" TEXT NOT NULL,
+ "dt_create" TIMESTAMP
+);
+
+
+DROP TABLE "profile";
+CREATE TABLE "profile" (
+ "id" SERIAL PRIMARY KEY,
+ "id_account" INTEGER NOT NULL,
+ "name" VARCHAR(100) NOT NULL,
+ "suname" VARCHAR(100) NOT NULL,
+ "git" VARCHAR(255) NOT NULL,
+ "dt_create" DATE
+);
+
+
+DROP TABLE "account";
+CREATE TABLE "account" (
+ "id" SERIAL PRIMARY KEY,
+ "login" VARCHAR(50) NOT NULL,
+ "pswd" VARCHAR(255) NOT NULL,
+ "is_block" BOOLEAN NOT NULL,
+ "dt_create" DATE NOT NULL,
+ "token" VARCHAR(255),
+ "refresh" VARCHAR(255)
+);
+
+
+
+-- TABLE "role_rules"
+-- DROP INDEX "idx_role_rules__id_rules";
+CREATE INDEX "idx_role_rules__id_rules" ON "role_rules" ("id_rules");
+ALTER TABLE "role_rules" ADD CONSTRAINT "fk_role_rules__id_roles" FOREIGN KEY ("id_roles") REFERENCES "role" ("id") ON DELETE CASCADE;
+ALTER TABLE "role_rules" ADD CONSTRAINT "fk_role_rules__id_rules" FOREIGN KEY ("id_rules") REFERENCES "rules" ("id") ON DELETE CASCADE;
+
+
+-- TABLE "profile"
+-- DROP INDEX "idx_profile__id_account";
+CREATE INDEX "idx_profile__id_account" ON "profile" ("id_account");
+ALTER TABLE "profile" ADD CONSTRAINT "fk_profile__id_account" FOREIGN KEY ("id_account") REFERENCES "account" ("id");
+
+
+-- TABLE "rules"
+-- DROP INDEX "idx_rules__id_rules";
+CREATE INDEX "idx_rules__id_rules" ON "rules" ("id_rules");
+ALTER TABLE "rules" ADD CONSTRAINT "fk_rules__id_rules" FOREIGN KEY ("id_rules") REFERENCES "rules" ("id") ON DELETE SET NULL;
+
+
+-- TABLE "task_for_student"
+-- DROP INDEX "idx_task_for_student__id_student";
+CREATE INDEX "idx_task_for_student__id_student" ON "task_for_student" ("id_student");
+ALTER TABLE "task_for_student" ADD CONSTRAINT "fk_task_for_student__id_student" FOREIGN KEY ("id_student") REFERENCES "profile" ("id") ON DELETE CASCADE;
+ALTER TABLE "task_for_student" ADD CONSTRAINT "fk_task_for_student__id_task" FOREIGN KEY ("id_task") REFERENCES "task" ("id") ON DELETE CASCADE;
+
+
+-- TABLE "role_account"
+-- DROP INDEX "idx_role_account__id_accont";
+CREATE INDEX "idx_role_account__id_accont" ON "role_account" ("id_accont");
+ALTER TABLE "role_account" ADD CONSTRAINT "fk_role_account__id_accont" FOREIGN KEY ("id_accont") REFERENCES "account" ("id") ON DELETE CASCADE;
+ALTER TABLE "role_account" ADD CONSTRAINT "fk_role_account__id_role" FOREIGN KEY ("id_role") REFERENCES "role" ("id") ON DELETE CASCADE;
+
+
+-- TABLE "solution"
+-- DROP INDEX "idx_solution__id_students";
+CREATE INDEX "idx_solution__id_students" ON "solution" ("id_students");
+ALTER TABLE "solution" ADD CONSTRAINT "fk_solution__id_students" FOREIGN KEY ("id_students") REFERENCES "profile" ("id") ON DELETE CASCADE;
+ALTER TABLE "solution" ADD CONSTRAINT "fk_solution__id_tasks" FOREIGN KEY ("id_tasks") REFERENCES "task" ("id") ON DELETE CASCADE;
diff --git a/pkg/database/postgres.go b/pkg/database/postgres.go
new file mode 100644
index 0000000..f487a25
--- /dev/null
+++ b/pkg/database/postgres.go
@@ -0,0 +1,30 @@
+package database
+
+import (
+ "code_runner/config"
+ "database/sql"
+ "log"
+
+ _ "github.com/lib/pq"
+)
+
+
+var DB *sql.DB
+
+func ConnectDatabasePostgres() {
+
+ connStr := config.GetDSN()
+ //log.Print(connStr)
+ database, err := sql.Open("postgres", connStr)
+ if err != nil {
+ log.Println(err)
+ panic("Failed to connect to database!")
+ }
+
+ if err = database.Ping(); err != nil {
+ panic("Server doesn't Ping!")
+ }
+
+ DB = database
+}
+
diff --git a/script/create_db.sh b/script/create_db.sh
new file mode 100755
index 0000000..16d722a
--- /dev/null
+++ b/script/create_db.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+
+# Настройки подключения
+DB_HOST="localhost"
+DB_PORT="5432"
+DB_USER="postgres"
+DB_PASSWORD="123"
+DB_NAME="postgres"
+
+# Настройки новой БД
+NEW_DB='code_ru'
+NEW_USER='code_ru'
+NEW_PASS='code_ru'
+
+
+# Экспортируем переменную для входа без пароля значение из
+export PGPASSWORD="$DB_PASSWORD"
+
+# Проверка наличия psql
+if ! command -v psql &> /dev/null
+then
+ echo "psql не найден. Установите PostgreSQL."
+ exit 1
+fi
+
+# Функция для проверки существования базы данных
+db_exists() {
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -lqt | cut -d \| -f 1 | grep -w "$1"
+}
+
+
+# Функция для создания пользователя базы данных
+create_user() {
+ echo "Создание пользователя [ $1 ]"
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -q -c "CREATE ROLE $1 WITH LOGIN PASSWORD '$2';"
+ if [ $? -eq 0 ]; then
+ echo "Пользователь [ $1 ] успешно создан."
+ else
+ echo "Фатальная ошибка при создании пользователя $1."
+ exit 1
+ fi
+}
+
+# Функция для создания базы данных
+create_db() {
+ echo "Создание базы данных [ $1 ]"
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -q -c "CREATE DATABASE $1 WITH OWNER = $2 ENCODING = 'UTF8' LC_COLLATE = 'ru_RU.UTF-8' LC_CTYPE = 'ru_RU.UTF-8'
+TEMPLATE = template0; "
+ if [ $? -eq 0 ]; then
+ echo "База данных [ $1 ] успешно создана!"
+ else
+ echo "Выход из скрипта. Фатальная ошибка при создании базы данных $1."
+ exit 1
+ fi
+}
+
+# Функция для предоставления прав пользователю на базу данных
+grant_privileges() {
+ echo "Предоставление прав пользователю [ $1 ] на базу данных [ $2 ]"
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -q -c "GRANT ALL PRIVILEGES ON DATABASE \"$2\" TO $1;"
+ if [ $? -eq 0 ]; then
+ echo "Права успешно предоставлены."
+ else
+ echo "Выход из скрипта. Фатальная ошибка при предоставлении прав."
+ exit 1
+ fi
+}
+
+
+# SQL-запрос для проверки существования пользователя
+SQL="SELECT 1 FROM pg_catalog.pg_user WHERE usename = '$NEW_USER';"
+
+if psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -q -t -c "$SQL" | grep -q "1"; then
+ echo "Пользователь [ $NEW_USER ] уже существует."
+else
+ echo "Пользователь [ $NEW_USER ] не существует."
+ create_user "$NEW_USER" "$NEW_PASS"
+fi
+
+
+# Проверка существования базы данных
+if db_exists "$NEW_DB"; then
+ echo "База данных [ $NEW_DB ] уже существует."
+else
+ # Создание базы данных
+ create_db "$NEW_DB" "$NEW_USER"
+fi
+
+# Предоставление прав пользователю на базу данных
+grant_privileges "$NEW_USER" "$NEW_DB"
+
+echo "Скрипт завершен успешно!"
+exit 0
\ No newline at end of file
diff --git a/script/run.sh b/script/run.sh
new file mode 100755
index 0000000..e9524d3
--- /dev/null
+++ b/script/run.sh
@@ -0,0 +1,14 @@
+#/bin/bash
+docker start rabbitmq && docker start my-redis
+
+
+/usr/bin/zsh -i -c "cd ../cmd/service_front && go run s_front.go"
+/usr/bin/zsh -i -c "cd ../cmd/service_push && go run s_push.go"
+/usr/bin/zsh -i -c "cd ../cmd/service_rest && go run s_rest.go"
+/usr/bin/zsh -i -c "cd ../cmd/service_agent && go run s_agent.go"
+
+#cd ./cmd/service_front && go run s_front.go
+#cd ./cmd/service_back && go run s_back.go
+#cd ./cmd/service_push && go run s_push.go
+#cd ./cmd/service_agent && go run s_agent.go
+