diff --git a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala index 46f4dbe..7e5baeb 100644 --- a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala +++ b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala @@ -6,6 +6,7 @@ import zio.prelude.Validation import works.iterative.tapir.CustomTapir import works.iterative.core.auth.* +import works.iterative.event.EventRecord private[codecs] case class TextEncoding( pml: Option[PlainMultiLine], @@ -41,6 +42,13 @@ given JsonCodec[FileRef] = DeriveJsonCodec.gen[FileRef] + given JsonCodec[Moment] = JsonCodec.instant.transform( + Moment(_), + _.toInstant + ) + given JsonCodec[UserHandle] = DeriveJsonCodec.gen[UserHandle] + given JsonCodec[EventRecord] = DeriveJsonCodec.gen[EventRecord] + trait TapirCodecs extends CustomTapir: given Schema[PlainMultiLine] = Schema.string given Schema[PlainOneLine] = Schema.string @@ -52,5 +60,9 @@ given Schema[Email] = Schema.string given Schema[BasicProfile] = Schema.derived[BasicProfile] given Schema[FileRef] = Schema.derived[FileRef] + given Schema[Moment] = + Schema.schemaForInstant.map(i => Some(Moment(i)))(_.toInstant) + given Schema[UserHandle] = Schema.derived[UserHandle] + given Schema[EventRecord] = Schema.derived[EventRecord] object Codecs extends Codecs diff --git a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala index 46f4dbe..7e5baeb 100644 --- a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala +++ b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala @@ -6,6 +6,7 @@ import zio.prelude.Validation import works.iterative.tapir.CustomTapir import works.iterative.core.auth.* +import works.iterative.event.EventRecord private[codecs] case class TextEncoding( pml: Option[PlainMultiLine], @@ -41,6 +42,13 @@ given JsonCodec[FileRef] = DeriveJsonCodec.gen[FileRef] + given JsonCodec[Moment] = JsonCodec.instant.transform( + Moment(_), + _.toInstant + ) + given JsonCodec[UserHandle] = DeriveJsonCodec.gen[UserHandle] + given JsonCodec[EventRecord] = DeriveJsonCodec.gen[EventRecord] + trait TapirCodecs extends CustomTapir: given Schema[PlainMultiLine] = Schema.string given Schema[PlainOneLine] = Schema.string @@ -52,5 +60,9 @@ given Schema[Email] = Schema.string given Schema[BasicProfile] = Schema.derived[BasicProfile] given Schema[FileRef] = Schema.derived[FileRef] + given Schema[Moment] = + Schema.schemaForInstant.map(i => Some(Moment(i)))(_.toInstant) + given Schema[UserHandle] = Schema.derived[UserHandle] + given Schema[EventRecord] = Schema.derived[EventRecord] object Codecs extends Codecs diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 85124f0..880c7b6 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -1,5 +1,6 @@ package works.iterative.core import auth.UserId +import works.iterative.core.auth.CurrentUser /** 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 @@ -8,15 +9,26 @@ */ final case class UserHandle( userId: UserId, - userName: UserName -) + userName: Option[UserName] +): + val displayName: String = userName.map(_.value).getOrElse(userId.value) object UserHandle: + + given userHandleFromCurrentUser(using u: CurrentUser): UserHandle = u.handle + + def apply(userId: String): Validated[UserHandle] = + for id <- UserId(userId) + yield UserHandle(id, None) + def apply(userId: String, userName: String): Validated[UserHandle] = for id <- UserId(userId) name <- UserName(userName) - yield UserHandle(id, name) + yield UserHandle(id, Some(name)) + + def unsafe(userId: String): UserHandle = + UserHandle(UserId.unsafe(userId), None) def unsafe(userId: String, userName: String): UserHandle = - UserHandle(UserId.unsafe(userId), UserName.unsafe(userName)) + UserHandle(UserId.unsafe(userId), Some(UserName.unsafe(userName))) diff --git a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala index 46f4dbe..7e5baeb 100644 --- a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala +++ b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala @@ -6,6 +6,7 @@ import zio.prelude.Validation import works.iterative.tapir.CustomTapir import works.iterative.core.auth.* +import works.iterative.event.EventRecord private[codecs] case class TextEncoding( pml: Option[PlainMultiLine], @@ -41,6 +42,13 @@ given JsonCodec[FileRef] = DeriveJsonCodec.gen[FileRef] + given JsonCodec[Moment] = JsonCodec.instant.transform( + Moment(_), + _.toInstant + ) + given JsonCodec[UserHandle] = DeriveJsonCodec.gen[UserHandle] + given JsonCodec[EventRecord] = DeriveJsonCodec.gen[EventRecord] + trait TapirCodecs extends CustomTapir: given Schema[PlainMultiLine] = Schema.string given Schema[PlainOneLine] = Schema.string @@ -52,5 +60,9 @@ given Schema[Email] = Schema.string given Schema[BasicProfile] = Schema.derived[BasicProfile] given Schema[FileRef] = Schema.derived[FileRef] + given Schema[Moment] = + Schema.schemaForInstant.map(i => Some(Moment(i)))(_.toInstant) + given Schema[UserHandle] = Schema.derived[UserHandle] + given Schema[EventRecord] = Schema.derived[EventRecord] object Codecs extends Codecs diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 85124f0..880c7b6 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -1,5 +1,6 @@ package works.iterative.core import auth.UserId +import works.iterative.core.auth.CurrentUser /** 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 @@ -8,15 +9,26 @@ */ final case class UserHandle( userId: UserId, - userName: UserName -) + userName: Option[UserName] +): + val displayName: String = userName.map(_.value).getOrElse(userId.value) object UserHandle: + + given userHandleFromCurrentUser(using u: CurrentUser): UserHandle = u.handle + + def apply(userId: String): Validated[UserHandle] = + for id <- UserId(userId) + yield UserHandle(id, None) + def apply(userId: String, userName: String): Validated[UserHandle] = for id <- UserId(userId) name <- UserName(userName) - yield UserHandle(id, name) + yield UserHandle(id, Some(name)) + + def unsafe(userId: String): UserHandle = + UserHandle(UserId.unsafe(userId), None) def unsafe(userId: String, userName: String): UserHandle = - UserHandle(UserId.unsafe(userId), UserName.unsafe(userName)) + UserHandle(UserId.unsafe(userId), Some(UserName.unsafe(userName))) diff --git a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala index 710f0ec..47ef47f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala @@ -7,7 +7,8 @@ email: Option[Email], avatar: Option[Avatar], roles: Set[UserRole] -) extends UserProfile +) extends UserProfile: + val handle: UserHandle = UserHandle(subjectId, userName) object BasicProfile: def apply(p: UserProfile): BasicProfile = p match diff --git a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala index 46f4dbe..7e5baeb 100644 --- a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala +++ b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala @@ -6,6 +6,7 @@ import zio.prelude.Validation import works.iterative.tapir.CustomTapir import works.iterative.core.auth.* +import works.iterative.event.EventRecord private[codecs] case class TextEncoding( pml: Option[PlainMultiLine], @@ -41,6 +42,13 @@ given JsonCodec[FileRef] = DeriveJsonCodec.gen[FileRef] + given JsonCodec[Moment] = JsonCodec.instant.transform( + Moment(_), + _.toInstant + ) + given JsonCodec[UserHandle] = DeriveJsonCodec.gen[UserHandle] + given JsonCodec[EventRecord] = DeriveJsonCodec.gen[EventRecord] + trait TapirCodecs extends CustomTapir: given Schema[PlainMultiLine] = Schema.string given Schema[PlainOneLine] = Schema.string @@ -52,5 +60,9 @@ given Schema[Email] = Schema.string given Schema[BasicProfile] = Schema.derived[BasicProfile] given Schema[FileRef] = Schema.derived[FileRef] + given Schema[Moment] = + Schema.schemaForInstant.map(i => Some(Moment(i)))(_.toInstant) + given Schema[UserHandle] = Schema.derived[UserHandle] + given Schema[EventRecord] = Schema.derived[EventRecord] object Codecs extends Codecs diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 85124f0..880c7b6 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -1,5 +1,6 @@ package works.iterative.core import auth.UserId +import works.iterative.core.auth.CurrentUser /** 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 @@ -8,15 +9,26 @@ */ final case class UserHandle( userId: UserId, - userName: UserName -) + userName: Option[UserName] +): + val displayName: String = userName.map(_.value).getOrElse(userId.value) object UserHandle: + + given userHandleFromCurrentUser(using u: CurrentUser): UserHandle = u.handle + + def apply(userId: String): Validated[UserHandle] = + for id <- UserId(userId) + yield UserHandle(id, None) + def apply(userId: String, userName: String): Validated[UserHandle] = for id <- UserId(userId) name <- UserName(userName) - yield UserHandle(id, name) + yield UserHandle(id, Some(name)) + + def unsafe(userId: String): UserHandle = + UserHandle(UserId.unsafe(userId), None) def unsafe(userId: String, userName: String): UserHandle = - UserHandle(UserId.unsafe(userId), UserName.unsafe(userName)) + UserHandle(UserId.unsafe(userId), Some(UserName.unsafe(userName))) diff --git a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala index 710f0ec..47ef47f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala @@ -7,7 +7,8 @@ email: Option[Email], avatar: Option[Avatar], roles: Set[UserRole] -) extends UserProfile +) extends UserProfile: + val handle: UserHandle = UserHandle(subjectId, userName) object BasicProfile: def apply(p: UserProfile): BasicProfile = p match diff --git a/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala b/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala index ff99535..403e61f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala @@ -1,4 +1,20 @@ package works.iterative.core.auth +import zio.* +import scala.Conversion +import works.iterative.core.UserHandle + final case class CurrentUser(userProfile: BasicProfile) extends UserProfile: export userProfile.* + +object CurrentUser: + given Conversion[CurrentUser, BasicProfile] = _.userProfile + given Conversion[CurrentUser, UserHandle] = _.handle + + def use[A](f: CurrentUser ?=> A): URIO[CurrentUser, A] = + ZIO.serviceWith(f(using _)) + + def useZIO[R, E, A]( + f: CurrentUser ?=> ZIO[R, E, A] + ): ZIO[CurrentUser & R, E, A] = + ZIO.serviceWithZIO[CurrentUser](f(using _)) diff --git a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala index 46f4dbe..7e5baeb 100644 --- a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala +++ b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala @@ -6,6 +6,7 @@ import zio.prelude.Validation import works.iterative.tapir.CustomTapir import works.iterative.core.auth.* +import works.iterative.event.EventRecord private[codecs] case class TextEncoding( pml: Option[PlainMultiLine], @@ -41,6 +42,13 @@ given JsonCodec[FileRef] = DeriveJsonCodec.gen[FileRef] + given JsonCodec[Moment] = JsonCodec.instant.transform( + Moment(_), + _.toInstant + ) + given JsonCodec[UserHandle] = DeriveJsonCodec.gen[UserHandle] + given JsonCodec[EventRecord] = DeriveJsonCodec.gen[EventRecord] + trait TapirCodecs extends CustomTapir: given Schema[PlainMultiLine] = Schema.string given Schema[PlainOneLine] = Schema.string @@ -52,5 +60,9 @@ given Schema[Email] = Schema.string given Schema[BasicProfile] = Schema.derived[BasicProfile] given Schema[FileRef] = Schema.derived[FileRef] + given Schema[Moment] = + Schema.schemaForInstant.map(i => Some(Moment(i)))(_.toInstant) + given Schema[UserHandle] = Schema.derived[UserHandle] + given Schema[EventRecord] = Schema.derived[EventRecord] object Codecs extends Codecs diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 85124f0..880c7b6 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -1,5 +1,6 @@ package works.iterative.core import auth.UserId +import works.iterative.core.auth.CurrentUser /** 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 @@ -8,15 +9,26 @@ */ final case class UserHandle( userId: UserId, - userName: UserName -) + userName: Option[UserName] +): + val displayName: String = userName.map(_.value).getOrElse(userId.value) object UserHandle: + + given userHandleFromCurrentUser(using u: CurrentUser): UserHandle = u.handle + + def apply(userId: String): Validated[UserHandle] = + for id <- UserId(userId) + yield UserHandle(id, None) + def apply(userId: String, userName: String): Validated[UserHandle] = for id <- UserId(userId) name <- UserName(userName) - yield UserHandle(id, name) + yield UserHandle(id, Some(name)) + + def unsafe(userId: String): UserHandle = + UserHandle(UserId.unsafe(userId), None) def unsafe(userId: String, userName: String): UserHandle = - UserHandle(UserId.unsafe(userId), UserName.unsafe(userName)) + UserHandle(UserId.unsafe(userId), Some(UserName.unsafe(userName))) diff --git a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala index 710f0ec..47ef47f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala @@ -7,7 +7,8 @@ email: Option[Email], avatar: Option[Avatar], roles: Set[UserRole] -) extends UserProfile +) extends UserProfile: + val handle: UserHandle = UserHandle(subjectId, userName) object BasicProfile: def apply(p: UserProfile): BasicProfile = p match diff --git a/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala b/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala index ff99535..403e61f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala @@ -1,4 +1,20 @@ package works.iterative.core.auth +import zio.* +import scala.Conversion +import works.iterative.core.UserHandle + final case class CurrentUser(userProfile: BasicProfile) extends UserProfile: export userProfile.* + +object CurrentUser: + given Conversion[CurrentUser, BasicProfile] = _.userProfile + given Conversion[CurrentUser, UserHandle] = _.handle + + def use[A](f: CurrentUser ?=> A): URIO[CurrentUser, A] = + ZIO.serviceWith(f(using _)) + + def useZIO[R, E, A]( + f: CurrentUser ?=> ZIO[R, E, A] + ): ZIO[CurrentUser & R, E, A] = + ZIO.serviceWithZIO[CurrentUser](f(using _)) diff --git a/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala b/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala index 43b1841..da934c1 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala @@ -8,8 +8,19 @@ trait FileStore: type Op[A] = UIO[A] def store(file: FileRepr): Op[FileRef] + def store( + name: String, + file: Array[Byte], + contentType: Option[String] + ): Op[FileRef] object FileStore: type Op[A] = URIO[FileStore, A] def store(file: FileRepr): Op[FileRef] = ZIO.serviceWithZIO(_.store(file)) + def store( + name: String, + file: Array[Byte], + contentType: Option[String] + ): Op[FileRef] = + ZIO.serviceWithZIO(_.store(name, file, contentType)) diff --git a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala index 46f4dbe..7e5baeb 100644 --- a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala +++ b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala @@ -6,6 +6,7 @@ import zio.prelude.Validation import works.iterative.tapir.CustomTapir import works.iterative.core.auth.* +import works.iterative.event.EventRecord private[codecs] case class TextEncoding( pml: Option[PlainMultiLine], @@ -41,6 +42,13 @@ given JsonCodec[FileRef] = DeriveJsonCodec.gen[FileRef] + given JsonCodec[Moment] = JsonCodec.instant.transform( + Moment(_), + _.toInstant + ) + given JsonCodec[UserHandle] = DeriveJsonCodec.gen[UserHandle] + given JsonCodec[EventRecord] = DeriveJsonCodec.gen[EventRecord] + trait TapirCodecs extends CustomTapir: given Schema[PlainMultiLine] = Schema.string given Schema[PlainOneLine] = Schema.string @@ -52,5 +60,9 @@ given Schema[Email] = Schema.string given Schema[BasicProfile] = Schema.derived[BasicProfile] given Schema[FileRef] = Schema.derived[FileRef] + given Schema[Moment] = + Schema.schemaForInstant.map(i => Some(Moment(i)))(_.toInstant) + given Schema[UserHandle] = Schema.derived[UserHandle] + given Schema[EventRecord] = Schema.derived[EventRecord] object Codecs extends Codecs diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 85124f0..880c7b6 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -1,5 +1,6 @@ package works.iterative.core import auth.UserId +import works.iterative.core.auth.CurrentUser /** 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 @@ -8,15 +9,26 @@ */ final case class UserHandle( userId: UserId, - userName: UserName -) + userName: Option[UserName] +): + val displayName: String = userName.map(_.value).getOrElse(userId.value) object UserHandle: + + given userHandleFromCurrentUser(using u: CurrentUser): UserHandle = u.handle + + def apply(userId: String): Validated[UserHandle] = + for id <- UserId(userId) + yield UserHandle(id, None) + def apply(userId: String, userName: String): Validated[UserHandle] = for id <- UserId(userId) name <- UserName(userName) - yield UserHandle(id, name) + yield UserHandle(id, Some(name)) + + def unsafe(userId: String): UserHandle = + UserHandle(UserId.unsafe(userId), None) def unsafe(userId: String, userName: String): UserHandle = - UserHandle(UserId.unsafe(userId), UserName.unsafe(userName)) + UserHandle(UserId.unsafe(userId), Some(UserName.unsafe(userName))) diff --git a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala index 710f0ec..47ef47f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala @@ -7,7 +7,8 @@ email: Option[Email], avatar: Option[Avatar], roles: Set[UserRole] -) extends UserProfile +) extends UserProfile: + val handle: UserHandle = UserHandle(subjectId, userName) object BasicProfile: def apply(p: UserProfile): BasicProfile = p match diff --git a/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala b/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala index ff99535..403e61f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala @@ -1,4 +1,20 @@ package works.iterative.core.auth +import zio.* +import scala.Conversion +import works.iterative.core.UserHandle + final case class CurrentUser(userProfile: BasicProfile) extends UserProfile: export userProfile.* + +object CurrentUser: + given Conversion[CurrentUser, BasicProfile] = _.userProfile + given Conversion[CurrentUser, UserHandle] = _.handle + + def use[A](f: CurrentUser ?=> A): URIO[CurrentUser, A] = + ZIO.serviceWith(f(using _)) + + def useZIO[R, E, A]( + f: CurrentUser ?=> ZIO[R, E, A] + ): ZIO[CurrentUser & R, E, A] = + ZIO.serviceWithZIO[CurrentUser](f(using _)) diff --git a/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala b/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala index 43b1841..da934c1 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala @@ -8,8 +8,19 @@ trait FileStore: type Op[A] = UIO[A] def store(file: FileRepr): Op[FileRef] + def store( + name: String, + file: Array[Byte], + contentType: Option[String] + ): Op[FileRef] object FileStore: type Op[A] = URIO[FileStore, A] def store(file: FileRepr): Op[FileRef] = ZIO.serviceWithZIO(_.store(file)) + def store( + name: String, + file: Array[Byte], + contentType: Option[String] + ): Op[FileRef] = + ZIO.serviceWithZIO(_.store(name, file, contentType)) diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/InMemoryFileStore.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/InMemoryFileStore.scala index 9420dbf..e1c8b28 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/InMemoryFileStore.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/InMemoryFileStore.scala @@ -10,4 +10,10 @@ new FileStore: override def store(file: FileRepr): Op[FileRef] = ZIO.succeed(FileRef.unsafe(file.name, "#")) + override def store( + name: String, + file: Array[Byte], + contentType: Option[String] + ): Op[FileRef] = + ZIO.succeed(FileRef.unsafe(name, "#")) } diff --git a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala index 46f4dbe..7e5baeb 100644 --- a/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala +++ b/codecs/src/main/scala/works/iterative/core/codecs/Codecs.scala @@ -6,6 +6,7 @@ import zio.prelude.Validation import works.iterative.tapir.CustomTapir import works.iterative.core.auth.* +import works.iterative.event.EventRecord private[codecs] case class TextEncoding( pml: Option[PlainMultiLine], @@ -41,6 +42,13 @@ given JsonCodec[FileRef] = DeriveJsonCodec.gen[FileRef] + given JsonCodec[Moment] = JsonCodec.instant.transform( + Moment(_), + _.toInstant + ) + given JsonCodec[UserHandle] = DeriveJsonCodec.gen[UserHandle] + given JsonCodec[EventRecord] = DeriveJsonCodec.gen[EventRecord] + trait TapirCodecs extends CustomTapir: given Schema[PlainMultiLine] = Schema.string given Schema[PlainOneLine] = Schema.string @@ -52,5 +60,9 @@ given Schema[Email] = Schema.string given Schema[BasicProfile] = Schema.derived[BasicProfile] given Schema[FileRef] = Schema.derived[FileRef] + given Schema[Moment] = + Schema.schemaForInstant.map(i => Some(Moment(i)))(_.toInstant) + given Schema[UserHandle] = Schema.derived[UserHandle] + given Schema[EventRecord] = Schema.derived[EventRecord] object Codecs extends Codecs diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 85124f0..880c7b6 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -1,5 +1,6 @@ package works.iterative.core import auth.UserId +import works.iterative.core.auth.CurrentUser /** 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 @@ -8,15 +9,26 @@ */ final case class UserHandle( userId: UserId, - userName: UserName -) + userName: Option[UserName] +): + val displayName: String = userName.map(_.value).getOrElse(userId.value) object UserHandle: + + given userHandleFromCurrentUser(using u: CurrentUser): UserHandle = u.handle + + def apply(userId: String): Validated[UserHandle] = + for id <- UserId(userId) + yield UserHandle(id, None) + def apply(userId: String, userName: String): Validated[UserHandle] = for id <- UserId(userId) name <- UserName(userName) - yield UserHandle(id, name) + yield UserHandle(id, Some(name)) + + def unsafe(userId: String): UserHandle = + UserHandle(UserId.unsafe(userId), None) def unsafe(userId: String, userName: String): UserHandle = - UserHandle(UserId.unsafe(userId), UserName.unsafe(userName)) + UserHandle(UserId.unsafe(userId), Some(UserName.unsafe(userName))) diff --git a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala index 710f0ec..47ef47f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/BasicProfile.scala @@ -7,7 +7,8 @@ email: Option[Email], avatar: Option[Avatar], roles: Set[UserRole] -) extends UserProfile +) extends UserProfile: + val handle: UserHandle = UserHandle(subjectId, userName) object BasicProfile: def apply(p: UserProfile): BasicProfile = p match diff --git a/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala b/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala index ff99535..403e61f 100644 --- a/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala +++ b/core/shared/src/main/scala/works/iterative/core/auth/CurrentUser.scala @@ -1,4 +1,20 @@ package works.iterative.core.auth +import zio.* +import scala.Conversion +import works.iterative.core.UserHandle + final case class CurrentUser(userProfile: BasicProfile) extends UserProfile: export userProfile.* + +object CurrentUser: + given Conversion[CurrentUser, BasicProfile] = _.userProfile + given Conversion[CurrentUser, UserHandle] = _.handle + + def use[A](f: CurrentUser ?=> A): URIO[CurrentUser, A] = + ZIO.serviceWith(f(using _)) + + def useZIO[R, E, A]( + f: CurrentUser ?=> ZIO[R, E, A] + ): ZIO[CurrentUser & R, E, A] = + ZIO.serviceWithZIO[CurrentUser](f(using _)) diff --git a/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala b/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala index 43b1841..da934c1 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/FileStore.scala @@ -8,8 +8,19 @@ trait FileStore: type Op[A] = UIO[A] def store(file: FileRepr): Op[FileRef] + def store( + name: String, + file: Array[Byte], + contentType: Option[String] + ): Op[FileRef] object FileStore: type Op[A] = URIO[FileStore, A] def store(file: FileRepr): Op[FileRef] = ZIO.serviceWithZIO(_.store(file)) + def store( + name: String, + file: Array[Byte], + contentType: Option[String] + ): Op[FileRef] = + ZIO.serviceWithZIO(_.store(name, file, contentType)) diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/InMemoryFileStore.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/InMemoryFileStore.scala index 9420dbf..e1c8b28 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/InMemoryFileStore.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/InMemoryFileStore.scala @@ -10,4 +10,10 @@ new FileStore: override def store(file: FileRepr): Op[FileRef] = ZIO.succeed(FileRef.unsafe(file.name, "#")) + override def store( + name: String, + file: Array[Byte], + contentType: Option[String] + ): Op[FileRef] = + ZIO.succeed(FileRef.unsafe(name, "#")) } diff --git a/core/shared/src/main/scala/works/iterative/event/EventRecord.scala b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala index c8bdba8..490417a 100644 --- a/core/shared/src/main/scala/works/iterative/event/EventRecord.scala +++ b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala @@ -4,7 +4,8 @@ import zio.* /** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) +final case class EventRecord(userHandle: UserHandle, timestamp: Moment): + val displayName: String = userHandle.displayName object EventRecord: def apply(userHandle: UserHandle): UIO[EventRecord] =