Skip to content

Commit c6a019b

Browse files
authored
Merge pull request #15 from Financial-Partner/feat/define_analysis_api_interface
Add report service and related routes for financial reporting
2 parents 4cc7cf7 + 1b2f26c commit c6a019b

16 files changed

Lines changed: 949 additions & 4 deletions

File tree

cmd/server/providers.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
gacha_usecase "github.com/Financial-Partner/server/internal/module/gacha/usecase"
2020
goal_usecase "github.com/Financial-Partner/server/internal/module/goal/usecase"
2121
investment_usecase "github.com/Financial-Partner/server/internal/module/investment/usecase"
22+
report_usecase "github.com/Financial-Partner/server/internal/module/report/usecase"
2223
transaction_usecase "github.com/Financial-Partner/server/internal/module/transaction/usecase"
2324
user_repository "github.com/Financial-Partner/server/internal/module/user/repository"
2425
user_usecase "github.com/Financial-Partner/server/internal/module/user/usecase"
@@ -91,16 +92,21 @@ func ProvideGachaService() *gacha_usecase.Service {
9192
return gacha_usecase.NewService()
9293
}
9394

95+
func ProvideReportService() *report_usecase.Service {
96+
return report_usecase.NewService()
97+
}
98+
9499
func ProvideHandler(
95100
userService *user_usecase.Service,
96101
authService *auth_usecase.Service,
97102
goalService *goal_usecase.Service,
98103
investmentService *investment_usecase.Service,
99104
transactionService *transaction_usecase.Service,
100105
gachaService *gacha_usecase.Service,
106+
reportService *report_usecase.Service,
101107
log loggerInfra.Logger,
102108
) *handler.Handler {
103-
return handler.NewHandler(userService, authService, goalService, investmentService, transactionService, gachaService, log)
109+
return handler.NewHandler(userService, authService, goalService, investmentService, transactionService, gachaService, reportService, log)
104110
}
105111

106112
func ProvideAuthMiddleware(jwtManager *authInfra.JWTManager, cfg *config.Config, log loggerInfra.Logger) *middleware.AuthMiddleware {

cmd/server/route.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,8 @@ func setupProtectedRoutes(router *mux.Router, handlers *handler.Handler) {
7373
gachaRoutes := router.PathPrefix("/gacha").Subrouter()
7474
gachaRoutes.HandleFunc("/draw", handlers.DrawGacha).Methods(http.MethodPost)
7575
gachaRoutes.HandleFunc("/preview", handlers.PreviewGachas).Methods(http.MethodGet)
76+
77+
reportRoutes := router.PathPrefix("/reports").Subrouter()
78+
reportRoutes.HandleFunc("/finance", handlers.GetReport).Methods(http.MethodGet)
79+
reportRoutes.HandleFunc("/analysis", handlers.GetReportSummary).Methods(http.MethodGet)
7680
}

cmd/server/wire.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func InitializeServer(cfgFile string) (*Server, error) {
2525
ProvideInvestmentService,
2626
ProvideTransactionService,
2727
ProvideGachaService,
28+
ProvideReportService,
2829
ProvideHandler,
2930
ProvideAuthMiddleware,
3031
ProvideRouter,

cmd/server/wire_gen.go

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/entities/report.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package entities
2+
3+
type Report struct {
4+
Revenue int64 `bson:"revenue" json:"revenue"`
5+
Expenses int64 `bson:"expenses" json:"expenses"`
6+
NetProfit int64 `bson:"net_profit" json:"net_profit"`
7+
Categories []string `bson:"categories" json:"categories"`
8+
Amounts []int64 `bson:"amounts" json:"amounts"`
9+
Percentages []float64 `bson:"percentages" json:"percentages"`
10+
}
11+
12+
type ReportSummary struct {
13+
Summary string `bson:"summary" json:"summary"`
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dto
2+
3+
type ReportResponse struct {
4+
Revenue int64 `json:"revenue" example:"10000" binding:"required"`
5+
Expenses int64 `json:"expenses" example:"5000" binding:"required"`
6+
NetProfit int64 `json:"net_profit" example:"5000" binding:"required"`
7+
Categories []string `json:"categories" example:"Food,Transport" binding:"required"`
8+
Amounts []int64 `json:"amounts" example:"1000,2000" binding:"required"`
9+
Percentages []float64 `json:"percentages" example:"0.33,0.67" binding:"required"`
10+
}
11+
12+
type ReportSummaryResponse struct {
13+
Summary string `json:"summary" example:"Report generated by AI"`
14+
}

internal/interfaces/http/error/error.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package httperror
22

33
const (
44
ErrInvalidRequest = "Invalid request format"
5+
ErrInvalidParameter = "Invalid parameter format"
56
ErrUnauthorized = "Unauthorized"
67
ErrEmailNotFound = "Email not found"
78
ErrUserIDNotFound = "User ID not found"
@@ -24,4 +25,6 @@ const (
2425
ErrFailedToCreateTransaction = "Failed to create a transaction"
2526
ErrFailedToDrawGacha = "Failed to draw a gacha"
2627
ErrFailedToPreviewGachas = "Failed to preview gachas"
28+
ErrFailedToGetReport = "Failed to get report"
29+
ErrFailedToGetReportSummary = "Failed to get report summary"
2730
)

internal/interfaces/http/handler.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ type Handler struct {
1111
investmentService InvestmentService
1212
transactionService TransactionService
1313
gachaService GachaService
14+
reportService ReportService
1415
log logger.Logger
1516
}
1617

17-
func NewHandler(us UserService, as AuthService, gs GoalService, is InvestmentService, ts TransactionService, gcs GachaService, log logger.Logger) *Handler {
18+
func NewHandler(us UserService, as AuthService, gs GoalService, is InvestmentService, ts TransactionService, gcs GachaService, rs ReportService, log logger.Logger) *Handler {
1819
return &Handler{
1920
userService: us,
2021
authService: as,
2122
goalService: gs,
2223
investmentService: is,
2324
transactionService: ts,
2425
gachaService: gcs,
26+
reportService: rs,
2527
log: log,
2628
}
2729
}

internal/interfaces/http/handler_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type MockServices struct {
1818
InvestmentService *handler.MockInvestmentService
1919
TransactionService *handler.MockTransactionService
2020
GachaService *handler.MockGachaService
21+
ReportService *handler.MockReportService
2122
}
2223

2324
func newTestHandler(t *testing.T) (*handler.Handler, *MockServices) {
@@ -31,8 +32,9 @@ func newTestHandler(t *testing.T) (*handler.Handler, *MockServices) {
3132
InvestmentService: handler.NewMockInvestmentService(ctrl),
3233
TransactionService: handler.NewMockTransactionService(ctrl),
3334
GachaService: handler.NewMockGachaService(ctrl),
35+
ReportService: handler.NewMockReportService(ctrl),
3436
}
35-
h := handler.NewHandler(ms.UserService, ms.AuthService, ms.GoalService, ms.InvestmentService, ms.TransactionService, ms.GachaService, logger.NewNopLogger())
37+
h := handler.NewHandler(ms.UserService, ms.AuthService, ms.GoalService, ms.InvestmentService, ms.TransactionService, ms.GachaService, ms.ReportService, logger.NewNopLogger())
3638

3739
return h, ms
3840
}

internal/interfaces/http/report.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package handler
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"strconv"
7+
"time"
8+
9+
"github.com/Financial-Partner/server/internal/contextutil"
10+
"github.com/Financial-Partner/server/internal/entities"
11+
"github.com/Financial-Partner/server/internal/interfaces/http/dto"
12+
httperror "github.com/Financial-Partner/server/internal/interfaces/http/error"
13+
responde "github.com/Financial-Partner/server/internal/interfaces/http/respond"
14+
)
15+
16+
//go:generate mockgen -source=report.go -destination=report_mock.go -package=handler
17+
18+
type ReportService interface {
19+
GetReport(ctx context.Context, userID string, startTime time.Time, endTime time.Time, reportType string) (*entities.Report, error)
20+
GetReportSummary(ctx context.Context, userID string) (*entities.ReportSummary, error)
21+
}
22+
23+
// @Summary Get report
24+
// @Description Get report for a user
25+
// @Tags reports
26+
// @Accept json
27+
// @Produce json
28+
// @Param Authorization header string true "Bearer {token}" default
29+
// @Param type query string true "Type of report (summary or detailed)"
30+
// @Param start query int64 false "Start time as Unix timestamp (seconds since epoch)"
31+
// @Param end query int64 false "End time as Unix timestamp (seconds since epoch)"
32+
// @Success 200 {object} dto.ReportResponse
33+
// @Failure 400 {object} dto.ErrorResponse
34+
// @Failure 401 {object} dto.ErrorResponse
35+
// @Failure 500 {object} dto.ErrorResponse
36+
// @Router /reports/finance [get]
37+
func (h *Handler) GetReport(w http.ResponseWriter, r *http.Request) {
38+
// Parse query parameters
39+
reportType := r.URL.Query().Get("type")
40+
start := r.URL.Query().Get("start")
41+
end := r.URL.Query().Get("end")
42+
43+
var startDate, endDate time.Time
44+
var err error
45+
if start != "" {
46+
startTimestamp, err := strconv.ParseInt(start, 10, 64) // Parse Unix timestamp
47+
if err != nil {
48+
h.log.Warnf("Invalid start timestamp format. Use a valid Unix timestamp.")
49+
responde.WithError(w, r, h.log, err, httperror.ErrInvalidParameter, http.StatusBadRequest)
50+
return
51+
}
52+
startDate = time.Unix(startTimestamp, 0).UTC() // Convert to time.Time in UTC
53+
}
54+
55+
if end != "" {
56+
endTimestamp, err := strconv.ParseInt(end, 10, 64) // Parse Unix timestamp
57+
if err != nil {
58+
h.log.Warnf("Invalid end timestamp format. Use a valid Unix timestamp.")
59+
responde.WithError(w, r, h.log, err, httperror.ErrInvalidParameter, http.StatusBadRequest)
60+
return
61+
}
62+
endDate = time.Unix(endTimestamp, 0).UTC() // Convert to time.Time in UTC
63+
}
64+
65+
userID, ok := contextutil.GetUserID(r.Context())
66+
if !ok {
67+
h.log.Warnf("failed to get user ID from context")
68+
responde.WithError(w, r, h.log, nil, httperror.ErrUnauthorized, http.StatusUnauthorized)
69+
return
70+
}
71+
72+
report, err := h.reportService.GetReport(r.Context(), userID, startDate, endDate, reportType)
73+
if err != nil {
74+
h.log.Errorf("failed to get report")
75+
responde.WithError(w, r, h.log, err, httperror.ErrFailedToGetReport, http.StatusInternalServerError)
76+
return
77+
}
78+
79+
resp := dto.ReportResponse{
80+
Revenue: report.Revenue,
81+
Expenses: report.Expenses,
82+
NetProfit: report.NetProfit,
83+
Categories: report.Categories,
84+
Amounts: report.Amounts,
85+
Percentages: report.Percentages,
86+
}
87+
88+
responde.WithJSON(w, r, resp, http.StatusOK)
89+
}
90+
91+
// @Summary Get report summary
92+
// @Description Get report summary generated by AI for a user
93+
// @Tags reports
94+
// @Accept json
95+
// @Produce json
96+
// @Param Authorization header string true "Bearer {token}" default
97+
// @Success 200 {object} dto.ReportSummaryResponse
98+
// @Failure 401 {object} dto.ErrorResponse
99+
// @Failure 500 {object} dto.ErrorResponse
100+
// @Router /reports/analysis [get]
101+
func (h *Handler) GetReportSummary(w http.ResponseWriter, r *http.Request) {
102+
userID, ok := contextutil.GetUserID(r.Context())
103+
if !ok {
104+
h.log.Warnf("failed to get user ID from context")
105+
responde.WithError(w, r, h.log, nil, httperror.ErrUnauthorized, http.StatusUnauthorized)
106+
return
107+
}
108+
109+
reportSummary, err := h.reportService.GetReportSummary(r.Context(), userID)
110+
if err != nil {
111+
h.log.Errorf("failed to get report summary")
112+
responde.WithError(w, r, h.log, err, httperror.ErrFailedToGetReportSummary, http.StatusInternalServerError)
113+
return
114+
}
115+
116+
resp := dto.ReportSummaryResponse{
117+
Summary: reportSummary.Summary,
118+
}
119+
120+
responde.WithJSON(w, r, resp, http.StatusOK)
121+
}

0 commit comments

Comments
 (0)