Yimaru-BackEnd/internal/web_server/handlers/report.go

229 lines
7.2 KiB
Go

package handlers
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2"
)
// GetDashboardReport returns a comprehensive dashboard report
// @Summary Get dashboard report
// @Description Returns a comprehensive dashboard report with key metrics
// @Tags Reports
// @Accept json
// @Produce json
// @Param company_id query int false "Company ID filter"
// @Param branch_id query int false "Branch ID filter"
// @Param user_id query int false "User ID filter"
// @Param start_time query string false "Start time filter (RFC3339 format)"
// @Param end_time query string false "End time filter (RFC3339 format)"
// @Param sport_id query string false "Sport ID filter"
// @Param status query int false "Status filter (0=Pending, 1=Win, 2=Loss, 3=Half, 4=Void, 5=Error)"
// @Security ApiKeyAuth
// @Success 200 {object} domain.DashboardSummary
// @Failure 400 {object} domain.ErrorResponse
// @Failure 401 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/reports/dashboard [get]
func (h *Handler) GetDashboardReport(c *fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Parse query parameters
filter, err := parseReportFilter(c)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid filter parameters",
Error: err.Error(),
})
}
// Get report data
summary, err := h.reportSvc.GetDashboardSummary(ctx, filter)
if err != nil {
h.logger.Error("failed to get dashboard report", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to generate report",
Error: err.Error(),
})
}
res := domain.ConvertDashboardSummaryToRes(summary)
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Dashboard reports generated successfully",
Success: true,
StatusCode: 200,
Data: res,
})
// return c.Status(fiber.StatusOK).JSON(summary)
}
// parseReportFilter parses query parameters into ReportFilter
func parseReportFilter(c *fiber.Ctx) (domain.ReportFilter, error) {
var filter domain.ReportFilter
var err error
if c.Query("company_id") != "" {
companyID, err := strconv.ParseInt(c.Query("company_id"), 10, 64)
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid company_id: %w", err)
}
filter.CompanyID = domain.ValidInt64{Value: companyID, Valid: true}
}
if c.Query("branch_id") != "" {
branchID, err := strconv.ParseInt(c.Query("branch_id"), 10, 64)
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid branch_id: %w", err)
}
filter.BranchID = domain.ValidInt64{Value: branchID, Valid: true}
}
if c.Query("user_id") != "" {
userID, err := strconv.ParseInt(c.Query("user_id"), 10, 64)
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid user_id: %w", err)
}
filter.UserID = domain.ValidInt64{Value: userID, Valid: true}
}
if c.Query("start_time") != "" {
startTime, err := time.Parse(time.RFC3339, c.Query("start_time"))
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid start_time: %w", err)
}
filter.StartTime = domain.ValidTime{Value: startTime, Valid: true}
}
if c.Query("end_time") != "" {
endTime, err := time.Parse(time.RFC3339, c.Query("end_time"))
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid end_time: %w", err)
}
filter.EndTime = domain.ValidTime{Value: endTime, Valid: true}
}
if c.Query("sport_id") != "" {
filter.SportID = domain.ValidString{Value: c.Query("sport_id"), Valid: true}
}
if c.Query("status") != "" {
status, err := strconv.ParseInt(c.Query("status"), 10, 32)
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid status: %w", err)
}
filter.Status = domain.ValidOutcomeStatus{Value: domain.OutcomeStatus(status), Valid: true}
}
return filter, err
}
// DownloadReportFile godoc
// @Summary Download a CSV report file
// @Description Downloads a generated report CSV file from the server
// @Tags Reports
// @Param filename path string true "Name of the report file to download (e.g., report_daily_2025-06-21.csv)"
// @Produce text/csv
// @Success 200 {file} file "CSV file will be downloaded"
// @Failure 400 {object} domain.ErrorResponse "Missing or invalid filename"
// @Failure 404 {object} domain.ErrorResponse "Report file not found"
// @Failure 500 {object} domain.ErrorResponse "Internal server error while serving the file"
// @Router /api/v1/report-files/download/{filename} [get]
func (h *Handler) DownloadReportFile(c *fiber.Ctx) error {
filename := c.Params("filename")
if filename == "" || strings.Contains(filename, "..") {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid filename parameter",
Error: "filename is required and must not contain '..'",
})
}
reportDir := "reports"
// Ensure reports directory exists
if _, err := os.Stat(reportDir); os.IsNotExist(err) {
if err := os.MkdirAll(reportDir, os.ModePerm); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to create report directory",
Error: err.Error(),
})
}
}
filePath := fmt.Sprintf("%s/%s", reportDir, filename)
// Check if the report file exists
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
Message: "Report file not found",
Error: "no such file",
})
}
// Set download headers
c.Set("Content-Type", "text/csv")
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
// Serve the file
if err := c.SendFile(filePath); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to serve file",
Error: err.Error(),
})
}
return nil
}
// ListReportFiles godoc
// @Summary List available report CSV files
// @Description Returns a list of all generated report CSV files available for download
// @Tags Reports
// @Produce json
// @Success 200 {object} domain.Response{data=[]string} "List of CSV report filenames"
// @Failure 500 {object} domain.ErrorResponse "Failed to read report directory"
// @Router /api/v1/report-files/list [get]
func (h *Handler) ListReportFiles(c *fiber.Ctx) error {
reportDir := "reports"
// Create the reports directory if it doesn't exist
if _, err := os.Stat(reportDir); os.IsNotExist(err) {
if err := os.MkdirAll(reportDir, os.ModePerm); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to create report directory",
Error: err.Error(),
})
}
}
files, err := os.ReadDir(reportDir)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to read report directory",
Error: err.Error(),
})
}
var reportFiles []string
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".csv") {
reportFiles = append(reportFiles, file.Name())
}
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
StatusCode: 200,
Message: "Report files retrieved successfully",
Data: reportFiles,
Success: true,
})
}