Updated readme, stats and api server

This commit is contained in:
Vítor Vieira 2025-11-28 18:56:21 +00:00
parent a7a7e2058c
commit 4a08a64dc9
7 changed files with 94 additions and 60 deletions

View file

@ -3,4 +3,4 @@ INTERVAL=5000
THRESHOLD=0.05 THRESHOLD=0.05
POSTGRES_USER=uphold POSTGRES_USER=uphold
POSTGRES_PASSWORD=uphold POSTGRES_PASSWORD=uphold
POSTGRES_DB=uphold_events POSTGRES_DB=uphold_db

View file

@ -8,4 +8,5 @@ COPY index.js ./
RUN addgroup -g 1001 uphold && adduser -S -G uphold -u 1001 uphold RUN addgroup -g 1001 uphold && adduser -S -G uphold -u 1001 uphold
USER uphold USER uphold
EXPOSE 3000
ENTRYPOINT ["node", "index.js"] ENTRYPOINT ["node", "index.js"]

130
README.md
View file

@ -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 ## Requirements
- Node.js >= v20 - Node.js >= v20
- Docker & Docker Compose (optional) - Docker and Docker Compose (optionally, Podman and Podman compose )
- PostgreSQL (optional, runs in-memory without it) - PostgreSQL (optional, runs in-memory without it)
## Quick Start ## Quick Start
@ -15,7 +17,7 @@ npm install
### 2. Run the Bot ### 2. Run the Bot
**With default parameters:** **With default parameters, no DB or API:**
```bash ```bash
node index.js node index.js
``` ```
@ -27,13 +29,13 @@ npm start
**With custom parameters:** **With custom parameters:**
```bash ```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 or
```bash ```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:** **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 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 ```bash
cp .env.example .env cp .env.example .env
````
# Edit .env with your settings 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
```
POSTGRES_USER=uphold
POSTGRES_PASSWORD=uphold
POSTGRES_DB=uphold_db
PAIRS=BTC-USD,ETH-USD
INTERVAL=5000
THRESHOLD=0.01
```
```
# Start services # Start services
docker compose up -d --build docker compose up -d --build
```
or
```
podman compose up -d --build
```
# View logs # View logs
docker compose logs -f bot
```
docker compose logs -f uphold_bot_1
```
or
```
podman compose logs -f uphold_bot_1
``` ```
## Configuration ## Configuration
@ -66,23 +88,8 @@ docker compose logs -f bot
| Parameter | Flag | Environment | Default | Description | | Parameter | Flag | Environment | Default | Description |
|-----------|------|-------------|---------|-------------| |-----------|------|-------------|---------|-------------|
| Pairs | `-p, --pairs` | `PAIRS` | `BTC-USD` | Comma-separated currency pairs | | Pairs | `-p, --pairs` | `PAIRS` | `BTC-USD` | Comma-separated currency pairs |
| Interval | `-i, --interval` | `INTERVAL` | `5000` | Check interval in milliseconds | | Interval | `-i, --interval` | `INTERVAL` | `5000` | Uphold API retrieval interval in milliseconds |
| Threshold | `-t, --threshold` | `THRESHOLD` | `0.01` | Alert threshold percentage | | Threshold | `-t, --threshold` | `THRESHOLD` | `0.01` | Alert difference 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.
## Running Tests ## Running Tests
@ -90,25 +97,53 @@ The `alerts` table is created automatically on first run.
npm test 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 ## Example Output
``` ```
[INFO] Uphold price alert bot starting... [18:44:51.898] INFO: Bot starting...
[INFO] [WATCHING] BTC-USD, ETH-USD | Every 5000ms | Threshold 0.01% [18:44:51.898] INFO: [WATCHING] BTC-USD | Every 5000ms | Threshold 0.01%
[INFO] [BTC-USD] Initial price: 42350.50 [18:44:51.898] WARN: [DB] specificiation missing. Running in memor
[INFO] [ETH-USD] Initial price: 2245.75 [18:44:52.167] INFO: [BTC-USD] Monitoring every 5000ms, +/-0.01%
[INFO] [ALERT] BTC-USD UP 0.0125% (42350.50 → 42355.79) [18:44:52.367] INFO: [BTC-USD] Initial price: 90977.65
[INFO] [DB] Event saved for BTC-USD [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 ## Architecture
- `index.js` - Entry point - `index.js` - Entry point
- `src/bot.js` - Bot monitoring logic - `src/bot.js` - Bot logic
- `src/api.js` - Uphold API client with rate limiting - `src/api.js` - Uphold API client
- `src/db.js` - PostgreSQL DB layer - `src/db.js` - PostgreSQL persistence layer
- `src/logger.js` - logging - `src/logger.js` - Simple logging
- `tests/bot.test.js` - Test suite - `src/server.js` - API server for docker diagnosis
- `src/stats.js` - Runtime statistics tracking
- `tests/bot.test.js` - Tests
## Stopping the Bot ## Stopping the Bot
@ -118,7 +153,8 @@ With Docker:
```bash ```bash
docker compose down docker compose down
``` ```
or Podman
## License ```bash
podman compose down
MIT podman volume rm uphold_pgdata
```

View file

@ -13,7 +13,7 @@ services:
- INTERVAL=${INTERVAL:-5000} - INTERVAL=${INTERVAL:-5000}
- THRESHOLD=${THRESHOLD:-0.01} - THRESHOLD=${THRESHOLD:-0.01}
healthcheck: 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 interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@ -24,7 +24,7 @@ services:
volumes: volumes:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
healthcheck: 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 interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5

View file

@ -32,11 +32,6 @@ const options = {
short: "t", short: "t",
default: process.env.THRESHOLD || "0.01", default: process.env.THRESHOLD || "0.01",
}, },
stats: {
type: "boolean",
default: process.env.STATS === "true",
description: "Show performance stats periodically",
},
}; };
const { values } = parseArgs({ const { values } = parseArgs({
@ -64,7 +59,7 @@ if (threshold <= 0 || threshold > 100) {
} }
async function main() { async function main() {
logger.info("Uphold price alert bot starting..."); logger.info("Bot starting...");
logger.info( logger.info(
`[WATCHING] ${pairs.join(", ")} | Every ${interval}ms | Threshold ${threshold}%`, `[WATCHING] ${pairs.join(", ")} | Every ${interval}ms | Threshold ${threshold}%`,
); );

View file

@ -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( const res = await pool.query(
"SELECT * FROM alerts ORDER BY created_at DESC LIMIT $1", "SELECT * FROM alerts ORDER BY created_at DESC LIMIT $1",
[limit], [limit],

View file

@ -1,7 +1,7 @@
import http from "node:http"; import http from "node:http";
import { URL } from "node:url"; import { URL } from "node:url";
import { stats } from "./stats.js"; import { stats } from "./stats.js";
import { getRecentAlerts } from "./db.js"; import { getAlerts } from "./db.js";
import logger from "./logger.js"; import logger from "./logger.js";
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
@ -26,11 +26,10 @@ const server = http.createServer(async (req, res) => {
if (req.method === "GET" && pathname === "/alerts") { if (req.method === "GET" && pathname === "/alerts") {
const limit = parseInt(searchParams.get("limit")) || 50; 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 }); return sendJSON(res, { count: alerts.length, data: alerts });
} }
// 404
sendJSON(res, { error: "Not Found" }, 404); sendJSON(res, { error: "Not Found" }, 404);
} catch (err) { } catch (err) {
logger.error(err, "API Error"); logger.error(err, "API Error");