diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala new file mode 100644 index 0000000..8ae9a64 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala @@ -0,0 +1,4 @@ +package works.iterative.core + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala new file mode 100644 index 0000000..8ae9a64 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala @@ -0,0 +1,4 @@ +package works.iterative.core + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala new file mode 100644 index 0000000..260ca13 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +import java.time.Instant +import scala.Conversion + +/** A moment in time, represented as an instant + * + * Why not to use Instant by itself? Well, the time representation might change + * for some reason, or might be different in different contexts (Like ScalaJS + * or Native). Better to abstract it away. + */ +opaque type Moment = Instant + +object Moment: + def apply(value: Instant): Moment = value + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) + given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala new file mode 100644 index 0000000..8ae9a64 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala @@ -0,0 +1,4 @@ +package works.iterative.core + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala new file mode 100644 index 0000000..260ca13 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +import java.time.Instant +import scala.Conversion + +/** A moment in time, represented as an instant + * + * Why not to use Instant by itself? Well, the time representation might change + * for some reason, or might be different in different contexts (Like ScalaJS + * or Native). Better to abstract it away. + */ +opaque type Moment = Instant + +object Moment: + def apply(value: Instant): Moment = value + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) + given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala new file mode 100644 index 0000000..7007704 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +/** A simple object to hold unique user identification together with "humane" + * identification Usually we need to display the user's name in the UI, which + * we should be able to do without resorting to external services. This handle + * is to be used in all user-generated content, like comments, reviews, etc. + */ +final case class UserHandle private ( + userId: UserId, + userName: UserName +) + +object UserHandle: + def apply(userId: String, userName: String): Validated[UserHandle] = + for + id <- UserId(userId) + name <- UserName(userName) + yield UserHandle(id, name) diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala new file mode 100644 index 0000000..8ae9a64 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala @@ -0,0 +1,4 @@ +package works.iterative.core + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala new file mode 100644 index 0000000..260ca13 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +import java.time.Instant +import scala.Conversion + +/** A moment in time, represented as an instant + * + * Why not to use Instant by itself? Well, the time representation might change + * for some reason, or might be different in different contexts (Like ScalaJS + * or Native). Better to abstract it away. + */ +opaque type Moment = Instant + +object Moment: + def apply(value: Instant): Moment = value + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) + given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala new file mode 100644 index 0000000..7007704 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +/** A simple object to hold unique user identification together with "humane" + * identification Usually we need to display the user's name in the UI, which + * we should be able to do without resorting to external services. This handle + * is to be used in all user-generated content, like comments, reviews, etc. + */ +final case class UserHandle private ( + userId: UserId, + userName: UserName +) + +object UserHandle: + def apply(userId: String, userName: String): Validated[UserHandle] = + for + id <- UserId(userId) + name <- UserName(userName) + yield UserHandle(id, name) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala new file mode 100644 index 0000000..130dd3a --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -0,0 +1,9 @@ +package works.iterative.core + +// Unique identifier of the user +opaque type UserId = String + +object UserId: + def apply(value: String): Validated[UserId] = + // Validate that the value is not empty + Validated.nonEmptyString("user.id")(value) diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala new file mode 100644 index 0000000..8ae9a64 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala @@ -0,0 +1,4 @@ +package works.iterative.core + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala new file mode 100644 index 0000000..260ca13 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +import java.time.Instant +import scala.Conversion + +/** A moment in time, represented as an instant + * + * Why not to use Instant by itself? Well, the time representation might change + * for some reason, or might be different in different contexts (Like ScalaJS + * or Native). Better to abstract it away. + */ +opaque type Moment = Instant + +object Moment: + def apply(value: Instant): Moment = value + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) + given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala new file mode 100644 index 0000000..7007704 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +/** A simple object to hold unique user identification together with "humane" + * identification Usually we need to display the user's name in the UI, which + * we should be able to do without resorting to external services. This handle + * is to be used in all user-generated content, like comments, reviews, etc. + */ +final case class UserHandle private ( + userId: UserId, + userName: UserName +) + +object UserHandle: + def apply(userId: String, userName: String): Validated[UserHandle] = + for + id <- UserId(userId) + name <- UserName(userName) + yield UserHandle(id, name) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala new file mode 100644 index 0000000..130dd3a --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -0,0 +1,9 @@ +package works.iterative.core + +// Unique identifier of the user +opaque type UserId = String + +object UserId: + def apply(value: String): Validated[UserId] = + // Validate that the value is not empty + Validated.nonEmptyString("user.id")(value) diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala new file mode 100644 index 0000000..664bff0 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -0,0 +1,9 @@ +package works.iterative.core + +// Full name of the user +opaque type UserName = String + +object UserName: + def apply(value: String): Validated[UserName] = + // Validate that the value is not empty + Validated.nonEmptyString("user.name")(value) diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala new file mode 100644 index 0000000..8ae9a64 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala @@ -0,0 +1,4 @@ +package works.iterative.core + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala new file mode 100644 index 0000000..260ca13 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +import java.time.Instant +import scala.Conversion + +/** A moment in time, represented as an instant + * + * Why not to use Instant by itself? Well, the time representation might change + * for some reason, or might be different in different contexts (Like ScalaJS + * or Native). Better to abstract it away. + */ +opaque type Moment = Instant + +object Moment: + def apply(value: Instant): Moment = value + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) + given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala new file mode 100644 index 0000000..7007704 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -0,0 +1,18 @@ +package works.iterative.core + +/** A simple object to hold unique user identification together with "humane" + * identification Usually we need to display the user's name in the UI, which + * we should be able to do without resorting to external services. This handle + * is to be used in all user-generated content, like comments, reviews, etc. + */ +final case class UserHandle private ( + userId: UserId, + userName: UserName +) + +object UserHandle: + def apply(userId: String, userName: String): Validated[UserHandle] = + for + id <- UserId(userId) + name <- UserName(userName) + yield UserHandle(id, name) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala new file mode 100644 index 0000000..130dd3a --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -0,0 +1,9 @@ +package works.iterative.core + +// Unique identifier of the user +opaque type UserId = String + +object UserId: + def apply(value: String): Validated[UserId] = + // Validate that the value is not empty + Validated.nonEmptyString("user.id")(value) diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala new file mode 100644 index 0000000..664bff0 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -0,0 +1,9 @@ +package works.iterative.core + +// Full name of the user +opaque type UserName = String + +object UserName: + def apply(value: String): Validated[UserName] = + // Validate that the value is not empty + Validated.nonEmptyString("user.name")(value) diff --git a/core/shared/src/main/scala/works/iterative/core/Validated.scala b/core/shared/src/main/scala/works/iterative/core/Validated.scala index 2e3a4ee..0b94e22 100644 --- a/core/shared/src/main/scala/works/iterative/core/Validated.scala +++ b/core/shared/src/main/scala/works/iterative/core/Validated.scala @@ -3,3 +3,14 @@ import zio.prelude.Validation type Validated[A] = Validation[UserMessage, A] + +object Validated: + /** Validate and normalize nonempty string, returning "error.empty.$lkey" if + * the string is empty, trimmed string otherwise + */ + def nonEmptyString(lkey: String)(value: String): Validated[String] = + Validation + .fromPredicateWith(UserMessage(s"error.empty.$lkey"))(value)( + _.trim.nonEmpty + ) + .map(_.trim)