From 2516f4214ec225df6319a05bffb6867ef7ee41fa Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 17 Nov 2025 01:22:13 +0000 Subject: [PATCH] 3 --- api.php | 159 +++++++++++++++ assets/css/custom.css | 18 ++ assets/js/main.js | 182 +++++++++++++++++- db/migrate.php | 35 +++- db/migrations/000_create_migrations_table.sql | 5 + .../002_create_conversations_table.sql | 4 + ...create_conversation_participants_table.sql | 7 + db/migrations/004_create_messages_table.sql | 9 + index.php | 21 +- 9 files changed, 417 insertions(+), 23 deletions(-) create mode 100644 api.php create mode 100644 db/migrations/000_create_migrations_table.sql create mode 100644 db/migrations/002_create_conversations_table.sql create mode 100644 db/migrations/003_create_conversation_participants_table.sql create mode 100644 db/migrations/004_create_messages_table.sql diff --git a/api.php b/api.php new file mode 100644 index 0000000..a64641a --- /dev/null +++ b/api.php @@ -0,0 +1,159 @@ + 'User not authenticated']); + exit; +} + +$action = $_GET['action'] ?? ''; + +switch ($action) { + case 'search_users': + search_users(); + break; + case 'start_conversation': + start_conversation(); + break; + case 'get_conversations': + get_conversations(); + break; + case 'get_messages': + get_messages(); + break; + case 'send_message': + send_message(); + break; + default: + http_response_code(400); + echo json_encode(['error' => 'Invalid action']); + exit; +} + +function search_users() { + $term = $_GET['term'] ?? ''; + if (empty($term)) { + echo json_encode([]); + exit; + } + + $pdo = db(); + $stmt = $pdo->prepare("SELECT id, username FROM users WHERE username LIKE ? AND id != ?"); + $stmt->execute(['%' . $term . '%', $_SESSION['user_id']]); + $users = $stmt->fetchAll(PDO::FETCH_ASSOC); + + header('Content-Type: application/json'); + echo json_encode($users); +} + +function start_conversation() { + $recipient_id = $_POST['recipient_id'] ?? ''; + if (empty($recipient_id)) { + http_response_code(400); + echo json_encode(['error' => 'Recipient ID is required']); + exit; + } + + $user_id = $_SESSION['user_id']; + + $pdo = db(); + + // Check if a conversation already exists between the two users + $stmt = $pdo->prepare(" + SELECT c.id + FROM conversations c + JOIN conversation_participants cp1 ON c.id = cp1.conversation_id + JOIN conversation_participants cp2 ON c.id = cp2.conversation_id + WHERE cp1.user_id = ? AND cp2.user_id = ? + "); + $stmt->execute([$user_id, $recipient_id]); + $conversation = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($conversation) { + // Conversation already exists + header('Content-Type: application/json'); + echo json_encode(['conversation_id' => $conversation['id']]); + exit; + } + + // Create a new conversation + $pdo->beginTransaction(); + try { + $stmt = $pdo->prepare("INSERT INTO conversations () VALUES ()"); + $stmt->execute(); + $conversation_id = $pdo->lastInsertId(); + + $stmt = $pdo->prepare("INSERT INTO conversation_participants (conversation_id, user_id) VALUES (?, ?), (?, ?)"); + $stmt->execute([$conversation_id, $user_id, $conversation_id, $recipient_id]); + + $pdo->commit(); + + header('Content-Type: application/json'); + echo json_encode(['conversation_id' => $conversation_id]); + } catch (Exception $e) { + $pdo->rollBack(); + http_response_code(500); + echo json_encode(['error' => 'Failed to create conversation']); + } +} + +function get_conversations() { + $user_id = $_SESSION['user_id']; + $pdo = db(); + + $stmt = $pdo->prepare(" + SELECT c.id, u.username, u.id as user_id + FROM conversations c + JOIN conversation_participants cp ON c.id = cp.conversation_id + JOIN users u ON u.id = cp.user_id + WHERE c.id IN ( + SELECT conversation_id + FROM conversation_participants + WHERE user_id = ? + ) AND cp.user_id != ? + "); + $stmt->execute([$user_id, $user_id]); + $conversations = $stmt->fetchAll(PDO::FETCH_ASSOC); + + header('Content-Type: application/json'); + echo json_encode($conversations); +} + +function get_messages() { + $conversation_id = $_GET['conversation_id'] ?? ''; + if (empty($conversation_id)) { + http_response_code(400); + echo json_encode(['error' => 'Conversation ID is required']); + exit; + } + + $pdo = db(); + $stmt = $pdo->prepare("SELECT * FROM messages WHERE conversation_id = ? ORDER BY created_at ASC"); + $stmt->execute([$conversation_id]); + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); + + header('Content-Type: application/json'); + echo json_encode($messages); +} + +function send_message() { + $conversation_id = $_POST['conversation_id'] ?? ''; + $message_text = $_POST['message_text'] ?? ''; + + if (empty($conversation_id) || empty($message_text)) { + http_response_code(400); + echo json_encode(['error' => 'Conversation ID and message text are required']); + exit; + } + + $sender_id = $_SESSION['user_id']; + + $pdo = db(); + $stmt = $pdo->prepare("INSERT INTO messages (conversation_id, sender_id, message_text) VALUES (?, ?, ?)"); + $stmt->execute([$conversation_id, $sender_id, $message_text]); + + header('Content-Type: application/json'); + echo json_encode(['success' => true]); +} diff --git a/assets/css/custom.css b/assets/css/custom.css index fac9f56..aede516 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -158,3 +158,21 @@ body { background-color: #ffffff; border-top: 1px solid #e5e7eb; } + +.avatar-placeholder { + width: 48px; + height: 48px; + border-radius: 50%; + background-color: #e9ecef; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: #6c757d; +} + +.avatar-placeholder.avatar-sm { + width: 40px; + height: 40px; + font-size: 20px; +} diff --git a/assets/js/main.js b/assets/js/main.js index 11b0b00..ab80e10 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,29 +1,191 @@ - document.addEventListener('DOMContentLoaded', function() { const chatForm = document.getElementById('chat-form'); const messageInput = document.getElementById('message-input'); + const userSearchForm = document.getElementById('user-search-form'); + const userSearchInput = document.getElementById('user-search-input'); + const searchResultsContainer = document.getElementById('search-results'); + const conversationListContainer = document.getElementById('conversation-list-container'); + const chatBody = document.querySelector('.chat-body'); + const chatHeaderName = document.querySelector('.chat-header .name'); + const chatHeaderStatus = document.querySelector('.chat-header .status'); + + let currentConversationId = null; if (chatForm) { chatForm.addEventListener('submit', function(e) { e.preventDefault(); const message = messageInput.value.trim(); - if (message) { - console.log('Sending message:', message); - // In a real app, you'd send this to the server. - // For this demo, we'll just clear the input. - messageInput.value = ''; + if (message && currentConversationId) { + sendMessage(currentConversationId, message); + } + }); + } - // Optional: Add the message to the UI for instant feedback - const chatBody = document.querySelector('.chat-body'); + if (userSearchForm) { + userSearchForm.addEventListener('submit', function(e) { + e.preventDefault(); + const searchTerm = userSearchInput.value.trim(); + if (searchTerm) { + searchUsers(searchTerm); + } + }); + } + + function searchUsers(term) { + fetch(`api.php?action=search_users&term=${term}`) + .then(response => response.json()) + .then(users => { + displaySearchResults(users); + }) + .catch(error => console.error('Error searching users:', error)); + } + + function displaySearchResults(users) { + searchResultsContainer.innerHTML = ''; + if (users.length === 0) { + searchResultsContainer.innerHTML = '

No users found.

'; + return; + } + + users.forEach(user => { + const userElement = document.createElement('div'); + userElement.classList.add('conversation-item'); + userElement.dataset.userId = user.id; + userElement.innerHTML = ` +
+ +
+
+
${user.username}
+
+ `; + userElement.addEventListener('click', () => { + startConversation(user.id); + }); + searchResultsContainer.appendChild(userElement); + }); + } + + function startConversation(recipientId) { + const formData = new FormData(); + formData.append('recipient_id', recipientId); + + fetch('api.php?action=start_conversation', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.conversation_id) { + loadMessages(data.conversation_id); + userSearchInput.value = ''; + searchResultsContainer.innerHTML = ''; + loadConversations(); + } else { + console.error('Failed to start conversation:', data.error); + } + }) + .catch(error => console.error('Error starting conversation:', error)); + } + + function loadConversations() { + fetch('api.php?action=get_conversations') + .then(response => response.json()) + .then(conversations => { + displayConversations(conversations); + }) + .catch(error => console.error('Error loading conversations:', error)); + } + + function displayConversations(conversations) { + conversationListContainer.innerHTML = ''; + if (conversations.length === 0) { + conversationListContainer.innerHTML = '

No conversations yet.

'; + return; + } + + conversations.forEach(conversation => { + const conversationElement = document.createElement('div'); + conversationElement.classList.add('conversation-item'); + conversationElement.dataset.conversationId = conversation.id; + conversationElement.dataset.recipientId = conversation.user_id; + conversationElement.innerHTML = ` +
+ +
+
+
${conversation.username}
+
+ `; + conversationElement.addEventListener('click', () => { + loadMessages(conversation.id, conversation.username); + }); + conversationListContainer.appendChild(conversationElement); + }); + } + + function loadMessages(conversationId, username) { + currentConversationId = conversationId; + chatHeaderName.textContent = username; + chatHeaderStatus.textContent = 'Online'; // Placeholder + + fetch(`api.php?action=get_messages&conversation_id=${conversationId}`) + .then(response => response.json()) + .then(messages => { + displayMessages(messages); + }) + .catch(error => console.error('Error loading messages:', error)); + } + + function displayMessages(messages) { + chatBody.innerHTML = ''; + if (messages.length === 0) { + chatBody.innerHTML = '

No messages yet. Say hi!

'; + return; + } + + messages.forEach(message => { + const messageElement = document.createElement('div'); + const isSent = message.sender_id == window.userId; + messageElement.classList.add('message', isSent ? 'sent' : 'received'); + messageElement.innerHTML = ` +
${message.message_text}
+
${new Date(message.created_at).toLocaleTimeString()}
+ `; + chatBody.appendChild(messageElement); + }); + chatBody.scrollTop = chatBody.scrollHeight; + } + + function sendMessage(conversationId, messageText) { + const formData = new FormData(); + formData.append('conversation_id', conversationId); + formData.append('message_text', messageText); + + fetch('api.php?action=send_message', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + messageInput.value = ''; + // Add message to UI immediately const messageElement = document.createElement('div'); messageElement.classList.add('message', 'sent'); messageElement.innerHTML = ` -
${message}
+
${messageText}
Just now
`; chatBody.appendChild(messageElement); chatBody.scrollTop = chatBody.scrollHeight; + } else { + console.error('Failed to send message:', data.error); } - }); + }) + .catch(error => console.error('Error sending message:', error)); } + + // Load conversations on page load + loadConversations(); }); diff --git a/db/migrate.php b/db/migrate.php index 06aba1c..ef651a5 100644 --- a/db/migrate.php +++ b/db/migrate.php @@ -4,18 +4,35 @@ require_once __DIR__ . '/config.php'; function run_migrations() { try { $pdo = db(); - $migration_file = __DIR__ . '/migrations/001_create_users_table.sql'; - if (file_exists($migration_file)) { - $sql = file_get_contents($migration_file); - $pdo->exec($sql); - echo "Migration '001_create_users_table.sql' applied successfully.\n"; - } else { - echo "Migration file not found.\n"; + + // Create migrations table if it doesn't exist + $pdo->exec("CREATE TABLE IF NOT EXISTS migrations (id INT AUTO_INCREMENT PRIMARY KEY, migration_name VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)"); + + // Get all migration files + $migration_files = glob(__DIR__ . '/migrations/*.sql'); + sort($migration_files); + + // Get already run migrations + $stmt = $pdo->query("SELECT migration_name FROM migrations"); + $run_migrations = $stmt->fetchAll(PDO::FETCH_COLUMN); + + foreach ($migration_files as $migration_file) { + $migration_name = basename($migration_file); + + if (!in_array($migration_name, $run_migrations)) { + $sql = file_get_contents($migration_file); + $pdo->exec($sql); + + $stmt = $pdo->prepare("INSERT INTO migrations (migration_name) VALUES (?)"); + $stmt->execute([$migration_name]); + + echo "Migration '" . $migration_name . "' applied successfully.\n"; + } } + } catch (PDOException $e) { die("Migration failed: " . $e->getMessage() . "\n"); } } -run_migrations(); - +run_migrations(); \ No newline at end of file diff --git a/db/migrations/000_create_migrations_table.sql b/db/migrations/000_create_migrations_table.sql new file mode 100644 index 0000000..3575ce8 --- /dev/null +++ b/db/migrations/000_create_migrations_table.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS migrations ( + id INT AUTO_INCREMENT PRIMARY KEY, + migration_name VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/db/migrations/002_create_conversations_table.sql b/db/migrations/002_create_conversations_table.sql new file mode 100644 index 0000000..91da198 --- /dev/null +++ b/db/migrations/002_create_conversations_table.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS conversations ( + id INT AUTO_INCREMENT PRIMARY KEY, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/db/migrations/003_create_conversation_participants_table.sql b/db/migrations/003_create_conversation_participants_table.sql new file mode 100644 index 0000000..81d06d7 --- /dev/null +++ b/db/migrations/003_create_conversation_participants_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS conversation_participants ( + id INT AUTO_INCREMENT PRIMARY KEY, + conversation_id INT NOT NULL, + user_id INT NOT NULL, + FOREIGN KEY (conversation_id) REFERENCES conversations(id), + FOREIGN KEY (user_id) REFERENCES users(id) +); \ No newline at end of file diff --git a/db/migrations/004_create_messages_table.sql b/db/migrations/004_create_messages_table.sql new file mode 100644 index 0000000..c8a93ab --- /dev/null +++ b/db/migrations/004_create_messages_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS messages ( + id INT AUTO_INCREMENT PRIMARY KEY, + conversation_id INT NOT NULL, + sender_id INT NOT NULL, + message_text TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (conversation_id) REFERENCES conversations(id), + FOREIGN KEY (sender_id) REFERENCES users(id) +); \ No newline at end of file diff --git a/index.php b/index.php index ea35e73..372360e 100644 --- a/index.php +++ b/index.php @@ -33,6 +33,10 @@ if (!isset($_SESSION['user_id'])) { + + @@ -44,16 +48,23 @@ if (!isset($_SESSION['user_id'])) {