Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions migrations/20260730_create_advanced_reports.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
-- Migration: 20260730_create_advanced_reports
-- Description: Add tables for advanced reporting engine (Issue #205)

-- Scheduled reports
CREATE TABLE IF NOT EXISTS scheduled_reports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
report_type VARCHAR(30) NOT NULL
CHECK (report_type IN ('pnl', 'settlement', 'aml', 'kyc_compliance', 'custom')),
schedule VARCHAR(20) NOT NULL CHECK (schedule IN ('once', 'daily', 'weekly', 'monthly')),
format VARCHAR(10) NOT NULL CHECK (format IN ('json', 'csv')),
parameters JSONB NOT NULL DEFAULT '{}',
deliver_to_email BOOLEAN NOT NULL DEFAULT false,
recipients JSONB NOT NULL DEFAULT '[]',
is_active BOOLEAN NOT NULL DEFAULT true,
next_run_at TIMESTAMPTZ NOT NULL,
last_run_at TIMESTAMPTZ,
created_by UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_scheduled_reports_next_run ON scheduled_reports (next_run_at) WHERE is_active = true;
CREATE INDEX IF NOT EXISTS idx_scheduled_reports_created_by ON scheduled_reports (created_by);

-- Report archives (generated reports stored for retrieval and retention)
CREATE TABLE IF NOT EXISTS report_archives (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
report_type VARCHAR(30) NOT NULL
CHECK (report_type IN ('pnl', 'settlement', 'aml', 'kyc_compliance', 'custom')),
format VARCHAR(10) NOT NULL CHECK (format IN ('json', 'csv')),
parameters JSONB NOT NULL DEFAULT '{}',
status VARCHAR(20) NOT NULL DEFAULT 'ready'
CHECK (status IN ('pending', 'generating', 'ready', 'failed', 'archived')),
payload JSONB,
generated_by UUID NOT NULL REFERENCES users(id),
generated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ
);

CREATE INDEX IF NOT EXISTS idx_report_archives_type ON report_archives (report_type);
CREATE INDEX IF NOT EXISTS idx_report_archives_generated_at ON report_archives (generated_at DESC);
CREATE INDEX IF NOT EXISTS idx_report_archives_expires_at ON report_archives (expires_at) WHERE expires_at IS NOT NULL;
34 changes: 34 additions & 0 deletions migrations/20260730_create_data_exports.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- Migration: 20260730_create_data_exports
-- Description: Add tables for data export functionality (Issue #202)

-- Scheduled export jobs
CREATE TABLE IF NOT EXISTS scheduled_exports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
format VARCHAR(10) NOT NULL CHECK (format IN ('csv', 'json', 'pdf')),
schedule VARCHAR(20) NOT NULL CHECK (schedule IN ('once', 'daily', 'weekly', 'monthly')),
filters JSONB NOT NULL DEFAULT '{}',
deliver_to_email BOOLEAN NOT NULL DEFAULT false,
template_id VARCHAR(100),
next_run_at TIMESTAMPTZ NOT NULL,
last_run_at TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_scheduled_exports_user_id ON scheduled_exports (user_id);
CREATE INDEX IF NOT EXISTS idx_scheduled_exports_next_run ON scheduled_exports (next_run_at) WHERE is_active = true;

-- Export access log for audit trail and GDPR compliance
CREATE TABLE IF NOT EXISTS export_access_log (
id BIGSERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
format VARCHAR(10) NOT NULL,
filters JSONB NOT NULL DEFAULT '{}',
row_count INTEGER,
ip_address INET,
accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_export_access_log_user_id ON export_access_log (user_id);
CREATE INDEX IF NOT EXISTS idx_export_access_log_accessed_at ON export_access_log (accessed_at DESC);
42 changes: 42 additions & 0 deletions migrations/20260730_create_provider_fee_configs.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- Migration: 20260730_create_provider_fee_configs
-- Description: Provider-specific fee configurations with versioning and approval workflow (Issue #200)

-- Provider-specific fee configurations with versioning
CREATE TABLE IF NOT EXISTS provider_fee_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
provider VARCHAR(20) NOT NULL,
fee_percentage DECIMAL(7,4) NOT NULL CHECK (fee_percentage >= 0 AND fee_percentage <= 100),
fee_minimum DECIMAL(20,7) NOT NULL CHECK (fee_minimum >= 0),
fee_maximum DECIMAL(20,7) NOT NULL CHECK (fee_maximum >= fee_minimum),
is_active BOOLEAN NOT NULL DEFAULT false,
version INTEGER NOT NULL DEFAULT 1,
description TEXT,
created_by UUID NOT NULL REFERENCES users(id),
updated_by UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

UNIQUE (provider, version)
);

CREATE INDEX IF NOT EXISTS idx_provider_fee_configs_provider ON provider_fee_configs (provider);
CREATE INDEX IF NOT EXISTS idx_provider_fee_configs_provider_active ON provider_fee_configs (provider, is_active);

-- Fee change approval workflow
CREATE TABLE IF NOT EXISTS fee_change_proposals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
provider VARCHAR(20), -- null = global config change
fee_config_id UUID REFERENCES fee_configurations(id) ON DELETE SET NULL,
proposed_changes JSONB NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'approved', 'rejected', 'superseded')),
proposed_by UUID NOT NULL REFERENCES users(id),
reviewed_by UUID REFERENCES users(id),
review_note TEXT,
proposed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
reviewed_at TIMESTAMPTZ
);

CREATE INDEX IF NOT EXISTS idx_fee_proposals_status ON fee_change_proposals (status);
CREATE INDEX IF NOT EXISTS idx_fee_proposals_proposed_by ON fee_change_proposals (proposed_by);
CREATE INDEX IF NOT EXISTS idx_fee_proposals_proposed_at ON fee_change_proposals (proposed_at DESC);
51 changes: 51 additions & 0 deletions migrations/20260730_create_provider_load_balancer.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
-- Migration: 20260730_create_provider_load_balancer
-- Description: Add tables for provider load balancing (Issue #203)

-- Provider capacity configuration
CREATE TABLE IF NOT EXISTS provider_capacity_config (
provider VARCHAR(20) PRIMARY KEY,
max_concurrent_requests INTEGER NOT NULL DEFAULT 100,
weight INTEGER NOT NULL DEFAULT 33 CHECK (weight BETWEEN 1 AND 100),
is_enabled BOOLEAN NOT NULL DEFAULT true,
health_status VARCHAR(20) NOT NULL DEFAULT 'healthy'
CHECK (health_status IN ('healthy', 'degraded', 'unhealthy')),
consecutive_failures INTEGER NOT NULL DEFAULT 0,
last_health_check TIMESTAMPTZ,
avg_response_time_ms INTEGER,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Seed default providers
INSERT INTO provider_capacity_config (provider, weight) VALUES
('mtn', 34),
('airtel', 33),
('orange', 33)
ON CONFLICT (provider) DO NOTHING;

-- Load balancer global configuration
CREATE TABLE IF NOT EXISTS load_balancer_config (
key VARCHAR(50) PRIMARY KEY,
value JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Seed default config
INSERT INTO load_balancer_config (key, value)
VALUES (
'default',
'{"strategy":"round_robin","healthCheckIntervalMs":30000,"failureThreshold":3,"recoveryThreshold":2,"stickySessionTtlSeconds":300}'::jsonb
)
ON CONFLICT (key) DO NOTHING;

-- Per-request metrics for load balancer observability
CREATE TABLE IF NOT EXISTS provider_load_balancer_metrics (
id BIGSERIAL PRIMARY KEY,
provider VARCHAR(20) NOT NULL,
success BOOLEAN NOT NULL,
duration_ms INTEGER,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_lb_metrics_provider ON provider_load_balancer_metrics (provider);
CREATE INDEX IF NOT EXISTS idx_lb_metrics_recorded_at ON provider_load_balancer_metrics (recorded_at);
CREATE INDEX IF NOT EXISTS idx_lb_metrics_provider_ts ON provider_load_balancer_metrics (provider, recorded_at DESC);
12 changes: 12 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ import settingsRoutes from "./routes/settings";
import { statementsRoutes } from "./routes/statements";
import { paymentLinkRoutes } from "./routes/paymentLinkRoutes.js";
import providerStatusRouter from "./routes/providerStatus";
import providerLoadBalancerRouter from "./routes/providerLoadBalancer";
import providerFeesRouter from "./routes/providerFees";
import dataExportsRouter from "./routes/dataExports";
import { advancedReportsRouter } from "./routes/advancedReports";
import { startHeartbeatService, stopHeartbeatService } from "./services/heartbeatService";
import { startStellarExporter } from "./services/stellarExporter";

Expand Down Expand Up @@ -377,6 +381,14 @@ app.use("/api/fees", feesRoutes);
app.use("/api/users", userRoutes);
app.use("/api/kyc", createKYCRoutes(pool));
app.use("/api/fee-strategies", feeStrategiesRouter);
// Issue #200 — Provider Fee Configuration (provider-specific fees, versioning, simulation, analytics)
app.use("/api/fees", providerFeesRouter);
// Issue #202 — Data Export (PDF, scheduled, GDPR, access logging)
app.use("/api/exports", dataExportsRouter);
// Issue #203 — Provider Load Balancing
app.use("/api/providers/load-balancer", providerLoadBalancerRouter);
// Issue #205 — Advanced Reporting (P&L, settlement, KYC compliance, custom builder, archive)
app.use("/api/reports", advancedReportsRouter);
app.use("/api/cross-chain", crossChainRouter);
app.use("/api/stellar", stellarRouter);
app.use("/api/reconciliation", reconciliationRoutes);
Expand Down
Loading
Loading