diff --git a/deploy/wallet/development/config.yml b/deploy/wallet/development/config.yml new file mode 100644 index 00000000..e69de29b diff --git a/deploy/wallet/development/docker-compose.yml b/deploy/wallet/development/docker-compose.yml new file mode 100644 index 00000000..e69de29b diff --git a/deploy/wallet/production/.gitkeep b/deploy/wallet/production/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/deploy/wallet/stage/.gitkeep b/deploy/wallet/stage/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/domain/wallet/entity/transaction.go b/domain/wallet/entity/transaction.go new file mode 100644 index 00000000..09c98888 --- /dev/null +++ b/domain/wallet/entity/transaction.go @@ -0,0 +1,28 @@ +package entity + +import "time" + +type Transaction struct { + ID uint64 + UserID uint64 + Amount float64 + Currency Currency + ActionType TransactionType + Timestamp time.Time +} + +type TransactionType string + +const ( + TransactionTypeDeposit TransactionType = "deposit" + TransactionTypeWithdraw TransactionType = "withdraw" + TransactionTypeRefund TransactionType = "refund" + TransactionTypeDonate TransactionType = "donate" +) + +type Currency string + +const ( + IRR Currency = "IRR" + USD Currency = "USD" +) diff --git a/domain/wallet/entity/wallet.go b/domain/wallet/entity/wallet.go new file mode 100644 index 00000000..175a638d --- /dev/null +++ b/domain/wallet/entity/wallet.go @@ -0,0 +1,23 @@ +package entity + +import "time" + +type Wallet struct { + ID uint64 + UserID uint64 // user unique ID + Balance float64 + Currency Currency + UpdatedAt time.Time + Status WalletStatus // "active", "frozen", "closed" +} + +type WalletStatus string + +const ( + Frozen WalletStatus = "frozen" // when need to check , approve ,validate , solve sth (but deposit is possible) + Active WalletStatus = "active" // when everything is ok + + // ?? + // Closed WalletStatus = "closed" // when need to check , approve ,validate , solve sth (exp : security problem) + +) diff --git a/domain/wallet/param/create_transaction.go b/domain/wallet/param/create_transaction.go new file mode 100644 index 00000000..1341ed46 --- /dev/null +++ b/domain/wallet/param/create_transaction.go @@ -0,0 +1,15 @@ +package param + +import ( + "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" +) + +type CreateTransactionRequest struct { + UserID uint64 `json:"user_id"` + Amount float64 `json:"amount"` + Currency entity.Currency `json:"currency"` + ActionType entity.TransactionType `json:"action_type"` +} + +type InsertTransactionResponse struct { +} diff --git a/domain/wallet/param/transaction_history.go b/domain/wallet/param/transaction_history.go new file mode 100644 index 00000000..fe720528 --- /dev/null +++ b/domain/wallet/param/transaction_history.go @@ -0,0 +1,24 @@ +package param + +import ( + "time" + + "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" +) + +type TransactionRequest struct { + UserID uint64 `json:"user_id"` +} + +type TransactionResponse struct { + Transaction []TransactionInfo `json:"transaction"` +} + +type TransactionInfo struct { + ID uint64 `json:"id"` + UserID uint64 `json:"user_id"` + Amount float64 `json:"amount"` + Currency entity.Currency `json:"currency"` + ActionType entity.TransactionType `json:"action_type"` + Timestamp time.Time `json:"timestamp"` +} diff --git a/domain/wallet/param/wallet_info.go b/domain/wallet/param/wallet_info.go new file mode 100644 index 00000000..c71962a5 --- /dev/null +++ b/domain/wallet/param/wallet_info.go @@ -0,0 +1,21 @@ +package param + +import ( + "time" + + "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" +) + +type WalletRequest struct { + UserID uint64 `json:"user_id"` +} + +type WalletResponse struct { + Wallet WalletInfo `json:"wallet"` +} + +type WalletInfo struct { + Balance float64 `json:"balance"` + UpdatedAt time.Time `json:"updated_at"` + Status entity.WalletStatus `json:"status"` +} diff --git a/domain/wallet/repository/postgres/db.go b/domain/wallet/repository/postgres/db.go new file mode 100644 index 00000000..787efa54 --- /dev/null +++ b/domain/wallet/repository/postgres/db.go @@ -0,0 +1,11 @@ +package postgres + +import "git.gocasts.ir/ebhomengo/niki/pkg/database/postgres" + +type DB struct { + conn *postgres.DB +} + +func New(conn *postgres.DB) *DB { + return &DB{conn: conn} +} diff --git a/domain/wallet/repository/postgres/dbconfig.yml b/domain/wallet/repository/postgres/dbconfig.yml new file mode 100644 index 00000000..af051ab0 --- /dev/null +++ b/domain/wallet/repository/postgres/dbconfig.yml @@ -0,0 +1,5 @@ +production: + dialect: postgres + datasource: "host=127.0.0.1 port=5432 user=wallet password=wallet2123 dbname=wallet_db sslmode=disable" + dir: domain/wallet/repository/postgres/migrations + table: wallet_migrationsns \ No newline at end of file diff --git a/domain/wallet/service/create_transaction.go b/domain/wallet/service/create_transaction.go new file mode 100644 index 00000000..cfd98ac6 --- /dev/null +++ b/domain/wallet/service/create_transaction.go @@ -0,0 +1,31 @@ +package service + +import ( + "context" + "time" + + "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" + "git.gocasts.ir/ebhomengo/niki/domain/wallet/param" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" +) + +func (s Service) CreateTransaction(ctx context.Context, request param.CreateTransactionRequest) (param.InsertTransactionResponse, error) { + + const op = richerror.Op("wallet.service.CreateTransaction") + + transaction := entity.Transaction{ + ID: 0, + UserID: request.UserID, + Amount: request.Amount, + Currency: request.Currency, + ActionType: request.ActionType, + Timestamp: time.Now(), + } + err := s.repo.InsertTransaction(ctx, transaction) + if err != nil { + return param.InsertTransactionResponse{}, err + } + + return param.InsertTransactionResponse{}, nil + +} diff --git a/domain/wallet/service/service.go b/domain/wallet/service/service.go new file mode 100644 index 00000000..3f949184 --- /dev/null +++ b/domain/wallet/service/service.go @@ -0,0 +1,27 @@ +package service + +import ( + "context" + + "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" +) + +type Repository interface { + GetTransactionListByUserID(ctx context.Context, UserID uint64) ([]entity.Transaction, error) + GetWalletByUserID(ctx context.Context, UserID uint64) (entity.Wallet, error) + InsertTransaction(ctx context.Context, transaction entity.Transaction) error +} + +type Config struct { +} + +type Service struct { + repo Repository + cfg Config +} + +func New(repo Repository, cfg Config) Service { + + return Service{repo: repo, cfg: cfg} + +} diff --git a/domain/wallet/service/transaction_history.go b/domain/wallet/service/transaction_history.go new file mode 100644 index 00000000..941b8da5 --- /dev/null +++ b/domain/wallet/service/transaction_history.go @@ -0,0 +1,39 @@ +package service + +import ( + "context" + + "git.gocasts.ir/ebhomengo/niki/domain/wallet/entity" + "git.gocasts.ir/ebhomengo/niki/domain/wallet/param" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" +) + +func (s Service) GetUserTransactionHistory(ctx context.Context, request param.TransactionRequest) (param.TransactionResponse, error) { + const op = richerror.Op("wallet.service.GetUserTransactionHistory") + + transactionList, err := s.repo.GetTransactionListByUserID(ctx, request.UserID) + + if err != nil { + return param.TransactionResponse{}, err + } + + return param.TransactionResponse{Transaction: transactionEntityToTransactionInfo(transactionList)}, nil + +} + +func transactionEntityToTransactionInfo(TransactionList []entity.Transaction) []param.TransactionInfo { + transactionInfoList := make([]param.TransactionInfo, len(TransactionList)) + for i, transaction := range TransactionList { + transactionInfoList[i] = param.TransactionInfo{ + ID: transaction.ID, + UserID: transaction.UserID, + Amount: transaction.Amount, + Currency: transaction.Currency, + ActionType: transaction.ActionType, + Timestamp: transaction.Timestamp, + } + } + + return transactionInfoList + +} diff --git a/domain/wallet/service/user_wallet.go b/domain/wallet/service/user_wallet.go new file mode 100644 index 00000000..12ac49b7 --- /dev/null +++ b/domain/wallet/service/user_wallet.go @@ -0,0 +1,27 @@ +package service + +import ( + "context" + + "git.gocasts.ir/ebhomengo/niki/domain/wallet/param" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" +) + +func (s Service) GetUserWallet(ctx context.Context, request param.WalletRequest) (param.WalletResponse, error) { + const op = richerror.Op("wallet.service.GetUserWallet") + + wallet, err := s.repo.GetWalletByUserID(ctx, request.UserID) + + if err != nil { + return param.WalletResponse{}, err + } + + return param.WalletResponse{ + Wallet: param.WalletInfo{ + Balance: wallet.Balance, + UpdatedAt: wallet.UpdatedAt, + Status: wallet.Status, + }, + }, nil + +} diff --git a/pkg/database/postgres/db.go b/pkg/database/postgres/db.go new file mode 100644 index 00000000..b843be3d --- /dev/null +++ b/pkg/database/postgres/db.go @@ -0,0 +1,97 @@ +package postgres + +import ( + "context" + "database/sql" + "fmt" + "sync" + "time" + + querier "git.gocasts.ir/ebhomengo/niki/pkg/query_transaction/sql" + _ "github.com/jackc/pgx/v5/stdlib" +) + +type Config struct { + Host string `koanf:"host"` + Port int `koanf:"port"` + User string `koanf:"user"` + Password string `koanf:"password"` + DbName string `koanf:"dbName"` + SSLMode string `koanf:"sslMode"` + MaxIdleConn int `koanf:"maxIdleConns"` + MaxOpenConn int `koanf:"maxOpenConns"` + ConnMaxLifetime int `koanf:"connMaxLifetime"` + PathOfMigrations string `koanf:"pathOfMigrations"` +} + +type DB struct { + config Config + db *querier.SQLDB + mu sync.Mutex + statements map[statementKey]*sql.Stmt +} + +func (db *DB) Conn() *querier.SQLDB { + return db.db +} + +func New(config Config) *DB { + dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", + config.Host, config.Port, config.User, config.Password, config.DbName, config.SSLMode) + + db, err := sql.Open("pgx", dsn) + if err != nil { + panic(fmt.Errorf("can't open postgres db: %w", err)) + } + + maxIdle := config.MaxIdleConn + + maxOpen := config.MaxOpenConn + + lifetime := time.Duration(config.ConnMaxLifetime) * time.Second + + db.SetMaxIdleConns(maxIdle) + db.SetMaxOpenConns(maxOpen) + db.SetConnMaxLifetime(lifetime) + + return &DB{ + config: config, + db: &querier.SQLDB{DB: db}, + statements: make(map[statementKey]*sql.Stmt), + } +} + +func (db *DB) PrepareStatement(ctx context.Context, key statementKey, query string) (*sql.Stmt, error) { + db.mu.Lock() + defer db.mu.Unlock() + + if stmt, ok := db.statements[key]; ok { + return stmt, nil + } + + stmt, err := db.db.PrepareContext(ctx, query) + if err != nil { + return nil, fmt.Errorf("prepare statement %q: %w", key, err) + } + db.statements[key] = stmt + + return stmt, nil +} + +func (db *DB) CloseStatements() error { + db.mu.Lock() + defer db.mu.Unlock() + + var lastErr error + for key, stmt := range db.statements { + if err := stmt.Close(); err != nil { + lastErr = err + } + delete(db.statements, key) + } + return lastErr +} + +func (db *DB) Close() error { + return db.db.DB.Close() +} diff --git a/pkg/database/postgres/migrator/migrator.go b/pkg/database/postgres/migrator/migrator.go new file mode 100644 index 00000000..0cbadd99 --- /dev/null +++ b/pkg/database/postgres/migrator/migrator.go @@ -0,0 +1 @@ +package migrator diff --git a/pkg/database/postgres/prepared_statement.go b/pkg/database/postgres/prepared_statement.go new file mode 100644 index 00000000..cebc2703 --- /dev/null +++ b/pkg/database/postgres/prepared_statement.go @@ -0,0 +1,9 @@ +package postgres + +type statementKey uint + +const ( + StatementKeyAWalletGetTransactionHistory statementKey = iota + 1 + StatementKeyWalletInsertTransaction + StatementKeyWalletGetUserWallet +) diff --git a/walletapp/app.go b/walletapp/app.go new file mode 100644 index 00000000..c2f98f84 --- /dev/null +++ b/walletapp/app.go @@ -0,0 +1,12 @@ +package walletapp + +type Application struct { +} + +func SetUp() { + +} + +func (app Application) StartServer() {} + +func (app Application) StopServer() {} diff --git a/walletapp/config.go b/walletapp/config.go new file mode 100644 index 00000000..a011ca94 --- /dev/null +++ b/walletapp/config.go @@ -0,0 +1,15 @@ +package walletapp + +import ( + "git.gocasts.ir/ebhomengo/niki/adapter/redis" + "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/repository" + "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" + logger "git.gocasts.ir/ebhomengo/niki/pkg/logger" +) + +type Config struct { + Redis redis.Config `koanf:"redis" json:"redis"` + Repo repository.Config `koanf:"repo" json:"repo"` + HTTPServer httpserver.Config `koanf:"http_server" json:"http_server"` + Logger logger.Config `koanf:"logger" json:"logger"` +} diff --git a/walletapp/delivery/httpserver/health_check.go b/walletapp/delivery/httpserver/health_check.go new file mode 100644 index 00000000..b482627c --- /dev/null +++ b/walletapp/delivery/httpserver/health_check.go @@ -0,0 +1 @@ +package httpserver diff --git a/walletapp/delivery/httpserver/server.go b/walletapp/delivery/httpserver/server.go new file mode 100644 index 00000000..b482627c --- /dev/null +++ b/walletapp/delivery/httpserver/server.go @@ -0,0 +1 @@ +package httpserver diff --git a/walletapp/delivery/httpserver/transaction/handler.go b/walletapp/delivery/httpserver/transaction/handler.go new file mode 100644 index 00000000..06192075 --- /dev/null +++ b/walletapp/delivery/httpserver/transaction/handler.go @@ -0,0 +1 @@ +package transaction