From 0017766e77e4bf8512989a7e4bc4e6fc7a6d9752 Mon Sep 17 00:00:00 2001 From: John Bojorquez Date: Mon, 24 Apr 2017 03:04:11 -0400 Subject: [PATCH] Create UI for receiving and sending messages and create API routes --- app/database/init.php | 1 + app/model/SecureMessage.php | 37 +++++++++- public/api.php | 39 ++++++++++ public/css/app.css | 10 ++- public/css/menu-button.css | 3 + public/css/messages.css | 15 ++++ public/js/app.js | 58 ++++++++++++++- public/messages.php | 105 +++++++++++++++++++++++++++ public/template/footer.html | 1 + public/template/user_menu_button.php | 17 ++++- 10 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 public/api.php create mode 100644 public/css/menu-button.css create mode 100644 public/css/messages.css create mode 100644 public/messages.php diff --git a/app/database/init.php b/app/database/init.php index 0f34725..7935cc7 100644 --- a/app/database/init.php +++ b/app/database/init.php @@ -31,6 +31,7 @@ $stmt = 'CREATE TABLE IF NOT EXISTS messages ( enc_keys VARCHAR(64) NOT NULL, keys_iv VARCHAR(24) NOT NULL, message_iv VARCHAR(24) NOT NULL, + is_read BOOLEAN NOT NULL DEFAULT 0, FOREIGN KEY (sender_id) REFERENCES users(id), FOREIGN KEY (receiver_id) REFERENCES users(id) )'; diff --git a/app/model/SecureMessage.php b/app/model/SecureMessage.php index f604447..849e61b 100644 --- a/app/model/SecureMessage.php +++ b/app/model/SecureMessage.php @@ -11,11 +11,21 @@ class SecureMessage { public $message; public $sender_id; public $receiver_id; + public $is_read = false; /** * Stores this information pertaining to this message in the DB */ public function save() { + $dbh = Database::connect(); + if (SecureMessage::exists($this->id)) { + $stmt = $dbh->prepare("UPDATE messages SET is_read=:is_read WHERE id=:message_id"); + $is_read = (int) $this->is_read; + $stmt->bindParam(':is_read', $is_read); + $stmt->bindParam(':message_id', $this->id); + $stmt->execute(); + return; + } $integrity_key = openssl_random_pseudo_bytes(16); $encryption_key = openssl_random_pseudo_bytes(16); $message_iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(Config::encr_algo)); @@ -27,7 +37,6 @@ class SecureMessage { $enc_keys = openssl_encrypt($integrity_key . $encryption_key, Config::encr_algo, Config::secret_key, 0, $keys_iv); - $dbh = Database::connect(); $stmt = $dbh->prepare('INSERT INTO messages (message, sender_id, receiver_id, enc_keys, keys_iv, message_iv) VALUES (:message, :sender_id, :receiver_id, :enc_keys, :keys_iv, :message_iv) '); @@ -52,6 +61,24 @@ class SecureMessage { $stmt->execute(); } + public static function exists($message_id) { + $dbh = Database::connect(); + $stmt = $dbh->prepare("SELECT count(*) FROM messages WHERE id = :message_id"); + $stmt->bindParam(':message_id', $message_id); + $stmt->execute(); + return $stmt->fetchColumn() > 0; + } + + public static function getByID($message_id) { + $messages = SecureMessage::all(); + foreach ($messages as $m) { + if ($m->id == $message_id) { + return $m; + } + } + return null; + } + public static function all() { $dbh = Database::connect(); $stmt = $dbh->prepare("SELECT * from messages"); @@ -81,6 +108,7 @@ class SecureMessage { $m->message = $message; $m->sender_id = $row['sender_id']; $m->receiver_id = $row['receiver_id']; + $m->is_read = (bool) $row['is_read']; return $m; }, $stmt->fetchAll()); @@ -111,6 +139,13 @@ class SecureMessage { }); } + public static function count_unread($user_id) { + $messages = SecureMessage::all(); + return count(array_filter($messages, function ($m) use ($user_id) { + return $m->receiver_id == $user_id && ! $m->is_read; + })); + } + public function sender() { return User::getByID($this->sender_id); } diff --git a/public/api.php b/public/api.php new file mode 100644 index 0000000..454fc0f --- /dev/null +++ b/public/api.php @@ -0,0 +1,39 @@ +is_read = true; + $m->save(); + echo json_encode(['success']); + } else if ($action === 'send_message') { + $user = User::getByID($params['user_id']); + $recipient = User::get($params['recipient']); + if (!$recipient) { + echo json_encode(['unknown_recipient']); + } else { + $m = new SecureMessage; + $m->message = $params['message']; + $m->sender_id = $user->id; + $m->receiver_id = $recipient->id; + $m->save(); + echo json_encode(['success']); + } + } + +} +?> diff --git a/public/css/app.css b/public/css/app.css index 6bc964c..0ba24a8 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -1,7 +1,8 @@ @import url("login.css"); @import url("admin.css"); @import url("user.css"); - +@import url("menu-button.css"); +@import url("messages.css"); body { background-color: #fcfcfc !important; @@ -15,3 +16,10 @@ main { input:-webkit-autofill { -webkit-box-shadow: 0 0 0 1000px white inset !important; } + +.notification-count { + background-color: #f44336; + border-radius: 50%; + padding: 2px 6px; + margin-left: 5px; +} diff --git a/public/css/menu-button.css b/public/css/menu-button.css new file mode 100644 index 0000000..3e0d083 --- /dev/null +++ b/public/css/menu-button.css @@ -0,0 +1,3 @@ +.menu-messages-count { + min-width: 1rem !important; +} diff --git a/public/css/messages.css b/public/css/messages.css new file mode 100644 index 0000000..e92a689 --- /dev/null +++ b/public/css/messages.css @@ -0,0 +1,15 @@ +.messages-container { + padding-top: 50px; +} + +.send-message-container { + position: absolute; + width: 100%; + left: 0; + bottom: 10px; +} + +.message-compose-box { + padding: 10px !important; + border-radius: 5px; +} diff --git a/public/js/app.js b/public/js/app.js index 50f51c0..c9c5d17 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -6,8 +6,8 @@ $(() => $('#login-form').addClass('scale-in')); $(() => $('.user-tile').addClass('scale-in')); $(document).ready(function(){ - // the "href" attribute of .modal-trigger must specify the modal ID that wants to be triggered $('.modal').modal(); + Materialize.showStaggeredList('#messages-list') }); $('.dropdown-button').dropdown({ @@ -25,3 +25,59 @@ $('.dropdown-button').dropdown({ $(document).ready(function(){ $('.collapsible').collapsible(); }); + + $('#messages-list li').click(function() { + var message_id = this.value; + var message_icon = $('#inbox-icon-' + message_id); + if (message_icon.html().trim() != 'check') { + var params = new URLSearchParams(); + params.append('action', 'mark_unread'); + params.append('message_id', message_id); + axios.post('/api.php', params).then(function(response) { + message_icon.html('check'); + message_icon.removeClass('red-text'); + }); + } + }) + + function addToSent(recipient, message) { + sentList = $('#sent-list'); + var listItem = '
  • '; + listItem += 'arrow_forward'; + listItem += recipient; + listItem += '
    '; + listItem += message; + listItem += '
  • '; + sentList.append(listItem); + } + + $('#send-button').click(function() { + var recipient_elem = $('#recipient'); + var compose_elem = $('#message-compose'); + var user_id = $('#user-id').val(); + + if (!recipient_elem.val().trim()) { + recipient_elem.addClass('invalid'); + $('#recipient-label').attr('data-error', 'This field is required'); + return; + } + + if (!compose_elem.val().trim()) return; + + var params = new URLSearchParams(); + params.append('action', 'send_message'); + params.append('user_id', user_id); + params.append('recipient', recipient_elem.val()); + params.append('message', compose_elem.val()); + axios.post('/api.php', params).then(function(response) { + if (response.data[0] == 'unknown_recipient') { + recipient_elem.addClass('invalid'); + $('#recipient-label').attr('data-error', 'User not found'); + } else if (response.data[0] == 'success') { + addToSent(recipient_elem.val(), compose_elem.val()); + recipient_elem.val(''); + compose_elem.val(''); + Materialize.toast('Message sent!', 4000) + } + }) + }); diff --git a/public/messages.php b/public/messages.php new file mode 100644 index 0000000..e0fb5e3 --- /dev/null +++ b/public/messages.php @@ -0,0 +1,105 @@ +username); +$inbox = SecureMessage::all_to($user->id); +$outbox = SecureMessage::all_from($user->id); + +include 'template/header.html'; +include 'template/user_menu_button.php'; +?> + +
    +
    +
    +
    +

    Messages

    +
    +
    + +
    +
    +
      + +
    • +
      + is_read) { ?> + check + + + fiber_new + + + sender()->username ?> +
      +
      + + message ?> + +
      +
    • + +
    +
    + +
    +
    +
    + +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/public/template/footer.html b/public/template/footer.html index 6924fd7..5db5d78 100644 --- a/public/template/footer.html +++ b/public/template/footer.html @@ -4,6 +4,7 @@ integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"> + diff --git a/public/template/user_menu_button.php b/public/template/user_menu_button.php index 5ab5ea8..d3f5aba 100644 --- a/public/template/user_menu_button.php +++ b/public/template/user_menu_button.php @@ -1,15 +1,24 @@ username); +$num_unread = SecureMessage::count_unread($user->id); + ?>
    - + username ?> + 0) { ?> + + + + @@ -20,6 +29,12 @@ $data = User::authenticated();
  • Admin
  • +
  • + Messages + + + +
  • Sign out