Working on the Smolban model.
This commit is contained in:
parent
d10dcf4fb3
commit
9ae8c76885
7 changed files with 186 additions and 2 deletions
|
@ -0,0 +1,24 @@
|
||||||
|
package gs.smolban.model
|
||||||
|
|
||||||
|
import cats.Show
|
||||||
|
import gs.smolban.model.users.User
|
||||||
|
|
||||||
|
opaque type CreatedBy = User.Id
|
||||||
|
|
||||||
|
object CreatedBy:
|
||||||
|
|
||||||
|
def apply(timestamp: User.Id): CreatedBy = timestamp
|
||||||
|
|
||||||
|
given CanEqual[CreatedBy, CreatedBy] = CanEqual.derived
|
||||||
|
|
||||||
|
given Show[CreatedBy] = _.toUserId().toUUID().withoutDashes()
|
||||||
|
|
||||||
|
extension (createdAt: CreatedBy)
|
||||||
|
/** Unwrap this value.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The underlying `User.Id` value.
|
||||||
|
*/
|
||||||
|
def toUserId(): User.Id = createdAt
|
||||||
|
|
||||||
|
end CreatedBy
|
|
@ -22,6 +22,8 @@ case class Group(
|
||||||
|
|
||||||
object Group:
|
object Group:
|
||||||
|
|
||||||
|
given CanEqual[Group, Group] = CanEqual.derived
|
||||||
|
|
||||||
/** Unique identifier for a [[Group]]. This is an opaque type for a UUID.
|
/** Unique identifier for a [[Group]]. This is an opaque type for a UUID.
|
||||||
*/
|
*/
|
||||||
opaque type Id = UUID
|
opaque type Id = UUID
|
||||||
|
|
38
modules/model/src/main/scala/gs/smolban/model/Status.scala
Normal file
38
modules/model/src/main/scala/gs/smolban/model/Status.scala
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package gs.smolban.model
|
||||||
|
|
||||||
|
/** Enumeration that describes the status of a [[Ticket]] in Smolban. Smolban
|
||||||
|
* does not yet support custom status/workflow.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The string value of the status.
|
||||||
|
*/
|
||||||
|
sealed abstract class Status(val value: String)
|
||||||
|
|
||||||
|
object Status:
|
||||||
|
|
||||||
|
/** This ticket is new, and ready to be started. New tickets may be put into
|
||||||
|
* progress or canceled.
|
||||||
|
*/
|
||||||
|
case object Ready extends Status("ready")
|
||||||
|
|
||||||
|
/** This ticket is being worked on actively. In progress tickets can be
|
||||||
|
* paused, completed, or canceled.
|
||||||
|
*/
|
||||||
|
case object InProgress extends Status("in_progress")
|
||||||
|
|
||||||
|
/** This ticket was being worked on, but was temporarily stopped. Paused
|
||||||
|
* tickets may be put into progress or canceled.
|
||||||
|
*/
|
||||||
|
case object Paused extends Status("paused")
|
||||||
|
|
||||||
|
/** This ticket was driven to completion. The work is done. Once in this
|
||||||
|
* state, the status may no longer change.
|
||||||
|
*/
|
||||||
|
case object Complete extends Status("complete")
|
||||||
|
|
||||||
|
/** This ticket was canceled for some reason. Once in this state, the status
|
||||||
|
* may no longer change.
|
||||||
|
*/
|
||||||
|
case object Canceled extends Status("canceled")
|
||||||
|
|
||||||
|
end Status
|
21
modules/model/src/main/scala/gs/smolban/model/Tag.scala
Normal file
21
modules/model/src/main/scala/gs/smolban/model/Tag.scala
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package gs.smolban.model
|
||||||
|
|
||||||
|
import cats.Show
|
||||||
|
|
||||||
|
/** Opaque type for a String that represents a unique `Tag` in Smolban. Tags are
|
||||||
|
* just arbitrary non-empty strings which can be used to annotate [[Ticket]].
|
||||||
|
*
|
||||||
|
* Tags are defined _globally_ in Smolban.
|
||||||
|
*/
|
||||||
|
opaque type Tag = String
|
||||||
|
|
||||||
|
object Tag:
|
||||||
|
|
||||||
|
def validate(candidate: String): Option[Tag] =
|
||||||
|
if candidate.isEmpty() then None else Some(candidate)
|
||||||
|
|
||||||
|
given CanEqual[Tag, Tag] = CanEqual.derived
|
||||||
|
|
||||||
|
given Show[Tag] = t => t
|
||||||
|
|
||||||
|
end Tag
|
|
@ -3,9 +3,15 @@ package gs.smolban.model
|
||||||
import cats.Show
|
import cats.Show
|
||||||
|
|
||||||
case class Ticket(
|
case class Ticket(
|
||||||
id: Long,
|
id: Ticket.Id,
|
||||||
group: Group.Id,
|
group: Group.Id,
|
||||||
createdAt: CreatedAt
|
createdAt: CreatedAt,
|
||||||
|
createdBy: CreatedBy,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
tags: List[Tag],
|
||||||
|
status: Status,
|
||||||
|
statusHistory: List[Status]
|
||||||
)
|
)
|
||||||
|
|
||||||
object Ticket:
|
object Ticket:
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package gs.smolban.model.users
|
||||||
|
|
||||||
|
import cats.Show
|
||||||
|
import gs.smolban.model.CreatedAt
|
||||||
|
import gs.uuid.v0.UUID
|
||||||
|
|
||||||
|
case class User(
|
||||||
|
id: User.Id,
|
||||||
|
createdAt: CreatedAt,
|
||||||
|
username: Username
|
||||||
|
)
|
||||||
|
|
||||||
|
object User:
|
||||||
|
|
||||||
|
given CanEqual[User, User] = CanEqual.derived
|
||||||
|
|
||||||
|
/** Unique identifier for a [[User]]. This is an opaque type for a UUID.
|
||||||
|
*/
|
||||||
|
opaque type Id = UUID
|
||||||
|
|
||||||
|
object Id:
|
||||||
|
|
||||||
|
/** Instantiate a new [[User.Id]].
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* The underlying UUID.
|
||||||
|
* @return
|
||||||
|
* The new [[User.Id]] instance.
|
||||||
|
*/
|
||||||
|
def apply(id: UUID): Id = id
|
||||||
|
|
||||||
|
given CanEqual[Id, Id] = CanEqual.derived
|
||||||
|
|
||||||
|
given Show[Id] = _.toUUID().withoutDashes()
|
||||||
|
|
||||||
|
extension (id: Id)
|
||||||
|
/** Unwrap this User ID.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The underlying UUID value.
|
||||||
|
*/
|
||||||
|
def toUUID(): UUID = id
|
||||||
|
|
||||||
|
end Id
|
||||||
|
|
||||||
|
end User
|
|
@ -0,0 +1,47 @@
|
||||||
|
package gs.smolban.model.users
|
||||||
|
|
||||||
|
import cats.Show
|
||||||
|
|
||||||
|
/** Opaque type for String that represents a unique username in Smolban.
|
||||||
|
*/
|
||||||
|
opaque type Username = String
|
||||||
|
|
||||||
|
object Username:
|
||||||
|
|
||||||
|
/** In Smolban, a [[Username]] must be at most 32 characters long.
|
||||||
|
*/
|
||||||
|
val MaximumLength: Int = 32
|
||||||
|
|
||||||
|
/** In Smolban, a [[Username]] must be at least 3 characters long.
|
||||||
|
*/
|
||||||
|
val MinimumLength: Int = 3
|
||||||
|
|
||||||
|
given CanEqual[Username, Username] = CanEqual.derived
|
||||||
|
|
||||||
|
/** Validate some candidate string, producing a [[Username]] if valid. Smolban
|
||||||
|
* usernames must be:
|
||||||
|
*
|
||||||
|
* - At least 3 characters long.
|
||||||
|
* - At most 32 characters long.
|
||||||
|
* - Non-blank -- non-whitespace characters must be used.
|
||||||
|
*
|
||||||
|
* @param candidate
|
||||||
|
* The candidate string to evaluate.
|
||||||
|
* @return
|
||||||
|
* The [[Username]], or `None` if the candidate was invalid.
|
||||||
|
*/
|
||||||
|
def validate(candidate: String): Option[Username] =
|
||||||
|
if isValid(candidate) then Some(candidate) else None
|
||||||
|
|
||||||
|
private def isValid(candidate: String): Boolean =
|
||||||
|
isValidSize(candidate) && isNonBlank(candidate)
|
||||||
|
|
||||||
|
private def isValidSize(candidate: String): Boolean =
|
||||||
|
candidate.length() >= MinimumLength && candidate.length() <= MaximumLength
|
||||||
|
|
||||||
|
private def isNonBlank(candidate: String): Boolean =
|
||||||
|
!candidate.isBlank()
|
||||||
|
|
||||||
|
given Show[Username] = u => u
|
||||||
|
|
||||||
|
end Username
|
Loading…
Add table
Reference in a new issue