This repository contains the coding test for evaluating essential skills and approaches to solving typical project challenges. Itโs intended to give insight into practical coding practices and solution quality, though itโs still far from perfect.
Disclaimer: The
config/master.key
file is intentionally removed from.gitignore
for coding test purposes only.
Ruby 3.3.5
Rails 7.2.1
PostgreSQL v13.x (minimal)
$ bundle install
$ bin/rails db:setup
Created database 'internal_wallet_api_development'
Created database 'internal_wallet_api_test'
It automatically seeds the data into the database.
$ bin/rails rspec
Finished in 1.58 seconds (files took 1.69 seconds to load)
48 examples, 0 failures
$ bin/rails s
=> Booting Puma
=> Rails 7.2.1 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 6.4.3 (ruby 3.3.5-p100) ("The Eagle of Durango")
* Min threads: 3
* Max threads: 3
* Environment: development
* PID: 1697027
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop
https://documenter.getpostman.com/view/8596816/2sAXxV4ozw
Request
curl --location 'http://localhost:3000/sign-in' \
--header 'Content-Type: application/json' \
--data-raw '{
"session": {
"email": "johndoe@gmail.com",
"password": "secure"
}
}'
Response (success)
{
"response": {
"status": 201,
"message": "Created",
"url": "http://localhost:3000/sign-in"
},
"data": {
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHBpcmVkX2F0IjoxNzI5MTQxMjQ4fQ._Reui0lLloWIxKRF4WCIXuRhkWjJazztGSr3pn6EK_8",
"token_expired_at": {
"format_datetime": "2024-10-17T05:00:48.276Z",
"format_integer": 1729141248
}
}
}
Response (failed)
{
"response": {
"status": 401,
"message": "Unauthorized",
"url": "http://localhost:3000/sign-in"
},
"data": {
"error": "Invalid email or password"
}
}
Request
curl --location --request DELETE 'http://localhost:3000/sign-out' \
--header 'Content-Type: application/json'
Response (success)
204 No Content
Response (failed)
{
"response": {
"status": 401,
"message": "Unauthorized",
"url": "http://localhost:3000/sign-out"
},
"data": {
"errors": "Unauthorized"
}
}
Request
curl --location 'http://localhost:3000/wallet' \
--header 'Content-Type: application/json'
Response (success)
{
"response": {
"status": 200,
"message": "OK",
"url": "http://localhost:3000/wallet"
},
"data": {
"id": 1,
"balance": 300
}
}
Request
curl --location 'http://localhost:3000/wallet/teams-balance' \
--header 'Content-Type: application/json'
Response (success)
{
"response": {
"status": 200,
"message": "OK",
"url": "http://localhost:3000/wallet/teams-balance"
},
"data": [
{
"id": 2,
"updated_at": "2024-10-16T05:11:52.230Z",
"name": "Gresini",
"balance": 150
}
]
}
Request
curl --location 'http://localhost:3000/wallet/stocks-balance' \
--header 'Content-Type: application/json'
Response (success)
{
"response": {
"status": 200,
"message": "OK",
"url": "http://localhost:3000/wallet/stocks-balance"
},
"data": [
{
"id": 3,
"updated_at": "2024-10-14T18:45:00.044Z",
"name": "Tesla, Inc.",
"symbol": "TSLA",
"balance": 1000
}
]
}
Request
curl --location 'http://localhost:3000/wallet/deposit' \
--header 'Content-Type: application/json' \
--data '{
"wallet": {
"amount": 150.0
}
}'
Response (success)
{
"response": {
"status": 200,
"message": "OK",
"url": "http://localhost:3000/wallet/deposit"
},
"data": {
"message": "Deposit successful",
"amount": 150,
"balance": 450
}
}
Request
curl --location 'http://localhost:3000/wallet/withdraw' \
--header 'Content-Type: application/json' \
--data '{
"wallet": {
"amount": 100.0
}
}'
Response (success)
{
"response": {
"status": 200,
"message": "OK",
"url": "http://localhost:3000/wallet/withdraw"
},
"data": {
"message": "Withdrawal successful",
"amount": 100,
"balance": 350
}
}
Request
curl --location 'http://localhost:3000/wallet/transfer' \
--header 'Content-Type: application/json' \
--data '{
"transfer": {
"target_type": "Team", // User, Team, Stock
"target_id": 1,
"amount": 50.0
}
}'
Response (success)
{
"response": {
"status": 200,
"message": "OK",
"url": "http://localhost:3000/wallet/transfer"
},
"data": {
"message": "Transfer successful",
"amount": 50,
"balance": 300
}
}
Response (failed)
{
"response": {
"status": null,
"message": null,
"url": "http://localhost:3000/wallet/transfer"
},
"data": {
"error": "Insufficient balance"
}
}
Request
curl --location 'http://localhost:3000/transactions?type=&_order=' \
--header 'Content-Type: application/json'
Response (success)
{
"response": {
"status": 200,
"message": "OK",
"url": "http://localhost:3000/transactions?type=&_order="
},
"data": [
{
"id": 25,
"created_at": "2024-10-16T05:11:52.259Z",
"type": "CreditTransaction",
"source_wallet": null,
"target_wallet": "Gresini",
"amount": 50
},
{
"id": 24,
"created_at": "2024-10-16T05:11:52.219Z",
"type": "DebitTransaction",
"source_wallet": "John Doe",
"target_wallet": null,
"amount": 50
},
{
"id": 23,
"created_at": "2024-10-16T05:11:22.966Z",
"type": "CreditTransaction",
"source_wallet": null,
"target_wallet": "John Doe",
"amount": 350
},
...
]
}
- Based on relationships every entity e.g. User, Team, Stock or any other should have their own defined "wallet" to which we could transfer money or withdraw.
- Every request for credit/debit (deposit or withdraw) should be based on records in database for given model.
- Every instance of a single transaction should have proper validations against required fields and their source and targetwallet, e.g. from who we are taking money and transferring to whom? (Credits == source wallet == nil, Debits == targetwallet == nil).
- Each record should be created in database transactions to comply with ACID standards.
- Balance for given entity (User, Team, Stock) should be calculated by summing records.
- Architect generic wallet solution (money manipulation) between entities (User, Stock, Team or any other)
- Create model relationships and validations for achieving proper calculations of every wallet, transactions
- Use STI (or any other design pattern) for proper money manipulation
- Apply your own sign in (new session solution, no sign up is needed) without any external gem
- Create a LatestStockPrice library (in lib folder in "gem style") for "price", "prices" and "price_all" endpoints - https://rapidapi.com/suneetk92/api/latest-stock-price