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
POSTGRES_USER=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
USER uphold
EXPOSE 3000
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
- 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
```

View file

@ -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

View file

@ -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}%`,
);

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(
"SELECT * FROM alerts ORDER BY created_at DESC LIMIT $1",
[limit],

View file

@ -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");