Yimaru-BackEnd/internal/services/report/csv.go
Samuel Tariku 485cba3c9c feat: Add new stat stores and reporting functionalities for bets, branches, and wallets
- Introduced BetStatStore, BranchStatStore, and WalletStatStore interfaces for handling statistics.
- Implemented repository methods for fetching and updating bet, branch, and wallet statistics.
- Created reporting services for generating interval reports for bets, branches, companies, and wallets.
- Enhanced CSV writing functionality to support dynamic struct to CSV conversion.
- Added cron jobs for periodic updates of branch and wallet statistics.
- Updated wallet handler to include transaction statistics in the response.
2025-10-29 07:14:38 +03:00

121 lines
3.2 KiB
Go

package report
import (
"context"
"encoding/csv"
"errors"
"fmt"
"os"
"path/filepath"
"time"
"reflect"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/google/uuid"
"go.uber.org/zap"
)
var (
ErrReportFileNotFound = errors.New("failed to find report file")
ErrReportFileError = errors.New("unknown error with report file")
ErrReportNotComplete = errors.New("report is not completed")
ErrReportFilePathInvalid = errors.New("report file path is invalid")
)
func StructToCSVRow(v any) ([]string, []string) {
t := reflect.TypeOf(v)
val := reflect.ValueOf(v)
headers := make([]string, t.NumField())
row := make([]string, t.NumField())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
headers[i] = field.Tag.Get("csv")
row[i] = fmt.Sprint(val.Field(i).Interface())
}
return headers, row
}
func (s *Service) WriteCSV(rows [][]string, filePrefix string) (string, error) {
if len(rows) == 0 {
s.mongoLogger.Error("[WriteCSV] CSV with no data",
zap.String("file_prefix", filePrefix),
)
return "", errors.New("no data to write")
}
filename := fmt.Sprintf("%s_%s_%s.csv",
filePrefix,
time.Now().Format("2006-01-02_15-04-05"),
uuid.NewString()[:8],
)
filePath := filepath.Join(s.cfg.ReportExportPath, filename)
file, err := os.Create(filePath)
if err != nil {
s.mongoLogger.Error("[WriteCSV] Failed to create file",
zap.String("file", filename),
zap.String("path", s.cfg.ReportExportPath),
zap.Error(err),
)
return "", fmt.Errorf("create csv: %w", err)
}
defer file.Close()
writer := csv.NewWriter(file)
if err := writer.WriteAll(rows); err != nil {
s.mongoLogger.Error("[WriteCSV] Error while writing csv",
zap.String("file", filename),
zap.String("path", s.cfg.ReportExportPath),
zap.Error(err),
)
return "", fmt.Errorf("write csv: %w", err)
}
return filePath, nil
}
func (s *Service) CheckAndFetchReportFile(ctx context.Context, ID int64) (string, error) {
report, err := s.GetReportRequestByID(ctx, ID)
if err != nil {
s.mongoLogger.Error("[CheckAndFetchReportFile] Failed to get report request by id",
zap.Error(err),
)
return "", fmt.Errorf("failed to get report request:%w", err)
}
if report.Status != domain.SuccessReportRequest {
s.mongoLogger.Error("[CheckAndFetchReportFile] Attempted download of report that isn't completed",
zap.String("status", string(report.Status)),
)
return "", ErrReportNotComplete
}
if !report.FilePath.Valid {
s.mongoLogger.Error("[CheckAndFetchReportFile] File Path is invalid even though the report is a success",
zap.String("file path", report.FilePath.Value),
)
return "", ErrReportFilePathInvalid
}
// Check if the report file exists
if _, err := os.Stat(report.FilePath.Value); err != nil {
if os.IsNotExist(err) {
s.mongoLogger.Error("[CheckAndFetchReportFile] Unable to find report file",
zap.String("file path", report.FilePath.Value),
)
return "", ErrReportFileNotFound
}
s.mongoLogger.Error("[CheckAndFetchReportFile] Unable to check report file",
zap.String("file path", report.FilePath.Value),
zap.Error(err),
)
return "", ErrReportFileError
}
return report.FilePath.Value, nil
}