User backend registration

This commit is contained in:
Leons Aleksandrovs
2025-07-05 22:17:14 +03:00
parent 8916f3e394
commit 66ed77e758
6 changed files with 100 additions and 13 deletions
+47 -6
View File
@@ -1,34 +1,75 @@
package controllers package controllers
import ( import (
"backend/models"
"backend/utils"
"errors"
"fmt"
"log" "log"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/jackc/pgx/v5/pgconn"
) )
type User struct{} type User struct{}
type RegisterForm struct { type RegisterForm struct {
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required,min=2,max=50"`
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required,min=8"`
RepeatPassword string `json:"repeatPassword" validate:"required"` RepeatPassword string `json:"repeatPassword" validate:"required,min=8,eqfield=Password"`
} }
func (u *User) Register(c *gin.Context) { func (u *User) Register(c *gin.Context) {
// Receive data from frontend, check if data is okay, hash password, call model // Receive data from frontend, check if data is okay, hash password, call model
var data RegisterForm var data RegisterForm
if err := c.ShouldBindJSON(&data); err != nil { if err := c.ShouldBindJSON(&data); err != nil {
// TODO: Handle error utils.Error(c, err.Error(), http.StatusInternalServerError)
} }
// Validate data // Validate data
validate := validator.New() validate := validator.New()
if err := validate.Struct(data); err != nil { if err := validate.Struct(data); err != nil {
// Handle error // Handle error
log.Printf("[ERROR]: %v", err.Error()) utils.Error(c, err.Error(), http.StatusBadRequest)
return
} }
// fmt.Println(data) // Hash password
hash, err := utils.HashPassword(data.Password)
if err != nil {
utils.Error(c, err.Error(), http.StatusInternalServerError)
return
}
// Insert into database
userMod := models.User{}
if err := userMod.Create(data.Email, data.Name, hash); err != nil {
// Find out postgres error
var pgErr *pgconn.PgError
if !errors.As(err, &pgErr) {
// Unknown error
utils.Error(c, fmt.Sprintf("[UNEXPECTED DB ERROR] %v", err.Error()), http.StatusInternalServerError)
return
}
// Postgres error
log.Printf("[ERROR] Postgres code: %s", pgErr.Code)
if pgErr.Code == "23505" {
// UNIQUE constraint violation (EMAIL TAKEN)
utils.Error(c, "Email already exists", http.StatusBadRequest)
return
}
// Unknown error
utils.Error(c, fmt.Sprintf("[UNKNOWN ERROR] %v", err.Error()), http.StatusInternalServerError)
return
}
// Return success
utils.Success(c, gin.H{
"message": "Successfully registered",
})
} }
+1 -3
View File
@@ -1,9 +1,7 @@
-- Create users table if it doesn't exist
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY, id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE, email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL, name TEXT NOT NULL,
password TEXT NOT NULL, password TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
+2 -2
View File
@@ -4,7 +4,9 @@ go 1.24.4
require ( require (
github.com/gin-gonic/gin v1.10.1 github.com/gin-gonic/gin v1.10.1
github.com/go-playground/validator/v10 v10.20.0
github.com/jackc/pgx/v5 v5.7.5 github.com/jackc/pgx/v5 v5.7.5
golang.org/x/crypto v0.37.0
) )
require ( require (
@@ -16,7 +18,6 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.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/goccy/go-json v0.10.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
@@ -33,7 +34,6 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.13.0 // indirect golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.32.0 // indirect
+17 -2
View File
@@ -1,5 +1,11 @@
package models package models
import (
"backend/db"
"context"
"time"
)
type User struct { type User struct {
ID int ID int
email string email string
@@ -8,6 +14,15 @@ type User struct {
createdAt string createdAt string
} }
func Create(email string, name string, hash string) { func (u *User) Create(email string, name string, hash string) error {
// TODO: Insert user into database // Generate background context for managing timeouts and disconnections
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// Build database query
query := `INSERT INTO users (email, name, password) VALUES ($1, $2, $3)`
_, err := db.Pool.Exec(ctx, query, email, name, hash)
// Return error if any
return err
} }
+13
View File
@@ -0,0 +1,13 @@
package utils
import "golang.org/x/crypto/bcrypt"
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
+20
View File
@@ -1 +1,21 @@
package utils package utils
import (
"github.com/gin-gonic/gin"
)
func Success(c *gin.Context, data gin.H) {
// Return success to api
c.JSON(200, gin.H{
"success": true,
"data": data,
})
}
func Error(c *gin.Context, err string, code int) {
// Return error to api
c.JSON(code, gin.H{
"success": false,
"error": err,
})
}