refactor: struct folder config and etc..

This commit is contained in:
vitaliy 2025-12-15 14:13:25 +03:00
parent 8c58dd0bcb
commit 8e2359c99a
30 changed files with 2777 additions and 680 deletions

1
.gitignore vendored
View File

@ -0,0 +1 @@
.idea

View File

@ -29,5 +29,5 @@
`$ docker run --name postgres2 -p 5432:5432 -e POSTGRES_USER=auth -e POSTGRES_PASSWORD=123 -e POSTGRES_DB=auth -d postgres:16`
### RabbitMQ
`$ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management`
`$ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=myuser -e RABBITMQ_DEFAULT_PASS=mypassword rabbitmq:3-management`

View File

@ -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))
}

View File

@ -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")
}

View File

@ -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))
}

View File

@ -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"))
}

View 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
View 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"))
}

View 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)
}
}

View 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>

View 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>

View 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>

View 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>

View 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
View 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))
}

View File

@ -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>

View File

@ -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
View 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
View 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
View File

@ -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
View 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
View 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
View 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"`
}

View 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
View 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")
}

View 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
View 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
View 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
View 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
View 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