Solution/priyank patel#104
Open
priiyank-xd wants to merge 3 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Implements a minimal wallet-to-wallet transfer service (POST /transfers) backed by Postgres, focusing on idempotency, double-entry ledger writes, and concurrency safety via row locks. The PR also adds migrations/seed data, local dev tooling (docker-compose + Makefile), and integration-style tests for handler/service/domain validation.
Changes:
- Added Postgres schema + seed migrations for wallets/transfers/ledger/idempotency records.
- Implemented handler/service/repository/domain layers for creating transfers with idempotency + ledger entries and concurrency locking.
- Added integration tests and local development tooling/docs to run the service and validate behavior.
Reviewed changes
Copilot reviewed 28 out of 30 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
TEST_CASES.md |
Test plan documenting expected validation/idempotency/ledger/concurrency behaviors. |
PR_DESCRIPTION.md |
Solution write-up covering schema, idempotency, concurrency, and run/test instructions. |
migrations/000001_init_schema.up.sql |
Creates core tables/constraints/indexes for wallets, transfers, ledger entries, idempotency records. |
migrations/000001_init_schema.down.sql |
Drops tables in dependency-safe order. |
migrations/000002_seed_wallets.up.sql |
Seeds wallet-alice and wallet-bob balances. |
migrations/000002_seed_wallets.down.sql |
Removes seeded wallets. |
Makefile |
Adds db-up/down/logs and migration helper targets. |
internal/testutil/db.go |
Test DB helper for pooling, advisory lock, reset, and assertions helpers. |
internal/service/transfer_service.go |
Core transfer workflow: validate, idempotency lookup, tx, wallet locks, status/ledger/balance updates. |
internal/service/transfer_service_test.go |
Service-level integration tests for validation, ledger, idempotency, and concurrency behavior. |
internal/repository/wallet_repository.go |
Wallet lock + balance update SQL methods. |
internal/repository/transfer_repository.go |
Transfer/idempotency/ledger persistence methods. |
internal/repository/transaction.go |
Transaction wrapper helper (WithTx). |
internal/repository/repository.go |
Repository struct + constructor. |
internal/repository/errors.go |
Repository error constants. |
internal/handler/transfer_handler.go |
HTTP endpoint wiring + request decode + response/status mapping. |
internal/handler/transfer_handler_test.go |
Handler integration tests covering success and key error paths. |
internal/domain/wallet.go |
Wallet domain model. |
internal/domain/transfer.go |
Transfer models + request validation. |
internal/domain/transfer_test.go |
Unit tests for request validation rules. |
internal/domain/status.go |
Transfer status enum. |
internal/domain/ledger.go |
Ledger entry model + entry type enum. |
internal/domain/errors.go |
Domain error definitions. |
internal/config/config.go |
Runtime config loading for DB URL and HTTP port. |
go.mod |
Defines module and Go/toolchain version + dependencies. |
go.sum |
Dependency checksums. |
docker-compose.yml |
Local Postgres + migrate service for running migrations. |
cmd/server/main.go |
Service wiring + HTTP server + graceful shutdown. |
.gitignore |
Expanded ignore patterns for local dev artifacts and tooling. |
.env.example |
Example environment variables for local runs. |
| @@ -0,0 +1,16 @@ | |||
| module github.com/priiyank-xd/wallet-transfer-service | |||
|
|
|||
| go 1.25.0 | |||
|
|
||
| Requirements: | ||
| - Docker | ||
| - Go 1.25+ |
| return s.processTransfer(ctx, req) | ||
| } | ||
|
|
||
| func (s *TransferService) checkIdemPotencyRecord(ctx context.Context, key string) (domain.TransferResponse, int, bool, error) { |
| return resp, http.StatusBadRequest, err | ||
| } | ||
|
|
||
| stored, status, ok, err := s.checkIdemPotencyRecord(ctx, req.IdempotencyKey) |
| } | ||
|
|
||
| func (s *TransferService) loadStoredResponse(ctx context.Context, key string) (domain.TransferResponse, int, error) { | ||
| if resp, status, ok, err := s.checkIdemPotencyRecord(ctx, key); err != nil { |
Comment on lines
+64
to
+69
| if _, err := pool.Exec(ctx, | ||
| `UPDATE wallets SET balance = $1, updated_at = NOW() WHERE id = $2`, | ||
| AliceSeedBalance, WalletAlice, | ||
| ); err != nil { | ||
| t.Fatalf("reset alice balance: %v", err) | ||
| } |
Comment on lines
+70
to
+75
| if _, err := pool.Exec(ctx, | ||
| `UPDATE wallets SET balance = $1, updated_at = NOW() WHERE id = $2`, | ||
| BobSeedBalance, WalletBob, | ||
| ); err != nil { | ||
| t.Fatalf("reset bob balance: %v", err) | ||
| } |
Comment on lines
+81
to
+87
| _, err := pool.Exec(context.Background(), | ||
| `UPDATE wallets SET balance = $2, updated_at = NOW() WHERE id = $1`, | ||
| walletID, balance, | ||
| ) | ||
| if err != nil { | ||
| t.Fatalf("set wallet balance: %v", err) | ||
| } |
Comment on lines
+36
to
+41
| func (r *Repository) UpdateBalance(ctx context.Context, tx pgx.Tx, walletID string, delta int64) error { | ||
| query := `UPDATE wallets SET balance = balance + $2, updated_at = NOW() WHERE id = $1` | ||
|
|
||
| _, err := tx.Exec(ctx, query, walletID, delta) | ||
| return err | ||
| } |
Comment on lines
+45
to
+52
| func (r *Repository) FindIdempotencyRecord(ctx context.Context, key string) ([]byte, int, error) { | ||
| query := `SELECT response_body, http_status FROM idempotency_records WHERE idempotency_key = $1` | ||
|
|
||
| var body []byte | ||
| var status int | ||
|
|
||
| err := r.pool.QueryRow(ctx, query, key).Scan(&body, &status) | ||
| if errors.Is(err, pgx.ErrNoRows) { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
.