483 lines
11 KiB
Go
483 lines
11 KiB
Go
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)
|
|
}
|
|
}
|