Commit a50f1a8e authored by Tom Käsler's avatar Tom Käsler

Merge branch 'addAttributes' into 'master'

Add attributes

Closes #23

See merge request !12
parents 9ab9abd1 10c57313
Pipeline #7880 passed with stages
in 4 minutes and 55 seconds
......@@ -7,10 +7,15 @@ CREATE TABLE users (
CREATE TABLE sessions (
id INT NOT NULL AUTO_INCREMENT,
sessionkey VARCHAR(8) NOT NULL,
keyword VARCHAR(8) NOT NULL,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
short_title VARCHAR(255) NOT NULL,
short_name VARCHAR(255) NOT NULL,
last_owner_activity varchar(30) NOT NULL,
creation_time varchar(30) NOT NULL,
active TINYINT(1) NOT NULL DEFAULT 0,
feedback_lock TINYINT(1) NOT NULL DEFAULT 0,
flip_flashcards TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY(id),
CONSTRAINT session_user_fk FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=INNODB;
......@@ -22,6 +27,13 @@ CREATE TABLE questions (
content TEXT NOT NULL,
variant VARCHAR(255) NOT NULL,
format VARCHAR(255) NOT NULL,
hint TEXT,
solution TEXT,
active TINYINT(1) NOT NULL DEFAULT 0,
voting_disabled TINYINT(1) NOT NULL DEFAULT 1,
show_statistic TINYINT(1) NOT NULL DEFAULT 0,
show_answer TINYINT(1) NOT NULL DEFAULT 0,
abstention_allowed TINYINT(1) NOT NULL DEFAULT 1,
format_attributes TEXT,
PRIMARY KEY(id),
CONSTRAINT question_session_fk FOREIGN KEY (session_id) REFERENCES sessions(id) ON UPDATE CASCADE ON DELETE CASCADE
......
......@@ -32,5 +32,5 @@ object QuestionJsonProtocol extends DefaultJsonProtocol {
}
// questin JSON protocol utilizes the above format. No need to manually do things here
implicit val questionFormat: RootJsonFormat[Question] = jsonFormat8(Question)
implicit val questionFormat: RootJsonFormat[Question] = jsonFormat15(Question)
}
......@@ -4,5 +4,6 @@ import de.thm.arsnova.models.Session
import spray.json.{DefaultJsonProtocol, RootJsonFormat}
object SessionJsonProtocol extends DefaultJsonProtocol {
implicit val sessionFormat: RootJsonFormat[Session] = jsonFormat5(Session)
import FeatureJsonProtocol._
implicit val sessionFormat: RootJsonFormat[Session] = jsonFormat11(Session)
}
......@@ -9,6 +9,13 @@ case class Question(
content: String,
variant: String,
format: String,
hint: Option[String],
solution: Option[String],
active: Boolean,
votingDisabled: Boolean,
showStatistic: Boolean,
showAnswer: Boolean,
abstentionAllowed: Boolean,
formatAttributes: Option[FormatAttributes],
answerOptions: Option[Seq[AnswerOption]]
)
package de.thm.arsnova.models
case class Session(id: Option[SessionId], key: String, userId: UserId, title: String, shortTitle: String)
case class Session(
id: Option[SessionId],
keyword: String,
userId: UserId,
title: String,
shortName: String,
lastOwnerActivity: String,
creationTime: String,
active: Boolean,
feedbackLock: Boolean,
flipFlashcards: Boolean,
features: Option[Features]
)
......@@ -13,30 +13,43 @@ class QuestionsTable(tag: Tag) extends Table[Question](tag, "questions"){
def content: Rep[String] = column[String]("content")
def variant: Rep[String] = column[String]("variant")
def format: Rep[String] = column[String]("format")
def hint: Rep[String] = column[String]("hint")
def solution: Rep[String] = column[String]("solution")
def active: Rep[Boolean] = column[Boolean]("active")
def votingDisabled: Rep[Boolean] = column[Boolean]("voting_disabled")
def showStatistic: Rep[Boolean] = column[Boolean]("show_statistic")
def showAnswer: Rep[Boolean] = column[Boolean]("show_answer")
def abstentionAllowed: Rep[Boolean] = column[Boolean]("abstention_allowed")
def formatAttributes: Rep[String] = column[String]("format_attributes")
def * = (id.?, sessionId, subject, content, variant, format, formatAttributes) <> (
// TODO: please clean this up.
def * = (id.?, sessionId, subject, content, variant, format, hint.?, solution.?, active, votingDisabled, showStatistic,
showAnswer, abstentionAllowed, formatAttributes) <> (
// first comes the part for fetching
{ q: (Option[QuestionId], SessionId, String, String, String, String, String) => q match {
case (id, sessionId, subject, content, variant, format, formatAttributes) => {
formatAttributes match {
// some question formats don't have formatAttributes
case "null" => new Question(id, sessionId, subject, content, variant, format, None, None)
// formatAttributes are stored as JSON strings. This parsese them into the map
// using the formatAttributes JSON protocol doesn't work since it returns a JsObject
case _ => val fA = formatAttributes.substring(1, formatAttributes.length - 1)
.split(",")
.map(_.split(":"))
.map { case Array(k, v) => (k.substring(1, k.length-1), v.substring(1, v.length-1))}
.toMap
new Question(id, sessionId, subject, content, variant, format, Some(FormatAttributes(fA)), None)
{ q: (Option[QuestionId], SessionId, String, String, String, String, Option[String], Option[String],
Boolean, Boolean, Boolean, Boolean, Boolean, String) =>
q match {
case (id, sessionId, subject, content, variant, format, hint, solution, active, vD, sS, sA, aA, formatAttributes) => {
formatAttributes match {
// some question formats don't have formatAttributes
case "null" => new Question(id, sessionId, subject, content, variant, format, hint, solution, active, vD, sS, sA, aA, None, None)
// formatAttributes are stored as JSON strings. This parsese them into the map
// using the formatAttributes JSON protocol doesn't work since it returns a JsObject
case _ => val fA = formatAttributes.substring(1, formatAttributes.length - 1)
.split(",")
.map(_.split(":"))
.map { case Array(k, v) => (k.substring(1, k.length-1), v.substring(1, v.length-1))}
.toMap
new Question(id, sessionId, subject, content, variant, format, hint, solution, active, vD, sS, sA, aA, Some(FormatAttributes(fA)), None)
}
}
}
}}, {
// part for storing questions in the table
q: Question =>
Some((q.id, q.sessionId, q.subject, q.content, q.variant, q.format, q.formatAttributes.toJson.toString)):
Option[(Option[QuestionId], SessionId, String, String, String, String, String)]
Some((q.id, q.sessionId, q.subject, q.content, q.variant, q.format, q.hint, q.solution,
q.active, q.votingDisabled, q.showStatistic, q.showAnswer, q.abstentionAllowed, q.formatAttributes.toJson.toString)):
Option[(Option[QuestionId], SessionId, String, String, String, String, Option[String], Option[String],
Boolean, Boolean, Boolean, Boolean, Boolean, String)]
})
def session: ForeignKeyQuery[SessionsTable, de.thm.arsnova.models.Session] = foreignKey("question_session_fk", sessionId, TableQuery[SessionsTable])(_.id)
......
......@@ -6,12 +6,24 @@ import slick.lifted.ForeignKeyQuery
class SessionsTable(tag: Tag) extends Table[Session](tag, "sessions"){
def id: Rep[SessionId] = column[SessionId]("id", O.PrimaryKey, O.AutoInc)
def key: Rep[String] = column[String]("sessionkey")
def key: Rep[String] = column[String]("keyword")
def userId: Rep[UserId] = column[UserId]("user_id")
def title: Rep[String] = column[String]("title")
def shortTitle: Rep[String] = column[String]("short_title")
def shortName: Rep[String] = column[String]("short_name")
def lastOwnerActivity: Rep[String] = column[String]("last_owner_activity")
def creationTime: Rep[String] = column[String]("creation_time")
def active: Rep[Boolean] = column[Boolean]("active")
def feedbackLock: Rep[Boolean] = column[Boolean]("feedback_lock")
def flipFlashcards: Rep[Boolean] = column[Boolean]("flip_flashcards")
def * = (id.?, key, userId, title, shortTitle) <> ((Session.apply _).tupled, Session.unapply)
def * = (id.?, key, userId, title, shortName, lastOwnerActivity, creationTime, active, feedbackLock, flipFlashcards) <> ({
s: (Option[SessionId], String, UserId, String, String, String, String, Boolean, Boolean, Boolean) => s match {
case (id, key, userId, title, shortName, lastOwnerActivity, creationTime, active, feedbackLock, flipFlashcards) =>
Session (id, key, userId, title, shortName, lastOwnerActivity, creationTime, active, feedbackLock, flipFlashcards, None)
}}, {
s: Session =>
Some((s.id, s.keyword, s.userId, s.title, s.shortName, s.lastOwnerActivity, s.creationTime, s.active, s.feedbackLock, s.flipFlashcards))
})
def author: ForeignKeyQuery[UsersTable, User] = foreignKey("session_user_fk", userId, TableQuery[UsersTable])(_.id)
}
package de.thm.arsnova.services
import de.thm.arsnova.models.{UserId, Session, SessionId}
import de.thm.arsnova.models.{UserId, Session, SessionId, Features}
import slick.driver.MySQLDriver.api._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object SessionService extends BaseService{
def findUserSessions(userId: UserId): Future[Seq[Session]] = {
(for{
user <- usersTable.filter(_.id === userId)
sessions <- sessionsTable.filter(_.userId === user.id)
} yield sessions).result
val resultTupleQry = for {
sessions <- sessionsTable.filter(_.userId === userId)
sessionFeatures <- featuresTable.filter(_.sessionId === sessions.id)
} yield (sessions, sessionFeatures)
val resultTuple: Future[Seq[Tuple2[Session, Features]]] = db.run(resultTupleQry.result)
resultTuple.map(_.map(tuple =>
tuple._1.copy(features = Some(tuple._2))
))
}
def findById(sessionId: SessionId): Future[Session] = {
sessionsTable.filter(_.id === sessionId).result.head
val futureTupleQry = (for {
session <- sessionsTable.filter(_.id === sessionId)
sessionFeatures <- featuresTable.filter(_.sessionId === session.id)
} yield (session, sessionFeatures)).result.head
val futureTuple = db.run(futureTupleQry)
futureTuple.map(tuple => tuple._1.copy(features = Some(tuple._2)))
}
def findByUserIdAndId(userId: UserId, sessionId: SessionId): Future[Session] = {
(for{
user <- usersTable.filter(_.id === userId)
post <- sessionsTable.filter(_.id === sessionId)
} yield post).result.head
val futureTupleQry = (for {
session <- sessionsTable.filter(s => (s.id === sessionId) && (s.userId === userId))
sessionFeatures <- featuresTable.filter(_.sessionId === session.id)
} yield (session, sessionFeatures)).result.head
val futureTuple = db.run(futureTupleQry)
futureTuple.map(tuple => tuple._1.copy(features = Some(tuple._2)))
}
def create(session: Session): Future[SessionId] = {
val sessionIdFuture: Future[SessionId] = sessionsTable returning sessionsTable.map(_.id) += session
sessionIdFuture.map { sessionId =>
session.features match {
case Some(features) => FeaturesService.create(features)
case None => FeaturesService.create(
Features(None, sessionId, true, true, true, true, true, true, true, true, true, true)
)
}
sessionId
}
}
def update(newSession: Session, sessionId: SessionId): Future[Int] = {
newSession.features match {
case Some(features) => FeaturesService.create(features)
case None => FeaturesService.create(
Features(None, sessionId, true, true, true, true, true, true, true, true, true, true)
)
}
sessionsTable.filter(_.id === sessionId)
.map(newSession => (newSession.title, newSession.shortName))
.update((newSession.title, newSession.shortName))
}
def create(session: Session): Future[SessionId] = sessionsTable returning sessionsTable.map(_.id) += session
def update(newSession: Session, sessionId: SessionId): Future[Int] = sessionsTable.filter(_.id === sessionId)
.map(newSession => (newSession.title, newSession.shortTitle))
.update((newSession.title, newSession.shortTitle))
def delete(sessionId: SessionId): Future[Int] = sessionsTable.filter(_.id === sessionId).delete
......
......@@ -56,7 +56,7 @@ trait QuestionApiSpec extends FunSpec with Matchers with ScalaFutures with BaseS
val content = "postContent"
val variant = "preparation"
val format = "freetext"
val newFreetext = Question(None, sessionId, subject, content, variant, format, None, None)
val newFreetext = Question(None, sessionId, subject, content, variant, format, None, None, true, true, false, true, true, None, None)
val requestEntity = HttpEntity(MediaTypes.`application/json`,newFreetext.toJson.toString)
Post("/question", requestEntity) ~> questionApi ~> check {
response.status should be(OK)
......@@ -76,7 +76,7 @@ trait QuestionApiSpec extends FunSpec with Matchers with ScalaFutures with BaseS
val variant = "preparation"
val format = "flashcard"
val backside = "backside"
val newFlashcard = Question(None, sessionId, subject, content, variant, format,
val newFlashcard = Question(None, sessionId, subject, content, variant, format, None, None, true, true, false, true, true,
Some(FormatAttributes(Map("backside" -> "backside"))), None)
val requestEntity = HttpEntity(MediaTypes.`application/json`, newFlashcard.toJson.toString)
Post("/question", requestEntity) ~> questionApi ~> check {
......@@ -97,7 +97,7 @@ trait QuestionApiSpec extends FunSpec with Matchers with ScalaFutures with BaseS
val variant = "preparation"
val format = "mc"
val answerOptions = Seq(AnswerOption(None, None, false, "false", -1), AnswerOption(None, None, true, "true", 1))
val newMC = Question(None, sessionId, subject, content, variant, format,
val newMC = Question(None, sessionId, subject, content, variant, format, None, None, true, true, false, true, true,
None, Some(answerOptions))
val requestEntity = HttpEntity(MediaTypes.`application/json`, newMC.toJson.toString)
Post("/question", requestEntity) ~> questionApi ~> check {
......
......@@ -46,11 +46,16 @@ trait SessionApiSpec extends FunSpec with Matchers with ScalaFutures with BaseSe
val newSessionShortTitle = "newShortTitle"
val requestEntity = HttpEntity(MediaTypes.`application/json`,
JsObject(
"key" -> JsString("55555555"),
"keyword" -> JsString("55555555"),
"userId" -> JsNumber(testUsers.head.id.get),
"title" -> JsString(newSessionTitle),
"shortTitle" -> JsString(newSessionShortTitle)
).toString())
"shortName" -> JsString(newSessionShortTitle),
"lastOwnerActivity" -> JsString((System.currentTimeMillis / 1000).toString),
"creationTime" -> JsString((System.currentTimeMillis / 1000).toString),
"active" -> JsBoolean(true),
"feedbackLock" -> JsBoolean(false),
"flipFlashcards" -> JsBoolean(false)
).toString)
Post("/session", requestEntity) ~> sessionApi ~> check {
response.status should be(OK)
val newSessionId: Future[String] = Unmarshal(response.entity).to[String]
......
......@@ -14,14 +14,19 @@ trait TestData {
User(Some(3), "user3", "password3")
)
val testFeatures = Seq(
Features(Some(1), 1, true, true, true, true, true, true, true, true, true, true),
Features(Some(2), 2, true, false, false, false, false, false, false, false, false, false)
)
val testSessionsForUser1 = Seq(
Session(Some(1), "11111111", 1, "session1", "s1"),
Session(Some(2), "22222222", 1, "session2", "s2")
Session(Some(1), "11111111", 1, "session1", "s1", "1490097542", "1490097542", true, false, true, Some(testFeatures.head)),
Session(Some(2), "22222222", 1, "session2", "s2", "1490097542", "1490097542", true, false, true, Some(testFeatures.drop(1).head))
)
val testSessionsForUser2 = Seq(
Session(Some(3), "33333333", 2, "session3", "s3"),
Session(Some(FOUR), "44444444", 2, "session4", "s4")
Session(Some(3), "33333333", 2, "session3", "s3", "1490097542", "1490097542", true, false, true, Some(testFeatures.head)),
Session(Some(FOUR), "44444444", 2, "session4", "s4", "1490097542", "1490097542", true, false, true, Some(testFeatures.head))
)
val testSessions = testSessionsForUser1 ++ testSessionsForUser2
......@@ -40,19 +45,19 @@ trait TestData {
)
val preparationQuestions = Seq(
Question(Some(1), 1, "subject1", "content1", "preparation", "freetext", None, None),
Question(Some(2), 1, "subject2", "content2", "preparation", "freetext", None, None),
Question(Some(3), 1, "subject3", "content3", "preparation", "flashcard",
Question(Some(1), 1, "subject1", "content1", "preparation", "freetext", None, None, true, true, false, true, true, None, None),
Question(Some(2), 1, "subject2", "content2", "preparation", "freetext", None, None, true, true, false, true, true, None, None),
Question(Some(3), 1, "subject3", "content3", "preparation", "flashcard", None, None, true, true, false, true, true,
Some(FormatAttributes(Map("backside" -> "backside3"))), None),
Question(Some(FOUR), 1, "subject4", "content4", "preparation", "flashcard",
Question(Some(FOUR), 1, "subject4", "content4", "preparation", "flashcard", None, None, true, true, false, true, true,
Some(FormatAttributes(Map("backside" -> "backside4"))), None),
Question(Some(FIVE), 1, "subject5", "content5", "preparation", "mc",
Question(Some(FIVE), 1, "subject5", "content5", "preparation", "mc", None, None, true, true, false, true, true,
None, Some(testAnswerOptions))
)
val liveQuestions = Seq(
Question(Some(SIX), 1, "subject6", "content6", "live", "freetext", None, None),
Question(Some(SEVEN), 1, "subject7", "content7", "live", "flashcard",
Question(Some(SIX), 1, "subject6", "content6", "live", "freetext", None, None, true, true, false, true, true, None, None),
Question(Some(SEVEN), 1, "subject7", "content7", "live", "flashcard", None, None, true, true, false, true, true,
Some(FormatAttributes(Map("backside" -> "backside7"))), None)
)
......@@ -79,9 +84,4 @@ trait TestData {
Comment(Some(2), 1, 1, true, "subject2", "text2", "1317574095000"),
Comment(Some(3), 1, 1, true, "subject2", "text2", "1317574085000")
)
val testFeatures = Seq(
Features(Some(1), 1, true, true, true, true, true, true, true, true, true, true),
Features(Some(2), 2, true, false, false, false, false, false, false, false, false, false)
)
}
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