Commit 0d182b1b authored by Tom Käsler's avatar Tom Käsler

Merge branch 'motd' into 'master'

Motd

See merge request !13
parents a50f1a8e 12481341
Pipeline #7894 passed with stages
in 5 minutes
......@@ -100,3 +100,24 @@ CREATE TABLE features (
PRIMARY KEY(id),
CONSTRAINT features_session_fk FOREIGN KEY (session_id) REFERENCES sessions(id) ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=INNODB;
CREATE TABLE session_motds (
id INT NOT NULL AUTO_INCREMENT,
session_id INT NOT NULL,
startdate VARCHAR(30) NOT NULL,
enddate VARCHAR(30) NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
PRIMARY KEY(id),
CONSTRAINT global_motd_session_fk FOREIGN KEY (session_id) REFERENCES sessions(id) ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=INNODB;
CREATE TABLE global_motds (
id INT NOT NULL AUTO_INCREMENT,
startdate VARCHAR(30) NOT NULL,
enddate VARCHAR(30) NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
audience VARCHAR(255) NOT NULL,
PRIMARY KEY(id)
) ENGINE=INNODB;
......@@ -4,7 +4,7 @@ import akka.http.scaladsl.server.Directives._
import de.thm.arsnova.api._
trait Routes extends ApiErrorHandler with UserApi with SessionApi with QuestionApi with FreetextAnswerApi with ChoiceAnswerApi
with CommentApi with FeaturesApi {
with CommentApi with FeaturesApi with SessionMotdApi with GlobalMotdApi {
val routes = {
userApi ~
sessionApi ~
......@@ -12,6 +12,8 @@ trait Routes extends ApiErrorHandler with UserApi with SessionApi with QuestionA
freetextAnswerApi ~
choiceAnswerApi ~
commentApi ~
featuresApi
featuresApi ~
sessionMotdApi ~
globalMotdApi
} ~ path("")(getFromResource("public/index.html"))
}
package de.thm.arsnova.api
import de.thm.arsnova.services.GlobalMotdService
import de.thm.arsnova.models._
import de.thm.arsnova.hateoas.{ApiRoutes, ResourceAdapter, Link}
import scala.concurrent.ExecutionContext.Implicits.global
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.server.Directives._
import spray.json._
/*
The API Interface regarding global messages.
Only an admin should post these motd
*/
trait GlobalMotdApi {
// protocol for serializing data
import de.thm.arsnova.mappings.GlobalMotdJsonProtocol._
// add the "top level" endpoint to ApiRoutes
ApiRoutes.addRoute("globalmotd", "globalmotd")
// function to generate the model links
def globalMotdLinks(motd: GlobalMotd): Seq[Link] = {
Seq(
Link("self", s"/${ApiRoutes.getRoute("globalmotd")}/${motd.id.get}")
)
}
// the HATEOAS Adapter
val globalMotdAdapter = new ResourceAdapter[GlobalMotd](globalMotdFormat, globalMotdLinks)
val globalMotdApi = pathPrefix(ApiRoutes.getRoute("globalmotd")) {
pathEndOrSingleSlash {
get {
parameter("audience".as[String]) { audience =>
complete (GlobalMotdService.getByAudience(audience).map(globalMotdAdapter.toResources(_)))
}
} ~
post {
entity(as[GlobalMotd]) { motd =>
complete (GlobalMotdService.create(motd).map(_.toJson))
}
}
} ~
pathPrefix(IntNumber) { motdId =>
pathEndOrSingleSlash {
get {
complete (GlobalMotdService.getById(motdId).map(globalMotdAdapter.toResource(_)))
} ~
put {
entity(as[GlobalMotd]) { motd =>
complete (GlobalMotdService.update(motd).map(_.toJson))
}
} ~
delete {
complete (GlobalMotdService.delete(motdId).map(_.toJson))
}
}
}
}
}
package de.thm.arsnova.api
import de.thm.arsnova.services.SessionMotdService
import de.thm.arsnova.models._
import de.thm.arsnova.hateoas.{ApiRoutes, ResourceAdapter, Link}
import scala.concurrent.ExecutionContext.Implicits.global
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.server.Directives._
import spray.json._
/*
The API Interface regarding session messages.
*/
trait SessionMotdApi {
// protocol for serializing data
import de.thm.arsnova.mappings.SessionMotdJsonProtocol._
// add the "top level" endpoint to ApiRoutes
ApiRoutes.addRoute("sessionmotd", "motd")
// function to generate the model links
def sessionMotdLinks(motd: SessionMotd): Seq[Link] = {
Seq(
Link("self", s"/${ApiRoutes.getRoute("session")}/${motd.sessionId}" +
s"/${ApiRoutes.getRoute("sessionmotd")}/${motd.id.get}"),
Link("session", s"/${ApiRoutes.getRoute("session")}/${motd.sessionId}")
)
}
// the HATEOAS Adapter
val sessionMotdAdapter = new ResourceAdapter[SessionMotd](sessionMotdFormat, sessionMotdLinks)
val sessionMotdApi = pathPrefix(ApiRoutes.getRoute("session")) {
pathPrefix(IntNumber) { sessionId =>
pathPrefix(ApiRoutes.getRoute("sessionmotd")) {
pathEndOrSingleSlash {
get {
complete (SessionMotdService.getBySessionId(sessionId).map(sessionMotdAdapter.toResources(_)))
} ~
post {
entity(as[SessionMotd]) { motd =>
complete (SessionMotdService.create(motd).map(_.toJson))
}
}
} ~
pathPrefix(IntNumber) { motdId =>
pathEndOrSingleSlash {
get {
complete (SessionMotdService.getById(motdId).map(sessionMotdAdapter.toResource(_)))
} ~
put {
entity(as[SessionMotd]) { motd =>
complete (SessionMotdService.update(motd).map(_.toJson))
}
} ~
delete {
complete (SessionMotdService.delete(motdId).map(_.toJson))
}
}
}
}
}
}
}
package de.thm.arsnova.mappings
import de.thm.arsnova.models.GlobalMotd
import spray.json._
object GlobalMotdJsonProtocol extends DefaultJsonProtocol {
implicit val globalMotdFormat: RootJsonFormat[GlobalMotd] = jsonFormat6(GlobalMotd)
}
package de.thm.arsnova.mappings
import de.thm.arsnova.models.SessionMotd
import spray.json._
object SessionMotdJsonProtocol extends DefaultJsonProtocol {
implicit val sessionMotdFormat: RootJsonFormat[SessionMotd] = jsonFormat6(SessionMotd)
}
package de.thm.arsnova.models
case class SessionMotd(
id: Option[SessionMotdId],
sessionId: SessionId,
startdate: String,
enddate: String,
title: String,
text: String
)
case class GlobalMotd(
id: Option[GlobalMotdId],
startdate: String,
enddate: String,
title: String,
text: String,
audience: String
)
package de.thm.arsnova.models.definitions
import de.thm.arsnova.models.{GlobalMotd, GlobalMotdId}
import slick.driver.MySQLDriver.api._
import slick.lifted.ForeignKeyQuery
class GlobalMotdsTable(tag: Tag) extends Table[GlobalMotd](tag, "global_motds") {
def id: Rep[GlobalMotdId] = column[GlobalMotdId]("id", O.PrimaryKey, O.AutoInc)
def startdate: Rep[String] = column[String]("startdate")
def enddate: Rep[String] = column[String]("enddate")
def title: Rep[String] = column[String]("title")
def text: Rep[String] = column[String]("content")
def audience: Rep[String] = column[String]("audience")
def * = (id.?, startdate, enddate, title, text, audience) <> ((GlobalMotd.apply _).tupled, GlobalMotd.unapply)
}
package de.thm.arsnova.models.definitions
import de.thm.arsnova.models.{SessionMotd, SessionMotdId, SessionId}
import slick.driver.MySQLDriver.api._
import slick.lifted.ForeignKeyQuery
class SessionMotdsTable(tag: Tag) extends Table[SessionMotd](tag, "session_motds") {
def id: Rep[SessionMotdId] = column[SessionMotdId]("id", O.PrimaryKey, O.AutoInc)
def sessionId: Rep[SessionId] = column[SessionId]("session_id")
def startdate: Rep[String] = column[String]("startdate")
def enddate: Rep[String] = column[String]("enddate")
def title: Rep[String] = column[String]("title")
def text: Rep[String] = column[String]("content")
def * = (id.?, sessionId, startdate, enddate, title, text) <> ((SessionMotd.apply _).tupled, SessionMotd.unapply)
}
......@@ -8,4 +8,6 @@ package object models {
type ChoiceAnswerId = Long
type CommentId = Long
type FeaturesId = Long
type SessionMotdId = Long
type GlobalMotdId = Long
}
......@@ -19,6 +19,8 @@ trait BaseService extends DatabaseConfig {
val choiceAnswersTable = TableQuery[ChoiceAnswersTable]
val commentsTable = TableQuery[CommentsTable]
val featuresTable = TableQuery[FeaturesTable]
val globalMotdsTable = TableQuery[GlobalMotdsTable]
val sessionMotdsTable = TableQuery[SessionMotdsTable]
protected implicit def executeFromDb[A](action: SqlAction[A, NoStream, _ <: slick.dbio.Effect]): Future[A] = {
db.run(action)
......
package de.thm.arsnova.services
import de.thm.arsnova.models.{GlobalMotd, GlobalMotdId}
import slick.driver.MySQLDriver.api._
import scala.concurrent.Future
object GlobalMotdService extends BaseService {
def getById(motdId: GlobalMotdId): Future[GlobalMotd] = {
globalMotdsTable.filter(_.id === motdId).result.head
}
def getByAudience(audience: String): Future[Seq[GlobalMotd]] = {
globalMotdsTable.filter(_.audience === audience).result
}
def create(motd: GlobalMotd): Future[GlobalMotdId] = {
globalMotdsTable.returning(globalMotdsTable.map(_.id)) += motd
}
def update(motd: GlobalMotd): Future[Int] = {
globalMotdsTable.filter(_.id === motd.id.get).map(m =>
(m.audience, m.title, m.text))
.update(motd.audience, motd.title, motd.text)
}
def delete(motdId: GlobalMotdId): Future[Int] = {
globalMotdsTable.filter(_.id === motdId).delete
}
}
package de.thm.arsnova.services
import de.thm.arsnova.models.{SessionMotd, SessionMotdId, SessionId}
import slick.driver.MySQLDriver.api._
import scala.concurrent.Future
object SessionMotdService extends BaseService {
def getById(motdId: SessionMotdId): Future[SessionMotd] = {
sessionMotdsTable.filter(_.id === motdId).result.head
}
def getBySessionId(sessionId: SessionId): Future[Seq[SessionMotd]] = {
sessionMotdsTable.filter(_.sessionId === sessionId).result
}
def create(motd: SessionMotd): Future[SessionMotdId] = {
sessionMotdsTable.returning(sessionMotdsTable.map(_.id)) += motd
}
def update(motd: SessionMotd): Future[Int] = {
sessionMotdsTable.filter(_.id === motd.id.get).map(m =>
(m.title, m.text))
.update(motd.title, motd.text)
}
def delete(motdId: SessionMotdId): Future[Int] = {
sessionMotdsTable.filter(_.id === motdId).delete
}
}
......@@ -13,7 +13,7 @@ import org.scalatest.BeforeAndAfterEach
class BaseServiceSpec extends FunSpec with Matchers with MigrationConfig with BeforeAndAfterAll with DatabaseConfig
with BaseService with HateoasSpec with SessionApiSpec with QuestionApiSpec with FreetextAnswerApiSpec with ChoiceAnswerApiSpec with CommentApiSpec
with FeaturesApiSpec with TestData {
with FeaturesApiSpec with GlobalMotdApiSpec with SessionMotdApiSpec with TestData {
protected val log: LoggingAdapter = NoLogging
import driver.api._
......@@ -30,5 +30,7 @@ class BaseServiceSpec extends FunSpec with Matchers with MigrationConfig with Be
Await.result(choiceAnswersTable ++= testChoiceAnswers, 10.seconds)
Await.result(commentsTable ++= testComments, 10.seconds)
Await.result(featuresTable ++= testFeatures, 10.seconds)
Await.result(globalMotdsTable ++= testGlobalMotds, 10.seconds)
Await.result(sessionMotdsTable ++= testSessionMotds, 10.seconds)
}
}
package de.thm.arsnova
import de.thm.arsnova.services.BaseService
import de.thm.arsnova.models._
import de.thm.arsnova.api.GlobalMotdApi
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.http.scaladsl.model.{HttpEntity, MediaTypes}
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import org.scalatest.{FunSpec, Matchers}
import org.scalatest.concurrent.ScalaFutures
import spray.json._
import java.util.Calendar
trait GlobalMotdApiSpec extends FunSpec with Matchers with ScalaFutures with BaseService with ScalatestRouteTest with Routes
with TestData with GlobalMotdApi {
import de.thm.arsnova.mappings.GlobalMotdJsonProtocol._
val ENDDATEDAYSFROMNOW = 7
describe("Global motd api") {
it("retrieve motd by id") {
Get("/globalmotd/1") ~> globalMotdApi ~> check {
responseAs[JsObject] should be(globalMotdAdapter.toResource(testGlobalMotds.head))
}
}
it("create motd") {
val now = Calendar.getInstance
val startdate = now.getTime.toString
now.add(Calendar.DAY_OF_MONTH, ENDDATEDAYSFROMNOW)
val enddate = now.getTime.toString
val newText = "a new global motd for all"
val newMotd = GlobalMotd(None, startdate, enddate, "newMotd", newText, "all")
val requestEntity = HttpEntity(MediaTypes.`application/json`, newMotd.toJson.toString)
Post("/globalmotd/", requestEntity) ~> globalMotdApi ~> check {
response.status should be(OK)
val newId = Await.result(Unmarshal(response.entity).to[String], 1.second).toLong
val checkMotd = newMotd.copy(id = Some(newId))
Get(s"/globalmotd/${newId}") ~> globalMotdApi ~> check {
responseAs[JsObject] should be(globalMotdAdapter.toResource(checkMotd))
}
}
}
it("update motd") {
val motd = testGlobalMotds.head
val updateMotd = motd.copy(text = "this in an updated text")
val requestEntity = HttpEntity(MediaTypes.`application/json`, updateMotd.toJson.toString)
Put(s"/globalmotd/${motd.id.get}", requestEntity) ~> globalMotdApi ~> check {
response.status should be(OK)
Get(s"/globalmotd/${motd.id.get}") ~> globalMotdApi ~> check {
responseAs[JsObject] should be(globalMotdAdapter.toResource(updateMotd))
}
}
}
it("delete motd") {
val motdId = testGlobalMotds.head.id.get
Delete(s"/globalmotd/$motdId") ~> globalMotdApi ~> check {
response.status should be(OK)
Get(s"/globalmotd/$motdId") ~> globalMotdApi ~> check {
response.status should be(NotFound)
}
}
}
}
}
package de.thm.arsnova
import de.thm.arsnova.services.BaseService
import de.thm.arsnova.models._
import de.thm.arsnova.api.SessionMotdApi
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.http.scaladsl.model.{HttpEntity, MediaTypes}
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import org.scalatest.{FunSpec, Matchers}
import org.scalatest.concurrent.ScalaFutures
import spray.json._
import java.util.Calendar
trait SessionMotdApiSpec extends FunSpec with Matchers with ScalaFutures with BaseService with ScalatestRouteTest with Routes
with TestData with SessionMotdApi {
import de.thm.arsnova.mappings.SessionMotdJsonProtocol._
val ENDDATEDAYSFROMNOW2 = 7
describe("Session motd api") {
it("retrieve motd by id") {
Get("/session/1/motd/1") ~> sessionMotdApi ~> check {
responseAs[JsObject] should be(sessionMotdAdapter.toResource(testSessionMotds.head))
}
}
it("create motd") {
val now = Calendar.getInstance
val startdate = now.getTime.toString
now.add(Calendar.DAY_OF_MONTH, ENDDATEDAYSFROMNOW2)
val enddate = now.getTime.toString
val newText = "a new session motd for session w/ id 1"
val newMotd = SessionMotd(None, 1, startdate, enddate, "newMotd", newText)
val requestEntity = HttpEntity(MediaTypes.`application/json`, newMotd.toJson.toString)
Post("/session/1/motd/", requestEntity) ~> sessionMotdApi ~> check {
response.status should be(OK)
val newId = Await.result(Unmarshal(response.entity).to[String], 1.second).toLong
val checkMotd = newMotd.copy(id = Some(newId))
Get(s"/session/1/motd/${newId}") ~> sessionMotdApi ~> check {
responseAs[JsObject] should be(sessionMotdAdapter.toResource(checkMotd))
}
}
}
it("update motd") {
val motd = testSessionMotds.head
val updateMotd = motd.copy(text = "this in an updated text")
val requestEntity = HttpEntity(MediaTypes.`application/json`, updateMotd.toJson.toString)
Put(s"/session/1/motd/${motd.id.get}", requestEntity) ~> sessionMotdApi ~> check {
response.status should be(OK)
Get(s"/session/1/motd/${motd.id.get}") ~> sessionMotdApi ~> check {
responseAs[JsObject] should be(sessionMotdAdapter.toResource(updateMotd))
}
}
}
it("delete motd") {
val motdId = testSessionMotds.head.id.get
Delete(s"/session/1/motd/$motdId") ~> sessionMotdApi ~> check {
response.status should be(OK)
Get(s"/session/1/motd/$motdId") ~> sessionMotdApi ~> check {
response.status should be(NotFound)
}
}
}
}
}
......@@ -84,4 +84,16 @@ trait TestData {
Comment(Some(2), 1, 1, true, "subject2", "text2", "1317574095000"),
Comment(Some(3), 1, 1, true, "subject2", "text2", "1317574085000")
)
val testSessionMotds = Seq(
SessionMotd(Some(1), 1, "1490181860", "1584057600", "session message", "The first test session message text"),
SessionMotd(Some(2), 1, "1490181860", "1584057600", "session message2", "The second test session message text")
)
val testGlobalMotds = Seq(
GlobalMotd(Some(1), "1490181860", "1584057600", "global message for all", "Global message text", "all"),
GlobalMotd(Some(2), "1490181860", "1584057600", "global message for loggedIn", "Global message text", "loggedIn"),
GlobalMotd(Some(2), "1490181860", "1584057600", "global message for students", "Global message text", "students"),
GlobalMotd(Some(3), "1490181860", "1584057600", "global message for tutors", "Global message text", "tutors")
)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment