first commit

This commit is contained in:
vitaliy 2025-06-20 15:11:48 +03:00
commit c0e8690bae
10 changed files with 703 additions and 0 deletions

0
.gitignore vendored Normal file
View File

23
README.md Normal file
View File

@ -0,0 +1,23 @@
# Code Runner
Миссия проекта **Code Runner** — предоставить платформу, где студенты могут совершенствовать свои навыки программирования посредством решения различных задач на разных языках программирования.
## Поддержа языков
Платформа поддерживает 1 язык программирования Python.
## Дизайн системы
![Описание изображения](doc/services.png)
## Рабочие Endpoints:
### Frontend Service
- `GET /task/{id}` \- Получить задачу по UUID
### REST API Service
- `POST /run` \- Отправить код на проверку

167
cmd/agent_service.go Normal file
View File

@ -0,0 +1,167 @@
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))
}

115
cmd/frontend_service.go Normal file
View File

@ -0,0 +1,115 @@
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")
}

138
cmd/push_service.go Normal file
View File

@ -0,0 +1,138 @@
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))
}

123
cmd/rest_service.go Normal file
View File

@ -0,0 +1,123 @@
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"))
}

37
cmd/templates/chat.html Normal file
View File

@ -0,0 +1,37 @@
<!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>

97
cmd/templates/index.html Normal file
View File

@ -0,0 +1,97 @@
<!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>

BIN
doc/services.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.gocommunity.ru/chertkov/code_runner
go 1.23.6