Browse Source

Rewrite

master
Nazar Kalinowski 3 months ago
parent
commit
e651e6efdd
47 changed files with 284 additions and 919 deletions
  1. +13
    -17
      app/controllers/AdminController.scala
  2. +56
    -91
      app/controllers/AuthController.scala
  3. +21
    -31
      app/controllers/FilesController.scala
  4. +11
    -11
      app/controllers/HomeController.scala
  5. +0
    -88
      app/controllers/UserController.scala
  6. +0
    -30
      app/controllers/rest/RolesController.scala
  7. +0
    -44
      app/controllers/rest/UsersController.scala
  8. +0
    -16
      app/entity/ConfirmationCodes.scala
  9. +0
    -45
      app/entity/Roles.scala
  10. +0
    -16
      app/entity/UserRoles.scala
  11. +3
    -32
      app/entity/Users.scala
  12. +0
    -16
      app/forms/LoginForm.scala
  13. +0
    -17
      app/forms/RegisterForm.scala
  14. +16
    -0
      app/forms/SignInForm.scala
  15. +16
    -0
      app/forms/SignUpForm.scala
  16. +3
    -1
      app/services/BCryptPasswordHasher.scala
  17. +0
    -11
      app/services/ConfirmationCodeService.scala
  18. +0
    -9
      app/services/ConfirmationCodeServiceImpl.scala
  19. +0
    -9
      app/services/EmailService.scala
  20. +0
    -17
      app/services/EmailServiceImpl.scala
  21. +1
    -31
      app/services/FileService.scala
  22. +3
    -48
      app/services/FileServiceImpl.scala
  23. +0
    -26
      app/services/dao/RoleDao.scala
  24. +0
    -61
      app/services/dao/SlickRoleDao.scala
  25. +8
    -47
      app/services/dao/SlickUserDao.scala
  26. +3
    -23
      app/services/dao/UserDao.scala
  27. +0
    -28
      app/util/Secure.scala
  28. +20
    -0
      app/utils/Secure.scala
  29. +7
    -6
      app/views/about.scala.html
  30. +4
    -2
      app/views/admin.scala.html
  31. +0
    -14
      app/views/authorization.scala.html
  32. +0
    -14
      app/views/filenotfound.scala.html
  33. +11
    -7
      app/views/files.scala.html
  34. +12
    -9
      app/views/generic/main.scala.html
  35. +5
    -4
      app/views/home.scala.html
  36. +0
    -17
      app/views/profile.scala.html
  37. +0
    -16
      app/views/registration.scala.html
  38. +0
    -25
      app/views/selfprofile.scala.html
  39. +25
    -0
      app/views/signin.scala.html
  40. +25
    -0
      app/views/signup.scala.html
  41. +1
    -7
      build.sbt
  42. +4
    -17
      conf/routes
  43. +1
    -1
      project/build.properties
  44. +0
    -3
      public/stylesheets/filenotfound.css
  45. +0
    -6
      public/stylesheets/fileslist.css
  46. +15
    -0
      public/stylesheets/generic/main.css
  47. +0
    -6
      public/stylesheets/home.css

+ 13
- 17
app/controllers/AdminController.scala View File

@@ -1,34 +1,30 @@
package controllers

import entity.User
import javax.inject.{Inject, Singleton}
import play.api.Logging
import play.api.i18n.{I18nSupport}
import play.api.i18n.I18nSupport
import play.api.mvc._
import services.dao.{RoleDao, UserDao}
import util.Secure
import services.dao.UserDao
import utils.Secure

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

@Singleton
class AdminController @Inject()(val cc: ControllerComponents,
implicit val userDao: UserDao[Future],
implicit val roleDao: RoleDao[Future])
implicit val userDao: UserDao[Future])
extends AbstractController(cc)
with Secure with I18nSupport with Logging {

def admin = Action.async { implicit request: Request[AnyContent] =>
logger.info(s"Admin page has been accessed! IP: ${request.remoteAddress}")
getUser.flatMap {
case Some(user) =>
roleDao.getUserRoles(user.id).map(roles => {
if (roles.exists(_.name.equals("administrator"))) {
Ok(views.html.admin(getUsername.get))
}
else Redirect(routes.HomeController.home())
})
def admin = Action.async { implicit request: Request[_] =>
getUser.map {
case optUser @ Some(_) =>
implicit val implicitOptUser: Option[User] = optUser
Ok(views.html.admin())
case None =>
Future(Redirect(routes.AuthController.authorization()))
Redirect(routes.AuthController.signin()).
flashing("warning" -> "You are not signed-in!")
}
}
}

+ 56
- 91
app/controllers/AuthController.scala View File

@@ -3,130 +3,95 @@ package controllers
import entity.User

import scala.concurrent.ExecutionContext.Implicits.global
import forms.{LoginForm, RegisterForm}
import forms.{SignInForm, SignUpForm}
import javax.inject._
import org.slf4j.{Logger, LoggerFactory}
import play.api.i18n.{I18nSupport, Messages}
import play.api.i18n.I18nSupport
import play.api.mvc._
import services.{ConfirmationCodeService, EmailService, PasswordHasher}
import services.dao.{RoleDao, UserDao}
import util.Secure
import services.PasswordHasher
import services.dao.UserDao
import utils.Secure

import scala.collection.mutable
import scala.concurrent.Future
import scala.util.{Failure, Success}

@Singleton
class AuthController @Inject()(cc: ControllerComponents,
encryptor: PasswordHasher,
codeGenerator: ConfirmationCodeService,
val emailer: EmailService,
userDao: UserDao[Future],
roleDao: RoleDao[Future])
implicit val userDao: UserDao[Future])
extends AbstractController(cc)
with Secure with I18nSupport {

val logger: Logger = LoggerFactory.getLogger(getClass)

val usedEmails: mutable.Set[String] = mutable.Set[String]()
val usedAddresses: mutable.Set[String] = mutable.Set[String]()

def registration(): Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
Ok(views.html.registration(RegisterForm.form))
def signup: Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
Ok(views.html.signup(SignUpForm.form))
}

private def getConfirmationLink(confirmationCode: String)(implicit request: Request[AnyContent]): String =
s"http${if (request.secure) "s" else ""}://${request.host}/confirm/$confirmationCode"

def register = Action.async { implicit request: Request[AnyContent] =>
RegisterForm.form.bindFromRequest.fold(
def signupForm = Action.async { implicit request: Request[AnyContent] =>
SignUpForm.form.bindFromRequest.fold(
formWithErrors => {
logger.debug(s"Registration form has errors")
Future(BadRequest(views.html.registration(formWithErrors)))
Future(BadRequest(views.html.signup(formWithErrors)))
},
userData => {
val RegisterForm.Data(name, email, password) = userData
val address = request.connection.remoteAddressString
if (usedEmails(email)) {
Future(BadRequest("This email address has already been used for registration!"))
} else if (usedAddresses(address)) {
Future(BadRequest("Your ip address has already been used for registration!"))
} else {
val optExistingUser = for {
byName <- userDao.getByName(name)
byEmail <- userDao.getByEmail(email)
} yield byName.orElse(byEmail)
optExistingUser.flatMap {
case Some(_) =>
logger.debug(s"Duplicated user tried to register! Email: '$email' nickname: '$name'")
Future(BadRequest(views.html.registration(RegisterForm.form.fill(userData))))
case None =>
userDao.create(User(0, name, email, encryptor.encrypt(password))).
flatMap(_ => userDao.getIdByName(name).map(_.get)).
map(userId => {
val confirmationCode = codeGenerator.generateConfirmationCode
userDao.setConfirmationCode(userId, confirmationCode)
confirmationCode
}).
transformWith {
case Failure(e) =>
logger.warn("Failed to register a user!", e)
Future(BadRequest(views.html.registration(RegisterForm.form.fill(userData))))
case Success(confirmationCode) =>
emailer.send(email, "Registration on GWMDevelopments",
"If you haven't recently registered on GWMDevelopments you can safely ignore this message.\n" +
s"Confirmation link: ${getConfirmationLink(confirmationCode)}")
usedEmails += email
usedAddresses += address
Future(Redirect(routes.HomeController.home(), SEE_OTHER))
}
}
val SignUpForm.Data(nickname, password) = userData
userDao.getByNickname(nickname).flatMap {
case Some(_) =>
val form = SignUpForm.form.fill(userData).
withGlobalError("User with your nickname is already registered!")
Future(BadRequest(views.html.signup(form)))
case None =>
userDao.create(User(0, nickname, encryptor.encrypt(password))).
transformWith {
case Success(id) =>
Future(Redirect(routes.HomeController.home(), SEE_OTHER).
flashing("info" -> "You have signed-up successfully!"))
case Failure(e) =>
logger.warn("Failed to register a user!", e)
val form = SignUpForm.form.fill(userData).
withGlobalError("Some server error has happened! Please try again.")
Future(BadRequest(views.html.signup(form)))
}
}
}
)
}

def authorization() = Action { implicit request: Request[AnyContent] =>
Ok(views.html.authorization(LoginForm.form))
def signin = Action { implicit request: Request[AnyContent] =>
Ok(views.html.signin(SignInForm.form))
}

def login() = Action.async { implicit request: Request[AnyContent] =>
LoginForm.form.bindFromRequest.fold(
def signinForm = Action.async { implicit request: Request[AnyContent] =>
SignInForm.form.bindFromRequest.fold(
formWithErrors => {
Future(BadRequest(views.html.authorization(formWithErrors)))
Future(BadRequest(views.html.signin(formWithErrors)))
},
userData => userDao.getByLogin(userData.login).map(option => {
val redirectFail = Redirect(routes.AuthController.authorization(), SEE_OTHER).
flashing("error" -> "Wrong user or password")
option.map(user => {
if (encryptor.check(userData.password, user.password)) {
Redirect(routes.HomeController.home(), SEE_OTHER).
withSession("username" -> user.name)
} else redirectFail
}).getOrElse(redirectFail)
})
)
}

def confirmRegistration(confirmationCode: String) = Action.async {
userDao.getByConfirmationCode(confirmationCode).
flatMap {
userData => userDao.getByNickname(userData.nickname).map {
case Some(user) =>
usedEmails -= user.email
userDao.deleteConfirmationCode(user.id).
flatMap(_ => userDao.confirm(user.id)).
transformWith {
case Failure(e) =>
logger.warn("Failed to confirm user!", e)
Future(Ok("Something bad has happened!"))
case Success(_) =>
Future(Ok("Successfully confirmed!"))
}
case None => Future(BadRequest("No confirmationCode like that exists"))
if (!encryptor.check(userData.password, user.password)) {
val form = SignInForm.form.fill(userData).
withGlobalError("Bad user or password")
Ok(views.html.signin(form))
} else if (!user.confirmed) {
val form = SignInForm.form.fill(userData).
withGlobalError("User is not confirmed")
Ok(views.html.signin(form))
} else {
Redirect(routes.HomeController.home()).
withSession("id" -> user.id.toString).
flashing("info" -> "You have signed-in successfully!")
}
case None =>
val form = SignInForm.form.fill(userData).
withGlobalError("Bad user or password")
Ok(views.html.signin(form))
}
)
}

def logout = Action {
Redirect("/").withNewSession
Redirect(routes.HomeController.home()).
withNewSession.
flashing("info" -> "You have logged out!")
}
}

+ 21
- 31
app/controllers/FilesController.scala View File

@@ -2,15 +2,16 @@ package controllers

import java.nio.file.Paths

import entity.{Role, Roles, User}
import entity.User
import javax.inject._
import play.api.i18n.{I18nSupport, Messages}
import play.api.i18n.I18nSupport
import play.api.mvc._
import services.FileService
import services.dao.{RoleDao, UserDao}
import util.Secure
import services.dao.UserDao
import utils.Secure

import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future}
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

object FilesController {

@@ -29,57 +30,46 @@ object FilesController {
}

@Singleton
class FilesController @Inject()(implicit val userDao: UserDao[Future],
implicit val roleDao: RoleDao[Future],
class FilesController @Inject()(cc: ControllerComponents,
fileService: FileService,
cc: ControllerComponents)
implicit val userDao: UserDao[Future])
extends AbstractController(cc)
with I18nSupport
with Secure {

implicit val ec: ExecutionContextExecutor = ExecutionContext.global

def files(name: String) = Action.async { implicit request: Request[AnyContent] =>
isAdministrator.map { showUpload =>
getUser.map { implicit optUser: Option[User] =>
val path = fileService.publicFilesDirectory/name
if (path.exists) {
if (path.isFile) {
Ok.sendFile(path.jfile)
} else {
Ok(views.html.fileslist(fileService.publicFilesDirectory, path.toDirectory, showUpload.getOrElse(false)))
Ok(views.html.files(fileService.publicFilesDirectory, path.toDirectory))
}
} else {
BadRequest(views.html.filenotfound(name))
Redirect(routes.FilesController.files("")).
flashing("warning" -> s"File '$name' is not found!")
}
}
}

def upload() = Action.async(parse.multipartFormData) { implicit request =>
def upload() = Action(parse.multipartFormData) { implicit request =>
request.body.
file("fileToUpload").
map { file =>
isAdministrator.map {
case Some(true) =>
val dataParts = request.body.dataParts
if (!dataParts.isDefinedAt("path")) {
//return BadRequest("")
}
val dataPartsPath = request.body.dataParts("path").headOption
if (dataPartsPath.isEmpty) {
//return BadRequest("")
}
val path = (fileService.publicFilesDirectory/dataPartsPath.head).toString
getId match {
case Some(_) =>
val requestPath = request.body.dataParts.get("path").flatMap(_.headOption).getOrElse("/")
val path = (fileService.publicFilesDirectory / requestPath).toString
val fileName = Paths.get(file.filename).getFileName.toString
file.ref.copyTo(Paths.get(path, fileName), replace = true)
Ok("File uploaded")
case Some(false) =>
Redirect(routes.FilesController.files("")).
flashing("error" -> "You have no permission to upload files")
flashing("info" -> "File has been uploaded successfully!")
case None =>
Redirect(routes.FilesController.files("")).
flashing("error" -> "You need to be authorised to upload files")
flashing("warning" -> "You need to be authorised to upload files!")
}
}.getOrElse(Future(Redirect(routes.FilesController.files("")).
flashing("error" -> "Missing a file to upload")))
}.getOrElse(Redirect(routes.FilesController.files("")).
flashing("error" -> "Missing a file to upload!"))
}
}

+ 11
- 11
app/controllers/HomeController.scala View File

@@ -1,30 +1,30 @@
package controllers

import entity.User
import javax.inject._
import play.api.i18n.I18nSupport
import play.api.mvc._
import services.FileService
import services.dao.{RoleDao, UserDao}
import util.Secure
import scala.concurrent.ExecutionContext.Implicits._
import services.dao.UserDao
import utils.Secure

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

@Singleton
class HomeController @Inject()(val fileService: FileService,
val cc: ControllerComponents,
implicit val userDao: UserDao[Future],
implicit val roleDao: RoleDao[Future])
class HomeController @Inject()(val cc: ControllerComponents,
val fileService: FileService,
implicit val userDao: UserDao[Future])
extends AbstractController(cc)
with Secure with I18nSupport {

def home = Action.async { implicit request: Request[AnyContent] =>
isAdministrator.map { admin =>
Ok(views.html.home(admin.getOrElse(false)))
def home = Action.async { implicit request: Request[_] =>
getUser.map { implicit optUser: Option[User] =>
Ok(views.html.home())
}
}

def about = Action { implicit request: Request[AnyContent] =>
def about = Action { implicit request: Request[_] =>
Ok(views.html.about())
}
}

+ 0
- 88
app/controllers/UserController.scala View File

@@ -1,88 +0,0 @@
package controllers

import entity.User
import javax.imageio.ImageIO
import javax.inject.Inject
import play.api.i18n.I18nSupport
import play.api.mvc.{AbstractController, AnyContent, ControllerComponents, Request}
import services.{FileService, MediumAvatar}
import services.dao.{RoleDao, UserDao}
import util.Secure

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

class UserController @Inject()(val cc: ControllerComponents,
implicit val userDao: UserDao[Future],
implicit val roleDao: RoleDao[Future],
implicit val fileService: FileService)
extends AbstractController(cc)
with Secure with I18nSupport {

def selfProfile = Action.async { implicit request: Request[AnyContent] =>
getUser.flatMap {
case Some(user) => roleDao.getUserRoles(user.id).map { roles =>
val optAvatar = if (fileService.getAvatarFile(user.id, MediumAvatar).exists) Some("/profile/avatar") else None
Ok(views.html.selfprofile(user, roles, optAvatar))
}
case None => Future(BadRequest("You are not authorised, go away!"))
}
}

def userProfileById(id: Long) = userProfile(id.toString, userDao.getById(id))

def userProfileByName(name: String) = userProfile(name, userDao.getByName(name))

private def userProfile(query: String, userF: Future[Option[User]]) = Action.async { implicit request: Request[AnyContent] =>
userF.flatMap {
case Some(user) => roleDao.getUserRoles(user.id).map { roles =>
val optAvatar = if (fileService.getAvatarFile(user.id, MediumAvatar).exists) Some(s"/profile/id/${user.id}/avatar") else None
Ok(views.html.profile(user, roles, optAvatar))
}
case None => Future(BadRequest(s"User $query does not exist, go away!"))
}
}

def selfAvatar = Action.async { implicit request: Request[AnyContent] =>
getUser.map {
case Some(user) =>
val avatarFile = fileService.getAvatarFile(user.id, MediumAvatar)
if (avatarFile.exists) {
Ok.sendFile(avatarFile.jfile)
} else {
BadRequest(s"You have no avatar!")
}
case None =>
BadRequest("Not authorised!")
}
}

def avatar(userId: Long) = Action.async { implicit request: Request[AnyContent] =>
userDao.getById(userId).map {
case Some(user) =>
val avatarFile = fileService.getAvatarFile(userId, MediumAvatar)
if (avatarFile.exists) {
Ok.sendFile(avatarFile.jfile)
} else {
BadRequest(s"User ${user.id} has no avatar!")
}
case None =>
BadRequest("User not found")
}
}

def uploadAvatar() = Action.async(parse.multipartFormData) { implicit request =>
request.body.
file("avatar").
map { file =>
getUser.flatMap {
case Some(user) =>
fileService.setAvatar(user.id, ImageIO.read(file.ref.toFile)).
map(_ => Ok("Avatar uploaded"))
case None =>
Future(BadRequest("You are not authorised"))
}
}.getOrElse(Future(Redirect(routes.HomeController.home()).
flashing("error" -> "Missing an avatar to upload")))
}
}

+ 0
- 30
app/controllers/rest/RolesController.scala View File

@@ -1,30 +0,0 @@
package controllers.rest

import javax.inject.{Inject, Singleton}
import play.api.libs.json.Json
import entity.RolesToJson.writesRole
import play.api.mvc.{AbstractController, AnyContent, ControllerComponents, Request}
import services.dao.RoleDao

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

@Singleton
class RolesController @Inject()(cc: ControllerComponents,
roleDao: RoleDao[Future])
extends AbstractController(cc) {

def roles = Action.async { implicit request: Request[AnyContent] =>
roleDao.getRoles.map(seq => Ok(Json.toJson(seq)))
}

def role(id: Long) = Action.async { implicit request: Request[AnyContent] =>
roleDao.getOptionById(id).map(optRole => {
if (optRole.isDefined) {
Ok(Json.toJson(optRole.get))
} else {
BadRequest("No role with id " + id)
}
})
}
}

+ 0
- 44
app/controllers/rest/UsersController.scala View File

@@ -1,44 +0,0 @@
package controllers.rest

import entity.UsersToJson.writesUser
import entity.UsersToJson.writesUserWithRoles
import javax.inject.{Inject, Singleton}
import play.api.libs.json.Json
import play.api.mvc._
import services.dao.{RoleDao, UserDao}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

@Singleton
class UsersController @Inject()(cc: ControllerComponents,
userDao: UserDao[Future],
roleDao: RoleDao[Future])
extends AbstractController(cc) {

def users() = Action.async { implicit request: Request[AnyContent] =>
userDao.getUsers.map(seq => Ok(Json.toJson(seq)))
}

def user(id: Long, withRoles: Boolean) = Action.async { implicit request: Request[AnyContent] =>
if (withRoles) {
userDao.getById(id).zip(roleDao.getUserRoles(id)).map(tuple => {
val optUser = tuple._1
val roles = tuple._2
if (optUser.isDefined) {
Ok(Json.toJson((optUser.get, roles)))
} else {
BadRequest("No user with id " + id)
}
})
} else {
userDao.getById(id).map(optUser => {
if (optUser.isDefined) {
Ok(Json.toJson(optUser.get))
} else {
BadRequest("No user with id " + id)
}
})
}
}
}

+ 0
- 16
app/entity/ConfirmationCodes.scala View File

@@ -1,16 +0,0 @@
package entity

import slick.jdbc.MySQLProfile.api._
import slick.lifted.{TableQuery, Tag}

object ConfirmationCodes {

val confirmationCodes = TableQuery[ConfirmationCodes]
}

class ConfirmationCodes(tag: Tag) extends Table[(Long, String)](tag, "confirmation_codes") {

def userId = column[Long]("user_id")
def confirmationCode = column[String]("confirmation_code", O.Length(24))
def * = (userId, confirmationCode)
}

+ 0
- 45
app/entity/Roles.scala View File

@@ -1,45 +0,0 @@
package entity

import java.awt.Color

import play.api.libs.json.{Json, Writes}
import slick.jdbc.MySQLProfile.api._
import slick.lifted.TableQuery

object RolesToJson {

implicit val writesRole: Writes[Role] = Writes[Role] {
case Role(id, name, color, priority) =>
Json.obj(
"id" -> id,
"name" -> name,
"color" -> color,
"priority" -> priority
)
}
}

case class Role(id: Long, name: String, color: Int, priority: Int)

object DefaultRoles {

object PremiumUser extends Role(0, "premium", new Color(255, 140, 0).getRGB, 200)
object Moderator extends Role(0, "moderator", Color.BLUE.getRGB, 900)
object Administrator extends Role(0, "administrator", Color.RED.getRGB, 1000)

val list: List[Role] = List(PremiumUser, Moderator, Administrator)
}

object Roles {

val roles = TableQuery[Roles]
}

class Roles(tag: Tag) extends Table[Role](tag, "roles") {

def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name", O.Unique, O.Length(40))
def color = column[Int]("color")
def priority = column[Int]("priority", O.Unique)
def * = (id, name, color, priority) <> (Role.tupled, Role.unapply)
}

+ 0
- 16
app/entity/UserRoles.scala View File

@@ -1,16 +0,0 @@
package entity

import slick.lifted.{TableQuery, Tag}
import slick.jdbc.MySQLProfile.api._

object UserRoles {

val userRoles = TableQuery[UserRoles]
}

class UserRoles(tag: Tag) extends Table[(Long, Long)](tag, "user_roles") {

def userId = column[Long]("user_id")
def roleId = column[Long]("role_id")
def * = (userId, roleId)
}

+ 3
- 32
app/entity/Users.scala View File

@@ -1,37 +1,9 @@
package entity

import play.api.libs.json.{Json, Writes}
import slick.jdbc.MySQLProfile.api._
import slick.lifted.TableQuery

object UsersToJson {

implicit val writesUser: Writes[User] = Writes[User] {
case User(id, name, email, _, _, _) =>
Json.obj(
"id" -> id,
"name" -> name,
"email" -> email
)
}

implicit val writesUserWithRoles: Writes[(User, Seq[Role])] = Writes[(User, Seq[Role])] {
case (User(id, name, email, _, _, _), roles) =>
Json.obj(
"id" -> id,
"name" -> name,
"email" -> email,
"roles" -> roles.map(role => Json.obj(
"id" -> role.id,
"name" -> role.name,
"color" -> role.color,
"priority" -> role.priority
))
)
}
}

case class User(id: Long, name: String, email: String, password: Array[Byte], registerDate: Long = System.currentTimeMillis(), confirmed: Boolean = false)
case class User(id: Long, nickname: String, password: Array[Byte], registerDate: Long = System.currentTimeMillis(), confirmed: Boolean = false)

object Users {

@@ -41,10 +13,9 @@ object Users {
class Users(tag: Tag) extends Table[User](tag, "users") {

def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name", O.Unique, O.Length(40))
def email = column[String]("email", O.Unique, O.Length(40))
def nickname = column[String]("nickname", O.Unique, O.Length(40))
def password = column[Array[Byte]]("password", O.Length(60))
def registrationDate = column[Long]("registration_date")
def confirmed = column[Boolean]("confirmed")
def * = (id, name, email, password, registrationDate, confirmed) <> (User.tupled, User.unapply)
def * = (id, nickname, password, registrationDate, confirmed) <> (User.tupled, User.unapply)
}

+ 0
- 16
app/forms/LoginForm.scala View File

@@ -1,16 +0,0 @@
package forms

object LoginForm {

import play.api.data.Forms._
import play.api.data.Form

case class Data(login: String, password: String)

val form: Form[Data] = Form(
mapping(
"login" -> nonEmptyText(minLength = 3),
"password" -> nonEmptyText(minLength = 8)
)(Data.apply)(Data.unapply)
)
}

+ 0
- 17
app/forms/RegisterForm.scala View File

@@ -1,17 +0,0 @@
package forms

object RegisterForm {

import play.api.data.Forms._
import play.api.data.Form

case class Data(name: String, email: String, password: String)

val form: Form[Data] = Form(
mapping(
"name" -> nonEmptyText(minLength = 3),
"email" -> email,
"password" -> nonEmptyText(minLength = 8)
)(Data.apply)(Data.unapply)
)
}

+ 16
- 0
app/forms/SignInForm.scala View File

@@ -0,0 +1,16 @@
package forms

object SignInForm {

import play.api.data.Forms._
import play.api.data.Form

case class Data(nickname: String, password: String)

val form: Form[Data] = Form(
mapping(
"nickname" -> nonEmptyText(minLength = 3, maxLength = 63),
"password" -> nonEmptyText(minLength = 8, maxLength = 71)
)(Data.apply)(Data.unapply)
)
}

+ 16
- 0
app/forms/SignUpForm.scala View File

@@ -0,0 +1,16 @@
package forms

object SignUpForm {

import play.api.data.Forms._
import play.api.data.Form

case class Data(nickname: String, password: String)

val form: Form[Data] = Form(
mapping(
"nickname" -> nonEmptyText(minLength = 3, maxLength = 63),
"password" -> nonEmptyText(minLength = 8, maxLength = 71)
)(Data.apply)(Data.unapply)
)
}

+ 3
- 1
app/services/BCryptPasswordHasher.scala View File

@@ -6,8 +6,10 @@ import javax.inject.Inject
class BCryptPasswordHasher @Inject()(val hasher: BCrypt.Hasher, val verifyer: BCrypt.Verifyer)
extends PasswordHasher {

val Cost: Int = 12

override def encrypt(password: Array[Byte]): Array[Byte] =
hasher.hash(12, password)
hasher.hash(Cost, password)

override def check(password: Array[Byte], hash: Array[Byte]): Boolean =
verifyer.verify(password, hash).verified


+ 0
- 11
app/services/ConfirmationCodeService.scala View File

@@ -1,11 +0,0 @@
package services

import com.google.inject.ImplementedBy

@ImplementedBy(classOf[ConfirmationCodeServiceImpl])
trait ConfirmationCodeService {

def generateConfirmationCode(length: Int): String

def generateConfirmationCode: String = generateConfirmationCode(24)
}

+ 0
- 9
app/services/ConfirmationCodeServiceImpl.scala View File

@@ -1,9 +0,0 @@
package services

import scala.util.Random

class ConfirmationCodeServiceImpl extends ConfirmationCodeService {

override def generateConfirmationCode(length: Int): String =
Random.alphanumeric.take(length).foldLeft("")(_ + _)
}

+ 0
- 9
app/services/EmailService.scala View File

@@ -1,9 +0,0 @@
package services

import com.google.inject.ImplementedBy

@ImplementedBy(classOf[EmailServiceImpl])
trait EmailService {

def send(to: String, subject: String, body: String): Unit
}

+ 0
- 17
app/services/EmailServiceImpl.scala View File

@@ -1,17 +0,0 @@
package services

import javax.inject.Inject
import play.api.libs.mailer._

class EmailServiceImpl @Inject()(mailerClient: MailerClient) extends EmailService {

override def send(to: String, subject: String, body: String): Unit = {
val email = Email(
subject,
"GWMDevelopments <gwm@gwm.dev>",
Seq(to),
bodyText = Some(body),
)
mailerClient.send(email)
}
}

+ 1
- 31
app/services/FileService.scala View File

@@ -1,45 +1,15 @@
package services

import java.awt.image.BufferedImage

import com.google.inject.ImplementedBy
import play.api.libs.Files
import play.api.mvc.MultipartFormData

import scala.concurrent.Future
import scala.reflect.io.{Directory, File}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.reflect.io.Directory

@ImplementedBy(classOf[FileServiceImpl])
trait FileService {

def publicFilesDirectory: Directory

def usersDirectory: Directory

def userDirectory(id: Long): Directory = usersDirectory/Directory(id.toString)

def getAvatarsDirectory(userId: Long): Directory

def getAvatarFile(userId: Long, size: AvatarSize): File

def getAvatar(userId: Long, size: AvatarSize): Future[Option[BufferedImage]]

def setAvatar(userId: Long, image: BufferedImage): Future[Unit]

def uploadFile(file: MultipartFormData.FilePart[Files.TemporaryFile]): Unit
}

object AvatarSize {

val Sizes: Seq[AvatarSize] = Seq(SmallAvatar, MediumAvatar, LargeAvatar)
}

case class AvatarSize(name: String, width: Int, height: Int)

object SmallAvatar extends AvatarSize("small", 64, 64)

object MediumAvatar extends AvatarSize("medium", 256, 256)

object LargeAvatar extends AvatarSize("large", 1024, 1024)

+ 3
- 48
app/services/FileServiceImpl.scala View File

@@ -1,61 +1,16 @@
package services

import java.awt.Image
import java.awt.image.BufferedImage

import com.typesafe.config.Config
import javax.imageio.ImageIO
import javax.inject.Inject
import play.api.libs.Files
import play.api.mvc.MultipartFormData

import scala.concurrent.Future
import scala.reflect.io.{Directory, File}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.reflect.io.Directory

class FileServiceImpl @Inject()(config: Config) extends FileService {

override def publicFilesDirectory: Directory = Directory(config.getString("publicFilesDirectory"))

override def usersDirectory: Directory = Directory(config.getString("usersDirectory"))

override def getAvatarsDirectory(userId: Long): Directory = userDirectory(userId)/Directory("avatars")

override def getAvatarFile(userId: Long, size: AvatarSize): File = getAvatarsDirectory(userId)/File(size.name + ".png")

override def getAvatar(userId: Long, size: AvatarSize): Future[Option[BufferedImage]] = Future {
val imageFile = getAvatarFile(userId, size)
if (imageFile.exists) {
Some(ImageIO.read(imageFile.jfile))
} else {
None
}
}

override def setAvatar(userId: Long, image: BufferedImage): Future[Unit] = Future {
val directory = getAvatarsDirectory(userId)
directory.createDirectory()
for (size <- AvatarSize.Sizes) {
val rescaledImage = image.getScaledInstance(size.width, size.height, Image.SCALE_DEFAULT).toBufferedImage
val imageFile = getAvatarFile(userId, size)
ImageIO.write(rescaledImage, "PNG", imageFile.jfile)
}
}
override def publicFilesDirectory: Directory =
Directory(config.getString("publicFilesDirectory"))

override def uploadFile(file: MultipartFormData.FilePart[Files.TemporaryFile]): Unit = ???

implicit class ImageOps(image: Image) {

implicit def toBufferedImage: BufferedImage =
image match {
case image: BufferedImage =>
image
case _ =>
val bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB)
val bGr = bufferedImage.createGraphics
bGr.drawImage(image, 0, 0, null)
bGr.dispose()
bufferedImage
}
}
}

+ 0
- 26
app/services/dao/RoleDao.scala View File

@@ -1,26 +0,0 @@
package services.dao

import com.google.inject.ImplementedBy
import entity.{Role, User}

@ImplementedBy(classOf[SlickRoleDao])
trait RoleDao[F[_]] {

def getRoles: F[Seq[Role]]

def getOptionById(id: Long): F[Option[Role]]

def getById(id: Long): F[Role]

def getOptionByName(name: String): F[Option[Role]]

def getByName(name: String): F[Role]

def create(role: Role): F[Int]

def delete(id: Long): F[Int]

def getUserRoles(userId: Long): F[Seq[Role]]

def getRoleUsers(roleId: Long): F[Seq[User]]
}

+ 0
- 61
app/services/dao/SlickRoleDao.scala View File

@@ -1,61 +0,0 @@
package services.dao

import entity.{DefaultRoles, Role, User}
import entity.Roles.roles
import entity.Users.users
import entity.UserRoles.userRoles
import javax.inject.{Inject, Singleton}
import play.api.Logging
import play.api.db.slick.DatabaseConfigProvider
import slick.jdbc.MySQLProfile.api._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.Success
import scala.util.Failure

@Singleton
class SlickRoleDao @Inject()(val dbConfig: DatabaseConfigProvider) extends RoleDao[Future] with Logging {

private val db = dbConfig.get.db

db.run(DBIO.seq(
roles.schema.createIfNotExists,
userRoles.schema.createIfNotExists)).
onComplete {
case Success(_) => logger.info("Startup db queries successfully executed.")
case Failure(e) => logger.error("Startup db queries failed!", e)
}

DefaultRoles.list.foreach(role => db.run(roles += role).onComplete {
case Success(_) => logger.info(s"Role $role successfully inserted into the db")
case Failure(e) => logger.debug(s"Failed to insert role $role into the db (probably it has already been inserted)", e)
})

override def getRoles: Future[Seq[Role]] = db.run(roles.result)

override def getOptionById(id: Long): Future[Option[Role]] = db.run(roles.filter(_.id === id).result.headOption)

override def getById(id: Long): Future[Role] = db.run(roles.filter(_.id === id).result.head)

override def getOptionByName(name: String): Future[Option[Role]] = db.run(roles.filter(_.name === name).result.headOption)

override def getByName(name: String): Future[Role] = db.run(roles.filter(_.name === name).result.head)

override def create(role: Role): Future[Int] = db.run(roles += role)

override def delete(id: Long): Future[Int] = db.run(roles.filter(_.id === id).delete)

override def getUserRoles(userId: Long): Future[Seq[Role]] = {
db.run(roles.
join(userRoles.filter(_.userId === userId)). //Select roles of the user
on(_.id === _.roleId). //Select roles from the db
map(_._1).result) //Take only roles
}

override def getRoleUsers(roleId: Long): Future[Seq[User]] =
db.run(users.
join(userRoles.filter(_.roleId === roleId)).
on(_.id === _.userId).
map(_._1).result)
}

+ 8
- 47
app/services/dao/SlickUserDao.scala View File

@@ -1,6 +1,5 @@
package services.dao

import entity.ConfirmationCodes.confirmationCodes
import entity.User
import entity.Users.users
import javax.inject.{Inject, Singleton}
@@ -10,7 +9,6 @@ import slick.jdbc.MySQLProfile.api._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.language.higherKinds
import scala.util.{Failure, Success}

@Singleton
@@ -18,9 +16,7 @@ class SlickUserDao @Inject()(val dbConfig: DatabaseConfigProvider) extends UserD

private val db = dbConfig.get.db

db.run(DBIO.seq(
users.schema.createIfNotExists,
confirmationCodes.schema.createIfNotExists)).
db.run(users.schema.createIfNotExists).
onComplete {
case Success(_) => logger.info("Startup db queries successfully executed.")
case Failure(e) => logger.error("Startup db queries failed!", e)
@@ -32,48 +28,13 @@ class SlickUserDao @Inject()(val dbConfig: DatabaseConfigProvider) extends UserD
override def getById(id: Long): Future[Option[User]] =
db.run(users.filter(_.id === id).result.headOption)

override def getIdByName(name: String): Future[Option[Long]] =
db.run(users.filter(_.name === name).map(_.id).result.headOption)
override def getByNickname(nickname: String): Future[Option[User]] =
db.run(users.filter(_.nickname === nickname).result.headOption)

override def getIdByEmail(email: String): Future[Option[Long]] =
db.run(users.filter(_.email === email).map(_.id).result.headOption)
override def create(user: User): Future[Long] =
db.run((users returning users.map(_.id)) += user)

override def getByName(name: String): Future[Option[User]] =
db.run(users.filter(_.name === name).result.headOption)

override def getByEmail(email: String): Future[Option[User]] =
db.run(users.filter(_.email === email).result.headOption)

override def getByLogin(login: String): Future[Option[User]] =
getByName(login).flatMap {
case user @ Some(_) => Future(user)
case None => getByEmail(login)
}

override def setConfirmationCode(id: Long, confirmationCode: String): Future[Int] =
db.run(confirmationCodes += (id, confirmationCode))

override def getConfirmationCode(id: Long): Future[Option[String]] =
db.run(confirmationCodes.filter(_.userId === id).map(_.confirmationCode).result.headOption)

def getIdByConfirmationCode(confirmationCode: String): Future[Option[Long]] =
db.run(confirmationCodes.filter(_.confirmationCode === confirmationCode).map(_.userId).result.headOption)

def getByConfirmationCode(confirmationCode: String): Future[Option[User]] =
getIdByConfirmationCode(confirmationCode).flatMap {
case Some(id) => getById(id)
case None => Future(None)
}

override def deleteConfirmationCode(id: Long): Future[Int] =
db.run(confirmationCodes.filter(_.userId === id).delete)

override def confirm(id: Long): Future[Int] =
db.run(users.filter(_.id === id).map(_.confirmed).update(true))

override def create(user: User): Future[Int] =
db.run(users += user)

override def delete(id: Long): Future[Int] =
db.run(users.filter(_.id === id).delete)
override def delete(id: Long): Future[Unit] =
db.run(users.filter(_.id === id).delete).
map(_ => ())
}

+ 3
- 23
app/services/dao/UserDao.scala View File

@@ -10,29 +10,9 @@ trait UserDao[F[_]] {

def getById(id: Long): F[Option[User]]

def getIdByName(name: String): F[Option[Long]]
def getByNickname(nickname: String): F[Option[User]]

def getIdByEmail(email: String): F[Option[Long]]
def create(user: User): F[Long]

def getByName(name: String): F[Option[User]]

def getByEmail(email: String): F[Option[User]]

def getByLogin(login: String): F[Option[User]]

def setConfirmationCode(id: Long, confirmationCode: String): F[Int]

def getConfirmationCode(id: Long): F[Option[String]]

def getIdByConfirmationCode(confirmationCode: String): F[Option[Long]]

def getByConfirmationCode(confirmationCode: String): F[Option[User]]

def deleteConfirmationCode(id: Long): F[Int]

def confirm(id: Long): F[Int]

def create(user: User): F[Int]

def delete(id: Long): F[Int]
def delete(id: Long): F[Unit]
}

+ 0
- 28
app/util/Secure.scala View File

@@ -1,28 +0,0 @@
package util

import entity.User
import play.api.mvc._
import services.dao.{RoleDao, UserDao}

import scala.concurrent.ExecutionContext.Implicits._
import scala.concurrent.Future

trait Secure {

implicit def getUsername[A](implicit request: Request[A]): Option[String] =
request.session.get("username")

implicit def getUser[A](implicit request: Request[A], userDao: UserDao[Future]): Future[Option[User]] =
getUsername match {
case Some(name) => userDao.getByName(name)
case None => Future(None)
}

def isAdministrator[A](implicit request: Request[A], userDao: UserDao[Future], roleDao: RoleDao[Future]): Future[Option[Boolean]] =
getUser.flatMap {
case Some(user) =>
roleDao.getUserRoles(user.id).map(roles => Some(roles.exists(_.name.equals("administrator"))))
case None =>
Future(None)
}
}

+ 20
- 0
app/utils/Secure.scala View File

@@ -0,0 +1,20 @@
package utils

import entity.User
import play.api.mvc.Request
import services.dao.UserDao

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

trait Secure {

def getId[A](implicit request: Request[A]): Option[Long] =
request.session.get("id").flatMap(_.toLongOption)

def getUser[A](implicit request: Request[A], userDao: UserDao[Future]): Future[Option[User]] =
getId match {
case Some(id) => userDao.getById(id)
case None => Future(None)
}
}

+ 7
- 6
app/views/about.scala.html View File

@@ -1,9 +1,11 @@
@()(implicit messagesProvider: MessagesProvider, optUsername: Option[String] = None)
@import entity.User

@()(implicit request: Request[_], messagesProvider: MessagesProvider, optUser: Option[User] = None)

@generic.main(messagesProvider.messages("about.title")) {
<h1>Hello there!</h1>
<p>
I am a software developer known as GreWeMa (GWM), and my real name is Nazar Kalinowski<br>
I am a software developer known as <b>GWM</b> (or <b>GreWeMa</b>), and my real name is <b>Nazar Kalinowski</b><br>
<span class="with-tooltip" title="Free and Open Source Software">FOSS</span> is my passion and (with a few exceptions) this is the only kind of software that I use<br>
Apart from maintaining a few FOSS projects of mine, I also like to contribute to other Open Source projects (<a href="https://phabricator.kde.org/p/nazark/">KDE</a>)<br>
My main programming languages are Scala and Java (and <span class="with-tooltip" title="Java Virtual Machine">JVM</span> stack in general)<br>
@@ -28,8 +30,8 @@
<ul>
<li>Gradle, Maven, sbt</li>
<li>Play Framework 2</li>
<li>Akka</li>
<li>Slick</li>
<li>Akka</li>
<li>...</li>
</ul>
<h3 title="All the tools that I have used at least once">
@@ -79,8 +81,8 @@
Language skills:
</h2>
<ul>
<li>Russian (<span class="with-tooltip" title="The language I use on my daily basis">Native</span>)</li>
<li>Ukrainian (<span class="with-tooltip" title="I am bilingual, and this is my second language">Native</span>)</li>
<li>Russian (Native)</li>
<li>Ukrainian (Native)</li>
<li>English (C1)</li>
<li>Polish (A2)</li>
<li>...</li>
@@ -94,7 +96,6 @@
<li>Discord: <i>GWM#2192</i></li>
<li><a href="https://gitea.gwm.dev/GWM">Gitea</a> hosting with my projects</li>
<li><a href="https://github.com/GreWeMa">GitHub</a> profile</li>
<li><a href="https://gitlab.com/GreWeMa">GitLab</a> profile</li>
<li><a href="https://www.hackerrank.com/GreWeMa">HackerRank</a> profile</li>
<li><a href="https://leetcode.com/grewema">Leetcode</a> profile</li>
</ul>

+ 4
- 2
app/views/admin.scala.html View File

@@ -1,5 +1,7 @@
@(optUser: String)(implicit messagesProvider: MessagesProvider, optUsername: Option[String] = None)
@import entity.User

@()(implicit request: Request[_], messagesProvider: MessagesProvider, optUser: Option[User])

@generic.main(messagesProvider.messages("index.title")) {
<h1>Admin's page!</h1>
}
}

+ 0
- 14
app/views/authorization.scala.html View File

@@ -1,14 +0,0 @@
@import helper._
@import forms.LoginForm

@(userForm: Form[LoginForm.Data])(implicit request: RequestHeader, messagesProvider: MessagesProvider, optUsername: Option[String] = None)

@generic.main(messagesProvider.messages("login.title")) {
<h1><b>Sign-in</b></h1>
@helper.form(action = routes.AuthController.login()) {
@CSRF.formField
@helper.inputText(userForm("login"))
@helper.inputPassword(userForm("password"))
<input type="submit">
}
}

+ 0
- 14
app/views/filenotfound.scala.html View File

@@ -1,14 +0,0 @@
@(file: String)(implicit messagesProvider: MessagesProvider, optUsername: Option[String] = None)

<!DOCTYPE html>
<html lang="en">
<head>
<title>GWMDevelopments - file not found</title>
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.ico")">
</head>
<body>
<h1>File @file not found!</h1>
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
</body>
</html>

app/views/fileslist.scala.html → app/views/files.scala.html View File

@@ -2,8 +2,9 @@
@import helper._
@import scala.reflect.io.Directory
@import scala.reflect.io.Path
@import entity.User

@(root: Directory, file: Directory, showUpload: Boolean)(implicit request: RequestHeader, messagesProvider: MessagesProvider, optUsername: Option[String] = None)
@(root: Directory, file: Directory)(implicit request: Request[_], messagesProvider: MessagesProvider, optUser: Option[User])

@fileToHref(root: Directory, path: Path) = @{
val relativized = root.relativize(path).toString
@@ -12,12 +13,14 @@
}

@generic.main(messagesProvider.messages("fileslist.title")) {
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/fileslist.css")">
@if(showUpload){
@if(optUser.isDefined){
@helper.form(action = routes.FilesController.upload, Symbol("enctype") -> "multipart/form-data") {
@CSRF.formField
<input type="file" name="fileToUpload"><br>
Custom name (optional): <input type="text" name="customName"><br>
<label>
Custom name (optional): <input type="text" name="customName">
</label>
<br>
<input type="hidden" name="path" value="@root.relativize(file).toString"/>
<input type="submit" value="Upload">
}
@@ -35,10 +38,11 @@
<td><a href="@fileToHref(root, subFile)"><b>@subFile.name</b> @if(subFile.isFile){
<i> (@FilesController.sizeToString(subFile.length))</i>
}</a></td>
<td><input/><button>Rename</button></td>
<td><button>Delete</button></td>
@if(optUser.isDefined) {
<td><input/><button>Rename</button></td>
<td><button>Delete</button></td>
}
</tr>
}
</table>
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
}

+ 12
- 9
app/views/generic/main.scala.html View File

@@ -1,28 +1,31 @@
@(title: String)(content: Html)(implicit messagesProvider: MessagesProvider, optUsername: Option[String] = None)
@import entity.User

<!doctype html>
@(title: String)(content: Html)(implicit request: Request[_], messagesProvider: MessagesProvider, optUser: Option[User])

<!DOCTYPE html>
<html>
<head>
<title>@title</title>
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/generic/main.css")">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
</head>
<body>
<div class="wrapper">
@for(entry <- request.flash.data) {
<div class="notification @entry._1">
<p>
@entry._2
</p>
</div>
}
<div class="header">
<div class="logo">
<a class="logo-img" href="/"><img class="logo" src="@routes.Assets.versioned("images/logo.png")"></a>
<b>GWMDevelopments</b>
</div>
<div class="user-info">
@if(optUsername.isDefined) {
<span>Hello, <a href="/profile">@optUsername.get</a>!</span><br>
@if(optUser.isDefined) {
<a href="/logout">@messagesProvider.messages("index.logout")</a>
} else {
<a href="/registration">@messagesProvider.messages("index.registration")</a><br>
<a href="/authorization">@messagesProvider.messages("index.login")</a>
}
</div>
</div>


+ 5
- 4
app/views/home.scala.html View File

@@ -1,14 +1,15 @@
@(admin: Boolean)(implicit messagesProvider: MessagesProvider, optUsername: Option[String] = None)
@import entity.User

@()(implicit request: Request[_], messagesProvider: MessagesProvider, optUser: Option[User])

@generic.main(messagesProvider.messages("index.title")) {
<h1><b><font color="#FB4C19">GWMDevelopments</font> site.</b></h1>
<h1><b><font color="#FB4C19">GWM's site</font></b></h1>
<h2><font color="red">TODO: make TODO</font></h2>
<a href="/files">@messagesProvider.messages("index.files")</a><br>
<a href="/about">@messagesProvider.messages("index.about")</a><br>
<a href="https://maven.gwm.dev/">@messagesProvider.messages("index.maven")</a><br>
<a href="https://gitea.gwm.dev/">@messagesProvider.messages("index.gitea")</a><br>
@if(admin) {
@if(optUser.isDefined) {
<a href="/admin">@messagesProvider.messages("index.admin")</a><br>
}
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
}

+ 0
- 17
app/views/profile.scala.html View File

@@ -1,17 +0,0 @@
@import entity.User
@import entity.Role

@(user: User, roles: Seq[Role], optAvatar: Option[String])(implicit messagesProvider: MessagesProvider, optUsername: Option[String] = None)

@generic.main(messagesProvider.messages("profile.title")) {
<span>Hello in @user.name's profile!</span><br>
@if(optAvatar.isDefined) {
<img src="@optAvatar.get"/><br>
}
@if(roles.nonEmpty) {
<span>@user.name's roles: </span><br>
@for(role <- roles) {
<span><b><font color="#@role.color.toHexString.drop(2)">@role.name</font></b></span><br>
}
}
}

+ 0
- 16
app/views/registration.scala.html View File

@@ -1,16 +0,0 @@
@import helper._
@import forms.RegisterForm

@(userForm: Form[RegisterForm.Data])(implicit request: RequestHeader, messagesProvider: MessagesProvider, optUsername: Option[String] = None)


@generic.main(messagesProvider.messages("register.title")) {
<h1><b>Sign-up</b></h1>
@helper.form(action = routes.AuthController.register()) {
@CSRF.formField
@helper.inputText(userForm("name"))
@helper.inputText(userForm("email"))
@helper.inputPassword(userForm("password"))
<input type="submit">
}
}

+ 0
- 25
app/views/selfprofile.scala.html View File

@@ -1,25 +0,0 @@
@import entity.User
@import entity.Role

@import helper._

@(user: User, roles: Seq[Role], optAvatar: Option[String])(implicit request: RequestHeader, messagesProvider: MessagesProvider, optUsername: Option[String] = None)

@generic.main(messagesProvider.messages("selfprofile.title")) {
<span>Hello in your profile, @{user.name}!</span><br>
@if(optAvatar.isDefined) {
<img src="@optAvatar.get"/><br>
}
@helper.form(action = routes.UserController.uploadAvatar, Symbol("enctype") -> "multipart/form-data") {
@CSRF.formField
Upload new avatar:<br>
<input type="file" name="avatar"><br>
<input type="submit" value="Upload">
}
@if(roles.nonEmpty) {
<span>Your roles: </span><br>
@for(role <- roles) {
<span><b><font color="#@role.color.toHexString.drop(2)">@role.name</font></b></span><br>
}
}
}

+ 25
- 0
app/views/signin.scala.html View File

@@ -0,0 +1,25 @@
@import helper._
@import forms.SignInForm
@import entity.User

@(signInForm: Form[SignInForm.Data])(implicit request: Request[_], messagesProvider: MessagesProvider, optUser: Option[User] = None)

@generic.main(messagesProvider.messages("login.title")) {

<h1><b>Sign-in</b></h1>

@if(signInForm.hasErrors) {
<div class="form-errors">
@for(error <- signInForm.errors) {
<span>@error.message</span><br>
}
</div>
}

@helper.form(action = routes.AuthController.signinForm()) {
@CSRF.formField
@helper.inputText(signInForm("nickname"))
@helper.inputPassword(signInForm("password"))
<input type="submit">
}
}

+ 25
- 0
app/views/signup.scala.html View File

@@ -0,0 +1,25 @@
@import helper._
@import forms.SignUpForm
@import entity.User

@(signUpForm: Form[SignUpForm.Data])(implicit request: Request[_], messagesProvider: MessagesProvider, optUser: Option[User] = None)

@generic.main(messagesProvider.messages("register.title")) {

<h1><b>Sign-up</b></h1>

@if(signUpForm.hasErrors) {
<div class="form-errors">
@for(error <- signUpForm.errors) {
<span>@error.message</span><br>
}
</div>
}

@helper.form(action = routes.AuthController.signupForm()) {
@CSRF.formField
@helper.inputText(signUpForm("nickname"))
@helper.inputPassword(signUpForm("password"))
<input type="submit">
}
}

+ 1
- 7
build.sbt View File

@@ -6,21 +6,15 @@ organization := "dev.gwm"
enablePlugins(PlayScala)

resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
resolvers += "Akka Snapshot Repository" at "https://repo.akka.io/snapshots/"
scalaVersion := "2.13.1"

libraryDependencies ++= Seq(
specs2 % Test,
guice,
"mysql" % "mysql-connector-java" % "8.0.18",
"mysql" % "mysql-connector-java" % "8.0.19",
"com.typesafe.slick" %% "slick" % "3.3.2",
"com.typesafe.play" %% "play-slick" % "5.0.0",
"com.typesafe.play" %% "play-slick-evolutions" % "5.0.0",
"com.typesafe.play" %% "play-mailer" % "7.0.1",
"com.typesafe.play" %% "play-mailer-guice" % "7.0.1",
"at.favre.lib" % "bcrypt" % "0.9.0")

unmanagedResourceDirectories in Test += baseDirectory ( _ /"target/web/public/test" ).value

+ 4
- 17
conf/routes View File

@@ -13,25 +13,12 @@ GET /files/*name controllers.FilesController.files(name: Strin

POST /upload controllers.FilesController.upload

GET /registration controllers.AuthController.registration
POST /register controllers.AuthController.register
GET /authorization controllers.AuthController.authorization
POST /login controllers.AuthController.login()
GET /confirm/:code controllers.AuthController.confirmRegistration(code: String)
GET /signup controllers.AuthController.signup
POST /signup controllers.AuthController.signupForm
GET /signin controllers.AuthController.signin
POST /sigin controllers.AuthController.signinForm
GET /logout controllers.AuthController.logout

GET /profile controllers.UserController.selfProfile
GET /profile/avatar controllers.UserController.selfAvatar()
GET /profile/id/$id<\d+> controllers.UserController.userProfileById(id: Long)
GET /profile/name/:name controllers.UserController.userProfileByName(name: String)
GET /profile/id/:id/avatar controllers.UserController.avatar(id: Long)
POST /profile/uploadAvatar controllers.UserController.uploadAvatar()

GET /api/rest/users controllers.rest.UsersController.users
GET /api/rest/users/$id<\d+> controllers.rest.UsersController.user(id: Long, withRoles: Boolean ?= false)
GET /api/rest/roles controllers.rest.RolesController.roles
GET /api/rest/roles/$id<\d+> controllers.rest.RolesController.role(id: Long)

GET /admin controllers.AdminController.admin

# Map static resources from the /public folder to the /assets URL path


+ 1
- 1
project/build.properties View File

@@ -1 +1 @@
sbt.version=1.3.5
sbt.version=1.3.8

+ 0
- 3
public/stylesheets/filenotfound.css View File

@@ -1,3 +0,0 @@
body {
background-color: coral;
}

+ 0
- 6
public/stylesheets/fileslist.css View File

@@ -1,6 +0,0 @@
body {
background-color: coral;
}
caption {
text-align: left
}

+ 15
- 0
public/stylesheets/generic/main.css View File

@@ -31,6 +31,7 @@
html,
body {
height: 100%;
background-color: coral;
}
.wrapper {
display: flex;
@@ -43,6 +44,20 @@ body {
.footer {
flex: 0 0 auto;
}
.notification {
background-color: darkslategray;
padding: 2px;
font-weight: bold;
}
.notification.info {
color: green;
}
.notification.warning {
color: red;
}
.notification.error {
color: orangered;
}
.with-tooltip {
border-bottom: 1px dotted #000;
text-decoration: none;


+ 0
- 6
public/stylesheets/home.css View File

@@ -1,6 +0,0 @@
body {
background-color: coral;
}
body {
margin: 0;
}

Loading…
Cancel
Save