From 9ae8c768858330aca8724d983363a7f873d1d7fc Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Sun, 19 May 2024 22:01:32 -0500 Subject: [PATCH 1/5] Working on the Smolban model. --- .../scala/gs/smolban/model/CreatedBy.scala | 24 ++++++++++ .../main/scala/gs/smolban/model/Group.scala | 2 + .../main/scala/gs/smolban/model/Status.scala | 38 +++++++++++++++ .../src/main/scala/gs/smolban/model/Tag.scala | 21 +++++++++ .../main/scala/gs/smolban/model/Ticket.scala | 10 +++- .../scala/gs/smolban/model/users/User.scala | 46 ++++++++++++++++++ .../gs/smolban/model/users/Username.scala | 47 +++++++++++++++++++ 7 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 modules/model/src/main/scala/gs/smolban/model/CreatedBy.scala create mode 100644 modules/model/src/main/scala/gs/smolban/model/Status.scala create mode 100644 modules/model/src/main/scala/gs/smolban/model/Tag.scala create mode 100644 modules/model/src/main/scala/gs/smolban/model/users/User.scala create mode 100644 modules/model/src/main/scala/gs/smolban/model/users/Username.scala diff --git a/modules/model/src/main/scala/gs/smolban/model/CreatedBy.scala b/modules/model/src/main/scala/gs/smolban/model/CreatedBy.scala new file mode 100644 index 0000000..6e4f27e --- /dev/null +++ b/modules/model/src/main/scala/gs/smolban/model/CreatedBy.scala @@ -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 diff --git a/modules/model/src/main/scala/gs/smolban/model/Group.scala b/modules/model/src/main/scala/gs/smolban/model/Group.scala index 3b66350..31994b6 100644 --- a/modules/model/src/main/scala/gs/smolban/model/Group.scala +++ b/modules/model/src/main/scala/gs/smolban/model/Group.scala @@ -22,6 +22,8 @@ case class Group( object Group: + given CanEqual[Group, Group] = CanEqual.derived + /** Unique identifier for a [[Group]]. This is an opaque type for a UUID. */ opaque type Id = UUID diff --git a/modules/model/src/main/scala/gs/smolban/model/Status.scala b/modules/model/src/main/scala/gs/smolban/model/Status.scala new file mode 100644 index 0000000..f62918d --- /dev/null +++ b/modules/model/src/main/scala/gs/smolban/model/Status.scala @@ -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 diff --git a/modules/model/src/main/scala/gs/smolban/model/Tag.scala b/modules/model/src/main/scala/gs/smolban/model/Tag.scala new file mode 100644 index 0000000..d836df2 --- /dev/null +++ b/modules/model/src/main/scala/gs/smolban/model/Tag.scala @@ -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 diff --git a/modules/model/src/main/scala/gs/smolban/model/Ticket.scala b/modules/model/src/main/scala/gs/smolban/model/Ticket.scala index f373bef..c63de5b 100644 --- a/modules/model/src/main/scala/gs/smolban/model/Ticket.scala +++ b/modules/model/src/main/scala/gs/smolban/model/Ticket.scala @@ -3,9 +3,15 @@ package gs.smolban.model import cats.Show case class Ticket( - id: Long, + id: Ticket.Id, group: Group.Id, - createdAt: CreatedAt + createdAt: CreatedAt, + createdBy: CreatedBy, + title: String, + description: String, + tags: List[Tag], + status: Status, + statusHistory: List[Status] ) object Ticket: diff --git a/modules/model/src/main/scala/gs/smolban/model/users/User.scala b/modules/model/src/main/scala/gs/smolban/model/users/User.scala new file mode 100644 index 0000000..ab2073d --- /dev/null +++ b/modules/model/src/main/scala/gs/smolban/model/users/User.scala @@ -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 diff --git a/modules/model/src/main/scala/gs/smolban/model/users/Username.scala b/modules/model/src/main/scala/gs/smolban/model/users/Username.scala new file mode 100644 index 0000000..a14b137 --- /dev/null +++ b/modules/model/src/main/scala/gs/smolban/model/users/Username.scala @@ -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 -- 2.43.0 From 45f61df4417ec7174a0f03ea0ae286a6dbb232fa Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Fri, 24 May 2024 21:54:01 -0500 Subject: [PATCH 2/5] Documentation and user concepts. --- .../main/scala/gs/smolban/model/Status.scala | 24 ++-- .../main/scala/gs/smolban/model/Ticket.scala | 21 ++++ .../scala/gs/smolban/model/users/Role.scala | 104 ++++++++++++++++++ .../scala/gs/smolban/model/users/Scope.scala | 78 +++++++++++++ .../scala/gs/smolban/model/users/User.scala | 17 ++- .../gs/smolban/model/users/UserType.scala | 19 ++++ 6 files changed, 253 insertions(+), 10 deletions(-) create mode 100644 modules/model/src/main/scala/gs/smolban/model/users/Role.scala create mode 100644 modules/model/src/main/scala/gs/smolban/model/users/Scope.scala create mode 100644 modules/model/src/main/scala/gs/smolban/model/users/UserType.scala diff --git a/modules/model/src/main/scala/gs/smolban/model/Status.scala b/modules/model/src/main/scala/gs/smolban/model/Status.scala index f62918d..b880796 100644 --- a/modules/model/src/main/scala/gs/smolban/model/Status.scala +++ b/modules/model/src/main/scala/gs/smolban/model/Status.scala @@ -1,38 +1,44 @@ package gs.smolban.model +import java.time.Instant + /** 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) +sealed trait Status: + def name: String + def enteredAt: Instant 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") + case class Ready(enteredAt: Instant) extends Status: + val name: String = "ready" /** This ticket is being worked on actively. In progress tickets can be * paused, completed, or canceled. */ - case object InProgress extends Status("in_progress") + case class InProgress(enteredAt: Instant) extends Status: + val name: String = "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") + case class Paused(enteredAt: Instant) extends Status: + val name: String = "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") + case class Complete(enteredAt: Instant) extends Status: + val name: String = "complete" /** This ticket was canceled for some reason. Once in this state, the status * may no longer change. */ - case object Canceled extends Status("canceled") + case class Canceled(enteredAt: Instant) extends Status: + val name: String = "canceled" end Status diff --git a/modules/model/src/main/scala/gs/smolban/model/Ticket.scala b/modules/model/src/main/scala/gs/smolban/model/Ticket.scala index c63de5b..47c82a7 100644 --- a/modules/model/src/main/scala/gs/smolban/model/Ticket.scala +++ b/modules/model/src/main/scala/gs/smolban/model/Ticket.scala @@ -2,6 +2,27 @@ package gs.smolban.model import cats.Show +/** Tickets represent some tracked work. + * + * @param id + * Unique identifier _within_ the [[Group]]. + * @param group + * Unique identifier of the [[Group]] that owns this ticket. + * @param createdAt + * The instant at which this ticket was created. + * @param createdBy + * The unique identifier of the [[User]] who created this ticket. + * @param title + * Arbitrary string title of the ticket. + * @param description + * Markdown contents of the ticket. + * @param tags + * List of [[Tag]] applied to this ticket. + * @param status + * Current [[Status]] of this ticket. + * @param statusHistory + * Linear history of this ticket in terms of status changes. + */ case class Ticket( id: Ticket.Id, group: Group.Id, diff --git a/modules/model/src/main/scala/gs/smolban/model/users/Role.scala b/modules/model/src/main/scala/gs/smolban/model/users/Role.scala new file mode 100644 index 0000000..afb9699 --- /dev/null +++ b/modules/model/src/main/scala/gs/smolban/model/users/Role.scala @@ -0,0 +1,104 @@ +package gs.smolban.model.users + +/** Roles define what each [[User]] is allowed to do. + * + * @param name + * The name of the role. + * @param scope + * The [[Scope]] to which the role applies. + */ +sealed abstract class Role( + val name: String, + val scope: Scope +): + + override def toString(): String = + s"[$name]${Role.Delimiter}$scope" + +object Role: + + val Delimiter: String = ":" + + /** Administrator for the API. Can perform any operation through the API. + */ + case object ApiAdmin extends Role(s"api${Delimiter}admin", Scope.ApiGlobal) + + /** Global read access to the API. + */ + case object ApiGlobalReader + extends Role(s"api${Delimiter}reader", Scope.ApiGlobal) + + /** Global write access to the API. + */ + case object ApiGlobalWriter + extends Role(s"api${Delimiter}writer", Scope.ApiGlobal) + + /** Administrator for a specific [[gs.smolban.model.Group]] via the API. Can + * perform any operation through the API for the indicated group. + * + * @param groupId + * The unique identifier for the group to which this role is scoped. + */ + case class ApiGroupAdmin( + groupId: gs.smolban.model.Group.Id + ) extends Role(s"api${Delimiter}group_admin", Scope.ApiGroup(groupId)) + + /** Grants API read access for a specific [[gs.smolban.model.Group]]. + * + * @param groupId + * The unique identifier for the group to which this role is scoped. + */ + case class ApiGroupReader( + groupId: gs.smolban.model.Group.Id + ) extends Role(s"api${Delimiter}group_reader", Scope.ApiGroup(groupId)) + + /** Grants API write access for a specific [[gs.smolban.model.Group]]. + * + * @param groupId + * The unique identifier for the group to which this role is scoped. + */ + case class ApiGroupWriter( + groupId: gs.smolban.model.Group.Id + ) extends Role(s"api${Delimiter}group_writer", Scope.ApiGroup(groupId)) + + /** Administrator for the UI. Can perform any operation through the UI. + */ + case object UiAdmin extends Role(s"ui${Delimiter}admin", Scope.UiGlobal) + + /** Global read access to the UI. + */ + case object UiGlobalReader extends Role("ui:reader", Scope.UiGlobal) + + /** Global write access to the UI. + */ + case object UiGlobalWriter extends Role("ui:writer", Scope.UiGlobal) + + /** Administrator for a specific [[gs.smolban.model.Group]] via the UI. Can + * perform any operation through the UI for the indicated group. + * + * @param groupId + * The unique identifier for the group to which this role is scoped. + */ + case class UiGroupAdmin( + groupId: gs.smolban.model.Group.Id + ) extends Role(s"ui${Delimiter}group_admin", Scope.UiGroup(groupId)) + + /** Grants UI read access for a specific [[gs.smolban.model.Group]]. + * + * @param groupId + * The unique identifier for the group to which this role is scoped. + */ + case class UiGroupReader( + groupId: gs.smolban.model.Group.Id + ) extends Role(s"ui${Delimiter}group_reader", Scope.UiGroup(groupId)) + + /** Grants UI write access for a specific [[gs.smolban.model.Group]]. + * + * @param groupId + * The unique identifier for the group to which this role is scoped. + */ + case class UiGroupWriter( + groupId: gs.smolban.model.Group.Id + ) extends Role(s"ui${Delimiter}group_writer", Scope.UiGroup(groupId)) + +end Role diff --git a/modules/model/src/main/scala/gs/smolban/model/users/Scope.scala b/modules/model/src/main/scala/gs/smolban/model/users/Scope.scala new file mode 100644 index 0000000..3ff5bc9 --- /dev/null +++ b/modules/model/src/main/scala/gs/smolban/model/users/Scope.scala @@ -0,0 +1,78 @@ +package gs.smolban.model.users + +import cats.syntax.all.* + +/** Describes a _Scope_, or service of Smolban, to which a user can have access. + * + * Scopes consist of two parts: + * + * - `service`: The name of a service Smolban offers. + * - `name`: The name of the scope within the given service. + * + * @param service + * The name of a service Smolban offers (e.g. `api`, `ui`) + * @param name + * The name of the scope within the service. + */ +sealed abstract class Scope( + val service: String, + val name: String +) + +object Scope: + + given CanEqual[Scope, Scope] = CanEqual.derived + + object Services: + + val Api: String = "api" + val Ui: String = "ui" + + end Services + + object Names: + + val Global: String = "global" + val Group: String = "group" + + end Names + + val Delimiter: String = ":" + + /** The `api:global` scope covers the Smolban API across all groups. + */ + case object ApiGlobal extends Scope(Services.Api, Names.Global): + override def toString(): String = s"[$service$Delimiter$name]" + + /** The `api:group:` scope refers the the Smolban API for a specific + * [[Group]]. + * + * @param groupId + * The unique identifier of the [[Group]]. + */ + case class ApiGroup( + groupId: gs.smolban.model.Group.Id + ) extends Scope(Services.Api, Names.Group): + + override def toString(): String = + s"[$service$Delimiter$name$Delimiter${groupId.show}]" + + /** The `ui:global` scope covers the Smolban UI across all groups. + */ + case object UiGlobal extends Scope(Services.Ui, Names.Global): + override def toString(): String = s"[$service$Delimiter$name]" + + /** The `ui:group:` scope refers the the Smolban UI for a specific + * [[Group]]. + * + * @param groupId + * The unique identifier of the [[Group]]. + */ + case class UiGroup( + groupId: gs.smolban.model.Group.Id + ) extends Scope(Services.Ui, Names.Group): + + override def toString(): String = + s"[$service$Delimiter$name$Delimiter${groupId.show}]" + +end Scope diff --git a/modules/model/src/main/scala/gs/smolban/model/users/User.scala b/modules/model/src/main/scala/gs/smolban/model/users/User.scala index ab2073d..24935b9 100644 --- a/modules/model/src/main/scala/gs/smolban/model/users/User.scala +++ b/modules/model/src/main/scala/gs/smolban/model/users/User.scala @@ -4,10 +4,25 @@ import cats.Show import gs.smolban.model.CreatedAt import gs.uuid.v0.UUID +/** Represents a user that can interact with Smolban. + * + * @param id + * Unique identifier of this user. + * @param createdAt + * The instant at which this user was created. + * @param username + * The (unique) [[Username]] of this user. + * @param userType + * The [[UserType]] of this user. + * @param roles + * List of [[Role]] assigned to this user. + */ case class User( id: User.Id, createdAt: CreatedAt, - username: Username + username: Username, + userType: UserType, + roles: List[Role] ) object User: diff --git a/modules/model/src/main/scala/gs/smolban/model/users/UserType.scala b/modules/model/src/main/scala/gs/smolban/model/users/UserType.scala new file mode 100644 index 0000000..ec3c8a6 --- /dev/null +++ b/modules/model/src/main/scala/gs/smolban/model/users/UserType.scala @@ -0,0 +1,19 @@ +package gs.smolban.model.users + +/** Enumeration that describes the type of a [[User]]. + */ +sealed abstract class UserType(val name: String) + +object UserType: + + /** Regular users are typically human, and are expected to be using the Web UI + * and _possibly_ APIs. + */ + case object Regular extends UserType("regular") + + /** Service users are intended for use by API-consuming services. They are not + * suitable for human/UI interactive use. + */ + case object Service extends UserType("service") + +end UserType -- 2.43.0 From d79d08dd686f1b4f0e2f2bb26d8cf4ae89ce5051 Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Mon, 1 Jul 2024 22:03:53 -0500 Subject: [PATCH 3/5] WIP --- build.sbt | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 43a1894..135a09f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -val scala3: String = "3.4.1" +val scala3: String = "3.4.2" ThisBuild / scalaVersion := scala3 ThisBuild / gsProjectName := "smolban" diff --git a/project/plugins.sbt b/project/plugins.sbt index 776be07..7daef75 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -29,5 +29,5 @@ externalResolvers := Seq( ) addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.8") -addSbtPlugin("gs" % "sbt-garrity-software" % "0.2.0") +addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0") addSbtPlugin("gs" % "sbt-gs-calver" % "0.2.0") -- 2.43.0 From 43abd31ededbd701ec859a5e3f80f803fc4f482d Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Tue, 2 Jul 2024 21:24:57 -0500 Subject: [PATCH 4/5] Reworking a bit and finalizing basic models. --- .../main/scala/gs/smolban/model/Group.scala | 5 +- .../main/scala/gs/smolban/model/Status.scala | 44 --------------- .../main/scala/gs/smolban/model/Ticket.scala | 54 +++++++++++++++++-- .../scala/gs/smolban/model/users/User.scala | 43 +++++++++++++-- .../gs/smolban/model/users/UserType.scala | 19 ------- 5 files changed, 94 insertions(+), 71 deletions(-) delete mode 100644 modules/model/src/main/scala/gs/smolban/model/Status.scala delete mode 100644 modules/model/src/main/scala/gs/smolban/model/users/UserType.scala diff --git a/modules/model/src/main/scala/gs/smolban/model/Group.scala b/modules/model/src/main/scala/gs/smolban/model/Group.scala index 31994b6..e9fa2aa 100644 --- a/modules/model/src/main/scala/gs/smolban/model/Group.scala +++ b/modules/model/src/main/scala/gs/smolban/model/Group.scala @@ -13,11 +13,14 @@ import gs.uuid.v0.UUID * The unique slug for the group. * @param createdAt * The instant at which this group was created. + * @param createdBy + * The unique identifier of the user who created this group. */ case class Group( id: Group.Id, slug: Slug, - createdAt: CreatedAt + createdAt: CreatedAt, + createdBy: CreatedBy ) object Group: diff --git a/modules/model/src/main/scala/gs/smolban/model/Status.scala b/modules/model/src/main/scala/gs/smolban/model/Status.scala deleted file mode 100644 index b880796..0000000 --- a/modules/model/src/main/scala/gs/smolban/model/Status.scala +++ /dev/null @@ -1,44 +0,0 @@ -package gs.smolban.model - -import java.time.Instant - -/** Enumeration that describes the status of a [[Ticket]] in Smolban. Smolban - * does not yet support custom status/workflow. - */ -sealed trait Status: - def name: String - def enteredAt: Instant - -object Status: - - /** This ticket is new, and ready to be started. New tickets may be put into - * progress or canceled. - */ - case class Ready(enteredAt: Instant) extends Status: - val name: String = "ready" - - /** This ticket is being worked on actively. In progress tickets can be - * paused, completed, or canceled. - */ - case class InProgress(enteredAt: Instant) extends Status: - val name: String = "in_progress" - - /** This ticket was being worked on, but was temporarily stopped. Paused - * tickets may be put into progress or canceled. - */ - case class Paused(enteredAt: Instant) extends Status: - val name: String = "paused" - - /** This ticket was driven to completion. The work is done. Once in this - * state, the status may no longer change. - */ - case class Complete(enteredAt: Instant) extends Status: - val name: String = "complete" - - /** This ticket was canceled for some reason. Once in this state, the status - * may no longer change. - */ - case class Canceled(enteredAt: Instant) extends Status: - val name: String = "canceled" - -end Status diff --git a/modules/model/src/main/scala/gs/smolban/model/Ticket.scala b/modules/model/src/main/scala/gs/smolban/model/Ticket.scala index 47c82a7..504019e 100644 --- a/modules/model/src/main/scala/gs/smolban/model/Ticket.scala +++ b/modules/model/src/main/scala/gs/smolban/model/Ticket.scala @@ -1,6 +1,8 @@ package gs.smolban.model import cats.Show +import gs.smolban.model.users.User +import java.time.Instant /** Tickets represent some tracked work. * @@ -19,9 +21,11 @@ import cats.Show * @param tags * List of [[Tag]] applied to this ticket. * @param status - * Current [[Status]] of this ticket. + * Current [[Ticket.Status]] of this ticket. * @param statusHistory * Linear history of this ticket in terms of status changes. + * @param assignee + * If set, this ticket is assigned to a specific user. */ case class Ticket( id: Ticket.Id, @@ -31,8 +35,9 @@ case class Ticket( title: String, description: String, tags: List[Tag], - status: Status, - statusHistory: List[Status] + status: Ticket.Status, + statusHistory: List[(Ticket.Status, Instant)], + assignee: Option[User.Id] ) object Ticket: @@ -68,4 +73,47 @@ object Ticket: end Id + /** Enumeration that describes the status of a [[Ticket]] in Smolban. Smolban + * does not yet support custom status/workflow. + */ + sealed abstract class Status(val name: String) + + object Status: + + given CanEqual[Status, Status] = CanEqual.derived + + given Show[Status] = _.name + + /** 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") + + val All: List[Status] = List(Ready, InProgress, Paused, Complete, Canceled) + + def parse(candidate: String): Option[Status] = + All.find(_.name.equalsIgnoreCase(candidate)) + + end Status + end Ticket diff --git a/modules/model/src/main/scala/gs/smolban/model/users/User.scala b/modules/model/src/main/scala/gs/smolban/model/users/User.scala index 24935b9..1c56f8e 100644 --- a/modules/model/src/main/scala/gs/smolban/model/users/User.scala +++ b/modules/model/src/main/scala/gs/smolban/model/users/User.scala @@ -12,17 +12,20 @@ import gs.uuid.v0.UUID * The instant at which this user was created. * @param username * The (unique) [[Username]] of this user. - * @param userType - * The [[UserType]] of this user. + * @param designation + * The [[User.Designation]] of this user. * @param roles * List of [[Role]] assigned to this user. + * @param status + * The current [[User.Status]] of this user. */ case class User( id: User.Id, createdAt: CreatedAt, username: Username, - userType: UserType, - roles: List[Role] + designation: User.Designation, + roles: List[Role], + status: User.Status ) object User: @@ -58,4 +61,36 @@ object User: end Id + /** Enumeration that describes the designation of a [[User]]. + */ + sealed abstract class Designation(val name: String) + + object Designation: + + /** Regular users are typically human, and are expected to be using the Web + * UI and _possibly_ APIs. + */ + case object Regular extends Designation("regular") + + /** Service users are intended for use by API-consuming services. They are + * not suitable for human/UI interactive use. + */ + case object Service extends Designation("service") + + end Designation + + /** Enumeration that describes the status of a [[User]] in Smolban. + */ + sealed abstract class Status(val name: String) + + object Status: + + given CanEqual[Status, Status] = CanEqual.derived + + given Show[Status] = _.name + + case object Active extends Status("active") + case object Suspended extends Status("suspended") + case object Off extends Status("off") + end User diff --git a/modules/model/src/main/scala/gs/smolban/model/users/UserType.scala b/modules/model/src/main/scala/gs/smolban/model/users/UserType.scala deleted file mode 100644 index ec3c8a6..0000000 --- a/modules/model/src/main/scala/gs/smolban/model/users/UserType.scala +++ /dev/null @@ -1,19 +0,0 @@ -package gs.smolban.model.users - -/** Enumeration that describes the type of a [[User]]. - */ -sealed abstract class UserType(val name: String) - -object UserType: - - /** Regular users are typically human, and are expected to be using the Web UI - * and _possibly_ APIs. - */ - case object Regular extends UserType("regular") - - /** Service users are intended for use by API-consuming services. They are not - * suitable for human/UI interactive use. - */ - case object Service extends UserType("service") - -end UserType -- 2.43.0 From e0ff515b062e07a45bcc2d18ce3cc35a3408e03c Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Tue, 2 Jul 2024 21:27:24 -0500 Subject: [PATCH 5/5] Minimizing the release build. --- .forgejo/workflows/release.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml index 618672a..ba6fd0b 100644 --- a/.forgejo/workflows/release.yaml +++ b/.forgejo/workflows/release.yaml @@ -44,7 +44,8 @@ jobs: sbtn coverageOff sbtn clean sbtn api/calVerWriteVersionToFile - sbtn compile + selected_version="$(cat .version)" + echo "PRODUCING A RELEASE IS CURRENTLY TURNED OFF -- $selected_version" - name: 'Create Git Tag' run: | selected_version="$(cat .version)" -- 2.43.0