diff --git a/cmd/server/providers.go b/cmd/server/providers.go index cd1a5dd..90afc8c 100644 --- a/cmd/server/providers.go +++ b/cmd/server/providers.go @@ -19,6 +19,7 @@ import ( gacha_usecase "github.com/Financial-Partner/server/internal/module/gacha/usecase" goal_usecase "github.com/Financial-Partner/server/internal/module/goal/usecase" investment_usecase "github.com/Financial-Partner/server/internal/module/investment/usecase" + report_usecase "github.com/Financial-Partner/server/internal/module/report/usecase" transaction_usecase "github.com/Financial-Partner/server/internal/module/transaction/usecase" user_repository "github.com/Financial-Partner/server/internal/module/user/repository" user_usecase "github.com/Financial-Partner/server/internal/module/user/usecase" @@ -91,6 +92,10 @@ func ProvideGachaService() *gacha_usecase.Service { return gacha_usecase.NewService() } +func ProvideReportService() *report_usecase.Service { + return report_usecase.NewService() +} + func ProvideHandler( userService *user_usecase.Service, authService *auth_usecase.Service, @@ -98,9 +103,10 @@ func ProvideHandler( investmentService *investment_usecase.Service, transactionService *transaction_usecase.Service, gachaService *gacha_usecase.Service, + reportService *report_usecase.Service, log loggerInfra.Logger, ) *handler.Handler { - return handler.NewHandler(userService, authService, goalService, investmentService, transactionService, gachaService, log) + return handler.NewHandler(userService, authService, goalService, investmentService, transactionService, gachaService, reportService, log) } func ProvideAuthMiddleware(jwtManager *authInfra.JWTManager, cfg *config.Config, log loggerInfra.Logger) *middleware.AuthMiddleware { diff --git a/cmd/server/route.go b/cmd/server/route.go index eb5ea33..4796951 100644 --- a/cmd/server/route.go +++ b/cmd/server/route.go @@ -73,4 +73,8 @@ func setupProtectedRoutes(router *mux.Router, handlers *handler.Handler) { gachaRoutes := router.PathPrefix("/gacha").Subrouter() gachaRoutes.HandleFunc("/draw", handlers.DrawGacha).Methods(http.MethodPost) gachaRoutes.HandleFunc("/preview", handlers.PreviewGachas).Methods(http.MethodGet) + + reportRoutes := router.PathPrefix("/reports").Subrouter() + reportRoutes.HandleFunc("/finance", handlers.GetReport).Methods(http.MethodGet) + reportRoutes.HandleFunc("/analysis", handlers.GetReportSummary).Methods(http.MethodGet) } diff --git a/cmd/server/wire.go b/cmd/server/wire.go index 1a27797..6cd6159 100644 --- a/cmd/server/wire.go +++ b/cmd/server/wire.go @@ -25,6 +25,7 @@ func InitializeServer(cfgFile string) (*Server, error) { ProvideInvestmentService, ProvideTransactionService, ProvideGachaService, + ProvideReportService, ProvideHandler, ProvideAuthMiddleware, ProvideRouter, diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 64bbfa7..3ecdfe9 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -40,7 +40,8 @@ func InitializeServer(cfgFile string) (*Server, error) { investment_usecaseService := ProvideInvestmentService() transaction_usecaseService := ProvideTransactionService() gacha_usecaseService := ProvideGachaService() - handler := ProvideHandler(service, auth_usecaseService, goal_usecaseService, investment_usecaseService, transaction_usecaseService, gacha_usecaseService, logger) + report_usecaseService := ProvideReportService() + handler := ProvideHandler(service, auth_usecaseService, goal_usecaseService, investment_usecaseService, transaction_usecaseService, gacha_usecaseService, report_usecaseService, logger) authMiddleware := ProvideAuthMiddleware(jwtManager, config, logger) loggerMiddleware := ProvideLoggerMiddleware(logger) router := ProvideRouter(handler, authMiddleware, loggerMiddleware, config) diff --git a/internal/entities/report.go b/internal/entities/report.go new file mode 100644 index 0000000..e8a9d7d --- /dev/null +++ b/internal/entities/report.go @@ -0,0 +1,14 @@ +package entities + +type Report struct { + Revenue int64 `bson:"revenue" json:"revenue"` + Expenses int64 `bson:"expenses" json:"expenses"` + NetProfit int64 `bson:"net_profit" json:"net_profit"` + Categories []string `bson:"categories" json:"categories"` + Amounts []int64 `bson:"amounts" json:"amounts"` + Percentages []float64 `bson:"percentages" json:"percentages"` +} + +type ReportSummary struct { + Summary string `bson:"summary" json:"summary"` +} diff --git a/internal/interfaces/http/dto/report.go b/internal/interfaces/http/dto/report.go new file mode 100644 index 0000000..596620b --- /dev/null +++ b/internal/interfaces/http/dto/report.go @@ -0,0 +1,14 @@ +package dto + +type ReportResponse struct { + Revenue int64 `json:"revenue" example:"10000" binding:"required"` + Expenses int64 `json:"expenses" example:"5000" binding:"required"` + NetProfit int64 `json:"net_profit" example:"5000" binding:"required"` + Categories []string `json:"categories" example:"Food,Transport" binding:"required"` + Amounts []int64 `json:"amounts" example:"1000,2000" binding:"required"` + Percentages []float64 `json:"percentages" example:"0.33,0.67" binding:"required"` +} + +type ReportSummaryResponse struct { + Summary string `json:"summary" example:"Report generated by AI"` +} diff --git a/internal/interfaces/http/error/error.go b/internal/interfaces/http/error/error.go index ea56c5d..a774f54 100644 --- a/internal/interfaces/http/error/error.go +++ b/internal/interfaces/http/error/error.go @@ -2,6 +2,7 @@ package httperror const ( ErrInvalidRequest = "Invalid request format" + ErrInvalidParameter = "Invalid parameter format" ErrUnauthorized = "Unauthorized" ErrEmailNotFound = "Email not found" ErrUserIDNotFound = "User ID not found" @@ -24,4 +25,6 @@ const ( ErrFailedToCreateTransaction = "Failed to create a transaction" ErrFailedToDrawGacha = "Failed to draw a gacha" ErrFailedToPreviewGachas = "Failed to preview gachas" + ErrFailedToGetReport = "Failed to get report" + ErrFailedToGetReportSummary = "Failed to get report summary" ) diff --git a/internal/interfaces/http/handler.go b/internal/interfaces/http/handler.go index c522ef9..c1a9444 100644 --- a/internal/interfaces/http/handler.go +++ b/internal/interfaces/http/handler.go @@ -11,10 +11,11 @@ type Handler struct { investmentService InvestmentService transactionService TransactionService gachaService GachaService + reportService ReportService log logger.Logger } -func NewHandler(us UserService, as AuthService, gs GoalService, is InvestmentService, ts TransactionService, gcs GachaService, log logger.Logger) *Handler { +func NewHandler(us UserService, as AuthService, gs GoalService, is InvestmentService, ts TransactionService, gcs GachaService, rs ReportService, log logger.Logger) *Handler { return &Handler{ userService: us, authService: as, @@ -22,6 +23,7 @@ func NewHandler(us UserService, as AuthService, gs GoalService, is InvestmentSer investmentService: is, transactionService: ts, gachaService: gcs, + reportService: rs, log: log, } } diff --git a/internal/interfaces/http/handler_test.go b/internal/interfaces/http/handler_test.go index 01a7c76..16d1fd7 100644 --- a/internal/interfaces/http/handler_test.go +++ b/internal/interfaces/http/handler_test.go @@ -18,6 +18,7 @@ type MockServices struct { InvestmentService *handler.MockInvestmentService TransactionService *handler.MockTransactionService GachaService *handler.MockGachaService + ReportService *handler.MockReportService } func newTestHandler(t *testing.T) (*handler.Handler, *MockServices) { @@ -31,8 +32,9 @@ func newTestHandler(t *testing.T) (*handler.Handler, *MockServices) { InvestmentService: handler.NewMockInvestmentService(ctrl), TransactionService: handler.NewMockTransactionService(ctrl), GachaService: handler.NewMockGachaService(ctrl), + ReportService: handler.NewMockReportService(ctrl), } - h := handler.NewHandler(ms.UserService, ms.AuthService, ms.GoalService, ms.InvestmentService, ms.TransactionService, ms.GachaService, logger.NewNopLogger()) + h := handler.NewHandler(ms.UserService, ms.AuthService, ms.GoalService, ms.InvestmentService, ms.TransactionService, ms.GachaService, ms.ReportService, logger.NewNopLogger()) return h, ms } diff --git a/internal/interfaces/http/report.go b/internal/interfaces/http/report.go new file mode 100644 index 0000000..c5a2cd4 --- /dev/null +++ b/internal/interfaces/http/report.go @@ -0,0 +1,121 @@ +package handler + +import ( + "context" + "net/http" + "strconv" + "time" + + "github.com/Financial-Partner/server/internal/contextutil" + "github.com/Financial-Partner/server/internal/entities" + "github.com/Financial-Partner/server/internal/interfaces/http/dto" + httperror "github.com/Financial-Partner/server/internal/interfaces/http/error" + responde "github.com/Financial-Partner/server/internal/interfaces/http/respond" +) + +//go:generate mockgen -source=report.go -destination=report_mock.go -package=handler + +type ReportService interface { + GetReport(ctx context.Context, userID string, startTime time.Time, endTime time.Time, reportType string) (*entities.Report, error) + GetReportSummary(ctx context.Context, userID string) (*entities.ReportSummary, error) +} + +// @Summary Get report +// @Description Get report for a user +// @Tags reports +// @Accept json +// @Produce json +// @Param Authorization header string true "Bearer {token}" default +// @Param type query string true "Type of report (summary or detailed)" +// @Param start query int64 false "Start time as Unix timestamp (seconds since epoch)" +// @Param end query int64 false "End time as Unix timestamp (seconds since epoch)" +// @Success 200 {object} dto.ReportResponse +// @Failure 400 {object} dto.ErrorResponse +// @Failure 401 {object} dto.ErrorResponse +// @Failure 500 {object} dto.ErrorResponse +// @Router /reports/finance [get] +func (h *Handler) GetReport(w http.ResponseWriter, r *http.Request) { + // Parse query parameters + reportType := r.URL.Query().Get("type") + start := r.URL.Query().Get("start") + end := r.URL.Query().Get("end") + + var startDate, endDate time.Time + var err error + if start != "" { + startTimestamp, err := strconv.ParseInt(start, 10, 64) // Parse Unix timestamp + if err != nil { + h.log.Warnf("Invalid start timestamp format. Use a valid Unix timestamp.") + responde.WithError(w, r, h.log, err, httperror.ErrInvalidParameter, http.StatusBadRequest) + return + } + startDate = time.Unix(startTimestamp, 0).UTC() // Convert to time.Time in UTC + } + + if end != "" { + endTimestamp, err := strconv.ParseInt(end, 10, 64) // Parse Unix timestamp + if err != nil { + h.log.Warnf("Invalid end timestamp format. Use a valid Unix timestamp.") + responde.WithError(w, r, h.log, err, httperror.ErrInvalidParameter, http.StatusBadRequest) + return + } + endDate = time.Unix(endTimestamp, 0).UTC() // Convert to time.Time in UTC + } + + userID, ok := contextutil.GetUserID(r.Context()) + if !ok { + h.log.Warnf("failed to get user ID from context") + responde.WithError(w, r, h.log, nil, httperror.ErrUnauthorized, http.StatusUnauthorized) + return + } + + report, err := h.reportService.GetReport(r.Context(), userID, startDate, endDate, reportType) + if err != nil { + h.log.Errorf("failed to get report") + responde.WithError(w, r, h.log, err, httperror.ErrFailedToGetReport, http.StatusInternalServerError) + return + } + + resp := dto.ReportResponse{ + Revenue: report.Revenue, + Expenses: report.Expenses, + NetProfit: report.NetProfit, + Categories: report.Categories, + Amounts: report.Amounts, + Percentages: report.Percentages, + } + + responde.WithJSON(w, r, resp, http.StatusOK) +} + +// @Summary Get report summary +// @Description Get report summary generated by AI for a user +// @Tags reports +// @Accept json +// @Produce json +// @Param Authorization header string true "Bearer {token}" default +// @Success 200 {object} dto.ReportSummaryResponse +// @Failure 401 {object} dto.ErrorResponse +// @Failure 500 {object} dto.ErrorResponse +// @Router /reports/analysis [get] +func (h *Handler) GetReportSummary(w http.ResponseWriter, r *http.Request) { + userID, ok := contextutil.GetUserID(r.Context()) + if !ok { + h.log.Warnf("failed to get user ID from context") + responde.WithError(w, r, h.log, nil, httperror.ErrUnauthorized, http.StatusUnauthorized) + return + } + + reportSummary, err := h.reportService.GetReportSummary(r.Context(), userID) + if err != nil { + h.log.Errorf("failed to get report summary") + responde.WithError(w, r, h.log, err, httperror.ErrFailedToGetReportSummary, http.StatusInternalServerError) + return + } + + resp := dto.ReportSummaryResponse{ + Summary: reportSummary.Summary, + } + + responde.WithJSON(w, r, resp, http.StatusOK) +} diff --git a/internal/interfaces/http/report_mock.go b/internal/interfaces/http/report_mock.go new file mode 100644 index 0000000..343b33e --- /dev/null +++ b/internal/interfaces/http/report_mock.go @@ -0,0 +1,73 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: report.go +// +// Generated by this command: +// +// mockgen -source=report.go -destination=report_mock.go -package=handler +// + +// Package handler is a generated GoMock package. +package handler + +import ( + context "context" + reflect "reflect" + time "time" + + entities "github.com/Financial-Partner/server/internal/entities" + gomock "go.uber.org/mock/gomock" +) + +// MockReportService is a mock of ReportService interface. +type MockReportService struct { + ctrl *gomock.Controller + recorder *MockReportServiceMockRecorder + isgomock struct{} +} + +// MockReportServiceMockRecorder is the mock recorder for MockReportService. +type MockReportServiceMockRecorder struct { + mock *MockReportService +} + +// NewMockReportService creates a new mock instance. +func NewMockReportService(ctrl *gomock.Controller) *MockReportService { + mock := &MockReportService{ctrl: ctrl} + mock.recorder = &MockReportServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockReportService) EXPECT() *MockReportServiceMockRecorder { + return m.recorder +} + +// GetReport mocks base method. +func (m *MockReportService) GetReport(ctx context.Context, userID string, startTime, endTime time.Time, reportType string) (*entities.Report, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetReport", ctx, userID, startTime, endTime, reportType) + ret0, _ := ret[0].(*entities.Report) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetReport indicates an expected call of GetReport. +func (mr *MockReportServiceMockRecorder) GetReport(ctx, userID, startTime, endTime, reportType any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReport", reflect.TypeOf((*MockReportService)(nil).GetReport), ctx, userID, startTime, endTime, reportType) +} + +// GetReportSummary mocks base method. +func (m *MockReportService) GetReportSummary(ctx context.Context, userID string) (*entities.ReportSummary, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetReportSummary", ctx, userID) + ret0, _ := ret[0].(*entities.ReportSummary) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetReportSummary indicates an expected call of GetReportSummary. +func (mr *MockReportServiceMockRecorder) GetReportSummary(ctx, userID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReportSummary", reflect.TypeOf((*MockReportService)(nil).GetReportSummary), ctx, userID) +} diff --git a/internal/interfaces/http/report_test.go b/internal/interfaces/http/report_test.go new file mode 100644 index 0000000..cbfd06f --- /dev/null +++ b/internal/interfaces/http/report_test.go @@ -0,0 +1,206 @@ +package handler_test + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/Financial-Partner/server/internal/entities" + "github.com/Financial-Partner/server/internal/interfaces/http/dto" + httperror "github.com/Financial-Partner/server/internal/interfaces/http/error" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestGetReport(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + t.Run("Unauthorized request", func(t *testing.T) { + h, _ := newTestHandler(t) + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/reports/finance", nil) + + h.GetReport(w, r) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var errorResp dto.ErrorResponse + err := json.NewDecoder(w.Body).Decode(&errorResp) + assert.NoError(t, err) + assert.Equal(t, http.StatusUnauthorized, errorResp.Code) + assert.Equal(t, httperror.ErrUnauthorized, errorResp.Message) + }) + + t.Run("Invalid parameter format", func(t *testing.T) { + h, _ := newTestHandler(t) + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/reports/finance?type=summary&start=invalid-date&end=1748908800", nil) + + h.GetReport(w, r) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + // Decode the error response + var errorResp dto.ErrorResponse + err := json.NewDecoder(w.Body).Decode(&errorResp) + assert.NoError(t, err) + + // Assert the error response details + assert.Equal(t, http.StatusBadRequest, errorResp.Code) + assert.Equal(t, httperror.ErrInvalidParameter, errorResp.Message) + }) + + t.Run("Service error", func(t *testing.T) { + h, mockService := newTestHandler(t) + + userID := "testUserID" + userEmail := "test@example.com" + + mockService.ReportService.EXPECT(). + GetReport(gomock.Any(), userID, gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, errors.New("service error")) + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/reports/finance?type=summary&start=1735689600&end=1748908800", nil) + ctx := newContext(userID, userEmail) + r = r.WithContext(ctx) + + h.GetReport(w, r) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var errorResp dto.ErrorResponse + err := json.NewDecoder(w.Body).Decode(&errorResp) + assert.NoError(t, err) + assert.Equal(t, http.StatusInternalServerError, errorResp.Code) + assert.Equal(t, httperror.ErrFailedToGetReport, errorResp.Message) + }) + + t.Run("Success", func(t *testing.T) { + h, mockService := newTestHandler(t) + + userID := "testUserID" + userEmail := "test@example.com" + + report := &entities.Report{ + Revenue: 1000, + Expenses: 500, + NetProfit: 500, + Categories: []string{"Food", "Transport"}, + Amounts: []int64{200, 300}, + Percentages: []float64{0.4, 0.6}, + } + + mockService.ReportService.EXPECT(). + GetReport(gomock.Any(), userID, gomock.Any(), gomock.Any(), gomock.Any()). + Return(report, nil) + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/reports/finance?type=summary&start=1735689600&end=1748908800", nil) + ctx := newContext(userID, userEmail) + r = r.WithContext(ctx) + + h.GetReport(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var response dto.ReportResponse + err := json.NewDecoder(w.Body).Decode(&response) + assert.NoError(t, err) + assert.Equal(t, report.Revenue, response.Revenue) + assert.Equal(t, report.Expenses, response.Expenses) + assert.Equal(t, report.NetProfit, response.NetProfit) + assert.Equal(t, report.Categories, response.Categories) + assert.Equal(t, report.Amounts, response.Amounts) + assert.Equal(t, report.Percentages, response.Percentages) + }) +} + +func TestGetReportSummary(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + t.Run("Unauthorized request", func(t *testing.T) { + h, _ := newTestHandler(t) + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/reports/analysis", nil) + + h.GetReportSummary(w, r) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var errorResp dto.ErrorResponse + err := json.NewDecoder(w.Body).Decode(&errorResp) + assert.NoError(t, err) + assert.Equal(t, http.StatusUnauthorized, errorResp.Code) + assert.Equal(t, httperror.ErrUnauthorized, errorResp.Message) + }) + + t.Run("Service error", func(t *testing.T) { + h, mockService := newTestHandler(t) + + userID := "testUserID" + userEmail := "test@example.com" + + mockService.ReportService.EXPECT(). + GetReportSummary(gomock.Any(), userID). + Return(nil, errors.New("service error")) + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/reports/analysis", nil) + ctx := newContext(userID, userEmail) + r = r.WithContext(ctx) + + h.GetReportSummary(w, r) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var errorResp dto.ErrorResponse + err := json.NewDecoder(w.Body).Decode(&errorResp) + assert.NoError(t, err) + assert.Equal(t, http.StatusInternalServerError, errorResp.Code) + assert.Equal(t, httperror.ErrFailedToGetReportSummary, errorResp.Message) + }) + + t.Run("Success", func(t *testing.T) { + h, mockService := newTestHandler(t) + + userID := "testUserID" + userEmail := "test@example.com" + + reportSummary := &entities.ReportSummary{ + Summary: "This is a summary", + } + + mockService.ReportService.EXPECT(). + GetReportSummary(gomock.Any(), userID). + Return(reportSummary, nil) + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/reports/analysis", nil) + ctx := newContext(userID, userEmail) + r = r.WithContext(ctx) + + h.GetReportSummary(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var response dto.ReportSummaryResponse + err := json.NewDecoder(w.Body).Decode(&response) + assert.NoError(t, err) + assert.Equal(t, reportSummary.Summary, response.Summary) + }) +} diff --git a/internal/module/report/usecase/service.go b/internal/module/report/usecase/service.go new file mode 100644 index 0000000..120f880 --- /dev/null +++ b/internal/module/report/usecase/service.go @@ -0,0 +1,23 @@ +package report_usecase + +import ( + "context" + "time" + + "github.com/Financial-Partner/server/internal/entities" +) + +type Service struct { +} + +func NewService() *Service { + return &Service{} +} + +func (s *Service) GetReport(ctx context.Context, userID string, startTime time.Time, endTime time.Time, reportType string) (*entities.Report, error) { + return nil, nil +} + +func (s *Service) GetReportSummary(ctx context.Context, userID string) (*entities.ReportSummary, error) { + return nil, nil +} diff --git a/swagger/docs.go b/swagger/docs.go index ab74d58..eb3ed0c 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -504,6 +504,119 @@ const docTemplate = `{ } } }, + "/reports/analysis": { + "get": { + "description": "Get report summary generated by AI for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "reports" + ], + "summary": "Get report summary", + "parameters": [ + { + "type": "string", + "description": "Bearer {token}", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ReportSummaryResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + } + } + } + }, + "/reports/finance": { + "get": { + "description": "Get report for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "reports" + ], + "summary": "Get report", + "parameters": [ + { + "type": "string", + "description": "Bearer {token}", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Type of report (summary or detailed)", + "name": "type", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Start time as Unix timestamp (seconds since epoch)", + "name": "start", + "in": "query" + }, + { + "type": "integer", + "description": "End time as Unix timestamp (seconds since epoch)", + "name": "end", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ReportResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + } + } + } + }, "/transactions": { "get": { "description": "Get transactions for a user", @@ -1296,6 +1409,70 @@ const docTemplate = `{ } } }, + "dto.ReportResponse": { + "type": "object", + "required": [ + "amounts", + "categories", + "expenses", + "net_profit", + "percentages", + "revenue" + ], + "properties": { + "amounts": { + "type": "array", + "items": { + "type": "integer" + }, + "example": [ + 1000, + 2000 + ] + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "Food", + "Transport" + ] + }, + "expenses": { + "type": "integer", + "example": 5000 + }, + "net_profit": { + "type": "integer", + "example": 5000 + }, + "percentages": { + "type": "array", + "items": { + "type": "number" + }, + "example": [ + 0.33, + 0.67 + ] + }, + "revenue": { + "type": "integer", + "example": 10000 + } + } + }, + "dto.ReportSummaryResponse": { + "type": "object", + "properties": { + "summary": { + "type": "string", + "example": "Report generated by AI" + } + } + }, "dto.TransactionResponse": { "type": "object", "required": [ diff --git a/swagger/swagger.json b/swagger/swagger.json index 45686de..ce4c048 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -497,6 +497,119 @@ } } }, + "/reports/analysis": { + "get": { + "description": "Get report summary generated by AI for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "reports" + ], + "summary": "Get report summary", + "parameters": [ + { + "type": "string", + "description": "Bearer {token}", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ReportSummaryResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + } + } + } + }, + "/reports/finance": { + "get": { + "description": "Get report for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "reports" + ], + "summary": "Get report", + "parameters": [ + { + "type": "string", + "description": "Bearer {token}", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Type of report (summary or detailed)", + "name": "type", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Start time as Unix timestamp (seconds since epoch)", + "name": "start", + "in": "query" + }, + { + "type": "integer", + "description": "End time as Unix timestamp (seconds since epoch)", + "name": "end", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ReportResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponse" + } + } + } + } + }, "/transactions": { "get": { "description": "Get transactions for a user", @@ -1289,6 +1402,70 @@ } } }, + "dto.ReportResponse": { + "type": "object", + "required": [ + "amounts", + "categories", + "expenses", + "net_profit", + "percentages", + "revenue" + ], + "properties": { + "amounts": { + "type": "array", + "items": { + "type": "integer" + }, + "example": [ + 1000, + 2000 + ] + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "Food", + "Transport" + ] + }, + "expenses": { + "type": "integer", + "example": 5000 + }, + "net_profit": { + "type": "integer", + "example": 5000 + }, + "percentages": { + "type": "array", + "items": { + "type": "number" + }, + "example": [ + 0.33, + 0.67 + ] + }, + "revenue": { + "type": "integer", + "example": 10000 + } + } + }, + "dto.ReportSummaryResponse": { + "type": "object", + "properties": { + "summary": { + "type": "string", + "example": "Report generated by AI" + } + } + }, "dto.TransactionResponse": { "type": "object", "required": [ diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 3c150a6..e250e63 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -329,6 +329,52 @@ definitions: example: Bearer type: string type: object + dto.ReportResponse: + properties: + amounts: + example: + - 1000 + - 2000 + items: + type: integer + type: array + categories: + example: + - Food + - Transport + items: + type: string + type: array + expenses: + example: 5000 + type: integer + net_profit: + example: 5000 + type: integer + percentages: + example: + - 0.33 + - 0.67 + items: + type: number + type: array + revenue: + example: 10000 + type: integer + required: + - amounts + - categories + - expenses + - net_profit + - percentages + - revenue + type: object + dto.ReportSummaryResponse: + properties: + summary: + example: Report generated by AI + type: string + type: object dto.TransactionResponse: properties: amount: @@ -747,6 +793,81 @@ paths: summary: Get investment opportunities tags: - investments + /reports/analysis: + get: + consumes: + - application/json + description: Get report summary generated by AI for a user + parameters: + - description: Bearer {token} + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.ReportSummaryResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/dto.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/dto.ErrorResponse' + summary: Get report summary + tags: + - reports + /reports/finance: + get: + consumes: + - application/json + description: Get report for a user + parameters: + - description: Bearer {token} + in: header + name: Authorization + required: true + type: string + - description: Type of report (summary or detailed) + in: query + name: type + required: true + type: string + - description: Start time as Unix timestamp (seconds since epoch) + in: query + name: start + type: integer + - description: End time as Unix timestamp (seconds since epoch) + in: query + name: end + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.ReportResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/dto.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/dto.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/dto.ErrorResponse' + summary: Get report + tags: + - reports /transactions: get: consumes: