Skip to content
Merged
34 changes: 10 additions & 24 deletions backend/cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,36 +81,22 @@ func main() {
mux.HandleFunc("POST /v1/reset-password", authHandler.ResetPassword)
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)
})
//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",)

// 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)

mux.HandleFunc("GET /admin/dashboard", adminHanlder.DashboardView)
mux.HandleFunc("GET /v1/admin/dashboard-data", adminHanlder.DashboardDataHandler)
mux.HandleFunc("POST /v1/admin/merchants/add", adminHanlder.AddMerchantHandler)
mux.HandleFunc("GET /admin/card-inventory", adminHanlder.CardInventoryView)
mux.HandleFunc("GET /v1/admin/card-inventory-data", adminHanlder.CardInventoryDataHandler)
mux.HandleFunc("GET /admin/addcard", adminHanlder.AddCardsView)
mux.HandleFunc("GET /admin/deactivatecard", adminHanlder.DeactivateView)
mux.HandleFunc("POST /v1/admin/addcardauth", adminHanlder.AddCardHandler)
Expand Down
212 changes: 80 additions & 132 deletions backend/internal/admin/addCard.go
Original file line number Diff line number Diff line change
@@ -1,181 +1,130 @@
package admin

import (
"crypto/rand"
"database/sql"
"encoding/json"
"errors"
"fmt"
"math/rand"
"log"
"math/big"
"net/http"
"strconv"
"strings"
"time"
jsonwrite "unicard-go/backend/internal/pkg/handler"
"unicard-go/backend/internal/pkg/structs"

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

// Card struct represents a card and its attributes.
type Card struct {
CardUID string
CardNumber string
CardType string
InitialAmount float64
ExpiryDate string
CreatedAt string
}
// CardRequest struct mapped directly to your frontend JSON payload

// 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...")

// 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)
}

// AddCardHandler handles card creation and returns JSON response.
func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) {
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
}

var req struct {
CardUID string `json:"cardUID"`
InitialAmount string `json:"initialAmount"`
}
var req structs.CardData

// Try reading JSON body first
// Decode JSON
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 == "" {
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
log.Println("Error decoding JSON:", err)
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Message: "Please fill in all required fields.",
Message: "Invalid request format",
})
return
}

// Auto-generate card number
fmt.Println("Calling generateCardNumber()...")
cardNumber := h.generateCardNumber()
fmt.Printf("Generated card number: %s\n", cardNumber)

// Set default card type
cardType := "Regular"

// Auto-calculate expiry date as 10 years from now
expiryDate := time.Now().AddDate(10, 0, 0).Format("2006-01-02")
cardUID := strings.TrimSpace(req.CardUID)

// Convert Initial Amount (String -> Float64)
amount, err := strconv.ParseFloat(initialAmount, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Invalid amount format. Must be a number.",
})
return
}
// Validate required fields
validate := validator.New()
if err := validate.Struct(req); err != nil {
errorMessage := "Invalid input provided."
var validationErrs validator.ValidationErrors
if errors.As(err, &validationErrs) {
errorMap := map[string]string{
"CardUID": "Card UID is required.",
"InitialAmount": "Initial amount is required and cannot be negative.",
}
if msg, ok := errorMap[validationErrs[0].Field()]; ok {
errorMessage = msg
}
}

// Check for existing card UID
cardUidExist, err := h.cardUIDExist(cardUID)
if err != nil {
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Error checking card UID.",
})
return
}
if cardUidExist {
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Message: "Card UID already exists.",
Message: errorMessage,
})
return
}

// Create a new Card struct
card := Card{
CardUID: cardUID,
CardNumber: cardNumber,
CardType: cardType,
InitialAmount: amount,
ExpiryDate: expiryDate,
}
// Auto-generate data
cardNumber := h.generateCardNumber()
cardType := "regular" // Lowercase to match your database ENUM
expiryDate := time.Now().AddDate(2, 0, 0).Format("2006-01-02") // 2 years from now unlinked

// Check for existing card number
cardNumExists, err := h.cardNumberExist(card)
// Check for existing UID
uidExists, err := h.cardUIDExist(cardUID)
if err != nil {
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Error checking card number.",
Message: "Error verifying Card UID.",
})
return
}
if cardNumExists {
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
if uidExists {
jsonwrite.WriteJSON(w, http.StatusConflict, jsonwrite.APIResponse{
Success: false,
Message: "Card number already exists.",
Message: "This Card UID is already registered in the system.",
})
return
}

createdAt := time.Now().Format("2006-01-02 15:04:05")
// Insert card into database (Fixed column name: 'balance')
// We omit created_at because MySQL handles it automatically via DEFAULT CURRENT_TIMESTAMP
query := `
INSERT INTO cards (card_uid, card_number, card_type, balance, expiry_date, status)
VALUES (?, ?, ?, ?, ?, 'inactive')
`

// Insert card into database
query := "INSERT INTO cards (card_uid, card_number, card_type, initial_amount, expiry_date, created_at) VALUES (?, ?, ?, ?, ?, ?)"
_, err = h.DB.Exec(
query,
card.CardUID,
card.CardNumber,
card.CardType,
card.InitialAmount,
card.ExpiryDate,
createdAt,
cardUID,
cardNumber,
cardType,
req.Balance, // Maps to 'balance'
expiryDate,
)

if err != nil {
fmt.Println("Error inserting card into database:", err)
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Error while adding card.",
Message: "Failed to save card to database.",
})
return
}

// Successfully added the card
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
// Success Response
jsonwrite.WriteJSON(w, http.StatusCreated, jsonwrite.APIResponse{
Success: true,
Message: "Card added successfully!",
})
}

//--- HELPER FUNCTIONS ---
// --- HELPER FUNCTIONS ---

func (h *Handler) cardUIDExist(card string) (bool, error) {
var uid string
func (h *Handler) cardUIDExist(uid string) (bool, error) {
var existingUID string
query := "SELECT card_uid FROM cards WHERE card_uid = ?"
err := h.DB.QueryRow(query, card).Scan(&uid)
err := h.DB.QueryRow(query, uid).Scan(&existingUID)
if err == sql.ErrNoRows {
return false, nil
} else if err != nil {
Expand All @@ -184,26 +133,25 @@ func (h *Handler) cardUIDExist(card string) (bool, error) {
return true, nil
}

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
} else if err != nil {
return false, err
// Generate Card Number of format YYSS + 10 random digits + DD
func (h *Handler) generateCardNumber() string {
loc, err := time.LoadLocation("Asia/Manila")
if err != nil {
loc = time.Local
}
return true, nil
}
now := time.Now().In(loc)

func (h *Handler) generateCardNumber() string {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
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)
yy := now.Format("06")
ss := now.Format("05")
dd := now.Format("02")

max10 := big.NewInt(10000000000) // 10^10 for 10 digits
random10, errRand := rand.Int(rand.Reader, max10)

randomDigits := "0000000000" // Default fallback format
if errRand == nil {
randomDigits = fmt.Sprintf("%010d", random10.Int64())
}

return fmt.Sprintf("%s%s%s%s", yy, ss, randomDigits, dd)
}
Loading