Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions backend/internal/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,23 @@ import (
)

type LoginRequest struct {
ID string `json:"id,omitempty" db:"ID"` // Optional: can be used for direct ID login
UserID string `json:"userId,omitempty" db:"user_id"` // Optional: can be used for direct user ID login
Identifier string `json:"identifier" validate:"required"` // Allow login via email or username
Password string `json:"password" db:"password_hash" validate:"required"` // Expecting the password hash in the database
ID string `json:"id,omitempty" db:"ID"` // Optional: can be used for direct ID login
UserID string `json:"userId,omitempty" db:"user_id"` // Optional: can be used for direct user ID login
Identifier string `json:"identifier" validate:"required" db:"full_name, email, phone"` // Allow login via email or username
Password string `json:"password" db:"password_hash" validate:"required"` // Expecting the password hash in the database
}

// This struct is used to get the data from the database
type Login struct {
ID string `db:"ID"`
UserID string `db:"user_id"`
Username string `db:"username"`
Password string `db:"password_hash"`
}

// Validator instance for struct validation
var validate = validator.New()
// Initialize the validator for all handlers
var Validate = validator.New()

// View Handler (GET)
// Serves the login page template
Expand All @@ -46,7 +55,7 @@ func (h *Handler) LoginAuthHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Login attempt for: %s", loginReq.Identifier)

// validate the login request
err := validate.Struct(loginReq)
err := Validate.Struct(loginReq)
if err != nil {
log.Printf("Validation failed: %v", err)

Expand All @@ -58,7 +67,7 @@ func (h *Handler) LoginAuthHandler(w http.ResponseWriter, r *http.Request) {
if errors.As(err, &validationErrs) {
firstErr := validationErrs[0] // Just look at the first error to keep it simple

// 3. Update the message based on exactly what failed
// Update the message based on exactly what failed
if firstErr.Field() == "Identifier" {
errorMessage = "Please enter a valid email or username."
} else if firstErr.Field() == "Password" {
Expand Down
135 changes: 80 additions & 55 deletions backend/internal/auth/signup.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,36 @@ package authentication

import (
"database/sql"
"encoding/json" // Added for JSON support
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strings"
"unicard-go/backend/internal/pkg/account"
jsonwrite "unicard-go/backend/internal/pkg/handler"

"github.com/go-playground/validator/v10"
)

const (
CardStatusInactive = "Inactive"
UserTypeRegular = "Regular"
)

// Create a struct to catch the incoming JSON from the frontend
type SignupRequest struct {
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
CardNumber string `json:"cardNumber"`
Password string `json:"password"`
Email string `json:"email"`
ContactNumber string `json:"contactNumber"`
}

// Create a standard API response struct
type APIResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
ID string `json:"id,omitempty" db:"ID"`
FirstName string `json:"first_name" db:"first_name" validate:"required"`
LastName string `json:"last_name" db:"last_name" validate:"required"`
CardNumber string `json:"card_number" db:"card_number" validate:"required,numeric,len=16"`
Password string `json:"password" db:"password_hash" validate:"required,min=8"`
Email string `json:"email" db:"email" validate:"required,email"`
ContactNumber string `json:"contact_number" db:"phone" validate:"required,numeric,len=11"`
}

// User struct to hold signup data (Keep your existing one)
// Create a struct to get the data from the database
type User struct {
UserID string `db:"user_id"`
Username string `db:"username"`
Expand All @@ -45,13 +49,16 @@ type User struct {
// View Handler (GET)
// You can now simplify this because JS handles the errors!
func (h *Handler) SignupView(w http.ResponseWriter, r *http.Request) {
fmt.Println("Signup view is running...")
log.Printf("Signup view is running...")
// Just serve the template. No need for the huge switch statement anymore.
h.Tpl.ExecuteTemplate(w, "signup.html", nil)
}

func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("Signup Handler is running...")
log.Printf("Signup Handler is running...")

// get context from request
ctx := r.Context()

// Decode incoming JSON
var req SignupRequest
Expand All @@ -73,51 +80,70 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
req.ContactNumber = strings.TrimSpace(req.ContactNumber)

// Validation: Empty Fields
fields := []string{req.FirstName, req.LastName, req.CardNumber, req.Password, req.Email, req.ContactNumber}
for _, f := range fields {
if f == "" {
log.Printf("Validation failed: Empty fields")
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Message: "Please fill in all fields.",
})
return
err := Validate.Struct(req)
if err != nil {
log.Printf("Validation failed: %v", err)

errorMessage := "Invalid input provided."
var validationErrs validator.ValidationErrors
if errors.As(err, &validationErrs) {
firstErr := validationErrs[0]
switch firstErr.Field() {
case "FirstName":
errorMessage = "First name is required."
case "LastName":
errorMessage = "Last name is required."
case "Email":
errorMessage = "Please provide a valid email address."
case "ContactNumber":
errorMessage = "Contact number must be exactly 11 digits."
case "CardNumber":
errorMessage = "Card number must be exactly 16 digits."
case "Password":
errorMessage = "Password must be at least 8 characters long."
}
}

jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Message: errorMessage,
})
return
}

// Validation: Password Length (fail fast before any DB calls)
if len(req.Password) < 8 {
/* len(req.Password) < 8 {
log.Printf("Validation failed: Password must be at least 8 characters long")
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "Password must be at least 8 characters long.",
})
return
}
}*/

// Check Card Status
var cardStatus string
err := h.DB.QueryRow("SELECT status FROM cards WHERE card_number = ?", req.CardNumber).Scan(&cardStatus)
err = h.DB.QueryRowContext(ctx, "SELECT status FROM cards WHERE card_number = ?", req.CardNumber).Scan(&cardStatus)
if err == sql.ErrNoRows {
log.Printf("Card not found: %v", req.CardNumber)
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "Card number not found. Please check your card.",
})
return
} else if err != nil {
log.Printf("Error checking card status: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error checking card status.",
})
return
}

if cardStatus != "Inactive" {
if cardStatus != CardStatusInactive {
log.Printf("Card is not inactive: %v", cardStatus)
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: fmt.Sprintf("Card is currently '%s'. Please contact support.", cardStatus),
})
return
Expand All @@ -127,14 +153,14 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
exists, err := account.IsEmailExist(h.DB, req.Email)
if err != nil {
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error checking email.",
})
return
}
if exists {
jsonwrite.WriteJSON(w, http.StatusConflict, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "Email already registered. Please use a different email.",
})
return
Expand All @@ -143,16 +169,16 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
// Check Phone
exists, err = h.isPhoneExist(req.ContactNumber)
if err != nil {
fmt.Println("Phone number check error:", err)
log.Printf("Phone number check error: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error checking phone number.",
})
return
}
if exists {
jsonwrite.WriteJSON(w, http.StatusConflict, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "Phone number already registered. Please use a different number.",
})
return
Expand All @@ -163,20 +189,19 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("Error hashing password: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error processing password.",
})
return
}
log.Printf("Password: %v", req.Password)
log.Printf("Password hashed successfully: %v", hashedPassword)

// Generate Username
generatedUsername, err := h.GenerateUniqueUsername()
if err != nil {
fmt.Println("Error generating username:", err)
log.Printf("Error generating username: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error generating username. Please try again.",
})
return
Expand All @@ -188,7 +213,7 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("Error generating UserID: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error generating UserID.",
})
return
Expand All @@ -207,19 +232,19 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("Error getting timestamp: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error getting timestamp.",
})
return
}
fmt.Println("Timestamp:", createdAt)
log.Printf("Timestamp: %v", createdAt)

// Get Initial Balance
balance, err := h.GetInitialBalance(req.CardNumber)
if err != nil {
log.Printf("Error getting initial balance: %v", err)
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "Invalid Card Number.",
})
return
Expand All @@ -229,7 +254,7 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
user := User{
UserID: fmt.Sprintf("%d", generateUserId),
CardID: generateCardID,
Usertype: "Regular",
Usertype: UserTypeRegular,
Username: generatedUsername,
Fullname: req.FirstName + " " + req.LastName,
CardNumber: req.CardNumber,
Expand All @@ -241,11 +266,11 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
}

// Begin transaction: insert user + activate card atomically
tx, err := h.DB.Begin()
tx, err := h.DB.BeginTx(ctx, nil)
if err != nil {
log.Printf("Error starting transaction: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error starting transaction.",
})
return
Expand All @@ -256,14 +281,14 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
insertQuery := `INSERT INTO users
(user_id, username, full_name, email, phone, password_hash, card_id, card_number, user_type, balance, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
_, err = tx.Exec(insertQuery,
_, err = tx.ExecContext(ctx, insertQuery,
user.UserID, user.Username, user.Fullname, user.Email, user.Phone,
user.Password, user.CardID, user.CardNumber, user.Usertype, user.Balance, user.CreatedAt,
)
if err != nil {
log.Printf("Error inserting user: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error creating account. Please try again.",
})
return
Expand All @@ -281,29 +306,29 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) {
expiry_date = DATE_ADD(CURRENT_DATE, INTERVAL 10 YEAR)
WHERE card_number = ?`

_, err = tx.Exec(updateCardQuery, user.UserID, user.Fullname, user.CardNumber)
_, err = tx.ExecContext(ctx, updateCardQuery, user.UserID, user.Fullname, user.CardNumber)

if err != nil {
log.Printf("Error activating card for card_number %s: %v", user.CardNumber, err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error activating card.",
} )
})
return
}

if err = tx.Commit(); err != nil {
log.Printf("Error committing transaction: %v", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Success: false,
Message: "System error finalizing account creation.",
})
return
}

log.Printf("Account successfully created! UserID: %s", user.UserID) // moved here
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: true,
Success: true,
Message: "Account created successfully!",
})
}
2 changes: 1 addition & 1 deletion frontend/assets/js/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ document.addEventListener("DOMContentLoaded", function () {
const idValue = document.getElementById("identifier").value;
const pass = document.getElementById("password").value;

fetch("v1/loginauth", {
fetch("/v1/loginauth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ identifier: idValue, password: pass })
Expand Down
Loading