Create your account
Step 1 of 3: Your Details
diff --git a/backend/internal/auth/login.go b/backend/internal/auth/login.go index 9df0fbf..66886cd 100644 --- a/backend/internal/auth/login.go +++ b/backend/internal/auth/login.go @@ -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 @@ -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) @@ -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" { diff --git a/backend/internal/auth/signup.go b/backend/internal/auth/signup.go index a835642..9ce2fa2 100644 --- a/backend/internal/auth/signup.go +++ b/backend/internal/auth/signup.go @@ -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"` @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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, @@ -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 @@ -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 @@ -281,21 +306,21 @@ 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 @@ -303,7 +328,7 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) { 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!", }) } diff --git a/frontend/assets/js/login.js b/frontend/assets/js/login.js index 90dcf11..88da77f 100644 --- a/frontend/assets/js/login.js +++ b/frontend/assets/js/login.js @@ -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 }) diff --git a/frontend/assets/js/signup.js b/frontend/assets/js/signup.js index 8983065..1019e2c 100644 --- a/frontend/assets/js/signup.js +++ b/frontend/assets/js/signup.js @@ -28,13 +28,13 @@ function isAlpha(evt) { } document.addEventListener("DOMContentLoaded", function () { - // --- STEP ELEMENTS --- + // STEP ELEMENTS const step1 = document.getElementById('step-1'); const step2 = document.getElementById('step-2'); const step3 = document.getElementById('step-3'); const stepSubtitle = document.getElementById('step-subtitle'); - // --- BUTTONS --- + // BUTTONS const btnStep1 = document.getElementById('btn-step-1'); const btnBack2 = document.getElementById('btn-back-2'); const btnStep2 = document.getElementById('btn-step-2'); @@ -42,31 +42,31 @@ document.addEventListener("DOMContentLoaded", function () { const createAccountBtn = document.getElementById('create-account-btn'); const signupForm = document.getElementById('signup-form'); - // --- STEP 1 INPUTS --- + // INPUTS const firstNameInput = document.getElementById('first_name'); const lastNameInput = document.getElementById('last_name'); const emailInput = document.getElementById('email'); const contactNumberInput = document.getElementById('contact_number'); - // --- STEP 2 INPUTS --- + // INPUTS const cardIdInput = document.getElementById('card_id'); const cardIdError = document.getElementById('card-id-error'); - // --- STEP 3 INPUTS --- + // INPUTS const passwordInput = document.getElementById('password'); const confirmPasswordInput = document.getElementById('confirm_password'); const checklist = document.getElementById('validation-checklist'); const lengthCheck = document.getElementById('length-check'); const matchCheck = document.getElementById('match-check'); - // --- MODAL (ADDED) --- + // MODAL (ADDED) const successModal = document.getElementById('success-modal'); const modalCloseBtn = document.getElementById('modal-close-btn'); - // --- GLOBAL --- + // GLOBAL const errorMessage = document.getElementById('error-message'); - // --- FORM DATA STORAGE --- + // FORM DATA STORAGE const formData = { firstName: '', lastName: '', @@ -75,15 +75,15 @@ document.addEventListener("DOMContentLoaded", function () { password: '', }; - // --- ROBUSTNESS CHECK --- + // ROBUSTNESS CHECK if (!step1 || !step2 || !step3 || !stepSubtitle || !btnStep1 || !btnBack2 || !btnStep2 || !btnBack3 || !createAccountBtn || !signupForm || !firstNameInput || !emailInput || !cardIdInput || !cardIdError || !passwordInput || !confirmPasswordInput || !checklist || !lengthCheck || !matchCheck || !errorMessage || !successModal || !modalCloseBtn) { console.error("Signup Script Error: Not all required HTML elements were found on the page."); return; // Stop the script } - // --- INITIALIZATION --- + // INITIALIZATION - // --- HELPER FUNCTIONS --- + // HELPER FUNCTIONS function showStep(stepNumber) { step1.classList.add('hidden'); step2.classList.add('hidden'); @@ -120,9 +120,9 @@ document.addEventListener("DOMContentLoaded", function () { errorMessage.classList.remove('hidden'); } - // --- STEP VALIDATION LOGIC --- + // VALIDATION LOGIC - // Step 1: Real-time validation for Name and Email fields + // Real-time validation for Name and Email fields function validateStep1Realtime() { const firstName = firstNameInput.value.trim(); const lastName = lastNameInput.value.trim(); @@ -138,7 +138,7 @@ document.addEventListener("DOMContentLoaded", function () { } } - // Step 1: Click validation (shows error) + // Click validation (shows error) function validateStep1() { const firstName = firstNameInput.value.trim(); const lastName = lastNameInput.value.trim(); @@ -158,7 +158,7 @@ document.addEventListener("DOMContentLoaded", function () { return true; } - // Step 2: Validate Card ID (real-time and click) + // Validate Card ID (real-time and click) function validateStep2() { const cardId = cardIdInput.value.trim(); @@ -169,7 +169,7 @@ document.addEventListener("DOMContentLoaded", function () { } const isCardIdOnlyNumbers = /^\d+$/.test(cardId); - const isCardIdValidLength = cardId.length === 10; + const isCardIdValidLength = cardId.length === 16; if (!isCardIdOnlyNumbers) { cardIdError.textContent = 'Card ID must contain only numbers.'; @@ -188,7 +188,7 @@ document.addEventListener("DOMContentLoaded", function () { return true; } - // Step 3: Validate Password + // Validate Password function validateStep3() { const password = passwordInput.value; const confirmPassword = confirmPasswordInput.value; @@ -208,12 +208,11 @@ document.addEventListener("DOMContentLoaded", function () { return false; } - // --- EVENT LISTENERS --- + // EVENT LISTENERS - // --- Add real-time listeners for Step 1 --- + // Add real-time listeners for Step 1 firstNameInput.addEventListener('input', validateStep1Realtime); lastNameInput.addEventListener('input', validateStep1Realtime); - emailInput.addEventListener('input', validateStep1Realtime); contactNumberInput.addEventListener('input', validateStep1Realtime); // Next from Step 1 @@ -252,15 +251,15 @@ document.addEventListener("DOMContentLoaded", function () { event.preventDefault(); if (validateStep3()) { - fetch("v1/signupauth", { + fetch("/v1/signupauth", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ - firstName: formData.firstName, - lastName: formData.lastName, + first_name: formData.firstName, + last_name: formData.lastName, email: formData.email, - contactNumber: formData.contactNumber, - cardNumber: formData.cardId, + contact_number: formData.contactNumber, + card_number: formData.cardId, password: formData.password }) }) @@ -281,9 +280,9 @@ document.addEventListener("DOMContentLoaded", function () { } }); - // --- MODAL BUTTON (ADDED) --- + // MODAL BUTTON (ADDED) // Add event listener for the modal's "Go to Login" button modalCloseBtn.addEventListener('click', function() { - window.location.href = "login.html"; + window.location.href = "/login"; }); }); \ No newline at end of file diff --git a/frontend/templates/signup.html b/frontend/templates/signup.html index 7fcf237..473abac 100644 --- a/frontend/templates/signup.html +++ b/frontend/templates/signup.html @@ -4,58 +4,30 @@
-Step 1 of 3: Your Details