diff --git a/src/main/scala/hackhack/AppRegistry.scala b/src/main/scala/hackhack/AppRegistry.scala index 33c4b02..b812132 100644 --- a/src/main/scala/hackhack/AppRegistry.scala +++ b/src/main/scala/hackhack/AppRegistry.scala @@ -18,7 +18,8 @@ import cats.syntax.either._ import cats.syntax.option._ import com.softwaremill.sttp.{SttpBackend, Uri, sttp} import hackhack.ipfs.{IpfsError, IpfsStore} -import io.circe.Json +import io.circe.{Decoder, Encoder, Json, ObjectEncoder} +import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} import io.circe.parser.parse import scodec.bits.ByteVector @@ -32,12 +33,23 @@ case class Peer( val RpcUri = Uri(host, rpcPort) } +object Peer { + implicit val encodePeer: Encoder[Peer] = deriveEncoder + implicit val decodePeer: Decoder[Peer] = deriveDecoder +} + case class App(name: String, containerId: String, peer: Peer, binaryHash: ByteVector, binaryPath: Path) +object App { + private implicit val encbc: Encoder[ByteVector] = Encoder.encodeString.contramap(_.toHex) + private implicit val encPath: Encoder[Path] = Encoder.encodeString.contramap(_.toString) + implicit val encodeApp: ObjectEncoder[App] = deriveEncoder +} + class AppRegistry[F[_]: Monad: Concurrent: ContextShift: Timer: LiftIO]( ipfsStore: IpfsStore[F], runner: Runner[F], @@ -138,6 +150,20 @@ class AppRegistry[F[_]: Monad: Concurrent: ContextShift: Timer: LiftIO]( _ <- EitherT.liftF(deferred.complete(app)) } yield status.sync_info.latest_block_height + def getAllApps: EitherT[F, Throwable, List[(App, Long)]] = { + for { + appList <- EitherT.right( + apps.get.flatMap(map => + Traverse[List].sequence(map.values.map(_.get).toList)) + ) + statuses <- Traverse[List].sequence(appList.map(app => + status(app.name, app.peer))) + } yield + appList.zip(statuses).map { + case (app, status) => (app, status.sync_info.latest_block_height) + } + } + private def log(str: String) = EitherT(IO(println(str)).attempt.to[F]) private def getApp(name: String): F[Either[Throwable, App]] = diff --git a/src/main/scala/hackhack/Runner.scala b/src/main/scala/hackhack/Runner.scala index 8af4d39..115de71 100644 --- a/src/main/scala/hackhack/Runner.scala +++ b/src/main/scala/hackhack/Runner.scala @@ -117,10 +117,11 @@ case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent]( apps <- Try { ids.zip(names).zip(envMap).map { case ((id, name), env) => + val rpcPort = env("RPCPORT").toShort val peer = env .get("PEER") .map(_.split(Array('@', ':'))) - .map(a => Peer(a(1), a(2).toShort)) + .map(a => Peer(a(1), rpcPort)) .get val binaryHash = ByteVector.fromValidHex(env("BINARY_HASH")) val binaryPath = Paths.get(env("BINARY_PATH")) diff --git a/src/main/scala/hackhack/WebsocketServer.scala b/src/main/scala/hackhack/WebsocketServer.scala index 448b8fa..44ac739 100644 --- a/src/main/scala/hackhack/WebsocketServer.scala +++ b/src/main/scala/hackhack/WebsocketServer.scala @@ -6,6 +6,7 @@ import cats.syntax.applicativeError._ import cats.syntax.flatMap._ import fs2.Stream import fs2.concurrent.SignallingRef +import io.circe.JsonLong import io.circe.syntax._ import org.http4s.HttpRoutes import org.http4s.dsl.Http4sDsl @@ -56,6 +57,24 @@ case class WebsocketServer[F[_]: ConcurrentEffect: Timer: ContextShift]( """.stripMargin) } + + case GET -> Root / "apps" => + (for { + apps <- appRegistry.getAllApps + json = apps + .map { + case (app, height) => + app.asJsonObject + .add("consensusHeight", height.asJson) + .toMap + .asJson + } + .asJson + .spaces2 + } yield json).value.flatMap { + case Left(e) => InternalServerError(s"Error on /apps: $e") + case Right(json) => Ok(json) + } // TODO: list of registered apps }