From 4a08a64dc9192809bf618f3b74ab45557b9c5195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vieira?= Date: Fri, 28 Nov 2025 18:56:21 +0000 Subject: [PATCH] Updated readme, stats and api server --- .env.example | 2 +- Containerfile | 1 + README.md | 130 ++++++++++++++++++++++++++++++++------------------ compose.yml | 4 +- index.js | 7 +-- src/db.js | 5 +- src/server.js | 5 +- 7 files changed, 94 insertions(+), 60 deletions(-) diff --git a/.env.example b/.env.example index ab6eda9..4c6ccbc 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,4 @@ INTERVAL=5000 THRESHOLD=0.05 POSTGRES_USER=uphold POSTGRES_PASSWORD=uphold -POSTGRES_DB=uphold_events +POSTGRES_DB=uphold_db diff --git a/Containerfile b/Containerfile index 1a91a36..83de036 100644 --- a/Containerfile +++ b/Containerfile @@ -8,4 +8,5 @@ COPY index.js ./ RUN addgroup -g 1001 uphold && adduser -S -G uphold -u 1001 uphold USER uphold +EXPOSE 3000 ENTRYPOINT ["node", "index.js"] diff --git a/README.md b/README.md index c2b00cb..955885c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -# Uphold Price Alert Bot +# Uphold currency rate bot for interview +This project includes a simple API to checkout statistics and health monitoring (requires running DB) + ## Requirements - Node.js >= v20 -- Docker & Docker Compose (optional) +- Docker and Docker Compose (optionally, Podman and Podman compose ) - PostgreSQL (optional, runs in-memory without it) ## Quick Start @@ -15,7 +17,7 @@ npm install ### 2. Run the Bot -**With default parameters:** +**With default parameters, no DB or API:** ```bash node index.js ``` @@ -27,13 +29,13 @@ npm start **With custom parameters:** ```bash -node index.js --pairs BTC-USD,ETH-USD --interval 5000 --threshold 0.01 +node index.js --pairs BTC-USD,ETH-USD --interval 10000 --threshold 0.05 ``` or ```bash -npm start -- --pairs BTC-USD,ETH-USD --interval 5000 --threshold 0.01 +npm start -- --pairs BTC-USD,ETH-USD --interval 10000 --threshold 0.01 ``` **With environment file:** @@ -41,24 +43,44 @@ npm start -- --pairs BTC-USD,ETH-USD --interval 5000 --threshold 0.01 node --env-file=.env.example index.js ``` -### 3. Run with Docker Compose (includes PostgreSQL) +### 3. Run with Docker/Podman Compose (includes DB and API) ```bash cp .env.example .env +```` -# Edit .env with your settings -# POSTGRES_USER=uphold -# POSTGRES_PASSWORD=uphold -# POSTGRES_DB=uphold_db -# PAIRS=BTC-USD,ETH-USD -# INTERVAL=5000 -# THRESHOLD=0.01 +Edit .env with your settings: +``` +POSTGRES_USER=uphold +POSTGRES_PASSWORD=uphold +POSTGRES_DB=uphold_db +PAIRS=BTC-USD,ETH-USD +INTERVAL=5000 +THRESHOLD=0.01 +``` + +``` # Start services docker compose up -d --build +``` +or + +``` +podman compose up -d --build +``` + # View logs -docker compose logs -f bot + +``` +docker compose logs -f uphold_bot_1 +``` + +or + +``` +podman compose logs -f uphold_bot_1 ``` ## Configuration @@ -66,23 +88,8 @@ docker compose logs -f bot | Parameter | Flag | Environment | Default | Description | |-----------|------|-------------|---------|-------------| | Pairs | `-p, --pairs` | `PAIRS` | `BTC-USD` | Comma-separated currency pairs | -| Interval | `-i, --interval` | `INTERVAL` | `5000` | Check interval in milliseconds | -| Threshold | `-t, --threshold` | `THRESHOLD` | `0.01` | Alert threshold percentage | -| Stats | `--stats` | `STATS` | `false` | Show performance statistics | - -## Database Setup (Optional) - -The bot runs in-memory mode by default. To persist alerts: - -```bash -# Set DATABASE_URL -export DATABASE_URL="postgres://user:password@localhost:5432/uphold_db" - -# Run the bot -node index.js -``` - -The `alerts` table is created automatically on first run. +| Interval | `-i, --interval` | `INTERVAL` | `5000` | Uphold API retrieval interval in milliseconds | +| Threshold | `-t, --threshold` | `THRESHOLD` | `0.01` | Alert difference percentage | ## Running Tests @@ -90,25 +97,53 @@ The `alerts` table is created automatically on first run. npm test ``` +## API +### Endpoints + +**Health Check** +```bash +curl http://localhost:3000/health +``` + +**Statistics** +```bash +curl http://localhost:3000/stats +``` + +**Alerts** +```bash +# Get last 50 alerts (default) +curl http://localhost:3000/alerts + +# Get last 100 alerts +curl http://localhost:3000/alerts?limit=100 +``` + ## Example Output ``` -[INFO] Uphold price alert bot starting... -[INFO] [WATCHING] BTC-USD, ETH-USD | Every 5000ms | Threshold 0.01% -[INFO] [BTC-USD] Initial price: 42350.50 -[INFO] [ETH-USD] Initial price: 2245.75 -[INFO] [ALERT] BTC-USD UP 0.0125% (42350.50 → 42355.79) -[INFO] [DB] Event saved for BTC-USD +[18:44:51.898] INFO: Bot starting... +[18:44:51.898] INFO: [WATCHING] BTC-USD | Every 5000ms | Threshold 0.01% +[18:44:51.898] WARN: [DB] specificiation missing. Running in memor +[18:44:52.167] INFO: [BTC-USD] Monitoring every 5000ms, +/-0.01% +[18:44:52.367] INFO: [BTC-USD] Initial price: 90977.65 +[18:45:07.879] INFO: [ALERT] BTC-USD DOWN -0.0116% + pair: "BTC-USD" + direction: "DOWN" + change: "-0.0116%" + prev: "90977.648894" + curr: "90967.085674" ``` ## Architecture - - `index.js` - Entry point -- `src/bot.js` - Bot monitoring logic -- `src/api.js` - Uphold API client with rate limiting -- `src/db.js` - PostgreSQL DB layer -- `src/logger.js` - logging -- `tests/bot.test.js` - Test suite +- `src/bot.js` - Bot logic +- `src/api.js` - Uphold API client +- `src/db.js` - PostgreSQL persistence layer +- `src/logger.js` - Simple logging +- `src/server.js` - API server for docker diagnosis +- `src/stats.js` - Runtime statistics tracking +- `tests/bot.test.js` - Tests ## Stopping the Bot @@ -118,7 +153,8 @@ With Docker: ```bash docker compose down ``` - -## License - -MIT +or Podman +```bash +podman compose down +podman volume rm uphold_pgdata +``` diff --git a/compose.yml b/compose.yml index aa5a420..6607dd3 100644 --- a/compose.yml +++ b/compose.yml @@ -13,7 +13,7 @@ services: - INTERVAL=${INTERVAL:-5000} - THRESHOLD=${THRESHOLD:-0.01} healthcheck: - test: ["CMD", "node", "-e", "require('node:process').exit(require('fs').existsSync('/tmp/healthy') ? 0 : 1)"] + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 @@ -24,7 +24,7 @@ services: volumes: - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-uphold} -d ${POSTGRES_DB:-uphold_alerts}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-uphold} -d ${POSTGRES_DB:-uphold_db}"] interval: 5s timeout: 5s retries: 5 diff --git a/index.js b/index.js index 5e3f2fc..3d01bfa 100644 --- a/index.js +++ b/index.js @@ -32,11 +32,6 @@ const options = { short: "t", default: process.env.THRESHOLD || "0.01", }, - stats: { - type: "boolean", - default: process.env.STATS === "true", - description: "Show performance stats periodically", - }, }; const { values } = parseArgs({ @@ -64,7 +59,7 @@ if (threshold <= 0 || threshold > 100) { } async function main() { - logger.info("Uphold price alert bot starting..."); + logger.info("Bot starting..."); logger.info( `[WATCHING] ${pairs.join(", ")} | Every ${interval}ms | Threshold ${threshold}%`, ); diff --git a/src/db.js b/src/db.js index 911fb47..e1382d1 100644 --- a/src/db.js +++ b/src/db.js @@ -111,7 +111,10 @@ export async function insertIntoDB(data) { } } -export async function getRecentAlerts(limit = 50) { +export async function getAlerts(limit = 50) { + if (!pool) { + return []; + } const res = await pool.query( "SELECT * FROM alerts ORDER BY created_at DESC LIMIT $1", [limit], diff --git a/src/server.js b/src/server.js index e5e8686..ed19b7b 100644 --- a/src/server.js +++ b/src/server.js @@ -1,7 +1,7 @@ import http from "node:http"; import { URL } from "node:url"; import { stats } from "./stats.js"; -import { getRecentAlerts } from "./db.js"; +import { getAlerts } from "./db.js"; import logger from "./logger.js"; const PORT = process.env.PORT || 3000; @@ -26,11 +26,10 @@ const server = http.createServer(async (req, res) => { if (req.method === "GET" && pathname === "/alerts") { const limit = parseInt(searchParams.get("limit")) || 50; - const alerts = await getRecentAlerts(limit); + const alerts = await getAlerts(limit); return sendJSON(res, { count: alerts.length, data: alerts }); } - // 404 sendJSON(res, { error: "Not Found" }, 404); } catch (err) { logger.error(err, "API Error");