Files
Siro/backend/pricing-engine/src/db/connection.ts
T

173 lines
5.2 KiB
TypeScript

import mysql, { RowDataPacket, ResultSetHeader } from 'mysql2/promise';
import dotenv from 'dotenv';
import path from 'path';
import fs from 'fs';
// مسارات ثابتة ومحددة بدقة لمنع الوقوع في فخاخ ملفات .env الوهمية
const possibleEnvPaths: string[] = [
// مسار السيرفر الفعلي حسب الصورة
'/home/intaleqapp-jordan-siro/.env',
// مسارات احتياطية للبيئة المحلية (جهاز الماك الخاص بك)
path.resolve(__dirname, '../../../.env'),
path.resolve(__dirname, '../../../../.env')
];
let envLoaded = false;
for (const envPath of possibleEnvPaths) {
if (fs.existsSync(envPath)) {
// Basic check to ensure we don't load a dummy Docker env file
const content = fs.readFileSync(envPath, 'utf8');
if (content.includes('DB_HOST=db')) {
console.log(`Skipping trap file: ${envPath}`);
continue;
}
dotenv.config({ path: envPath });
console.log(`Loaded environment from: ${envPath}`);
envLoaded = true;
break;
}
}
if (!envLoaded) {
console.warn('⚠️ No .env file found in the specified exact paths. Falling back to default environment variables.');
}
let mysqlPool: mysql.Pool | null = null;
export async function getMySQL(): Promise<mysql.Pool> {
if (!mysqlPool) {
mysqlPool = mysql.createPool({
host: process.env.DB_PRIMARY_HOST_V2 || process.env.DB_HOST || '127.0.0.1',
port: parseInt(process.env.DB_PORT || '3306'),
database: process.env.DB_PRIMARY_NAME_V2 || process.env.DB_NAME || 'siro',
user: process.env.DB_PRIMARY_USER_V2 || process.env.DB_USER || 'root',
password: process.env.DB_PRIMARY_PASS_V2 || process.env.DB_PASS || '',
waitForConnections: true,
connectionLimit: 5,
queueLimit: 0,
});
}
return mysqlPool;
}
export async function fetchSamples(
pool: mysql.Pool,
competitorName?: string,
countryCode?: string,
hoursBack?: number
): Promise<RowDataPacket[]> {
const conditions: string[] = ['distance_km > 0', 'duration_min > 0', 'price_amount > 0'];
const params: (string | number)[] = [];
if (competitorName) {
conditions.push('competitor_name = ?');
params.push(competitorName);
}
if (countryCode) {
conditions.push('country_code = ?');
params.push(countryCode);
}
if (hoursBack) {
conditions.push('scraped_at >= DATE_SUB(NOW(), INTERVAL ? HOUR)');
params.push(hoursBack);
}
const sql = `SELECT * FROM scraped_competitor_prices WHERE ${conditions.join(' AND ')} ORDER BY id DESC LIMIT 10000`;
const [rows] = await pool.query<RowDataPacket[]>(sql, params);
return rows;
}
export async function saveFormulas(
pool: mysql.Pool,
formulas: Array<{
competitorName: string;
countryCode: string;
tier: string;
baseFare: number;
kmRate: number;
minRate: number;
minFare: number;
rmse: number;
rSquared: number;
sampleCount: number;
surgeMultiplier: number;
peakHours: string;
}>
): Promise<void> {
if (formulas.length === 0) return;
// Batch INSERT with ON DUPLICATE KEY UPDATE
const values = formulas.map(f => `(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`).join(',');
const flatParams: (string | number)[] = [];
for (const f of formulas) {
flatParams.push(
f.competitorName, f.countryCode, f.tier,
f.baseFare, f.kmRate, f.minRate, f.minFare,
f.rmse, f.rSquared, f.surgeMultiplier,
f.sampleCount, f.peakHours
);
}
const sql = `INSERT INTO competitor_secret_formulas
(competitor_name, country_code, tier, base_fare, price_per_km, price_per_min, min_fare, rmse, r_squared, surge_multiplier, sample_size, peak_hours, last_updated)
VALUES ${values}
ON DUPLICATE KEY UPDATE
base_fare = VALUES(base_fare),
price_per_km = VALUES(price_per_km),
price_per_min = VALUES(price_per_min),
min_fare = VALUES(min_fare),
rmse = VALUES(rmse),
r_squared = VALUES(r_squared),
surge_multiplier = VALUES(surge_multiplier),
sample_size = VALUES(sample_size),
peak_hours = VALUES(peak_hours),
last_updated = NOW()`;
await pool.execute(sql, flatParams);
}
export async function saveSurgeInsights(
pool: mysql.Pool,
insights: Array<{
competitorName: string;
countryCode: string;
surgeMultiplier: number;
peakStartHour: number;
peakEndHour: number;
sampleCount: number;
}>
): Promise<void> {
if (insights.length === 0) return;
const values = insights.map(() => `(?, ?, ?, ?, ?, ?, NOW())`).join(',');
const flatParams: (string | number)[] = [];
for (const ins of insights) {
flatParams.push(
ins.competitorName, ins.countryCode,
ins.surgeMultiplier, ins.peakStartHour,
ins.peakEndHour, ins.sampleCount
);
}
const sql = `INSERT INTO competitor_surge_insights
(competitor_name, country_code, surge_multiplier, peak_start_hour, peak_end_hour, sample_count, detected_at)
VALUES ${values}
ON DUPLICATE KEY UPDATE
surge_multiplier = VALUES(surge_multiplier),
sample_count = VALUES(sample_count),
detected_at = NOW()`;
await pool.execute(sql, flatParams);
}
export async function closeConnections(): Promise<void> {
if (mysqlPool) {
await mysqlPool.end();
mysqlPool = null;
}
}