first commit of refactor
This commit is contained in:
76
backend/models/AddressCheckpointModel.js
Normal file
76
backend/models/AddressCheckpointModel.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import pool from '../infra/database.js';
|
||||
|
||||
|
||||
/**
|
||||
* Get the last checked block number for an address
|
||||
* @param {number} addressId - Address ID from the addresses table
|
||||
* @returns {Promise<number|null>} Last checked block number, or null if never checked
|
||||
*/
|
||||
export async function getLastCheckedBlock(addressId) {
|
||||
const result = await pool.query(
|
||||
`SELECT last_checked_block
|
||||
FROM address_checkpoints
|
||||
WHERE address_id = $1`,
|
||||
[addressId]
|
||||
);
|
||||
|
||||
return result.rows[0]?.last_checked_block || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the last checked block number for an address
|
||||
* Creates a new checkpoint record if one doesn't exist
|
||||
* @param {number} addressId - Address ID from the addresses table
|
||||
* @param {number} blockNumber - Block number that was just checked
|
||||
* @returns {Promise<Object>} Updated checkpoint record
|
||||
*/
|
||||
export async function updateLastCheckedBlock(addressId, blockNumber) {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO address_checkpoints (address_id, last_checked_block, last_checked_at)
|
||||
VALUES ($1, $2, NOW())
|
||||
ON CONFLICT (address_id)
|
||||
DO UPDATE SET
|
||||
last_checked_block = $2,
|
||||
last_checked_at = NOW()
|
||||
RETURNING address_id, last_checked_block, last_checked_at`,
|
||||
[addressId, blockNumber]
|
||||
);
|
||||
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all address checkpoints (useful for debugging/monitoring)
|
||||
* @returns {Promise<Array>} Array of checkpoint objects
|
||||
*/
|
||||
export async function listAll() {
|
||||
const result = await pool.query(
|
||||
`SELECT
|
||||
ac.address_id,
|
||||
a.address,
|
||||
a.label,
|
||||
ac.last_checked_block,
|
||||
ac.last_checked_at
|
||||
FROM address_checkpoints ac
|
||||
JOIN addresses a ON a.id = ac.address_id
|
||||
ORDER BY ac.last_checked_at DESC`
|
||||
);
|
||||
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete checkpoint for an address (useful when address is removed)
|
||||
* Note: This should happen automatically via CASCADE when address is deleted
|
||||
* @param {number} addressId - Address ID
|
||||
* @returns {Promise<boolean>} True if deleted, false if not found
|
||||
*/
|
||||
export async function remove(addressId) {
|
||||
const result = await pool.query(
|
||||
`DELETE FROM address_checkpoints WHERE address_id = $1`,
|
||||
[addressId]
|
||||
);
|
||||
|
||||
return result.rowCount > 0;
|
||||
}
|
||||
|
||||
84
backend/models/AddressModel.js
Normal file
84
backend/models/AddressModel.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import pool from '../infra/database.js';
|
||||
|
||||
/**
|
||||
* Create a new address
|
||||
* @param {string} user_id - Firebase user ID
|
||||
* @param {string} address - Ethereum address
|
||||
* @param {string|null} label - Optional label
|
||||
* @returns {Promise<Object>} Created address object
|
||||
*/
|
||||
export async function create(user_id, address, label) {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO addresses (user_id, address, label)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, user_id, address, label, created_at`,
|
||||
[user_id, address, label]
|
||||
);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* List all addresses for a specific user
|
||||
* @param {string} user_id - Firebase user ID
|
||||
* @returns {Promise<Array>} Array of address objects
|
||||
*/
|
||||
export async function listByUser(user_id) {
|
||||
const result = await pool.query(
|
||||
`SELECT id, address, label, created_at
|
||||
FROM addresses
|
||||
WHERE user_id = $1
|
||||
ORDER BY created_at DESC`,
|
||||
[user_id]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all addresses (for system use only, e.g., poller)
|
||||
* @returns {Promise<Array>} Array of address objects
|
||||
*/
|
||||
export async function list() {
|
||||
const result = await pool.query(
|
||||
`SELECT id, user_id, address, label, created_at
|
||||
FROM addresses
|
||||
ORDER BY created_at DESC`
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find address by ID (for specific user)
|
||||
* @param {number} id - Address ID
|
||||
* @param {string} user_id - Firebase user ID (for ownership verification)
|
||||
* @returns {Promise<Object|null>} Address object or null if not found or not owned by user
|
||||
*/
|
||||
export async function findById(id, user_id = null) {
|
||||
const query = user_id
|
||||
? `SELECT id, user_id, address, label, created_at
|
||||
FROM addresses
|
||||
WHERE id = $1 AND user_id = $2`
|
||||
: `SELECT id, user_id, address, label, created_at
|
||||
FROM addresses
|
||||
WHERE id = $1`;
|
||||
|
||||
const params = user_id ? [id, user_id] : [id];
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove address by ID (for specific user)
|
||||
* @param {number} id - Address ID
|
||||
* @param {string} user_id - Firebase user ID (for ownership verification)
|
||||
* @returns {Promise<boolean>} True if deleted, false if not found or not owned by user
|
||||
*/
|
||||
export async function remove(id, user_id) {
|
||||
const result = await pool.query(
|
||||
`DELETE FROM addresses
|
||||
WHERE id = $1 AND user_id = $2`,
|
||||
[id, user_id]
|
||||
);
|
||||
return result.rowCount > 0;
|
||||
}
|
||||
|
||||
56
backend/models/AlertEventModel.js
Normal file
56
backend/models/AlertEventModel.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import pool from '../infra/database.js';
|
||||
|
||||
/**
|
||||
* List recent alert events for a specific user
|
||||
* @param {string} user_id - Firebase user ID
|
||||
* @param {number} limit - Maximum number of events to return
|
||||
* @returns {Promise<Array>} Array of alert event objects
|
||||
*/
|
||||
export async function listRecentByUser(user_id, limit = 20) {
|
||||
const result = await pool.query(
|
||||
`SELECT ae.id, ae.alert_rule_id, ae.message, ae.address_label, ae.tx_hash, ae.timestamp
|
||||
FROM alert_events ae
|
||||
JOIN alert_rules ar ON ar.id = ae.alert_rule_id
|
||||
JOIN addresses a ON a.id = ar.address_id
|
||||
WHERE a.user_id = $1
|
||||
ORDER BY ae.timestamp DESC
|
||||
LIMIT $2`,
|
||||
[user_id, limit]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* List recent alert events
|
||||
* @param {number} limit - Maximum number of event
|
||||
* @returns {Promise<Array>} Array of alert event objects
|
||||
*/
|
||||
export async function listRecent(limit = 20) {
|
||||
const result = await pool.query(
|
||||
`SELECT id, alert_rule_id, message, address_label, tx_hash, timestamp
|
||||
FROM alert_events
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT $1`,
|
||||
[limit]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new alert event
|
||||
* @param {number} alertRuleId - Alert rule ID
|
||||
* @param {string} message - Alert message
|
||||
* @param {string|null} addressLabel - Address label (denormalized)
|
||||
* @param {string|null} txHash - Transaction hash (optional, null for non-tx alerts)
|
||||
* @returns {Promise<Object>} Created alert event object
|
||||
*/
|
||||
export async function create(alertRuleId, message, addressLabel, txHash = null) {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO alert_events (alert_rule_id, message, address_label, tx_hash)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, alert_rule_id, message, address_label, tx_hash, timestamp`,
|
||||
[alertRuleId, message, addressLabel, txHash]
|
||||
);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
92
backend/models/AlertRuleModel.js
Normal file
92
backend/models/AlertRuleModel.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import pool from '../infra/database.js';
|
||||
|
||||
/**
|
||||
* Create new alertrule
|
||||
* @param {number} addressId - Address ID
|
||||
* @param {string} type - Alert type
|
||||
* @param {string|null} threshold - Threshold value (nullable)
|
||||
* @returns {Promise<Object>} Created alert rule object
|
||||
*/
|
||||
export async function create(addressId, type, threshold) {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO alert_rules (address_id, type, threshold, enabled)
|
||||
VALUES ($1, $2, $3, TRUE)
|
||||
RETURNING id, address_id, type, threshold, enabled, created_at`,
|
||||
[addressId, type, threshold]
|
||||
);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* List alert rules by address ID
|
||||
* @param {number} addressId - Address ID
|
||||
* @returns {Promise<Array>} Array of alert rule objects
|
||||
*/
|
||||
export async function listByAddress(addressId) {
|
||||
const result = await pool.query(
|
||||
`SELECT id, address_id, type, threshold, enabled, created_at
|
||||
FROM alert_rules
|
||||
WHERE address_id = $1
|
||||
ORDER BY created_at DESC`,
|
||||
[addressId]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find alert rule by ID
|
||||
* @param {number} id - Alert rule ID
|
||||
* @param {string} user_id - Optional user ID for ownership verification
|
||||
* @returns {Promise<Object|null>} Alert rule object or null if not found/not owned
|
||||
*/
|
||||
export async function findById(id, user_id = null) {
|
||||
let query, params;
|
||||
|
||||
if (user_id) {
|
||||
// Verify ownership
|
||||
query = `SELECT ar.id, ar.address_id, ar.type, ar.threshold, ar.enabled, ar.created_at
|
||||
FROM alert_rules ar
|
||||
JOIN addresses a ON a.id = ar.address_id
|
||||
WHERE ar.id = $1 AND a.user_id = $2`;
|
||||
params = [id, user_id];
|
||||
} else {
|
||||
query = `SELECT id, address_id, type, threshold, enabled, created_at
|
||||
FROM alert_rules
|
||||
WHERE id = $1`;
|
||||
params = [id];
|
||||
}
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update alert rule enabled status
|
||||
* @param {number} id - Alert rule ID
|
||||
* @param {boolean} enabled - Enabled status
|
||||
* @returns {Promise<Object|null>} Updated alert rule or null if not found
|
||||
*/
|
||||
export async function updateEnabled(id, enabled) {
|
||||
const result = await pool.query(
|
||||
`UPDATE alert_rules
|
||||
SET enabled = $2
|
||||
WHERE id = $1
|
||||
RETURNING id, address_id, type, threshold, enabled, created_at`,
|
||||
[id, enabled]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove alert rule by ID
|
||||
* @param {number} id - Alert rule ID
|
||||
* @returns {Promise<boolean>} True if deleted, false if not found
|
||||
*/
|
||||
export async function remove(id) {
|
||||
const result = await pool.query(
|
||||
`DELETE FROM alert_rules WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
return result.rowCount > 0;
|
||||
}
|
||||
|
||||
86
backend/models/NotificationConfigModel.js
Normal file
86
backend/models/NotificationConfigModel.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import pool from '../infra/database.js';
|
||||
|
||||
/**
|
||||
* Get notification config for a user
|
||||
* @param {string} user_id - Firebase user ID
|
||||
* @returns {Promise<Object|null>} Notification config or null if not set
|
||||
*/
|
||||
export async function getConfig(user_id) {
|
||||
const result = await pool.query(
|
||||
`SELECT user_id, discord_webhook_url, telegram_chat_id, telegram_bot_token,
|
||||
email, notification_enabled, created_at, updated_at
|
||||
FROM user_notification_configs
|
||||
WHERE user_id = $1`,
|
||||
[user_id]
|
||||
);
|
||||
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update notification config for a user
|
||||
* @param {string} user_id - Firebase user ID
|
||||
* @param {Object} config - Notification configuration
|
||||
* @param {string} [config.discord_webhook_url] - Discord webhook URL
|
||||
* @param {string} [config.telegram_chat_id] - Telegram chat ID
|
||||
* @param {string} [config.email] - Email address
|
||||
* @param {boolean} [config.notification_enabled] - Enable/disable notifications
|
||||
* @returns {Promise<Object>} Updated config
|
||||
*/
|
||||
export async function upsertConfig(user_id, config) {
|
||||
const {
|
||||
discord_webhook_url,
|
||||
telegram_chat_id,
|
||||
telegram_bot_token,
|
||||
email,
|
||||
notification_enabled = true
|
||||
} = config;
|
||||
|
||||
const result = await pool.query(
|
||||
`INSERT INTO user_notification_configs
|
||||
(user_id, discord_webhook_url, telegram_chat_id, telegram_bot_token, email, notification_enabled, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, NOW())
|
||||
ON CONFLICT (user_id)
|
||||
DO UPDATE SET
|
||||
discord_webhook_url = COALESCE($2, user_notification_configs.discord_webhook_url),
|
||||
telegram_chat_id = COALESCE($3, user_notification_configs.telegram_chat_id),
|
||||
telegram_bot_token = COALESCE($4, user_notification_configs.telegram_bot_token),
|
||||
email = COALESCE($5, user_notification_configs.email),
|
||||
notification_enabled = $6,
|
||||
updated_at = NOW()
|
||||
RETURNING user_id, discord_webhook_url, telegram_chat_id, telegram_bot_token,
|
||||
email, notification_enabled, created_at, updated_at`,
|
||||
[user_id, discord_webhook_url, telegram_chat_id, telegram_bot_token, email, notification_enabled]
|
||||
);
|
||||
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete notification config for a user
|
||||
* @param {string} user_id - Firebase user ID
|
||||
* @returns {Promise<boolean>} True if deleted, false if not found
|
||||
*/
|
||||
export async function remove(user_id) {
|
||||
const result = await pool.query(
|
||||
`DELETE FROM user_notification_configs WHERE user_id = $1`,
|
||||
[user_id]
|
||||
);
|
||||
|
||||
return result.rowCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users with notifications enabled (for system use)
|
||||
* @returns {Promise<Array>} Array of user configs
|
||||
*/
|
||||
export async function listEnabled() {
|
||||
const result = await pool.query(
|
||||
`SELECT user_id, discord_webhook_url, telegram_chat_id, email
|
||||
FROM user_notification_configs
|
||||
WHERE notification_enabled = TRUE`
|
||||
);
|
||||
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user