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-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
+ }