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

Initial commit, Dateien teilweise von Flask-Prototyp

parents
logs
target
/.idea
/.idea_modules
/.classpath
/.project
/.settings
/RUNNING_PID
-J-XX:MaxMetaspaceSize=1024M
package controllers
import javax.inject.{Inject, Singleton}
import model.{Language, TableDefs}
import play.api.mvc.{AbstractController, ControllerComponents, EssentialAction, Result}
import scala.concurrent.{ExecutionContext, Future}
@Singleton
class HomeController @Inject()(cc: ControllerComponents, protected val tableDefs: TableDefs)(implicit ec: ExecutionContext)
extends AbstractController(cc) with Secured {
private def onNoSuchLanguage(langId: Int): Result = NotFound(s"Es gibt keine Sprache mit der ID $langId")
private def onNoSuchCollection(language: Language, collId: Int): Result =
NotFound(s"Es gibt keine Sammlung mit der ID $collId für die Sprache ${language.name}")
def index: EssentialAction = futureWithUser { user =>
implicit request =>
tableDefs.futureLanguagesForUser(user) map { languages =>
Ok(views.html.myLanguages(user, languages))
}
}
def allLanguages: EssentialAction = futureWithUser { user =>
implicit request =>
tableDefs.futureLanguagesAndUserLearns(user) map { languagesAndUserLearns =>
Ok(views.html.allLanguages(user, languagesAndUserLearns))
}
}
def language(langId: Int): EssentialAction = futureWithUser { user =>
implicit request =>
tableDefs.futureLanguageById(langId) flatMap {
case None => Future(onNoSuchLanguage(langId))
case Some(language) =>
tableDefs.futureCollectionsForLanguage(language) map {
collections => Ok(views.html.language(user, language, collections))
}
}
}
def selectLanguage(langId: Int): EssentialAction = futureWithUser { user =>
implicit request =>
tableDefs.futureLanguageById(langId) flatMap {
case None => Future(onNoSuchLanguage(langId))
case Some(language) => tableDefs.activateLanguageForUser(user, language) map {
case true => Redirect(routes.HomeController.allLanguages())
case false => ???
}
}
}
def deselectLanguage(langId: Int): EssentialAction = futureWithUser { user =>
implicit request =>
tableDefs.futureLanguageById(langId) flatMap {
case None => Future(onNoSuchLanguage(langId))
case Some(language) => tableDefs.deactivateLanguageForUser(user, language) map {
case true => Redirect(routes.HomeController.allLanguages())
case false => ???
}
}
}
def collection(langId: Int, collId: Int): EssentialAction = futureWithUser { user =>
implicit request =>
tableDefs.futureLanguageById(langId) flatMap {
case None => Future(onNoSuchLanguage(langId))
case Some(language) =>
tableDefs.futureCollectionById(language, collId) flatMap {
case None => Future(onNoSuchCollection(language, collId))
case Some(collection) =>
for {
flashcardCount <- tableDefs.futureFlashcardCountForCollection(collection)
toLearnCount <- Future(-1)
toRepeatCount <- Future(-1)
} yield Ok(views.html.collection(user, language, collection, flashcardCount, toLearnCount, toRepeatCount))
}
}
}
}
package controllers
import com.github.t3hnar.bcrypt._
import javax.inject._
import model.RegisterFormValues
import model.Consts._
import model._
import play.api.data.Form
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import play.api.mvc._
import slick.jdbc.JdbcProfile
import scala.concurrent.{ExecutionContext, Future}
import scala.language.postfixOps
class LoginController @Inject()(cc: ControllerComponents, val dbConfigProvider: DatabaseConfigProvider, val tableDefs: TableDefs)(implicit ec: ExecutionContext)
extends AbstractController(cc) with HasDatabaseConfigProvider[JdbcProfile] with play.api.i18n.I18nSupport {
def registerForm: Action[AnyContent] = Action {
implicit request => Ok(views.html.forms.registerForm(FormMappings.registerValuesForm))
}
def register: Action[AnyContent] = Action.async { implicit request =>
val onError: Form[RegisterFormValues] => Future[Result] = { _ =>
Future(BadRequest("There has been an error in your form..."))
}
val onRead: RegisterFormValues => Future[Result] = { credentials =>
val newUser = User(credentials.username, credentials.name)
val pwHash = UserPassword(credentials.username, credentials.pw.bcrypt)
tableDefs.futureSaveUser(newUser) flatMap {
case false => Future(BadRequest("Could not save user!"))
case true =>
tableDefs.savePwHash(pwHash) map {
_ => Redirect(routes.LoginController.loginForm())
// Ok(views.html.registered.render(credentials.username))
}
}
}
FormMappings.registerValuesForm.bindFromRequest.fold(onError, onRead)
}
def login: Action[AnyContent] = Action.async { implicit request =>
val onError: Form[LoginFormValues] => Future[Result] = { formWithErrors =>
Future(BadRequest(views.html.forms.loginForm(formWithErrors)))
}
val onRead: LoginFormValues => Future[Result] = { credentials =>
val futureUserAndPwHash: Future[(Option[User], Option[UserPassword])] = for {
user <- tableDefs.futureUserByUserName(credentials.username)
pwHash <- tableDefs.futurePwHashForUser(credentials.username)
} yield (user, pwHash)
futureUserAndPwHash map {
case (None, _) => Redirect(controllers.routes.LoginController.register())
case (Some(_), None) => BadRequest("Cannot change password!")
case (Some(user), Some(pwHash)) =>
if (credentials.password isBcrypted pwHash.pwHash) {
Redirect(controllers.routes.HomeController.index()).withSession(idName -> user.username)
} else {
Ok(views.html.forms.loginForm(FormMappings.loginValuesForm.fill(credentials)))
}
}
}
FormMappings.loginValuesForm.bindFromRequest.fold(onError, onRead)
}
def loginForm: Action[AnyContent] = Action {
implicit request => Ok(views.html.forms.loginForm(FormMappings.loginValuesForm))
}
def logout: Action[AnyContent] = Action {
implicit request => Redirect(routes.LoginController.loginForm()).withNewSession
}
}
\ No newline at end of file
package controllers
import model.User
import model.Consts.idName
import model.TableDefs
import play.api.mvc._
import scala.concurrent.{ExecutionContext, Future}
trait Secured {
self: AbstractController =>
protected val tableDefs: TableDefs
private def username(request: RequestHeader): Option[String] = request.session.get(idName)
private def onUnauthorized(request: RequestHeader): Result = Redirect(controllers.routes.LoginController.loginForm()).withNewSession
private def futureOnUnauthorized(request: RequestHeader)(implicit ec: ExecutionContext): Future[Result] =
Future(onUnauthorized(request))
// private def onInsufficientPrivileges(request: RequestHeader): Result = Redirect(routes.HomeController.index()).flashing("msg" -> "You do not have sufficient privileges!")
// private def futureOnInsufficientPrivileges(request: RequestHeader)(implicit ec: ExecutionContext): Future[Result] =
// Future(onInsufficientPrivileges(request))
private def withAuth(f: => String => Request[AnyContent] => Future[Result]): EssentialAction =
Security.Authenticated(username, onUnauthorized)(user => controllerComponents.actionBuilder.async(request => f(user)(request)))
private def withAuthWithBodyParser[A](bodyParser: BodyParser[A])(f: => String => Request[A] => Future[Result]): EssentialAction =
Security.Authenticated(username, onUnauthorized)(user => controllerComponents.actionBuilder.async(bodyParser)(request => f(user)(request)))
def withUser(f: User => Request[AnyContent] => Result)(implicit ec: ExecutionContext): EssentialAction = withAuth { username =>
implicit request => {
tableDefs.futureUserByUserName(username) map {
case Some(user) => f(user)(request)
case None => onUnauthorized(request)
}
}
}
def futureWithUser(f: User => Request[AnyContent] => Future[Result])(implicit ec: ExecutionContext): EssentialAction = withAuth { username =>
implicit request =>
tableDefs.futureUserByUserName(username) flatMap {
case Some(user) => f(user)(request)
case None => futureOnUnauthorized(request)
}
}
def futureWithUserWithBodyParser[A](bodyParser: BodyParser[A])(f: User => Request[A] => Future[Result])(implicit ec: ExecutionContext): EssentialAction =
withAuthWithBodyParser(bodyParser) { username =>
implicit request =>
tableDefs.futureUserByUserName(username) flatMap {
case Some(user) => f(user)(request)
case None => futureOnUnauthorized(request)
}
}
// def withAdmin(f: User => Request[AnyContent] => Result)(implicit ec: ExecutionContext): EssentialAction = withAuth { username =>
// implicit request =>
// tableDefs.userByName(username) map {
// case Some(user) =>
// if (user.isAdmin) f(user)(request)
// else onInsufficientPrivileges(request)
// case None => onUnauthorized(request)
// }
// }
// def futureWithAdmin(f: User => Request[AnyContent] => Future[Result])(implicit ec: ExecutionContext): EssentialAction = withAuth { username =>
// implicit request =>
// tableDefs.userByName(username) flatMap {
// case Some(user) =>
// if (user.isAdmin) f(user)(request)
// else futureOnInsufficientPrivileges(request)
// case None => futureOnUnauthorized(request)
// }
// }
}
\ No newline at end of file
package model
object Consts {
val answerName: String = "answer"
val correctName : String = "correct"
val correctnessName: String = "correctness"
val idName: String = "id"
val meaningName: String = "meaning"
val nameName: String = "name"
val pwName: String = "pw"
val questionName: String = "question"
val repeatPwName: String = "repeatPw"
val triesName: String = "tries"
val usernameName: String = "username"
}
package model
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, name: String, pw: String, pwRepeat: String)
object FormMappings {
val loginValuesForm: Form[LoginFormValues] = Form(
mapping(
usernameName -> nonEmptyText,
pwName -> nonEmptyText
)(LoginFormValues.apply)(LoginFormValues.unapply)
)
val registerValuesForm: Form[RegisterFormValues] = Form(
mapping(
usernameName -> nonEmptyText,
nameName -> nonEmptyText,
pwName -> nonEmptyText,
repeatPwName -> nonEmptyText
)(RegisterFormValues.apply)(RegisterFormValues.unapply)
)
}
package model
import enumeratum.{EnumEntry, PlayEnum}
import scala.collection.immutable
// User and password
final case class User(username: String, name: String)
final case class UserPassword(username: String, pwHash: String)
// Languages and Collections
final case class Language(id: Int, shortName: String, name: String)
final case class Collection(id: Int, langId: Int, name: String)
final case class Bucket(id: Int, distanceDays: Int)
// Flashcards
sealed trait CardType extends EnumEntry
case object CardType extends PlayEnum[CardType] {
val values: immutable.IndexedSeq[CardType] = findValues
case object VocableCard extends CardType
case object TextCard extends CardType
case object SingleChoice extends CardType
case object MultipleChoice extends CardType
}
sealed trait Correctness extends EnumEntry
case object Correctness extends PlayEnum[Correctness] {
val values: immutable.IndexedSeq[Correctness] = findValues
case object Correct extends Correctness
case object Optional extends Correctness
case object Wrong extends Correctness
}
final case class Flashcard(id: Int, collId: Int, langId: Int, cardType: CardType, question: String, meaning: Option[String])
final case class ChoiceAnswer(id: Int, cardId: Int, collId: Int, langId: Int, answer: String, correctness: Correctness)
\ No newline at end of file
package model
import javax.inject.Inject
import model.Consts._
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.jdbc.JdbcProfile
import slick.lifted.{ForeignKeyQuery, PrimaryKey, ProvenShape}
import scala.concurrent.{ExecutionContext, Future}
class TableDefs @Inject()(override protected val dbConfigProvider: DatabaseConfigProvider)(implicit ec: ExecutionContext)
extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
// Table queries
private val usersTQ : TableQuery[UsersTable] = TableQuery[UsersTable]
private val userPasswordsTQ: TableQuery[UserPasswordsTable] = TableQuery[UserPasswordsTable]
private val languagesTQ : TableQuery[LanguagesTable] = TableQuery[LanguagesTable]
private val collectionsTQ: TableQuery[CollectionsTable] = TableQuery[CollectionsTable]
private val flashcardsTQ : TableQuery[FlashcardsTable] = TableQuery[FlashcardsTable]
private val userLearnsLanguageTQ: TableQuery[UserLearnsLanguageTable] = TableQuery[UserLearnsLanguageTable]
// Queries
def futureUserByUserName(username: String): Future[Option[User]] = db.run(usersTQ.filter(_.username === username).result.headOption)
def futurePwHashForUser(username: String): Future[Option[UserPassword]] = db.run(userPasswordsTQ.filter(_.username === username).result.headOption)
def futureSaveUser(user: User): Future[Boolean] = db.run(usersTQ += user) transform(_ == 1, identity)
def savePwHash(userPassword: UserPassword): Future[Boolean] = db.run(userPasswordsTQ += userPassword) transform(_ == 1, identity)
def futureLanguageById(langId: Int): Future[Option[Language]] = db.run(languagesTQ.filter(_.id === langId).result.headOption)
def futureLanguagesForUser(user: User): Future[Seq[Language]] = {
val query = userLearnsLanguageTQ
.join(languagesTQ).on(_.langId === _.id)
.filter(_._1.username === user.username)
.map(_._2)
.result
db.run(query)
}
def futureUserLearnsLanguage(user: User, language: Language): Future[Boolean] = db.run(userLearnsLanguageTQ.filter {
ull => ull.username === user.username && ull.langId === language.id
}.result.headOption.map(_.isDefined))
def futureLanguagesAndUserLearns(user: User): Future[Seq[(Language, Boolean)]] = db.run(languagesTQ.result) flatMap { languages =>
Future.sequence {
languages map { lang =>
futureUserLearnsLanguage(user, lang) map {
userLearnsLang => (lang, userLearnsLang)
}
}
}
}
def activateLanguageForUser(user: User, language: Language): Future[Boolean] =
db.run(userLearnsLanguageTQ += (user.username, language.id)) transform(_ == 1, identity)
def deactivateLanguageForUser(user: User, language: Language): Future[Boolean] =
db.run(userLearnsLanguageTQ.filter {
ull => ull.username === user.username && ull.langId === language.id
}.delete) transform(_ == 1, identity)
def futureCollectionsForLanguage(language: Language): Future[Seq[Collection]] =
db.run(collectionsTQ.filter(_.langId === language.id).result)
def futureCollectionById(language: Language, collId: Int): Future[Option[Collection]] =
db.run(collectionsTQ.filter(coll => coll.langId === language.id && coll.id === collId).result.headOption)
def futureFlashcardCountForCollection(collection: Collection): Future[Int] =
db.run(flashcardsTQ.filter(fc => fc.collId === collection.id && fc.langId === collection.langId).size.result)
// Column types
private implicit val cardTypeColumnType: BaseColumnType[CardType] =
MappedColumnType.base[CardType, String](_.entryName, CardType.withNameInsensitive)
private implicit val correctnessColumnType: BaseColumnType[Correctness] =
MappedColumnType.base[Correctness, String](_.entryName, Correctness.withNameInsensitive)
// Table definitions
class UsersTable(tag: Tag) extends Table[User](tag, "users") {
def username: Rep[String] = column[String](usernameName, O.PrimaryKey)
def name: Rep[String] = column[String](nameName)
override def * : ProvenShape[User] = (username, name) <> (User.tupled, User.unapply)
}
class UserPasswordsTable(tag: Tag) extends Table[UserPassword](tag, "user_passwords") {
def username: Rep[String] = column[String](usernameName, O.PrimaryKey)
def pwHash: Rep[String] = column[String]("password_hash")
override def * : ProvenShape[UserPassword] = (username, pwHash) <> (UserPassword.tupled, UserPassword.unapply)
}
class LanguagesTable(tag: Tag) extends Table[Language](tag, "languages") {
def id: Rep[Int] = column[Int](idName, O.PrimaryKey)
def shortName: Rep[String] = column[String]("short_name")
def name: Rep[String] = column[String](nameName)
override def * : ProvenShape[Language] = (id, shortName, name) <> (Language.tupled, Language.unapply)
}
class UserLearnsLanguageTable(tag: Tag) extends Table[(String, Int)](tag, "user_learns_language") {
def username: Rep[String] = column[String](usernameName)
def langId: Rep[Int] = column[Int]("lang_id")
def pk: PrimaryKey = primaryKey("ull_pk", (username, langId))
def userFk: ForeignKeyQuery[UsersTable, User] = foreignKey("ull_user_fk", username, usersTQ)(_.username)
def langFk: ForeignKeyQuery[LanguagesTable, Language] = foreignKey("ull_lang_fk", langId, languagesTQ)(_.id)
override def * : ProvenShape[(String, Int)] = (username, langId)
}
class BucketsTable(tag: Tag) extends Table[Bucket](tag, "buckets") {
def id: Rep[Int] = column[Int](idName, O.PrimaryKey)
def distanceDays: Rep[Int] = column[Int]("distance_days")
def * : ProvenShape[Bucket] = (id, distanceDays) <> (Bucket.tupled, Bucket.unapply)
}
class CollectionsTable(tag: Tag) extends Table[Collection](tag, "collections") {
def id: Rep[Int] = column[Int](idName)
def langId: Rep[Int] = column[Int]("lang_id")
def name: Rep[String] = column[String](nameName)
def pk: PrimaryKey = primaryKey("coll_pk", (id, langId))
def langFk: ForeignKeyQuery[LanguagesTable, Language] = foreignKey("coll_language_fk", langId, languagesTQ)(_.id)
override def * : ProvenShape[Collection] = (id, langId, name) <> (Collection.tupled, Collection.unapply)
}
class FlashcardsTable(tag: Tag) extends Table[Flashcard](tag, "flashcards") {
def id: Rep[Int] = column[Int](idName)
def collId: Rep[Int] = column[Int]("coll_id")
def langId: Rep[Int] = column[Int]("lang_id")
def flashcardType: Rep[CardType] = column[CardType]("flash_card_type")
def question: Rep[String] = column[String](questionName)
def meaning: Rep[Option[String]] = column[Option[String]](meaningName)
def pk: PrimaryKey = primaryKey("fc_pk", (id, collId, langId))
def collFk: ForeignKeyQuery[CollectionsTable, Collection] = foreignKey("fc_coll_fk", (collId, langId), collectionsTQ)(c => (c.id, c.langId))
override def * : ProvenShape[Flashcard] = (id, collId, langId, flashcardType, question, meaning) <> (Flashcard.tupled, Flashcard.unapply)
}
class ChoiceAnswersTable(tag: Tag) extends Table[ChoiceAnswer](tag, "choice_answers") {
def id: Rep[Int] = column[Int](idName)
def cardId: Rep[Int] = column[Int]("card_id")
def collId: Rep[Int] = column[Int]("coll_id")
def langId: Rep[Int] = column[Int]("lang_id")
def answer: Rep[String] = column[String](answerName)
def correctness: Rep[Correctness] = column[Correctness](correctnessName)
def pk: PrimaryKey = primaryKey("ca_pk", (id, cardId, collId, langId))
def cardFk: ForeignKeyQuery[FlashcardsTable, Flashcard] = foreignKey("ca_card_fk", (cardId, collId, langId), flashcardsTQ)(fc => (fc.id, fc.collId, fc.langId))
override def * : ProvenShape[ChoiceAnswer] = (id, cardId, collId, langId, answer, correctness) <> (ChoiceAnswer.tupled, ChoiceAnswer.unapply)