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
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;