Skip to content
Permalink
Browse files

Create UI for receiving and sending messages and create API routes

  • Loading branch information
john committed Apr 24, 2017
1 parent 19cf87b commit 0017766e77e4bf8512989a7e4bc4e6fc7a6d9752
@@ -31,6 +31,7 @@
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)
)';
@@ -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 @@ public function save() {
$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 @@ public function delete() {
$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 @@ public static function all() {
$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 @@ public static function all_for($user_id) {
});
}
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);
}
@@ -0,0 +1,39 @@
<?php
defined('APP_DIR') or define('APP_DIR', __DIR__ . '/../app/');
include_once(APP_DIR . 'include/http.php');
include_once(APP_DIR . 'model/SecureMessage.php');
include_once(APP_DIR . 'model/User.php');
$data = User::authenticated();
if (!$data) {
echo json_encode(['unauthorized']);
exit();
}
/* POST API Routes */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$params = Http::post_params();
$action = $params['action'];
if ($action === 'mark_unread') {
$m = SecureMessage::getByID($params['message_id']);
$m->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']);
}
}
}
?>
@@ -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;
}
@@ -0,0 +1,3 @@
.menu-messages-count {
min-width: 1rem !important;
}
@@ -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;
}
@@ -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 = '<li><div class="collapsible-header">';
listItem += '<i class="material-icons">arrow_forward</i>';
listItem += recipient;
listItem += '</div><div class="collapsible-body"><span>';
listItem += message;
listItem += '</span></div></li>';
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)
}
})
});
@@ -0,0 +1,105 @@
<?php
defined('APP_DIR') or define('APP_DIR', __DIR__ . '/../app/');
include_once(APP_DIR . 'model/User.php');
include_once(APP_DIR . 'model/SecureMessage.php');
include_once(APP_DIR . 'include/http.php');
$data = User::authenticated();
if (!$data) {
Http::redirect('index.php');
}
$user = User::get($data->username);
$inbox = SecureMessage::all_to($user->id);
$outbox = SecureMessage::all_from($user->id);
include 'template/header.html';
include 'template/user_menu_button.php';
?>

<div class="row messages-container">
<div class="col l8 m10 s12 offset-l2 offset-m1">
<div class="row">
<div class="col s12">
<h1> Messages </h1>
</div>
<div class="col s12 card-panel">
<ul class="tabs">
<li class="tab col s3"><a href="#inbox">Inbox</a></li>
<li class="tab col s3"><a href="#sent">Sent</a></li>
</ul>
</div>
<div id="inbox" class="col s12">
<ul id="messages-list" class="collapsible popout" data-collapsible="accordion">
<?php foreach($inbox as $message) { ?>
<li value="<?php echo $message->id ?>">
<div class="collapsible-header">
<?php if ($message->is_read) { ?>
<i id="inbox-icon-<?php echo $message->id ?>"
class="material-icons">check
</i>
<?php } else { ?>
<i id="inbox-icon-<?php echo $message->id ?>"
class="material-icons red-text">fiber_new
</i>
<?php } ?>
<?php echo $message->sender()->username ?>
</div>
<div class="collapsible-body">
<span>
<?php echo $message->message ?>
</span>
</div>
</li>
<?php } ?>
</ul>
</div>
<div id="sent" class="col s12" style="display:none">
<ul id="sent-list" class="collapsible popout" data-collapsible="accordion">
<?php foreach($outbox as $message) { ?>
<li>
<div class="collapsible-header">
<?php if ($message->is_read) { ?>
<i class="material-icons green-text">check_circle</i>
<?php } else { ?>
<i class="material-icons">arrow_forward</i>
<?php } ?>
<?php echo $message->receiver()->username ?>
</div>
<div class="collapsible-body">
<span>
<?php echo $message->message ?>
</span>
</div>
</li>
<?php } ?>
</ul>
</div>
</div>
</div>
</div>

<div class="row send-message-container">
<div class="col l8 m10 s12 offset-l2 offset-m1 z-depth-3 message-compose-box">
<input type="hidden" id="user-id" value="<?php echo $user->id ?>">
<div class="input-field col s6">
<input placeholder="Enter their username" id="recipient" type="text" class="validate">
<label id="recipient-label" for="recipient">Recipient</label>
</div>
<div clas="col s12"></div>
<div class="input-field col s12">
<textarea placeholder="Craft a message..." id="message-compose" class="materialize-textarea"></textarea>
</div>
</div>
</div>

<div class="fixed-action-btn">
<button data-position="top" data-delay="50" data-tooltip="Send message"
id="send-button" class="modal-trigger btn-floating btn-large blue tooltipped">
<i class="large material-icons waves-effect waves-light">send</i>
</button>
</div>

<?php
include 'template/footer.html';
?>
@@ -4,6 +4,7 @@
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="js/app.js"></script>
</body>
</html>
@@ -1,15 +1,24 @@
<?php
defined('APP_DIR') or define('APP_DIR', __DIR__ . '/../app/');
include_once(APP_DIR . 'model/User.php');
include_once(APP_DIR . 'model/SecureMessage.php');
include_once(APP_DIR . 'include/http.php');
$data = User::authenticated();
$user = User::get($data->username);
$num_unread = SecureMessage::count_unread($user->id);
?>

<div class="user-button-container">
<!-- Dropdown Trigger -->
<a class='dropdown-button btn user-button' href='#' data-activates='dropdown1'>
<a class='dropdown-button btn user-button'data-activates='dropdown1'>
<?php echo $data->username ?>
<?php if ($num_unread > 0) { ?>
<span class="notification-count">
<?php echo $num_unread ?>
</span>
<?php } ?>
</a>

<!-- Dropdown Structure -->
@@ -20,6 +29,12 @@
<li><a href ="admin.php">Admin</a></li>
<?php } ?>
<li class="divider"></li>
<li><a href="messages.php">
Messages
<span class="new badge menu-messages-count" data-badge-caption="">
<?php echo $num_unread ?>
</span>
</a></li>
<li><a href="logout.php">Sign out</a></li>
</ul>
</div>

0 comments on commit 0017766

Please sign in to comment.
You can’t perform that action at this time.