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
4 changes: 3 additions & 1 deletion backend/cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ func main() {
//mux.HandleFunc("GET /logout",)

// super admin endpoints
mux.HandleFunc("GET /admin/platform-overview", adminHanlder.PlatformOverviewView)
mux.HandleFunc("GET /admin/dashboard", adminHanlder.AdminDashboardView)
mux.HandleFunc("POST /v1/admin/dashboard-data", adminHanlder.AdminDashboardDataHandler)
mux.HandleFunc("GET /admin/merchants", adminHanlder.MerchantManagementView)
mux.HandleFunc("GET /admin/terminals", adminHanlder.TerminalRegistryView)
mux.HandleFunc("GET /admin/settings", adminHanlder.SystemSettingsView)
Expand All @@ -102,6 +103,7 @@ func main() {
mux.HandleFunc("POST /v1/admin/addcardauth", adminHanlder.AddCardHandler)
mux.HandleFunc("POST /v1/admin/deactivatecardauth", adminHanlder.DeactivateCardHanlder)
mux.HandleFunc("POST /v1/admin/deletecardauth", adminHanlder.DeleteCardHandler)
mux.HandleFunc("GET /admin/delete-cards", adminHanlder.DeleteCardView)


// Wrap mux with custom handler for root redirect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ import (
"strings"
"time"
jsonwrite "unicard-go/backend/internal/pkg/handler"
"unicard-go/backend/internal/pkg/structs"
structure "unicard-go/backend/internal/pkg/structs"

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

// 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...")
Expand All @@ -29,7 +27,8 @@ func (h *Handler) AddCardsView(w http.ResponseWriter, r *http.Request) {
func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("AddCardHandler running...")

var req structs.CardData
// Define request struct
var req structure.CardData

// Decode JSON
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Expand All @@ -50,8 +49,8 @@ func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) {
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.",
"CardUID": "Card UID is required.",
"Balance": "Initial amount is required and cannot be negative.",
}
if msg, ok := errorMap[validationErrs[0].Field()]; ok {
errorMessage = msg
Expand Down
156 changes: 156 additions & 0 deletions backend/internal/admin/admin_dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package admin

import (
"encoding/json"
"log"
"net/http"
jsonwrite "unicard-go/backend/internal/pkg/handler"
)

// AdminDashboardData struct represents the data to be displayed on the admin dashboard
type AdminDashboardData struct {
GrossRevenue float64 `json:"grossRevenue"`
NetRevenue float64 `json:"netRevenue"`
TotalUsers int `json:"totalUsers"`
TotalCards int `json:"totalCards"`
ActiveMerchants int `json:"activeMerchants"`
ActiveTerminals int `json:"activeTerminals"`
}

// AdminDashboardView renders the platform_overview.html template after checking the admin session.
func (h *Handler) AdminDashboardView(w http.ResponseWriter, r *http.Request) {
log.Println("AdminDashboardView running...")
h.Tpl.ExecuteTemplate(w, "admin_dashboard.html", nil)
}

// AdminDashboardDataHandler handles the request for admin dashboard data and returns JSON response.
// AdminDashboardDataHandler handles the request for admin dashboard data and returns JSON response.
func (h *Handler) AdminDashboardDataHandler(w http.ResponseWriter, r *http.Request) {
log.Println("AdminDashboardDataHandler running...")

var req AdminDashboardData

if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
log.Println("Error decoding JSON:", err)
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Message: "Invalid request format",
})
return
}

// Compute UniCard's Absolute Gross Revenue
// (Transaction Service Fees)
query := `SELECT COALESCE(SUM(service_fee), 0.00) FROM transactions WHERE transaction_type = 'payment'`
row := h.DB.QueryRow(query)

var grossRevenue float64
if err := row.Scan(&grossRevenue); err != nil {
log.Println("Error scanning gross revenue:", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Internal server error",
})
return
}
log.Println("Gross revenue row:", grossRevenue)

// Compute UniCard's Net Revenue
// (Service fees from successful payments MINUS service fees lost to refunds/reversals)
query = `SELECT COALESCE(SUM(
CASE
WHEN transaction_type = 'payment' THEN service_fee
WHEN transaction_type IN ('refund', 'reversal') THEN -service_fee
ELSE 0
END
), 0.00) FROM transactions`

row = h.DB.QueryRow(query)

var netRevenue float64
if err := row.Scan(&netRevenue); err != nil {
log.Println("Error scanning net revenue:", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Internal server error while calculating Net Revenue",
})
return
}
log.Println("Net revenue row:", netRevenue)

// Display the number of users
// Counting only 'active' customers (excluding suspended or inactive accounts)
row = h.DB.QueryRow("SELECT COUNT(*) FROM users WHERE role = 'customer'") // status = 'active'

var totalUsers int
if err := row.Scan(&totalUsers); err != nil {
log.Println("Error scanning total users:", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Internal server error",
})
return
}
log.Println("Total users row:", totalUsers)

// Display the number of cards
// Counting only 'active' cards to show actual circulatory supply
row = h.DB.QueryRow("SELECT COUNT(*) FROM cards") // WHERE status = 'active'

var totalCards int
if err := row.Scan(&totalCards); err != nil {
log.Println("Error scanning total cards:", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Internal server error",
})
return
}
log.Println("Total cards row:", totalCards)

// Display the number of merchants
// Counting only 'active' merchants (excluding 'pending_approval' or 'suspended')
row = h.DB.QueryRow("SELECT COUNT(*) FROM merchants WHERE status = 'active'")

var totalMerchants int
if err := row.Scan(&totalMerchants); err != nil {
log.Println("Error scanning total merchants:", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Internal server error",
})
return
}
log.Println("Total merchants row:", totalMerchants)

// Display the number of terminals
// Counting 'active' ESP32 nodes (excluding 'offline' or 'suspended')
row = h.DB.QueryRow("SELECT COUNT(*) FROM terminals WHERE status = 'active'")

var totalTerminals int
if err := row.Scan(&totalTerminals); err != nil {
log.Println("Error scanning total terminals:", err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Internal server error",
})
return
}
log.Println("Total terminals row:", totalTerminals)

// Return the data as JSON
response := AdminDashboardData{
GrossRevenue: grossRevenue,
NetRevenue: netRevenue,
TotalUsers: totalUsers,
TotalCards: totalCards,
ActiveMerchants: totalMerchants,
ActiveTerminals: totalTerminals,
}
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: true,
Message: "Admin dashboard data retrieved successfully",
Data: response,
})
log.Println("AdminDashboardDataHandler finished")
}
4 changes: 2 additions & 2 deletions backend/internal/admin/deactivate_card.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"net/http"
"strings"
jsonwrite "unicard-go/backend/internal/pkg/handler"
"unicard-go/backend/internal/pkg/structs"
structure "unicard-go/backend/internal/pkg/structs"

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

// Type alias for CardData to resolve the redeclaration error
type CardData = structs.CardData
type CardData = structure.CardData

func (h *Handler) DeactivateView(w http.ResponseWriter, r *http.Request) {
fmt.Println("DeactivateView running...")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,54 @@ package admin

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
jsonwrite "unicard-go/backend/internal/pkg/handler"
structure "unicard-go/backend/internal/pkg/structs"

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

func (h *Handler) DeleteCardView(w http.ResponseWriter, r *http.Request) {
fmt.Println("DeleteCardView running...")

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

// DeleteCardHandler handles deleting a card by card_number and returns JSON.
func (h *Handler) DeleteCardHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("DeleteCardHandler running...")

if r.Method != http.MethodPost {
jsonwrite.WriteJSON(w, http.StatusMethodNotAllowed, jsonwrite.APIResponse{
var req structure.CardData

if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Message: "Method not allowed",
Message: "Invalid request format",
})
return
}

var req struct {
CardNumber string `json:"cardNumber"`
}
cardNumber := strings.TrimSpace(req.CardNumber)

// Try reading JSON body
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
// Fallback to post form values
if err := r.ParseForm(); err == nil {
req.CardNumber = r.PostFormValue("cardNumber")
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{
"CardNumber": "Card number is required.",
}
if msg, ok := errorMap[validationErrs[0].Field()]; ok {
errorMessage = msg
}
}
}

cardNumber := strings.TrimSpace(req.CardNumber)

if cardNumber == "" {
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{
Success: false,
Message: "Card number is required for deletion.",
Message: errorMessage,
})
return
}
Expand All @@ -54,7 +60,7 @@ func (h *Handler) DeleteCardHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("Error deleting card from DB:", err)
jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{
Success: false,
Message: "Failed to delete card due to database error.",
Message: "Failed to delete card.",
})
return
}
Expand Down
1 change: 1 addition & 0 deletions backend/internal/pkg/handler/jsonWrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type APIResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Field string `json:"field,omitempty"`
Data any `json:"data,omitempty"` // Optional field for additional data
}

// Login specific response — returns user data after login
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package structs
package structure

// CardData struct represents the data required to create a new card
type CardData struct {
CardUID string `json:"card_uid" db:"card_uid" validate:"required"`
CardUID string `json:"card_uid" db:"card_uid" validate:"required"`
CardNumber string `json:"cardNumber" db:"card_number" validate:"required"`
CardHolder string `json:"cardHolder" db:"user_id" validate:"required"`
CardType string `json:"cardType" db:"card_type" validate:"required"`
Balance float64 `json:"initial_amount" db:"balance" validate:"required,min=0"`
}
}
Loading