Updated readme, stats and api server
This commit is contained in:
parent
a7a7e2058c
commit
4a08a64dc9
7 changed files with 94 additions and 60 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
130
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
|
## 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
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
7
index.js
7
index.js
|
|
@ -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}%`,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue