first commit of refactor
This commit is contained in:
94
backend/infra/README.md
Normal file
94
backend/infra/README.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Database Setup
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- PostgreSQL installed locally
|
||||
- Database created (e.g., `koin_ping_dev`)
|
||||
|
||||
## Setup Steps
|
||||
|
||||
### 1. Create Database
|
||||
|
||||
```bash
|
||||
# Using psql
|
||||
createdb koin_ping_dev
|
||||
|
||||
# Or via psql command line
|
||||
psql -U postgres
|
||||
CREATE DATABASE koin_ping_dev;
|
||||
\q
|
||||
```
|
||||
|
||||
### 2. Set Environment Variables
|
||||
|
||||
Create a `.env` file in the `backend/` directory:
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://your_user:your_password@localhost:5432/koin_ping_dev
|
||||
```
|
||||
|
||||
Or if using individual variables (for `infra/database.js`):
|
||||
|
||||
```env
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=your_user
|
||||
DB_PASSWORD=your_password
|
||||
DB_NAME=koin_ping_dev
|
||||
```
|
||||
|
||||
### 3. Run Schema
|
||||
|
||||
```bash
|
||||
# From the backend
|
||||
psql -d koin_ping_dev -f db/schema.sql
|
||||
|
||||
# Or the full connection string
|
||||
psql postgresql://your_user:your_password@localhost:5432/koin_ping_dev -f db/schema.sql
|
||||
```
|
||||
|
||||
### 4. Verify Tables
|
||||
|
||||
```bash
|
||||
psql -d koin_ping_dev
|
||||
|
||||
# In psql:
|
||||
\dt # List tables
|
||||
\d addresses # Describe addresses table
|
||||
\d alert_rules # Describe alert_rules table
|
||||
\d alert_events # Describe alert_events table
|
||||
```
|
||||
|
||||
## Tables
|
||||
|
||||
### addresses
|
||||
- `id` - Primary key
|
||||
- `address` - Ethereum address (unique)
|
||||
- `label` - Optional display name
|
||||
- `created_at` - Timestamp
|
||||
|
||||
### alert_rules
|
||||
- `id` - Primary key
|
||||
- `address_id` - Foreign key to addresses
|
||||
- `type` - Alert type: `incoming_tx`, `outgoing_tx`, `large_transfer`, `balance_below`
|
||||
- `threshold` - ETH amount (nullable)
|
||||
- `enabled` - Active status
|
||||
- `created_at` - Timestamp
|
||||
|
||||
### alert_events
|
||||
- `id` - Primary key
|
||||
- `alert_rule_id` - Foreign key to alert_rules
|
||||
- `message` - Alert description
|
||||
- `address_label` - Denormalized label for display
|
||||
- `timestamp` - When alert fired
|
||||
|
||||
## Reset Database
|
||||
|
||||
To start fresh:
|
||||
|
||||
```bash
|
||||
psql -d koin_ping_dev -f db/schema.sql
|
||||
```
|
||||
|
||||
The schema includes `DROP TABLE IF EXISTS` statements for clean reruns.
|
||||
|
||||
21
backend/infra/add_notification_config.sql
Normal file
21
backend/infra/add_notification_config.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
-- Run: psql -d koin_ping_dev -f add_notification_config.sql
|
||||
|
||||
-- Create user notification configuration tableStores notification preferences and webhook URLs for each user
|
||||
CREATE TABLE user_notification_configs (
|
||||
user_id VARCHAR(128) PRIMARY KEY,
|
||||
discord_webhook_url TEXT, -- Discord webhook URL (nullable)
|
||||
telegram_chat_id VARCHAR(128), -- Telegram chat ID (nullable)
|
||||
telegram_bot_token VARCHAR(255), -- Telegram bot token (nullable, future use)
|
||||
email VARCHAR(255), -- Email for notifications (nullable)
|
||||
notification_enabled BOOLEAN DEFAULT TRUE, -- Master on/off switch
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create index for active notifications lookup
|
||||
CREATE INDEX idx_notification_configs_enabled ON user_notification_configs(notification_enabled);
|
||||
|
||||
-- Note: No foreign key to a users table (doesn't exist yet)
|
||||
-- user_id is the Firebase UID, validated by backend middleware
|
||||
|
||||
16
backend/infra/add_user_id_migration.sql
Normal file
16
backend/infra/add_user_id_migration.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
ALTER TABLE addresses
|
||||
ADD COLUMN user_id VARCHAR(128);
|
||||
|
||||
UPDATE addresses
|
||||
SET user_id = 'legacy_user'
|
||||
WHERE user_id IS NULL;
|
||||
|
||||
ALTER TABLE addresses
|
||||
ALTER COLUMN user_id SET NOT NULL;
|
||||
|
||||
CREATE INDEX idx_addresses_user_id ON addresses(user_id);
|
||||
|
||||
ALTER TABLE addresses DROP CONSTRAINT IF EXISTS addresses_address_key;
|
||||
|
||||
ALTER TABLE addresses ADD CONSTRAINT addresses_user_address_unique UNIQUE (user_id, address);
|
||||
32
backend/infra/database.js
Normal file
32
backend/infra/database.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import pg from "pg";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const { Pool } = pg;
|
||||
|
||||
const pool = new Pool(
|
||||
process.env.DATABASE_URL
|
||||
? { connectionString: process.env.DATABASE_URL }
|
||||
: {
|
||||
host: process.env.DB_HOST || "localhost",
|
||||
port: process.env.DB_PORT || 5432,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
max: 20,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 2000,
|
||||
}
|
||||
);
|
||||
|
||||
pool.on("connect", () => {
|
||||
console.log("Connected to PostgreSQL database");
|
||||
});
|
||||
|
||||
pool.on("error", (err) => {
|
||||
console.error("Unexpected error on idle client", err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export default pool;
|
||||
71
backend/infra/schema.sql
Normal file
71
backend/infra/schema.sql
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
DROP TABLE IF EXISTS alert_events CASCADE;
|
||||
DROP TABLE IF EXISTS alert_rules CASCADE;
|
||||
DROP TABLE IF EXISTS address_checkpoints CASCADE;
|
||||
DROP TABLE IF EXISTS user_notification_configs CASCADE;
|
||||
DROP TABLE IF EXISTS addresses CASCADE;
|
||||
|
||||
CREATE TABLE addresses (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(128) NOT NULL, -- Firebase user ID (multi-user support)
|
||||
address VARCHAR(42) NOT NULL, -- Ethereum address format (0x + 40 hex chars)
|
||||
label VARCHAR(255), -- Optional human-readable label
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
|
||||
-- Unique users can track the same address independently
|
||||
UNIQUE(user_id, address)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE alert_rules (
|
||||
id SERIAL PRIMARY KEY,
|
||||
address_id INTEGER NOT NULL REFERENCES addresses(id) ON DELETE CASCADE,
|
||||
type VARCHAR(50) NOT NULL, -- 'incoming_tx', 'outgoing_tx', 'large_transfer', 'balance_below'
|
||||
threshold DECIMAL(20, 6), -- ETH amount threshold (nullable for tx types that don't need it)
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT valid_alert_type CHECK (
|
||||
type IN ('incoming_tx', 'outgoing_tx', 'large_transfer', 'balance_below')
|
||||
),
|
||||
|
||||
CONSTRAINT positive_threshold CHECK (
|
||||
threshold IS NULL OR threshold > 0
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE alert_events (
|
||||
id SERIAL PRIMARY KEY,
|
||||
alert_rule_id INTEGER NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE,
|
||||
message TEXT NOT NULL, -- Human-readable alert message
|
||||
address_label VARCHAR(255), -- Denormalized for display (avoids joins)
|
||||
tx_hash VARCHAR(66), -- Transaction hash that triggered alert (nullable for balance_below)
|
||||
timestamp TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE address_checkpoints (
|
||||
address_id INTEGER PRIMARY KEY REFERENCES addresses(id) ON DELETE CASCADE,
|
||||
last_checked_block INTEGER NOT NULL, -- Last block number that was checked for this address
|
||||
last_checked_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE user_notification_configs (
|
||||
user_id VARCHAR(128) PRIMARY KEY,
|
||||
discord_webhook_url TEXT, -- Discord webhook URL (nullable)
|
||||
telegram_chat_id VARCHAR(128), -- Telegram chat ID (nullable)
|
||||
telegram_bot_token VARCHAR(255), -- Telegram bot token (nullable, future use)
|
||||
email VARCHAR(255), -- Email for notifications (nullable)
|
||||
notification_enabled BOOLEAN DEFAULT TRUE, -- Master on/off switch
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create indexes for common queries
|
||||
CREATE INDEX idx_addresses_user_id ON addresses(user_id);
|
||||
CREATE INDEX idx_alert_rules_address_id ON alert_rules(address_id);
|
||||
CREATE INDEX idx_alert_rules_enabled ON alert_rules(enabled);
|
||||
CREATE INDEX idx_alert_events_timestamp ON alert_events(timestamp DESC);
|
||||
CREATE INDEX idx_alert_events_alert_rule_id ON alert_events(alert_rule_id);
|
||||
CREATE INDEX idx_address_checkpoints_last_checked_at ON address_checkpoints(last_checked_at);
|
||||
CREATE INDEX idx_notification_configs_enabled ON user_notification_configs(notification_enabled);
|
||||
Reference in New Issue
Block a user