All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.kodeworks.kvarg.util.DomainRouter.scala Maven / Gradle / Ivy

There is a newer version: 0.7
Show newest version
package no.kodeworks.kvarg.util

import akka.actor.{Actor, ActorLogging, ActorRef}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.{ActorMaterializer, Materializer}
import akka.util.Timeout
import no.kodeworks.kvarg.message._
import no.kodeworks.kvarg.model.{HasId, Page}
import no.kodeworks.kvarg.patch.{Patch, Pvalue}
import no.kodeworks.kvarg.util.AtLeastOnceDelivery.pattern
import io.circe.syntax._
import io.circe.{Decoder, Encoder, Json}
import shapeless.ops.hlist.Length
import shapeless.ops.record.UnzipFields
import shapeless._

import scala.concurrent.Future
import no.kodeworks.kvarg.util.Directives._
import cats.syntax.option._
import Crud._
import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
import no.kodeworks.kvarg.util._
/*
This is neccessary when calling apply:
import io.circe.generic.auto._
 */
sealed trait DomainsRouter[Domain <: HList] {
  type DomainRouters <: HList
  type ServicesFields <: HList

  def patchEncoders: Map[String, Encoder[Patch[_ <: Any]]]

  def route(
             actor: Actor with ActorLogging,
             page: Page,
             materializer: ActorMaterializer,
             timeout: Timeout
           ): Route
}


object DomainsRouter {
  def apply[Domains <: HList]
  (implicit mkMkDomainsRouter: MkMkDomainsRouter[Domains]
  ): mkMkDomainsRouter.type = mkMkDomainsRouter

  sealed trait MkMkDomainsRouter[Domains <: HList] {
    type DomainRouters <: HList
    type Out <: MkDomainsRouter[_]

    def apply(domainRouters: DomainRouters): Out
  }

  object MkMkDomainsRouter {
    type Aux[
    Domains <: HList,
    DomainRouters0 <: HList,
    Out0 <: MkDomainsRouter[_]
    ] = MkMkDomainsRouter[Domains] {
      type DomainRouters = DomainRouters0
      type Out = Out0
    }
  }

  implicit def mkMkDomainsRouterHNil: MkMkDomainsRouter.Aux[HNil, HNil, MkDomainsRouter.Aux[HNil, HNil]] =
    new MkMkDomainsRouter[HNil] {
      override type DomainRouters = HNil
      override type Out = MkDomainsRouter.Aux[HNil, HNil]

      override def apply(domainRouters: DomainRouters): Out = {
        new MkDomainsRouter[HNil] {
          override type DomainRouters0 = HNil

          override def domainRouters0 = HNil
        }
      }
    }

  implicit def mkMkDomainsRouterHCons[DomainHead <: HList, DomainsTail <: HList]
  (implicit
   mkMkDomainsRouterTail: MkMkDomainsRouter[DomainsTail]
  ): MkMkDomainsRouter.Aux[
    DomainHead :: DomainsTail,
    DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters,
    MkDomainsRouter.Aux[DomainHead :: DomainsTail, DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters]
    ]
  = new MkMkDomainsRouter[DomainHead :: DomainsTail] {
    override type DomainRouters = DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters
    override type Out = MkDomainsRouter.Aux[DomainHead :: DomainsTail, DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters]

    override def apply(domainRouters: DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters): Out = {
      new MkDomainsRouter[DomainHead :: DomainsTail] {
        override type DomainRouters0 = DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters

        override def domainRouters0 = domainRouters
      }
    }
  }

  sealed trait MkDomainsRouter[Domains <: HList] {
    type DomainRouters0 <: HList

    def domainRouters0: DomainRouters0

    def apply[
    Services
    , ServicesFields0 <: HList
    , ServicesKeys0 <: HList
    , KeysRouters0 <: HList
    , Timeouts <: HList
    , Actors <: HList
    , Mats <: HList
    , ServicesValues0 <: HList
    , ServicesLength <: Nat
    , ServicesDomainRouters0 <: HList
    ](services0: Services)
     (implicit
      fields: LabelledGeneric.Aux[Services, ServicesFields0]
      , keysValues: UnzipFields.Aux[ServicesFields0, ServicesKeys0, ServicesValues0]
      , servicesLength: Length.Aux[ServicesFields0, ServicesLength]
      , domainsLength: Length.Aux[DomainRouters0, ServicesLength]
     )
    : DomainsRouter[Domains] = {
      val routers = hlistToList[DomainRouter[_ <: HList]](domainRouters0)
      val services = hlistToList[ActorRef](keysValues.values(fields.to(services0)))
      val names = hlistToList[Symbol](keysValues.keys()).map(_.name)
      val routersServicesNames: List[(DomainRouter[_ <: HList], ActorRef, String)] =
        (routers zip services zip names) map { case ((r, s), n) => (r, s, n) }

      new DomainsRouter[Domains] {
        override type DomainRouters = DomainRouters0

        override def patchEncoders: Map[String, Encoder[Patch[_]]] =
          routers.map(_.patchEncoders).reduce(_ ++ _)

        override def route(
                            actor: Actor with ActorLogging,
                            page: Page,
                            mat: ActorMaterializer,
                            timeout: Timeout
                          ): Route = {
          import actor.context.dispatcher

          def getAllResult: Future[Json] = routersServicesNames.map {
            case (router, service, name) =>
              actor.log.debug("GetAllResult router {}, service {}, name {}", router, service.path.name, name)
              import AtLeastOnceDelivery.pattern
              import actor.context.dispatcher
              val getAll: Future[GetAllReply[router.DomainList]] = (service.??(GetAll)(
                actor.self, actor.context.system, timeout
              )).mapTo[GetAllReply[router.DomainList]]
              val toJson: Future[Json] = getAll.map(router.getAllReplyEncoder.apply)
              toJson
          } reduce ((f0, f1) =>
            (f0 zip f1).map { case (j0, j1) => j0 deepMerge j1 })

          val domainRoutes: Route = routersServicesNames.map {
            case (router, service, _) => router.route(actor, service, page, timeout, mat)
          } reduce (_ ~ _)

          path("all") {
            import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
            complete(getAllResult)
          } ~ domainRoutes
        }
      }
    }
  }

  object MkDomainsRouter {
    type Aux[
    Domains <: HList,
    DomainRouters <: HList
    ] = MkDomainsRouter[Domains] {
      type DomainRouters0 = DomainRouters
    }
  }

}

sealed trait DomainRouter[Domain <: HList] {
  type DomainList <: HList

  def patchEncoders: Map[String, Encoder[Patch[_ <: Any]]]

  def getAllReplyEncoder: Encoder[GetAllReply[DomainList]]

  def getAllRoute(actor: Actor with ActorLogging,
                  service: ActorRef,
                  timeout: Timeout,
                  mat: Materializer): Route

  def eachRoute(actor: Actor with ActorLogging,
                service: ActorRef,
                page: Page,
                timeout: Timeout,
                mat: Materializer
               ): Route

  def route(actor: Actor with ActorLogging,
            service: ActorRef,
            page: Page,
            timeout: Timeout,
            mat: Materializer): Route
}

object DomainRouter {
  type Aux[
  Domain <: HList,
  DomainList0 <: HList] =
    DomainRouter[Domain] {
      type DomainList = DomainList0
    }

  def apply[Domain <: HList]
  (implicit domainRouter: DomainRouter[Domain]
  ): domainRouter.type = domainRouter

  implicit def domainRouterHNil: Aux[HNil, HNil] =
    new DomainRouter[HNil] {
      override type DomainList = HNil

      override def toString = "HNilDomainRouter"

      override def patchEncoders = Map.empty

      override def getAllReplyEncoder: Encoder[GetAllReply[DomainList]] =
        _ => Json.obj()

      override def eachRoute(actor: Actor with ActorLogging,
                             service: ActorRef,
                             page: Page,
                             timeout: Timeout,
                             mat: Materializer): Route =
        reject

      override def getAllRoute(actor: Actor with ActorLogging,
                               service: ActorRef,
                               timeout: Timeout,
                               mat: Materializer): Route =
        reject

      override def route(actor: Actor with ActorLogging,
                         service: ActorRef,
                         page: Page,
                         timeout: Timeout,
                         mat: Materializer): Route =
        reject
    }


  implicit def domainRouterHCons[ModelHead, ModelTail <: HList]
  (implicit
   domainRouterTail: DomainRouter[ModelTail]
   , enc: Encoder[ModelHead]
   , dec: Decoder[ModelHead]
   , penc: Encoder[Patch[ModelHead]]
   , pdec: Decoder[Patch[ModelHead]]
   , unmarsh: FromEntityUnmarshaller[ModelHead]
   , marsh: ToEntityMarshaller[ModelHead]
   , pUnmarsh: FromEntityUnmarshaller[Patch[ModelHead]]
   , pMarsh: ToEntityMarshaller[Patch[ModelHead]]
   , listMarsh: ToEntityMarshaller[List[ModelHead]]
   , tr0: ToEntityMarshaller[SaveInserted[ModelHead]]
   , tr1: ToEntityMarshaller[SaveUpdated[ModelHead]]
   , tr2: ToEntityMarshaller[IncompletelyUpdated[ModelHead]]
   , tr3: ToEntityMarshaller[UpdateStashed[ModelHead]]
   , tt: Typeable[ModelHead]
   , hasId: HasId[ModelHead]
  ): Aux[ModelHead :: ModelTail, List[ModelHead] :: domainRouterTail.DomainList]
  = new DomainRouter[ModelHead :: ModelTail] {
    override type DomainList = List[ModelHead] :: domainRouterTail.DomainList
    val singularName = typeableToSimpleName(tt)
    val pluralName = singularName + "s"

    override def toString = pluralName

    override def eachRoute(actor: Actor with ActorLogging,
                           service: ActorRef,
                           page: Page,
                           timeout: Timeout,
                           mat: Materializer): Route =
      extractRequestContext { rc =>
        pathPrefix(singularName) {
          get {
            path(LongNumber ?) { id =>
              import actor.context.dispatcher
              processGet[ModelHead](service.??(Get[ModelHead](id)(tt))(
                actor.self, actor.context.system, timeout
              ))
            }
          } ~ {
            def doPatch(commit: Boolean, stash: Boolean) =
              (pathEnd.tmap(_ => none[Long]) | path(LongNumber ?)) { id0 =>
                entity(as[Patch[ModelHead]]) { pp =>
                  processSaveOrUpdate[ModelHead](service.??(
                    Update(
                      id0 match {
                        case Some(_) => pp.copy(pp.poptions + ('id -> Pvalue(id0)))
                        case _ => pp
                      }, page.some, commit, stash))(actor.self, actor.context.system, timeout))
                }
              }

            parameters(
              'commit.as[Boolean] ? commitDefault,
              'stash.as[Boolean] ? stashDefault) { (commit, stash) =>
              post {
                entity(as[ModelHead]) { ent =>
                  processSaveOrUpdate[ModelHead](service.??(
                    Save(ent, page.some, commit, stash))(actor.self, actor.context.system, timeout))
                } ~
                  doPatch(commit, stash)
              } ~
                (akka.http.scaladsl.server.Directives.patch | put) {
                  doPatch(commit, stash)
                }
            }
          }
        }
      } ~ domainRouterTail.eachRoute(actor, service, page, timeout, mat)

    override val patchEncoders =
      domainRouterTail.patchEncoders + (typeableToSimpleName(tt) -> penc.asInstanceOf[Encoder[Patch[_ <: Any]]])

    override val getAllReplyEncoder: Encoder[GetAllReply[DomainList]] = _.ts match {
      case domainList0 :: domainList1 =>
        domainRouterTail.getAllReplyEncoder(GetAllReply(domainList1)).asJson
          .asObject.get.add(
          pluralName, domainList0.asJson).asJson
    }

    override def getAllRoute(actor: Actor with ActorLogging,
                             service: ActorRef,
                             timeout: Timeout,
                             mat: Materializer): Route = {
      implicit val enc = getAllReplyEncoder
      (get & path(pluralName)) {
        import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
        complete((service.??(GetAll)(
          actor.self, actor.context.system, timeout
        )).mapTo[GetAllReply[DomainList]])
      }
    }

    override def route(actor: Actor with ActorLogging,
                       service: ActorRef,
                       page: Page,
                       timeout: Timeout,
                       mat: Materializer): Route =
      getAllRoute(actor, service, timeout, mat) ~ eachRoute(actor, service, page, timeout, mat)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy