Blog post

Design Patterns, Libraries and APIs

By Peter West • 28th April 2014 22:28

Originally posted for the
Web Science COMP6051 module, University of Southampton

During our development of the LeapIn.it prototype, we have used a number of design patterns, libraries and API’s. This post will give an overview of the most crucial of these.

Libraries (blue), APIs (red) and design patterns (green) used within the LeapIn.it prototype, and where they are used.
Libraries (blue), APIs (red) and design patterns (green) used within the LeapIn.it prototype, and where they are used.

Server

The server is responsible for creating a REST interface for database access. As mentioned in our post on architecture, we have chosen to use RedBeanPHP to communicate with the database, and Slim.php as a REST endpoint. We have also used tokens for security, PHPImageWorkshop for thumbnail generation and AlchemyAPI for sentiment analysis.

Database access through RedBean

RedBean is an Object-Relational Mapping (ORM), meaning that records in the database are available as objects. For example, the following PHP code may be used to change a person that exists in the database:

// Load the person with ID 209 from the database
$person = R::load('person', 209);
// Get their name
$name = $person->name;
// Change their name
$person->name = 'Joel';
// Update the database
R::store($person);

Using RedBean results in simple and more maintainable code when accessing the database. It also provides security over direct SQL queries, as it prevents SQL injection.

REST interface through Slim.php

Slim provides a simple way to create a REST endpoint on the server, which we use to allow clients to interact with the database.

As an example, the following code allows a user to create a user. First, the client will sent a POST request to ‘/api/person’ with the username and password in the request body. All interactions with the database are done through RedBean (denoted by R). Using Slim.php, we listen for that response. In addition, we use middleware to ensure that all interactions are in JavaScript Object Notation (JSON) – this is done by adding the variable $requestJSON in the first line. The body of the function first checks if the username is already in use, and if not, creates a “person” with a username and a password (which is stored as a SHA1 hash for security). An avatar is then created by copying a random template (there are about 50 avatar templates in the database by default). The person is then stored in the database and exported as REST to the HTTP response.

/leapinit/blob/master/server/index.php
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
$app->post('/person/', $requestJSON, function () use (&$app, &$params) {
	if (R::findOne('person', ' username = ? ', array($params->username))) {
		$app->render(401, [
			'msg' => 'Username already in use.'
		]);
	} else {
		$person = R::dispense('person');
		$person->username = $params->username;
		$person->password = sha1($params->password);
 
		$avatar = R::dup(R::findOne('avatar', ' ORDER BY RAND() LIMIT 1 '));
		$avatar->bgcolor = randomColor();
		$person->avatar = $avatar;
		$person->joined = time();
 
		R::store($person);
 
		$app->render(201, [
			'response' => exportPerson($person)
		]);
	}
});

Security through tokens

Because the server and client will reside in different domains, it is not possible to use cookies for authentication. Instead, we have used tokens, which are unique random strings assigned to authenticated users, which they must provide whenever they make a request to the server. This is based on a model provided in this Stack Exchange discussion.

The token is sent as part of the URL. For example, to request a room, the token would be placed on then end: http://leapin.it/api/room/4?token=[user's token]

This is not actually safe over HTTP, as the token may be observed by third parties who see the URL. If a person were to steal a token, they would be able to authenticate themselves as the user by appending the token to the end of URL’s. This is known as session hijacking. To prevent this, encryption will need to be used, ideally through SSL.

Checking for tokens is done as middleware within Slim.php. The middleware is a function which validates that a token exists and identifies the user it belongs to:

$validateToken = function () use ($app) {
	$tokenKey = $app->request()->params('token');
	error_log('checking token ' . $tokenKey);
	$token = R::findOne('token', ' `key` = ? ', array($tokenKey));
	error_log('ok');
	if ($token !== null) {
		error_log('found token with user', $token->person_id);
		$user = R::load('person', $token->person_id);
	}
	if (isset($user) && $user->id !== 0) {
		$app->user = $user;
	} else {
		error_log('no user');
		$app->render(401, [
			'msg' => 'Unauthorized.'
		]);
	}
};

This can then be used when defining a request response, as shown below when fetching a user:

/leapinit/blob/master/server/index.php
248
249
250
251
$app->get('/person/:id/', $requestJSON, $validateToken, function ($id) use (&$app, &$params) {
    $person = R::load('person', intval($id));
    $app->render(200, ['result' => exportPerson($person)]);
});

Thumbnails using PHPImageWorkshop

When viewing posts on the cleint, hexagon thumbnails are displayed to form a honeycomb. These thumbnails are generated using PHPImageWorkshop. As an example, the following code loads a picture, crops it to the largest square possible, and then resizes it to 100×100 pixels. Finally, it save the file.

$layer = ImageWorkshop::initFromPath($file);
$layer->cropMaximumInPixel(0, 0, 'MM');
$layer->resizeInPixel(100, 100)
imagepng($layer->getResult(), 'thumb.png');

Sentiment analysis through AlchemyAPI

To form the honeycomb, all posts needed to be converted to thumbnails, including text. Simply displaying text in a small hexagon would not be effective – it would be too small and cramped to read. Instead, we wanted to display a preview of what the text may contain. We therefore form a preview comprising of the following:

For both keyword extraction and sentiment analysis, we use AlchemyAPI, which has been trained with vast amounts of data, such that it is ready to analyse data with diverse contexts. The sentiment is returned as a value between -1 and 1, which we use to adjust the hue of the hexagon, such that a negative mood is red, and a positive mood is green.

Analysis of "Tango is awesome!"Analysis of “Tango is awesome!”. Sentiment is 0.9 – extremely positive – so the background is green. The keyword is “Tango”. Analysis of "I hate orange!"Analysis of “I hate orange!”. Sentiment is -0.8 – very negative – so the background is red. The keyword is “orange”. Analysis of "i have a tv!"Analysis of “i have a tv”. Sentiment is 0.2 – fairly neutral – so the background is off-green. The keyword is “tv”.

Client

The client is the application which people will use to access LeapIn.it. We use HTML, CSS and JavaScript, and use Apache Cordova to package this as native Android and iPhone apps. Backbone is used to interface with the REST endpoint and Angular is used to generate DOM content.

Apache Cordova to create mobile apps

Cordova provides a wrapper for HTML, CSS and JavaScript, such that it may be packaged as a mobile app. It also exposes a hardware-level API which can be used within JavaScript.

The process of converting a web application to a Cordova application is minimal – it simply needs an app name and icon.

We use Apache Cordova to utilise the camera as a barcode scanner. The following code is responsible for that:

/leapinit/blob/master/app/www/js/screens/scan.js
13
14
15
cordova.plugins.barcodeScanner.scan(function (result) {
	scan(result.text); // find a room for this code
});

Backbone used as a REST client

To interface with the REST endpoint on the server, Backbone.js is used. This provides an object mapper for models, allowing us to modify database records much like we would with RedBean on the server. As an example, the following will get and change a room title:

// Define the Rooms collection
var Rooms = Backbone.Collection.extend({
	url: 'http://leapin.it/api/rooms'
});
// Instantiate the collection
var rooms = new Rooms();
// Fetch all rooms
rooms.fetch().then(function () {
	// Get room with ID 10
	var myroom = people.get(10);
	// Get the title
	var title = myroom.get('title');
	// Change the title
	myroom.save({ title: 'myroom' }).then(function () {
		console.log('SAVED!');
	});
});

Angular used to generate DOM

Angular provides HTML markup to generate the DOM automatically when data has loaded. For example, the following will generate a list of rooms:

<ul class="rooms">
	<li><a href="{{ room.url }}">{{ room.title }}</a></li>
</ul>

Other libraries used on the client

 

Conclusion

The use of existing design patterns, libraries and API’s has greatly reduced the complexity of development, allowing us to develop three prototypes in a short period of time.