code_runner/cmd/service_front/s_front.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)
}
}