mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-06-03 20:12:58 +00:00
221 lines
5.6 KiB
Go
221 lines
5.6 KiB
Go
package api
|
|
|
|
import (
|
|
"containr/internal/database"
|
|
"database/sql"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type LoginRequest struct {
|
|
Email string `json:"email" binding:"required,email"`
|
|
Password string `json:"password" binding:"required,min=6"`
|
|
}
|
|
|
|
type RegisterRequest struct {
|
|
Email string `json:"email" binding:"required,email"`
|
|
Password string `json:"password" binding:"required,min=6"`
|
|
Name string `json:"name" binding:"required,min=2"`
|
|
}
|
|
|
|
type AuthResponse struct {
|
|
Token string `json:"token"`
|
|
User interface{} `json:"user"`
|
|
}
|
|
|
|
type User struct {
|
|
ID string `json:"id"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
AvatarURL string `json:"avatar_url,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
func handleLogin(c *gin.Context) {
|
|
var req LoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
db := c.MustGet("db").(*database.DB)
|
|
jwtSecret := c.MustGet("jwt_secret").(string)
|
|
|
|
// Find user by email
|
|
var user User
|
|
var hashedPassword string
|
|
err := db.QueryRow(`
|
|
SELECT id, email, password_hash, name, COALESCE(avatar_url, ''), created_at
|
|
FROM users
|
|
WHERE email = $1
|
|
`, req.Email).Scan(&user.ID, &user.Email, &hashedPassword, &user.Name, &user.AvatarURL, &user.CreatedAt)
|
|
|
|
if err == sql.ErrNoRows {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
|
|
return
|
|
} else if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
|
return
|
|
}
|
|
|
|
// Check password
|
|
if err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(req.Password)); err != nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
|
|
return
|
|
}
|
|
|
|
// Generate JWT token
|
|
token, err := generateJWT(user.ID, user.Email, jwtSecret)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, AuthResponse{
|
|
Token: token,
|
|
User: user,
|
|
})
|
|
}
|
|
|
|
func handleRegister(c *gin.Context) {
|
|
var req RegisterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
db := c.MustGet("db").(*database.DB)
|
|
jwtSecret := c.MustGet("jwt_secret").(string)
|
|
|
|
// Check if user already exists
|
|
var count int
|
|
err := db.QueryRow("SELECT COUNT(*) FROM users WHERE email = $1", req.Email).Scan(&count)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
|
return
|
|
}
|
|
|
|
if count > 0 {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "User already exists"})
|
|
return
|
|
}
|
|
|
|
// Hash password
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
|
return
|
|
}
|
|
|
|
// Create user
|
|
var user User
|
|
err = db.QueryRow(`
|
|
INSERT INTO users (email, password_hash, name)
|
|
VALUES ($1, $2, $3)
|
|
RETURNING id, email, name, COALESCE(avatar_url, ''), created_at
|
|
`, req.Email, string(hashedPassword), req.Name).Scan(&user.ID, &user.Email, &user.Name, &user.AvatarURL, &user.CreatedAt)
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
|
|
return
|
|
}
|
|
|
|
// Generate JWT token
|
|
token, err := generateJWT(user.ID, user.Email, jwtSecret)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, AuthResponse{
|
|
Token: token,
|
|
User: user,
|
|
})
|
|
}
|
|
|
|
func handleGetProfile(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(string)
|
|
db := c.MustGet("db").(*database.DB)
|
|
|
|
var user User
|
|
err := db.QueryRow(`
|
|
SELECT id, email, name, COALESCE(avatar_url, ''), created_at
|
|
FROM users
|
|
WHERE id = $1
|
|
`, userID).Scan(&user.ID, &user.Email, &user.Name, &user.AvatarURL, &user.CreatedAt)
|
|
|
|
if err == sql.ErrNoRows {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
return
|
|
} else if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
func handleUpdateProfile(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(string)
|
|
db := c.MustGet("db").(*database.DB)
|
|
|
|
var req struct {
|
|
Name string `json:"name,omitempty"`
|
|
AvatarURL string `json:"avatar_url,omitempty"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Update user profile
|
|
_, err := db.Exec(`
|
|
UPDATE users
|
|
SET name = COALESCE($1, name), avatar_url = COALESCE($2, avatar_url)
|
|
WHERE id = $3
|
|
`, req.Name, req.AvatarURL, userID)
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update profile"})
|
|
return
|
|
}
|
|
|
|
// Return updated user
|
|
handleGetProfile(c)
|
|
}
|
|
|
|
func generateJWT(userID, email, secret string) (string, error) {
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
"user_id": userID,
|
|
"email": email,
|
|
"exp": time.Now().Add(time.Hour * 24 * 7).Unix(), // 7 days
|
|
})
|
|
|
|
return token.SignedString([]byte(secret))
|
|
}
|
|
|
|
// ValidateJWT validates a JWT token and returns the claims
|
|
func ValidateJWT(tokenString, secret string) (jwt.MapClaims, error) {
|
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, jwt.ErrSignatureInvalid
|
|
}
|
|
return []byte(secret), nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
|
return claims, nil
|
|
}
|
|
|
|
return nil, jwt.ErrInvalidKey
|
|
}
|