devcode-canavar-pro 3.3.0
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/.env.example +9 -0
- package/README.md +82 -0
- package/bin/devcode.js +43 -0
- package/index.js +47 -0
- package/lib/BackupService.js +184 -0
- package/lib/CLI.js +87 -0
- package/lib/Cache.js +41 -0
- package/lib/Collection.js +295 -0
- package/lib/Core.js +164 -0
- package/lib/Dashboard.js +247 -0
- package/lib/Database.js +60 -0
- package/lib/Index.js +69 -0
- package/lib/Journal.js +79 -0
- package/lib/Middleware.js +50 -0
- package/lib/QueryParser.js +57 -0
- package/lib/RemoteClient.js +275 -0
- package/lib/Schema.js +54 -0
- package/lib/Server.js +224 -0
- package/lib/Storage.js +52 -0
- package/lib/UpdateParser.js +71 -0
- package/package.json +36 -0
- package/test_uri.js +31 -0
- package/vds.js +49 -0
- package/vds_setup.js +36 -0
- package/vds_start.js +27 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
|
|
3
|
+
// Baran'ın Merkezi Veri Sunucusu (VDS) - Veri Merkezi IP
|
|
4
|
+
const CLOUD_HOST = '45.74.244.192';
|
|
5
|
+
const CLOUD_PORT = 4242;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* DevCode Remote Client
|
|
9
|
+
* VDS'teki DevCode Server'a uzaktan bağlanır.
|
|
10
|
+
* API, yerel Collection ile birebir aynıdır.
|
|
11
|
+
*/
|
|
12
|
+
class RemoteClient {
|
|
13
|
+
/**
|
|
14
|
+
* @param {string|object} options - Connection URI string or options object
|
|
15
|
+
*/
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
let uri = typeof options === 'string' && options.startsWith('devcode:') ? options : (options.uri || process.env.DEVCODE_URI);
|
|
18
|
+
let host, port, secret;
|
|
19
|
+
|
|
20
|
+
if (uri) {
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(uri);
|
|
23
|
+
secret = url.username || null;
|
|
24
|
+
host = url.hostname;
|
|
25
|
+
port = parseInt(url.port) || CLOUD_PORT;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
throw new Error("Geçersiz Connection URI! Format: devcode://secret@vds-ip:4242");
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
// Varsayılan olarak Baran'ın Bulut Sunucusuna bağlan
|
|
31
|
+
host = options.host || process.env.DEVCODE_HOST || CLOUD_HOST;
|
|
32
|
+
port = options.port || process.env.DEVCODE_PORT || CLOUD_PORT;
|
|
33
|
+
secret = typeof options === 'string' ? options : (options.secret || null);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.host = host;
|
|
37
|
+
this.port = port;
|
|
38
|
+
this.secret = secret;
|
|
39
|
+
this.timeout = options.timeout || 10000;
|
|
40
|
+
this.maxRetries = options.maxRetries || 3; // Varsayılan 3 deneme
|
|
41
|
+
this.retryDelay = options.retryDelay || 1000; // Denemeler arası 1 saniye
|
|
42
|
+
this._dashboard = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Kullanıcının kendi verilerini yönetebilmesi için yerel paneli başlatır.
|
|
47
|
+
* Bu panel VDS'teki verileri gösterir!
|
|
48
|
+
*/
|
|
49
|
+
startDashboard(port = 3000) {
|
|
50
|
+
const Dashboard = require('./Dashboard');
|
|
51
|
+
this._dashboard = new Dashboard(this, port);
|
|
52
|
+
this._dashboard.start();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Bağlantıyı test eder.
|
|
57
|
+
* @returns {Promise<boolean>}
|
|
58
|
+
*/
|
|
59
|
+
async ping() {
|
|
60
|
+
const res = await this._request('GET', '/ping');
|
|
61
|
+
return res.ok === true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Uzak sunucudaki veritabanı listesini getirir.
|
|
66
|
+
* @returns {Promise<string[]>}
|
|
67
|
+
*/
|
|
68
|
+
async listDatabases() {
|
|
69
|
+
const res = await this._request('GET', '/databases');
|
|
70
|
+
return res.data || [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Bir veritabanına bağlanır.
|
|
75
|
+
* @param {string} dbName
|
|
76
|
+
* @returns {RemoteDatabase}
|
|
77
|
+
*/
|
|
78
|
+
use(dbName) {
|
|
79
|
+
return new RemoteDatabase(this, dbName);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* HTTP isteği gönderir (Hata durumunda otomatik tekrar denemeli).
|
|
84
|
+
* @private
|
|
85
|
+
*/
|
|
86
|
+
async _request(method, path, body = null, retryCount = 0) {
|
|
87
|
+
try {
|
|
88
|
+
return await new Promise((resolve, reject) => {
|
|
89
|
+
const bodyStr = body ? JSON.stringify(body) : null;
|
|
90
|
+
|
|
91
|
+
const options = {
|
|
92
|
+
hostname: this.host,
|
|
93
|
+
port: this.port,
|
|
94
|
+
path: path,
|
|
95
|
+
method: method,
|
|
96
|
+
headers: {
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
...(this.secret ? { 'x-secret': this.secret } : {}),
|
|
99
|
+
...(bodyStr ? { 'Content-Length': Buffer.byteLength(bodyStr) } : {})
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const req = http.request(options, (res) => {
|
|
104
|
+
let data = '';
|
|
105
|
+
res.on('data', chunk => { data += chunk; });
|
|
106
|
+
res.on('end', () => {
|
|
107
|
+
try {
|
|
108
|
+
const parsed = JSON.parse(data);
|
|
109
|
+
resolve(parsed);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
reject(new Error('Sunucudan geçersiz yanıt.'));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
req.setTimeout(this.timeout, () => {
|
|
117
|
+
req.destroy();
|
|
118
|
+
reject(new Error(`Zaman aşımı (${this.timeout}ms)`));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
req.on('error', (err) => {
|
|
122
|
+
reject(err);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (bodyStr) req.write(bodyStr);
|
|
126
|
+
req.end();
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
// Sadece bağlantı hatalarında ve zaman aşımında tekrar dene
|
|
130
|
+
if (retryCount < this.maxRetries) {
|
|
131
|
+
console.warn(`⚠️ [Monster-Retry] ${path} hatası: ${error.message}. Tekrar deneniyor (${retryCount + 1}/${this.maxRetries})...`);
|
|
132
|
+
await new Promise(r => setTimeout(r, this.retryDelay));
|
|
133
|
+
return this._request(method, path, body, retryCount + 1);
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`[DevCode Remote] ${path} başarısız: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Uzak veritabanı örneği — RemoteClient.use() tarafından oluşturulur.
|
|
142
|
+
*/
|
|
143
|
+
class RemoteDatabase {
|
|
144
|
+
constructor(client, dbName) {
|
|
145
|
+
this._client = client;
|
|
146
|
+
this._dbName = dbName;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Bir koleksiyona bağlanır.
|
|
151
|
+
* @param {string} name
|
|
152
|
+
* @returns {RemoteCollection}
|
|
153
|
+
*/
|
|
154
|
+
collection(name) {
|
|
155
|
+
return new RemoteCollection(this._client, this._dbName, name);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Koleksiyon listesini getirir (Dashboard için).
|
|
160
|
+
*/
|
|
161
|
+
async listCollections() {
|
|
162
|
+
return this._client._request('GET', `/use/${this._dbName}/-dummy-/collections`)
|
|
163
|
+
.then(res => res.data);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Uzak koleksiyon — yerel Collection ile aynı API'ye sahiptir.
|
|
169
|
+
*/
|
|
170
|
+
class RemoteCollection {
|
|
171
|
+
constructor(client, dbName, collectionName) {
|
|
172
|
+
this._client = client;
|
|
173
|
+
this._base = `/use/${dbName}/${collectionName}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @private */
|
|
177
|
+
_post(action, body) {
|
|
178
|
+
return this._client._request('POST', `${this._base}/${action}`, body)
|
|
179
|
+
.then(res => res.data);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** @private */
|
|
183
|
+
_get(action) {
|
|
184
|
+
return this._client._request('GET', `${this._base}/${action}`)
|
|
185
|
+
.then(res => res.data);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Tek bir belge ekler.
|
|
190
|
+
* @param {object} doc
|
|
191
|
+
*/
|
|
192
|
+
async insert(doc) {
|
|
193
|
+
return this._post('insert', { doc });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Birden fazla belge ekler.
|
|
198
|
+
* @param {object[]} docs
|
|
199
|
+
*/
|
|
200
|
+
async insertMany(docs) {
|
|
201
|
+
return this._post('insertMany', { docs });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Sorguya uyan tüm belgeleri getirir.
|
|
206
|
+
* @param {object} query
|
|
207
|
+
*/
|
|
208
|
+
async find(query = {}) {
|
|
209
|
+
return this._post('find', { query });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Sorguya uyan ilk belgeyi getirir.
|
|
214
|
+
* @param {object} query
|
|
215
|
+
*/
|
|
216
|
+
async findOne(query = {}) {
|
|
217
|
+
return this._post('findOne', { query });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Sorguya uyan ilk belgeyi günceller.
|
|
222
|
+
* @param {object} query
|
|
223
|
+
* @param {object} update
|
|
224
|
+
*/
|
|
225
|
+
async update(query, update) {
|
|
226
|
+
return this._post('update', { query, update });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Sorguya uyan tüm belgeleri günceller.
|
|
231
|
+
* @param {object} query
|
|
232
|
+
* @param {object} update
|
|
233
|
+
*/
|
|
234
|
+
async updateMany(query, update) {
|
|
235
|
+
return this._post('updateMany', { query, update });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Sorguya uyan ilk belgeyi siler.
|
|
240
|
+
* @param {object} query
|
|
241
|
+
*/
|
|
242
|
+
async delete(query) {
|
|
243
|
+
return this._post('delete', { query });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Sorguya uyan tüm belgeleri siler.
|
|
248
|
+
* @param {object} query
|
|
249
|
+
*/
|
|
250
|
+
async deleteMany(query) {
|
|
251
|
+
return this._post('deleteMany', { query });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Belge sayısını döner.
|
|
256
|
+
* @param {object} query
|
|
257
|
+
*/
|
|
258
|
+
async count(query = {}) {
|
|
259
|
+
return this._post('count', { query });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Koleksiyonu tamamen siler.
|
|
264
|
+
*/
|
|
265
|
+
async drop() {
|
|
266
|
+
return this._post('drop', {});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Dashboard uyumluluğu için alias */
|
|
270
|
+
async remove(query) {
|
|
271
|
+
return this.delete(query);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = RemoteClient;
|
package/lib/Schema.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevCode Schema Validator
|
|
3
|
+
* Verilerin bütünlüğünü ve doğruluğunu kontrol eder.
|
|
4
|
+
*/
|
|
5
|
+
class Schema {
|
|
6
|
+
constructor(definition) {
|
|
7
|
+
this.definition = definition;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Dökümanı şemaya göre doğrular.
|
|
12
|
+
*/
|
|
13
|
+
validate(doc) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
|
|
16
|
+
for (let field in this.definition) {
|
|
17
|
+
const rules = this.definition[field];
|
|
18
|
+
const value = doc[field];
|
|
19
|
+
|
|
20
|
+
// Zorunlu alan kontrolü
|
|
21
|
+
if (rules.required && (value === undefined || value === null)) {
|
|
22
|
+
errors.push(`${field} alanı zorunludur.`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (value !== undefined) {
|
|
27
|
+
// Tip kontrolü
|
|
28
|
+
if (rules.type && typeof value !== rules.type && rules.type !== 'array') {
|
|
29
|
+
errors.push(`${field} alanı ${rules.type} tipinde olmalıdır.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Array kontrolü
|
|
33
|
+
if (rules.type === 'array' && !Array.isArray(value)) {
|
|
34
|
+
errors.push(`${field} alanı bir dizi olmalıdır.`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Özel doğrulama (validator function)
|
|
38
|
+
if (rules.validate && typeof rules.validate === 'function') {
|
|
39
|
+
if (!rules.validate(value)) {
|
|
40
|
+
errors.push(`${field} alanı geçersiz değer içeriyor.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (errors.length > 0) {
|
|
47
|
+
throw new Error(`Şema Doğrulama Hatası: ${errors.join(' ')}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = Schema;
|
package/lib/Server.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const url = require('url');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DevCode Remote Server
|
|
6
|
+
* VDS üzerinde çalışır. HTTP API ile uzaktan veritabanı erişimi sağlar.
|
|
7
|
+
* Kullanıcılar RemoteClient ile bu sunucuya bağlanır.
|
|
8
|
+
*/
|
|
9
|
+
class Server {
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} devcode - DevCode core örneği
|
|
12
|
+
* @param {object} options
|
|
13
|
+
* @param {number} options.port - Sunucu portu (varsayılan: 4242)
|
|
14
|
+
* @param {string} options.secret - Güvenlik anahtarı (boş bırakılırsa açık erişim)
|
|
15
|
+
*/
|
|
16
|
+
constructor(devcode, options = {}) {
|
|
17
|
+
this.devcode = devcode;
|
|
18
|
+
this.port = options.port || 4242;
|
|
19
|
+
this.secret = options.secret || null;
|
|
20
|
+
this.rateLimit = options.rateLimit || 100; // Dakikada 100 istek sınırı
|
|
21
|
+
this.requestTracker = new Map(); // IP/Secret bazlı istek takibi
|
|
22
|
+
this._server = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sunucuyu başlatır.
|
|
27
|
+
*/
|
|
28
|
+
start() {
|
|
29
|
+
this._server = http.createServer((req, res) => {
|
|
30
|
+
this._handle(req, res);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this._server.listen(this.port, '0.0.0.0', () => {
|
|
34
|
+
console.log(`\x1b[32m[DevCode Server]\x1b[0m Uzak veritabanı sunucusu aktif → Port: \x1b[33m${this.port}\x1b[0m`);
|
|
35
|
+
if (this.secret) {
|
|
36
|
+
console.log(`\x1b[32m[DevCode Server]\x1b[0m Güvenlik: \x1b[32mAktif\x1b[0m (secret key gerekli)`);
|
|
37
|
+
} else {
|
|
38
|
+
console.log(`\x1b[33m[DevCode Server]\x1b[0m Uyarı: Güvenlik anahtarı tanımlanmadı! Açık erişim aktif.`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sunucuyu durdurur.
|
|
47
|
+
*/
|
|
48
|
+
stop() {
|
|
49
|
+
if (this._server) {
|
|
50
|
+
this._server.close();
|
|
51
|
+
console.log('\x1b[33m[DevCode Server]\x1b[0m Sunucu durduruldu.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* HTTP isteklerini işler.
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
async _handle(req, res) {
|
|
60
|
+
res.setHeader('Content-Type', 'application/json');
|
|
61
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
62
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
63
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, x-secret');
|
|
64
|
+
|
|
65
|
+
// simple rate limit logic
|
|
66
|
+
const ip = req.socket.remoteAddress;
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const clientData = this.requestTracker.get(ip) || { count: 0, startTime: now };
|
|
69
|
+
|
|
70
|
+
if (now - clientData.startTime > 60000) {
|
|
71
|
+
clientData.count = 0;
|
|
72
|
+
clientData.startTime = now;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
clientData.count++;
|
|
76
|
+
this.requestTracker.set(ip, clientData);
|
|
77
|
+
|
|
78
|
+
if (clientData.count > this.rateLimit) {
|
|
79
|
+
res.writeHead(429);
|
|
80
|
+
return res.end(JSON.stringify({ ok: false, error: 'Rate limit aşıldı! Lütfen bekleyin.' }));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// OPTIONS (preflight) isteğini yanıtla
|
|
84
|
+
if (req.method === 'OPTIONS') {
|
|
85
|
+
res.writeHead(204);
|
|
86
|
+
return res.end();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let namespace = 'public'; // Varsayılan namespace
|
|
90
|
+
// Kimlik doğrulama
|
|
91
|
+
if (this.secret) {
|
|
92
|
+
const clientSecret = req.headers['x-secret'];
|
|
93
|
+
if (clientSecret !== this.secret) {
|
|
94
|
+
res.writeHead(401);
|
|
95
|
+
return res.end(JSON.stringify({ ok: false, error: 'Geçersiz güvenlik anahtarı.' }));
|
|
96
|
+
}
|
|
97
|
+
namespace = clientSecret; // Güvenlik anahtarı aynı zamanda namespace olarak kullanılır
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// URL parse: /use/:db/:collection/:action
|
|
101
|
+
const parsed = url.parse(req.url, true);
|
|
102
|
+
const parts = parsed.pathname.replace(/^\//, '').split('/');
|
|
103
|
+
|
|
104
|
+
// Özel route: GET /ping — bağlantı testi
|
|
105
|
+
if (parts[0] === 'ping') {
|
|
106
|
+
res.writeHead(200);
|
|
107
|
+
return res.end(JSON.stringify({ ok: true, message: 'pong', version: '2.0' }));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Özel route: GET /databases — veritabanı listesi
|
|
111
|
+
if (parts[0] === 'databases') {
|
|
112
|
+
res.writeHead(200);
|
|
113
|
+
return res.end(JSON.stringify({ ok: true, data: this.devcode.listDatabases(namespace) }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Beklenen format: /use/:db/:collection/:action
|
|
117
|
+
if (parts[0] !== 'use' || parts.length < 4) {
|
|
118
|
+
res.writeHead(400);
|
|
119
|
+
return res.end(JSON.stringify({ ok: false, error: 'Geçersiz route. Format: /use/:db/:collection/:action' }));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const [, dbName, collectionName, action] = parts;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const db = this.devcode.use(dbName, namespace);
|
|
126
|
+
const col = db.collection(collectionName);
|
|
127
|
+
|
|
128
|
+
// İstek gövdesini oku
|
|
129
|
+
let body = {};
|
|
130
|
+
if (req.method === 'POST') {
|
|
131
|
+
body = await this._readBody(req);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let result;
|
|
135
|
+
|
|
136
|
+
switch (action) {
|
|
137
|
+
case 'insert':
|
|
138
|
+
result = await col.insert(body.doc);
|
|
139
|
+
break;
|
|
140
|
+
case 'insertMany':
|
|
141
|
+
result = await col.insertMany(body.docs);
|
|
142
|
+
break;
|
|
143
|
+
case 'find':
|
|
144
|
+
result = col.find(body.query || {});
|
|
145
|
+
break;
|
|
146
|
+
case 'findOne':
|
|
147
|
+
result = col.findOne(body.query || {});
|
|
148
|
+
break;
|
|
149
|
+
case 'update':
|
|
150
|
+
result = await col.update(body.query, body.update);
|
|
151
|
+
break;
|
|
152
|
+
case 'updateMany':
|
|
153
|
+
result = await col.updateMany(body.query, body.update);
|
|
154
|
+
break;
|
|
155
|
+
case 'delete':
|
|
156
|
+
result = await col.delete(body.query);
|
|
157
|
+
break;
|
|
158
|
+
case 'deleteMany':
|
|
159
|
+
result = await col.deleteMany(body.query);
|
|
160
|
+
break;
|
|
161
|
+
case 'count':
|
|
162
|
+
result = col.count(body.query || {});
|
|
163
|
+
break;
|
|
164
|
+
case 'drop':
|
|
165
|
+
result = col.drop();
|
|
166
|
+
break;
|
|
167
|
+
case 'collections':
|
|
168
|
+
result = db.listCollections();
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
res.writeHead(400);
|
|
172
|
+
return res.end(JSON.stringify({ ok: false, error: `Bilinmeyen işlem: ${action}` }));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
res.writeHead(200);
|
|
176
|
+
res.end(JSON.stringify({ ok: true, data: result }));
|
|
177
|
+
} catch (err) {
|
|
178
|
+
res.writeHead(500);
|
|
179
|
+
res.end(JSON.stringify({ ok: false, error: err.message }));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
_readBody(req) {
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
let body = '';
|
|
186
|
+
req.on('data', chunk => { body += chunk.toString(); });
|
|
187
|
+
req.on('end', () => {
|
|
188
|
+
try {
|
|
189
|
+
resolve(body ? JSON.parse(body) : {});
|
|
190
|
+
} catch (e) {
|
|
191
|
+
reject(new Error('Geçersiz JSON gövdesi.'));
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
req.on('error', reject);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Sunucuyu başlatır ve dinler.
|
|
200
|
+
*/
|
|
201
|
+
startServer(port) {
|
|
202
|
+
this.port = port || this.port;
|
|
203
|
+
this.start();
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Görsel paneli başlatır.
|
|
209
|
+
*/
|
|
210
|
+
startDashboard(port) {
|
|
211
|
+
this.devcode.startDashboard(port);
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* İnteraktif kabuğu başlatır.
|
|
217
|
+
*/
|
|
218
|
+
startShell() {
|
|
219
|
+
this.devcode.startShell();
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = Server;
|
package/lib/Storage.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const zlib = require('zlib');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DevCode Storage Engine 2.0 (Enterprise Edition)
|
|
7
|
+
* Veri güvenliğini atomik işlemlerle (Atomic Write) sağlar.
|
|
8
|
+
*/
|
|
9
|
+
class Storage {
|
|
10
|
+
/**
|
|
11
|
+
* Veriyi atomik olarak diske yazar.
|
|
12
|
+
* Bu yöntem veri bozulmasını (corruption) %100 önler.
|
|
13
|
+
*/
|
|
14
|
+
static save(filePath, data, compress = false) {
|
|
15
|
+
const json = JSON.stringify(data);
|
|
16
|
+
const tempPath = `${filePath}.tmp`;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
if (compress) {
|
|
20
|
+
const buffer = zlib.gzipSync(json);
|
|
21
|
+
fs.writeFileSync(tempPath, buffer);
|
|
22
|
+
} else {
|
|
23
|
+
fs.writeFileSync(tempPath, json, 'utf8');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ATOMİK YER DEĞİŞTİRME: Dosya tamamen yazılmadan asıl dosya değişmez.
|
|
27
|
+
fs.renameSync(tempPath, compress ? filePath + '.gz' : filePath);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
|
|
30
|
+
throw new Error(`Kritik Depolama Hatası: ${err.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Veriyi diskten okur.
|
|
36
|
+
*/
|
|
37
|
+
static load(filePath) {
|
|
38
|
+
const gzPath = filePath + '.gz';
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(gzPath)) {
|
|
41
|
+
const buffer = fs.readFileSync(gzPath);
|
|
42
|
+
const json = zlib.gunzipSync(buffer).toString();
|
|
43
|
+
return JSON.parse(json);
|
|
44
|
+
} else if (fs.existsSync(filePath)) {
|
|
45
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
46
|
+
return JSON.parse(content || '[]');
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = Storage;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Update Parser (Enterprise Edition)
|
|
3
|
+
* MongoDB benzeri güncelleme operatörlerini ($set, $inc, $push, $pull, $unset) işler.
|
|
4
|
+
*/
|
|
5
|
+
class UpdateParser {
|
|
6
|
+
/**
|
|
7
|
+
* Bir dökümanı verilen güncelleme sorgusuna göre mutasyona uğratır.
|
|
8
|
+
* @param {Object} doc Güncellenecek döküman
|
|
9
|
+
* @param {Object} updateQuery Güncelleme sorgusu ($set, $inc vb.)
|
|
10
|
+
* @returns {Object} Güncellenmiş döküman
|
|
11
|
+
*/
|
|
12
|
+
static parse(doc, updateQuery) {
|
|
13
|
+
for (let key in updateQuery) {
|
|
14
|
+
const val = updateQuery[key];
|
|
15
|
+
|
|
16
|
+
switch (key) {
|
|
17
|
+
case '$set':
|
|
18
|
+
for (let field in val) doc[field] = val[field];
|
|
19
|
+
break;
|
|
20
|
+
|
|
21
|
+
case '$inc':
|
|
22
|
+
for (let field in val) {
|
|
23
|
+
if (typeof doc[field] === 'number') doc[field] += val[field];
|
|
24
|
+
else doc[field] = val[field];
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
|
|
28
|
+
case '$push':
|
|
29
|
+
for (let field in val) {
|
|
30
|
+
if (!Array.isArray(doc[field])) doc[field] = [];
|
|
31
|
+
doc[field].push(val[field]);
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
case '$pull':
|
|
36
|
+
for (let field in val) {
|
|
37
|
+
if (Array.isArray(doc[field])) {
|
|
38
|
+
doc[field] = doc[field].filter(v => v !== val[field]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
|
|
43
|
+
case '$unset':
|
|
44
|
+
for (let field in val) {
|
|
45
|
+
if (val[field]) delete doc[field];
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
|
|
49
|
+
case '$rename':
|
|
50
|
+
for (let field in val) {
|
|
51
|
+
const newName = val[field];
|
|
52
|
+
if (doc[field] !== undefined) {
|
|
53
|
+
doc[newName] = doc[field];
|
|
54
|
+
delete doc[field];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
default:
|
|
60
|
+
// Eğer operatör değilse, direkt set olarak kabul et (Mongo mantığı)
|
|
61
|
+
if (!key.startsWith('$')) {
|
|
62
|
+
doc[key] = val;
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return doc;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = UpdateParser;
|