noho-platform 1.0.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/.noho_session.json +6 -0
- package/login.js +49 -0
- package/noho-cli.js +123 -0
- package/noho-dashboard.html +891 -0
- package/noho-lib.js +431 -0
- package/noho-server.js +441 -0
- package/noho_data/pages/3f550d44-2383-45cd-bd12-96d5c16f7fb7_1429be93890e6120.json +21 -0
- package/noho_data/users/3444499b-de5a-4931-b802-e02a054ef0c1.json +20 -0
- package/noho_data/users/3f550d44-2383-45cd-bd12-96d5c16f7fb7.json +22 -0
- package/noho_data/users/d4555ad8-9324-4ad3-a485-c234f17c2708.json +20 -0
- package/package.json +22 -0
- package/test-lib.js +56 -0
- package/test_data/pages/a019aa48-0caf-478f-927e-266c812eae10_10022dfa5d34936d.json +24 -0
- package/test_data/users/a019aa48-0caf-478f-927e-266c812eae10.json +22 -0
package/noho-server.js
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NOHO Server v3.2 - Express v5 Final Fix
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const WebSocket = require('ws');
|
|
8
|
+
const cors = require('cors');
|
|
9
|
+
const helmet = require('helmet');
|
|
10
|
+
const rateLimit = require('express-rate-limit');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
const fetch = require('node-fetch');
|
|
14
|
+
const NOHOLibrary = require('./noho-lib');
|
|
15
|
+
|
|
16
|
+
class NOHOServer {
|
|
17
|
+
constructor(config = {}) {
|
|
18
|
+
this.config = {
|
|
19
|
+
port: config.port || 5000,
|
|
20
|
+
host: config.host || '0.0.0.0',
|
|
21
|
+
...config
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
this.lib = new NOHOLibrary(config);
|
|
25
|
+
this.app = express();
|
|
26
|
+
this.server = null;
|
|
27
|
+
this.wss = null;
|
|
28
|
+
this.clients = new Map();
|
|
29
|
+
|
|
30
|
+
this.setupMiddleware();
|
|
31
|
+
this.setupRoutes();
|
|
32
|
+
this.setupWebSocket();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setupMiddleware() {
|
|
36
|
+
this.app.use(helmet({
|
|
37
|
+
contentSecurityPolicy: {
|
|
38
|
+
directives: {
|
|
39
|
+
defaultSrc: ["'self'"],
|
|
40
|
+
styleSrc: ["'self'", "'unsafe-inline'", "https:"],
|
|
41
|
+
scriptSrc: ["'self'", "'unsafe-inline'"],
|
|
42
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
this.app.use(cors({
|
|
48
|
+
origin: '*',
|
|
49
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
50
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key']
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
this.app.use(express.json({ limit: '10mb' }));
|
|
54
|
+
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
55
|
+
this.app.use('/static', express.static('public'));
|
|
56
|
+
|
|
57
|
+
const limiter = rateLimit({
|
|
58
|
+
windowMs: 15 * 60 * 1000,
|
|
59
|
+
max: (req) => req.user ? 100 : 30,
|
|
60
|
+
message: { error: 'Too many requests' },
|
|
61
|
+
standardHeaders: true,
|
|
62
|
+
legacyHeaders: false
|
|
63
|
+
});
|
|
64
|
+
this.app.use('/api/', limiter);
|
|
65
|
+
|
|
66
|
+
this.app.use(this.extractAuth.bind(this));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
extractAuth(req, res, next) {
|
|
70
|
+
const token = req.headers.authorization?.replace('Bearer ', '') ||
|
|
71
|
+
req.cookies?.token ||
|
|
72
|
+
req.query.token;
|
|
73
|
+
|
|
74
|
+
const apiKey = req.headers['x-api-key'] || req.query.apiKey;
|
|
75
|
+
|
|
76
|
+
if (token) {
|
|
77
|
+
req.user = this.lib.validateToken(token);
|
|
78
|
+
if (req.user) req.authType = 'session';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (apiKey && !req.user) {
|
|
82
|
+
req.user = this.lib.getUserByApiKey(apiKey);
|
|
83
|
+
if (req.user) req.authType = 'apikey';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
next();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
requireAuth(req, res, next) {
|
|
90
|
+
if (!req.user) {
|
|
91
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
92
|
+
}
|
|
93
|
+
next();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setupRoutes() {
|
|
97
|
+
this.app.get('/health', (req, res) => {
|
|
98
|
+
res.json({
|
|
99
|
+
status: 'ok',
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
users: this.lib.users.size,
|
|
102
|
+
pages: this.lib.pages.size,
|
|
103
|
+
uptime: process.uptime()
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this.app.post('/api/auth/register', async (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
const { email, password, username } = req.body;
|
|
110
|
+
const result = await this.lib.registerUser(email, password, username);
|
|
111
|
+
res.status(201).json({
|
|
112
|
+
success: true,
|
|
113
|
+
message: 'User created',
|
|
114
|
+
data: {
|
|
115
|
+
userId: result.userId,
|
|
116
|
+
apiKey: result.apiKey,
|
|
117
|
+
username: result.username
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
res.status(400).json({ success: false, error: error.message });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this.app.post('/api/auth/login', async (req, res) => {
|
|
126
|
+
try {
|
|
127
|
+
const { email, password } = req.body;
|
|
128
|
+
const result = await this.lib.loginUser(email, password);
|
|
129
|
+
res.json({
|
|
130
|
+
success: true,
|
|
131
|
+
token: result.token,
|
|
132
|
+
user: result.user
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
res.status(401).json({ success: false, error: error.message });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
this.app.post('/api/auth/logout', this.requireAuth, (req, res) => {
|
|
140
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
141
|
+
if (token) this.lib.sessions.delete(token);
|
|
142
|
+
res.json({ success: true });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.app.get('/api/auth/me', this.requireAuth, (req, res) => {
|
|
146
|
+
res.json({
|
|
147
|
+
success: true,
|
|
148
|
+
user: this.lib.sanitizeUser(req.user),
|
|
149
|
+
stats: this.lib.getUserStats(req.user.id)
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
this.app.post('/api/auth/regenerate-key', this.requireAuth, async (req, res) => {
|
|
154
|
+
try {
|
|
155
|
+
const newKey = await this.lib.regenerateApiKey(req.user.id);
|
|
156
|
+
res.json({ success: true, apiKey: newKey });
|
|
157
|
+
} catch (error) {
|
|
158
|
+
res.status(500).json({ error: error.message });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
this.app.post('/api/pages', this.requireAuth, async (req, res) => {
|
|
163
|
+
try {
|
|
164
|
+
const { route, code, options } = req.body;
|
|
165
|
+
|
|
166
|
+
if (!route || !code) {
|
|
167
|
+
return res.status(400).json({ error: 'Route and code required' });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const page = await this.lib.createPage(req.user.id, route, code, options);
|
|
171
|
+
|
|
172
|
+
res.status(201).json({
|
|
173
|
+
success: true,
|
|
174
|
+
page: {
|
|
175
|
+
id: page.id,
|
|
176
|
+
route: page.route,
|
|
177
|
+
shortRoute: page.shortRoute,
|
|
178
|
+
createdAt: page.stats.createdAt,
|
|
179
|
+
analysis: page.analysis
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
} catch (error) {
|
|
183
|
+
res.status(400).json({ success: false, error: error.message });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
this.app.get('/api/pages', this.requireAuth, (req, res) => {
|
|
188
|
+
const userPages = req.user.pages.map(pid => {
|
|
189
|
+
const p = this.lib.pages.get(pid);
|
|
190
|
+
return p ? {
|
|
191
|
+
id: p.id,
|
|
192
|
+
route: p.route,
|
|
193
|
+
views: p.stats.views,
|
|
194
|
+
createdAt: p.stats.createdAt,
|
|
195
|
+
public: p.options.public
|
|
196
|
+
} : null;
|
|
197
|
+
}).filter(Boolean);
|
|
198
|
+
|
|
199
|
+
res.json({ success: true, pages: userPages });
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
this.app.get('/api/pages/:pageId', this.requireAuth, (req, res) => {
|
|
203
|
+
const page = this.lib.pages.get(req.params.pageId);
|
|
204
|
+
if (!page || page.userId !== req.user.id) {
|
|
205
|
+
return res.status(404).json({ error: 'Page not found' });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
res.json({
|
|
209
|
+
success: true,
|
|
210
|
+
page: {
|
|
211
|
+
...page,
|
|
212
|
+
code: undefined,
|
|
213
|
+
originalCode: undefined
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
this.app.delete('/api/pages/:pageId', this.requireAuth, async (req, res) => {
|
|
219
|
+
try {
|
|
220
|
+
await this.lib.deletePage(req.user.id, req.params.pageId);
|
|
221
|
+
res.json({ success: true });
|
|
222
|
+
} catch (error) {
|
|
223
|
+
res.status(400).json({ error: error.message });
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
this.app.post('/api/ai/analyze', this.requireAuth, async (req, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const { code, context } = req.body;
|
|
230
|
+
const result = await this.lib.analyzeCode(code, context);
|
|
231
|
+
res.json({ success: true, ...result });
|
|
232
|
+
} catch (error) {
|
|
233
|
+
res.status(500).json({ error: error.message });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
this.app.post('/api/ai/generate', this.requireAuth, async (req, res) => {
|
|
238
|
+
try {
|
|
239
|
+
const { description } = req.body;
|
|
240
|
+
const code = await this.lib.generatePageCode(description, req.user.id);
|
|
241
|
+
res.json({ success: true, code });
|
|
242
|
+
} catch (error) {
|
|
243
|
+
res.status(500).json({ error: error.message });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ===== PUBLIC PAGE SERVING - REGEXP FIX =====
|
|
248
|
+
// استخدام RegExp بدلاً من wildcard
|
|
249
|
+
this.app.get(/^\/u\/([^\/]+)\/(.+)$/, async (req, res) => {
|
|
250
|
+
try {
|
|
251
|
+
const username = req.params[0];
|
|
252
|
+
const pagePath = req.params[1];
|
|
253
|
+
const route = `/${username}/${pagePath}`;
|
|
254
|
+
|
|
255
|
+
const page = this.lib.getPageByRoute(route);
|
|
256
|
+
|
|
257
|
+
if (!page) {
|
|
258
|
+
return res.status(404).send(`
|
|
259
|
+
<!DOCTYPE html>
|
|
260
|
+
<html dir="rtl">
|
|
261
|
+
<head>
|
|
262
|
+
<meta charset="UTF-8">
|
|
263
|
+
<title>404 - NOHO</title>
|
|
264
|
+
<style>
|
|
265
|
+
body { font-family: system-ui, sans-serif; text-align: center; padding: 50px; background: #0f172a; color: #f8fafc; }
|
|
266
|
+
h1 { font-size: 72px; margin: 0; color: #6366f1; }
|
|
267
|
+
a { color: #6366f1; text-decoration: none; }
|
|
268
|
+
code { background: #1e293b; padding: 4px 8px; border-radius: 4px; }
|
|
269
|
+
</style>
|
|
270
|
+
</head>
|
|
271
|
+
<body>
|
|
272
|
+
<h1>404</h1>
|
|
273
|
+
<p>الصفحة غير موجودة</p>
|
|
274
|
+
<p><code>${route}</code></p>
|
|
275
|
+
<p><a href="/">العودة للرئيسية</a></p>
|
|
276
|
+
</body>
|
|
277
|
+
</html>
|
|
278
|
+
`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!page.options.public) {
|
|
282
|
+
return res.status(403).send(`
|
|
283
|
+
<!DOCTYPE html>
|
|
284
|
+
<html dir="rtl">
|
|
285
|
+
<head><title>403 - Private</title></head>
|
|
286
|
+
<body style="text-align:center; padding:50px; font-family:sans-serif;">
|
|
287
|
+
<h1>🔒 صفحة خاصة</h1>
|
|
288
|
+
</body>
|
|
289
|
+
</html>
|
|
290
|
+
`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await this.lib.trackPageView(page.id);
|
|
294
|
+
|
|
295
|
+
const sandbox = {
|
|
296
|
+
req, res,
|
|
297
|
+
console,
|
|
298
|
+
setTimeout, setInterval, clearTimeout, clearInterval,
|
|
299
|
+
Date, Math, JSON, Object, Array, String, Number, Boolean, Promise,
|
|
300
|
+
fetch: fetch,
|
|
301
|
+
Buffer,
|
|
302
|
+
process: { env: {} }
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const fn = new Function(...Object.keys(sandbox), `
|
|
306
|
+
"use strict";
|
|
307
|
+
return (async () => {
|
|
308
|
+
${page.code}
|
|
309
|
+
})();
|
|
310
|
+
`);
|
|
311
|
+
|
|
312
|
+
await fn(...Object.values(sandbox));
|
|
313
|
+
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error(`[PAGE ERROR]`, error);
|
|
316
|
+
res.status(500).send(`
|
|
317
|
+
<!DOCTYPE html>
|
|
318
|
+
<html dir="rtl">
|
|
319
|
+
<head>
|
|
320
|
+
<meta charset="UTF-8">
|
|
321
|
+
<title>Error - NOHO</title>
|
|
322
|
+
<style>
|
|
323
|
+
body { font-family: system-ui; text-align: center; padding: 50px; background: #0f172a; color: #f8fafc; }
|
|
324
|
+
.error-box { color: #ef4444; background: #1e293b; padding: 20px; border-radius: 8px; display: inline-block; margin-top: 20px; }
|
|
325
|
+
</style>
|
|
326
|
+
</head>
|
|
327
|
+
<body>
|
|
328
|
+
<h1 style="color: #ef4444;">⚠️ خطأ في تنفيذ الصفحة</h1>
|
|
329
|
+
<div class="error-box">${error.message}</div>
|
|
330
|
+
</body>
|
|
331
|
+
</html>
|
|
332
|
+
`);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
this.app.get('/api/admin/users', this.requireAuth, (req, res) => {
|
|
337
|
+
if (req.user.email !== 'admin@noho.local') {
|
|
338
|
+
return res.status(403).json({ error: 'Admin only' });
|
|
339
|
+
}
|
|
340
|
+
res.json({ users: this.lib.listUsers() });
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
this.app.get('/', (req, res) => {
|
|
344
|
+
res.sendFile(path.join(__dirname, 'noho-dashboard.html'));
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
this.app.use((req, res) => {
|
|
348
|
+
res.status(404).json({ error: 'Not found' });
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
this.app.use((err, req, res, next) => {
|
|
352
|
+
console.error('[SERVER ERROR]', err);
|
|
353
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
setupWebSocket() {
|
|
358
|
+
this.wss = new WebSocket.Server({ noServer: true });
|
|
359
|
+
|
|
360
|
+
this.wss.on('connection', (ws, req) => {
|
|
361
|
+
const clientId = crypto.randomUUID();
|
|
362
|
+
this.clients.set(clientId, { ws, authenticated: false });
|
|
363
|
+
|
|
364
|
+
ws.on('message', async (data) => {
|
|
365
|
+
try {
|
|
366
|
+
const msg = JSON.parse(data);
|
|
367
|
+
|
|
368
|
+
if (msg.type === 'auth') {
|
|
369
|
+
const user = this.lib.validateToken(msg.token) ||
|
|
370
|
+
this.lib.getUserByApiKey(msg.apiKey);
|
|
371
|
+
if (user) {
|
|
372
|
+
this.clients.get(clientId).authenticated = true;
|
|
373
|
+
this.clients.get(clientId).userId = user.id;
|
|
374
|
+
ws.send(JSON.stringify({ type: 'auth_success', user: this.lib.sanitizeUser(user) }));
|
|
375
|
+
} else {
|
|
376
|
+
ws.send(JSON.stringify({ type: 'auth_failed' }));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else if (msg.type === 'analyze_code' && this.clients.get(clientId).authenticated) {
|
|
380
|
+
const result = await this.lib.analyzeCode(msg.code, msg.context);
|
|
381
|
+
ws.send(JSON.stringify({ type: 'analysis_result', ...result }));
|
|
382
|
+
}
|
|
383
|
+
else if (msg.type === 'ping') {
|
|
384
|
+
ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
} catch (e) {
|
|
388
|
+
ws.send(JSON.stringify({ type: 'error', message: e.message }));
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
ws.on('close', () => {
|
|
393
|
+
this.clients.delete(clientId);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
handleUpgrade(request, socket, head) {
|
|
399
|
+
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
400
|
+
this.wss.emit('connection', ws, request);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async start() {
|
|
405
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
406
|
+
|
|
407
|
+
this.server = http.createServer(this.app);
|
|
408
|
+
this.server.on('upgrade', this.handleUpgrade.bind(this));
|
|
409
|
+
|
|
410
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
411
|
+
console.log(`
|
|
412
|
+
╔════════════════════════════════════════╗
|
|
413
|
+
║ NOHO SERVER v3.2 ║
|
|
414
|
+
║ (Express v5 Compatible) ║
|
|
415
|
+
╠════════════════════════════════════════╣
|
|
416
|
+
║ HTTP: http://${this.config.host}:${this.config.port} ║
|
|
417
|
+
║ WS: ws://${this.config.host}:${this.config.port} ║
|
|
418
|
+
║ Pages: /u/username/page-name ║
|
|
419
|
+
╚════════════════════════════════════════╝
|
|
420
|
+
`);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
stop() {
|
|
425
|
+
if (this.server) {
|
|
426
|
+
this.server.close();
|
|
427
|
+
console.log('[SERVER] Stopped');
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (require.main === module) {
|
|
433
|
+
const server = new NOHOServer({
|
|
434
|
+
port: process.env.PORT || 5000,
|
|
435
|
+
aiKey: process.env.OPENAI_KEY
|
|
436
|
+
});
|
|
437
|
+
server.start();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
module.exports = NOHOServer;
|
|
441
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "3f550d44-2383-45cd-bd12-96d5c16f7fb7_1429be93890e6120",
|
|
3
|
+
"userId": "3f550d44-2383-45cd-bd12-96d5c16f7fb7",
|
|
4
|
+
"route": "/ahmed/welcome",
|
|
5
|
+
"shortRoute": "/welcome",
|
|
6
|
+
"code": "res.send(\"<h1>Welcome to NOHO!</h1><p>Created by ahmed</p>\")",
|
|
7
|
+
"originalCode": "res.send(\"<h1>Welcome to NOHO!</h1><p>Created by ahmed</p>\")",
|
|
8
|
+
"analysis": {
|
|
9
|
+
"warnings": [],
|
|
10
|
+
"changes": []
|
|
11
|
+
},
|
|
12
|
+
"options": {
|
|
13
|
+
"public": true,
|
|
14
|
+
"allowApi": true
|
|
15
|
+
},
|
|
16
|
+
"stats": {
|
|
17
|
+
"views": 0,
|
|
18
|
+
"lastAccessed": null,
|
|
19
|
+
"createdAt": "2026-03-23T12:04:05.614Z"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "3444499b-de5a-4931-b802-e02a054ef0c1",
|
|
3
|
+
"email": "test@test.com",
|
|
4
|
+
"username": "test",
|
|
5
|
+
"password": "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
|
|
6
|
+
"apiKey": "noho_mn32pieq_15c407b3681e9882c87cc41e",
|
|
7
|
+
"createdAt": "2026-03-23T10:59:11.858Z",
|
|
8
|
+
"lastLogin": null,
|
|
9
|
+
"pages": [],
|
|
10
|
+
"stats": {
|
|
11
|
+
"requests": 0,
|
|
12
|
+
"pagesCreated": 0,
|
|
13
|
+
"lastActive": 1774263551858
|
|
14
|
+
},
|
|
15
|
+
"settings": {
|
|
16
|
+
"autoFix": true,
|
|
17
|
+
"notifications": true,
|
|
18
|
+
"theme": "dark"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "3f550d44-2383-45cd-bd12-96d5c16f7fb7",
|
|
3
|
+
"email": "ahmed@test.com",
|
|
4
|
+
"username": "ahmed",
|
|
5
|
+
"password": "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
|
|
6
|
+
"apiKey": "noho_mn32p0wc_0c2b22b0f50585a560589e4c",
|
|
7
|
+
"createdAt": "2026-03-23T10:58:49.165Z",
|
|
8
|
+
"lastLogin": "2026-03-23T12:03:33.321Z",
|
|
9
|
+
"pages": [
|
|
10
|
+
"3f550d44-2383-45cd-bd12-96d5c16f7fb7_1429be93890e6120"
|
|
11
|
+
],
|
|
12
|
+
"stats": {
|
|
13
|
+
"requests": 0,
|
|
14
|
+
"pagesCreated": 1,
|
|
15
|
+
"lastActive": 1774267413323
|
|
16
|
+
},
|
|
17
|
+
"settings": {
|
|
18
|
+
"autoFix": true,
|
|
19
|
+
"notifications": true,
|
|
20
|
+
"theme": "dark"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "d4555ad8-9324-4ad3-a485-c234f17c2708",
|
|
3
|
+
"email": "ali@test.com",
|
|
4
|
+
"username": "ali",
|
|
5
|
+
"password": "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
|
|
6
|
+
"apiKey": "noho_mn33mh72_fa58da8ba86f656cb884f1eb",
|
|
7
|
+
"createdAt": "2026-03-23T11:24:49.935Z",
|
|
8
|
+
"lastLogin": null,
|
|
9
|
+
"pages": [],
|
|
10
|
+
"stats": {
|
|
11
|
+
"requests": 0,
|
|
12
|
+
"pagesCreated": 0,
|
|
13
|
+
"lastActive": 1774265089935
|
|
14
|
+
},
|
|
15
|
+
"settings": {
|
|
16
|
+
"autoFix": true,
|
|
17
|
+
"notifications": true,
|
|
18
|
+
"theme": "dark"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "noho-platform",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"cors": "^2.8.6",
|
|
15
|
+
"express": "^5.2.1",
|
|
16
|
+
"express-rate-limit": "^8.3.1",
|
|
17
|
+
"helmet": "^8.1.0",
|
|
18
|
+
"node-fetch": "^3.3.2",
|
|
19
|
+
"ws": "^8.19.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {}
|
|
22
|
+
}
|
package/test-lib.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const NOHOLibrary = require('./noho-lib');
|
|
2
|
+
|
|
3
|
+
async function test() {
|
|
4
|
+
console.log('🧪 Testing NOHO Library...\n');
|
|
5
|
+
|
|
6
|
+
const lib = new NOHOLibrary({
|
|
7
|
+
aiKey: 'test-key',
|
|
8
|
+
dbPath: './test_data'
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// انتظر التهيئة
|
|
12
|
+
await new Promise(r => setTimeout(r, 500));
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// 1. اختبار التسجيل
|
|
16
|
+
console.log('1️⃣ Testing register...');
|
|
17
|
+
const user = await lib.registerUser('ahmed@test.com', '12345678', 'ahmed');
|
|
18
|
+
console.log('✅ User created:', user.username);
|
|
19
|
+
console.log(' API Key:', user.apiKey.substring(0, 20) + '...');
|
|
20
|
+
|
|
21
|
+
// 2. اختبار تسجيل الدخول
|
|
22
|
+
console.log('\n2️⃣ Testing login...');
|
|
23
|
+
const login = await lib.loginUser('ahmed@test.com', '12345678');
|
|
24
|
+
console.log('✅ Login successful, token:', login.token.substring(0, 20) + '...');
|
|
25
|
+
|
|
26
|
+
// 3. اختبار إنشاء صفحة
|
|
27
|
+
console.log('\n3️⃣ Testing create page...');
|
|
28
|
+
const page = await lib.createPage(
|
|
29
|
+
user.userId,
|
|
30
|
+
'/hello',
|
|
31
|
+
'res.send("<h1>Hello from NOHO!</h1>")',
|
|
32
|
+
{ public: true }
|
|
33
|
+
);
|
|
34
|
+
console.log('✅ Page created:', page.route);
|
|
35
|
+
console.log(' ID:', page.id);
|
|
36
|
+
|
|
37
|
+
// 4. اختبار استرجاع الصفحة
|
|
38
|
+
console.log('\n4️⃣ Testing get page...');
|
|
39
|
+
const found = lib.getPageByRoute('/ahmed/hello');
|
|
40
|
+
console.log('✅ Page found:', found ? 'YES' : 'NO');
|
|
41
|
+
console.log(' Views:', found.stats.views);
|
|
42
|
+
|
|
43
|
+
// 5. اختبار الإحصائيات
|
|
44
|
+
console.log('\n5️⃣ Testing stats...');
|
|
45
|
+
const stats = lib.getUserStats(user.userId);
|
|
46
|
+
console.log('✅ Stats:', JSON.stringify(stats, null, 2));
|
|
47
|
+
|
|
48
|
+
console.log('\n🎉 All tests passed!');
|
|
49
|
+
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('\n❌ Test failed:', error.message);
|
|
52
|
+
console.error(error.stack);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "a019aa48-0caf-478f-927e-266c812eae10_10022dfa5d34936d",
|
|
3
|
+
"userId": "a019aa48-0caf-478f-927e-266c812eae10",
|
|
4
|
+
"route": "/ahmed/hello",
|
|
5
|
+
"shortRoute": "/hello",
|
|
6
|
+
"code": "res.send(\"<h1>Hello from NOHO!</h1>\")",
|
|
7
|
+
"originalCode": "res.send(\"<h1>Hello from NOHO!</h1>\")",
|
|
8
|
+
"analysis": {
|
|
9
|
+
"fixed": "res.send(\"<h1>Hello from NOHO!</h1>\")",
|
|
10
|
+
"warnings": [
|
|
11
|
+
"AI analysis failed"
|
|
12
|
+
],
|
|
13
|
+
"changes": []
|
|
14
|
+
},
|
|
15
|
+
"options": {
|
|
16
|
+
"public": true,
|
|
17
|
+
"allowApi": true
|
|
18
|
+
},
|
|
19
|
+
"stats": {
|
|
20
|
+
"views": 0,
|
|
21
|
+
"lastAccessed": null,
|
|
22
|
+
"createdAt": "2026-03-23T11:57:31.495Z"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "a019aa48-0caf-478f-927e-266c812eae10",
|
|
3
|
+
"email": "ahmed@test.com",
|
|
4
|
+
"username": "ahmed",
|
|
5
|
+
"password": "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
|
|
6
|
+
"apiKey": "noho_mn34sh7f_18e2d399a9501678df3eaf99",
|
|
7
|
+
"createdAt": "2026-03-23T11:57:29.500Z",
|
|
8
|
+
"lastLogin": "2026-03-23T11:57:29.510Z",
|
|
9
|
+
"pages": [
|
|
10
|
+
"a019aa48-0caf-478f-927e-266c812eae10_10022dfa5d34936d"
|
|
11
|
+
],
|
|
12
|
+
"stats": {
|
|
13
|
+
"requests": 0,
|
|
14
|
+
"pagesCreated": 1,
|
|
15
|
+
"lastActive": 1774267049510
|
|
16
|
+
},
|
|
17
|
+
"settings": {
|
|
18
|
+
"autoFix": true,
|
|
19
|
+
"notifications": true,
|
|
20
|
+
"theme": "dark"
|
|
21
|
+
}
|
|
22
|
+
}
|