refactor: struct folder config and etc..
This commit is contained in:
parent
8c58dd0bcb
commit
8e2359c99a
1
.gitignore
vendored
1
.gitignore
vendored
@ -0,0 +1 @@
|
|||||||
|
.idea
|
||||||
@ -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`
|
`$ docker run --name postgres2 -p 5432:5432 -e POSTGRES_USER=auth -e POSTGRES_PASSWORD=123 -e POSTGRES_DB=auth -d postgres:16`
|
||||||
|
|
||||||
### RabbitMQ
|
### 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`
|
||||||
|
|
||||||
|
|||||||
@ -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))
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -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")
|
|
||||||
}
|
|
||||||
@ -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))
|
|
||||||
}
|
|
||||||
@ -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"))
|
|
||||||
}
|
|
||||||
222
cmd/service_agent/s_agent.go
Normal file
222
cmd/service_agent/s_agent.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
115
cmd/service_back/s_back.go
Normal file
115
cmd/service_back/s_back.go
Normal file
@ -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"))
|
||||||
|
}
|
||||||
482
cmd/service_front/s_front.go
Normal file
482
cmd/service_front/s_front.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
63
cmd/service_front/templates/404.html
Normal file
63
cmd/service_front/templates/404.html
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Страница не найдена</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.error-container {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
.error-code {
|
||||||
|
font-size: 80px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3f51b5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 20px 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.home-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 12px 30px;
|
||||||
|
background-color: #606fc7;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.home-link:hover {
|
||||||
|
background-color: #32408f;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(63, 81, 181, 0.3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="error-container">
|
||||||
|
<h1 class="error-code">404</h1>
|
||||||
|
<p class="error-message">Упс! Страница не найдена</p>
|
||||||
|
<p>Возможно, она была перемещена или удалена.</p>
|
||||||
|
<a href="/" class="home-link">На главную</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
257
cmd/service_front/templates/auth.html
Normal file
257
cmd/service_front/templates/auth.html
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Регистрация/Авторизация</title>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 400px;
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #eee;
|
||||||
|
border: none;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.tab.active {
|
||||||
|
background-color: #3f51b5;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.form-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.form-container.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 8px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: #3f51b5;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #606fc7;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
background-color: #dff0d8;
|
||||||
|
color: #3c763d;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background-color: #f2dede;
|
||||||
|
color: #a94442;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="tabs">
|
||||||
|
<button class="tab active" onclick="openTab('login')">Вход</button>
|
||||||
|
<button class="tab" onclick="openTab('register')">Регистрация</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="login-form" class="form-container active">
|
||||||
|
<h2>Вход</h2>
|
||||||
|
<form id="loginForm">
|
||||||
|
<input type="email" id="loginEmail" placeholder="Email" required>
|
||||||
|
<input type="password" id="loginPassword" placeholder="Пароль" required>
|
||||||
|
<button type="submit">Войти</button>
|
||||||
|
</form>
|
||||||
|
<div id="loginMessage" class="message"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="register-form" class="form-container">
|
||||||
|
<h2>Регистрация</h2>
|
||||||
|
<form id="registerForm">
|
||||||
|
<input type="text" id="registerName" placeholder="Имя" required>
|
||||||
|
<input type="email" id="registerEmail" placeholder="Email" required>
|
||||||
|
<input type="password" id="registerPassword" placeholder="Пароль (минимум 6 символов)" required>
|
||||||
|
<button type="submit">Зарегистрироваться</button>
|
||||||
|
</form>
|
||||||
|
<div id="registerMessage" class="message"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function setCookie(name, value, days) {
|
||||||
|
let expires = "";
|
||||||
|
if (days) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
|
}
|
||||||
|
function getCookie(name) {
|
||||||
|
const nameEQ = name + "=";
|
||||||
|
const ca = document.cookie.split(';');
|
||||||
|
for(let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||||
|
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAuthData(token, userData) {
|
||||||
|
setCookie('jwtToken', token, 7);
|
||||||
|
localStorage.setItem('jwtToken', token);
|
||||||
|
localStorage.setItem('userData', JSON.stringify(userData));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToken() {
|
||||||
|
const cookieToken = getCookie('jwtToken');
|
||||||
|
return cookieToken || localStorage.getItem('jwtToken');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAuthHeaders() {
|
||||||
|
const token = getToken();
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token ? {'Authorization': `Bearer ${token}`} : {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openTab(tabName) {
|
||||||
|
document.querySelectorAll('.form-container').forEach(form => {
|
||||||
|
form.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById(tabName + '-form').classList.add('active');
|
||||||
|
|
||||||
|
document.querySelectorAll('.tab').forEach(tab => {
|
||||||
|
tab.classList.remove('active');
|
||||||
|
});
|
||||||
|
event.currentTarget.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loginForm').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const email = document.getElementById('loginEmail').value;
|
||||||
|
const password = document.getElementById('loginPassword').value;
|
||||||
|
const messageElement = document.getElementById('loginMessage');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: email,
|
||||||
|
password: password
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
saveAuthData(data.data.token, data.data.user);
|
||||||
|
|
||||||
|
showMessage('loginMessage', 'success', 'Успешный вход! Перенаправление...');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/profile';
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
showMessage('loginMessage', 'error', data.error || 'Ошибка входа');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage('loginMessage', 'error', 'Ошибка сети: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('registerForm').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const name = document.getElementById('registerName').value;
|
||||||
|
const email = document.getElementById('registerEmail').value;
|
||||||
|
const password = document.getElementById('registerPassword').value;
|
||||||
|
|
||||||
|
if (password.length < 6) {
|
||||||
|
showMessage('registerMessage', 'error', 'Пароль должен содержать минимум 6 символов');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/register', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: name,
|
||||||
|
email: email,
|
||||||
|
password: password
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
saveAuthData(data.data.token, data.data.user);
|
||||||
|
showMessage('registerMessage', 'success', 'Регистрация успешна! Перенаправление...');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/profile';
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
showMessage('registerMessage', 'error', data.error || 'Ошибка регистрации');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage('registerMessage', 'error', 'Ошибка сети: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showMessage(elementId, type, message) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
element.textContent = message;
|
||||||
|
element.className = 'message ' + type;
|
||||||
|
element.style.display = 'block';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
element.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
174
cmd/service_front/templates/profile.html
Normal file
174
cmd/service_front/templates/profile.html
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Профиль пользователя</title>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.user-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
background-color: #3f51b5;
|
||||||
|
padding: 15px 20px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
.toolbar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.toolbar-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.toolbar-navigation {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.toolbar-btn {
|
||||||
|
background-color: #606fc7;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background-color: #32408f;
|
||||||
|
}
|
||||||
|
.profile-container {
|
||||||
|
background-color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.profile-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #3f51b5;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 32px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.profile-info h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.profile-info p {
|
||||||
|
margin: 5px 0 0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.profile-details {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.detail-label {
|
||||||
|
width: 150px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.detail-value {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.logout-btn {
|
||||||
|
background-color: #696969;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.logout-btn:hover {
|
||||||
|
background-color: #424242;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="user-card">
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="toolbar-header">
|
||||||
|
<div class="toolbar-title">Профиль пользователя</div>
|
||||||
|
<button class="logout-btn" id="logout-btn">Выйти</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toolbar-navigation">
|
||||||
|
<a href="/task" class="toolbar-btn">Задачи</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-container">
|
||||||
|
<div class="profile-header">
|
||||||
|
<div class="avatar" id="avatar">?</div>
|
||||||
|
<div class="profile-info">
|
||||||
|
<h2 id="user-email">{{ .userEmail }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-details">
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">Имя пользователя:</div>
|
||||||
|
<div class="detail-value" id="reg-date">{{ .userName }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<div class="detail-label">ID пользователя:</div>
|
||||||
|
<div class="detail-value" id="user-id">{{ .userId }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const userName = document.getElementById('reg-date').textContent;
|
||||||
|
const avatar = document.getElementById('avatar');
|
||||||
|
|
||||||
|
if (userName) {
|
||||||
|
const initials = userName.split(' ').map(n => n[0]).join('').toUpperCase();
|
||||||
|
avatar.textContent = initials || '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('logout-btn').addEventListener('click', async function () {
|
||||||
|
const response = await fetch('/logout', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка выхода');
|
||||||
|
}
|
||||||
|
localStorage.removeItem('jwtToken');
|
||||||
|
localStorage.removeItem('userData');
|
||||||
|
window.location.href = '/';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
141
cmd/service_front/templates/task_list.html
Normal file
141
cmd/service_front/templates/task_list.html
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Список задач Python</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.user-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
background-color: #3f51b5;
|
||||||
|
padding: 15px 20px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
.toolbar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.toolbar-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.toolbar-navigation {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.toolbar-btn {
|
||||||
|
background-color: #606fc7;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background-color: #32408f;
|
||||||
|
}
|
||||||
|
.tasks-container {
|
||||||
|
background-color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.task-list {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.task-item {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.task-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.task-link {
|
||||||
|
padding: 15px 10px;
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.task-link:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.task-meta {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.logout-btn {
|
||||||
|
background-color: #696969;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.logout-btn:hover {
|
||||||
|
background-color: #424242;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="user-card">
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="toolbar-header">
|
||||||
|
<div class="toolbar-title">Список задач</div>
|
||||||
|
<button class="logout-btn" id="logout-btn">Выйти</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toolbar-navigation">
|
||||||
|
<a href="/profile" class="toolbar-btn">Профиль</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tasks-container">
|
||||||
|
<ul class="task-list">
|
||||||
|
{{range .task_list}}
|
||||||
|
<li class="task-item">
|
||||||
|
<a href="/task/{{.ID}}" class="task-link">
|
||||||
|
{{.Description}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('logout-btn').addEventListener('click', async function () {
|
||||||
|
const response = await fetch('/logout', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка выхода');
|
||||||
|
}
|
||||||
|
localStorage.removeItem('jwtToken');
|
||||||
|
localStorage.removeItem('userData');
|
||||||
|
window.location.href = '/';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
300
cmd/service_front/templates/task_solution.html
Normal file
300
cmd/service_front/templates/task_solution.html
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Задача #{{ .task.ID }}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/mode/python/python.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.task-container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.task-header {
|
||||||
|
background-color: #3f51b5;
|
||||||
|
color: white;
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
.task-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.task-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.task-description {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 4px solid #3f51b5;
|
||||||
|
}
|
||||||
|
.code-editor {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.CodeMirror {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.run-button {
|
||||||
|
background-color: #606fc7;
|
||||||
|
color: white;
|
||||||
|
min-width: 120px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.run-button:hover {
|
||||||
|
background-color: #32408f;
|
||||||
|
}
|
||||||
|
.save-button {
|
||||||
|
display: none;
|
||||||
|
background-color: #606fc7;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.save-button:hover {
|
||||||
|
background-color: #32408f;
|
||||||
|
}
|
||||||
|
.output-container {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.navigation {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.back-link {
|
||||||
|
color: #3f51b5;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.back-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.button.loading {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.button.loading::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
border-top-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: button-loading-spinner 1s ease infinite;
|
||||||
|
}
|
||||||
|
@keyframes button-loading-spinner {
|
||||||
|
from { transform: rotate(0turn); }
|
||||||
|
to { transform: rotate(1turn); }
|
||||||
|
}
|
||||||
|
#messages {
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="task-container">
|
||||||
|
<div class="task-header">
|
||||||
|
<h1 class="task-title">Задача №{{ .task.ID }}. Язык программирования {{ .task.Tp_lang }} </h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="task-content">
|
||||||
|
<div class="task-description">
|
||||||
|
<h3>Условие задачи:</h3>
|
||||||
|
<p>{{ .task.Description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="code-editor">
|
||||||
|
<h3>Ваше решение:</h3>
|
||||||
|
<textarea id="codeEditor">{{ .task.Code }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="button run-button" onclick="runCode()">Выполнить</button>
|
||||||
|
<button class="button save-button" onclick="saveSolution()">Сохранить</button>
|
||||||
|
<button class="button" onclick="sendMessage()">Test WS</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="output-container" id="output">
|
||||||
|
<h3>Результат выполнения:</h3>
|
||||||
|
<div id="messages"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navigation">
|
||||||
|
<a href="/task" class="back-link">← Вернуться к списку задач</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const userData = {
|
||||||
|
userName: "{{ .User.Name }}",
|
||||||
|
id_user: "{{ .id_user }}",
|
||||||
|
id_task: "{{ .task.ID }}",
|
||||||
|
tp_lang: "{{ .task.Tp_lang }}",
|
||||||
|
unit_test: "{{ .task.Unit_test }}"
|
||||||
|
};
|
||||||
|
|
||||||
|
const editor = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
|
||||||
|
mode: "javascript",
|
||||||
|
lineNumbers: true,
|
||||||
|
theme: "default"
|
||||||
|
});
|
||||||
|
|
||||||
|
const websocket = new WebSocket("ws://localhost:8082/ws");
|
||||||
|
|
||||||
|
websocket.onopen = function(event) {
|
||||||
|
console.log("Connected to WebSocket server");
|
||||||
|
};
|
||||||
|
|
||||||
|
websocket.onmessage = function(event) {
|
||||||
|
const messages = document.getElementById("messages");
|
||||||
|
messages.innerHTML = '';
|
||||||
|
const message = document.createElement("p");
|
||||||
|
message.textContent = event.data;
|
||||||
|
messages.appendChild(message);
|
||||||
|
const saveButton = document.querySelector(".save-button");
|
||||||
|
saveButton.style.display = 'block';
|
||||||
|
};
|
||||||
|
|
||||||
|
websocket.onclose = function(event) {
|
||||||
|
console.log("Disconnected from WebSocket server");
|
||||||
|
};
|
||||||
|
|
||||||
|
function runCode() {
|
||||||
|
const runButton = document.querySelector('.run-button');
|
||||||
|
const messages = document.getElementById("messages");
|
||||||
|
|
||||||
|
runButton.classList.add('loading');
|
||||||
|
runButton.disabled = true;
|
||||||
|
runButton.textContent = '';
|
||||||
|
messages.innerHTML = '<p>Выполнение...</p>';
|
||||||
|
|
||||||
|
fetch('http://127.0.0.1:3000/run', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id_task: userData.id_task,
|
||||||
|
id_user: userData.id_user,
|
||||||
|
tp_lang: userData.tp_lang,
|
||||||
|
unit_test: userData.unit_test,
|
||||||
|
code: btoa(editor.getValue()),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.catch(error => console.error('Error updating code:', error))
|
||||||
|
.finally(() => {
|
||||||
|
runButton.classList.remove('loading');
|
||||||
|
runButton.disabled = false;
|
||||||
|
runButton.textContent = 'Выполнить';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSolution() {
|
||||||
|
const saveButton = document.querySelector('.save-button');
|
||||||
|
const messages = document.getElementById("messages");
|
||||||
|
|
||||||
|
saveButton.classList.add('loading');
|
||||||
|
saveButton.disabled = true;
|
||||||
|
saveButton.textContent = 'Сохранение...';
|
||||||
|
|
||||||
|
fetch('/solution', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('jwtToken')}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
task_id: userData.id_task,
|
||||||
|
code: editor.getValue()
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка сохранения');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
messages.innerHTML = '<p style="color: green">Решение успешно сохранено!</p>';
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
messages.innerHTML = `<p style="color: red">Ошибка сохранения: ${error.message}</p>`;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
saveButton.classList.remove('loading');
|
||||||
|
saveButton.disabled = false;
|
||||||
|
saveButton.textContent = 'Сохранить';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
const messageInput = document.getElementById("codeEditor");
|
||||||
|
websocket.send("01975b05-647e-7ed5-abdd-f412a8a4882b");
|
||||||
|
messageInput.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загружаем сохраненное решение при загрузке страницы
|
||||||
|
function loadSavedSolution() {
|
||||||
|
fetch(`/solution/${userData.id_task}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('jwtToken')}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data && data.code) {
|
||||||
|
editor.setValue(data.code);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error loading solution:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', loadSavedSolution);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
108
cmd/service_push/s_push.go
Normal file
108
cmd/service_push/s_push.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>WebSocket Client</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>WebSocket Client</h1>
|
|
||||||
<input type="text" id="messageInput" placeholder="Enter message">
|
|
||||||
<button onclick="sendMessage()">Send</button>
|
|
||||||
<div id="messages"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var websocket = new WebSocket("ws://localhost:8081/ws");
|
|
||||||
|
|
||||||
websocket.onopen = function(event) {
|
|
||||||
console.log("Connected to WebSocket server");
|
|
||||||
};
|
|
||||||
|
|
||||||
websocket.onmessage = function(event) {
|
|
||||||
var messages = document.getElementById("messages");
|
|
||||||
var message = document.createElement("p");
|
|
||||||
message.textContent = "Received: " + event.data;
|
|
||||||
messages.appendChild(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
websocket.onclose = function(event) {
|
|
||||||
console.log("Disconnected from WebSocket server");
|
|
||||||
};
|
|
||||||
|
|
||||||
function sendMessage() {
|
|
||||||
var messageInput = document.getElementById("messageInput");
|
|
||||||
websocket.send(messageInput.value);
|
|
||||||
messageInput.value = "";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Code Editor</title>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.css">
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/mode/javascript/javascript.min.js"></script>
|
|
||||||
<style>
|
|
||||||
.CodeMirror {
|
|
||||||
border: 1px solid #eee;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.CodeMirror-scroll {
|
|
||||||
overflow-y: hidden;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Code Editor</h1>
|
|
||||||
<textarea id="codeEditor"></textarea>
|
|
||||||
<button onclick="updateCode()">Update Code</button>
|
|
||||||
<button onclick="runCode()">Run</button>
|
|
||||||
<button onclick="sendMessage()">Test WS</button>
|
|
||||||
<div id="messages"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var editor = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
|
|
||||||
mode: "javascript",
|
|
||||||
lineNumbers: true,
|
|
||||||
theme: "default"
|
|
||||||
});
|
|
||||||
|
|
||||||
var websocket = new WebSocket("ws://localhost:8081/ws");
|
|
||||||
|
|
||||||
websocket.onopen = function(event) {
|
|
||||||
console.log("Connected to WebSocket server");
|
|
||||||
};
|
|
||||||
|
|
||||||
websocket.onmessage = function(event) {
|
|
||||||
var messages = document.getElementById("messages");
|
|
||||||
var message = document.createElement("p");
|
|
||||||
message.textContent = "Received: " + event.data;
|
|
||||||
messages.appendChild(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
websocket.onclose = function(event) {
|
|
||||||
console.log("Disconnected from WebSocket server");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to fetch the code snippet from the server
|
|
||||||
function fetchCode() {
|
|
||||||
fetch('/task/01975b05-647e-7ed5-abdd-f412a8a4882b') // Assuming snippet ID is 1
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
editor.setValue(atob(JSON.parse(data.code).code));
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error fetching code:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to update the code snippet on the server
|
|
||||||
function runCode() {
|
|
||||||
fetch('http://127.0.0.1:3000/run', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ code: btoa(editor.getValue()) }),
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
alert(data.message);
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error updating code:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function sendMessage() {
|
|
||||||
var messageInput = document.getElementById("codeEditor");
|
|
||||||
websocket.send("01975b05-647e-7ed5-abdd-f412a8a4882b");
|
|
||||||
// Fetch code on page load
|
|
||||||
//fetchCode();
|
|
||||||
// websocket.send(messageInput.value);
|
|
||||||
messageInput.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch code on page load
|
|
||||||
fetchCode();
|
|
||||||
setTimeout(sendMessage, 1000);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
50
config/config.go
Normal file
50
config/config.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
8
doc/code.json
Normal file
8
doc/code.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"id_user": 100,
|
||||||
|
"id_task": "01975b05-647e-7ed5-abdd-f412a8a4882b",
|
||||||
|
"tp_runner": "python",
|
||||||
|
"code": "CmRlZiBoZWxsb193b3JsZCgpOgogICAgcHJpbnQoIkhlbGxvLCBXb3JsZCEiKQogICAg",
|
||||||
|
"test": "CmltcG9ydCB1bml0dGVzdAppbXBvcnQgaW8KaW1wb3J0IHN5cwpmcm9tIGNvbnRleHRsaWIgaW1wb3J0IHJlZGlyZWN0X3N0ZG91dApmcm9tIG1haW4gaW1wb3J0IGhlbGxvX3dvcmxkCgpjbGFzcyBUZXN0SGVsbG9Xb3JsZCh1bml0dGVzdC5UZXN0Q2FzZSk6CgogICAgZGVmIHRlc3RfaGVsbG9fd29ybGRfb3V0cHV0KHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFRlc3QgdGhhdCBoZWxsb193b3JsZCgpIHByaW50cyAiSGVsbG8sIFdvcmxkISIgdG8gc3Rkb3V0LgogICAgICAgICIiIgogICAgICAgIGYgPSBpby5TdHJpbmdJTygpCiAgICAgICAgd2l0aCByZWRpcmVjdF9zdGRvdXQoZik6CiAgICAgICAgICAgIGhlbGxvX3dvcmxkKCkKICAgICAgICBzZWxmLmFzc2VydEVxdWFsKGYuZ2V0dmFsdWUoKSwgIkhlbGxvLCBXb3JsZCFcbiIpCgppZiBfX25hbWVfXyA9PSAnX19tYWluX18nOgogICAgdW5pdHRlc3QubWFpbigpCiAgICA="
|
||||||
|
}
|
||||||
|
|
||||||
54
go.mod
54
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
|
||||||
|
)
|
||||||
|
|||||||
129
go.sum
Normal file
129
go.sum
Normal file
@ -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=
|
||||||
137
internal/handler/task.go
Normal file
137
internal/handler/task.go
Normal file
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
21
internal/handler/types.go
Normal file
21
internal/handler/types.go
Normal file
@ -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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
58
internal/model/task_model.go
Normal file
58
internal/model/task_model.go
Normal file
@ -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-ответ с информацией о пользователе.
|
||||||
|
}
|
||||||
52
migration/create_db.go
Normal file
52
migration/create_db.go
Normal file
@ -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")
|
||||||
|
|
||||||
|
}
|
||||||
110
migration/create_task_table.go
Normal file
110
migration/create_task_table.go
Normal file
@ -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")
|
||||||
|
}
|
||||||
159
migration/db.sql
Normal file
159
migration/db.sql
Normal file
@ -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;
|
||||||
30
pkg/database/postgres.go
Normal file
30
pkg/database/postgres.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
93
script/create_db.sh
Executable file
93
script/create_db.sh
Executable file
@ -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
|
||||||
14
script/run.sh
Executable file
14
script/run.sh
Executable file
@ -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
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user