Add commands engine

This commit is contained in:
Paul-Henri Froidmont 2025-02-28 05:40:32 +01:00
parent 1919e4b72c
commit 91584c18d5
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
9 changed files with 336 additions and 58 deletions

View file

@ -1,71 +1,22 @@
package lu.foyer
import lu.foyer.clients.ClientState
import lu.foyer.clients.*
import zio.*
import zio.Console.*
import zio.http.*
import zio.schema.*
import zio.http.endpoint.*
import zio.http.codec.*
import java.net.URI
import zio.http.codec.PathCodec.path
import zio.http.endpoint.*
import zio.http.endpoint.openapi.OpenAPIGen
import zio.http.endpoint.openapi.SwaggerUI
import zio.http.codec.PathCodec.path
import zio.schema.*
case class JsonApiResponseSingle[T](
data: JsonApiResponseEntity[T],
links: JsonApiResponseLinks)
derives Schema
case class JsonApiResponseMultiple[T](
data: List[JsonApiResponseEntity[T]],
links: JsonApiResponseLinks,
meta: JsonApiResponseMeta)
derives Schema
case class JsonApiResponseLinks(
self: String,
first: Option[String] = None,
prev: Option[String] = None,
next: Option[String] = None,
last: Option[String] = None)
derives Schema
case class JsonApiResponseMeta(totalRecords: Int, totalPages: Int) derives Schema
final case class JsonApiResponseEntity[T](id: String, `type`: String, attributes: T) derives Schema
case class Page(number: Int, size: Int, totals: Boolean)
val pageParams =
(HttpCodec.query[Option[Int]]("page[number]")
& HttpCodec.query[Option[Int]]("page[size]")
& HttpCodec.query[Option[Boolean]]("page[totals]"))
.transform[Page]((number, size, totals) =>
Page(number.getOrElse(0), size.getOrElse(50), totals.getOrElse(false))
)(p => (Some(p._1), Some(p._2), Some(p._3)))
object ClientsController:
private val fetchMany =
Endpoint(Method.GET / "clients").query(pageParams).out[JsonApiResponseMultiple[ClientState]]
private val fetchManyRoute =
fetchMany.implement(page =>
ZIO.succeed(
JsonApiResponseMultiple[ClientState](
List.empty,
JsonApiResponseLinks("https://api.example.org"),
meta = JsonApiResponseMeta(0, 1)
)
)
)
val endpoints = List(fetchMany)
val routes = Routes(fetchManyRoute)
import java.net.URI
import java.time.LocalDate
import java.util.UUID
object App extends ZIOAppDefault:
val openAPI = OpenAPIGen.fromEndpoints(ClientsController.endpoints)
val routes = ClientsController.routes ++ SwaggerUI.routes("docs" / "openapi", openAPI)
val openAPI = OpenAPIGen.fromEndpoints(ClientController.endpoints)
val routes = ClientController.routes ++ SwaggerUI.routes("docs" / "openapi", openAPI)
override def run = Server.serve(routes).provide(Server.default)

View file

@ -0,0 +1,174 @@
package lu.foyer
package clients
import zio.*
import zio.Console.*
import zio.http.*
import zio.http.codec.*
import zio.http.codec.PathCodec.path
import zio.http.endpoint.*
import zio.schema.*
import java.net.URI
import java.time.LocalDate
import java.util.UUID
object JsonApiResponse:
case class One[T](
data: Entity[T],
links: Links)
derives Schema
case class Many[T](
data: List[Entity[T]],
links: Links,
meta: Meta)
derives Schema
case class Links(
self: String,
first: Option[String] = None,
prev: Option[String] = None,
next: Option[String] = None,
last: Option[String] = None)
derives Schema
case class Meta(totalRecords: Option[Long], totalPages: Option[Long]) derives Schema
case class Entity[T](id: String, `type`: String, attributes: T) derives Schema
case class Error(message: String) derives Schema // TODO
val pageParams =
(HttpCodec.query[Option[Int]]("page[number]")
& HttpCodec.query[Option[Int]]("page[size]")
& HttpCodec.query[Option[Boolean]]("page[totals]"))
.transform[Page]((number, size, totals) =>
Page(number.getOrElse(0), size.getOrElse(50), totals.getOrElse(false))
)(p => (Some(p._1), Some(p._2), Some(p._3)))
trait CommandEngineController[Command: Schema, Event: Schema, State: Schema](
entityName: String,
commands: List[Command],
commandEngine: CommandEngine[Command, Event, State]):
private val fetchMany =
Endpoint(Method.GET / entityName)
.query(pageParams)
.out[JsonApiResponse.Many[State]]
.outError[JsonApiResponse.Error](Status.InternalServerError)
private val fetchOne =
Endpoint(Method.GET / entityName / string("entityId"))
.out[JsonApiResponse.One[State]]
private val fetchEventsMany =
Endpoint(Method.GET / entityName / string("entityId") / "events")
.query(pageParams)
.out[JsonApiResponse.Many[Event]]
private val fetchEventsOne =
Endpoint(Method.GET / entityName / string("entityId") / "events" / string("eventId"))
.out[JsonApiResponse.One[Event]]
private val commandsEndpoints = commands.map(command =>
Endpoint(Method.POST / "clients" / "commands" / command.toString.toLowerCase)
.in[Command]
.out[JsonApiResponse.One[Event]]
)
private val fetchManyRoute =
fetchMany.implement(page =>
commandEngine.stateRepo
.fetchMany(page)
.map(paged =>
JsonApiResponse.Many(
paged.items.map(entity =>
JsonApiResponse.Entity(entity.entityId.toString, "todo", entity.data)
),
JsonApiResponse.Links("https://api.example.org"),
meta = JsonApiResponse.Meta(paged.totals, None) // TODO
)
).mapError(e => JsonApiResponse.Error(e.getMessage))
)
end CommandEngineController
object ClientController:
private val fetchMany =
Endpoint(Method.GET / "clients")
.query(pageParams)
.out[JsonApiResponse.Many[ClientState]]
private val fetchOne =
Endpoint(Method.GET / "clients" / string("entityId"))
.out[JsonApiResponse.One[ClientState]]
private val createCommand =
Endpoint(Method.POST / "clients" / "commands" / "create")
.in[ClientCommand.Create]
.out[JsonApiResponse.One[ClientEvent]]
private val updateCommand =
Endpoint(Method.PUT / "clients" / string("entityId") / "commands" / "udpate")
.in[ClientCommand.Update]
.out[JsonApiResponse.One[ClientEvent]]
private val disableCommand =
Endpoint(Method.PUT / "clients" / string("entityId") / "commands" / "disable")
.in[ClientCommand.Disable]
.out[JsonApiResponse.One[ClientEvent]]
private val fetchEventsMany =
Endpoint(Method.GET / "clients" / string("entityId") / "events")
.query(pageParams)
.out[JsonApiResponse.Many[ClientEvent]]
private val fetchEventsOne =
Endpoint(Method.GET / "clients" / string("entityId") / "events" / string("eventId"))
.out[JsonApiResponse.One[ClientEvent]]
private val fetchManyRoute =
fetchMany.implement(page =>
ZIO.succeed(
JsonApiResponse.Many[ClientState](
List.empty,
JsonApiResponse.Links("https://api.example.org"),
meta = JsonApiResponse.Meta(None, None)
)
)
)
private val createCommandRoute =
createCommand.implement(command =>
ZIO.succeed(
JsonApiResponse.One[ClientEvent](
JsonApiResponse.Entity(
id = "todo",
`type` = "todo",
ClientEvent.Created(
lastName = command.lastName,
firstName = command.firstName,
birthDate = command.birthDate,
drivingLicenseDate = command.drivingLicenseDate,
phoneNumber = command.phoneNumber,
email = command.email,
address = command.address
)
),
JsonApiResponse.Links("https://api.example.org")
)
)
)
val endpoints = List(
fetchMany,
fetchOne,
createCommand,
updateCommand,
disableCommand,
fetchEventsMany,
fetchEventsOne
)
val routes = Routes(fetchManyRoute)
end ClientController