diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4924814e --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +.env +__pycache__/ +.DS_Store +*.log +coverage/ +.vitest/ \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..cbf4cd42 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "coin-gekko-api", + "version": "1.0.0", + "description": "Coin Gekko API Implementation", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "test": "vitest", + "start": "node dist/index.js" + }, + "keywords": ["gekko", "crypto", "api"], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/node": "^16.0.0", + "typescript": "^4.5.0", + "vitest": "^0.30.0" + }, + "dependencies": { + "axios": "^0.21.1" + } +} \ No newline at end of file diff --git a/src/index.spec.ts b/src/index.spec.ts new file mode 100644 index 00000000..95c173d6 --- /dev/null +++ b/src/index.spec.ts @@ -0,0 +1,81 @@ +import { describe, it, expect } from 'vitest'; +import { CoinGekkoApi } from './index'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; + +describe('CoinGekkoApi', () => { + const mock = new MockAdapter(axios); + const api = new CoinGekkoApi(); + + afterEach(() => { + mock.reset(); + }); + + describe('getCoinList', () => { + it('should fetch coin list successfully', async () => { + const mockCoins = [ + { + id: 'bitcoin', + symbol: 'btc', + name: 'Bitcoin', + current_price: 50000, + market_cap: 1000000000000 + } + ]; + + mock.onGet(/coins\/markets/).reply(200, mockCoins); + + const coins = await api.getCoinList(1); + expect(coins).toHaveLength(1); + expect(coins[0]).toEqual({ + id: 'bitcoin', + symbol: 'btc', + name: 'Bitcoin', + price: 50000, + marketCap: 1000000000000 + }); + }); + + it('should handle error when fetching coin list fails', async () => { + mock.onGet(/coins\/markets/).reply(500); + + await expect(api.getCoinList()).rejects.toThrow('Failed to fetch coin list'); + }); + }); + + describe('getCoinById', () => { + it('should fetch coin details successfully', async () => { + const mockCoin = { + id: 'bitcoin', + symbol: 'btc', + name: 'Bitcoin', + market_data: { + current_price: { usd: 50000 }, + market_cap: { usd: 1000000000000 } + } + }; + + mock.onGet(/coins\/bitcoin/).reply(200, mockCoin); + + const coin = await api.getCoinById('bitcoin'); + expect(coin).toEqual({ + id: 'bitcoin', + symbol: 'btc', + name: 'Bitcoin', + price: 50000, + marketCap: 1000000000000 + }); + }); + + it('should return null for non-existent coin', async () => { + mock.onGet(/coins\/nonexistent/).reply(404); + + const coin = await api.getCoinById('nonexistent'); + expect(coin).toBeNull(); + }); + + it('should throw error for invalid coin id', async () => { + await expect(api.getCoinById('')).rejects.toThrow('Coin ID is required'); + }); + }); +}); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..da99ae57 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,67 @@ +import axios from 'axios'; + +export interface CoinData { + id: string; + symbol: string; + name: string; + price: number; + marketCap: number; +} + +export class CoinGekkoApi { + private baseUrl: string; + + constructor(baseUrl: string = 'https://api.coingecko.com/api/v3') { + this.baseUrl = baseUrl; + } + + async getCoinList(limit: number = 100): Promise { + try { + const response = await axios.get(`${this.baseUrl}/coins/markets`, { + params: { + vs_currency: 'usd', + order: 'market_cap_desc', + per_page: limit, + page: 1, + sparkline: false + } + }); + + return response.data.map((coin: any) => ({ + id: coin.id, + symbol: coin.symbol, + name: coin.name, + price: coin.current_price, + marketCap: coin.market_cap + })); + } catch (error) { + console.error('Error fetching coin list:', error); + throw new Error('Failed to fetch coin list'); + } + } + + async getCoinById(id: string): Promise { + if (!id) { + throw new Error('Coin ID is required'); + } + + try { + const response = await axios.get(`${this.baseUrl}/coins/${id}`); + const coin = response.data; + + return { + id: coin.id, + symbol: coin.symbol, + name: coin.name, + price: coin.market_data.current_price.usd, + marketCap: coin.market_data.market_cap.usd + }; + } catch (error) { + if (axios.isAxiosError(error) && error.response?.status === 404) { + return null; + } + console.error('Error fetching coin details:', error); + throw new Error('Failed to fetch coin details'); + } + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..aa8d2992 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/*.spec.ts"] +} \ No newline at end of file