Deploy: 2026-05-21 15:33:14
This commit is contained in:
27
backend/app/Models/Campaign.php
Normal file
27
backend/app/Models/Campaign.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Campaign Model
|
||||
* Manages broadcast campaigns linking templates to contact groups.
|
||||
*/
|
||||
class Campaign extends BaseModel
|
||||
{
|
||||
protected string $table = 'campaigns';
|
||||
|
||||
/**
|
||||
* Get all campaigns for a company
|
||||
*/
|
||||
public function findAllByCompany(int $companyId)
|
||||
{
|
||||
return $this->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();
|
||||
}
|
||||
}
|
||||
104
backend/app/Models/Contact.php
Normal file
104
backend/app/Models/Contact.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Core\Security;
|
||||
|
||||
/**
|
||||
* Contact Model
|
||||
* Handles the contacts table with military-grade encryption for PII.
|
||||
*/
|
||||
class Contact extends BaseModel
|
||||
{
|
||||
protected string $table = 'contacts';
|
||||
|
||||
/**
|
||||
* Create a new contact with encryption
|
||||
*/
|
||||
public function createSecure(array $data)
|
||||
{
|
||||
if (!empty($data['phone'])) {
|
||||
$data['phone_hash'] = Security::blindIndex($data['phone']);
|
||||
$data['phone'] = Security::encrypt($data['phone']);
|
||||
}
|
||||
|
||||
if (!empty($data['email'])) {
|
||||
$data['email_hash'] = Security::blindIndex($data['email']);
|
||||
$data['email'] = Security::encrypt($data['email']);
|
||||
}
|
||||
|
||||
if (!empty($data['notes'])) {
|
||||
$data['notes'] = Security::encrypt($data['notes']);
|
||||
}
|
||||
|
||||
return $this->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;
|
||||
}
|
||||
}
|
||||
56
backend/app/Models/ContactGroup.php
Normal file
56
backend/app/Models/ContactGroup.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* ContactGroup Model
|
||||
* Manages groupings/lists of contacts for broadcast campaigns.
|
||||
*/
|
||||
class ContactGroup extends BaseModel
|
||||
{
|
||||
protected string $table = 'contact_groups';
|
||||
|
||||
/**
|
||||
* Attach a contact to this group
|
||||
*/
|
||||
public function attachContact(int $groupId, int $contactId)
|
||||
{
|
||||
$exists = $this->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();
|
||||
}
|
||||
}
|
||||
35
backend/app/Models/MessageLog.php
Normal file
35
backend/app/Models/MessageLog.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Core\Security;
|
||||
|
||||
/**
|
||||
* MessageLog Model
|
||||
* Records every message sent or received with full payload encryption.
|
||||
*/
|
||||
class MessageLog extends BaseModel
|
||||
{
|
||||
protected string $table = 'messages_log';
|
||||
|
||||
/**
|
||||
* Securely log a new message
|
||||
*/
|
||||
public function logMessage(array $data)
|
||||
{
|
||||
if (!empty($data['contact_phone'])) {
|
||||
$data['contact_phone_hash'] = Security::blindIndex($data['contact_phone']);
|
||||
$data['contact_phone'] = Security::encrypt($data['contact_phone']);
|
||||
}
|
||||
|
||||
if (!empty($data['message_body'])) {
|
||||
$data['message_body'] = Security::encrypt($data['message_body']);
|
||||
}
|
||||
|
||||
if (!empty($data['media_url'])) {
|
||||
$data['media_url'] = Security::encrypt($data['media_url']);
|
||||
}
|
||||
|
||||
return $this->create($data);
|
||||
}
|
||||
}
|
||||
61
backend/app/Models/Template.php
Normal file
61
backend/app/Models/Template.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Core\Security;
|
||||
|
||||
/**
|
||||
* Template Model
|
||||
* Stores predefined WhatsApp message templates with variables.
|
||||
*/
|
||||
class Template extends BaseModel
|
||||
{
|
||||
protected string $table = 'templates';
|
||||
|
||||
/**
|
||||
* Create template with encrypted media URL if exists
|
||||
*/
|
||||
public function createSecure(array $data)
|
||||
{
|
||||
if (!empty($data['media_url'])) {
|
||||
$data['media_url'] = Security::encrypt($data['media_url']);
|
||||
}
|
||||
return $this->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;
|
||||
}
|
||||
}
|
||||
89
backend/app/Models/WhatsAppSession.php
Normal file
89
backend/app/Models/WhatsAppSession.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Core\Security;
|
||||
|
||||
/**
|
||||
* WhatsAppSession Model
|
||||
* Handles the whatsapp_sessions table with encryption for phone and QR code.
|
||||
*/
|
||||
class WhatsAppSession extends BaseModel
|
||||
{
|
||||
protected string $table = 'whatsapp_sessions';
|
||||
|
||||
/**
|
||||
* Get the session for a specific company
|
||||
*/
|
||||
public function findByCompany(int $companyId)
|
||||
{
|
||||
$session = $this->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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user