{"id":26220611,"url":"https://github.com/alfahami/crypto-portfolio","last_synced_at":"2026-04-24T06:33:33.941Z","repository":{"id":274520243,"uuid":"923178611","full_name":"alfahami/crypto-portfolio","owner":"alfahami","description":"A simple Spring Boot RESTful api to track your crypto holdings, view real-time market data, calculate portfolio valuation, and manage your investments.","archived":false,"fork":false,"pushed_at":"2025-03-11T10:56:19.000Z","size":262,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-27T16:02:55.051Z","etag":null,"topics":["mockwebserver","projectreactor","reactive-programming","rest-api","spring-boot","webclient-springboot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alfahami.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-27T19:15:57.000Z","updated_at":"2025-06-10T12:08:22.000Z","dependencies_parsed_at":"2025-01-27T20:31:21.950Z","dependency_job_id":"5646c7e9-1d9b-424d-a465-78d7e27903e2","html_url":"https://github.com/alfahami/crypto-portfolio","commit_stats":null,"previous_names":["alfahami/crypto-portfolio-api","alfahami/crypto-portfolio"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/alfahami/crypto-portfolio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alfahami%2Fcrypto-portfolio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alfahami%2Fcrypto-portfolio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alfahami%2Fcrypto-portfolio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alfahami%2Fcrypto-portfolio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alfahami","download_url":"https://codeload.github.com/alfahami/crypto-portfolio/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alfahami%2Fcrypto-portfolio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32212807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T03:15:14.334Z","status":"ssl_error","status_checked_at":"2026-04-24T03:15:11.608Z","response_time":64,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mockwebserver","projectreactor","reactive-programming","rest-api","spring-boot","webclient-springboot"],"created_at":"2025-03-12T15:18:24.268Z","updated_at":"2026-04-24T06:33:33.922Z","avatar_url":"https://github.com/alfahami.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Crypto Portfolio API  \n\nBuilt with **Spring Boot** and **WebClient** for seamless integration with external APIs.  \n\n## Overview  \nThe **Crypto Portfolio API** allows users to manage portfolios of cryptocurrency holdings.\nUsers can create multiple portfolios, add holdings to each portfolio, and track their valuations in different currencies. \n\nThe solution integrates with an exchange rate service running on port `8801` and provides portfolio management endpoints on port `8080`.\n\nThe API consists of two main services:  \n\n- **Portfolio Service** – Manages users, portfolios, and holdings.  \n- **Exchange Rate Service** – Provides cryptocurrency exchange rates using [CoinMarketCap](https://coinmarketcap.com/api/documentation/v1/#section/Quick-Start-Guide).  \n\n### ⚠ Architecture Note  \nWhile the application follows a modular approach and runs services on separate ports, it does not fully implement a **microservices architecture** due to limited experience in that area.  \n\n---\n\n## Base URLs  \n- **Portfolio Service**: `http://localhost:8080`  \n- **Exchange Rate Service**: `http://localhost:8081`  \n\n---\n\n## Authentication  \nThis API does not currently implement authentication.\\\nIn a production environment, appropriate authentication mechanisms (e.g., JWT, OAuth2) should be integrated.  \n\n---\n\n## Development Challenge  \nBuilding this application involved:\n\n### ExchangerateService\n- Exploring **reactive programming** concepts using `WebClient` to call an external API.  \n- Writing **unit tests** and **integration tests** using `WebTestClient`.  \n- Learning how to **simulate or integrate** external APIs using `MockWebServer` form [okhttp](https://github.com/square/okhttp) (this implementation uses **[CoinMarketCap](https://coinmarketcap.com/api/documentation/v1/#section/Quick-Start-Guide)**, a real crypto API). \n\n### **PortfolioService**\n- **Exploring JPA \u0026 Hibernate** for efficient data persistence and entity relationships.  \n- **Designing RESTful APIs** with proper CRUD operations for managing portfolios.  \n- **Implementing transaction management** to ensure atomic updates.  \n- **Integrating with ExchangeRateService** to retrieve real-time crypto prices.  \n- **Writing unit and integration tests** using `JUnit`, `Mockito`, and `Spring Boot Test`.  \n- **Implementing global exception handling** using `@ControllerAdvice` to catch and handle errors gracefully.  \n- **Custom error responses** for better clarity and user experience when exceptions occur in the API.\n  \n### General Challenges  \n\n#### **1. Validation and Authorization**  \nWhen managing holdings, I needed to validate the user and ensure the relationship **user → portfolio → holding** was respected.  \n\n##### **How I Ensured a User Manages Their Own Holdings**  \nTo guarantee that a user only manages holdings within their own portfolio, I had to design a validation mechanism while avoiding circular dependencies and respecting the **Single Responsibility Principle (SRP)**.  \n\n##### **Approaches I Tried:**  \n- **Injecting `UserRepository` directly into `HoldingService`**:  \n  - I used this approach solely for validation.  \n- **Injecting `PortfolioServiceImp` into `HoldingService`**:  \n  - I initially considered this, but it led to tight coupling between services.  \n- **Creating a `ValidationAuthorizationService`**:  \n  - I introduced a dedicated service to handle validation.  \n  - However, this required injecting both `UserRepository` and `PortfolioRepository`, which is practically the same as the last one I sticked with.\n\n#### **2. Preventing ID Tampering**  \nWhen updating an object, I wanted to prevent the risk of an ID mismatch between the URI and the request body.  \n\n##### **My Solution:** \n- I initially tried to ignore the ID in the request body by setting the updated object's ID to match the retrieved one. However, I found it more appropriate to simply update the retrieved object and save it.\n- I ignored the ID from the request body.  \n- I retrieved the entity using the ID from the URI.  \n- I performed validation and applied updates only to the retrieved object.  \n\n#### **3. Managing `MockWebServer` Between Tests**  \nFor end-to-end testing, I used `MockWebServer` to simulate external services, such as an **exchange rate service**.  \n\n##### **Best Practices I Followed:**  \n- **Using `@BeforeAll` and `@AfterAll`**:  \n  - I started the server once before all tests and shut it down afterward to prevent unnecessary restarts.  \n- **Handling Requests Properly:**  \n  - I made sure each test enqueued new responses to maintain predictable behavior.  \n- **Avoiding Unfinished Requests:**  \n  - Since only one test relied on external service in portfolio service, I didn’t need to manually clear pending requests in the mocked server.  \n---\n\n## Build Instructions  \n\n1. **Clone this repository**:  \n   ```bash\n   git clone git@github.com:alfahami/crypto-portfolio-api.git\n   cd crypto-portfolio-api\n   ```\n\n2. **Build the Exchange Rate Service**:  \n   ```bash\n   cd exchangerateservice\n   mvn clean install\n   ```\n\n3. **Build the Portfolio Service**:  \n   ```bash\n   cd portfolioservice\n   mvn clean install\n   ```\n---\n\n## Run Instructions  \n\n1. **Run the Exchange Rate Service**:  \n   ```bash\n   # Terminal 1:\n   cd exchangerateservice\n   mvn spring-boot:run\n   ```\n\n2. **Run the Portfolio Service**:  \n   ```bash\n   # Terminal 2:\n   cd portfolioservice\n   mvn spring-boot:run\n   ```\n\n## Running Tests\n\nTo ensure the correctness of the services, you can run all unit and integration tests using the following commands.\n\n### 1. Run All Tests for ExchangeRateService\n```bash\ncd exchangerateservice\nmvn test\n```\n\n### 2. Run All Tests for PortfolioService\n```bash\ncd portfolioservice\nmvn test\n```\n---\n\n## Functional Requirements  \n\n### Exchange Rate Service  \n**Retrieve Crypto Prices**  \n- A REST endpoint that returns the current price for given crypto symbols (e.g., BTC, ETH) in a base currency (e.g., USD).   \n\n**Endpoints**  \n- `GET /exchange-rate?symbol={symbol}\u0026base={base}` – Returns the current or last known price in the given base currency.  \n\n### Portfolio Service  \n**Manage Portfolios**  \n- Users can create multiple portfolios and add crypto holdings.  \n- CRUD operations: create, update, delete portfolios and holdings by its symbol.  \n\n**Portfolio Valuation**  \n- Retrieves the total value of a portfolio in a specified base currency (e.g., USD).  \n- Calls the **Exchange Rate Service** to fetch the latest exchange rates.  \n\n**Endpoints**  \n- `POST /portfolios` – Create a new portfolio.  \n- `GET /portfolios/{id}` – Get portfolio details (including holdings).  \n- `POST /portfolios/{id}/holdings` – Add a holding.  \n- `DELETE /portfolios/{id}/holdings/{symbol}` – Remove a holding.  \n- `GET /portfolios/{id}/valuation?base={base}` – Get total portfolio value in the given currency.  \n\n---\n\n## Technical Requirements  \n\n- **Backend**: Java 17, Spring Boot 3.4.1  \n- **Build Tool**: Maven  \n- **Database**: H2 (in-memory) *(could be replaced with PostgreSQL, MySQL, etc.)*  \n- **Persistence**: Hibernate/JPA  \n- **API Communication**: REST (JSON format)  \n- **Testing**:  \n  - Unit Tests (`JUnit`, `Mockito`)  \n  - Integration Tests (`WebTestClient`, `MockMvc`)  \n- **Documentation**:  \n  - API Documentation: **[View Full API Documentation](#api-documentation)**  \n  - **Postman collection generated** from the application and can be found in the file [crypto-portfolio.postman_collection.json](./crypto-portfolio.postman_collection.json).\n\n---\n\n## API Documentation  \n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand for detailed API documentation\u003c/summary\u003e\n  \n\nThis section provides details endpoints, descriptions, request methods, and sample payloads of the **Crypto Portfolio API** \n\n### 1. Exchange Rate API\n\n#### 1.1 Get Latest Exchange Rates\n**Endpoint:** `GET /exchange-rate/latest`\n- Retrieves the latest exchange rates for supported cryptocurrencies.\n\n**Request Example:**\n```http\nGET http://localhost:8081/exchange-rate/latest\n```\n\n#### 1.2 Get Last Price for a Specific Symbol\n**Endpoint:** `GET /exchange-rate?symbol={symbol}\u0026base={base}`\n- Retrieves the latest exchange rate for a specific cryptocurrency.\n\n**Request Example:**\n```http\nGET http://localhost:8081/exchange-rate?symbol=BTC\u0026base=MAD\n```\n---\n\n### 2. User Management\n\n#### 2.1 Create User\n**Endpoint:** `POST /users`\n- Creates a new user.\n\n**Request Example:**\n```json\n{\n  \"firstName\": \"Tupac\",\n  \"lastName\": \"Amaru\",\n  \"birthDate\": \"1992-04-29\",\n  \"profession\": \"Producer\"\n}\n```\n#### 2.2 Retrieve User\n**Endpoint:** `GET /users/{userId}`\n- Retrieves details of a user by ID.\n\n#### 2.3 Update User\n**Endpoint:** `PATCH /users/{userId}`\n- Updates user details.\n\n**Request Example:**\n```json\n{\n  \"id\": \"1\",\n  \"lastName\": \"Shakur\",\n  \"birthDate\": \"1978-04-29\",\n  \"profession\": \"King of Rap\"\n}\n```\n\n#### 2.4 Remove User\n**Endpoint:** `DELETE /users/{userId}`\n- Deletes a user by ID.\n\n#### 2.5 Retrieve All Portfolios for a User\n**Endpoint:** `GET /users/{userId}/portfolios/all`\n- Fetches all portfolios owned by a user.\n\n---\n\n### 3. Portfolio Management\n\n#### 3.1 Create Portfolio\n**Endpoint:** `POST /users/{userId}/portfolios`\n- Creates a new portfolio for a user.\n\n**Request Example:**\n```json\n{\n  \"name\": \"Medical Sales Stock\"\n}\n```\n#### 3.2 Retrieve Portfolio\n**Endpoint:** `GET /users/{userId}/portfolios/{portfolioId}`\n- Retrieves portfolio details by ID.\n\n#### 3.3 Update Portfolio\n**Endpoint:** `PATCH /users/{userId}/portfolios/{portfolioId}`\n- Updates an existing portfolio.\n\n**Request Example:**\n```json\n{\n  \"id\": 1,\n  \"name\": \"Shakur Music Investment\"\n}\n```\n#### 3.4 Remove Portfolio\n**Endpoint:** `DELETE /users/{userId}/portfolios/{portfolioId}`\n- Deletes a portfolio.\n\n#### 3.5 Retrieve All Holdings in a Portfolio\n**Endpoint:** `GET /users/{userId}/portfolios/{portfolioId}/holdings/all`\n- Lists all holdings in a portfolio.\n\n#### 3.6 Get Portfolio Valuation\n**Endpoint:** `GET /users/{userId}/portfolios/{portfolioId}/valuation?base={currency}`\n- Returns the total value of a portfolio in the specified base currency.\n\n**Request Example:**\n```http\nGET http://localhost:8080/users/1/portfolios/1/valuation?base=MAD\n```\n\n---\n\n### 4. Holding Management\n\n#### 4.1 Create Holding\n**Endpoint:** `POST /users/{userId}/portfolios/{portfolioId}/holdings`\n- Adds a cryptocurrency holding to a portfolio.\n\n**Request Example:**\n```json\n{\n  \"symbol\": \"LTC\",\n  \"amount\": 15.5\n}\n```\n\n#### 4.2 Retrieve Holding\n**Endpoint:** `GET /users/{userId}/portfolios/{portfolioId}/holdings/{symbol}`\n- Retrieves a specific holding by its symbol.\n\n#### 4.3 Update Holding\n**Endpoint:** `PATCH /users/{userId}/portfolios/{portfolioId}/holdings/{symbol}`\n- Updates a holding.\n\n**Request Example:**\n```json\n{\n  \"symbol\": \"BTC\",\n  \"amount\": 345.123\n}\n```\n\n#### 4.4 Remove Holding\n**Endpoint:** `DELETE /users/{userId}/portfolios/{portfolioId}/holdings/{symbol}`\n- Removes a holding from a portfolio.\n\n**Request Example:**\n```http\nDELETE http://localhost:8080/users/1/portfolios/1/holdings/BTC\n```\n---\n\n#### Notes\n- All endpoints assume a `localhost` setup. \n- `DELETE` operations do not return a body but should return `204 No Content`.\n- `PATCH` allows partial updates.\n- Consider adding authentication and validation layers if necessary.\n\u003c/details\u003e\n\n## 💡 **Contribute!**  \nFeel free to reach out for improvements in design and code quality.  \nYou’re welcome to create PRs to add new functionalities!\n\n## License  \nThis project is open-source and available under the **MIT License**.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falfahami%2Fcrypto-portfolio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falfahami%2Fcrypto-portfolio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falfahami%2Fcrypto-portfolio/lists"}