Skip to content
Snippets Groups Projects
Commit d4d8440f authored by Dave McCormick's avatar Dave McCormick
Browse files

adding mongo db logic, needs tidy up, also added bootstrap template

parent 5f5f1b12
No related branches found
No related tags found
No related merge requests found
Showing
with 1456 additions and 59 deletions
Architecture.png

206 KiB

package actors;
import com.fasterxml.jackson.databind.node.ObjectNode;
import model.TweetHub;
import play.libs.Json;
import twitter4j.*;
import akka.actor.Props;
import akka.actor.UntypedActor;
import twitter4j.conf.ConfigurationBuilder;
/*
Consumer Key (API Key) MkpBoVPy3REAIdsPNSZpC7u0I
Consumer Secret (API Secret) 3cNybn6qSJPpw2AnizXGitOclFV3ZolFXidWBWLpOnafRw7uL8
Access Token 315679785-SVT1kCKoow1eCTgTWLzPrh6BPaKA5OiPOfIlmAxt
Access Token Secret TYTg0PLDbU7isjXxm3uPIeu2xdYg3rXGaEGUdJuBkvCg2
*/
/**
* A rather trivial Actor that just plays FizzBuzz
*/
public class TwitterStreamActor extends UntypedActor {
int nextNum = 0;
/**
*
*/
public static Props props = Props.create(TwitterStreamActor.class);
TwitterStream twitterStream;
StatusListener listener;
public TwitterStreamActor() {
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true)
.setOAuthConsumerKey("MkpBoVPy3REAIdsPNSZpC7u0I")
.setOAuthConsumerSecret("3cNybn6qSJPpw2AnizXGitOclFV3ZolFXidWBWLpOnafRw7uL8")
.setOAuthAccessToken("315679785-SVT1kCKoow1eCTgTWLzPrh6BPaKA5OiPOfIlmAxt")
.setOAuthAccessTokenSecret("TYTg0PLDbU7isjXxm3uPIeu2xdYg3rXGaEGUdJuBkvCg2");
twitterStream = new TwitterStreamFactory(cb.build()).getInstance();
listener = new StatusListener() {
@Override
public void onStatus(Status status) {
// Work around for bug in filter.
GeoLocation loc = status.getGeoLocation();
if (loc == null){
return;
}
// Build a json object to send over the wire to the client.
ObjectNode n = Json.newObject();
//n.put("msg", "@" + status.getUser().getScreenName() + "said " + status.getText() + " - " + loc);
n.put("lat", loc.getLatitude());
n.put("lng", loc.getLongitude());
TweetHub.getInstance().send(n);
}
@Override
public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) {
System.out.println("Got a status deletion notice id:" + statusDeletionNotice.getStatusId());
}
@Override
public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
System.out.println("Got track limitation notice:" + numberOfLimitedStatuses);
}
@Override
public void onScrubGeo(long userId, long upToStatusId) {
System.out.println("Got scrub_geo event userId:" + userId + " upToStatusId:" + upToStatusId);
}
@Override
public void onStallWarning(StallWarning warning) {
System.out.println("Got stall warning:" + warning);
}
@Override
public void onException(Exception ex) {
ex.printStackTrace();
}
};
}
@Override
public void onReceive(Object message) throws TwitterException {
if (message.equals("ConnectToStream")){
// Effectively the wake up call that starts the stream up.
ConnectToTwitterStreamWithFilter();
}
}
void ConnectToTwitterStreamWithFilter(){
FilterQuery fq = new FilterQuery();
double[][] boundingBox= {{-180, -90}, {180, 90}}; // whole world;
fq.locations(boundingBox);
twitterStream.addListener(listener);
twitterStream.filter(fq);
}
}
......@@ -3,6 +3,7 @@ package controllers;
import actors.Setup;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import model.UserService;
import play.mvc.Controller;
import play.mvc.LegacyWebSocket;
import play.mvc.Result;
......@@ -12,6 +13,8 @@ import play.libs.ws.*;
import java.util.concurrent.CompletionStage;
import static akka.pattern.Patterns.ask;
import static controllers.UserController.getSessionId;
import static controllers.UserController.getUserService;
@Singleton
public class Application extends Controller {
......@@ -20,12 +23,34 @@ public class Application extends Controller {
WSClient wsClient;
protected static UserService getUserService() {
return UserService.instance;
}
protected static final String SESSIONVAR = "mySession";
/**
* Returns our generated session ID for this user, creating one if necessary
*/
protected static String getSessionId() {
String id = session(SESSIONVAR);
if (!getUserService().isValidId(id)) {
id = getUserService().allocateId();
session(SESSIONVAR, id);
}
return id;
}
@Inject
public Application(Setup actorSetup, WSClient wsClient) {
this.actorSetup = actorSetup;
this.wsClient = wsClient;
}
public Result index() {
return ok(views.html.application.index.render(getUserService().getUserFromSession(getSessionId())));
}
/**
* Our WebSockets endpoint. We'll assume we're sending String messages for now
*/
......
package controllers;
/**
* Created by davidmccormick on 21/08/16.
*/
import akka.actor.*;
import model.TweetHub;
import model.TweetListener;
public class TweetWebSocketActor extends UntypedActor {
/**
* We don't create the actor ourselves. Instead, Play will ask Akka to make it for us. We have to give Akka a
* "Props" object that tells Akka what kind of actor to create, and what constructor arguments to pass to it.
* This method produces that Props object.
*/
public static Props props(String topic, ActorRef out) {
// Create a Props object that says:
// - I want a TweetWebSocketActor,
// - and pass (topic, out) as the arguments to its constructor
return Props.create(TweetWebSocketActor.class, topic, out);
}
/** The Actor for the client (browser) */
private final ActorRef out;
/** The topic string we have subscribed to */
private final String topic;
/** A listener that we will register with our TweetHub */
private final TweetListener listener;
/**
* This constructor is called by Akka to create our actor (we don't call it ourselves).
*/
public TweetWebSocketActor(String topic, ActorRef out) {
this.topic = topic;
this.out = out;
/*
Our TweetListener, written as a Java 8 Lambda.
Whenever we receive a tweet, if it matches our topic, convert it to a JSON string, and send it to the client.
*/
this.listener = (tweetJson) -> {
String message = tweetJson.toString();
out.tell(message, self());
};
// Register this actor to hear tweets
TweetHub.getInstance().addListener(listener);
}
/**
* This is called whenever the browser sends a message to the serverover the websocket
*/
public void onReceive(Object message) throws Exception {
// The client isn't going to send us messages down the websocket in this example, so this doesn't matter
if (message instanceof String) {
out.tell("I received your message: " + message, self());
}
}
/**
* This is called by Play after the WebSocket has closed
*/
public void postStop() throws Exception {
// De-register our listener
TweetHub.getInstance().removeListener(this.listener);
}
}
package controllers;
import model.Session;
import model.User;
import model.UserService;
import org.mindrot.jbcrypt.BCrypt;
import play.mvc.Result;
import play.mvc.Controller;
public class UserController extends Controller {
protected static UserService getUserService() {
return UserService.instance;
}
protected static final String SESSIONVAR = "mySession";
/**
* Returns our generated session ID for this user, creating one if necessary
*/
protected static String getSessionId() {
String id = session(SESSIONVAR);
if (!getUserService().isValidId(id)) {
id = getUserService().allocateId();
session(SESSIONVAR, id);
}
return id;
}
public Result loginView() {
return ok(views.html.application.login.render(null));
}
public Result registerView() {
return ok(views.html.application.register.render(null));
}
public Result sessionsView() {
return ok(views.html.application.sessions.render(getUserService().getUserFromSession(getSessionId())));
}
public Result doLogin() {
String sessionId = getSessionId();
String email;
String password;
// We're doing this very basically, as Play forms are not in scope for the course
// (The unit prefers to teach things that are a little closer to the HTTP, rather than convenience wrappers)
try {
email = request().body().asFormUrlEncoded().get("email")[0];
password = request().body().asFormUrlEncoded().get("password")[0];
} catch (Exception e) {
return badRequest(views.html.application.login.render("Email and password could not be found in the request"));
}
if (getUserService().getUserFromSession(sessionId)!= null) {
return badRequest(views.html.application.login.render("You're already logged in!"));
}
User u = getUserService().getUser(email, password);
if (u != null) {
u.pushSession(new Session(sessionId, request().remoteAddress(), System.currentTimeMillis()));
getUserService().update(u);
return redirect("/");
} else {
return forbidden(views.html.application.login.render("Wrong email address or password"));
}
}
public Result doRegister() {
String sessionId = getSessionId();
String email;
String password;
// We're doing this very basically, as Play forms are not in scope for the course
// (The unit prefers to teach things that are a little closer to the HTTP, rather than convenience wrappers)
try {
email = request().body().asFormUrlEncoded().get("email")[0];
password = request().body().asFormUrlEncoded().get("password")[0];
} catch (Exception e) {
return badRequest(views.html.application.login.render("Email and password could not be found in the request"));
}
if (getUserService().getUserFromSession(sessionId)!= null) {
return badRequest(views.html.application.login.render("You're already logged in!"));
}
// Create a new user object
User u = new User(getUserService().allocateId(), email, BCrypt.hashpw(password, BCrypt.gensalt()));
// Try to register them
try {
getUserService().registerUser(u);
} catch (Exception ex) {
return badRequest(views.html.application.register.render(ex.getMessage()));
}
// Log them in
u.pushSession(new Session(sessionId, request().remoteAddress(), System.currentTimeMillis()));
return redirect("/");
}
public Result doLogout() {
String sessionId = getSessionId();
User u = getUserService().getUserFromSession(sessionId);
if (u != null) {
u.removeSession(sessionId);
}
return ok(views.html.application.login.render(null));
}
public Result doRemoteLogout() {
String sessionId = getSessionId();
String toRemove;
try {
toRemove = request().body().asFormUrlEncoded().get("remove")[0];
} catch (Exception e) {
return badRequest(views.html.application.login.render("Session to remove could not be found in the request"));
}
User u = getUserService().getUserFromSession(sessionId);
if (u != null) {
u.removeSession(toRemove);
}
getUserService().update(u);
return redirect("/");
}
}
\ No newline at end of file
package model;
public class Session {
String id;
String ipAddress;
long since;
public Session(String id, String ipAddress, long since) {
this.id = id;
this.ipAddress = ipAddress;
this.since = since;
}
public String getId() {
return id;
}
public String getIpAddress() {
return ipAddress;
}
public long getSince() {
return since;
}
}
package model;
/**
* Created by davidmccormick on 21/08/16.
*/
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* This is a simple publish-subscribe list. It maintains a list of listeners, and whenever it receives a call to
* <code>send</code>, it calls <code>receiveTweet</code> on every registered listener.
*/
public class TweetHub {
List<TweetListener> listeners;
static final TweetHub instance = new TweetHub();
public static TweetHub getInstance() {
return instance;
}
protected TweetHub() {
this.listeners = Collections.synchronizedList(new ArrayList<>());
}
public void send(ObjectNode g) {
for (TweetListener listener : listeners) {
listener.receiveTweet(g);
}
}
public void addListener(TweetListener l) {
this.listeners.add(l);
}
public void removeListener(TweetListener l) {
this.listeners.remove(l);
}
}
\ No newline at end of file
package model;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Created by davidmccormick on 21/08/16.
*/
public interface TweetListener {
void receiveTweet(ObjectNode g);
}
\ No newline at end of file
package model;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
public class User {
String id;
String email;
String hash;
/**
* We need to hold the user's active sessions; this stands in for our database table
*/
ConcurrentHashMap<String, Session> activeSessions = new ConcurrentHashMap<>();
public User(String id, String email, String hash) {
this.id = id;
this.email = email;
this.hash = hash;
}
public String getId() {
return id;
}
public String getEmail() {
return email;
}
public String getHash() {
return hash;
}
/**
* Is a particular session active on this user?
*/
public boolean hasSession(String sessionId) {
return activeSessions.containsKey(sessionId);
}
public Session[] getSessions() {
Collection<Session> values = activeSessions.values();
return values.toArray(new Session[values.size()]);
}
/**
* Record that a particular session is logged in as this user
*/
public void pushSession(Session s) {
activeSessions.put(s.id, s);
}
/**
* Remove an active session from this user
*/
public void removeSession(String sessionId) {
activeSessions.remove(sessionId);
}
}
\ No newline at end of file
package model;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.BsonDocument;
import org.bson.BsonWriter;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mindrot.jbcrypt.BCrypt;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public class UserService {
public static final UserService instance = new UserService();
protected MongoClient mongoClient;
protected UserService() {
mongoClient = new MongoClient("127.0.0.1", 27017);
}
protected MongoDatabase getDB() {
// TODO: Change your database name, to avoid clashing with others on turing
return mongoClient.getDatabase("comp560_dmccormi");
}
protected MongoCollection<Document> getChitterCollection() {
return getDB().getCollection("chitterUser");
}
/**
* Allocates an ObjectID and returns it as a hex string; I've exposed this so we can use it also for session IDs.
*/
public String allocateId() {
return new ObjectId().toHexString();
}
/**
* Checks if this is a valid ObjectID, as some browsers might have old UUIDs cached
*/
public boolean isValidId(String id) {
try {
ObjectId i = new ObjectId(id);
return i.toHexString().equals(id);
} catch (Exception ex) {
return false;
}
}
public User registerUser(User u) {
// Let's first check the user isn't already registered
if (getChitterCollection().find(new Document("email", u.getEmail())).first() != null) {
throw new IllegalArgumentException("That email address has already been registered");
}
insert(u);
return u;
}
public User getUser(String id) {
Document d = getChitterCollection().find(new Document("_id", new ObjectId(id))).first();
if (d != null) {
return userFromBson(d);
} else {
return null;
}
}
/**
* Get the user by email and password, returning null if they don't exist (or the password is wrong)
*/
public User getUser(String email, String password) {
Document d = getChitterCollection().find(new Document("email", email)).first();
// I wrote userFromBson to accept nulls
User u = userFromBson(d);
if (u != null && BCrypt.checkpw(password, u.getHash())) {
return u;
} else {
return null;
}
}
/**
* Get the user who is logged in with this session, if there is one
*/
public User getUserFromSession(String sessionId) {
Document d = getChitterCollection().find(new Document("sessions._id", new ObjectId(sessionId))).first();
return userFromBson(d);
}
protected static Document userToBson(User u) {
List<Document> sessions = new ArrayList<>();
for (Session s : u.getSessions()) {
sessions.add(sessionToBson(s));
}
return new Document("_id", new ObjectId(u.getId()))
.append("email", u.email)
.append("hash", u.getHash())
.append("sessions", sessions);
}
protected static User userFromBson(Document d) {
// This lets us call this method even if d is null
if (d == null) {
return null;
}
String id = d.getObjectId("_id").toHexString();
String email = d.getString("email");
String hash = d.getString("hash");
User u = new User(id, email, hash);
// This gives an unchecked warning; we'd need to use the safer means of doing this (which we don't cover)
// to avoid the warning
List<Document> sessions = d.get("sessions", List.class);
for (Document sd : sessions) {
Session s = sessionFromBson(sd);
u.pushSession(s);
}
return u;
}
protected static Session sessionFromBson(Document d) {
// This lets us call this method even if d is null
if (d == null) {
return null;
}
String id = d.getObjectId("_id").toHexString();
String ip = d.getString("ipAddress");
long since = d.getLong("since");
return new Session(id, ip, since);
}
protected static Document sessionToBson(Session s) {
return new Document("_id", new ObjectId(s.getId()))
.append("ipAddress", s.getIpAddress())
.append("since", s.getSince());
}
protected void insert(User u) {
getChitterCollection().insertOne(userToBson(u));
}
public void update(User u) {
getChitterCollection().replaceOne(new Document("_id", new ObjectId(u.getId())), userToBson(u));
}
}
\ No newline at end of file
@(u:model.User)
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" />
<!--<script src="https://maps.google.com/maps/api/js?libraries=placeses,visualization,drawing,geometry,places"></script>-->
<!--<script src="https://code.angularjs.org/1.3.15/angular.js"></script>-->
<!--<script src="/assets/javascript/ng-map.js"></script>-->
<!--<script src="/assets/javascript/taxi-data.js"></script>-->
<script src="//maps.googleapis.com/maps/api/js?sensor=false&libraries=placeses,visualization,drawing,geometry,places"></script>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="/assets/html/favicon.ico">
<title>Hawkeye</title>
<!-- Bootstrap core CSS -->
<link href="/assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="/assets/bootstrap/css/cover.css" rel="stylesheet">
<!--<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" />-->
<script src="//maps.googleapis.com/maps/api/js?sensor=false&libraries=placeses,visualization,drawing,geometry,places"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script>
<script src="//rawgit.com/angular-ui/angular-google-maps/2.1.5/dist/angular-google-maps.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<script src="/assets/javascript/cbuffer.js"></script>
<script src="/assets/javascript/dynamicHeatMap.js"></script>
</head>
<script>
<body>
angular.module("myApp", ['uiGmapgoogle-maps'])
.controller("Example", function($scope, $interval) {
<style>
.angular-google-map-container { height: 700px; }
</style>
$scope.tweetRingBuffer = CBuffer(1000);
@if(u != null) {
<div ng-app="myApp" ng-controller="Example">
<ui-gmap-google-map center='map.center' zoom='map.zoom' options="map.options">
<ui-gmap-layer namespace="visualization" type="HeatmapLayer" show="map.showHeat" onCreated="map.heatLayerCallback"></ui-gmap-layer>
</ui-gmap-google-map>
@for(session <- u.getSessions()) {
<form action="remoteLogout" method="POST">
<input type="hidden" value="@{session.getId()}" name="remove" />
<button type="submit">Log out remotely</button>
</form>
}
</div>
function MockHeatLayer(heatLayer) {
$scope.heatLayer = heatLayer;
$scope.pointArray = new google.maps.MVCArray($scope.tweetRingBuffer.toArray());
$scope.heatLayer.setData($scope.pointArray);
};
} else {
$scope.map = {
center: {
latitude: 37.782551,
longitude: -122.445368
},
zoom: 12,
heatLayerCallback: function (layer) {
//set the heat layers backend data
var mockHeatLayer = new MockHeatLayer(layer);
},
showHeat: true
};
<div class="site-wrapper">
var ws = new WebSocket("ws://127.0.0.1:9000/websocket?topic=Algernons");
<div class="site-wrapper-inner">
// binding model for the UI
$scope.messages = [];
<div class="cover-container">
// what to do when we receive message from the webserver
ws.onmessage = function(msg) {
<div class="masthead clearfix">
<div class="inner">
<h3 class="masthead-brand">Hawkeye</h3>
<nav>
<ul class="nav masthead-nav">
<li class="active"><a href="/">Home</a></li>
<li><a href="/login">Login</a></li>
<li><a href="/register">Register</a></li>
</ul>
</nav>
</div>
</div>
var obj = JSON.parse(msg.data);
$scope.$digest();
$scope.tweetRingBuffer.push(new google.maps.LatLng(obj.lat, obj.lng));
$scope.pointArray = new google.maps.MVCArray($scope.tweetRingBuffer.toArray());
$scope.heatLayer.setData($scope.pointArray);
};
<div class="inner cover">
<h1 class="cover-heading">See whats going on around you.</h1>
<p class="lead">Hawkeye is a single-page app that displays all tweets globally as a realtime heatmap.</p>
</div>
});
</div>
</script>
</div>
</head>
<body>
<style>
.angular-google-map-container { height: 800px; }
</div>
</style>
<div ng-app="myApp" ng-controller="Example">
<ui-gmap-google-map center='map.center' zoom='map.zoom'>
<ui-gmap-layer namespace="visualization" type="HeatmapLayer" show="map.showHeat" onCreated="map.heatLayerCallback"></ui-gmap-layer>
</ui-gmap-google-map>
}
<!--<div>-->
<!--<ul>-->
<!--<li ng-repeat="msg in messages track by $index">-->
<!--{{msg}}-->
<!--</li>-->
<!--</ul>-->
<!--</div>-->
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
\ No newline at end of file
@(error:String)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="/assets/html/favicon.ico">
<!-- Bootstrap core CSS -->
<link href="/assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="/assets/bootstrap/css/cover.css" rel="stylesheet">
</head>
<body>
<h2>Log in</h2>
<form method="post" action="login" >
<input type="text" placeholder="Email address" name="email" /> <br/>
<input type="password" placeholder="Password" name="password" /> <br/>
<button type="submit" >Log in</button>
@if(error != null) {
<p>@error</p>
}
</form>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../assets/js/ie10-viewport-bug-workaround.js"></script>
</body>
</html>
\ No newline at end of file
package views
/**
* Created by davidmccormick on 16/08/16.
*/
package object application {
}
@(error:String)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Register</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="/assets/html/favicon.ico">
<!-- Bootstrap core CSS -->
<link href="/assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="/assets/bootstrap/css/cover.css" rel="stylesheet">
</head>
<body>
<h2>Register</h2>
<form method="post" action="register" >
<input type="text" placeholder="Email address" name="email" /> <br/>
<input type="password" placeholder="Password" name="password" /> <br/>
<button type="submit" >Register</button>
@if(error != null) {
<p>@error</p>
}
</form>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="../../dist/js/bootstrap.min.js"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="../../assets/js/ie10-viewport-bug-workaround.js"></script>
</body>
</html>
\ No newline at end of file
@(u:model.User)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h2>Sessions</h2>
@if(u != null) {
<p>
You are logged in as @{u.getEmail()}
</p>
<p>Your sessions:</p>
<ul>
@for(session <- u.getSessions()) {
<li>
<label>IP address: </label> @{session.getIpAddress()}
<label>Since: </label> @{session.getSince()}
<form action="remoteLogout" method="POST">
<input type="hidden" value="@{session.getId()}" name="remove" />
<button type="submit">Log out remotely</button>
</form>
</li>
}
</ul>
} else {
<p>You are not logged in</p>
}
</body>
</html>
\ No newline at end of file
......@@ -11,6 +11,7 @@ routesGenerator := InjectedRoutesGenerator
libraryDependencies ++= Seq(
javaWs,
"org.mindrot" % "jbcrypt" % "0.3m",
"org.mongodb" % "mongodb-driver" % "3.0.2",
"org.twitter4j" % "twitter4j-core" % "4.0.4",
"org.twitter4j" % "twitter4j-stream" % "4.0.4"
)
......
......@@ -2,8 +2,17 @@
# This file defines all application routes (Higher priority routes first)
# ~~~~
GET / controllers.Assets.at(path="/public/html", file="index.html")
#GET / controllers.Assets.at(path="/public/html", file="index.html")
GET / controllers.Application.index()
GET /websocket controllers.Application.socket(topic:String)
# Added for our user registration
GET /login controllers.UserController.loginView()
GET /sessions controllers.UserController.sessionsView()
GET /register controllers.UserController.registerView()
POST /login controllers.UserController.doLogin()
POST /register controllers.UserController.doRegister()
POST /remoteLogout controllers.UserController.doRemoteLogout()
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
\ No newline at end of file
File added
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment