diff --git a/.gitignore b/.gitignore index 54f139b..649faed 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ release.properties dependency-reduced-pom.xml buildNumber.properties .mvn/timing.properties + +#dotenv +src/main/resources/.env \ No newline at end of file diff --git a/README.md b/README.md index c1a6587..372262f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,19 @@ # OfficeHours A web-based system to manage office hours + +### Known Issues +1. [Failed to compile with Scala 2.13](https://github.com/UB-CSE/OfficeHours/issues/174) + +### Contribution Guide + +#### 1. How to Build this Project +1. Set up Scala SDK +2. Mark `src/main/scala` as `Source Root` if need +3. Set up the local configurations + - Rename `src/main/resources/.env.example` to `src/main/resources/.env` + + Revise anything you need to change (Ex: change DB configurations. By default, it will + use `List` as DB Driver, that means you don't really need to care about the DB connection + if you're not familiar with it, but data(Ex: Queue data), will lose once you restart the project). + +4. Build it. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 44e6ab8..d2c1055 100644 --- a/pom.xml +++ b/pom.xml @@ -83,6 +83,13 @@ + + + src/main/resources + + src/main/resources/.env + + \ No newline at end of file diff --git a/src/main/resources/.env.example b/src/main/resources/.env.example new file mode 100644 index 0000000..9dea8cb --- /dev/null +++ b/src/main/resources/.env.example @@ -0,0 +1,24 @@ +# src/main/resources/.env.example +# Rename this file into `.env` +# to make this project work + +# ENV +# +# Not used by anything yet +ENV=DEV + +# DB_TYPE +# Type of the database the server gonna use +# Types are implemented in model.database, +# Loaded in src/main/scala/model/Configuration +# +# "MySQL" || "List" +# Default: "List" +DB_TYPE=List + +# DB Connection Informations +# Only need if you're using MySQL as + +# DB_URL=jdbc:mysql://localhost/officehours?autoReconnect=true&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC +# DB_USERNAME=root +# DB_PASSWORD=root diff --git a/src/main/scala/helper/dotenv/DirtyEnvironmentHack.scala b/src/main/scala/helper/dotenv/DirtyEnvironmentHack.scala new file mode 100644 index 0000000..77ac7de --- /dev/null +++ b/src/main/scala/helper/dotenv/DirtyEnvironmentHack.scala @@ -0,0 +1,51 @@ +package helper.dotenv + +import java.util.Collections +import scala.util.control.NonFatal +import scala.util.{Failure, Success, Try} + +/** + * Rewrite the runtime Environment, embedding entries from the .env file. + * + * Taken from: https://github.com/mefellows/sbt-dotenv/blob/master/src/main/scala/au/com/onegeek/sbtdotenv/DirtyEnvironmentHack.scala + * Original from: http://stackoverflow.com/questions/318239/how-do-i-set-environment-variables-from-java/7201825#7201825 + * + * Created by mfellows on 20/07/2014. + */ +object DirtyEnvironmentHack { + def setEnv(newEnv: java.util.Map[String, String]): Unit = { + Try({ + val processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment") + + val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment") + theEnvironmentField.setAccessible(true) + val env = theEnvironmentField.get(null).asInstanceOf[java.util.Map[String, String]] // scalastyle:off null + env.putAll(newEnv) + + val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment") + theCaseInsensitiveEnvironmentField.setAccessible(true) + val ciEnv = theCaseInsensitiveEnvironmentField.get(null).asInstanceOf[java.util.Map[String, String]] // scalastyle:off null + ciEnv.putAll(newEnv) + }) match { + case Failure(_: NoSuchFieldException) => + Try({ + val classes = classOf[Collections].getDeclaredClasses + val env = System.getenv + classes.filter(_.getName == "java.util.Collections$UnmodifiableMap").foreach(cl => { + val field = cl.getDeclaredField("m") + field.setAccessible(true) + val map = field.get(env).asInstanceOf[java.util.Map[String, String]] + map.clear() + map.putAll(newEnv) + }) + }) match { + case Failure(NonFatal(e2)) => + e2.printStackTrace() + case Success(_) => + } + case Failure(NonFatal(e1)) => + e1.printStackTrace() + case Success(_) => + } + } +} diff --git a/src/main/scala/helper/dotenv/Dotenv.scala b/src/main/scala/helper/dotenv/Dotenv.scala new file mode 100644 index 0000000..3368bd0 --- /dev/null +++ b/src/main/scala/helper/dotenv/Dotenv.scala @@ -0,0 +1,36 @@ +package helper.dotenv + +import scala.io.Source +import scala.util.{Failure, Success, Try} +import collection.JavaConverters._ + +object Dotenv { + // To load .env file to the Environmental Variables for development + def loadEnv(envFilePath: String = ".env"): Unit = { + var envLines: List[String] = Try(Source.fromResource(envFilePath).getLines().toList) match { + case Success(lines) => lines + case Failure(e) => { + print("[Warn] Failed to load .env: ") + println(e) + + // return empty list if .env not exist + List() + } + } + + for (line <- envLines) { + // skip the lines which are commented out + if (line.trim != "" && !line.trim.startsWith("#")) { + // Split the line by character `=` once + var lineContent: Array[String] = line.split("=", 2) + if (lineContent.length > 0) { + if (lineContent.length == 2) { + DirtyEnvironmentHack.setEnv((sys.env ++ Map( + lineContent(0) -> lineContent(1) + )).asJava) + } + } + } + } + } +} diff --git a/src/main/scala/model/Configuration.scala b/src/main/scala/model/Configuration.scala index 7b8ece5..3f0a58f 100644 --- a/src/main/scala/model/Configuration.scala +++ b/src/main/scala/model/Configuration.scala @@ -1,7 +1,24 @@ package model +import scala.util.{Failure, Success, Try} + object Configuration { val DEV_MODE = true + /** + * DB_TYPE + * + * Type of the database the server gonna use + * Types are implemented in model.database + * + * "MySQL" || "List" + * + * default: "List" + * */ + var DB_TYPE: String = Try(sys.env("DB_TYPE")) match { + case Success(dbType) => dbType + case Failure(e) => "List" + } + } diff --git a/src/main/scala/model/OfficeHoursServer.scala b/src/main/scala/model/OfficeHoursServer.scala index 09ef61b..cdbc16a 100644 --- a/src/main/scala/model/OfficeHoursServer.scala +++ b/src/main/scala/model/OfficeHoursServer.scala @@ -1,23 +1,26 @@ package model +import model.{Configuration => LocalConfiguration} import com.corundumstudio.socketio.listener.{DataListener, DisconnectListener} -import com.corundumstudio.socketio.{AckRequest, Configuration, SocketIOClient, SocketIOServer} +import com.corundumstudio.socketio.{AckRequest, SocketIOClient, SocketIOServer, Configuration => SocketIOConfiguration} +import helper.dotenv.Dotenv import model.database.{Database, DatabaseAPI, TestingDatabase} import play.api.libs.json.{JsValue, Json} class OfficeHoursServer() { - val database: DatabaseAPI = if(Configuration.DEV_MODE){ - new TestingDatabase - }else{ + val database: DatabaseAPI = if(LocalConfiguration.DB_TYPE == "MySQL") { new Database + } else { + // fallback to using List as DB if it's "List" or others + new TestingDatabase } var usernameToSocket: Map[String, SocketIOClient] = Map() var socketToUsername: Map[SocketIOClient, String] = Map() - val config: Configuration = new Configuration { + val config: SocketIOConfiguration = new SocketIOConfiguration { setHostname("0.0.0.0") setPort(8080) } @@ -40,6 +43,9 @@ class OfficeHoursServer() { object OfficeHoursServer { def main(args: Array[String]): Unit = { + // Load environmental variables + Dotenv.loadEnv() + new OfficeHoursServer() } } diff --git a/src/main/scala/model/database/Database.scala b/src/main/scala/model/database/Database.scala index bbc9f7a..6f0d035 100644 --- a/src/main/scala/model/database/Database.scala +++ b/src/main/scala/model/database/Database.scala @@ -7,7 +7,7 @@ import model.StudentInQueue class Database extends DatabaseAPI{ - val url = "jdbc:mysql://mysql/officehours?autoReconnect=true" + val url: String = sys.env("DB_URL") val username: String = sys.env("DB_USERNAME") val password: String = sys.env("DB_PASSWORD")