vantuz 3.5.18 → 4.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +9 -9
- package/config.js +6 -6
- package/core/agent-loop.js +6 -6
- package/core/agent.js +6 -6
- package/core/ai-copilot.js +461 -0
- package/core/ai-provider.js +60 -10
- package/core/automation.js +8 -8
- package/core/cache-manager.js +232 -0
- package/core/channels.js +8 -8
- package/core/dashboard.js +5 -5
- package/core/database-manager.js +331 -0
- package/core/database.js +124 -83
- package/core/eia-brain.js +2 -2
- package/core/eia-monitor.js +7 -7
- package/core/engine.js +24 -24
- package/core/error-handler.js +203 -0
- package/core/gateway.js +9 -9
- package/core/learning.js +7 -7
- package/core/license-manager.js +1 -1
- package/core/license.js +6 -6
- package/core/logger.js +228 -0
- package/core/marketplace-adapter.js +5 -5
- package/core/memory.js +6 -6
- package/core/multi-agent.js +180 -0
- package/core/openclaw-bridge.js +6 -6
- package/core/queue.js +3 -3
- package/core/scheduler.js +6 -6
- package/core/scrapers/Scraper.js +1 -1
- package/core/scrapers/TrendyolScraper.js +1 -1
- package/core/self-healer.js +8 -6
- package/core/unified-product.js +8 -8
- package/core/vector-db.js +5 -5
- package/core/vision-service.js +5 -5
- package/desktop/index.html +2804 -0
- package/desktop/main.js +97 -0
- package/desktop/preload.js +30 -0
- package/dev.sh +5 -0
- package/index.js +484 -116
- package/modules/crm/sentiment-crm.js +4 -4
- package/modules/healer/listing-healer.js +2 -2
- package/modules/oracle/predictor.js +5 -5
- package/modules/researcher/agent.js +4 -4
- package/modules/war-room/competitor-tracker.js +5 -5
- package/modules/war-room/pricing-engine.js +5 -5
- package/nodes/warehouse.js +5 -5
- package/onboard.js +1 -1
- package/package.json +11 -3
- package/pkg.json +26 -0
- package/plugins/vantuz/index.js +16 -17
- package/plugins/vantuz/memory/hippocampus.js +3 -3
- package/plugins/vantuz/platforms/_request.js +1 -1
- package/plugins/vantuz/platforms/_template.js +2 -2
- package/plugins/vantuz/platforms/amazon.js +3 -3
- package/plugins/vantuz/platforms/ciceksepeti.js +2 -2
- package/plugins/vantuz/platforms/hepsiburada.js +2 -2
- package/plugins/vantuz/platforms/index.js +9 -24
- package/plugins/vantuz/platforms/n11.js +3 -3
- package/plugins/vantuz/platforms/pazarama.js +2 -2
- package/plugins/vantuz/platforms/pttavm.js +2 -2
- package/plugins/vantuz/platforms/trendyol.js +3 -3
- package/plugins/vantuz/services/alerts.js +1 -1
- package/plugins/vantuz/services/scheduler.js +1 -1
- package/plugins/vantuz/tools/nl-parser.js +1 -1
- package/plugins/vantuz/tools/quick-report.js +2 -2
- package/plugins/vantuz/tools/repricer.js +1 -1
- package/plugins/vantuz/tools/vision.js +3 -3
- package/server/app.js +8 -8
- package/DOCS_TR.md +0 -80
- package/modules/team/agents/base.js +0 -92
- package/modules/team/agents/dev.js +0 -33
- package/modules/team/agents/josh.js +0 -40
- package/modules/team/agents/marketing.js +0 -33
- package/modules/team/agents/milo.js +0 -36
- package/modules/team/index.js +0 -78
- package/modules/team/shared-memory.js +0 -87
- package/n11docs.md +0 -1680
- package/openclawdocs.md +0 -3
- package/vantuz.sqlite +0 -0
- package/workspace/AGENTS.md +0 -73
- package/workspace/BRAND.md +0 -29
- package/workspace/SOUL.md +0 -72
- package/workspace/team/DECISIONS.md +0 -3
- package/workspace/team/GOALS.md +0 -3
- package/workspace/team/PROJECT_STATUS.md +0 -3
- package/workspace/team/agents/dev/SOUL.md +0 -12
- package/workspace/team/agents/josh/SOUL.md +0 -12
- package/workspace/team/agents/marketing/SOUL.md +0 -12
- package/workspace/team/agents/milo/SOUL.md +0 -12
package/core/automation.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// core/automation.js
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const { log, chat as aiChat } = require('./ai-provider.js');
|
|
7
|
+
const { getScheduler } = require('./scheduler.js');
|
|
8
8
|
|
|
9
9
|
const VANTUZ_HOME = path.join(os.homedir(), '.vantuz');
|
|
10
10
|
const CONFIG_JSON = path.join(VANTUZ_HOME, 'config.json');
|
|
@@ -212,7 +212,7 @@ function fallbackPlan(message) {
|
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
|
|
215
|
+
module.exports = AutomationManager {
|
|
216
216
|
constructor(engine) {
|
|
217
217
|
this.engine = engine;
|
|
218
218
|
this.scheduler = getScheduler();
|
|
@@ -520,4 +520,4 @@ export class AutomationManager {
|
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
-
|
|
523
|
+
module.exports = AutomationManager;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🐙 VANTUZ Cache Manager - Database Query Caching
|
|
3
|
+
* Sık kullanılan veriler için in-memory cache
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const logger = require('./logger');
|
|
7
|
+
|
|
8
|
+
class CacheManager {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.ttl = options.ttl || 300000; // 5 dakika varsayılan
|
|
11
|
+
this.maxSize = options.maxSize || 100; // Maksimum entry sayısı
|
|
12
|
+
this.cache = new Map();
|
|
13
|
+
this.stats = { hits: 0, misses: 0 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Cache'e veri ekle
|
|
18
|
+
*/
|
|
19
|
+
set(key, value, ttl = null) {
|
|
20
|
+
const effectiveTtl = ttl || this.ttl;
|
|
21
|
+
|
|
22
|
+
// Boyut kontrolü
|
|
23
|
+
if (this.cache.size >= this.maxSize) {
|
|
24
|
+
// En eski entry'yi sil
|
|
25
|
+
const firstKey = this.cache.keys().next().value;
|
|
26
|
+
this.cache.delete(firstKey);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.cache.set(key, {
|
|
30
|
+
value,
|
|
31
|
+
expires: Date.now() + effectiveTtl
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
logger.debug('Cache set', { key, ttl: effectiveTtl });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Cache'den veri getir
|
|
39
|
+
*/
|
|
40
|
+
get(key) {
|
|
41
|
+
const entry = this.cache.get(key);
|
|
42
|
+
|
|
43
|
+
if (!entry) {
|
|
44
|
+
this.stats.misses++;
|
|
45
|
+
logger.debug('Cache miss', { key });
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Süre dolmuş mu?
|
|
50
|
+
if (Date.now() > entry.expires) {
|
|
51
|
+
this.cache.delete(key);
|
|
52
|
+
this.stats.misses++;
|
|
53
|
+
logger.debug('Cache expired', { key });
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.stats.hits++;
|
|
58
|
+
logger.debug('Cache hit', { key });
|
|
59
|
+
return entry.value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cache'den veri sil
|
|
64
|
+
*/
|
|
65
|
+
delete(key) {
|
|
66
|
+
return this.cache.delete(key);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Tüm cache'i temizle
|
|
71
|
+
*/
|
|
72
|
+
clear() {
|
|
73
|
+
this.cache.clear();
|
|
74
|
+
logger.info('Cache temizlendi');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Süresi dolmuş entry'leri temizle
|
|
79
|
+
*/
|
|
80
|
+
cleanup() {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
let cleaned = 0;
|
|
83
|
+
|
|
84
|
+
for (const [key, entry] of this.cache) {
|
|
85
|
+
if (now > entry.expires) {
|
|
86
|
+
this.cache.delete(key);
|
|
87
|
+
cleaned++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
logger.info('Cache cleanup', { cleaned });
|
|
92
|
+
return cleaned;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Cache istatistikleri
|
|
97
|
+
*/
|
|
98
|
+
getStats() {
|
|
99
|
+
const total = this.stats.hits + this.stats.misses;
|
|
100
|
+
const hitRate = total > 0 ? (this.stats.hits / total * 100).toFixed(2) : 0;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
size: this.cache.size,
|
|
104
|
+
maxSize: this.maxSize,
|
|
105
|
+
hits: this.stats.hits,
|
|
106
|
+
misses: this.stats.misses,
|
|
107
|
+
hitRate: `${hitRate}%`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Query cache decorator
|
|
114
|
+
*/
|
|
115
|
+
function cachedQuery(cache, keyPrefix = 'query') {
|
|
116
|
+
return async function(queryFn, key, ttl) {
|
|
117
|
+
const cacheKey = `${keyPrefix}:${key}`;
|
|
118
|
+
const cached = cache.get(cacheKey);
|
|
119
|
+
|
|
120
|
+
if (cached !== null) {
|
|
121
|
+
return cached;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const result = await queryFn();
|
|
125
|
+
cache.set(cacheKey, result, ttl);
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Debounce fonksiyonu - çoklu istekleri birleştir
|
|
132
|
+
*/
|
|
133
|
+
function debounce(fn, delay = 300) {
|
|
134
|
+
let timeoutId = null;
|
|
135
|
+
let pendingArgs = null;
|
|
136
|
+
|
|
137
|
+
return function(...args) {
|
|
138
|
+
pendingArgs = args;
|
|
139
|
+
|
|
140
|
+
if (timeoutId) {
|
|
141
|
+
clearTimeout(timeoutId);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
timeoutId = setTimeout(() => {
|
|
145
|
+
if (pendingArgs !== null) {
|
|
146
|
+
fn.apply(this, pendingArgs);
|
|
147
|
+
pendingArgs = null;
|
|
148
|
+
}
|
|
149
|
+
timeoutId = null;
|
|
150
|
+
}, delay);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Throttle fonksiyonu - istekleri sınırla
|
|
156
|
+
*/
|
|
157
|
+
function throttle(fn, limit = 10) {
|
|
158
|
+
let inThrottle = false;
|
|
159
|
+
let lastResult = null;
|
|
160
|
+
|
|
161
|
+
return function(...args) {
|
|
162
|
+
if (!inThrottle) {
|
|
163
|
+
inThrottle = true;
|
|
164
|
+
lastResult = fn.apply(this, args);
|
|
165
|
+
setTimeout(() => inThrottle = false, limit);
|
|
166
|
+
}
|
|
167
|
+
return lastResult;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Lazy loader - sayfa açılınca veri çek
|
|
173
|
+
*/
|
|
174
|
+
function createLazyLoader(loadFn) {
|
|
175
|
+
let isLoaded = false;
|
|
176
|
+
let data = null;
|
|
177
|
+
let loading = false;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
async get() {
|
|
181
|
+
if (isLoaded && data) {
|
|
182
|
+
return data;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (loading) {
|
|
186
|
+
// Zaten yükleniyorsa bekle
|
|
187
|
+
while (loading) {
|
|
188
|
+
await new Promise(r => setTimeout(r, 50));
|
|
189
|
+
}
|
|
190
|
+
return data;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
loading = true;
|
|
194
|
+
try {
|
|
195
|
+
data = await loadFn();
|
|
196
|
+
isLoaded = true;
|
|
197
|
+
logger.debug('Lazy load tamamlandı');
|
|
198
|
+
return data;
|
|
199
|
+
} finally {
|
|
200
|
+
loading = false;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
refresh() {
|
|
205
|
+
isLoaded = false;
|
|
206
|
+
data = null;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Global cache instance
|
|
212
|
+
const globalCache = new CacheManager({
|
|
213
|
+
ttl: 300000, // 5 dakika
|
|
214
|
+
maxSize: 200
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Periyodik cleanup
|
|
218
|
+
setInterval(() => {
|
|
219
|
+
const cleaned = globalCache.cleanup();
|
|
220
|
+
if (cleaned > 0) {
|
|
221
|
+
logger.info('Periyodik cache cleanup', { cleaned });
|
|
222
|
+
}
|
|
223
|
+
}, 60000); // Her dakika
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
CacheManager,
|
|
227
|
+
cachedQuery,
|
|
228
|
+
debounce,
|
|
229
|
+
throttle,
|
|
230
|
+
createLazyLoader,
|
|
231
|
+
globalCache
|
|
232
|
+
};
|
package/core/channels.js
CHANGED
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
* - Telegram (Bot API / Gateway)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const { log } = require('./ai-provider.js');
|
|
14
|
+
const { getGateway } = require('./gateway.js');
|
|
15
15
|
|
|
16
16
|
const VANTUZ_HOME = path.join(os.homedir(), '.vantuz');
|
|
17
17
|
const CONFIG_PATH = path.join(VANTUZ_HOME, '.env');
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
module.exports = ChannelManager {
|
|
20
20
|
constructor() {
|
|
21
21
|
this.gateway = null;
|
|
22
22
|
this.status = {
|
|
@@ -158,7 +158,7 @@ export class ChannelManager {
|
|
|
158
158
|
// Singleton
|
|
159
159
|
let managerInstance = null;
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
module.exports.getChannelManager = async function() {
|
|
162
162
|
if (!managerInstance) {
|
|
163
163
|
managerInstance = new ChannelManager();
|
|
164
164
|
await managerInstance.initAll();
|
|
@@ -166,4 +166,4 @@ export async function getChannelManager() {
|
|
|
166
166
|
return managerInstance;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
module.exports = ChannelManager;
|
package/core/dashboard.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// System Health Dashboard — Vantuz OS V2 Control Tower
|
|
3
3
|
// Aggregates status from all modules into a single real-time overview.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { log } = require('./ai-provider.js');
|
|
7
7
|
|
|
8
8
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
9
|
// UPTIME TRACKER
|
|
@@ -195,7 +195,7 @@ class Dashboard {
|
|
|
195
195
|
|
|
196
196
|
let dashboardInstance = null;
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
module.exports.getDashboard = function() {
|
|
199
199
|
if (!dashboardInstance) {
|
|
200
200
|
dashboardInstance = new Dashboard();
|
|
201
201
|
}
|
|
@@ -206,7 +206,7 @@ export function getDashboard() {
|
|
|
206
206
|
* Helper: Wire up all V2 modules to the dashboard.
|
|
207
207
|
* Call this after all modules are initialized.
|
|
208
208
|
*/
|
|
209
|
-
|
|
209
|
+
module.exports.w = functionireModulesToDashboard(refs = {}) {
|
|
210
210
|
const dash = getDashboard();
|
|
211
211
|
|
|
212
212
|
if (refs.agentLoop) dash.registerModule('AgentLoop', () => refs.agentLoop.getStatus());
|
|
@@ -227,4 +227,4 @@ export function wireModulesToDashboard(refs = {}) {
|
|
|
227
227
|
return dash;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
module.exports = Dashboard;
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🐙 VANTUZ Database Manager - Auto-migration, Backup & Integrity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
const logger = require('./logger');
|
|
9
|
+
const { withErrorHandler } = require('./error-handler');
|
|
10
|
+
|
|
11
|
+
class DatabaseManager {
|
|
12
|
+
constructor(db, dbPath) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
this.dbPath = dbPath;
|
|
15
|
+
this.backupDir = path.join(path.dirname(dbPath), 'backups');
|
|
16
|
+
this.migrationsDir = path.join(__dirname, 'migrations');
|
|
17
|
+
this.schemaVersion = 1;
|
|
18
|
+
|
|
19
|
+
// Backup klasörü yoksa oluştur
|
|
20
|
+
if (!fs.existsSync(this.backupDir)) {
|
|
21
|
+
fs.mkdirSync(this.backupDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Auto-migration sistemi
|
|
27
|
+
*/
|
|
28
|
+
async runMigrations() {
|
|
29
|
+
logger.info('Migration kontrol ediliyor', { currentVersion: this.schemaVersion });
|
|
30
|
+
|
|
31
|
+
const migrations = this.getAvailableMigrations();
|
|
32
|
+
|
|
33
|
+
for (const migration of migrations) {
|
|
34
|
+
if (migration.version > this.schemaVersion) {
|
|
35
|
+
logger.info(`Migration çalıştırılıyor: v${migration.version}`, { name: migration.name });
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await migration.up(this.db);
|
|
39
|
+
this.schemaVersion = migration.version;
|
|
40
|
+
logger.info(`Migration tamamlandı: v${migration.version}`);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
logger.error('Migration hatası', {
|
|
43
|
+
version: migration.version,
|
|
44
|
+
error: error.message
|
|
45
|
+
});
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Mevcut migration'ları listele
|
|
54
|
+
*/
|
|
55
|
+
getAvailableMigrations() {
|
|
56
|
+
const migrations = [];
|
|
57
|
+
|
|
58
|
+
if (fs.existsSync(this.migrationsDir)) {
|
|
59
|
+
const files = fs.readdirSync(this.migrationsDir)
|
|
60
|
+
.filter(f => f.endsWith('.js') && f.startsWith('migration-'))
|
|
61
|
+
.sort();
|
|
62
|
+
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
const match = file.match(/migration-v(\d+)-(.+)\.js/);
|
|
65
|
+
if (match) {
|
|
66
|
+
const migration = require(path.join(this.migrationsDir, file));
|
|
67
|
+
migrations.push({
|
|
68
|
+
version: parseInt(match[1]),
|
|
69
|
+
name: match[2],
|
|
70
|
+
up: migration.up,
|
|
71
|
+
down: migration.down
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return migrations;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Manuel migration kaydet (tabloya)
|
|
82
|
+
*/
|
|
83
|
+
async recordMigration(version, name) {
|
|
84
|
+
const migrationsTable = `
|
|
85
|
+
CREATE TABLE IF NOT EXISTS SchemaMigrations (
|
|
86
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
87
|
+
version INTEGER UNIQUE,
|
|
88
|
+
name TEXT,
|
|
89
|
+
appliedAt TEXT
|
|
90
|
+
)
|
|
91
|
+
`;
|
|
92
|
+
this.db.run(migrationsTable);
|
|
93
|
+
this.db.run(
|
|
94
|
+
'INSERT OR IGNORE INTO SchemaMigrations (version, name, appliedAt) VALUES (?, ?, ?)',
|
|
95
|
+
[version, name, new Date().toISOString()]
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Günlük backup
|
|
101
|
+
*/
|
|
102
|
+
async createBackup(reason = 'scheduled') {
|
|
103
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
104
|
+
const backupFile = path.join(this.backupDir, `vantuz-${timestamp}.sqlite`);
|
|
105
|
+
|
|
106
|
+
logger.info('Backup oluşturuluyor', { reason, file: backupFile });
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// DB export
|
|
110
|
+
const data = this.db.export();
|
|
111
|
+
const buffer = Buffer.from(data);
|
|
112
|
+
|
|
113
|
+
// Hash hesapla
|
|
114
|
+
const hash = crypto.createHash('sha256').update(buffer).digest('hex');
|
|
115
|
+
|
|
116
|
+
// Backup dosyasına yaz
|
|
117
|
+
fs.writeFileSync(backupFile, buffer);
|
|
118
|
+
|
|
119
|
+
// Metadata
|
|
120
|
+
const meta = {
|
|
121
|
+
date: timestamp,
|
|
122
|
+
reason,
|
|
123
|
+
size: buffer.length,
|
|
124
|
+
hash,
|
|
125
|
+
version: this.schemaVersion
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
fs.writeFileSync(backupFile + '.meta.json', JSON.stringify(meta, null, 2));
|
|
129
|
+
|
|
130
|
+
logger.info('Backup tamamlandı', { file: backupFile, size: buffer.length });
|
|
131
|
+
return { success: true, file: backupFile, hash };
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.error('Backup hatası', { error: error.message });
|
|
134
|
+
return { success: false, error: error.message };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Veri bütünlüğü kontrolü
|
|
140
|
+
*/
|
|
141
|
+
async checkIntegrity() {
|
|
142
|
+
logger.info('Veri bütünlüğü kontrol ediliyor...');
|
|
143
|
+
|
|
144
|
+
const issues = [];
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// 1. DB dosyası mevcut mu?
|
|
148
|
+
if (!fs.existsSync(this.dbPath)) {
|
|
149
|
+
issues.push({ type: 'missing', message: 'Veritabanı dosyası bulunamadı' });
|
|
150
|
+
return { valid: false, issues };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 2. Hash kontrolü (son backup ile)
|
|
154
|
+
const latestBackup = this.getLatestBackup();
|
|
155
|
+
if (latestBackup) {
|
|
156
|
+
const currentHash = this.calculateHash();
|
|
157
|
+
if (currentHash !== latestBackup.hash) {
|
|
158
|
+
// Değişiklik var, bu normal olabilir
|
|
159
|
+
logger.info('Veritabanı değişikliği tespit edildi', {
|
|
160
|
+
oldHash: latestBackup.hash.substring(0, 8),
|
|
161
|
+
newHash: currentHash.substring(0, 8)
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 3. Temel tabloları kontrol et
|
|
167
|
+
const tables = ['Stores', 'Products', 'Orders', 'Insights'];
|
|
168
|
+
for (const table of tables) {
|
|
169
|
+
const result = this.db.get(`SELECT COUNT(*) as count FROM ${table}`);
|
|
170
|
+
if (result === undefined) {
|
|
171
|
+
issues.push({ type: 'missing_table', table });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 4. Orphaned records kontrolü
|
|
176
|
+
const orphanedOrders = this.db.all(`
|
|
177
|
+
SELECT o.* FROM Orders o
|
|
178
|
+
LEFT JOIN Stores s ON o.platform = s.platform
|
|
179
|
+
WHERE s.id IS NULL
|
|
180
|
+
`);
|
|
181
|
+
|
|
182
|
+
if (orphanedOrders.length > 0) {
|
|
183
|
+
issues.push({
|
|
184
|
+
type: 'orphaned_records',
|
|
185
|
+
table: 'Orders',
|
|
186
|
+
count: orphanedOrders.length
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 5. Index kontrolü
|
|
191
|
+
const requiredIndexes = [
|
|
192
|
+
{ table: 'Products', column: 'barcode' },
|
|
193
|
+
{ table: 'Orders', column: 'orderNumber' }
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
for (const idx of requiredIndexes) {
|
|
197
|
+
const indexName = `idx_${idx.table}_${idx.column}`;
|
|
198
|
+
const hasIndex = this.db.get(
|
|
199
|
+
"SELECT name FROM sqlite_master WHERE type='index' AND name=?",
|
|
200
|
+
[indexName]
|
|
201
|
+
);
|
|
202
|
+
if (!hasIndex) {
|
|
203
|
+
this.db.run(`CREATE INDEX ${indexName} ON ${idx.table} (${idx.column})`);
|
|
204
|
+
logger.info('Eksik index oluşturuldu', { index: indexName });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const valid = issues.filter(i => i.type !== 'orphaned_records').length === 0;
|
|
209
|
+
|
|
210
|
+
logger.info('Bütünlük kontrolü tamamlandı', { valid, issues: issues.length });
|
|
211
|
+
return { valid, issues };
|
|
212
|
+
|
|
213
|
+
} catch (error) {
|
|
214
|
+
logger.error('Bütünlük kontrolü hatası', { error: error.message });
|
|
215
|
+
return { valid: false, issues: [{ type: 'error', message: error.message }] };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Hash hesapla
|
|
221
|
+
*/
|
|
222
|
+
calculateHash() {
|
|
223
|
+
if (!fs.existsSync(this.dbPath)) return null;
|
|
224
|
+
const buffer = fs.readFileSync(this.dbPath);
|
|
225
|
+
return crypto.createHash('sha256').update(buffer).digest('hex');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Son backup'ı getir
|
|
230
|
+
*/
|
|
231
|
+
getLatestBackup() {
|
|
232
|
+
if (!fs.existsSync(this.backupDir)) return null;
|
|
233
|
+
|
|
234
|
+
const files = fs.readdirSync(this.backupDir)
|
|
235
|
+
.filter(f => f.endsWith('.sqlite.meta.json'))
|
|
236
|
+
.sort()
|
|
237
|
+
.reverse();
|
|
238
|
+
|
|
239
|
+
if (files.length === 0) return null;
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const meta = JSON.parse(
|
|
243
|
+
fs.readFileSync(path.join(this.backupDir, files[0]), 'utf-8')
|
|
244
|
+
);
|
|
245
|
+
return meta;
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Backup'tan geri yükle
|
|
253
|
+
*/
|
|
254
|
+
async restoreFromBackup(backupFile) {
|
|
255
|
+
if (!fs.existsSync(backupFile)) {
|
|
256
|
+
throw new Error('Backup dosyası bulunamadı');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
logger.info('Backup geri yükleniyor', { file: backupFile });
|
|
260
|
+
|
|
261
|
+
// Mevcut DB'yi yedekle
|
|
262
|
+
if (fs.existsSync(this.dbPath)) {
|
|
263
|
+
const emergencyBackup = this.dbPath + '.emergency-' + Date.now();
|
|
264
|
+
fs.copyFileSync(this.dbPath, emergencyBackup);
|
|
265
|
+
logger.warn('Mevcut DB yedeklendi', { file: emergencyBackup });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Backup'ı geri yükle
|
|
269
|
+
const buffer = fs.readFileSync(backupFile);
|
|
270
|
+
const newDb = new (require('sql.js'))();
|
|
271
|
+
newDb.run(buffer);
|
|
272
|
+
|
|
273
|
+
// Mevcut db'yi değiştir
|
|
274
|
+
Object.assign(this.db, newDb);
|
|
275
|
+
|
|
276
|
+
logger.info('Backup geri yüklendi');
|
|
277
|
+
return { success: true };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Eski backup'ları temizle (son 30 gün)
|
|
282
|
+
*/
|
|
283
|
+
cleanupOldBackups() {
|
|
284
|
+
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
|
285
|
+
|
|
286
|
+
if (!fs.existsSync(this.backupDir)) return 0;
|
|
287
|
+
|
|
288
|
+
const files = fs.readdirSync(this.backupDir);
|
|
289
|
+
let cleaned = 0;
|
|
290
|
+
|
|
291
|
+
for (const file of files) {
|
|
292
|
+
const filePath = path.join(this.backupDir, file);
|
|
293
|
+
const stats = fs.statSync(filePath);
|
|
294
|
+
|
|
295
|
+
if (stats.mtimeMs < thirtyDaysAgo) {
|
|
296
|
+
fs.unlinkSync(filePath);
|
|
297
|
+
cleaned++;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
logger.info('Eski backup\'lar temizlendi', { cleaned });
|
|
302
|
+
return cleaned;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Günlük backup scheduler
|
|
308
|
+
*/
|
|
309
|
+
function scheduleDailyBackup(dbManager, hour = 3, minute = 0) {
|
|
310
|
+
const now = new Date();
|
|
311
|
+
const target = new Date();
|
|
312
|
+
target.setHours(hour, minute, 0, 0);
|
|
313
|
+
|
|
314
|
+
if (target <= now) {
|
|
315
|
+
target.setDate(target.getDate() + 1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const msUntilTarget = target - now;
|
|
319
|
+
|
|
320
|
+
setTimeout(() => {
|
|
321
|
+
dbManager.createBackup('scheduled');
|
|
322
|
+
scheduleDailyBackup(dbManager, hour, minute);
|
|
323
|
+
}, msUntilTarget);
|
|
324
|
+
|
|
325
|
+
logger.info('Günlük backup planlandı', { time: `${hour}:${minute}` });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = {
|
|
329
|
+
DatabaseManager,
|
|
330
|
+
scheduleDailyBackup
|
|
331
|
+
};
|