Emoji Problem

So my group decided on doing an app using emoji’s and mapbox, it’s Pokemon Go for chat where you (or businesses) could drop and discover messages (mockup screenshots below) around you. Well apparently you need to upload images to Mapbox Studio for custom marker icons in MapboxGL, the only other custom image (using css) was the user’s avartar which updated on map move rather than as a marker property. We thought we could upload all 1200+ emoji / 874 twemoji svg files and be done (they were only 1mb, why not?), but strangely there was an (AFAIK) undocumented 500 sprite limit.

Lesser is better

Anyway emojis like flags and letters would not be too relevant for our purposes, so we just had to choose which emojis we would support. Based on http://emojitracker.com/ we thought that using the people + objects categories (425 twemojis) would cover well enough ground for people to post their reactions and activities.

How to get the files

The next step was to upload the svgs. To choose a subset of twemojis to upload to Mapbox Studio and to use with react-emoji-picker in our app, I had to first get the list of twemojis we would support on the emoji picker. There was a category to name mapping, then there was a name to unicode mapping, put the two together and a script and tada I got the files, here’s the repo.

Mockups of our app

What it looks like now

Edit: Check it out - https://drop-dev.surge.sh/

Have a rival

The title on Discord’s landing page is “It’s time to ditch Skype and TeamSpeak.” For gamers used to these services, it sends a clear signal that Discord is promising to do better.

The group’s discussion of Discord positioning themselves as a competitor to other VOIP apps reminded me of a blog article I read about how Jeff Atwood of Stack Overflow saw Experts-Exchange as its arch-enemy, when he wrote

by far the most effective way to explain what we do – is this:
We’re like experts-exchange, but without all the evil.

I feel that it’s important for apps to do this especially if the technology may be foreign to some. Saying that “X is the better version of Y” is a stronger comparison than the (cliched?) advice associating a product as the “X of Y”.

Having a point of reference is easier than having to imagine an entirely new product.

In retrospect, personally many apps I’ve used have a similar argument, for example, NUSmods vs other calendars, NUSwhispers vs NUSconfessions, etc. even if they did not explicitly state it.

Thoughts

I feel that looking at weaker or ‘defective’ products is another way to ideate. By looking at ‘pain points’, rather than predicting users’ needs, we can find areas where demand is already present but not met.

To relate this to the presentation my group did about Periscope, the CEO of Meerkat (a rival app which ditched live-streaming), explains the challenge with using new technology like live-streaming -

Before Instagram, people already knew what constituted a beautiful photo and tried to take them. With live video no one really knows what ‘good’ live video they can create is.”

Perhaps it also would have been a better choice to pick an app/tech that everyone in class was familiar with, as we had to spend time explaining the app to everyone.

An aside: Like Ash and Gary, rivalry is relatable

Be the very best, that no one ever was

Discord also makes it very clear what they are best at - chat, and for who - gamers. From their logo being a speech bubble, to their focus on live chat (text and voice, and on different platforms), the group identified the niche that Discord targets - communication between gamers. I felt that this was an important point, as Discord understands gamers’ unique pains and offers them ‘painkillers’ - from a fuss-free setup (for non-technical gamers), voice chat that has the lowest cpu consumption doesn’t make you lag, to the in-game chat where you don’t need to switch to the chat application.

Compared to services like skype and teamspeak, which cater to groups other than gamers, like business and educational content, Discord stands out clearly. Differentiating themselves clearly from their competitors and directly addressing gamer-specific needs allows Discord to quickly gain a loyal following.

Thoughts

The founders being avid gamers themselves know their users inside out, and more than met the technical challenges of in-game communication. I feel that part of the reason why NUS apps like NUSmods and NUSwhispers have succeeded is that the developers themselves were the users and this gave them a deeper understanding of the needs of their users.

Gotta catch ‘em all!

Discord goes further than solving their users’ needs but also relates to them as gamers on an emotional level. I would argue that although this is subjective it is clear to me their users feel they are being understood. As Zhi An from the group pointed out during the Q&A, from their website to the loading screen backgrounds, to small details like their cute graphics are things that are easily relatable to users who identify themselves as gamers.

I feel that it is important to ‘make something users love’, as the result for Discord is a user base that is very supportive of their efforts. Some even encourage other gamers to use Discord because they like it so much. This much desired ‘organic growth’ I think comes only when users can feel that the app is a part of their own identy that they are willing to promote it, something which discord manages to do through their love for online gaming.

Another aside: like Ash, you don’t need to catch pokemon if you can become their friends.

Other thoughts

I felt that some suggestions the group had about recording music and presentations diluted the main message that Discord was ‘for gamers by gamers’, and also for that reason they are unlikely to be targetting slack though their UI looks the same and some features overlap. As a gamer the key feature I would want is free and fast VOIP chat and slack really excels more for work related tasks like using integrations and file-sharing.

The following is our process thus far for developing the exchange buddy web app:

From Ideas to Paper

After discussing as a team, we decided to go with the exchange buddy idea. Our first step was to list down the features we thought exchange students, or ourselves, would want. Eugene already had a simple wordpress site he built for exchangebuddy.com, and it was insightful to hear his view on the needs of exchange students, something I myself had not yet considered (being year 2, probably I should). Mainly the gap we identified was that exchange students who do not go to places with their friends do look for others who are going on exchange to the same university with them. Normally there would be a google form where people would fill in their contacts and people could view who else were going to that university, or a facebook group (for example: Singaporeans in Cambridge).

Also issues like accomodation, information or paperwork (for example: rules for visa holders setting up bank account) were some of the common pain points we got from talking to our friends who as of this writing had just left for exchange, which corroborated Eugene’s research from exchange buddy.

From Paper to Sketch

We then made simple paper prototypes (or on whiteboard) where we threw around ideas and layouts.

I then made a few mockups thinking of the components we would use. Pictures speak louder than words so:

After a few rounds of discussion our features changed, for example we didn’t think a listing of other university exchange groups would be that important. Also the grid layout for tips became more of a list.

I’ve been using Sketch for a while after my photoshop had expired and I actually rarely had to go back unless I’m working with heavy textures for which Sketch being vector-based is not the best for. But I’m quite biased toward it because I feel it’s less bloated and hangs less on my Macbook Air. Also I can easily export images without having to scale the artboard beforehand so it’s always so crisp, a plus for viewing/ printing. There are also resources like sketchappsources.com where many talented designers post their work, much respect to them!

From Sketch to React

To build our prototype for the mid-submission, we relied on libraries like material-ui to provide components like cards that we could use, which by default looks ‘better’ than bootstrap. After getting some feedback from tutors and students we also decided that news itself would be quite irrelevant and local events would suit the students need better.

Other React components we needed were also available which meant Irvin could also focus on what I saw as the hard part which was the backend where we were going for ‘Reactive MySQL’, ie. using meteor’s pub sub features with MySQL as our database for real-time updates. Meanwhile I just mocked the data although Thanh and Irvin had made a schema it made developing easier without having to worry about the backend for now.

So as of mid-submission we got most of the layout up and it looks as such:

Back to Paper

So after building the rough layout we decided to change the info tab and avatar menu as we felt that it didn’t really work out when we were building it. For example the list of cards were really not feasible if there were so many cards and each would be so big when expanded. We decided then to split it into a home tab and an info tab where each grid section linked to it’s own article, so it was more of the original grid layout. I felt that although some more work had to be done to reorder the layouts it was a necessary decision we had to make. So back to the drawing board for those parts:

Reflections on Prof Damith’s presentation about presentations

Prof Damith was quite engaging despite it being a saturday morning and everyone looking slightly tired and unresponsive. As Assignment 2 has a presentation component I feel that the tips he gave would be applicable, especially his emphasis that we would learn by taking initiative to follow up his on presentation, and that there really is no substitute for practice and taking action, which was quite a meta call to action.

Prof Damith gave us a high level overview into different aspects of presentations, and also introduced to us his ‘puma’ framework, which consisted of starting strongly with a punchline, considering “what’s in it for the user”, and a specific promise of a gain. And then considering the audiences’ desired actions, beliefs and knowledge to craft key points and evidences.

Keeping the user’s needs in mind helps us to prioritise the features we consider when we develop, so we can present a compelling argument to use our app in terms of the users’ benefit, and not just the features we develop. Of course if that user was the developer then that process would be easier, to get a real feel for the problem you would know it at least exists, even better if that developer can build it.

In terms of presentation visuals, I think he made a good case for appealing to emotions to make it memorable, and selling benefits instead of features, as that is what makes a difference to users. Also the way he contrasted between boring statistics like ‘1000GB of memory’ and ‘1000 hours of movies’ was a good reminder of how to craft a meaningful message to our audience.

Deciding on which framework to use for the Facebook app

After experimenting with different boilerplates like react-starter-kit, our team decided against using the new boilerplates as we just did not have the time to figure out new tools like GraphQL, and risk getting stuck or wasting time getting familiar with new file structures. Also we had to spend an unknown amount of time learning new conventions and best practices of the tools and boilerplate.

We decided to use MeteorJS, React and Redux as Irvin and I had previous experience developing apps with it and our other developer Thanh picked up most of it relatively quickly.

Although we were required to submit a SQL schema and the official supported database was MongoDB, we could also use MySQL with Meteor.

We also chose React over Angular / Blaze for the rendering engine as there were front-end libraries like Material UI which provided us with components to use, helping us save the little time that we have to deliver a working prototype.

Pushing myself to learn more quickly

Being accepted into this module was unexpected for me, as I didn’t consider myself an experienced developer or developer, being year 2 this semester. I was uncertain about taking it at first but even though I know it is hard what I hope will carry me through is my desire and interest to learn how to better design and build web applications. Through this module I hope I will get better at learning itself, in that I can learn to learn better and faster about designing and developing web applications.

Building something good

Despite my inexperience, I hope to become more confident in my own skills, by becoming better at my craft! Although I know the learning doesn’t end with CS3216, I hope to build a web application that I can be proud of by the end of the module. And although we will be marketing it, hopefully the usefulness of the app will show for itself.

Working with others

I believe the people taking CS3216 all bring something unique to the table and I hope we can all learn things from one another. It will be interesting to see how having non-SoC students in the course will affect the development process. Lastly I hope not just to learn from them but also to learn more about my fellow students in NUS, even though those taking the course may be from different years and faculties, I hope we can all make some friends!

You can now customize the background and card colors on loklak walls!

Here’s how we did it:

First, we had to add extra fields to the wall schema:

1
2
3
4
5
6
7
var UserSchema = new Schema({
apps: {
wall: [{
// other options
cardBgColour: String,
cardForeColour: String,
wallBgColour: String,

Next, we had to add these extra options in the angular controller (wall.js) for the creation modal:

1
2
3
4
5
6
7
8
9
10
var initWallOptions = function() {
$scope.newWallOptions.wallBgColour = '#ecf0f5';
$scope.newWallOptions.cardBgColour = '#ffffff';
}

$scope.$watch('newWallOptions.cardBgColour', function() {
if ($scope.newWallOptions.cardBgColour) {
$scope.newWallOptions.cardForeColour = colourCalculator(hexToRgb($scope.newWallOptions.cardBgColour));
}
});

The $watch function watches for any changes in the card background color and changes the cardForeColour / text color to be black or white depending on the bg color.

Now, we have to use the saved data in the wall display pages (display.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div ng-style="{'background-color': wall.wallOptions.wallBgColour}" class="wall-container container-fluid">
<div class="container content-container wall-body">
<div ng-switch on="wall.wallOptions.layoutStyle" ng-show="wall.statuses.length>0" ng-class="wall.wallOptions.showStatistics || wall.currentAnnoucement?'col-md-8':'col-md-12'" masonry>
<!-- 1. Linear -->
<div ng-switch-when="1" linear ng-repeat="status in wall.statuses" open="wall.open" data="status"
cardbgcolor="wall.wallOptions.cardBgColour" cardtxtcolor="wall.wallOptions.cardForeColour"></div>

<!-- 2. Masonry -->
<div ng-switch-when="2" card ng-repeat="status in wall.statuses" open="wall.open" data="status"
cardbgcolor="wall.wallOptions.cardBgColour" cardtxtcolor="wall.wallOptions.cardForeColour"
leaderboardEnabled="{{wall.wallOptions.showStatistics}}"></div>

<!-- 3. Single -->
<div ng-switch-when="3" coa ng-repeat="status in wall.statuses" open="wall.open" data="status"
cardbgcolor="wall.wallOptions.cardBgColour" cardtxtcolor="wall.wallOptions.cardForeColour"
></div>

</div>
</div>
</div>

We pass the saved wall options into each directive using the attributes cardbgcolor, cardtxtcolor, and we use ng-style to evaluate the expression with wallBgColour.

In the linear layout directive file, we use the ‘=’ sign to signal 2-way-binding.

1
2
3
4
5
6
7
8
9
10
function linearLayoutDirective() {
return {
scope: {
data: '=',
cardbgcolor:'=',
cardtxtcolor:'=',
},
templateUrl: 'wall/templates/linear.html',
};
}

Then we can use it in our template (linear.html):

1
2
3
4
<div class="linear linear-simple" style="background-color: {{cardbgcolor}};">
<!-- Main content -->
<p class="linear-content-text" style="color:{{cardtxtcolor}}" ng-class="data.images.length>0?'col-xs-9':'col-xs-12'" ng-bind-html="data.text | tweetTextLink:cardbgcolor | tweetMention:cardbgcolor | tweetHashtag:cardbgcolor | toTrusted"></p>
</div>

I have passed the cardbgcolour into the filter | tweetTextLink:cardbgcolor so we can also change the colours of the links:

1
2
3
4
5
filtersModule.filter('tweetTextLink', function() {
return function(input, cardBgColour) {
var textClassName = cardBgColour ? colourCalculator(hexToRgb(cardBgColour)) : '';
}
}

Have fun customizing your walls at: loklak-wall.herokuapp.com
Screenshot 2016-07-10 09.25.14

This is the final post on the manual moderation feature, where we will cover how to deal with the case of multiple users logged in moderating or viewing the same wall. The problem was that whenever two pages of the wall were open at the same time, there was no way to tell if another page was polling for tweets, so there would be duplicate tweets added to the database, which would then show up on both walls. This had to be solved as it is expected to have multiple displays or moderators for each wall.

To solve this, we needed to be able to store the user-wall id that was currently polling from the server. Not only did an open page have to check if there was another page polling, but also when the page was closed, the store would have to remove the user-wall id, so that when another open page checked it could start polling and store it’s user-wall id.

This connecting and disconnecting behavior can be detected through websocket events, and socket.io has a convenient way for us to listen to these events on the server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var server = express();
var s = http.createServer(server);
var io = require('socket.io')(s);
var pollingWalls = {};
var clientIds = {};

io.on('connection', function (socket) {

// Create and join UserwallId room when user opens page
socket.on('create', function(userWallId) {
socket.join(userWallId);
});

// When close window, use the socket.id to remove from maps so another poll can pass
socket.on('disconnect', function() {
var clientWallPolls = clientIds[socket.id];
clientWallPolls.forEach(function(wallId){
pollingWalls[wallId] = null;
})
delete clientIds[socket.id];
})

// Check duplicate, start if no one else polling
socket.on('checkDup', function(data){
var clients_in_the_room = io.sockets.adapter.rooms[data.userWallId];
var isNoOneElsePolling = pollingWalls[data.userWallId] === socket.id || !pollingWalls[data.userWallId];
if(clients_in_the_room){
var result = clients_in_the_room.length === 1 || isNoOneElsePolling;
var responseEmit = 'checkDupSuccess'+ data.userWallId+ data.socketId;
socket.emit(responseEmit, result);
}
})

// Start polling and mark poller
// Pre-cond: no one else polling / previous poller leaves
socket.on('addPollingWalls', function(userWallId){
pollingWalls[userWallId] = socket.id;
var clientWalls = clientIds[socket.id];
if(clientWalls.indexOf(userWallId) === -1){
clientWalls.push(userWallId);
}
})

I have pasted the server events(above) and client events(below) so that it is easier to view and explain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var init = function() {
// ... other config
socket.emit('create', $stateParams.user + $stateParams.id);
}

// Timeout that checks for multiple users on the same wall
vm.update2 = function(refreshTime) {
return $timeout(function() {
socket.emit('checkDup', {userWallId:userWallId, socketId:socketId});
}, refreshTime);
};

// Event listener that polls if there are no duplicates
socket.on('checkDupSuccess'+userWallId+socketId, function(result){
if(result){
SearchService.initData(searchParams).then(successCb, errorCb);
socket.emit('addPollingWalls', userWallId);
}
})

In server.js, we use two objects - pollingWall‘ to map the walls that are currently polling to the userId, and clientId‘ to map each user to the walls that has the user has opened, so we do not have to traverse the whole object.

We also use socket.ioroom to check if there is no one else polling on the same wall.socket.join allows the connected user to join an existing room or create one if it does not exist. Each room is marked by their user-wall id. In the client code, (in wallDisplay.js the controller for the wall display page), the create‘ event is emitted when a user opens a wall.

The other 2 event listeners on the server are to check if there are duplicate users.

Before each polling interval, in the timeout function below, the checkDup‘ event is emitted, carrying with it the userwallId and the socketId. The event listener on server (as seen above) checks if there are no other users in the room with io.sockets.adapter.rooms[data.userWallId] or nobody polling var isNoOneElsePolling = pollingWalls[data.userWallId] === socket.id || !pollingWalls[data.userWallId];. It then emits an event checkDupSuccess carrying the boolean result to the specific socket.id of the client that requested the check.

If the check passes then the angular SearchService is called, and another event addPollingWall‘ is emitted to store the userWallId and associate it with the client’s socket.id in the pollingWall‘ and clientId‘ object.

Create your own walls at: loklak-wall.herokuapp.com

I have made 2 visualisations in the front-end apps section using D3.js and AngularJS, that anyone can use on loklak.org or locally.

The first type is a bubble chart, these are great for representing single values, such as the number of mentions or the frequency of certain words.

As you can see, when I search for the term loklak, one can see the number of times a user was mentioned and the relative frequency of words.Screenshot 2016-05-30 10.55.26

First, I had to solve the problem of getting the data. This can be done through the data service used for the tweet feed previously. This gives us an updated array of tweet objects in the local storage of the browser.

Next, I had to analyse the tweets. True to its name, D3.js handles a wide variety of data formats, so storing term-frequency pairs in an object is sufficient. Do take a look at the code if you are interested. The processed data is then stored in a separate array in local storage, for eg $storage.mentionFreq instead of $storage.tweets.

Next, I had to display the processed data. Thankfully, angularJS plays nicely with d3.js, so to create a directive in angular, I could use similar D3.js code in the $link function of the directive, as I was manipulating DOM elements with D3.js.

Finally, to make the chart update itself without a page refresh, I added a $watch function on the processed data in the local storage, which runs an update function. Within it, D3.js then uses array joins to render the new data with .data(), so it seamlessly updates itself. Do take a look at the code for the directives for a more in-depth explanation.

The beauty of directives is that they are components which are reusable, the above example is actually using the same directive but with different attributes passed in.

1
2
3
4
<div ng-controller='bubbleCloudCtrl' >
<bubble-cloud flex data="$storage.mentionFreq" min ="1" title="Most Mentions"></bubble-cloud>
<bubble-cloud flex data="$storage.wordFreq" min="3" title="Word Frequency"></bubble-cloud>
</div>

The same pattern can be applied to other D3.js charts, below we can see a stacked bar chart example using the general pattern as described above:

Screenshot 2016-05-30 10.55.19.png

The difference is in analaytics and directive code, which is a bit more involved, as I had to process by date, and then by quantity.

Hopefully this helps future developers to make their own, some ideas I have are making radar and force directed graph.

This is a continuation from the previous post - Loklak walls manual moderation - tweet storage. In this section, I will show the changes I made to enable the user to approve or reject a tweet, and make that change happen across all walls that are opened.

First, I had to examine how loklak.net displays it’s tweets previously. On each wall page, a timeout would be called every interval to retrieve new tweets from loklak_server, these tweets would then be stored on the client’s browser window, in the view model, or $scope in angular.

1
2
3
4
5
6
7
8
vm.update2 = function(refreshTime) {
return $timeout(function() {
SearchService.initData(searchParams).then(function(data) {
...
vm.statuses = data.statuses.splice(0, searchParams.count);
...
}, refreshTime);
};

Having this kind of storage for each browser makes the data inconsistent across the same wall open in different browser windows, as they start querying loklak_server at different times and intervals. This also makes it tough to implement manual moderation previously as the tweets on each open page could not be controlled from the dashboard, since they all lived in their own page.

Now that we have shifted the storage of tweets to mongoDB, we are now able to control how tweets are displayed for all open walls! First, I shifted the calls to loklak_server to the dashboard page instead of the walls page as it made more sense to control the interval from the dashboard rather than the display pages themselves.

Next, I needed a way to sync changes in the database across the dashboard as well as the display pages. At first, I tried to use the same method of http calls, but I soon found them too complicated to sync, having 3 components with interconnected actions. Actions from the dashboard and new entries from the database would have to affect the display, and new entries from the database would have to affect the dashboard and display. Also having an interval for updating the wall after changes were made to the database made it seem very unresponsive and resulted in a bad user experience.

The solution to this was: WebSockets! This allows us to listen for new events like addition of new tweets. When first initialized, the display pages and the dashboard just had to load the existing tweets in the database, when new tweets are loaded, they’ll be added into the database AND the displays and dashboard, making it update in real time.

websocket-small

I chose socket.io as it made integrating WebSockets into the MEAN stack relatively easy. After the http request for new tweets from loklak server is returned, the app then sends a POST request to the node server, which then emits an event to update the display and the dashboard. Below is the route controller, which posts the tweet array received from loklak server.

1
2
3
4
5
6
7
8
9
10
11
module.exports.storeTweet = function (req, res) {
req.body.tweetArr.forEach(function(tweet){
var newTweet = new Tweet(tweet);
newTweet.save(function(err,tweet){
// EMIT DASHBOARD EVENT
io.emit("addNewTweet", tweet);
// EMIT WALL DISPLAY EVENT
io.emit("addNewTweet"+req.body.userWallId, tweet);
}
})
});

On the wall display page controller, it listens for the emitted event and adds the data to the display.

1
2
3
 socket.on('addNewTweets' + $stateParams.user + $stateParams.id, function(tweet){
vm.statuses.splice(0,0, tweet);
})

The toggle events are similar in that instead of POST requests, now we are sending PUT requests from the dashboard to update the tweet in mongoDB, and then changing the data attribute on the wall display. Using AngularJS’s ng-hide we can show/hide the tweet depending on it’s approval field.

Inside the angular directive on the dashboard we attach a toggle function to the click:

1
2
3
4
$scope.toggle = function(){
$scope.data.approval = !$scope.data.approval;
$http.put('/api/tweets/'+$scope.data._id, $scope.data);
}
1
<div ng-show="data.approval" ng-attr-id="{{data.id_str}}" class="linear linear-simple" />;

Similarly on the server we emit an event:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports.updateTweet = function (req, res) {
Tweet
.findById(req.params.tweetId)
.exec(function(err, tweet) {
tweet.approval = !tweet.approval;
tweet.save(function(err) {
res.json({tweet: tweet});
});
});

// EMIT TOGGLE EVENT
io.emit("toggle", req.params.tweetId);
}
}

And on the wallDisplay controller we can listen to that toggle event:

1
2
3
4
5
6
socket.on('toggle',function(tweetId){
var tweetIdx = vm.statuses.findIndex(function(tweet){
return tweet._id === tweetId;
});
vm.statuses[tweetIdx].approval = !vm.statuses[tweetIdx].approval;
});

The end result is manual moderation from the dashboard!

icLfu4KZE9

Signing in locally is great but what we want is to make sure the right person has access to the right routes. This was done for the loklak user walls, as we have to check if the user is signed in and is authorized to see the current walls, and edit them.

Server side - protecting routes

To verify the identity of the logged in user we have to check for the JWT. To setup and use the route authentication we use express-jwt as middleware, which checks if the JWT is present before going to the route.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var jwt = require('express-jwt');
var config = require('../../custom_configFile.json');

var auth = jwt({
secret: config.jwtsecret,
userProperty: 'payload'
});

var ctrlAuth = require('../controllers/authentication');
var ctrlMailer = require('../controllers/email');
var ctrlWalls = require('../controllers/walls');

// WALL API
router.get ('/:user/:app/:id', ctrlWalls.getWallById);
router.get ('/:user/:app', auth, ctrlWalls.getUserWalls);
router.post ('/:user/:app', auth, ctrlWalls.createWall);
router.put ('/:user/:app/:id', auth, ctrlWalls.updateWall);
router.delete('/:user/:app/:id', auth, ctrlWalls.deleteWall);

Notice that we do not use authentication for the wall route as we want anyone to be able to access the wall.

The route controllers then handle manipulating the mongoose user models. We check for the JWT payload in the getUserWalls controller but not the getWallById controller.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module.exports.getWallById = function (req, res) {
User
.findById(req.params.user)
.exec(function(err, user) {
if (user.apps[req.params.app]) {
for (i = 0; i < user.apps[req.params.app].length; i = i + 1) {
if (user.apps[req.params.app][i].id === req.params.id) {
return res.jsonp(user.apps[req.params.app][i]);
}
}
res.jsonp({});
} else {
res.jsonp({});
}
});
}

module.exports.getUserWalls = function (req, res) {
// If no user ID exists in the JWT return a 401
if (!req.payload._id) {
res.status(401).json({
message : UnauthorizedError: private wall page
});
} else {
User
.findById(req.params.user)
.exec(function(err, user) {
if (user.apps && user.apps[req.params.app]) {
res.jsonp(user.apps[req.params.app]);
} else {
res.jsonp([]);
}
});
}
}

One error I got stuck on was the Mongoose: TypeError: doc.validate is not a function, in the update and delete methods. Mutating the array with splice instead of assigning it a new object solves this.

1
2
3
4
5
// x
// appData[req.params.app][i] = req.body;

// √
appData[req.params.app].splice(i, 1, req.body);

Model Schema

I have chosen to embed the wall options into the User model schema itself instead of creating another collection, as it is one user-few walls, so there would be lesser queries to the API as there’s mostly reading wall options, and relatively less updating of wall options.

1
2
3
4
5
6
7
8
9
10
11
12
13
var UserSchema = new Schema({
email: { type: String, unique: true, required: true },
name: { type: String, required: true },
hash: String,
salt: String,
isVerified: { type: Boolean, required: true },
apps: {
wall: [{
profanity: Boolean,
...
}]
}
});

Client side - consuming the API.

We use $resource, a factory built on $http, to interact with the routes, through an angular service.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function AppsService($q, $http, $resource, AppSettings, AuthService) {
return $resource('/api/:user/:app/:id', {
user: '@user',
app: '@app',
id: '@id'
}, {
query: {
method: 'GET',
isArray: true
},
save: {
method: 'POST',
transformRequest: function(data) {
delete data.user;
delete data.app;
delete data.showLoading;
return JSON.stringify(data);
},
params: {
user: '@user',
app: '@app',
id: '@id'
}
}, ...

Then in our controller, WallCtrl, we use this service, for eg:

1
2
3
4
5
6
7
8
9
10
11
12
var init = function() {
if ($scope.isLoggedIn) {
$scope.userWalls = AppsService.query({
user: $scope.currentUser._id,
app: 'wall'
}, function(result) {
if ($scope.userWalls.length === 0) {
$scope.wallsPresent = false;
}
});
}
};

To attach the JWT in the header we will get the JWT from local storage and attach it using an httpInterceptor through the client side routes which uses UI-router. If you do not do this you will get the UnauthorizedError, as no token is found.

1
2
3
4
5
6
7
8
9
function tokenInjectorService($window) {
var tokenInjector = {
request: function(config) {
config.headers['Authorization'] = 'Bearer '+ $window.localStorage['jwt-token'];
return config;
}
};
return tokenInjector;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
function Routes($stateProvider, $locationProvider, $httpProvider) {
$locationProvider.html5Mode(true);

$stateProvider
.state('Home', {
url: '/',
controller: 'MapCtrl as map',
templateUrl: 'home.html',
title: 'Home'
}) // and other routes

$httpProvider.interceptors.push('tokenInjector');
}

Hope that helps anyone who has trouble with their MEAN app and JWT auth.