diff --git a/cmd/server/providers.go b/cmd/server/providers.go index 90afc8c..f80d756 100644 --- a/cmd/server/providers.go +++ b/cmd/server/providers.go @@ -20,6 +20,7 @@ import ( 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_repository "github.com/Financial-Partner/server/internal/module/transaction/repository" 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" @@ -58,6 +59,14 @@ func ProvideUserService(repo user_repository.Repository, store *perRedis.UserSto return user_usecase.NewService(repo, store, log) } +func ProvideTransactionRepository(db *dbInfra.Client) transaction_repository.Repository { + return perMongo.NewTransactionRepository(db) +} + +func ProvideTransactionStore(cache *cacheInfra.Client) *perRedis.TransactionStore { + return perRedis.NewTransactionStore(cache) +} + func ProvideJWTManager(cfg *config.Config) *authInfra.JWTManager { return authInfra.NewJWTManager(cfg.JWT.SecretKey, cfg.JWT.AccessExpiry, cfg.JWT.RefreshExpiry) } @@ -84,8 +93,8 @@ func ProvideInvestmentService() *investment_usecase.Service { return investment_usecase.NewService() } -func ProvideTransactionService() *transaction_usecase.Service { - return transaction_usecase.NewService() +func ProvideTransactionService(repo transaction_repository.Repository, store *perRedis.TransactionStore, log loggerInfra.Logger) *transaction_usecase.Service { + return transaction_usecase.NewService(repo, store, log) } func ProvideGachaService() *gacha_usecase.Service { diff --git a/cmd/server/wire.go b/cmd/server/wire.go index 6cd6159..a899b8f 100644 --- a/cmd/server/wire.go +++ b/cmd/server/wire.go @@ -23,6 +23,8 @@ func InitializeServer(cfgFile string) (*Server, error) { ProvideAuthService, ProvideGoalService, ProvideInvestmentService, + ProvideTransactionRepository, + ProvideTransactionStore, ProvideTransactionService, ProvideGachaService, ProvideReportService, diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 3ecdfe9..6cc36c3 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -38,7 +38,9 @@ func InitializeServer(cfgFile string) (*Server, error) { auth_usecaseService := ProvideAuthService(config, authClient, jwtManager, tokenStore, service) goal_usecaseService := ProvideGoalService() investment_usecaseService := ProvideInvestmentService() - transaction_usecaseService := ProvideTransactionService() + transaction_repositoryRepository := ProvideTransactionRepository(client) + transactionStore := ProvideTransactionStore(cacheClient) + transaction_usecaseService := ProvideTransactionService(transaction_repositoryRepository, transactionStore, logger) gacha_usecaseService := ProvideGachaService() report_usecaseService := ProvideReportService() handler := ProvideHandler(service, auth_usecaseService, goal_usecaseService, investment_usecaseService, transaction_usecaseService, gacha_usecaseService, report_usecaseService, logger) diff --git a/internal/infrastructure/auth/dummy.go b/internal/infrastructure/auth/dummy.go index 3b83d87..aeb7e77 100644 --- a/internal/infrastructure/auth/dummy.go +++ b/internal/infrastructure/auth/dummy.go @@ -3,6 +3,8 @@ package auth import ( "errors" + "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/Financial-Partner/server/internal/config" ) @@ -21,8 +23,13 @@ func (v *DummyJWTValidator) ValidateToken(tokenString string) (*Claims, error) { return nil, errors.New("invalid token") } + dummyObjectID, err := primitive.ObjectIDFromHex("680b4fc122fc6fd9212d78f9") + if err != nil { + return nil, errors.New("failed to create dummy ObjectID") + } + return &Claims{ - ID: "test-id", + ID: dummyObjectID.Hex(), Email: "bypass@example.com", }, nil } diff --git a/internal/infrastructure/persistence/mongodb/transaction.go b/internal/infrastructure/persistence/mongodb/transaction.go index dbb2efc..c22d70a 100644 --- a/internal/infrastructure/persistence/mongodb/transaction.go +++ b/internal/infrastructure/persistence/mongodb/transaction.go @@ -4,6 +4,7 @@ import ( "context" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "github.com/Financial-Partner/server/internal/entities" @@ -21,6 +22,7 @@ func NewTransactionRepository(db MongoClient) transaction_repository.Repository } func (r *MongoTransactionRepository) Create(ctx context.Context, entity *entities.Transaction) (*entities.Transaction, error) { + entity.ID = primitive.NewObjectID() _, err := r.collection.InsertOne(ctx, entity) if err != nil { return nil, err @@ -28,7 +30,7 @@ func (r *MongoTransactionRepository) Create(ctx context.Context, entity *entitie return entity, nil } -func (r *MongoTransactionRepository) FindByUserId(ctx context.Context, userID string) ([]entities.Transaction, error) { +func (r *MongoTransactionRepository) FindByUserId(ctx context.Context, userID primitive.ObjectID) ([]entities.Transaction, error) { var transactions []entities.Transaction cursor, err := r.collection.Find(ctx, bson.M{"user_id": userID}) if err != nil { diff --git a/internal/infrastructure/persistence/mongodb/transaction_test.go b/internal/infrastructure/persistence/mongodb/transaction_test.go index accfde9..9363c3a 100644 --- a/internal/infrastructure/persistence/mongodb/transaction_test.go +++ b/internal/infrastructure/persistence/mongodb/transaction_test.go @@ -17,7 +17,7 @@ import ( func TestMongoTransactionRepository(t *testing.T) { mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) - testUserID := primitive.NewObjectID().Hex() + testUserID := primitive.NewObjectID() testTransactions := []entities.Transaction{ { ID: primitive.NewObjectID(), diff --git a/internal/infrastructure/persistence/redis/transaction.go b/internal/infrastructure/persistence/redis/transaction.go index 75c9ca0..3f67ee8 100644 --- a/internal/infrastructure/persistence/redis/transaction.go +++ b/internal/infrastructure/persistence/redis/transaction.go @@ -2,11 +2,12 @@ package redis import ( "context" - "encoding/json" + "errors" "fmt" "time" "github.com/Financial-Partner/server/internal/entities" + "github.com/redis/go-redis/v9" ) const ( @@ -31,13 +32,18 @@ func (s *TransactionStore) GetByUserId(ctx context.Context, userID string) ([]en return transactions, nil } -func (s *TransactionStore) SetByUserId(ctx context.Context, userID string, transactions []entities.Transaction) error { - data, err := json.Marshal(transactions) - if err != nil { +func (s *TransactionStore) SetMultipleByUserId(ctx context.Context, userID string, transactions []entities.Transaction) error { + existingTransactions, err := s.GetByUserId(ctx, userID) + if err != nil && !errors.Is(err, redis.Nil) { return err } - return s.cacheClient.Set(ctx, fmt.Sprintf(transactionCacheKey, userID), data, transactionCacheTTL) + transactions = append(existingTransactions, transactions...) + err = s.cacheClient.Set(ctx, fmt.Sprintf(transactionCacheKey, userID), transactions, transactionCacheTTL) + if err != nil { + return err + } + return nil } func (s *TransactionStore) DeleteByUserId(ctx context.Context, userID string) error { diff --git a/internal/infrastructure/persistence/redis/transaction_test.go b/internal/infrastructure/persistence/redis/transaction_test.go index 6e9e6fe..27032bc 100644 --- a/internal/infrastructure/persistence/redis/transaction_test.go +++ b/internal/infrastructure/persistence/redis/transaction_test.go @@ -25,12 +25,12 @@ func TestTransactionStore(t *testing.T) { transactionStore := redis.NewTransactionStore(mockRedisClient) // Mock data - userID := primitive.NewObjectID().Hex() + userID := primitive.NewObjectID() mockTransactions := []entities.Transaction{ { ID: primitive.NewObjectID(), - UserID: primitive.NewObjectID(), + UserID: userID, Amount: 100, Description: "Groceries", Date: time.Now(), @@ -41,7 +41,7 @@ func TestTransactionStore(t *testing.T) { }, { ID: primitive.NewObjectID(), - UserID: primitive.NewObjectID(), + UserID: userID, Amount: 200, Description: "Rent", Date: time.Now(), @@ -56,12 +56,12 @@ func TestTransactionStore(t *testing.T) { mockData, _ := json.Marshal(mockTransactions) // Mock the Get method to return the serialized JSON data - mockRedisClient.EXPECT().Get(gomock.Any(), fmt.Sprintf("user:%s:transactions", userID), gomock.Any()).DoAndReturn( + mockRedisClient.EXPECT().Get(gomock.Any(), fmt.Sprintf("user:%s:transactions", userID.Hex()), gomock.Any()).DoAndReturn( func(_ context.Context, _ string, dest interface{}) error { return json.Unmarshal(mockData, dest) }, ) - transactions, err := transactionStore.GetByUserId(context.Background(), userID) + transactions, err := transactionStore.GetByUserId(context.Background(), userID.Hex()) require.NoError(t, err) assert.NotNil(t, transactions) }) @@ -70,24 +70,24 @@ func TestTransactionStore(t *testing.T) { mockRedisClient := redis.NewMockRedisClient(ctrl) transactionStore := redis.NewTransactionStore(mockRedisClient) - userID := primitive.NewObjectID().Hex() + userID := primitive.NewObjectID() mockRedisClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(goredis.Nil) - transactions, err := transactionStore.GetByUserId(context.Background(), userID) + transactions, err := transactionStore.GetByUserId(context.Background(), userID.Hex()) require.Error(t, err) assert.Nil(t, transactions) }) - t.Run("SetByUserIdSuccess", func(t *testing.T) { + t.Run("SetMultipleByUserIdWithExistingTransactions", func(t *testing.T) { mockRedisClient := redis.NewMockRedisClient(ctrl) transactionStore := redis.NewTransactionStore(mockRedisClient) - // mock data - userID := primitive.NewObjectID().Hex() - mockTransactions := []entities.Transaction{ + // Mock data + userID := primitive.NewObjectID() + existingTransactions := []entities.Transaction{ { ID: primitive.NewObjectID(), - UserID: primitive.NewObjectID(), + UserID: userID, Amount: 100, Description: "Groceries", Date: time.Now(), @@ -96,9 +96,11 @@ func TestTransactionStore(t *testing.T) { CreatedAt: time.Now(), UpdatedAt: time.Now(), }, + } + newTransactions := []entities.Transaction{ { ID: primitive.NewObjectID(), - UserID: primitive.NewObjectID(), + UserID: userID, Amount: 200, Description: "Rent", Date: time.Now(), @@ -107,10 +109,47 @@ func TestTransactionStore(t *testing.T) { CreatedAt: time.Now(), UpdatedAt: time.Now(), }, + { + ID: primitive.NewObjectID(), + UserID: userID, + Amount: 50, + Description: "Utilities", + Date: time.Now(), + Category: "Bills", + Type: "expense", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, } - mockRedisClient.EXPECT().Set(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - err := transactionStore.SetByUserId(context.Background(), userID, mockTransactions) + // Serialize existing transactions to JSON + mockData, _ := json.Marshal(existingTransactions) + + // Mock the Get method to return the existing transactions + mockRedisClient.EXPECT().Get( + gomock.Any(), + fmt.Sprintf("user:%s:transactions", userID.Hex()), + gomock.Any(), + ).DoAndReturn( + func(_ context.Context, _ string, dest interface{}) error { + return json.Unmarshal(mockData, dest) + }, + ) + + // Mock the Set method to save the updated transactions + mockRedisClient.EXPECT().Set( + gomock.Any(), + fmt.Sprintf("user:%s:transactions", userID.Hex()), + gomock.Any(), + gomock.Any(), + ).DoAndReturn( + func(_ context.Context, _ string, value interface{}, _ time.Duration) error { + return nil + }, + ) + + // Call SetMultipleByUserId + err := transactionStore.SetMultipleByUserId(context.Background(), userID.Hex(), newTransactions) require.NoError(t, err) }) @@ -118,10 +157,10 @@ func TestTransactionStore(t *testing.T) { mockRedisClient := redis.NewMockRedisClient(ctrl) transactionStore := redis.NewTransactionStore(mockRedisClient) - userID := primitive.NewObjectID().Hex() - mockRedisClient.EXPECT().Delete(gomock.Any(), fmt.Sprintf("user:%s:transactions", userID)).Return(nil) + userID := primitive.NewObjectID() + mockRedisClient.EXPECT().Delete(gomock.Any(), fmt.Sprintf("user:%s:transactions", userID.Hex())).Return(nil) - err := transactionStore.DeleteByUserId(context.Background(), userID) + err := transactionStore.DeleteByUserId(context.Background(), userID.Hex()) require.NoError(t, err) }) } diff --git a/internal/interfaces/http/report.go b/internal/interfaces/http/report.go index c5a2cd4..8eb6932 100644 --- a/internal/interfaces/http/report.go +++ b/internal/interfaces/http/report.go @@ -10,7 +10,7 @@ import ( "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" + respond "github.com/Financial-Partner/server/internal/interfaces/http/respond" ) //go:generate mockgen -source=report.go -destination=report_mock.go -package=handler @@ -46,7 +46,7 @@ func (h *Handler) GetReport(w http.ResponseWriter, r *http.Request) { 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) + respond.WithError(w, r, h.log, err, httperror.ErrInvalidParameter, http.StatusBadRequest) return } startDate = time.Unix(startTimestamp, 0).UTC() // Convert to time.Time in UTC @@ -56,7 +56,7 @@ func (h *Handler) GetReport(w http.ResponseWriter, r *http.Request) { 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) + respond.WithError(w, r, h.log, err, httperror.ErrInvalidParameter, http.StatusBadRequest) return } endDate = time.Unix(endTimestamp, 0).UTC() // Convert to time.Time in UTC @@ -65,14 +65,14 @@ func (h *Handler) GetReport(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) + respond.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) + respond.WithError(w, r, h.log, err, httperror.ErrFailedToGetReport, http.StatusInternalServerError) return } @@ -85,7 +85,7 @@ func (h *Handler) GetReport(w http.ResponseWriter, r *http.Request) { Percentages: report.Percentages, } - responde.WithJSON(w, r, resp, http.StatusOK) + respond.WithJSON(w, r, resp, http.StatusOK) } // @Summary Get report summary @@ -102,14 +102,14 @@ 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) + respond.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) + respond.WithError(w, r, h.log, err, httperror.ErrFailedToGetReportSummary, http.StatusInternalServerError) return } @@ -117,5 +117,5 @@ func (h *Handler) GetReportSummary(w http.ResponseWriter, r *http.Request) { Summary: reportSummary.Summary, } - responde.WithJSON(w, r, resp, http.StatusOK) + respond.WithJSON(w, r, resp, http.StatusOK) } diff --git a/internal/interfaces/http/transaction.go b/internal/interfaces/http/transaction.go index 9d1453b..4e56727 100644 --- a/internal/interfaces/http/transaction.go +++ b/internal/interfaces/http/transaction.go @@ -94,7 +94,7 @@ func (h *Handler) CreateTransaction(w http.ResponseWriter, r *http.Request) { transaction, err := h.transactionService.CreateTransaction(r.Context(), userID, &req) if err != nil { - h.log.Errorf("failed to create transaction") + h.log.Errorf("failed to create transaction: %v", err) respond.WithError(w, r, h.log, err, httperror.ErrFailedToCreateTransaction, http.StatusInternalServerError) return } diff --git a/internal/interfaces/http/transaction_test.go b/internal/interfaces/http/transaction_test.go index 57d8ae9..3e529f5 100644 --- a/internal/interfaces/http/transaction_test.go +++ b/internal/interfaces/http/transaction_test.go @@ -110,7 +110,7 @@ func TestCreateTransaction(t *testing.T) { t.Run("Success", func(t *testing.T) { h, mockServices := newTestHandler(t) - userID := primitive.NewObjectID().Hex() + userID := primitive.NewObjectID() userEmail := "test@example.com" now := time.Now() @@ -128,7 +128,7 @@ func TestCreateTransaction(t *testing.T) { } mockServices.TransactionService.EXPECT(). - CreateTransaction(gomock.Any(), userID, gomock.Any()). + CreateTransaction(gomock.Any(), userID.Hex(), gomock.Any()). Return(transaction, nil) req := dto.CreateTransactionRequest{ @@ -141,7 +141,7 @@ func TestCreateTransaction(t *testing.T) { body, _ := json.Marshal(req) w := httptest.NewRecorder() r := httptest.NewRequest("POST", "/transactions", bytes.NewBuffer(body)) - ctx := newContext(userID, userEmail) + ctx := newContext(userID.Hex(), userEmail) r = r.WithContext(ctx) h.CreateTransaction(w, r) @@ -187,16 +187,16 @@ func TestGetTransactions(t *testing.T) { t.Run("Service error", func(t *testing.T) { h, mockServices := newTestHandler(t) - userID := primitive.NewObjectID().Hex() + userID := primitive.NewObjectID() userEmail := "test@example.com" mockServices.TransactionService.EXPECT(). - GetTransactions(gomock.Any(), userID). + GetTransactions(gomock.Any(), userID.Hex()). Return(nil, errors.New("service error")) w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/transactions", nil) - ctx := newContext(userID, userEmail) + ctx := newContext(userID.Hex(), userEmail) r = r.WithContext(ctx) h.GetTransactions(w, r) @@ -214,7 +214,7 @@ func TestGetTransactions(t *testing.T) { t.Run("Success", func(t *testing.T) { h, mockServices := newTestHandler(t) - userID := primitive.NewObjectID().Hex() + userID := primitive.NewObjectID() userEmail := "test@example.com" now := time.Now() @@ -234,12 +234,12 @@ func TestGetTransactions(t *testing.T) { } mockServices.TransactionService.EXPECT(). - GetTransactions(gomock.Any(), userID). + GetTransactions(gomock.Any(), userID.Hex()). Return(transactions, nil) w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/transactions", nil) - ctx := newContext(userID, userEmail) + ctx := newContext(userID.Hex(), userEmail) r = r.WithContext(ctx) h.GetTransactions(w, r) diff --git a/internal/module/transaction/repository/repository.go b/internal/module/transaction/repository/repository.go index 99d18f0..68a87d4 100644 --- a/internal/module/transaction/repository/repository.go +++ b/internal/module/transaction/repository/repository.go @@ -3,6 +3,8 @@ package transaction_repository import ( "context" + "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/Financial-Partner/server/internal/entities" ) @@ -10,11 +12,11 @@ import ( type Repository interface { Create(ctx context.Context, transaction *entities.Transaction) (*entities.Transaction, error) - FindByUserId(ctx context.Context, userID string) ([]entities.Transaction, error) + FindByUserId(ctx context.Context, userID primitive.ObjectID) ([]entities.Transaction, error) } type TransactionStore interface { GetByUserId(ctx context.Context, userID string) ([]entities.Transaction, error) - SetByUserId(ctx context.Context, userID string, transactions []entities.Transaction) error DeleteByUserId(ctx context.Context, userID string) error + SetMultipleByUserId(ctx context.Context, userID string, transactions []entities.Transaction) error } diff --git a/internal/module/transaction/repository/repository_mock.go b/internal/module/transaction/repository/repository_mock.go index 20c2ca3..31780f9 100644 --- a/internal/module/transaction/repository/repository_mock.go +++ b/internal/module/transaction/repository/repository_mock.go @@ -14,6 +14,7 @@ import ( reflect "reflect" entities "github.com/Financial-Partner/server/internal/entities" + primitive "go.mongodb.org/mongo-driver/bson/primitive" gomock "go.uber.org/mock/gomock" ) @@ -57,7 +58,7 @@ func (mr *MockRepositoryMockRecorder) Create(ctx, transaction any) *gomock.Call } // FindByUserId mocks base method. -func (m *MockRepository) FindByUserId(ctx context.Context, userID string) ([]entities.Transaction, error) { +func (m *MockRepository) FindByUserId(ctx context.Context, userID primitive.ObjectID) ([]entities.Transaction, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FindByUserId", ctx, userID) ret0, _ := ret[0].([]entities.Transaction) @@ -95,6 +96,34 @@ func (m *MockTransactionStore) EXPECT() *MockTransactionStoreMockRecorder { return m.recorder } +// AddByUserId mocks base method. +func (m *MockTransactionStore) AddByUserId(ctx context.Context, userID string, transaction *entities.Transaction) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddByUserId", ctx, userID, transaction) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddByUserId indicates an expected call of AddByUserId. +func (mr *MockTransactionStoreMockRecorder) AddByUserId(ctx, userID, transaction any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddByUserId", reflect.TypeOf((*MockTransactionStore)(nil).AddByUserId), ctx, userID, transaction) +} + +// SetMultipleByUserId mocks base method. +func (m *MockTransactionStore) SetMultipleByUserId(ctx context.Context, userID string, transactions []entities.Transaction) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetMultipleByUserId", ctx, userID, transactions) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetMultipleByUserId indicates an expected call of SetMultipleByUserId. +func (mr *MockTransactionStoreMockRecorder) SetMultipleByUserId(ctx, userID, transactions any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMultipleByUserId", reflect.TypeOf((*MockTransactionStore)(nil).SetMultipleByUserId), ctx, userID, transactions) +} + // DeleteByUserId mocks base method. func (m *MockTransactionStore) DeleteByUserId(ctx context.Context, userID string) error { m.ctrl.T.Helper() @@ -123,17 +152,3 @@ func (mr *MockTransactionStoreMockRecorder) GetByUserId(ctx, userID any) *gomock mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByUserId", reflect.TypeOf((*MockTransactionStore)(nil).GetByUserId), ctx, userID) } - -// SetByUserId mocks base method. -func (m *MockTransactionStore) SetByUserId(ctx context.Context, userID string, transactions []entities.Transaction) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetByUserId", ctx, userID, transactions) - ret0, _ := ret[0].(error) - return ret0 -} - -// SetByUserId indicates an expected call of SetByUserId. -func (mr *MockTransactionStoreMockRecorder) SetByUserId(ctx, userID, transactions any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetByUserId", reflect.TypeOf((*MockTransactionStore)(nil).SetByUserId), ctx, userID, transactions) -} diff --git a/internal/module/transaction/usecase/service.go b/internal/module/transaction/usecase/service.go index f4fd091..db1a1bc 100644 --- a/internal/module/transaction/usecase/service.go +++ b/internal/module/transaction/usecase/service.go @@ -2,22 +2,90 @@ package transaction_usecase import ( "context" + "fmt" + "time" "github.com/Financial-Partner/server/internal/entities" + "github.com/Financial-Partner/server/internal/infrastructure/logger" "github.com/Financial-Partner/server/internal/interfaces/http/dto" + transaction_repository "github.com/Financial-Partner/server/internal/module/transaction/repository" + "go.mongodb.org/mongo-driver/bson/primitive" ) type Service struct { + repo transaction_repository.Repository + store transaction_repository.TransactionStore + log logger.Logger } -func NewService() *Service { - return &Service{} +func NewService(repo transaction_repository.Repository, store transaction_repository.TransactionStore, log logger.Logger) *Service { + return &Service{ + repo: repo, + store: store, + log: log, + } } func (s *Service) CreateTransaction(ctx context.Context, userID string, req *dto.CreateTransactionRequest) (*entities.Transaction, error) { - return nil, nil + transactionDate, err := time.Parse("2006-01-02", req.Date) + if err != nil { + return nil, fmt.Errorf("invalid date format: %w", err) + } + + objectID, err := primitive.ObjectIDFromHex(userID) + if err != nil { + return nil, fmt.Errorf("invalid user ID: %w", err) + } + + // Convert DTO to Entity + transaction := &entities.Transaction{ + UserID: objectID, + Amount: req.Amount, + Category: req.Category, + Type: req.Type, + Date: transactionDate.UTC(), + Description: req.Description, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + } + + // Call the repository to persist the transaction + createdTransaction, err := s.repo.Create(ctx, transaction) + if err != nil { + return nil, fmt.Errorf("failed to create transaction: %w", err) + } + + cacheErr := s.store.DeleteByUserId(ctx, userID) + if cacheErr != nil { + s.log.Warnf("Failed to delete transaction cache for userID %s: %v", userID, cacheErr) + } + + return createdTransaction, nil } func (s *Service) GetTransactions(ctx context.Context, userID string) ([]entities.Transaction, error) { - return nil, nil + objectID, err := primitive.ObjectIDFromHex(userID) + if err != nil { + return nil, fmt.Errorf("invalid user ID: %w", err) + } + // Check if transactions are cached + cachedTransactions, err := s.store.GetByUserId(ctx, userID) + if err != nil && err.Error() != "redis: nil" { + return nil, fmt.Errorf("failed to get cached transactions: %w", err) + } + if cachedTransactions != nil { + return cachedTransactions, nil + } + // If not cached, fetch from the repository + transactions, err := s.repo.FindByUserId(ctx, objectID) + if err != nil { + return nil, fmt.Errorf("failed to get transactions: %w", err) + } + + // Cache the fetched transactions + cacheErr := s.store.SetMultipleByUserId(ctx, userID, transactions) + if cacheErr != nil { + s.log.Warnf("Failed to cache transaction for userID %s: %v", userID, cacheErr) + } + return transactions, nil }