From 41e9e797c1c682188c2fe62bffe054b268bd2c7e Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Thu, 8 Aug 2024 21:37:25 -0500 Subject: [PATCH] WIP --- build.sbt | 4 +- .../gs/smolban/db/doobie/DoobieTicketDb.scala | 54 +++++++++------ .../gs/smolban/db/doobie/DoobieTypes.scala | 68 +++++++++++++++++++ .../scala/gs/smolban/model/CreatedAt.scala | 16 +++++ .../src/main/scala/gs/smolban/model/Tag.scala | 2 +- 5 files changed, 119 insertions(+), 25 deletions(-) create mode 100644 modules/db/src/main/scala/gs/smolban/db/doobie/DoobieTypes.scala diff --git a/build.sbt b/build.sbt index fcbf622..8aa1307 100644 --- a/build.sbt +++ b/build.sbt @@ -38,10 +38,10 @@ val Deps = new { } val Gs = new { - val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.2.4" + val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.3.0" val Slug: ModuleID = "gs" %% "gs-slug-v0" % "0.1.3" val Config: ModuleID = "gs" %% "gs-config-v0" % "0.1.1" - val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.1.1" + val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.2.0" } val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.0.0-RC1" diff --git a/modules/db/src/main/scala/gs/smolban/db/doobie/DoobieTicketDb.scala b/modules/db/src/main/scala/gs/smolban/db/doobie/DoobieTicketDb.scala index 5395019..ebdf696 100644 --- a/modules/db/src/main/scala/gs/smolban/db/doobie/DoobieTicketDb.scala +++ b/modules/db/src/main/scala/gs/smolban/db/doobie/DoobieTicketDb.scala @@ -6,22 +6,28 @@ import doobie.* import doobie.implicits.* import gs.smolban.db.DbError import gs.smolban.db.TicketDb +import gs.smolban.db.doobie.DoobieTypes.* import gs.smolban.model.CreatedAt import gs.smolban.model.CreatedBy import gs.smolban.model.Group import gs.smolban.model.Ticket import gs.smolban.model.users.User -import gs.uuid.v0.UUIDFormat -import java.util.UUID -final class DoobieTicketDb[F[_]: Async] extends TicketDb[F]: +final class DoobieTicketDb[F[_]: Async]( + val xa: Transactor[F] +) extends TicketDb[F]: + import DoobieTicketDb.Sql + /** @inheritdoc */ override def createTicket(ticket: Ticket): EitherT[F, DbError, Ticket] = ??? /** @inheritdoc */ - override def readTicket(ref: Ticket.Reference): F[Option[Ticket]] = ??? + override def readTicket(ref: Ticket.Reference): F[Option[Ticket]] = + ??? + // Sql.readTicket(ref.id, ref.group).option.transact(xa) + // for compose multiple reads /** @inheritdoc */ @@ -36,27 +42,31 @@ final class DoobieTicketDb[F[_]: Async] extends TicketDb[F]: object DoobieTicketDb: - object Sql: + private object Sql: - implicit val ticketIdGet: Get[Ticket.Id] = Get[Long].tmap(Ticket.Id(_)) - implicit val ticketIdPut: Put[Ticket.Id] = Put[Long].tcontramap(_.toLong()) + case class TicketContents( + createdAt: CreatedAt, + createdBy: CreatedBy, + title: String, + description: String, + status: Ticket.Status, + assignee: Option[User.Id] + ) - implicit val groupIdGet: Get[Group.Id] = Get[Array[Byte]].tmap { bytes => - Group.Id(gs.uuid.v0.UUID(gs.uuid.v0.UUIDFormat.fromBytes(bytes))) - } + // TODO: figure out how to read tags + // also storing them + // make tagdb and such... - implicit val groupIdPut: Put[Group.Id] = Put[Array[Byte]].tcontramap { id => - gs.uuid.v0.UUIDFormat.toBytes(id.toUUID().toUUID) - } - - /* private case class TicketContents( createdAt: CreatedAt, createdBy: - * CreatedBy, title: String, description: String, status: Ticket.Status, - * assignee: Option[User.Id] ) - * - * def readTicket(ticketId: Ticket.Id, groupId: Group.Id) = sql""" SELECT - * created_at, created_by, title, description, status, assignee FROM tickets - * WHERE ticket_id = $ticketId AND group_id = $groupId - * """.query[TicketContents] */ + def readTicket( + ticketId: Ticket.Id, + groupId: Group.Id + ): Query0[TicketContents] = sql""" + SELECT + created_at, created_by, title, description, status, assignee + FROM tickets + WHERE + ticket_id = $ticketId AND group_id = $groupId + """.query[TicketContents] end Sql diff --git a/modules/db/src/main/scala/gs/smolban/db/doobie/DoobieTypes.scala b/modules/db/src/main/scala/gs/smolban/db/doobie/DoobieTypes.scala new file mode 100644 index 0000000..94a724e --- /dev/null +++ b/modules/db/src/main/scala/gs/smolban/db/doobie/DoobieTypes.scala @@ -0,0 +1,68 @@ +package gs.smolban.db.doobie + +import DoobieTypes.ErrorMessages +import doobie.* +import gs.smolban.model.CreatedAt +import gs.smolban.model.CreatedBy +import gs.smolban.model.Group +import gs.smolban.model.Ticket +import gs.smolban.model.users.User +import gs.uuid.v0.UUID +import gs.uuid.v0.UUIDFormat + +trait DoobieTypes: + + implicit val ticketIdGet: Get[Ticket.Id] = + Get[Long].tmap(Ticket.Id(_)) + + implicit val ticketIdPut: Put[Ticket.Id] = + Put[Long].tcontramap(_.toLong()) + + implicit val ticketStatusGet: Get[Ticket.Status] = + Get[String].temap(dbValue => + Ticket.Status.parse(dbValue) match + case None => Left(ErrorMessages.invalidTicketStatus(dbValue)) + case Some(status) => Right(status) + ) + + implicit val ticketStatusPut: Put[Ticket.Status] = + Put[String].tcontramap(_.name) + + implicit val groupIdGet: Get[Group.Id] = + Get[Array[Byte]].tmap(bytes => Group.Id(UUID(UUIDFormat.fromBytes(bytes)))) + + implicit val groupIdPut: Put[Group.Id] = + Put[Array[Byte]].tcontramap(id => UUIDFormat.toBytes(id.toUUID().toUUID())) + + implicit val userIdGet: Get[User.Id] = + Get[Array[Byte]].tmap(bytes => User.Id(UUID(UUIDFormat.fromBytes(bytes)))) + + implicit val userIdPut: Put[User.Id] = + Put[Array[Byte]].tcontramap(id => UUIDFormat.toBytes(id.toUUID().toUUID())) + + implicit val createdAtGet: Get[CreatedAt] = + Get[Long].tmap(CreatedAt.fromMilliseconds) + + implicit val createdAtPut: Put[CreatedAt] = + Put[Long].tcontramap(_.toMilliseconds()) + + implicit val createdByGet: Get[CreatedBy] = + Get[Array[Byte]].tmap(bytes => + CreatedBy(User.Id(UUID(UUIDFormat.fromBytes(bytes)))) + ) + + implicit val createdByPut: Put[CreatedBy] = + Put[Array[Byte]].tcontramap(id => + UUIDFormat.toBytes(id.toUserId().toUUID().toUUID()) + ) + +object DoobieTypes extends DoobieTypes: + + object ErrorMessages: + + def invalidTicketStatus(candidate: String): String = + s"'$candidate' is not a valid ticket status." + + end ErrorMessages + +end DoobieTypes diff --git a/modules/model/src/main/scala/gs/smolban/model/CreatedAt.scala b/modules/model/src/main/scala/gs/smolban/model/CreatedAt.scala index 085842a..876c260 100644 --- a/modules/model/src/main/scala/gs/smolban/model/CreatedAt.scala +++ b/modules/model/src/main/scala/gs/smolban/model/CreatedAt.scala @@ -19,6 +19,15 @@ object CreatedAt: */ def apply(timestamp: Instant): CreatedAt = timestamp + /** Instantiate a new [[CreatedAt]]. + * + * @param millis + * The epoch milliseconds describing the instant of this timestamp. + * @return + * The new instance. + */ + def fromMilliseconds(millis: Long): CreatedAt = Instant.ofEpochMilli(millis) + given CanEqual[CreatedAt, CreatedAt] = CanEqual.derived given Show[CreatedAt] = _.toInstant().toString() @@ -31,4 +40,11 @@ object CreatedAt: */ def toInstant(): Instant = createdAt + /** Express this instant as epoch milliseconds. + * + * @return + * The epoch milliseconds. + */ + def toMilliseconds(): Long = createdAt.toEpochMilli() + end CreatedAt diff --git a/modules/model/src/main/scala/gs/smolban/model/Tag.scala b/modules/model/src/main/scala/gs/smolban/model/Tag.scala index d836df2..fbc224d 100644 --- a/modules/model/src/main/scala/gs/smolban/model/Tag.scala +++ b/modules/model/src/main/scala/gs/smolban/model/Tag.scala @@ -7,7 +7,7 @@ import cats.Show * * Tags are defined _globally_ in Smolban. */ -opaque type Tag = String +opaque type Tag = String // TODO: Make this have a unique id (long?) and value object Tag: