Initialisation du projet geosector complet (web + flutter)
This commit is contained in:
213
flutt/lib/chat/scripts/chat_tables.sql
Normal file
213
flutt/lib/chat/scripts/chat_tables.sql
Normal file
@@ -0,0 +1,213 @@
|
||||
-- Script de création des tables chat pour MariaDB
|
||||
-- Compatible avec le module chat GEOSECTOR
|
||||
-- Création des tables pour le système de chat
|
||||
|
||||
-- Table des salles de discussion
|
||||
DROP TABLE IF EXISTS `chat_rooms`;
|
||||
CREATE TABLE `chat_rooms` (
|
||||
`id` varchar(50) NOT NULL,
|
||||
`type` enum('privee', 'groupe', 'liste_diffusion', 'broadcast', 'announcement') NOT NULL,
|
||||
`title` varchar(100) DEFAULT NULL,
|
||||
`date_creation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`fk_user` int unsigned NOT NULL,
|
||||
`fk_entite` int unsigned DEFAULT NULL,
|
||||
`statut` enum('active', 'archive') NOT NULL DEFAULT 'active',
|
||||
`description` text,
|
||||
`reply_permission` enum('all', 'admins_only', 'sender_only', 'none') NOT NULL DEFAULT 'all',
|
||||
`is_pinned` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`expiry_date` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user` (`fk_user`),
|
||||
KEY `idx_entite` (`fk_entite`),
|
||||
KEY `idx_type` (`type`),
|
||||
KEY `idx_statut` (`statut`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table des participants aux salles de discussion
|
||||
DROP TABLE IF EXISTS `chat_participants`;
|
||||
CREATE TABLE `chat_participants` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`id_room` varchar(50) NOT NULL,
|
||||
`id_user` int unsigned DEFAULT NULL,
|
||||
`anonymous_id` varchar(50) DEFAULT NULL,
|
||||
`role` enum('administrateur', 'participant', 'en_lecture_seule') NOT NULL DEFAULT 'participant',
|
||||
`date_ajout` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`notification_activee` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||
`last_read_message_id` varchar(50) DEFAULT NULL,
|
||||
`via_target` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`can_reply` tinyint(1) unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_room` (`id_room`),
|
||||
KEY `idx_user` (`id_user`),
|
||||
KEY `idx_anonymous_id` (`anonymous_id`),
|
||||
CONSTRAINT `fk_chat_participants_room` FOREIGN KEY (`id_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `uc_room_user` UNIQUE (`id_room`, `id_user`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table des messages
|
||||
DROP TABLE IF EXISTS `chat_messages`;
|
||||
CREATE TABLE `chat_messages` (
|
||||
`id` varchar(50) NOT NULL,
|
||||
`fk_room` varchar(50) NOT NULL,
|
||||
`fk_user` int unsigned DEFAULT NULL,
|
||||
`sender_type` enum('user', 'anonymous', 'system') NOT NULL DEFAULT 'user',
|
||||
`content` text,
|
||||
`content_type` enum('text', 'image', 'file') NOT NULL DEFAULT 'text',
|
||||
`date_sent` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`date_delivered` timestamp NULL DEFAULT NULL,
|
||||
`date_read` timestamp NULL DEFAULT NULL,
|
||||
`statut` enum('envoye', 'livre', 'lu', 'error') NOT NULL DEFAULT 'envoye',
|
||||
`is_announcement` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_room` (`fk_room`),
|
||||
KEY `idx_user` (`fk_user`),
|
||||
KEY `idx_date` (`date_sent`),
|
||||
KEY `idx_status` (`statut`),
|
||||
CONSTRAINT `fk_chat_messages_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table des cibles d'audience
|
||||
DROP TABLE IF EXISTS `chat_audience_targets`;
|
||||
CREATE TABLE `chat_audience_targets` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_room` varchar(50) NOT NULL,
|
||||
`target_type` enum('role', 'entity', 'all', 'combined') NOT NULL DEFAULT 'all',
|
||||
`target_id` varchar(50) DEFAULT NULL,
|
||||
`role_filter` varchar(20) DEFAULT NULL,
|
||||
`entity_filter` varchar(50) DEFAULT NULL,
|
||||
`date_creation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_room` (`fk_room`),
|
||||
KEY `idx_type` (`target_type`),
|
||||
CONSTRAINT `fk_chat_audience_targets_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table des listes de diffusion
|
||||
DROP TABLE IF EXISTS `chat_broadcast_lists`;
|
||||
CREATE TABLE `chat_broadcast_lists` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_room` varchar(50) NOT NULL,
|
||||
`name` varchar(100) NOT NULL,
|
||||
`description` text,
|
||||
`fk_user_creator` int unsigned NOT NULL,
|
||||
`date_creation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_room` (`fk_room`),
|
||||
KEY `idx_user_creator` (`fk_user_creator`),
|
||||
CONSTRAINT `fk_chat_broadcast_lists_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table pour suivre la lecture des messages
|
||||
DROP TABLE IF EXISTS `chat_read_messages`;
|
||||
CREATE TABLE `chat_read_messages` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_message` varchar(50) NOT NULL,
|
||||
`fk_user` int unsigned NOT NULL,
|
||||
`date_read` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_message` (`fk_message`),
|
||||
KEY `idx_user` (`fk_user`),
|
||||
CONSTRAINT `uc_message_user` UNIQUE (`fk_message`, `fk_user`),
|
||||
CONSTRAINT `fk_chat_read_messages_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table des notifications
|
||||
DROP TABLE IF EXISTS `chat_notifications`;
|
||||
CREATE TABLE `chat_notifications` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_user` int unsigned NOT NULL,
|
||||
`fk_message` varchar(50) DEFAULT NULL,
|
||||
`fk_room` varchar(50) DEFAULT NULL,
|
||||
`type` varchar(50) NOT NULL,
|
||||
`contenu` text,
|
||||
`date_creation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`date_lecture` timestamp NULL DEFAULT NULL,
|
||||
`statut` enum('non_lue', 'lue') NOT NULL DEFAULT 'non_lue',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user` (`fk_user`),
|
||||
KEY `idx_message` (`fk_message`),
|
||||
KEY `idx_room` (`fk_room`),
|
||||
KEY `idx_statut` (`statut`),
|
||||
CONSTRAINT `fk_chat_notifications_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE SET NULL,
|
||||
CONSTRAINT `fk_chat_notifications_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table des utilisateurs anonymes (pour Resalice)
|
||||
DROP TABLE IF EXISTS `chat_anonymous_users`;
|
||||
CREATE TABLE `chat_anonymous_users` (
|
||||
`id` varchar(50) NOT NULL,
|
||||
`device_id` varchar(100) NOT NULL,
|
||||
`name` varchar(100) DEFAULT NULL,
|
||||
`email` varchar(100) DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`converted_to_user_id` int unsigned DEFAULT NULL,
|
||||
`metadata` json DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_device_id` (`device_id`),
|
||||
KEY `idx_converted_user` (`converted_to_user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table pour la file d'attente hors ligne
|
||||
DROP TABLE IF EXISTS `chat_offline_queue`;
|
||||
CREATE TABLE `chat_offline_queue` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int unsigned NOT NULL,
|
||||
`operation_type` varchar(50) NOT NULL,
|
||||
`operation_data` json NOT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`processed_at` timestamp NULL DEFAULT NULL,
|
||||
`status` enum('pending', 'processing', 'completed', 'failed') NOT NULL DEFAULT 'pending',
|
||||
`error_message` text,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Table pour les pièces jointes
|
||||
DROP TABLE IF EXISTS `chat_attachments`;
|
||||
CREATE TABLE `chat_attachments` (
|
||||
`id` varchar(50) NOT NULL,
|
||||
`fk_message` varchar(50) NOT NULL,
|
||||
`file_name` varchar(255) NOT NULL,
|
||||
`file_path` varchar(500) NOT NULL,
|
||||
`file_type` varchar(100) NOT NULL,
|
||||
`file_size` int unsigned NOT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_message` (`fk_message`),
|
||||
CONSTRAINT `fk_chat_attachments_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Vues utiles
|
||||
|
||||
-- Vue des messages avec informations utilisateur
|
||||
CREATE OR REPLACE VIEW `chat_messages_with_users` AS
|
||||
SELECT
|
||||
m.*,
|
||||
u.name as sender_name,
|
||||
u.username as sender_username,
|
||||
u.fk_entite as sender_entity_id
|
||||
FROM chat_messages m
|
||||
LEFT JOIN users u ON m.fk_user = u.id;
|
||||
|
||||
-- Vue des conversations avec compte de messages non lus
|
||||
CREATE OR REPLACE VIEW `chat_conversations_unread` AS
|
||||
SELECT
|
||||
r.*,
|
||||
COUNT(DISTINCT m.id) as total_messages,
|
||||
COUNT(DISTINCT rm.id) as read_messages,
|
||||
COUNT(DISTINCT m.id) - COUNT(DISTINCT rm.id) as unread_messages,
|
||||
(SELECT date_sent FROM chat_messages
|
||||
WHERE fk_room = r.id
|
||||
ORDER BY date_sent DESC LIMIT 1) as last_message_date
|
||||
FROM chat_rooms r
|
||||
LEFT JOIN chat_messages m ON r.id = m.fk_room
|
||||
LEFT JOIN chat_read_messages rm ON m.id = rm.fk_message
|
||||
GROUP BY r.id;
|
||||
|
||||
-- Index supplémentaires pour les performances
|
||||
CREATE INDEX idx_messages_unread ON chat_messages(fk_room, statut);
|
||||
CREATE INDEX idx_participants_active ON chat_participants(id_room, id_user, notification_activee);
|
||||
CREATE INDEX idx_notifications_unread ON chat_notifications(fk_user, statut);
|
||||
323
flutt/lib/chat/scripts/mqtt_notification_sender.php
Normal file
323
flutt/lib/chat/scripts/mqtt_notification_sender.php
Normal file
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
/**
|
||||
* Service d'envoi de notifications MQTT pour le chat
|
||||
*
|
||||
* Ce script gère l'envoi des notifications via MQTT depuis le backend PHP
|
||||
*/
|
||||
|
||||
require_once 'vendor/autoload.php'; // PhpMqtt
|
||||
|
||||
use PhpMqtt\Client\MqttClient;
|
||||
use PhpMqtt\Client\ConnectionSettings;
|
||||
|
||||
class MqttNotificationSender {
|
||||
private $mqtt;
|
||||
private $db;
|
||||
private $config;
|
||||
|
||||
public function __construct($dbConnection, $mqttConfig) {
|
||||
$this->db = $dbConnection;
|
||||
$this->config = $mqttConfig;
|
||||
|
||||
// Initialiser le client MQTT
|
||||
$this->initializeMqttClient();
|
||||
}
|
||||
|
||||
private function initializeMqttClient() {
|
||||
$this->mqtt = new MqttClient(
|
||||
$this->config['host'],
|
||||
$this->config['port'],
|
||||
'geosector_api_' . uniqid(), // Client ID unique
|
||||
MqttClient::MQTT_3_1_1
|
||||
);
|
||||
|
||||
$connectionSettings = (new ConnectionSettings)
|
||||
->setUsername($this->config['username'])
|
||||
->setPassword($this->config['password'])
|
||||
->setKeepAliveInterval(60)
|
||||
->setConnectTimeout(30)
|
||||
->setUseTls($this->config['use_ssl'] ?? false);
|
||||
|
||||
$this->mqtt->connect($connectionSettings, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie une notification pour un nouveau message
|
||||
*/
|
||||
public function sendMessageNotification($receiverId, $senderId, $messageId, $content, $conversationId) {
|
||||
try {
|
||||
// Vérifier les préférences de notification
|
||||
$settings = $this->getUserNotificationSettings($receiverId);
|
||||
|
||||
if (!$this->shouldSendNotification($settings, $conversationId)) {
|
||||
return ['status' => 'skipped', 'reason' => 'notification_settings'];
|
||||
}
|
||||
|
||||
// Obtenir les informations de l'expéditeur
|
||||
$sender = $this->getSenderInfo($senderId);
|
||||
|
||||
// Obtenir le nom de la conversation
|
||||
$conversationName = $this->getConversationName($conversationId, $receiverId);
|
||||
|
||||
// Préparer le payload de la notification
|
||||
$payload = [
|
||||
'type' => 'chat_message',
|
||||
'messageId' => $messageId,
|
||||
'conversationId' => $conversationId,
|
||||
'senderId' => $senderId,
|
||||
'senderName' => $sender['name'] ?? 'Utilisateur',
|
||||
'content' => $settings['show_preview'] ? $content : 'Nouveau message',
|
||||
'conversationName' => $conversationName,
|
||||
'timestamp' => time(),
|
||||
];
|
||||
|
||||
// Définir le topic MQTT
|
||||
$topic = sprintf('chat/user/%s/messages', $receiverId);
|
||||
|
||||
// Publier le message
|
||||
$this->mqtt->publish($topic, json_encode($payload), 1);
|
||||
|
||||
// Enregistrer la notification dans la base de données
|
||||
$this->saveNotificationToDatabase($receiverId, $messageId, $conversationId, $payload);
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'topic' => $topic
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'reason' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie une annonce à plusieurs utilisateurs
|
||||
*/
|
||||
public function sendBroadcastAnnouncement($audienceTargets, $messageId, $title, $content, $conversationId) {
|
||||
$results = [];
|
||||
$userIds = $this->resolveAudienceTargets($audienceTargets);
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
// Préparer le payload pour l'annonce
|
||||
$payload = [
|
||||
'type' => 'announcement',
|
||||
'messageId' => $messageId,
|
||||
'conversationId' => $conversationId,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'timestamp' => time(),
|
||||
];
|
||||
|
||||
// Envoyer à chaque utilisateur
|
||||
$topic = sprintf('chat/user/%s/messages', $userId);
|
||||
|
||||
try {
|
||||
$this->mqtt->publish($topic, json_encode($payload), 1);
|
||||
$results[$userId] = ['status' => 'success'];
|
||||
|
||||
// Enregistrer la notification
|
||||
$this->saveNotificationToDatabase($userId, $messageId, $conversationId, $payload);
|
||||
} catch (Exception $e) {
|
||||
$results[$userId] = ['status' => 'error', 'reason' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
// Publier aussi sur le topic général des annonces
|
||||
$this->mqtt->publish('chat/announcement', json_encode($payload), 1);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie une notification à une conversation spécifique
|
||||
*/
|
||||
public function sendConversationNotification($conversationId, $messageId, $senderId, $content) {
|
||||
$participants = $this->getConversationParticipants($conversationId);
|
||||
|
||||
foreach ($participants as $participant) {
|
||||
if ($participant['id'] !== $senderId) {
|
||||
$this->sendMessageNotification(
|
||||
$participant['id'],
|
||||
$senderId,
|
||||
$messageId,
|
||||
$content,
|
||||
$conversationId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une notification doit être envoyée
|
||||
*/
|
||||
private function shouldSendNotification($settings, $conversationId) {
|
||||
if (!$settings['enable_notifications']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($conversationId, $settings['muted_conversations'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($settings['do_not_disturb'] && $this->isInDoNotDisturbPeriod($settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les paramètres de notification de l'utilisateur
|
||||
*/
|
||||
private function getUserNotificationSettings($userId) {
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT * FROM notification_settings
|
||||
WHERE user_id = ?
|
||||
");
|
||||
|
||||
$stmt->execute([$userId]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Valeurs par défaut si pas de préférences
|
||||
return $result ?: [
|
||||
'enable_notifications' => true,
|
||||
'show_preview' => true,
|
||||
'muted_conversations' => [],
|
||||
'do_not_disturb' => false,
|
||||
'do_not_disturb_start' => null,
|
||||
'do_not_disturb_end' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si on est dans la période "Ne pas déranger"
|
||||
*/
|
||||
private function isInDoNotDisturbPeriod($settings) {
|
||||
if (!$settings['do_not_disturb']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = new DateTime();
|
||||
$start = new DateTime($settings['do_not_disturb_start']);
|
||||
$end = new DateTime($settings['do_not_disturb_end']);
|
||||
|
||||
if ($start < $end) {
|
||||
return $now >= $start && $now <= $end;
|
||||
} else {
|
||||
// Période qui chevauche minuit
|
||||
return $now >= $start || $now <= $end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre la notification dans la base de données
|
||||
*/
|
||||
private function saveNotificationToDatabase($userId, $messageId, $conversationId, $payload) {
|
||||
$stmt = $this->db->prepare("
|
||||
INSERT INTO chat_notifications
|
||||
(fk_user, fk_message, fk_room, type, contenu, statut)
|
||||
VALUES (?, ?, ?, ?, ?, 'non_lue')
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$userId,
|
||||
$messageId,
|
||||
$conversationId,
|
||||
$payload['type'],
|
||||
json_encode($payload)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les informations de l'expéditeur
|
||||
*/
|
||||
private function getSenderInfo($senderId) {
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT id, name, username
|
||||
FROM users
|
||||
WHERE id = ?
|
||||
");
|
||||
|
||||
$stmt->execute([$senderId]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le nom de la conversation
|
||||
*/
|
||||
private function getConversationName($conversationId, $userId) {
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT title
|
||||
FROM chat_rooms
|
||||
WHERE id = ?
|
||||
");
|
||||
|
||||
$stmt->execute([$conversationId]);
|
||||
return $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les participants d'une conversation
|
||||
*/
|
||||
private function getConversationParticipants($conversationId) {
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT id_user as id, role
|
||||
FROM chat_participants
|
||||
WHERE id_room = ? AND notification_activee = 1
|
||||
");
|
||||
|
||||
$stmt->execute([$conversationId]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Résout les cibles d'audience en une liste d'IDs utilisateur
|
||||
*/
|
||||
private function resolveAudienceTargets($targets) {
|
||||
$userIds = [];
|
||||
|
||||
foreach ($targets as $target) {
|
||||
switch ($target['target_type']) {
|
||||
case 'all':
|
||||
$stmt = $this->db->query("SELECT id FROM users WHERE chk_active = 1");
|
||||
$userIds = array_merge($userIds, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
break;
|
||||
|
||||
case 'role':
|
||||
$stmt = $this->db->prepare("SELECT id FROM users WHERE fk_role = ?");
|
||||
$stmt->execute([$target['role_filter']]);
|
||||
$userIds = array_merge($userIds, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
break;
|
||||
|
||||
case 'entity':
|
||||
$stmt = $this->db->prepare("SELECT id FROM users WHERE fk_entite = ?");
|
||||
$stmt->execute([$target['entity_filter']]);
|
||||
$userIds = array_merge($userIds, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
break;
|
||||
|
||||
case 'combined':
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT id FROM users
|
||||
WHERE fk_role = ? AND fk_entite = ?
|
||||
");
|
||||
$stmt->execute([$target['role_filter'], $target['entity_filter']]);
|
||||
$userIds = array_merge($userIds, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ferme la connexion MQTT
|
||||
*/
|
||||
public function disconnect() {
|
||||
if ($this->mqtt) {
|
||||
$this->mqtt->disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
263
flutt/lib/chat/scripts/send_notification.php
Normal file
263
flutt/lib/chat/scripts/send_notification.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
/**
|
||||
* Script d'envoi de notifications push pour le chat
|
||||
*
|
||||
* Ce script est appelé par l'API backend pour envoyer des notifications
|
||||
* lorsqu'un nouveau message est reçu
|
||||
*/
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use Kreait\Firebase\Factory;
|
||||
use Kreait\Firebase\Messaging\CloudMessage;
|
||||
use Kreait\Firebase\Messaging\Notification;
|
||||
|
||||
class ChatNotificationSender {
|
||||
private $messaging;
|
||||
private $db;
|
||||
|
||||
public function __construct($firebaseServiceAccount, $dbConnection) {
|
||||
$factory = (new Factory)->withServiceAccount($firebaseServiceAccount);
|
||||
$this->messaging = $factory->createMessaging();
|
||||
$this->db = $dbConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie une notification à un utilisateur pour un nouveau message
|
||||
*/
|
||||
public function sendMessageNotification($userId, $messageId, $senderId, $content, $conversationId) {
|
||||
try {
|
||||
// Récupérer les préférences de notification de l'utilisateur
|
||||
$settings = $this->getUserNotificationSettings($userId);
|
||||
|
||||
if (!$settings['enable_notifications']) {
|
||||
return ['status' => 'skipped', 'reason' => 'notifications_disabled'];
|
||||
}
|
||||
|
||||
// Vérifier si la conversation est en silencieux
|
||||
if (in_array($conversationId, $settings['muted_conversations'])) {
|
||||
return ['status' => 'skipped', 'reason' => 'conversation_muted'];
|
||||
}
|
||||
|
||||
// Vérifier le mode Ne pas déranger
|
||||
if ($this->isInDoNotDisturbPeriod($settings)) {
|
||||
return ['status' => 'skipped', 'reason' => 'do_not_disturb'];
|
||||
}
|
||||
|
||||
// Obtenir le token du device
|
||||
$deviceToken = $this->getUserDeviceToken($userId);
|
||||
if (!$deviceToken) {
|
||||
return ['status' => 'error', 'reason' => 'no_device_token'];
|
||||
}
|
||||
|
||||
// Obtenir les informations de l'expéditeur
|
||||
$sender = $this->getSenderInfo($senderId);
|
||||
|
||||
// Obtenir le nom de la conversation
|
||||
$conversationName = $this->getConversationName($conversationId, $userId);
|
||||
|
||||
// Préparation du contenu de la notification
|
||||
$title = $conversationName ?? $sender['name'];
|
||||
$body = $settings['show_preview'] ? $content : 'Nouveau message';
|
||||
|
||||
// Créer le message Firebase
|
||||
$message = CloudMessage::withTarget('token', $deviceToken)
|
||||
->withNotification(Notification::create($title, $body))
|
||||
->withData([
|
||||
'type' => 'chat_message',
|
||||
'messageId' => $messageId,
|
||||
'conversationId' => $conversationId,
|
||||
'senderId' => $senderId,
|
||||
'click_action' => 'FLUTTER_NOTIFICATION_CLICK',
|
||||
])
|
||||
->withAndroidConfig([
|
||||
'priority' => 'high',
|
||||
'notification' => [
|
||||
'sound' => $settings['sound_enabled'] ? 'default' : null,
|
||||
'channel_id' => 'chat_messages',
|
||||
'icon' => 'ic_launcher',
|
||||
],
|
||||
])
|
||||
->withApnsConfig([
|
||||
'payload' => [
|
||||
'aps' => [
|
||||
'sound' => $settings['sound_enabled'] ? 'default' : null,
|
||||
'badge' => 1, // TODO: Calculer le nombre réel de messages non lus
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Envoyer la notification
|
||||
$result = $this->messaging->send($message);
|
||||
|
||||
// Enregistrer la notification dans la base de données
|
||||
$this->saveNotificationToDatabase($userId, $messageId, $conversationId, $title, $body);
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message_id' => $result,
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'reason' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie une notification de type broadcast
|
||||
*/
|
||||
public function sendBroadcastNotification($audienceTargets, $messageId, $content, $conversationId) {
|
||||
$results = [];
|
||||
|
||||
// Résoudre les cibles d'audience
|
||||
$userIds = $this->resolveAudienceTargets($audienceTargets);
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
$result = $this->sendMessageNotification($userId, $messageId, null, $content, $conversationId);
|
||||
$results[$userId] = $result;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre la notification dans la base de données
|
||||
*/
|
||||
private function saveNotificationToDatabase($userId, $messageId, $conversationId, $title, $body) {
|
||||
$stmt = $this->db->prepare("
|
||||
INSERT INTO chat_notifications (fk_user, fk_message, fk_room, type, contenu, statut)
|
||||
VALUES (?, ?, ?, 'chat_message', ?, 'non_lue')
|
||||
");
|
||||
|
||||
$stmt->execute([$userId, $messageId, $conversationId, json_encode([
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les préférences de notification de l'utilisateur
|
||||
*/
|
||||
private function getUserNotificationSettings($userId) {
|
||||
// Implémenter la logique pour récupérer les paramètres
|
||||
return [
|
||||
'enable_notifications' => true,
|
||||
'sound_enabled' => true,
|
||||
'vibration_enabled' => true,
|
||||
'muted_conversations' => [],
|
||||
'show_preview' => true,
|
||||
'do_not_disturb' => false,
|
||||
'do_not_disturb_start' => null,
|
||||
'do_not_disturb_end' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si on est dans la période Ne pas déranger
|
||||
*/
|
||||
private function isInDoNotDisturbPeriod($settings) {
|
||||
if (!$settings['do_not_disturb']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = new DateTime();
|
||||
$start = new DateTime($settings['do_not_disturb_start']);
|
||||
$end = new DateTime($settings['do_not_disturb_end']);
|
||||
|
||||
if ($start < $end) {
|
||||
return $now >= $start && $now <= $end;
|
||||
} else {
|
||||
// Période qui chevauche minuit
|
||||
return $now >= $start || $now <= $end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le token du device de l'utilisateur
|
||||
*/
|
||||
private function getUserDeviceToken($userId) {
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT device_token
|
||||
FROM notification_settings
|
||||
WHERE user_id = ? AND device_token IS NOT NULL
|
||||
ORDER BY updated_at DESC LIMIT 1
|
||||
");
|
||||
|
||||
$stmt->execute([$userId]);
|
||||
return $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les informations de l'expéditeur
|
||||
*/
|
||||
private function getSenderInfo($senderId) {
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT id, name, username
|
||||
FROM users
|
||||
WHERE id = ?
|
||||
");
|
||||
|
||||
$stmt->execute([$senderId]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le nom de la conversation
|
||||
*/
|
||||
private function getConversationName($conversationId, $userId) {
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT title
|
||||
FROM chat_rooms
|
||||
WHERE id = ?
|
||||
");
|
||||
|
||||
$stmt->execute([$conversationId]);
|
||||
return $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Résout les cibles d'audience en une liste d'IDs utilisateur
|
||||
*/
|
||||
private function resolveAudienceTargets($targets) {
|
||||
$userIds = [];
|
||||
|
||||
foreach ($targets as $target) {
|
||||
switch ($target['target_type']) {
|
||||
case 'all':
|
||||
// Récupérer tous les utilisateurs
|
||||
$stmt = $this->db->query("SELECT id FROM users WHERE chk_active = 1");
|
||||
$userIds = array_merge($userIds, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
break;
|
||||
|
||||
case 'role':
|
||||
// Récupérer les utilisateurs par rôle
|
||||
$stmt = $this->db->prepare("SELECT id FROM users WHERE fk_role = ?");
|
||||
$stmt->execute([$target['role_filter']]);
|
||||
$userIds = array_merge($userIds, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
break;
|
||||
|
||||
case 'entity':
|
||||
// Récupérer les utilisateurs par entité
|
||||
$stmt = $this->db->prepare("SELECT id FROM users WHERE fk_entite = ?");
|
||||
$stmt->execute([$target['entity_filter']]);
|
||||
$userIds = array_merge($userIds, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
break;
|
||||
|
||||
case 'combined':
|
||||
// Récupérer les utilisateurs par combinaison de rôle et entité
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT id FROM users
|
||||
WHERE fk_role = ? AND fk_entite = ?
|
||||
");
|
||||
$stmt->execute([$target['role_filter'], $target['entity_filter']]);
|
||||
$userIds = array_merge($userIds, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($userIds);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user