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

next.extensions.extension.scala Maven / Gradle / Ivy

package otoroshi.next.extensions

import akka.stream.scaladsl.{Sink, Source}
import akka.util.ByteString
import otoroshi.actions.{ApiAction, BackOfficeAction, PrivateAppsAction}
import otoroshi.api.Resource
import otoroshi.env.Env
import otoroshi.models.{ApiKey, BackOfficeUser, EntityLocationSupport, PrivateAppsUser}
import otoroshi.next.utils.Vault
import otoroshi.storage.DataStoresBuilder
import otoroshi.utils.cache.types.UnboundedTrieMap
import otoroshi.utils.syntax.implicits._
import play.api.Configuration
import play.api.libs.json.{JsObject, JsResult, JsValue, Reads}
import play.api.mvc._
import play.twirl.api.Html

import scala.concurrent.Future
import scala.reflect.ClassTag

class KubernetesResourceReader(f: JsValue => JsResult[JsValue]) extends Reads[JsValue] {
  override def reads(json: JsValue): JsResult[JsValue] = f(json)
}
object KubernetesHelper {
  def reader(f: JsValue => JsResult[JsValue]): Reads[JsValue] = new KubernetesResourceReader(f)
}

case class AdminExtensionId(value: String) {
  lazy val cleanup: String = value.replace(".", "_").toLowerCase()
}
case class AdminExtensionEntity[A <: EntityLocationSupport](resource: otoroshi.api.Resource)

case class AdminExtensionFrontendExtension(path: String)
case class AdminExtensionGlobalConfigExtension()
trait AdminExtensionRoute {
  def method: String
  def path: String
  def wantsBody: Boolean
  def routeId: String = s"${method}${path}"
}

class AdminExtensionRouterContext[A <: AdminExtensionRoute](
    underlying: org.bigtesting.routd.Route,
    val adminRoute: A,
    method: String,
    path: String,
    matchPath: String
) {
  def named(name: String): Option[String] = Option(underlying.getNamedParameter(name, matchPath))
  def splat(index: Int): Option[String]   = Option(underlying.getSplatParameter(index, matchPath))
}

class AdminExtensionRouter[A <: AdminExtensionRoute](routes: Seq[A]) {
  private val (router, config) = {
    var m = Map.empty[String, A]
    val r = new org.bigtesting.routd.TreeRouter()
    routes.foreach { route =>
      r.add(new org.bigtesting.routd.Route(route.routeId))
      m = m.+((route.routeId, route))
    }
    (r, m)
  }
  def find(request: RequestHeader): Option[AdminExtensionRouterContext[A]] = {
    val matchedPath = s"${request.method}${request.path}"
    Option(router.route(matchedPath)).flatMap { r =>
      config
        .get(r.getResourcePath)
        .map(rr => new AdminExtensionRouterContext[A](r, rr, request.method, request.path, matchedPath))
    }
  }
}

case class AdminExtensionAssetRoute(
    path: String,
    handle: (AdminExtensionRouterContext[AdminExtensionAssetRoute], RequestHeader) => Future[Result]
) extends AdminExtensionRoute {
  override def method: String     = "GET"
  override def wantsBody: Boolean = false
}
case class AdminExtensionBackofficeAuthRoute(
    method: String,
    path: String,
    wantsBody: Boolean,
    handle: (
        AdminExtensionRouterContext[AdminExtensionBackofficeAuthRoute],
        RequestHeader,
        Option[BackOfficeUser],
        Option[Source[ByteString, _]]
    ) => Future[Result]
) extends AdminExtensionRoute
case class AdminExtensionBackofficePublicRoute(
    method: String,
    path: String,
    wantsBody: Boolean,
    handle: (
        AdminExtensionRouterContext[AdminExtensionBackofficePublicRoute],
        RequestHeader,
        Option[Source[ByteString, _]]
    ) => Future[Result]
) extends AdminExtensionRoute
case class AdminExtensionAdminApiRoute(
    method: String,
    path: String,
    wantsBody: Boolean,
    handle: (
        AdminExtensionRouterContext[AdminExtensionAdminApiRoute],
        RequestHeader,
        ApiKey,
        Option[Source[ByteString, _]]
    ) => Future[Result]
) extends AdminExtensionRoute
case class AdminExtensionPrivateAppAuthRoute(
    method: String,
    path: String,
    wantsBody: Boolean,
    handle: (
        AdminExtensionRouterContext[AdminExtensionPrivateAppAuthRoute],
        RequestHeader,
        Seq[PrivateAppsUser],
        Option[Source[ByteString, _]]
    ) => Future[Result]
) extends AdminExtensionRoute
case class AdminExtensionPrivateAppPublicRoute(
    method: String,
    path: String,
    wantsBody: Boolean,
    handle: (
        AdminExtensionRouterContext[AdminExtensionPrivateAppPublicRoute],
        RequestHeader,
        Option[Source[ByteString, _]]
    ) => Future[Result]
) extends AdminExtensionRoute
case class AdminExtensionWellKnownRoute(
    method: String,
    path: String,
    wantsBody: Boolean,
    handle: (
        AdminExtensionRouterContext[AdminExtensionWellKnownRoute],
        RequestHeader,
        Option[Source[ByteString, _]]
    ) => Future[Result]
) extends AdminExtensionRoute

case class AdminExtensionConfig(enabled: Boolean)

case class AdminExtensionVault(name: String, build: (String, Configuration, Env) => Vault)

trait AdminExtension {

  def env: Env

  def id: AdminExtensionId
  def enabled: Boolean
  def name: String
  def description: Option[String]

  def start(): Unit              = ()
  def stop(): Unit               = ()
  def syncStates(): Future[Unit] = ().vfuture

  // TODO: add util function to access and update global_config extensions with id cleanup as key

  def datastoreBuilders(): Map[String, DataStoresBuilder]                         = Map.empty
  def entities(): Seq[AdminExtensionEntity[EntityLocationSupport]]                = Seq.empty
  def frontendExtensions(): Seq[AdminExtensionFrontendExtension]                  = Seq.empty
  def globalConfigExtensions(): Seq[AdminExtensionGlobalConfigExtension]          = Seq.empty
  def assets(): Seq[AdminExtensionAssetRoute]                                     = Seq.empty
  def assetsOverrides(): Seq[AdminExtensionAssetRoute]                            = Seq.empty
  def backofficeAuthRoutes(): Seq[AdminExtensionBackofficeAuthRoute]              = Seq.empty
  def backofficeAuthOverridesRoutes(): Seq[AdminExtensionBackofficeAuthRoute]     = Seq.empty
  def backofficePublicRoutes(): Seq[AdminExtensionBackofficePublicRoute]          = Seq.empty
  def backofficePublicOverridesRoutes(): Seq[AdminExtensionBackofficePublicRoute] = Seq.empty
  def adminApiRoutes(): Seq[AdminExtensionAdminApiRoute]                          = Seq.empty
  def adminApiOverridesRoutes(): Seq[AdminExtensionAdminApiRoute]                 = Seq.empty
  def privateAppAuthRoutes(): Seq[AdminExtensionPrivateAppAuthRoute]              = Seq.empty
  def privateAppAuthOverridesRoutes(): Seq[AdminExtensionPrivateAppAuthRoute]     = Seq.empty
  def privateAppPublicRoutes(): Seq[AdminExtensionPrivateAppPublicRoute]          = Seq.empty
  def privateAppPublicOverridesRoutes(): Seq[AdminExtensionPrivateAppPublicRoute] = Seq.empty
  def wellKnownRoutes(): Seq[AdminExtensionWellKnownRoute]                        = Seq.empty
  def wellKnownOverridesRoutes(): Seq[AdminExtensionWellKnownRoute]               = Seq.empty
  def vaults(): Seq[AdminExtensionVault]                                          = Seq.empty
  def configuration: Configuration                                                = env.configuration
    .getOptional[Configuration](s"otoroshi.admin-extensions.configurations.${id.cleanup}")
    .getOrElse(Configuration.empty)
}

object AdminExtensions {
  def current(env: Env, config: AdminExtensionConfig): AdminExtensions = {
    if (config.enabled) {
      val extensions = env.scriptManager.adminExtensionNames
        .map { name =>
          try {
            val clazz       = this.getClass.getClassLoader.loadClass(name)
            val constructor = (Option(clazz.getDeclaredConstructor(classOf[Env])).toSeq ++ Option(
              clazz.getConstructor(classOf[Env])
            ).toSeq).head
            val inst        = constructor.newInstance(env)
            Right(inst.asInstanceOf[AdminExtension])
          } catch {
            case e: Throwable =>
              e.printStackTrace()
              Left(e)
          }
        }
        .collect { case Right(ext) =>
          ext
        }
      new AdminExtensions(env, extensions)
    } else {
      new AdminExtensions(env, Seq.empty)
    }
  }
}

class AdminExtensions(env: Env, _extensions: Seq[AdminExtension]) {

  private implicit val ec  = env.otoroshiExecutionContext
  private implicit val mat = env.otoroshiMaterializer
  private implicit val ev  = env

  private val hasExtensions = _extensions.nonEmpty

  private val extensions: Seq[AdminExtension] = _extensions.filter(_.enabled)

  private val entitiesMap: Map[String, Seq[AdminExtensionEntity[EntityLocationSupport]]] =
    extensions.map(v => (v.id.cleanup, v.entities())).toMap
  private val entities: Seq[AdminExtensionEntity[EntityLocationSupport]]                 = entitiesMap.values.flatten.toSeq

  private val frontendExtensions: Seq[AdminExtensionFrontendExtension]         = extensions.flatMap(_.frontendExtensions())
  private val globalConfigExtensions: Seq[AdminExtensionGlobalConfigExtension] =
    extensions.flatMap(_.globalConfigExtensions())

  // ----------------------------------------------------------------------------------------------------------------
  private val assets: Seq[AdminExtensionAssetRoute]                                     = extensions.flatMap(_.assets())
  private val assetsRouter                                                              = new AdminExtensionRouter[AdminExtensionAssetRoute](assets)
  private val assetsOverrides: Seq[AdminExtensionAssetRoute]                            = extensions.flatMap(_.assetsOverrides())
  private val assetsOverridesRouter                                                     = new AdminExtensionRouter[AdminExtensionAssetRoute](assetsOverrides)
  // ----------------------------------------------------------------------------------------------------------------
  private val backofficeAuthRoutes: Seq[AdminExtensionBackofficeAuthRoute]              =
    extensions.flatMap(_.backofficeAuthRoutes())
  private val backofficeAuthRouter                                                      = new AdminExtensionRouter[AdminExtensionBackofficeAuthRoute](backofficeAuthRoutes)
  private val backofficeAuthOverridesRoutes: Seq[AdminExtensionBackofficeAuthRoute]     =
    extensions.flatMap(_.backofficeAuthOverridesRoutes())
  private val backofficeAuthOverridesRouter                                             =
    new AdminExtensionRouter[AdminExtensionBackofficeAuthRoute](backofficeAuthOverridesRoutes)
  // ----------------------------------------------------------------------------------------------------------------
  private val backofficePublicRoutes: Seq[AdminExtensionBackofficePublicRoute]          =
    extensions.flatMap(_.backofficePublicRoutes())
  private val backofficePublicRouter                                                    =
    new AdminExtensionRouter[AdminExtensionBackofficePublicRoute](backofficePublicRoutes)
  private val backofficePublicOverridesRoutes: Seq[AdminExtensionBackofficePublicRoute] =
    extensions.flatMap(_.backofficePublicOverridesRoutes())
  private val backofficePublicOverridesRouter                                           =
    new AdminExtensionRouter[AdminExtensionBackofficePublicRoute](backofficePublicOverridesRoutes)
  // ----------------------------------------------------------------------------------------------------------------
  private val adminApiOverridesRoutes: Seq[AdminExtensionAdminApiRoute]                 =
    extensions.flatMap(_.adminApiOverridesRoutes())
  private val adminApiOverridesRouter                                                   = new AdminExtensionRouter[AdminExtensionAdminApiRoute](adminApiOverridesRoutes)
  private val adminApiRoutes: Seq[AdminExtensionAdminApiRoute]                          = extensions.flatMap(_.adminApiRoutes())
  private val adminApiRouter                                                            = new AdminExtensionRouter[AdminExtensionAdminApiRoute](adminApiRoutes)
  // ----------------------------------------------------------------------------------------------------------------
  private val privateAppAuthRoutes: Seq[AdminExtensionPrivateAppAuthRoute]              =
    extensions.flatMap(_.privateAppAuthRoutes())
  private val privateAppAuthRouter                                                      = new AdminExtensionRouter[AdminExtensionPrivateAppAuthRoute](privateAppAuthRoutes)
  private val privateAppAuthOverridesRoutes: Seq[AdminExtensionPrivateAppAuthRoute]     =
    extensions.flatMap(_.privateAppAuthOverridesRoutes())
  private val privateAppAuthOverridesRouter                                             =
    new AdminExtensionRouter[AdminExtensionPrivateAppAuthRoute](privateAppAuthOverridesRoutes)
  // ----------------------------------------------------------------------------------------------------------------
  private val privateAppPublicRoutes: Seq[AdminExtensionPrivateAppPublicRoute]          =
    extensions.flatMap(_.privateAppPublicRoutes())
  private val privateAppPublicRouter                                                    =
    new AdminExtensionRouter[AdminExtensionPrivateAppPublicRoute](privateAppPublicRoutes)
  private val privateAppPublicOverridesRoutes: Seq[AdminExtensionPrivateAppPublicRoute] =
    extensions.flatMap(_.privateAppPublicOverridesRoutes())
  private val privateAppPublicOverridesRouter                                           =
    new AdminExtensionRouter[AdminExtensionPrivateAppPublicRoute](privateAppPublicOverridesRoutes)
  // ----------------------------------------------------------------------------------------------------------------
  private val wellKnownRoutes: Seq[AdminExtensionWellKnownRoute]                        = extensions.flatMap(_.wellKnownRoutes())
  private val wellKnownRouter                                                           = new AdminExtensionRouter[AdminExtensionWellKnownRoute](wellKnownRoutes)
  private val wellKnownOverridesRoutes: Seq[AdminExtensionWellKnownRoute]               =
    extensions.flatMap(_.wellKnownOverridesRoutes())
  private val wellKnownOverridesRouter                                                  =
    new AdminExtensionRouter[AdminExtensionWellKnownRoute](wellKnownOverridesRoutes)
  // ----------------------------------------------------------------------------------------------------------------
  private val vaults: Seq[AdminExtensionVault]                                          = extensions.flatMap(_.vaults())

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  private val extCache = new UnboundedTrieMap[Class[_], Any]

  def enabledExtensions(): JsValue = {
    JsObject(extensions.map(e => (e.id.value, e.enabled.json)))
  }

  def enabledExtensionsHtml(): Html = {
    Html(
      enabledExtensions().stringify
    )
  }

  def vault(name: String): Option[AdminExtensionVault] = vaults.find(_.name == name)

  def extension[A](implicit ct: ClassTag[A]): Option[A] = {
    if (hasExtensions) {
      extCache.get(ct.runtimeClass) match {
        case Some(any) => any.asInstanceOf[A].some
        case None      => {
          val opt = extensions
            .find { e =>
              e.getClass == ct.runtimeClass
            }
            .map(_.asInstanceOf[A])
          opt.foreach(ex => extCache.put(ct.runtimeClass, ex))
          opt
        }
      }
    } else {
      None
    }
  }

  def datastoreFrom(extId: AdminExtensionId, name: String): Option[DataStoresBuilder] = {
    extensions.find(_.id == extId).flatMap(_.datastoreBuilders().get(name))
  }

  def datastore(name: String): Option[DataStoresBuilder] = {
    extensions
      .collect {
        case e if e.datastoreBuilders().nonEmpty => e.datastoreBuilders()
      }
      .foldLeft(Map.empty[String, DataStoresBuilder])((a, b) => a ++ b)
      .get(name)
  }

  def getAssetsCallHandler(
      request: RequestHeader,
      actionBuilder: ActionBuilder[Request, AnyContent]
  ): Option[AdminExtensionRouterContext[AdminExtensionAssetRoute]] = {
    if (hasExtensions && assetsOverrides.nonEmpty) {
      assetsOverridesRouter.find(request)
    } else if (hasExtensions && request.path.startsWith("/extensions/assets/") && assets.nonEmpty) {
      assetsRouter.find(request)
    } else None
  }

  def handleAssetsCall(
      request: RequestHeader,
      actionBuilder: ActionBuilder[Request, AnyContent]
  )(f: => Option[Handler]): Option[Handler] = {
    if (hasExtensions && assetsOverrides.nonEmpty) {
      assetsOverridesRouter.find(request) match {
        case Some(route) => Some(actionBuilder.async { ctx => route.adminRoute.handle(route, ctx) })
        case None        => f
      }
    } else if (hasExtensions && request.path.startsWith("/extensions/assets/") && assets.nonEmpty) {
      assetsRouter.find(request) match {
        case Some(route) => Some(actionBuilder.async { ctx => route.adminRoute.handle(route, ctx) })
        case None        => f
      }
    } else f
  }

  def handleWellKnownCall(
      request: RequestHeader,
      actionBuilder: ActionBuilder[Request, AnyContent],
      sourceBodyParser: BodyParser[Source[ByteString, _]]
  )(f: => Option[Handler]): Option[Handler] = {
    if (hasExtensions && wellKnownOverridesRoutes.nonEmpty) {
      wellKnownOverridesRouter.find(request) match {
        case None                                       => f
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(actionBuilder.async(sourceBodyParser) { req => route.adminRoute.handle(route, req, req.body.some) })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(actionBuilder.async { req => route.adminRoute.handle(route, req, None) })
      }
    } else if (
      hasExtensions && request.path.startsWith("/.well-known/otoroshi/extensions/") && wellKnownRoutes.nonEmpty
    ) {
      wellKnownRouter.find(request) match {
        case None                                       => f
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(actionBuilder.async(sourceBodyParser) { req => route.adminRoute.handle(route, req, req.body.some) })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(actionBuilder.async { req => route.adminRoute.handle(route, req, None) })
      }
    } else f
  }

  def handleAdminApiCall(
      request: RequestHeader,
      actionBuilder: ActionBuilder[Request, AnyContent],
      ApiAction: ApiAction,
      sourceBodyParser: BodyParser[Source[ByteString, _]]
  )(f: => Option[Handler]): Option[Handler] = {
    if (hasExtensions && adminApiOverridesRoutes.nonEmpty) {
      adminApiOverridesRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(ApiAction.async(sourceBodyParser) { ctx =>
            route.adminRoute.handle(route, ctx.request, ctx.apiKey, ctx.request.body.some)
          })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(ApiAction.async { ctx => route.adminRoute.handle(route, ctx.request, ctx.apiKey, None) })
        case None                                       => f
      }
    } else if (hasExtensions && request.path.startsWith("/api/extensions/") && adminApiRoutes.nonEmpty) {
      adminApiRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(ApiAction.async(sourceBodyParser) { ctx =>
            route.adminRoute.handle(route, ctx.request, ctx.apiKey, ctx.request.body.some)
          })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(ApiAction.async { ctx => route.adminRoute.handle(route, ctx.request, ctx.apiKey, None) })
        case None                                       => f
      }
    } else f
  }

  def handleBackofficeCall(
      request: RequestHeader,
      actionBuilder: ActionBuilder[Request, AnyContent],
      BackOfficeAction: BackOfficeAction,
      sourceBodyParser: BodyParser[Source[ByteString, _]]
  )(f: => Option[Handler]): Option[Handler] = {
    if (hasExtensions && assetsOverrides.nonEmpty) {
      assetsOverridesRouter.find(request) match {
        case Some(route) => Some(actionBuilder.async { ctx => route.adminRoute.handle(route, ctx) })
        case None        => f
      }
    } else if (hasExtensions && backofficePublicOverridesRoutes.nonEmpty) {
      backofficePublicOverridesRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(actionBuilder.async(sourceBodyParser) { req => route.adminRoute.handle(route, req, req.body.some) })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(actionBuilder.async { req => route.adminRoute.handle(route, req, None) })
        case None                                       => f
      }
    } else if (hasExtensions && backofficeAuthOverridesRoutes.nonEmpty) {
      backofficeAuthOverridesRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(BackOfficeAction.async(sourceBodyParser) { ctx =>
            route.adminRoute.handle(route, ctx.request, ctx.user, ctx.request.body.some)
          })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(BackOfficeAction.async { ctx => route.adminRoute.handle(route, ctx.request, ctx.user, None) })
        case None                                       => f
      }
    } else if (hasExtensions && request.path.startsWith("/extensions/assets/") && assets.nonEmpty) {
      assetsRouter.find(request) match {
        case Some(route) => Some(actionBuilder.async { ctx => route.adminRoute.handle(route, ctx) })
        case None        => f
      }
    } else if (hasExtensions && request.path.startsWith("/extensions/pub/") && backofficePublicRoutes.nonEmpty) {
      backofficePublicRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(actionBuilder.async(sourceBodyParser) { req => route.adminRoute.handle(route, req, req.body.some) })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(actionBuilder.async { req => route.adminRoute.handle(route, req, None) })
        case None                                       => f
      }
    } else if (hasExtensions && request.path.startsWith("/extensions/") && backofficeAuthRoutes.nonEmpty) {
      backofficeAuthRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(BackOfficeAction.async(sourceBodyParser) { ctx =>
            route.adminRoute.handle(route, ctx.request, ctx.user, ctx.request.body.some)
          })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(BackOfficeAction.async { ctx => route.adminRoute.handle(route, ctx.request, ctx.user, None) })
        case None                                       => f
      }
    } else f
  }

  def handlePrivateAppsCall(
      request: RequestHeader,
      actionBuilder: ActionBuilder[Request, AnyContent],
      PrivateAppsAction: PrivateAppsAction,
      sourceBodyParser: BodyParser[Source[ByteString, _]]
  )(f: => Option[Handler]): Option[Handler] = {
    if (hasExtensions && assetsOverrides.nonEmpty) {
      assetsOverridesRouter.find(request) match {
        case Some(route) => Some(actionBuilder.async { ctx => route.adminRoute.handle(route, ctx) })
        case None        => f
      }
    } else if (hasExtensions && privateAppPublicOverridesRoutes.nonEmpty) {
      privateAppPublicOverridesRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(actionBuilder.async(sourceBodyParser) { req => route.adminRoute.handle(route, req, req.body.some) })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(actionBuilder.async { req => route.adminRoute.handle(route, req, None) })
        case None                                       => f
      }
    } else if (hasExtensions && privateAppAuthOverridesRoutes.nonEmpty) {
      privateAppAuthOverridesRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(PrivateAppsAction.async(sourceBodyParser) { ctx =>
            route.adminRoute.handle(route, ctx.request, ctx.users, ctx.request.body.some)
          })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(PrivateAppsAction.async { ctx => route.adminRoute.handle(route, ctx.request, ctx.users, None) })
        case None                                       => f
      }
    } else if (hasExtensions && request.path.startsWith("/extensions/assets/") && assets.nonEmpty) {
      assetsRouter.find(request) match {
        case Some(route) => Some(actionBuilder.async { ctx => route.adminRoute.handle(route, ctx) })
        case None        => f
      }
    } else if (hasExtensions && request.path.startsWith("/extensions/pub/") && privateAppPublicRoutes.nonEmpty) {
      privateAppPublicRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(actionBuilder.async(sourceBodyParser) { req => route.adminRoute.handle(route, req, req.body.some) })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(actionBuilder.async { req => route.adminRoute.handle(route, req, None) })
        case None                                       => f
      }
    } else if (hasExtensions && request.path.startsWith("/extensions/") && privateAppAuthRoutes.nonEmpty) {
      privateAppAuthRouter.find(request) match {
        case Some(route) if route.adminRoute.wantsBody  =>
          Some(PrivateAppsAction.async(sourceBodyParser) { ctx =>
            route.adminRoute.handle(route, ctx.request, ctx.users, ctx.request.body.some)
          })
        case Some(route) if !route.adminRoute.wantsBody =>
          Some(PrivateAppsAction.async { ctx => route.adminRoute.handle(route, ctx.request, ctx.users, None) })
        case None                                       => f
      }
    } else f
  }

  def resources(): Seq[Resource] = {
    if (hasExtensions) {
      entities.map(_.resource)
    } else {
      Seq.empty
    }
  }

  def start(): Unit = {
    if (hasExtensions) {
      extensions.foreach(_.start())
    } else {
      ()
    }
  }

  def stop(): Unit = {
    if (hasExtensions) {
      extensions.foreach(_.stop())
    } else {
      ()
    }
  }

  def syncStates(): Future[Unit] = {
    if (hasExtensions) {
      Source(extensions.toList)
        .mapAsync(1) { extension =>
          extension.syncStates()
        }
        .runWith(Sink.ignore)
        .map(_ => ())
    } else {
      ().vfuture
    }
  }

  def exportAllEntities(): Future[Map[String, Map[String, Seq[JsValue]]]] = {
    if (hasExtensions) {
      Source(entitiesMap.toList)
        .mapAsync(1) {
          case (group, ett) => {
            Source(ett.toList)
              .mapAsync(1) { entity =>
                entity.resource.access
                  .findAll(entity.resource.version.name)
                  .map(values => (entity.resource.pluralName, values))
              // entity.datastore.findAll().map(values => (entity.resource.pluralName, values))
              }
              .runFold((group, Map.empty[String, Seq[JsValue]])) { (tuple, elem) =>
                val newMap = tuple._2 + (elem._1 -> elem._2)
                (tuple._1, newMap)
              }
          }
        }
        .runFold(Map.empty[String, Map[String, Seq[JsValue]]])((map, elem) => map + elem)
    } else {
      Map.empty[String, Map[String, Seq[JsValue]]].vfuture
    }
  }

  def importAllEntities(source: JsObject): Future[Unit] = {
    if (hasExtensions) {
      val extensions: Map[String, Map[String, Seq[JsValue]]] =
        source.asOpt[Map[String, Map[String, Seq[JsValue]]]].getOrElse(Map.empty[String, Map[String, Seq[JsValue]]])
      Source(
        extensions
          .mapValues(_.toSeq)
          .toSeq
          .flatMap { case (key, items) =>
            items.map(tuple => (key, tuple._1, tuple._2))
          }
          .toList
      ).mapAsync(1) {
        case (entityGroup, entityPluralName, entities) => {
          entitiesMap.get(entityGroup).flatMap(_.find(_.resource.pluralName == entityPluralName)) match {
            case None      => ().vfuture
            case Some(ent) => {
              Source(entities.toList)
                .mapAsync(1) { value =>
                  // env.datastores.rawDataStore.set(ent.datastore.key(value.select("id").asString), value.stringify.byteString, None)
                  env.datastores.rawDataStore.set(
                    ent.resource.access.key(value.select("id").asString),
                    value.stringify.byteString,
                    None
                  )
                }
                .runWith(Sink.ignore)
            }
          }
        }
      }.runWith(Sink.ignore)
        .map(_ => ())
    } else {
      ().vfuture
    }
  }

  def frontendExtensionsHtml(): Html = {
    Html(
      frontendExtensions
        .map(_.path)
        .map(p => s"""""")
        .mkString("\n")
    )
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy