Commit 78fed46a authored by Björn Eyselein's avatar Björn Eyselein
Browse files

Update: Karten in beide Richtungen

parent 957f5a1a
......@@ -59,7 +59,8 @@ function readSolution(cardType: CardType): undefined | Solution {
collId: flashcard.collId,
courseId: flashcard.courseId,
solution,
selectedAnswers
selectedAnswers,
frontToBack: flashcard.frontToBack
};
}
......@@ -125,6 +126,9 @@ function loadNextFlashcard(loadFlashcardUrl: string): void {
fetch(loadFlashcardUrl).then(response => {
if (response.status === 200) {
response.json().then(loadedFlashcard => {
console.warn(JSON.stringify(loadedFlashcard, null, 2));
flashcard = loadedFlashcard;
canSolve = true;
......@@ -188,6 +192,20 @@ function initAll(loadNextFlashcard: (string) => void, checkSolution: () => void)
correctionTextPar = document.querySelector<HTMLParagraphElement>('#correctionTextPar');
// FIXME: activate...?
// const readQuestionButton = document.querySelector<HTMLButtonElement>('#readQuestionButton');
// readQuestionButton.onclick = () => {
// const utterThis = new SpeechSynthesisUtterance('l\'univers');
//
// const voices = window.speechSynthesis.getVoices();
//
// console.info(voices.length);
//
// window.speechSynthesis.speak(utterThis);
//
// console.info("TODO!");
// };
nextFlashcardBtn = document.querySelector<HTMLButtonElement>('#nextFlashcardBtn');
nextFlashcardBtn.onclick = () => loadNextFlashcard(loadFlashcardUrl);
......
......@@ -37,22 +37,23 @@ interface Flashcard {
cardType: CardType;
question: string;
questionHint: string | undefined;
meaning: string;
meaningHint: string | undefined;
front: string;
frontHint: string | undefined;
frontToBack: boolean;
blanksAnswers: BlanksAnswerFragment[];
choiceAnswers: ChoiceAnswer[];
}
interface Solution {
cardId: number
collId: number
courseId: number
cardId: number;
collId: number;
courseId: number;
solution: string
selectedAnswers: number[]
solution: string;
selectedAnswers: number[];
frontToBack: boolean;
}
interface EditOperation {
......
......@@ -31,9 +31,9 @@ function buildChoiceAnswers(choiceAnswers: ChoiceAnswer[]): string {
}
function updateQuestionText(flashcard: Flashcard): void {
let questionText = flashcard.question;
if (flashcard.questionHint !== undefined) {
questionText += ` <i>${flashcard.questionHint}</i>`;
let questionText = flashcard.front;
if (flashcard.frontHint !== undefined) {
questionText += ` <i>${flashcard.frontHint}</i>`;
}
document.querySelector<HTMLHeadingElement>('#questionDiv').innerHTML = questionText;
......
......@@ -65,18 +65,25 @@ class AdminController @Inject()(cc: ControllerComponents, protected val tableDef
def newCollectionForm(courseId: Int): EssentialAction = futureWithUser { admin =>
implicit request =>
tableDefs.futureNextCollectionIdInCourse(courseId).map { nextCollectionId =>
Ok(views.html.forms.newCollectionForm(admin, courseId, FormMappings.newCollectionForm.fill(Collection(nextCollectionId, courseId, ""))))
for {
allLanguages <- tableDefs.futureAllLanguages
nextCollectionId <- tableDefs.futureNextCollectionIdInCourse(courseId)
} yield {
val filledForm = FormMappings.newCollectionForm.fill(
CollectionBasics(nextCollectionId, courseId, allLanguages.head.id, allLanguages.head.id, "")
)
Ok(views.html.forms.newCollectionForm(admin, courseId, filledForm))
}
}
def newCollection(courseId: Int): EssentialAction = futureWithUser { admin =>
implicit request =>
def onError: Form[Collection] => Future[Result] = { formWithErrors =>
def onError: Form[CollectionBasics] => Future[Result] = { formWithErrors =>
Future.successful(BadRequest(views.html.forms.newCollectionForm(admin, courseId, formWithErrors)))
}
def onRead: Collection => Future[Result] = { newCollection =>
def onRead: CollectionBasics => Future[Result] = { newCollection =>
tableDefs.futureInsertCollection(newCollection) map {
_ => Redirect(routes.AdminController.courseAdmin(courseId))
}
......
......@@ -74,7 +74,7 @@ class HomeController @Inject()(cc: ControllerComponents, protected val tableDefs
} yield Ok(views.html.collection(user, courseId, collection, flashcardCount, toLearnCount, toRepeatCount))
}
def learn(courseId: Int, collId: Int): EssentialAction = futureWithUserAndCollection(courseId, collId) { (user, course, collection) =>
def learn(courseId: Int, collId: Int, frontToBack: Boolean = true): EssentialAction = futureWithUserAndCollection(courseId, collId) { (user, course, collection) =>
implicit request =>
tableDefs.futureFlashcardsToLearnCount(user, collection).map {
cardsToLearnCount => Ok(views.html.learn(user, Math.min(cardsToLearnCount, 10), Some(course, collection)))
......@@ -85,7 +85,7 @@ class HomeController @Inject()(cc: ControllerComponents, protected val tableDefs
implicit request =>
tableDefs.futureMaybeNextFlashcardToLearn(user, course, collection).map {
case None => NotFound("No Flashcard to learn found")
case Some(fc) => Ok(JsonFormats.flashcardFormat.writes(fc))
case Some(fc) => Ok(JsonFormats.flashcardToAnswerFormat.writes(fc))
}
}
......@@ -100,11 +100,11 @@ class HomeController @Inject()(cc: ControllerComponents, protected val tableDefs
implicit request =>
tableDefs.futureMaybeNextFlashcardToRepeat(user).map {
case None => NotFound("There has been an error?")
case Some(fc) => Ok(JsonFormats.flashcardFormat.writes(fc))
case Some(fc) => Ok(JsonFormats.flashcardToAnswerFormat.writes(fc))
}
}
def checkSolution(): EssentialAction = futureWithUser { user =>
def checkSolution: EssentialAction = futureWithUser { user =>
implicit request =>
request.body.asJson.flatMap(json => JsonFormats.solutionFormat.reads(json).asOpt) match {
case None => Future.successful(BadRequest(JsString("Could not read solution...")))
......@@ -114,16 +114,17 @@ class HomeController @Inject()(cc: ControllerComponents, protected val tableDefs
case None => ???
case Some(flashcard) =>
tableDefs.futureUserAnswerForFlashcard(user, flashcard).flatMap { maybePreviousAnswer: Option[UserAnsweredFlashcard] =>
tableDefs.futureUserAnswerForFlashcard(user, flashcard, solution.frontToBack).flatMap {
maybePreviousAnswer: Option[UserAnsweredFlashcard] =>
Corrector.completeCorrect(user, solution, flashcard, maybePreviousAnswer) match {
case Failure(exception) => Future.successful(BadRequest(exception.getMessage))
case Success((corrResult, newAnswer)) =>
Corrector.completeCorrect(user, solution, flashcard, maybePreviousAnswer) match {
case Failure(exception) => Future.successful(BadRequest(exception.getMessage))
case Success((corrResult, newAnswer)) =>
tableDefs.futureInsertOrUpdateUserAnswer(newAnswer).map { _ =>
Ok(JsonFormats.completeCorrectionResultFormat.writes(corrResult))
}
}
tableDefs.futureInsertOrUpdateUserAnswer(newAnswer).map { _ =>
Ok(JsonFormats.completeCorrectionResultFormat.writes(corrResult))
}
}
}
}
......
package model
final case class CollectionBasics(collectionId: Int, courseId: Int, frontLanguageId: Int, backLanguageId: Int, name: String)
final case class FlashcardToAnswer(
cardId: Int,
collId: Int,
courseId: Int,
cardType: CardType,
front: String,
frontHint: Option[String],
frontToBack: Boolean,
blanksAnswerFragments: Seq[BlanksAnswerFragment] = Seq.empty,
choiceAnswers: Seq[ChoiceAnswer] = Seq.empty
)
......@@ -5,6 +5,8 @@ object Consts {
val answerName : String = "answer"
val answerSelectionName: String = "answerSelection"
val backName: String = "back"
val cardTypeName : String = "cardType"
val charName : String = "char"
val correctName : String = "correct"
......@@ -13,6 +15,8 @@ object Consts {
val excelFileName: String = "excelFile"
val frontName: String = "front"
val idName : String = "id"
val indexName: String = "index"
......
......@@ -2,7 +2,14 @@ package model
import model.levenshtein.EditOperation
final case class Solution(cardId: Int, collId: Int, courseId: Int, solution: String, selectedAnswers: Seq[Int])
final case class Solution(
cardId: Int,
collId: Int,
courseId: Int,
solution: String,
selectedAnswers: Seq[Int],
frontToBack: Boolean
)
final case class CorrectionResult(
correct: Boolean,
......
......@@ -27,23 +27,23 @@ object Corrector {
matchAnswerIds(selectedAnswerIds, correctAnswerIds)
}
private def correctFlashcard(completeFlashcard: Flashcard, solution: Solution): CorrectionResult = completeFlashcard.cardType match {
private def correctFlashcard(flashcard: Flashcard, solution: Solution): CorrectionResult = flashcard.cardType match {
case CardType.Vocable | CardType.Text =>
val editOperations = Levenshtein.calculateBacktrace(solution.solution, completeFlashcard.meaning)
val sampleSolution = if(solution.frontToBack) flashcard.back else flashcard.front
val editOperations = Levenshtein.calculateBacktrace(solution.solution, sampleSolution)
CorrectionResult(editOperations.isEmpty, operations = editOperations)
case CardType.Blank =>
val correct = correctBlanksFlashcard(completeFlashcard, solution)
val correct = correctBlanksFlashcard(flashcard, solution)
CorrectionResult(correct)
case CardType.Choice =>
val answerSelectionResult = correctChoiceFlashcard(completeFlashcard, solution)
val answerSelectionResult = correctChoiceFlashcard(flashcard, solution)
CorrectionResult(answerSelectionResult.isCorrect, answersSelection = Some(answerSelectionResult))
}
def completeCorrect(user: User, solution: Solution, flashcard: Flashcard, maybePreviousDbAnswer: Option[UserAnsweredFlashcard]): Try[(CorrectionResult, UserAnsweredFlashcard)] = {
val correctionResult = correctFlashcard(flashcard, solution)
val isCorrect = correctionResult.correct
......@@ -55,7 +55,7 @@ object Corrector {
val newTries = 0
Success((
correctionResult.copy(newTriesCount = newTries),
UserAnsweredFlashcard(user.username, flashcard.cardId, flashcard.collId, flashcard.courseId, bucket = 0, today, isCorrect, tries = newTries)
UserAnsweredFlashcard(user.username, flashcard.cardId, flashcard.collId, flashcard.courseId, bucket = 0, today, isCorrect, tries = newTries, solution.frontToBack)
))
case Some(oldAnswer) =>
......@@ -64,7 +64,7 @@ object Corrector {
val isTryInNewBucket = daysSinceLastAnswer >= Math.pow(3, oldAnswer.bucket)
if (!isTryInNewBucket && oldAnswer.tries >= 2) {
if (!isTryInNewBucket && (oldAnswer.correct || oldAnswer.tries >= 2)) {
Failure(new Exception("More than 2 tries already..."))
} else {
val newBucket = Math.min(if (isCorrect) oldAnswer.bucket + 1 else oldAnswer.bucket, maxBucketId)
......
package model
import model.Consts._
import play.api.data.Form
import play.api.data.Forms._
import Consts._
final case class LoginFormValues(username: String, password: String)
final case class RegisterFormValues(username: String, pw: String, pwRepeat: String)
final case class LtiToolProxyRegistrationRequestFormValues(ltiMessageType: String, ltiVersion: String, regkey: String,
regPassword: String, tcProfileUrl: String, launchPresentationReturnUrl: String
)
final case class LtiToolProxyRegistrationRequestFormValues(
ltiMessageType: String,
ltiVersion: String,
regkey: String,
regPassword: String,
tcProfileUrl: String,
launchPresentationReturnUrl: String
)
final case class LtiFormValues(username: String, courseIdentifier: String, courseName: String)
......@@ -51,12 +56,14 @@ object FormMappings {
)(Course.apply)(Course.unapply)
)
val newCollectionForm: Form[Collection] = Form(
val newCollectionForm: Form[CollectionBasics] = Form(
mapping(
idName -> number,
courseIdName -> number,
"frontLanguageId" -> number,
"backLanguageId" -> number,
nameName -> nonEmptyText
)(Collection.apply)(Collection.unapply)
)(CollectionBasics.apply)(CollectionBasics.unapply)
)
val ltiToolProxyRegistrationRequestForm: Form[LtiToolProxyRegistrationRequestFormValues] = Form(
......
......@@ -31,6 +31,6 @@ object JsonFormats {
private implicit val blanksAnswerFragmentFormat: Format[BlanksAnswerFragment] = Json.format[BlanksAnswerFragment]
val flashcardFormat: Format[Flashcard] = Json.format[Flashcard]
val flashcardToAnswerFormat: Format[FlashcardToAnswer] = Json.format[FlashcardToAnswer]
}
......@@ -17,13 +17,14 @@ final case class UserPassword(username: String, pwHash: String)
final case class Course(id: Int, shortName: String, name: String)
final case class Collection(id: Int, courseId: Int, name: String)
final case class Language(id: Int, name: String)
final case class Collection(id: Int, courseId: Int, startLanguage: Language, targetLanguage: Language, name: String)
// User <-> Course
final case class UserInCourse(username: String, courseId: Int)
// Flashcards
sealed trait CardType extends EnumEntry
......@@ -45,10 +46,10 @@ case object CardType extends PlayEnum[CardType] {
final case class Flashcard(
cardId: Int, collId: Int, courseId: Int,
cardType: CardType,
question: String,
questionHint: Option[String] = None,
meaning: String = "",
meaningHint: Option[String] = None,
front: String,
frontHint: Option[String] = None,
back: String = "",
backHint: Option[String] = None,
blanksAnswers: Seq[BlanksAnswerFragment] = Seq.empty,
choiceAnswers: Seq[ChoiceAnswer] = Seq.empty
) {
......@@ -94,7 +95,7 @@ final case class FlashcardIdentifier(cardId: Int, collId: Int, courseId: Int) {
// User answered flashcard
final case class UserAnsweredFlashcard(username: String, cardId: Int, collId: Int, courseId: Int, bucket: Int, dateAnswered: LocalDate, correct: Boolean, tries: Int) {
final case class UserAnsweredFlashcard(username: String, cardId: Int, collId: Int, courseId: Int, bucket: Int, dateAnswered: LocalDate, correct: Boolean, tries: Int, frontToBack: Boolean) {
// def cardIdentifier: FlashcardIdentifier = FlashcardIdentifier(cardId, collId, courseId)
......
......@@ -22,6 +22,8 @@ trait CoursesCollectionsFlashcardsTableDefs
protected val coursesTQ: TableQuery[CoursesTable] = TableQuery[CoursesTable]
protected val languagesTQ: TableQuery[LanguagesTable] = TableQuery[LanguagesTable]
protected val collectionsTQ: TableQuery[CollectionsTable] = TableQuery[CollectionsTable]
protected val flashcardsTQ: TableQuery[FlashcardsTable] = TableQuery[FlashcardsTable]
......@@ -53,12 +55,27 @@ trait CoursesCollectionsFlashcardsTableDefs
}
class CollectionsTable(tag: Tag) extends Table[Collection](tag, "collections") {
class LanguagesTable(tag: Tag) extends Table[Language](tag, "languages") {
def id: Rep[Int] = column[Int](idName, O.PrimaryKey)
def name: Rep[String] = column[String](nameName)
override def * : ProvenShape[Language] = (id, name) <> (Language.tupled, Language.unapply)
}
class CollectionsTable(tag: Tag) extends Table[CollectionBasics](tag, "collections") {
def id: Rep[Int] = column[Int](idName)
def courseId: Rep[Int] = column[Int]("course_id")
def frontLanguageId: Rep[Int] = column[Int]("front_language_id")
def backLanguageId: Rep[Int] = column[Int]("back_language_id")
def name: Rep[String] = column[String](nameName)
......@@ -66,8 +83,12 @@ trait CoursesCollectionsFlashcardsTableDefs
def courseFk: ForeignKeyQuery[CoursesTable, Course] = foreignKey("coll_course_fk", courseId, coursesTQ)(_.id)
def frontLanguageFk: ForeignKeyQuery[LanguagesTable, Language] = foreignKey("coll_front_lang_fk", frontLanguageId, languagesTQ)(_.id)
def backLanguageFk: ForeignKeyQuery[LanguagesTable, Language] = foreignKey("coll_back_lang_fk", backLanguageId, languagesTQ)(_.id)
override def * : ProvenShape[Collection] = (id, courseId, name) <> (Collection.tupled, Collection.unapply)
override def * : ProvenShape[CollectionBasics] = (id, courseId, frontLanguageId, backLanguageId, name) <> (CollectionBasics.tupled, CollectionBasics.unapply)
}
......@@ -82,18 +103,18 @@ trait CoursesCollectionsFlashcardsTableDefs
def flashcardType: Rep[CardType] = column[CardType]("flash_card_type")
def question: Rep[String] = column[String](questionName)
def question: Rep[String] = column[String](frontName)
def questionHint: Rep[Option[String]] = column[Option[String]]("question_hint")
def questionHint: Rep[Option[String]] = column[Option[String]]("front_hint")
def meaning: Rep[String] = column[String](meaningName)
def meaning: Rep[String] = column[String](backName)
def meaningHint: Rep[Option[String]] = column[Option[String]]("meaning_hint")
def meaningHint: Rep[Option[String]] = column[Option[String]]("back_hint")
def pk: PrimaryKey = primaryKey("fc_pk", (id, collId, courseId))
def collFk: ForeignKeyQuery[CollectionsTable, Collection] = foreignKey("fc_coll_fk", (collId, courseId), collectionsTQ)(coll => (coll.id, coll.courseId))
def collFk: ForeignKeyQuery[CollectionsTable, CollectionBasics] = foreignKey("fc_coll_fk", (collId, courseId), collectionsTQ)(coll => (coll.id, coll.courseId))
override def * : ProvenShape[DBFlashcard] = (id, collId, courseId, flashcardType, question, questionHint, meaning, meaningHint) <> (DBFlashcard.tupled, DBFlashcard.unapply)
......
package model.persistence
import model.{BlanksAnswerFragment, ChoiceAnswer, Collection, Course, Flashcard}
import model.{BlanksAnswerFragment, ChoiceAnswer, Collection, CollectionBasics, Course, Flashcard, Language}
import scala.concurrent.Future
......@@ -32,42 +32,54 @@ trait CoursesCollectionsFlashcardsTableQueries {
def futureInsertCourse(course: Course): Future[Boolean] = db.run(coursesTQ += course).transform(_ == 1, identity)
def futureAllCollectionsInCourse(courseId: Int): Future[Seq[Collection]] =
db.run(collectionsTQ.filter(_.courseId === courseId).result)
def futureAllLanguages: Future[Seq[Language]] = db.run(languagesTQ.result)
def futureCollectionById(courseId: Int, collId: Int): Future[Option[Collection]] =
db.run(collectionsTQ.filter { coll => coll.id === collId && coll.courseId === courseId }.result.headOption)
private lazy val completeCollectionTQ = collectionsTQ
.join(languagesTQ).on(_.frontLanguageId === _.id)
.join(languagesTQ).on(_._1.backLanguageId === _.id)
.map { case ((dbColl, frontLang), backLang) => (dbColl, frontLang, backLang) }
def futureInsertCollection(collection: Collection): Future[Boolean] = db.run(collectionsTQ += collection).transform(_ == 1, identity)
def futureAllCollectionsInCourse(courseId: Int): Future[Seq[Collection]] = db.run(
completeCollectionTQ
.filter { case (dbColl, _, _) => dbColl.courseId === courseId }
.result
).map(_.map(PersistenceModels.collFromDbColl))
private def blanksAnswersForDbFlashcard(dbfc: DBFlashcard): Future[Seq[BlanksAnswerFragment]] = {
val blanksAnswersForDbFlashcardQuery = blanksAnswersTQ.filter {
ba => ba.cardId === dbfc.cardId && ba.collId === dbfc.collId
}.result
def futureCollectionById(courseId: Int, collId: Int): Future[Option[Collection]] = db.run(
completeCollectionTQ
.filter { case (coll, _, _) => coll.id === collId && coll.courseId === courseId }
.result.headOption
).map(_.map(PersistenceModels.collFromDbColl))
db.run(blanksAnswersForDbFlashcardQuery)
}
private def choiceAnswersForDbFlashcard(dbfc: DBFlashcard): Future[Seq[ChoiceAnswer]] = {
def futureInsertCollection(collection: CollectionBasics): Future[Boolean] =
db.run(collectionsTQ += collection).transform(_ == 1, identity)
val dbChoiceAnswersForDbFlashcardQuery = choiceAnswersTQ.filter {
ca => ca.cardId === dbfc.cardId && ca.collId === dbfc.collId
}.result
protected def blanksAnswersForFlashcard(cardId: Int, collId: Int, courseId: Int): Future[Seq[BlanksAnswerFragment]] = db.run(
blanksAnswersTQ
.filter { ba => ba.cardId === cardId && ba.collId === collId && ba.courseId === courseId }
.result
)
db.run(dbChoiceAnswersForDbFlashcardQuery)
}
protected def choiceAnswersForFlashcard(cardId: Int, collId: Int, courseId: Int): Future[Seq[ChoiceAnswer]] = db.run(
choiceAnswersTQ
.filter { ca => ca.cardId === cardId && ca.collId === collId && ca.courseId === courseId }
.result
)
def futureFlashcardsForCollection(collection: Collection): Future[Seq[Flashcard]] = {
val dbFlashcardsForFollQuery = flashcardsTQ.filter(_.collId === collection.id).result
val dbFlashcardsForFollQuery = flashcardsTQ
.filter { fc => fc.collId === collection.id && fc.courseId === collection.courseId }
.result
db.run(dbFlashcardsForFollQuery) flatMap { dbFlashcards: Seq[DBFlashcard] =>
Future.sequence(dbFlashcards map { dbFlashcard =>
Future.sequence(dbFlashcards.map {
case DBFlashcard(cardId, collId, courseId, cardType, front, frontHint, back, backHint) =>
for {
choiceAnswers <- choiceAnswersForDbFlashcard(dbFlashcard)
blanksAnswers <- blanksAnswersForDbFlashcard(dbFlashcard)
} yield PersistenceModels.dbFlashcardToFlashcard(DBCompleteFlashcard(dbFlashcard, choiceAnswers, blanksAnswers))
for {
choiceAnswers <- choiceAnswersForFlashcard(cardId, collId, courseId)
blanksAnswers <- blanksAnswersForFlashcard(cardId, collId, courseId)
} yield Flashcard(cardId, collId, courseId, cardType, front, frontHint, back, backHint, blanksAnswers, choiceAnswers)
})
}
......@@ -78,13 +90,14 @@ trait CoursesCollectionsFlashcardsTableQueries {
fc => fc.id === cardId && fc.collId === collId && fc.courseId === courseId
}.result.headOption
db.run(dbFlashcardByIdQuery) flatMap {
case None => Future.successful(None)
case Some(dbFlashcard: DBFlashcard) =>
db.run(dbFlashcardByIdQuery).flatMap {
case None => Future.successful(None)
case Some(DBFlashcard(_, _, _, cardType, front, frontHint, back, backHint)) =>
for {
choiceAnswersForDBFlashcard <- choiceAnswersForDbFlashcard(dbFlashcard)
blanksAnswersForDbFlashcard <- blanksAnswersForDbFlashcard(dbFlashcard)
} yield Some(PersistenceModels.dbFlashcardToFlashcard(DBCompleteFlashcard(dbFlashcard, choiceAnswersForDBFlashcard, blanksAnswersForDbFlashcard)))
choiceAnswersForDBFlashcard <- choiceAnswersForFlashcard(cardId, collId, courseId)
blanksAnswersForDbFlashcard <- blanksAnswersForFlashcard(cardId, collId, courseId)
} yield Some(Flashcard(cardId, collId, courseId, cardType, front, frontHint, back, backHint, blanksAnswersForDbFlashcard, choiceAnswersForDBFlashcard))
}
}
......
......@@ -2,27 +2,25 @@ package model.persistence
import model._
object PersistenceModels {
def collFromDbColl(dbValues: (CollectionBasics, Language, Language)): Collection = dbValues match {
case (dbColl, frontLang, backLang) => Collection(dbColl.collectionId, dbColl.courseId, frontLang, backLang, dbColl.name)
}
def dbCollFromColl(collection: Collection): CollectionBasics = ???
def flashcardToDbFlashcard(fc: Flashcard): DBCompleteFlashcard = DBCompleteFlashcard(
flashcard = DBFlashcard(fc.cardId, fc.collId, fc.courseId, fc.cardType, fc.question, fc.questionHint, fc.meaning, fc.meaningHint),
flashcard = DBFlashcard(fc.cardId, fc.collId, fc.courseId, fc.cardType, fc.front, fc.frontHint, fc.back, fc.backHint),
choiceAnswers = fc.choiceAnswers, blanksAnswers = fc.blanksAnswers
)
def dbFlashcardToFlashcard(dbfc: DBCompleteFlashcard): Flashcard = dbfc match {