226 lines
7.4 KiB
JavaScript
226 lines
7.4 KiB
JavaScript
let db;
|
|
let settingsModule;
|
|
let emailModule;
|
|
let telegramModule;
|
|
|
|
let jwt = null;
|
|
let syncTimer = null;
|
|
let running = false;
|
|
|
|
const PRIMARY_URL = (process.env.PRIMARY_URL || '').replace(/\/+$/, '');
|
|
const PRIMARY_LOGIN = process.env.PRIMARY_LOGIN || '';
|
|
const PRIMARY_PASSWORD = process.env.PRIMARY_PASSWORD || '';
|
|
const PRIMARY_SYNC_INTERVAL = parseInt(process.env.PRIMARY_SYNC_INTERVAL) || 60;
|
|
|
|
function init(database, settings, email, telegram) {
|
|
db = database;
|
|
settingsModule = settings;
|
|
emailModule = email;
|
|
telegramModule = telegram;
|
|
}
|
|
|
|
function start() {
|
|
if (!PRIMARY_URL || !PRIMARY_LOGIN || !PRIMARY_PASSWORD) {
|
|
console.log('[SYNC] PRIMARY_URL/LOGIN/PASSWORD not set — replica mode disabled');
|
|
return;
|
|
}
|
|
console.log(`[SYNC] Replica mode: pulling from ${PRIMARY_URL} every ${PRIMARY_SYNC_INTERVAL}s`);
|
|
loginAndSync();
|
|
}
|
|
|
|
function stop() {
|
|
running = false;
|
|
if (syncTimer) {
|
|
clearTimeout(syncTimer);
|
|
syncTimer = null;
|
|
}
|
|
}
|
|
|
|
function loginAndSync() {
|
|
login((err) => {
|
|
if (err) {
|
|
console.error(`[SYNC] Login failed: ${err.message}. Retrying in 30s...`);
|
|
syncTimer = setTimeout(loginAndSync, 30000);
|
|
return;
|
|
}
|
|
running = true;
|
|
settingsModule.get('sync_first_sync_done', (err, val) => {
|
|
if (err || val !== 'true') {
|
|
firstSync();
|
|
} else {
|
|
syncLoop();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function login(callback) {
|
|
fetch(`${PRIMARY_URL}/api/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ login: PRIMARY_LOGIN, password: PRIMARY_PASSWORD })
|
|
})
|
|
.then(res => {
|
|
if (!res.ok) return res.json().then(d => { throw new Error(`HTTP ${res.status}: ${d.error || 'unknown'}`); });
|
|
return res.json();
|
|
})
|
|
.then(data => {
|
|
jwt = data.token;
|
|
console.log('[SYNC] Logged in to primary server');
|
|
callback(null);
|
|
})
|
|
.catch(err => {
|
|
callback(err);
|
|
});
|
|
}
|
|
|
|
function firstSync() {
|
|
console.log('[SYNC] First sync — pulling ALL bookings...');
|
|
apiGet('/api/admin/bookings?limit=99999', (err, result) => {
|
|
if (err) {
|
|
console.error(`[SYNC] First sync failed: ${err.message}. Retrying in 30s...`);
|
|
syncTimer = setTimeout(firstSync, 30000);
|
|
return;
|
|
}
|
|
const bookings = result.data || [];
|
|
insertBatch(bookings, false, (insertCount) => {
|
|
console.log(`[SYNC] First sync done: ${insertCount} bookings imported (no notifications)`);
|
|
settingsModule.set('sync_first_sync_done', 'true', (err) => {
|
|
if (err) console.error('[SYNC] Failed to save sync_first_sync_done:', err);
|
|
});
|
|
saveLastSync(() => {
|
|
scheduleNext();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function syncLoop() {
|
|
settingsModule.get('sync_last_sync_at', (err, lastSyncAt) => {
|
|
if (err) {
|
|
console.error('[SYNC] Failed to read last_sync_at:', err);
|
|
scheduleNext();
|
|
return;
|
|
}
|
|
const since = lastSyncAt ? encodeURIComponent(lastSyncAt) : '';
|
|
const url = since ? `/api/admin/bookings?limit=99999&since=${since}` : '/api/admin/bookings?limit=99999';
|
|
|
|
apiGet(url, (err, result) => {
|
|
if (err) {
|
|
if (err.message.includes('401') || err.message.includes('token') || err.message.includes('jwt')) {
|
|
console.log('[SYNC] Token expired, re-logging in...');
|
|
loginAndSync();
|
|
return;
|
|
}
|
|
console.error(`[SYNC] Sync failed: ${err.message}. Retry at next interval.`);
|
|
scheduleNext();
|
|
return;
|
|
}
|
|
const bookings = result.data || [];
|
|
if (bookings.length === 0) {
|
|
saveLastSync(() => scheduleNext());
|
|
return;
|
|
}
|
|
|
|
insertBatch(bookings, true, (insertCount, notifyCount) => {
|
|
console.log(`[SYNC] Pulled ${bookings.length}, new: ${insertCount}, notified: ${notifyCount}`);
|
|
saveLastSync(() => scheduleNext());
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function scheduleNext() {
|
|
if (!running) return;
|
|
syncTimer = setTimeout(syncLoop, PRIMARY_SYNC_INTERVAL * 1000);
|
|
}
|
|
|
|
function saveLastSync(callback) {
|
|
const now = new Date().toISOString();
|
|
settingsModule.set('sync_last_sync_at', now, (err) => {
|
|
if (err) console.error('[SYNC] Failed to save last_sync_at:', err);
|
|
callback();
|
|
});
|
|
}
|
|
|
|
function apiGet(path, callback) {
|
|
if (!jwt) return callback(new Error('Not authenticated'));
|
|
|
|
fetch(PRIMARY_URL + path, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': `Bearer ${jwt}`,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(res => {
|
|
if (!res.ok) {
|
|
if (res.status === 401) {
|
|
jwt = null;
|
|
return res.json().then(d => { throw Object.assign(new Error('401 Unauthorized'), { code: 401 }); });
|
|
}
|
|
return res.json().then(d => { throw new Error(`HTTP ${res.status}: ${d.error || 'unknown'}`); });
|
|
}
|
|
return res.json();
|
|
})
|
|
.then(data => callback(null, data))
|
|
.catch(err => callback(err));
|
|
}
|
|
|
|
function insertBatch(bookings, withNotifications, callback) {
|
|
let inserted = 0;
|
|
let notified = 0;
|
|
const total = bookings.length;
|
|
const stmt = db.prepare(`INSERT OR IGNORE INTO bookings (remote_id, name, phone, adults, children, checkin_date, checkout_date, wishes, status, room_type, room_id, base_price, discount_percent, discount_amount, total_price, promocode_id, comment, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
|
|
bookings.forEach((b, index) => {
|
|
stmt.run(
|
|
b.id, b.name, b.phone, b.adults, b.children,
|
|
b.checkin_date, b.checkout_date, b.wishes,
|
|
b.status || 'новая', b.room_type, b.room_id,
|
|
b.base_price, b.discount_percent, b.discount_amount,
|
|
b.total_price, b.promocode_id, b.comment, b.created_at,
|
|
function(err) {
|
|
if (err) {
|
|
console.error('[SYNC] Insert error:', err.message);
|
|
} else if (this.changes > 0) {
|
|
inserted++;
|
|
if (withNotifications && b.status !== 'отменена') {
|
|
sendNotifications(b);
|
|
notified++;
|
|
}
|
|
}
|
|
|
|
if (index === total - 1) {
|
|
stmt.finalize();
|
|
callback(inserted, notified);
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
if (total === 0) {
|
|
stmt.finalize();
|
|
callback(0, 0);
|
|
}
|
|
}
|
|
|
|
function sendNotifications(booking) {
|
|
try {
|
|
if (telegramModule && typeof telegramModule.sendBookingNotification === 'function') {
|
|
telegramModule.sendBookingNotification(booking);
|
|
}
|
|
} catch(e) {
|
|
console.error('[SYNC] Telegram notify error:', e.message);
|
|
}
|
|
try {
|
|
if (emailModule && typeof emailModule.sendBookingConfirmation === 'function') {
|
|
emailModule.sendBookingConfirmation(booking);
|
|
}
|
|
} catch(e) {
|
|
console.error('[SYNC] Email notify error:', e.message);
|
|
}
|
|
}
|
|
|
|
module.exports = { init, start, stop };
|