diff --git a/.gitignore b/.gitignore
index 9c07d4a..9f29af7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
*.class
*.log
+.idea/
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e138150
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,47 @@
+FROM ubuntu:18.04
+
+RUN apt-get update
+
+RUN apt-get install -y wget
+
+#Java Setup
+RUN apt-get update --fix-missing
+RUN apt-get install -y default-jdk
+
+#Scala setup
+RUN apt-get remove scala-library scala
+RUN wget http://scala-lang.org/files/archive/scala-2.12.6.deb
+RUN dpkg -i scala-2.12.6.deb
+RUN apt-get update
+RUN apt-get install -y scala
+
+RUN apt-get install -y gnupg2
+RUN echo "deb https://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list
+RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
+RUN apt-get update
+RUN apt-get install -y sbt
+
+#Maven Setup
+RUN apt-get install -y maven
+
+
+# Set the home directory to /root and cd into that directory
+ENV HOME /root
+WORKDIR /root
+
+
+# Copy all app files into the image
+COPY . .
+
+# Download dependancies and build the app
+RUN mvn package
+
+
+# Allow port 8080 to be accessed from outside the container
+EXPOSE 8080
+
+ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait
+RUN chmod +x /wait
+
+# Run the app
+CMD /wait && java -jar target/office-hours-0.0.1-jar-with-dependencies.jar
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e816900
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,19 @@
+version: '3.3'
+services:
+ nginx:
+ build: ./nginx
+ ports:
+ - '9005:80'
+ mysql:
+ image: mysql
+ environment:
+ MYSQL_ROOT_PASSWORD: 'changeme'
+ MYSQL_DATABASE: 'officehours'
+ MYSQL_USER: 'sqluser'
+ MYSQL_PASSWORD: 'changeme'
+ app:
+ build: .
+ environment:
+ WAIT_HOSTS: mysql:3306
+ DB_USERNAME: 'sqluser'
+ DB_PASSWORD: 'changeme'
\ No newline at end of file
diff --git a/nginx/Dockerfile b/nginx/Dockerfile
new file mode 100644
index 0000000..184f4a0
--- /dev/null
+++ b/nginx/Dockerfile
@@ -0,0 +1,6 @@
+FROM nginx
+
+COPY ./public /usr/share/nginx/html
+COPY ./default.conf /etc/nginx/conf.d
+
+EXPOSE 80
\ No newline at end of file
diff --git a/nginx/default.conf b/nginx/default.conf
new file mode 100644
index 0000000..a449256
--- /dev/null
+++ b/nginx/default.conf
@@ -0,0 +1,17 @@
+server {
+ listen 80;
+ server_name localhost;
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ }
+
+ location /socket.io {
+ proxy_pass http://app:8080;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "Upgrade";
+ }
+
+}
diff --git a/nginx/public/index.html b/nginx/public/index.html
new file mode 100644
index 0000000..5443216
--- /dev/null
+++ b/nginx/public/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+ Office Hours
+
+
+
+
+
+
+Welcome!
+
+Name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nginx/public/officeHours.js b/nginx/public/officeHours.js
new file mode 100644
index 0000000..f0c0c95
--- /dev/null
+++ b/nginx/public/officeHours.js
@@ -0,0 +1,28 @@
+const socket = io.connect({transports: ['websocket']});
+
+socket.on('queue', displayQueue);
+socket.on('message', displayMessage);
+
+function displayMessage(newMessage) {
+ document.getElementById("message").innerHTML = newMessage;
+}
+
+function displayQueue(queueJSON) {
+ const queue = JSON.parse(queueJSON);
+ let formattedQueue = "";
+ for (const student of queue) {
+ formattedQueue += student['username'] + " has been waiting since " + student['timestamp'] + "
"
+ }
+ document.getElementById("queue").innerHTML = formattedQueue;
+}
+
+
+function enterQueue() {
+ let name = document.getElementById("name").value;
+ socket.emit("enter_queue", name);
+ document.getElementById("name").value = "";
+}
+
+function readyToHelp() {
+ socket.emit("ready_for_student");
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..62790e0
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,88 @@
+
+ edu.buffalo.cse
+ office-hours
+ 4.0.0
+ http://maven.apache.org
+ 0.0.1
+
+
+ UTF-8
+ 2.12.9
+
+
+
+
+
+
+ com.typesafe.play
+ play-json_2.12
+ 2.7.1
+
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.15
+
+
+
+ com.corundumstudio.socketio
+ netty-socketio
+ 1.7.12
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.28
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.4
+
+
+ jar-with-dependencies
+
+
+
+ model.OfficeHoursServer
+
+
+
+
+
+ package
+
+ single
+
+
+
+
+
+
+ org.scala-tools
+ maven-scala-plugin
+ 2.15.2
+
+
+
+ compile
+ testCompile
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/scala/model/Database.scala b/src/main/scala/model/Database.scala
new file mode 100644
index 0000000..c3c5c78
--- /dev/null
+++ b/src/main/scala/model/Database.scala
@@ -0,0 +1,55 @@
+package model
+
+import java.sql.{Connection, DriverManager, ResultSet}
+
+object Database {
+
+ val url = "jdbc:mysql://mysql/officehours"
+ val username: String = sys.env("DB_USERNAME")
+ val password: String = sys.env("DB_PASSWORD")
+
+ var connection: Connection = DriverManager.getConnection(url, username, password)
+ setupTable()
+
+
+ def setupTable(): Unit = {
+ val statement = connection.createStatement()
+ statement.execute("CREATE TABLE IF NOT EXISTS queue (username TEXT, timestamp BIGINT)")
+ }
+
+
+ def addStudentToQueue(student: StudentInQueue): Unit = {
+ val statement = connection.prepareStatement("INSERT INTO queue VALUE (?, ?)")
+
+ statement.setString(1, student.username)
+ statement.setLong(2, student.timestamp)
+
+ statement.execute()
+ }
+
+
+ def removeStudentFromQueue(username: String): Unit = {
+ val statement = connection.prepareStatement("DELETE FROM queue WHERE username=?")
+
+ statement.setString(1, username)
+
+ statement.execute()
+ }
+
+
+ def getQueue(): List[StudentInQueue] = {
+ val statement = connection.prepareStatement("SELECT * FROM queue")
+ val result: ResultSet = statement.executeQuery()
+
+ var queue: List[StudentInQueue] = List()
+
+ while (result.next()) {
+ val username = result.getString("username")
+ val timestamp = result.getLong("timestamp")
+ queue = new StudentInQueue(username, timestamp) :: queue
+ }
+
+ queue
+ }
+
+}
diff --git a/src/main/scala/model/OfficeHoursServer.scala b/src/main/scala/model/OfficeHoursServer.scala
new file mode 100644
index 0000000..8ea6167
--- /dev/null
+++ b/src/main/scala/model/OfficeHoursServer.scala
@@ -0,0 +1,79 @@
+package model
+
+import com.corundumstudio.socketio.listener.{DataListener, DisconnectListener}
+import com.corundumstudio.socketio.{AckRequest, Configuration, SocketIOClient, SocketIOServer}
+import play.api.libs.json.{JsValue, Json}
+
+
+class OfficeHoursServer() {
+
+ var usernameToSocket: Map[String, SocketIOClient] = Map()
+ var socketToUsername: Map[SocketIOClient, String] = Map()
+
+ val config: Configuration = new Configuration {
+ setHostname("0.0.0.0")
+ setPort(8080)
+ }
+
+ val server: SocketIOServer = new SocketIOServer(config)
+
+ server.addDisconnectListener(new DisconnectionListener(this))
+ server.addEventListener("enter_queue", classOf[String], new EnterQueueListener(this))
+ server.addEventListener("ready_for_student", classOf[Nothing], new ReadyForStudentListener(this))
+
+ server.start()
+
+ def queueJSON(): String = {
+ val queue: List[StudentInQueue] = Database.getQueue()
+ val queueJSON: List[JsValue] = queue.map((entry: StudentInQueue) => entry.asJsValue())
+ Json.stringify(Json.toJson(queueJSON))
+ }
+
+}
+
+object OfficeHoursServer {
+ def main(args: Array[String]): Unit = {
+ new OfficeHoursServer()
+ }
+}
+
+
+class DisconnectionListener(server: OfficeHoursServer) extends DisconnectListener {
+ override def onDisconnect(socket: SocketIOClient): Unit = {
+ if (server.socketToUsername.contains(socket)) {
+ val username = server.socketToUsername(socket)
+ server.socketToUsername -= socket
+ if (server.usernameToSocket.contains(username)) {
+ server.usernameToSocket -= username
+ }
+ }
+ }
+}
+
+
+class EnterQueueListener(server: OfficeHoursServer) extends DataListener[String] {
+ override def onData(socket: SocketIOClient, username: String, ackRequest: AckRequest): Unit = {
+ Database.addStudentToQueue(StudentInQueue(username, System.nanoTime()))
+ server.socketToUsername += (socket -> username)
+ server.usernameToSocket += (username -> socket)
+ server.server.getBroadcastOperations.sendEvent("queue", server.queueJSON())
+ }
+}
+
+
+class ReadyForStudentListener(server: OfficeHoursServer) extends DataListener[Nothing] {
+ override def onData(socket: SocketIOClient, dirtyMessage: Nothing, ackRequest: AckRequest): Unit = {
+ val queue = Database.getQueue().sortBy(_.timestamp)
+ if(queue.nonEmpty){
+ val studentToHelp = queue.head
+ Database.removeStudentFromQueue(studentToHelp.username)
+ socket.sendEvent("message", "You are now helping " + studentToHelp.username)
+ if(server.usernameToSocket.contains(studentToHelp.username)){
+ server.usernameToSocket(studentToHelp.username).sendEvent("message", "A TA is ready to help you")
+ }
+ server.server.getBroadcastOperations.sendEvent("queue", server.queueJSON())
+ }
+ }
+}
+
+
diff --git a/src/main/scala/model/StudentInQueue.scala b/src/main/scala/model/StudentInQueue.scala
new file mode 100644
index 0000000..464108f
--- /dev/null
+++ b/src/main/scala/model/StudentInQueue.scala
@@ -0,0 +1,36 @@
+package model
+
+import play.api.libs.json.{JsValue, Json}
+
+
+object StudentInQueue {
+
+ def cleanString(input: String): String = {
+ var output = input
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ if (output.length > 20) {
+ output = output.slice(0, 20) + "..."
+ }
+ output
+ }
+
+ def apply(username: String, timestamp: Long): StudentInQueue = {
+ new StudentInQueue(cleanString(username), timestamp)
+ }
+
+
+}
+
+class StudentInQueue(val username: String, val timestamp: Long) {
+
+ def asJsValue(): JsValue ={
+ val messageMap: Map[String, JsValue] = Map(
+ "username" -> Json.toJson(username),
+ "timestamp" -> Json.toJson(timestamp)
+ )
+ Json.toJson(messageMap)
+ }
+
+}