From cd1b635b2ae90d9ac2d8b1779183a1fbd8c5fd5c Mon Sep 17 00:00:00 2001 From: vlad Date: Tue, 13 Jun 2017 16:12:20 -0700 Subject: Adding domain entities --- .../xyz/driver/pdsuicommon/domain/Email.scala | 3 + .../xyz/driver/pdsuicommon/domain/FuzzyValue.scala | 18 ++++++ .../scala/xyz/driver/pdsuicommon/domain/Id.scala | 51 +++++++++++++++ .../driver/pdsuicommon/domain/PasswordHash.scala | 36 +++++++++++ .../xyz/driver/pdsuicommon/domain/TextJson.scala | 14 ++++ .../scala/xyz/driver/pdsuicommon/domain/User.scala | 74 ++++++++++++++++++++++ 6 files changed, 196 insertions(+) create mode 100644 src/main/scala/xyz/driver/pdsuicommon/domain/Email.scala create mode 100644 src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala create mode 100644 src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala create mode 100644 src/main/scala/xyz/driver/pdsuicommon/domain/PasswordHash.scala create mode 100644 src/main/scala/xyz/driver/pdsuicommon/domain/TextJson.scala create mode 100644 src/main/scala/xyz/driver/pdsuicommon/domain/User.scala (limited to 'src/main/scala/xyz/driver/pdsuicommon/domain') diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/Email.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/Email.scala new file mode 100644 index 0000000..092ad40 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/Email.scala @@ -0,0 +1,3 @@ +package xyz.driver.pdsuicommon.domain + +final case class Email(value: String) diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala new file mode 100644 index 0000000..e338b2e --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala @@ -0,0 +1,18 @@ +package xyz.driver.pdsuicommon.domain + +import xyz.driver.pdsuicommon.logging._ +import xyz.driver.pdsuicommon.utils.Utils + +sealed trait FuzzyValue + +object FuzzyValue { + case object Yes extends FuzzyValue + case object No extends FuzzyValue + case object Maybe extends FuzzyValue + + val All: Set[FuzzyValue] = Set(Yes, No, Maybe) + + def fromBoolean(x: Boolean): FuzzyValue = if (x) Yes else No + + implicit def toPhiString(x: FuzzyValue): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass)) +} diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala new file mode 100644 index 0000000..1bb70f8 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala @@ -0,0 +1,51 @@ +package xyz.driver.pdsuicommon.domain + +import java.util.UUID + +import xyz.driver.pdsuicommon.logging._ + +sealed trait Id[+T] + +case class CompoundId[Id1 <: Id[_], Id2 <: Id[_]](part1: Id1, part2: Id2) extends Id[(Id1, Id2)] + +case class LongId[+T](id: Long) extends Id[T] { + override def toString: String = id.toString + + def is(longId: Long): Boolean = { + id == longId + } +} + +object LongId { + implicit def toPhiString[T](x: LongId[T]): PhiString = Unsafe(s"LongId(${x.id})") +} + +case class StringId[+T](id: String) extends Id[T] { + override def toString: String = id + + def is(stringId: String): Boolean = { + id == stringId + } +} + +object StringId { + implicit def toPhiString[T](x: StringId[T]): PhiString = Unsafe(s"StringId(${x.id})") +} + +case class UuidId[+T](id: UUID) extends Id[T] { + override def toString: String = id.toString +} + +object UuidId { + + /** + * @note May fail, if `string` is invalid UUID. + */ + def apply[T](string: String): UuidId[T] = new UuidId[T](UUID.fromString(string)) + + def apply[T](): UuidId[T] = new UuidId[T](UUID.randomUUID()) + + implicit def ordering[T] = Ordering.by[UuidId[T], String](_.toString) + + implicit def toPhiString[T](x: UuidId[T]): PhiString = Unsafe(s"UuidId(${x.id})") +} diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/PasswordHash.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/PasswordHash.scala new file mode 100644 index 0000000..82cba8d --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/PasswordHash.scala @@ -0,0 +1,36 @@ +package xyz.driver.pdsuicommon.domain + +import java.nio.charset.Charset + +import org.mindrot.jbcrypt.BCrypt + +final case class PasswordHash(value: Array[Byte]) { + + lazy val hashString: String = new String(value, Charset.forName("UTF-8")) + + override def toString: String = { + s"${this.getClass.getSimpleName}($hashString)" + } + + override def equals(that: Any): Boolean = { + that match { + case thatHash: PasswordHash => java.util.Arrays.equals(this.value, thatHash.value) + case _ => false + } + } + + override def hashCode(): Int = + 42 + java.util.Arrays.hashCode(this.value) + + def is(password: String): Boolean = + BCrypt.checkpw(password, hashString) +} + +object PasswordHash { + + def apply(password: String): PasswordHash = + new PasswordHash(getHash(password)) + + private def getHash(str: String): Array[Byte] = + BCrypt.hashpw(str, BCrypt.gensalt()).getBytes(Charset.forName("UTF-8")) +} diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/TextJson.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/TextJson.scala new file mode 100644 index 0000000..ee4d884 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/TextJson.scala @@ -0,0 +1,14 @@ +package xyz.driver.pdsuicommon.domain + +import xyz.driver.pdsuicommon.logging._ + +final case class TextJson[+T](content: T) { + def map[U](f: T => U): TextJson[U] = copy(f(content)) +} + +object TextJson { + + implicit def toPhiString[T](x: TextJson[T])(implicit inner: T => PhiString): PhiString = { + phi"TextJson(${x.content})" + } +} diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala new file mode 100644 index 0000000..acc3565 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala @@ -0,0 +1,74 @@ +package xyz.driver.pdsuicommon.domain + +import java.time.LocalDateTime + +import xyz.driver.pdsuicommon.logging._ +import xyz.driver.pdsuicommon.domain.User.Role +import xyz.driver.pdsuicommon.utils.Utils + +case class User(id: LongId[User], + email: Email, + name: String, + role: Role, + passwordHash: PasswordHash, + latestActivity: Option[LocalDateTime], + deleted: Option[LocalDateTime]) + +object User { + + sealed trait Role extends Product with Serializable { + + /** + * Bit representation of this role + */ + def bit: Int + + def is(that: Role): Boolean = this == that + + def oneOf(roles: Role*): Boolean = roles.contains(this) + + def oneOf(roles: Set[Role]): Boolean = roles.contains(this) + } + + object Role extends PhiLogging { + case object RecordAdmin extends Role {val bit = 1 << 0} + case object RecordCleaner extends Role {val bit = 1 << 1} + case object RecordOrganizer extends Role {val bit = 1 << 2} + case object DocumentExtractor extends Role {val bit = 1 << 3} + case object TrialSummarizer extends Role {val bit = 1 << 4} + case object CriteriaCurator extends Role {val bit = 1 << 5} + case object TrialAdmin extends Role {val bit = 1 << 6} + case object EligibilityVerifier extends Role{val bit = 1 << 7} + case object TreatmentMatchingAdmin extends Role{val bit = 1 << 8} + case object RoutesCurator extends Role{val bit = 1 << 9} + + val RepRoles = Set[Role](RecordAdmin, RecordCleaner, RecordOrganizer, DocumentExtractor) + + val TcRoles = Set[Role](TrialSummarizer, CriteriaCurator, TrialAdmin) + + val TreatmentMatchingRoles = Set[Role](RoutesCurator, EligibilityVerifier, TreatmentMatchingAdmin) + + val All = RepRoles ++ TcRoles ++ TreatmentMatchingRoles + + def apply(bitMask: Int): Role = { + def extractRole(role: Role): Set[Role] = + if ((bitMask & role.bit) != 0) Set(role) else Set.empty[Role] + + val roles = All.flatMap(extractRole) + roles.size match { + case 1 => roles.head + case _ => + logger.error(phi"Can't convert a bit mask ${Unsafe(bitMask)} to any role") + throw new IllegalArgumentException() + } + } + + implicit def toPhiString(x: Role): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass)) + } + + implicit def toPhiString(x: User): PhiString = { + import x._ + phi"User(id=$id, role=$role)" + } + +} -- cgit v1.2.3