173 lines
5.2 KiB
TypeScript
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;
|
|
}
|
|
}
|