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
6 changes: 6 additions & 0 deletions .agents/rules/unicard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
trigger: always_on
glob:
description:
---

Binary file modified app.exe
Binary file not shown.
47 changes: 39 additions & 8 deletions backend/cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func main() {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUser, dbPass, dbHost, dbPort, dbName)

// Setup Templates
tpl, err = template.ParseGlob("./frontend/templates/*.html")
tpl, err = template.ParseGlob("./frontend/templates/*/*.html")
if err != nil {
log.Fatal("Templates loaded but variable is nil. Check your folder path.")
log.Fatalf("Failed to load templates: %v. Check your folder path.", err)
}

// Setup Database
Expand All @@ -68,25 +68,56 @@ func main() {
fileServer := http.FileServer(http.Dir("./frontend/assets"))
mux.Handle("/assets/", http.StripPrefix("/assets/", fileServer))

// POST Request: JSON API endpoints
// general endpoints
mux.HandleFunc("GET /login", authHandler.LoginView)
mux.HandleFunc("GET /signup", authHandler.SignupView)
mux.HandleFunc("POST /v1/loginauth", authHandler.LoginAuthHandler) // Login authentication endpoint
mux.HandleFunc("POST /v1/signupauth", authHandler.SignupHandler)
mux.HandleFunc("POST /v1/signup/check-details", authHandler.CheckDetailsHandler)
mux.HandleFunc("POST /v1/signup/check-card", authHandler.CheckCardHandler)
mux.HandleFunc("GET /login", authHandler.LoginView)
mux.HandleFunc("GET /signup", authHandler.SignupView)
mux.HandleFunc("GET /forgot-password", authHandler.ForgotPasswordView)
mux.HandleFunc("POST /v1/forgot-password/send-otp", authHandler.ForgotPasswordSendOTP)
mux.HandleFunc("POST /v1/forgot-password/verify-otp", authHandler.ForgotPasswordVerifyOTP)
mux.HandleFunc("POST /v1/reset-password", authHandler.ResetPassword)
mux.HandleFunc("GET /dashboard", userHandler.DashboardHandler)
mux.HandleFunc("GET /dashboard", userHandler.DashboardView)
mux.HandleFunc("GET /v1/user/dashboard", userHandler.DashboardHandler)
mux.HandleFunc("GET /transaction", userHandler.TransactionView)
mux.HandleFunc("GET /topup", userHandler.TopupView)
mux.HandleFunc("GET /profile", userHandler.ProfileView)
mux.HandleFunc("GET /settings", userHandler.SettingsView)
mux.HandleFunc("GET /card", userHandler.CardView)
mux.HandleFunc("GET /v1/user/transactions", userHandler.TransactionsJSONHandler)
mux.HandleFunc("GET /logout", func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: "session_user_id",
Value: "",
Path: "/",
MaxAge: -1,
})
http.SetCookie(w, &http.Cookie{
Name: "session_admin_username",
Value: "",
Path: "/",
MaxAge: -1,
})
http.Redirect(w, r, "/login", http.StatusSeeOther)
})

// super admin endpoints
mux.HandleFunc("GET /admin/platform-overview", adminHanlder.PlatformOverviewView)
mux.HandleFunc("GET /admin/merchants", adminHanlder.MerchantManagementView)
mux.HandleFunc("GET /admin/terminals", adminHanlder.TerminalRegistryView)
mux.HandleFunc("GET /admin/settings", adminHanlder.SystemSettingsView)

// endpoints for admin
mux.HandleFunc("GET /admin/dashboard", adminHanlder.DashboardView)
mux.HandleFunc("GET /v1/admin/dashboard-data", adminHanlder.DashboardDataHandler)
mux.HandleFunc("GET /admin/addcard", adminHanlder.AddCardsView)
mux.HandleFunc("GET /admin/deactivatecard", adminHanlder.DeactivateView)
mux.HandleFunc("POST /v1/admin/addcardauth", adminHanlder.AddCardHandler)
mux.HandleFunc("POST /v1/admin/deactivatecardauth", adminHanlder.DeactivateCardHanlder)

mux.HandleFunc("POST /v1/admin/deletecardauth", adminHanlder.DeleteCardHandler)


// Wrap mux with custom handler for root redirect
customHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
Expand Down
152 changes: 85 additions & 67 deletions backend/internal/admin/addCard.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ package admin

import (
"database/sql"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"strconv"
"strings"
"time"
message "unicard-go/backend/internal/pkg"
jsonwrite "unicard-go/backend/internal/pkg/handler"
)

// This struct represents a card and its attributes.
// We can use it to easily pass card data around in our functions.
// It also helps to keep our code organized and makes it easier to manage card-related data.
// Card struct represents a card and its attributes.
type Card struct {
CardUID string
CardNumber string
Expand All @@ -23,33 +22,57 @@ type Card struct {
CreatedAt string
}

// This function renders the addCards.html template when the admin visits the /admin/addcard page.
// It doesn't do any processing yet, it just shows the form to the admin.
// We can also pass an empty AddCardsData struct to the template, which allows us to easily display error or success messages later on when we process the form submission.
// AddCardsView renders the addCards.html template after checking the admin session.
func (h *Handler) AddCardsView(w http.ResponseWriter, r *http.Request) {
fmt.Println("AddCardsView running...")
// Render the addCards.html template
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{})

// Validate admin session
cookie, err := r.Cookie("session_admin_username")
if err != nil || cookie.Value == "" {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}

h.Tpl.ExecuteTemplate(w, "addCards.html", nil)
}

// This function handles the form submission from the addCards.html page.
// It processes the form data, validates it, generates a card number, checks for duplicates, and inserts the new card into the database.
// Also have error handling at each step, and we pass error or success messages back to the template to inform the admin of the result.
// AddCardHandler handles card creation and returns JSON response.
func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("addcardshandler running...")

if err := r.ParseForm(); err != nil {
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Error: "Failed to parse form"})
fmt.Println("AddCardHandler running...")

// Verify session
cookie, err := r.Cookie("session_admin_username")
if err != nil || cookie.Value == "" {
jsonwrite.WriteJSON(w, http.StatusUnauthorized, jsonwrite.APIResponse{
Success: false,
Message: "Unauthorized",
})
return
}

// Process the form data here
cardUID := strings.TrimSpace(r.PostFormValue("cardUID"))
initialAmount := strings.TrimSpace(r.PostFormValue("initialAmount"))
var req struct {
CardUID string `json:"cardUID"`
InitialAmount string `json:"initialAmount"`
}

// Try reading JSON body first
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
// Fallback to post form values
if err := r.ParseForm(); err == nil {
req.CardUID = r.PostFormValue("cardUID")
req.InitialAmount = r.PostFormValue("initialAmount")
}
}

cardUID := strings.TrimSpace(req.CardUID)
initialAmount := strings.TrimSpace(req.InitialAmount)

// Validate required fields
if cardUID == "" || initialAmount == "" {
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Error: "Please fill in all required fields."})
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Please fill in all required fields.",
})
return
}

Expand All @@ -60,36 +83,37 @@ func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) {

// Set default card type
cardType := "Regular"
fmt.Printf("Set card type: %s\n", cardType)

// Auto-calculate expiry date as 10 years from now
expiryDate := time.Now().AddDate(10, 0, 0).Format("2006-01-02")
fmt.Printf("Set expiry date: %s\n", expiryDate)

// Convert Initial Amount (String -> Float64)
fmt.Println("Parsing initial amount...")
amount, err := strconv.ParseFloat(initialAmount, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Error: "Invalid amount format. Must be a number."})
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Invalid amount format. Must be a number.",
})
return
}
fmt.Printf("Parsed amount: %.2f\n", amount)

// Check for existing card UID
fmt.Printf("Calling cardUIDExist() for UID: %s\n", cardUID)
cardUidExist, err := h.cardUIDExist(cardUID)
if err != nil {
fmt.Println("Error checking card UID existence:", err)
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Error: "Error checking card UID."})
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Error checking card UID.",
})
return
}
if cardUidExist {
fmt.Println("Card UID already exists")
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Error: "Card UID already exists."})
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Card UID already exists.",
})
return
}
fmt.Println("Card UID is unique")

// Create a new Card struct
card := Card{
Expand All @@ -101,28 +125,25 @@ func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) {
}

// Check for existing card number
fmt.Printf("Calling cardNumberExist() for card number: %s\n", card.CardNumber)
cardNumExists, err := h.cardNumberExist(card)
if err != nil {
fmt.Println("Error checking card number existence:", err)
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Error: "Error checking card number."})
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Error checking card number.",
})
return
}
if cardNumExists {
fmt.Println("Card number already exists")
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Error: "Card number already exists."})
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Card number already exists.",
})
return
}
fmt.Println("Card number is unique")

// Create current timestamp
// Output example: 2024-06-15 14:30:00
fmt.Println("Generating timestamp...")
createdAt := time.Now().Format("2006-01-02 15:04:05")
fmt.Printf("Timestamp: %s\n", createdAt)

// Insert card into database
fmt.Println("Executing database insert...")
query := "INSERT INTO cards (card_uid, card_number, card_type, initial_amount, expiry_date, created_at) VALUES (?, ?, ?, ?, ?, ?)"
_, err = h.DB.Exec(
query,
Expand All @@ -131,61 +152,58 @@ func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) {
card.CardType,
card.InitialAmount,
card.ExpiryDate,
createdAt)
createdAt,
)
if err != nil {
fmt.Println("Error inserting card into database:", err)
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Error: "Error while adding card."})
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Error while adding card.",
})
return
}

// Successfully added the card
fmt.Printf("Card added successfully: %s\n", card.CardNumber)
h.Tpl.ExecuteTemplate(w, "addCards.html", message.MessageData{Success: "Card added successfully!"})
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: true,
Message: "Card added successfully!",
})
}

//--- HELPER FUNCTIONS ---

// Check card UID existence
// We can use this before adding a new card to avoid duplicates
func (h *Handler) cardUIDExist(card string) (bool, error) {
var uid string
query := "SELECT card_uid FROM cards WHERE card_uid = ?"
err := h.DB.QueryRow(query, card).Scan(&uid)
if err == sql.ErrNoRows {
return false, nil // UID is unique
return false, nil
} else if err != nil {
fmt.Println("Error checking card UID existence:", err)
return false, err // Exit on DB error
return false, err
}
return true, nil // UID exists
return true, nil
}

// Check if card number already exists.
// We can use this before adding a new card to avoid duplicates
func (h *Handler) cardNumberExist(card Card) (bool, error) {
var cardNum string
query := "SELECT card_number FROM cards WHERE card_number = ?"
err := h.DB.QueryRow(query, card.CardNumber).Scan(&cardNum)
if err == sql.ErrNoRows {
return false, nil // Card number is unique
return false, nil
} else if err != nil {
fmt.Println("Error checking card existence:", err)
return false, err
}
return true, nil // Card number exists
return true, nil
}

// Generate a unique card number with format YYDDMM + 10 random digits
func (h *Handler) generateCardNumber() string {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
// Get current date components
now := time.Now() // Fixed prefix for all cards
year := now.Format("06") // YY format
month := now.Format("01") // MM format
day := now.Format("02") // DD format
datePrefix := year + day + month // YYDDMM format

// Generate remaining 10 random digits to make 16 total
randomNum := rng.Intn(10000000000)
now := time.Now()
year := now.Format("06")
month := now.Format("01")
day := now.Format("02")
datePrefix := year + day + month

randomNum := rng.Intn(1000000000)
return fmt.Sprintf("%s%010d", datePrefix, randomNum)
}
Loading