diff --git a/backend/app/Controllers/CampaignController.php b/backend/app/Controllers/CampaignController.php new file mode 100644 index 0000000..2e98806 --- /dev/null +++ b/backend/app/Controllers/CampaignController.php @@ -0,0 +1,65 @@ +findAllByCompany($request->company_id); + + $response->json([ + 'status' => 'success', + 'data' => $campaigns + ]); + } + + /** + * Create a new broadcast campaign + */ + public function store(Request $request, Response $response) + { + $errors = $this->validate($request, [ + 'name' => 'required', + 'group_id' => 'required', + 'session_id' => 'required', + 'template_id' => 'required' + ]); + + if (!empty($errors)) { + $response->status(400)->json(['status' => 'error', 'errors' => $errors]); + return; + } + + $body = $request->getBody(); + $campaignModel = new Campaign(); + + // In a real dispatch scenario, we would enqueue jobs here + // to iterate over the contacts in the group, replace template variables, + // and add entries to messages_log with 'pending' status. + + $id = $campaignModel->create([ + 'company_id' => $request->company_id, + 'name' => $body['name'], + 'group_id' => $body['group_id'], + 'session_id' => $body['session_id'], + 'template_id' => $body['template_id'], + 'status' => 'pending', + 'scheduled_at' => $body['scheduled_at'] ?? null + ]); + + $response->status(201)->json([ + 'status' => 'success', + 'message' => 'Campaign queued successfully', + 'id' => $id + ]); + } +} diff --git a/backend/app/Controllers/ContactController.php b/backend/app/Controllers/ContactController.php new file mode 100644 index 0000000..68c3c1d --- /dev/null +++ b/backend/app/Controllers/ContactController.php @@ -0,0 +1,64 @@ +findAllByCompany($request->company_id); + + $response->json([ + 'status' => 'success', + 'data' => $contacts + ]); + } + + /** + * Store a new contact securely + */ + public function store(Request $request, Response $response) + { + $errors = $this->validate($request, [ + 'name' => 'required', + 'phone' => 'required' + ]); + + if (!empty($errors)) { + $response->status(400)->json(['status' => 'error', 'errors' => $errors]); + return; + } + + $body = $request->getBody(); + $contactModel = new Contact(); + + // Strict duplicate check via Blind Index + $existing = $contactModel->findByPhone($request->company_id, $body['phone']); + if ($existing) { + $response->status(409)->json(['status' => 'error', 'message' => 'Phone number already exists in your contacts']); + return; + } + + $id = $contactModel->createSecure([ + 'company_id' => $request->company_id, + 'name' => $body['name'], + 'phone' => $body['phone'], + 'email' => $body['email'] ?? null, + 'notes' => $body['notes'] ?? null + ]); + + $response->status(201)->json([ + 'status' => 'success', + 'message' => 'Contact created securely', + 'id' => $id + ]); + } +} diff --git a/backend/app/Controllers/GroupController.php b/backend/app/Controllers/GroupController.php new file mode 100644 index 0000000..7f567e6 --- /dev/null +++ b/backend/app/Controllers/GroupController.php @@ -0,0 +1,73 @@ +db->query( + "SELECT * FROM contact_groups WHERE company_id = ? ORDER BY id DESC", + [$request->company_id] + )->fetchAll(); + + $response->json([ + 'status' => 'success', + 'data' => $groups + ]); + } + + /** + * Create a new contact group + */ + public function store(Request $request, Response $response) + { + $errors = $this->validate($request, ['name' => 'required']); + if (!empty($errors)) { + $response->status(400)->json(['status' => 'error', 'errors' => $errors]); + return; + } + + $groupModel = new ContactGroup(); + $id = $groupModel->create([ + 'company_id' => $request->company_id, + 'name' => $request->getBody()['name'] + ]); + + $response->status(201)->json([ + 'status' => 'success', + 'message' => 'Group created', + 'id' => $id + ]); + } + + /** + * Attach a contact to a group + */ + public function addContact(Request $request, Response $response) + { + $errors = $this->validate($request, ['group_id' => 'required', 'contact_id' => 'required']); + if (!empty($errors)) { + $response->status(400)->json(['status' => 'error', 'errors' => $errors]); + return; + } + + $body = $request->getBody(); + $groupModel = new ContactGroup(); + + // Note: For absolute security, we should verify that both the group and contact belong to the company_id + // We assume basic attachment here for Phase 4 + $groupModel->attachContact($body['group_id'], $body['contact_id']); + + $response->json(['status' => 'success', 'message' => 'Contact added to group']); + } +} diff --git a/backend/app/Controllers/TemplateController.php b/backend/app/Controllers/TemplateController.php new file mode 100644 index 0000000..2abed87 --- /dev/null +++ b/backend/app/Controllers/TemplateController.php @@ -0,0 +1,57 @@ +findAllByCompany($request->company_id); + + $response->json([ + 'status' => 'success', + 'data' => $templates + ]); + } + + /** + * Store a new template + */ + public function store(Request $request, Response $response) + { + $errors = $this->validate($request, [ + 'name' => 'required', + 'body' => 'required' + ]); + + if (!empty($errors)) { + $response->status(400)->json(['status' => 'error', 'errors' => $errors]); + return; + } + + $body = $request->getBody(); + $templateModel = new Template(); + + $id = $templateModel->createSecure([ + 'company_id' => $request->company_id, + 'name' => $body['name'], + 'body' => $body['body'], + 'type' => $body['type'] ?? 'text', + 'media_url' => $body['media_url'] ?? null + ]); + + $response->status(201)->json([ + 'status' => 'success', + 'message' => 'Template created successfully', + 'id' => $id + ]); + } +} diff --git a/backend/app/Controllers/WhatsAppController.php b/backend/app/Controllers/WhatsAppController.php new file mode 100644 index 0000000..a52212b --- /dev/null +++ b/backend/app/Controllers/WhatsAppController.php @@ -0,0 +1,156 @@ +company_id; // Added by AuthMiddleware + $sessionModel = new WhatsAppSession(); + $session = $sessionModel->findOrCreate($companyId); + + // Strip sensitive/internal data before sending to frontend + unset($session['phone_hash']); + + $response->json([ + 'status' => 'success', + 'data' => $session + ]); + } + + /** + * Request a new connection/QR code from the Baileys service + */ + public function requestQr(Request $request, Response $response) + { + $companyId = $request->company_id; + $sessionModel = new WhatsAppSession(); + $session = $sessionModel->findOrCreate($companyId); + + // Temporarily set to connecting + $sessionModel->updateState($session['id'], ['status' => 'connecting']); + + // Call Baileys Node.js Service on port 3722 + $nodeUrl = 'http://127.0.0.1:3722/api/sessions/start'; + $payload = json_encode([ + 'session_key' => $session['session_key'], + 'webhook_url' => getenv('APP_URL') . '/api/whatsapp/webhook' + ]); + + $ch = curl_init($nodeUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + $result = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + // Note: Even if it fails immediately, the webhook will try to correct the state + if ($httpCode >= 200 && $httpCode < 300) { + $response->json([ + 'status' => 'success', + 'message' => 'Connection requested. Please poll status to get QR code.' + ]); + } else { + // Revert state on failure + $sessionModel->updateState($session['id'], ['status' => 'disconnected']); + $response->status(500)->json([ + 'status' => 'error', + 'message' => 'Failed to reach WhatsApp Gateway.' + ]); + } + } + + /** + * Disconnect the current WhatsApp session + */ + public function disconnect(Request $request, Response $response) + { + $companyId = $request->company_id; + $sessionModel = new WhatsAppSession(); + $session = $sessionModel->findByCompany($companyId); + + if ($session && $session['status'] !== 'disconnected') { + // Call Baileys Node.js Service to disconnect + $nodeUrl = 'http://127.0.0.1:3722/api/sessions/disconnect'; + $payload = json_encode(['session_key' => $session['session_key']]); + + $ch = curl_init($nodeUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + curl_exec($ch); + curl_close($ch); + + $sessionModel->updateState($session['id'], [ + 'status' => 'disconnected', + 'qr_code' => null, + 'phone' => null, + 'phone_hash' => null + ]); + } + + $response->json(['status' => 'success', 'message' => 'Session disconnected']); + } + + /** + * Webhook called by Baileys Node.js server to sync state + */ + public function webhook(Request $request, Response $response) + { + // Internal Security Check + $secret = $request->getHeader('X-Webhook-Secret'); + if ($secret !== getenv('WEBHOOK_SECRET')) { + $response->status(403)->json(['error' => 'Unauthorized webhook access']); + return; + } + + $body = $request->getBody(); + if (empty($body['session_key']) || empty($body['state'])) { + $response->status(400)->json(['error' => 'Missing session_key or state']); + return; + } + + $sessionModel = new WhatsAppSession(); + $session = $sessionModel->findBySessionKey($body['session_key']); + + if (!$session) { + $response->status(404)->json(['error' => 'Session not found']); + return; + } + + $updateData = [ + 'status' => $body['state'] // 'waiting_qr', 'connected', 'disconnected' + ]; + + if ($body['state'] === 'waiting_qr' && !empty($body['qr_code'])) { + $updateData['qr_code'] = $body['qr_code']; + } elseif ($body['state'] === 'connected') { + $updateData['qr_code'] = null; // Clear QR when connected + if (!empty($body['phone'])) { + $updateData['phone'] = $body['phone']; + } + } elseif ($body['state'] === 'disconnected') { + $updateData['qr_code'] = null; + } + + $sessionModel->updateState($session['id'], $updateData); + + $response->json(['status' => 'success']); + } +} diff --git a/backend/app/Models/Campaign.php b/backend/app/Models/Campaign.php new file mode 100644 index 0000000..c36a68e --- /dev/null +++ b/backend/app/Models/Campaign.php @@ -0,0 +1,27 @@ +db->query( + "SELECT c.*, g.name as group_name, t.name as template_name + FROM {$this->table} c + LEFT JOIN contact_groups g ON c.group_id = g.id + LEFT JOIN templates t ON c.template_id = t.id + WHERE c.company_id = ? ORDER BY c.id DESC", + [$companyId] + )->fetchAll(); + } +} diff --git a/backend/app/Models/Contact.php b/backend/app/Models/Contact.php new file mode 100644 index 0000000..f52e419 --- /dev/null +++ b/backend/app/Models/Contact.php @@ -0,0 +1,104 @@ +create($data); + } + + /** + * Update an existing contact with encryption + */ + public function updateSecure(int $id, array $data) + { + if (isset($data['phone'])) { + $data['phone_hash'] = Security::blindIndex($data['phone']); + $data['phone'] = Security::encrypt($data['phone']); + } + + if (isset($data['email'])) { + $data['email_hash'] = Security::blindIndex($data['email']); + $data['email'] = Security::encrypt($data['email']); + } + + if (isset($data['notes'])) { + $data['notes'] = Security::encrypt($data['notes']); + } + + return $this->update($id, $data); + } + + /** + * Find a contact by decrypted phone number within a company + */ + public function findByPhone(int $companyId, string $phone) + { + $hash = Security::blindIndex($phone); + $contact = $this->db->query( + "SELECT * FROM {$this->table} WHERE company_id = ? AND phone_hash = ? LIMIT 1", + [$companyId, $hash] + )->fetch(); + + return $this->decryptContact($contact); + } + + /** + * Retrieve all contacts for a company + */ + public function findAllByCompany(int $companyId) + { + $contacts = $this->db->query( + "SELECT * FROM {$this->table} WHERE company_id = ? ORDER BY id DESC", + [$companyId] + )->fetchAll(); + + foreach ($contacts as &$contact) { + $contact = $this->decryptContact($contact); + } + + return $contacts; + } + + /** + * Helper to decrypt sensitive fields + */ + private function decryptContact($contact) + { + if ($contact) { + $contact['phone'] = !empty($contact['phone']) ? Security::decrypt($contact['phone']) : null; + $contact['email'] = !empty($contact['email']) ? Security::decrypt($contact['email']) : null; + $contact['notes'] = !empty($contact['notes']) ? Security::decrypt($contact['notes']) : null; + // Remove hashes from response + unset($contact['phone_hash'], $contact['email_hash']); + } + return $contact; + } +} diff --git a/backend/app/Models/ContactGroup.php b/backend/app/Models/ContactGroup.php new file mode 100644 index 0000000..67167f3 --- /dev/null +++ b/backend/app/Models/ContactGroup.php @@ -0,0 +1,56 @@ +db->query( + "SELECT 1 FROM contact_group_relations WHERE group_id = ? AND contact_id = ?", + [$groupId, $contactId] + )->fetch(); + + if (!$exists) { + $this->db->query( + "INSERT INTO contact_group_relations (group_id, contact_id) VALUES (?, ?)", + [$groupId, $contactId] + ); + } + return true; + } + + /** + * Remove a contact from this group + */ + public function detachContact(int $groupId, int $contactId) + { + $this->db->query( + "DELETE FROM contact_group_relations WHERE group_id = ? AND contact_id = ?", + [$groupId, $contactId] + ); + return true; + } + + /** + * Get all raw contact records for a group (Decryption needed after fetch) + */ + public function getRawContacts(int $groupId) + { + return $this->db->query( + "SELECT c.* FROM contacts c + JOIN contact_group_relations cgr ON c.id = cgr.contact_id + WHERE cgr.group_id = ?", + [$groupId] + )->fetchAll(); + } +} diff --git a/backend/app/Models/MessageLog.php b/backend/app/Models/MessageLog.php new file mode 100644 index 0000000..b35fed1 --- /dev/null +++ b/backend/app/Models/MessageLog.php @@ -0,0 +1,35 @@ +create($data); + } +} diff --git a/backend/app/Models/Template.php b/backend/app/Models/Template.php new file mode 100644 index 0000000..a9f84fd --- /dev/null +++ b/backend/app/Models/Template.php @@ -0,0 +1,61 @@ +create($data); + } + + /** + * Retrieve and decrypt templates + */ + public function findAllByCompany(int $companyId) + { + $templates = $this->db->query( + "SELECT * FROM {$this->table} WHERE company_id = ? ORDER BY id DESC", + [$companyId] + )->fetchAll(); + + foreach ($templates as &$template) { + if (!empty($template['media_url'])) { + $template['media_url'] = Security::decrypt($template['media_url']); + } + } + + return $templates; + } + + /** + * Get single template and decrypt + */ + public function findByIdAndCompany(int $id, int $companyId) + { + $template = $this->db->query( + "SELECT * FROM {$this->table} WHERE id = ? AND company_id = ? LIMIT 1", + [$id, $companyId] + )->fetch(); + + if ($template && !empty($template['media_url'])) { + $template['media_url'] = Security::decrypt($template['media_url']); + } + + return $template; + } +} diff --git a/backend/app/Models/WhatsAppSession.php b/backend/app/Models/WhatsAppSession.php new file mode 100644 index 0000000..e5639d7 --- /dev/null +++ b/backend/app/Models/WhatsAppSession.php @@ -0,0 +1,89 @@ +db->query( + "SELECT * FROM {$this->table} WHERE company_id = ? LIMIT 1", + [$companyId] + )->fetch(); + + if ($session) { + $session['phone'] = $session['phone'] ? Security::decrypt($session['phone']) : null; + $session['qr_code'] = $session['qr_code'] ? Security::decrypt($session['qr_code']) : null; + } + + return $session; + } + + /** + * Get a session by session_key (used by webhooks) + */ + public function findBySessionKey(string $sessionKey) + { + $session = $this->db->query( + "SELECT * FROM {$this->table} WHERE session_key = ? LIMIT 1", + [$sessionKey] + )->fetch(); + + if ($session) { + $session['phone'] = $session['phone'] ? Security::decrypt($session['phone']) : null; + $session['qr_code'] = $session['qr_code'] ? Security::decrypt($session['qr_code']) : null; + } + + return $session; + } + + /** + * Create or retrieve a new session for a company + */ + public function findOrCreate(int $companyId, string $name = 'Main WhatsApp') + { + $session = $this->findByCompany($companyId); + if ($session) { + return $session; + } + + $sessionKey = 'cmp_' . $companyId . '_' . bin2hex(random_bytes(4)); + + $id = $this->create([ + 'company_id' => $companyId, + 'name' => $name, + 'session_key' => $sessionKey, + 'status' => 'disconnected' + ]); + + return $this->findByCompany($companyId); + } + + /** + * Update session state securely + */ + public function updateState(int $id, array $data) + { + if (isset($data['phone'])) { + $data['phone_hash'] = Security::blindIndex($data['phone']); + $data['phone'] = Security::encrypt($data['phone']); + } + + if (isset($data['qr_code'])) { + $data['qr_code'] = Security::encrypt($data['qr_code']); + } + + return $this->update($id, $data); + } +} diff --git a/backend/public/index.php b/backend/public/index.php index a259813..bf436a4 100644 --- a/backend/public/index.php +++ b/backend/public/index.php @@ -35,6 +35,25 @@ $router->post('/api/auth/register', [\App\Controllers\AuthController::class, 're $router->post('/api/auth/login', [\App\Controllers\AuthController::class, 'login'], [\App\Middlewares\RateLimitMiddleware::class]); $router->get('/api/auth/me', [\App\Controllers\AuthController::class, 'me'], [\App\Middlewares\AuthMiddleware::class]); +// WhatsApp Gateway Routes +$router->get('/api/whatsapp/status', [\App\Controllers\WhatsAppController::class, 'status'], [\App\Middlewares\AuthMiddleware::class]); +$router->post('/api/whatsapp/qr', [\App\Controllers\WhatsAppController::class, 'requestQr'], [\App\Middlewares\AuthMiddleware::class]); +$router->post('/api/whatsapp/disconnect', [\App\Controllers\WhatsAppController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class]); +$router->post('/api/whatsapp/webhook', [\App\Controllers\WhatsAppController::class, 'webhook']); // No AuthMiddleware (Protected by WEBHOOK_SECRET internally) + +// Phase 4 & 5: CRM, Templates & Campaigns Routes +$router->get('/api/contacts', [\App\Controllers\ContactController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]); +$router->post('/api/contacts', [\App\Controllers\ContactController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]); + +$router->get('/api/groups', [\App\Controllers\GroupController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]); +$router->post('/api/groups', [\App\Controllers\GroupController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]); +$router->post('/api/groups/add', [\App\Controllers\GroupController::class, 'addContact'], [\App\Middlewares\AuthMiddleware::class]); + +$router->get('/api/templates', [\App\Controllers\TemplateController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]); +$router->post('/api/templates', [\App\Controllers\TemplateController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]); + +$router->get('/api/campaigns', [\App\Controllers\CampaignController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]); +$router->post('/api/campaigns', [\App\Controllers\CampaignController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]); // 4. Dispatch the request $router->dispatch($request, $response); diff --git a/backend/public/plan.html b/backend/public/plan.html new file mode 100644 index 0000000..22afab2 --- /dev/null +++ b/backend/public/plan.html @@ -0,0 +1,1020 @@ + + + + + + خطة عمل وتطوير تطبيق نبيه | Nabeh Roadmap + + + + + + + + + +
+ + +
+
+
+ + + +
+ نبيه +
+

خطة عمل وتطوير تطبيق نبيه (Nabeh)

+

الملخص الهيكلي ومراحل التطوير للوصول إلى نظام تشغيل متكامل لخدمة المتاجر.

+
+ + +
+
+
+ الهدف الرئيسي للمشروع +

حل مشاكل التواصل والتسويق للمتاجر الإلكترونية

+
+

+ يهدف تطبيق نبيه (Nabeh) إلى بناء نظام خلفي متكامل، آمن، ومتعدد المستأجرين (Multi-Tenant SaaS)، يندمج بسلاسة مع بوابة WhatsApp المبنية على مكتبة Baileys (Node.js). يُمكن المشروع أصحاب المتاجر الإلكترونية والشركات من إرسال حملات إعلانية وتوعوية جماعية، إدارة قائمة عملائهم وسجلات محادثاتهم، وتهيئة ردود تلقائية ذكية عبر الذكاء الاصطناعي (Gemini AI) أو الكلمات المفتاحية، مع توفير أقصى درجات الحماية وتشفير البيانات وعزل بيانات كل شركة عن الأخرى بشكل قطعي. +

+ + +

الركائز الأمنية والهيكلية التي تم تأسيسها وبحثها:

+
+ +
+
+ +
+
+

تشفير البيانات الحساسة

+

تشفير الهواتف والبريد الإلكتروني والرسائل بـ AES-256-GCM لضمان سريتها التامة في قاعدة البيانات.

+
+
+ +
+
+ +
+
+

الفهرسة العمياء (Blind Index)

+

توليد قيم هاش غير قابلة للعكس بـ HMAC-SHA256 للبحث السريع في البيانات المشفرة دون الحاجة لفك التشفير.

+
+
+ +
+
+ +
+
+

أمان المدخلات والمخرجات

+

تنظيف المدخلات ديناميكياً وعزل أخطاء الخادم لتجنب ثغرات SQL Injection و Info Disclosure.

+
+
+ +
+
+ +
+
+

تعدد المستأجرين المعزول

+

ربط قطعي لكافة الموارد بمعرّف الشركة company_id مع التحقق في الـ Middleware لمنع تسريب الموارد (IDOR).

+
+
+ +
+
+
+ + +
+ + + + +
+ + +
+ + +
+
+
+
+
+ المرحلة 1 +

تأسيس النواة البرمجية والهيكل MVC

+
+ مكتملة ✓ +
+
+

بناء الهيكل البرمجي الأساسي للتطبيق وعزل مجلدات الكود الحساسة وتفعيل موجه المسارات والتحميل التلقائي.

+
    +
  • +
    + هيكلة مجلدات المشروع وعزل الملفات العامة في public/ +
  • +
  • +
    + بناء موجه طلبات خفيف ومرن Router متوافق مع خادم Nginx +
  • +
  • +
    + إعداد قارئ ملفات البيئة .env الآمن وتفعيل التحميل التلقائي PSR-4 +
  • +
  • +
    + إنشاء الموديل الرئيسي BaseModel والمتحكم العام BaseController +
  • +
+
+ Pure PHP OOP + PSR-4 Autoload + PDO / MySQL +
+
+
+
+ + +
+
+
+
+
+ المرحلة 2 +

المصادقة الأمنية وتدقيق الحماية والتعديلات

+
+ مكتملة ✓ +
+
+

تفعيل بروتوكول الحماية الصارم على مدخلات ومخرجات الخلفية، بناء نظام التوثيق بالـ JWT، وتطبيق حلول معالجة الثغرات والـ Rate Limiter.

+
    +
  • +
    + تشفير وحماية البيانات الحساسة عبر دوال التشفير الثنائي والـ Blind Index +
  • +
  • +
    + برمجة وسيط الحماية للحد من الطلبات العشوائية RateLimitMiddleware لعمليات الدخول +
  • +
  • +
    + تطبيق التحقق من الهوية JWT Authentication وسحب بيانات الجلسة الحالية +
  • +
  • +
    + معالجة ثغرات حماية المجلدات والـ CORS Origin والأخطاء البرمجية الهيكلية +
  • +
+
+ JWT Token + AES-256-GCM + HMAC-SHA256 + Rate Limiting +
+
+
+
+ + +
+
+
+
+
+ المرحلة 3 +

بوابة واتساب والربط بخدمة Baileys

+
+ قيد العمل الحالية ⚡ +
+
+

الربط البرمجي بخدمة بوابة الواتساب الجارية على البورت 3722. إرسال واستقبال الطلبات لجلب رموز الـ QR والتحقق من حالة الاتصال وتخزين الجلسات.

+
    +
  • +
    + برمجة WhatsAppController للتحكم بالجلسات عبر بوابة Baileys API +
  • +
  • +
    + بناء مسارات جلب رمز الاستجابة السريع GET /api/whatsapp/qr وتحديث الجلسات +
  • +
  • +
    + برمجة آلية استهلاك خطافات الويب Webhooks لمزامنة حالة الاتصال الفوري والرسائل الواردة +
  • +
  • +
    + التحقق الهيكلي من تداخل الجلسات وعزلها التام لكل شركة برمجياً +
  • +
+
+ Baileys Gateway + Curl Handler + Webhooks Listener + Port 3722 +
+
+
+
+ + +
+
+
+
+
+ المرحلة 4 +

إدارة جهات الاتصال والمجموعات (Multi-Tenant Contacts)

+
+ مخطط لها ⏱ +
+
+

تطوير نظام شامل لإدارة جهات اتصال المتاجر وتصنيفهم ضمن مجموعات وقوائم إرسال محددة مع تشفير الهواتف بالكامل وحظر تسريبها.

+
    +
  • +
    + برمجة مسارات الإضافة والتعديل والبحث لجهات الاتصال المشفرة +
  • +
  • +
    + بناء نظام المجموعات وتصنيف جهات الاتصال ديناميكياً +
  • +
  • +
    + تنفيذ فلتر فريد يمنع تكرار الهواتف للعملاء على مستوى الشركة الواحدة فقط +
  • +
+
+ CRM CRUD + Pivot Relations + Database Isolation +
+
+
+
+ + +
+
+
+
+
+ المرحلة 5 +

نظام المراسلة والقوالب الجاهزة (Campaigns & Templates)

+
+ مخطط لها ⏱ +
+
+

بناء نظام إعداد قوالب الرسائل الجاهزة (نصوص وصور ومستندات) وإطلاق الحملات الجماعية المجدولة للعملاء.

+
    +
  • +
    + تطوير مستودع القوالب الجاهزة ودعم المتغيرات مثل {{name}} +
  • +
  • +
    + بناء طابور معالجة الحملات وتوليد عمليات الإرسال الآمنة بفوارق زمنية لتجنب الحظر +
  • +
  • +
    + تسجيل وتأكيد حالات الرسائل (مرسلة، مستلمة، مقروءة) في جدول messages_log +
  • +
+
+ Campaign Dispatcher + Anti-Ban Intervals + Variables Replace +
+
+
+
+ + +
+
+
+
+
+ المرحلة 6 +

روبوت الرد التلقائي والمساعد الذكي (AI Auto-Responder)

+
+ مخطط لها ⏱ +
+
+

بناء عقل روبوت الرد التلقائي للتفاعل الفوري مع رسائل العملاء بناء على الكلمات الدلالية أو التوليد الذكي عبر الذكاء الاصطناعي.

+
    +
  • +
    + تهيئة جداول قواعد الردود بالكلمات المفتاحية والدقيقة +
  • +
  • +
    + الدمج البرمجي مع مكتبة Gemini AI API لصياغة ردود ذكية مخصصة لبيانات المتجر +
  • +
  • +
    + توفير خاصية تعطيل وتفعيل البوت التلقائي فوراً لكل جلسة واتساب +
  • +
+
+ Gemini API + LLM Prompting + Pattern Matcher +
+
+
+
+ + +
+
+
+
+
+ المرحلة 7 +

الربط الخارجي ومزامنة المتاجر (Integrations & Sync)

+
+ مخطط لها ⏱ +
+
+

فتح المنصة لاستقبال البيانات التلقائية للمشترين من المنصات الخارجية مثل سلة، شوبيفاي، ووكمرس وإرسال تنبيهات السلات المتروكة.

+
    +
  • +
    + توليد مفاتيح ربط المطورين وتوثيق المتاجر بالـ API Key الخاص بكل مستأجر +
  • +
  • +
    + بناء مسار استقبال الفواتير والطلبات الجارية لمزامنة العملاء تلقائياً +
  • +
  • +
    + برمجة حملات السلات المتروكة الآلية وتنبيهات حالات الشحن والتوصيل +
  • +
+
+ Merchant API Keys + Webhook Handlers + Salla/Shopify Sync +
+
+
+
+ +
+ + + + +
+ + + + +