Initial commit
This commit is contained in:
195
dist/shared/db/pool.js
vendored
Normal file
195
dist/shared/db/pool.js
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
import { Pool } from "pg";
|
||||
import { env } from "../../config/env";
|
||||
let pool = null;
|
||||
function buildPoolConfig() {
|
||||
if (env.DATABASE_URL) {
|
||||
return {
|
||||
connectionString: env.DATABASE_URL
|
||||
};
|
||||
}
|
||||
return {
|
||||
host: env.PGHOST,
|
||||
port: env.PGPORT,
|
||||
user: env.PGUSER,
|
||||
password: env.PGPASSWORD,
|
||||
database: env.PGDATABASE
|
||||
};
|
||||
}
|
||||
export function getPool() {
|
||||
if (!pool) {
|
||||
const config = buildPoolConfig();
|
||||
pool = new Pool(config);
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
export async function withClient(work) {
|
||||
const client = await getPool().connect();
|
||||
try {
|
||||
return await work(client);
|
||||
}
|
||||
finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
export async function ensureSchema() {
|
||||
const pool = getPool();
|
||||
await pool.query(MIGRATIONS_SQL);
|
||||
}
|
||||
const MIGRATIONS_SQL = `
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS merchants (
|
||||
id TEXT PRIMARY KEY,
|
||||
merchant_code TEXT NOT NULL UNIQUE,
|
||||
legal_name TEXT NOT NULL,
|
||||
brand_name TEXT,
|
||||
settlement_account_reference TEXT,
|
||||
settlement_account_type TEXT,
|
||||
payout_mode TEXT NOT NULL DEFAULT 'merchant_direct' CHECK (payout_mode IN ('merchant_direct', 'manual')),
|
||||
fee_profile_id TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
|
||||
onboarding_status TEXT NOT NULL DEFAULT 'pending' CHECK (onboarding_status IN ('pending', 'approved', 'rejected')),
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS outlets (
|
||||
id TEXT PRIMARY KEY,
|
||||
merchant_id TEXT NOT NULL REFERENCES merchants (id) ON DELETE CASCADE,
|
||||
outlet_code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS terminals (
|
||||
id TEXT PRIMARY KEY,
|
||||
outlet_id TEXT NOT NULL REFERENCES outlets (id) ON DELETE CASCADE,
|
||||
terminal_code TEXT NOT NULL UNIQUE,
|
||||
qr_mode TEXT NOT NULL DEFAULT 'static' CHECK (qr_mode IN ('static', 'dynamic_mqtt', 'dynamic_api')),
|
||||
partner_reference TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id TEXT PRIMARY KEY,
|
||||
device_code TEXT NOT NULL UNIQUE,
|
||||
serial_number TEXT,
|
||||
vendor TEXT,
|
||||
model TEXT,
|
||||
communication_mode TEXT NOT NULL DEFAULT 'static' CHECK (communication_mode IN ('static', 'mqtt', 'api')),
|
||||
capability_profile_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
auth_method TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
|
||||
last_seen_at TIMESTAMPTZ,
|
||||
firmware_version TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_devices_status_last_seen ON devices (status, last_seen_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS device_bindings (
|
||||
id TEXT PRIMARY KEY,
|
||||
device_id TEXT NOT NULL REFERENCES devices (id) ON DELETE CASCADE,
|
||||
merchant_id TEXT NOT NULL REFERENCES merchants (id) ON DELETE CASCADE,
|
||||
outlet_id TEXT NOT NULL REFERENCES outlets (id) ON DELETE CASCADE,
|
||||
terminal_id TEXT NOT NULL REFERENCES terminals (id) ON DELETE CASCADE,
|
||||
active_flag BOOLEAN NOT NULL DEFAULT false,
|
||||
bound_at TIMESTAMPTZ NOT NULL,
|
||||
unbound_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_device_active_binding ON device_bindings (device_id) WHERE active_flag = TRUE;
|
||||
CREATE INDEX IF NOT EXISTS idx_device_bindings_terminal_active ON device_bindings (terminal_id, active_flag);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS device_heartbeats (
|
||||
id TEXT PRIMARY KEY,
|
||||
device_id TEXT NOT NULL REFERENCES devices (id) ON DELETE CASCADE,
|
||||
timestamp TIMESTAMPTZ NOT NULL,
|
||||
received_at TIMESTAMPTZ NOT NULL,
|
||||
firmware_version TEXT,
|
||||
network_strength INTEGER,
|
||||
battery_level INTEGER,
|
||||
state TEXT,
|
||||
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_device_heartbeats_device ON device_heartbeats (device_id, received_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS device_commands (
|
||||
id TEXT PRIMARY KEY,
|
||||
device_id TEXT NOT NULL REFERENCES devices (id) ON DELETE CASCADE,
|
||||
command TEXT NOT NULL,
|
||||
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
status TEXT NOT NULL DEFAULT 'accepted' CHECK (status IN ('accepted', 'delivered', 'failed', 'timeout')),
|
||||
requested_at TIMESTAMPTZ NOT NULL,
|
||||
acknowledged_at TIMESTAMPTZ,
|
||||
result_payload_json JSONB,
|
||||
reason TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_device_commands_device_request ON device_commands (device_id, requested_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transactions (
|
||||
id TEXT PRIMARY KEY,
|
||||
transaction_code TEXT NOT NULL UNIQUE,
|
||||
merchant_id TEXT NOT NULL REFERENCES merchants (id) ON DELETE CASCADE,
|
||||
outlet_id TEXT NOT NULL REFERENCES outlets (id) ON DELETE CASCADE,
|
||||
terminal_id TEXT NOT NULL REFERENCES terminals (id) ON DELETE CASCADE,
|
||||
device_id TEXT REFERENCES devices (id) ON DELETE SET NULL,
|
||||
qr_mode TEXT NOT NULL DEFAULT 'static' CHECK (qr_mode IN ('static', 'dynamic')),
|
||||
initiation_mode TEXT NOT NULL DEFAULT 'static' CHECK (initiation_mode IN ('static', 'manual', 'dynamic_api', 'dynamic_mqtt')),
|
||||
partner_reference TEXT NOT NULL UNIQUE,
|
||||
amount NUMERIC(20,2) NOT NULL,
|
||||
currency TEXT NOT NULL DEFAULT 'IDR',
|
||||
status TEXT NOT NULL DEFAULT 'initiated' CHECK (status IN ('initiated', 'awaiting_payment', 'paid', 'failed', 'expired', 'reversed')),
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
paid_at TIMESTAMPTZ,
|
||||
expired_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_transactions_partner_ref ON transactions (partner_reference);
|
||||
CREATE INDEX IF NOT EXISTS idx_transactions_merchant_created ON transactions (merchant_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_transactions_status_created ON transactions (status, created_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transaction_events (
|
||||
id TEXT PRIMARY KEY,
|
||||
transaction_id TEXT NOT NULL REFERENCES transactions (id) ON DELETE CASCADE,
|
||||
event_type TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_transaction_events_tx ON transaction_events (transaction_id, created_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id TEXT PRIMARY KEY,
|
||||
transaction_id TEXT NOT NULL REFERENCES transactions (id) ON DELETE CASCADE,
|
||||
device_id TEXT REFERENCES devices (id) ON DELETE SET NULL,
|
||||
delivery_channel TEXT NOT NULL DEFAULT 'mqtt' CHECK (delivery_channel IN ('mqtt')),
|
||||
payload_type TEXT NOT NULL DEFAULT 'payment_success' CHECK (payload_type IN ('payment_success')),
|
||||
delivery_status TEXT NOT NULL CHECK (delivery_status IN ('queued', 'sent', 'acknowledged', 'failed', 'retrying')),
|
||||
retry_count INT NOT NULL DEFAULT 0,
|
||||
ack_status TEXT NOT NULL DEFAULT 'not_needed' CHECK (ack_status IN ('pending', 'received', 'not_supported', 'not_needed')),
|
||||
event_id TEXT NOT NULL,
|
||||
reason TEXT,
|
||||
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
sent_at TIMESTAMPTZ,
|
||||
ack_at TIMESTAMPTZ,
|
||||
next_retry_at TIMESTAMPTZ,
|
||||
CONSTRAINT notifications_unique_tx_event UNIQUE (transaction_id, event_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_device_status ON notifications (device_id, delivery_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_status_created ON notifications (delivery_status, created_at DESC);
|
||||
|
||||
COMMIT;
|
||||
`;
|
||||
Reference in New Issue
Block a user