Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(example): Support initial loading of data in example app #43

Merged
merged 14 commits into from
Jan 23, 2023
17 changes: 15 additions & 2 deletions modules/example/src/main/scala/example/GitHubRepo.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package example

import example.external.github.model.RepoResponse
import zio.json.{DeriveJsonEncoder, JsonEncoder}
import zio.schema.{DeriveSchema, Schema}

import java.time.LocalDateTime
import java.time.{Instant, LocalDateTime, ZoneId}

final case class GitHubRepo(
id: Option[String],
id: String,
organization: String,
name: String,
url: String,
Expand All @@ -17,6 +18,18 @@ final case class GitHubRepo(
)

object GitHubRepo {
def fromResponse(response: RepoResponse): GitHubRepo =
GitHubRepo(
id = response.id.toString,
organization = response.owner.organization,
name = response.name,
url = response.url,
description = response.description,
lastCommitAt = LocalDateTime.ofInstant(Instant.parse(response.updatedAt), ZoneId.systemDefault()),
stars = response.stars,
forks = response.forks
)

implicit val schema: Schema[GitHubRepo] = DeriveSchema.gen[GitHubRepo]

implicit val encoder: JsonEncoder[GitHubRepo] = DeriveJsonEncoder.gen[GitHubRepo]
Expand Down
13 changes: 11 additions & 2 deletions modules/example/src/main/scala/example/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package example

import example.api.{HealthCheck, Repositories}
import example.config.{AppConfig, ElasticsearchConfig, HttpConfig}
import example.external.github.RepoFetcher
import sttp.client3.SttpBackend
import sttp.client3.httpclient.zio.HttpClientZioBackend
import zio._
import zio.config.getConfig
Expand All @@ -23,7 +25,7 @@ object Main extends ZIOAppDefault {
)
}

private[this] def prepare: RIO[ElasticExecutor, Unit] = {
private[this] def prepare: RIO[SttpBackend[Task, Any] with ElasticExecutor, Unit] = {
val deleteIndex: RIO[ElasticExecutor, Unit] =
for {
_ <- ZIO.logInfo(s"Deleting index '$Index'...")
Expand All @@ -37,7 +39,14 @@ object Main extends ZIOAppDefault {
_ <- ElasticRequest.createIndex(Index, Some(mapping)).execute
} yield ()

deleteIndex *> createIndex
val populate: RIO[SttpBackend[Task, Any] with ElasticExecutor, Unit] =
(for {
repositories <- RepoFetcher.fetchAllByOrganization("zio")
_ <- ZIO.logInfo(s"Adding GitHub repositories...")
dbulaja98 marked this conversation as resolved.
Show resolved Hide resolved
_ <- RepositoriesElasticsearch.createAll(repositories)
} yield ()).provideSome(RepositoriesElasticsearch.live)

deleteIndex *> createIndex *> populate
}

private[this] def runServer: RIO[HttpConfig with ElasticExecutor, ExitCode] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package example

import zio._
import zio.elasticsearch.ElasticRequest.BulkableRequest
import zio.elasticsearch.{DeletionOutcome, DocumentId, ElasticExecutor, ElasticRequest, Routing}
import zio.prelude.Newtype.unsafeWrap

final case class RepositoriesElasticsearch(executor: ElasticExecutor) {

Expand All @@ -19,6 +21,24 @@ final case class RepositoriesElasticsearch(executor: ElasticExecutor) {
res <- executor.execute(req)
} yield res

def createAll(repositories: List[GitHubRepo]): Task[Unit] =
for {
reqs <- ZIO.collectAllPar {
repositories.map { repository =>
for {
dbulaja98 marked this conversation as resolved.
Show resolved Hide resolved
routing <- routingOf(repository.organization)
req = BulkableRequest(
ElasticRequest
.create[GitHubRepo](Index, unsafeWrap(DocumentId)(repository.id), repository)
.routing(routing)
)
} yield req
}
}
bulkReq = ElasticRequest.bulk(reqs: _*)
_ <- executor.execute(bulkReq)
} yield ()

def upsert(id: String, repository: GitHubRepo): Task[Unit] =
for {
routing <- routingOf(repository.organization)
Expand Down Expand Up @@ -46,6 +66,9 @@ object RepositoriesElasticsearch {
def create(repository: GitHubRepo): RIO[RepositoriesElasticsearch, DocumentId] =
ZIO.serviceWithZIO[RepositoriesElasticsearch](_.create(repository))

def createAll(repositories: List[GitHubRepo]): RIO[RepositoriesElasticsearch, Unit] =
ZIO.serviceWithZIO[RepositoriesElasticsearch](_.createAll(repositories))

def upsert(id: String, repository: GitHubRepo): RIO[RepositoriesElasticsearch, Unit] =
ZIO.serviceWithZIO[RepositoriesElasticsearch](_.upsert(id, repository))

Expand Down
6 changes: 3 additions & 3 deletions modules/example/src/main/scala/example/api/Repositories.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ object Repositories {
ZIO.succeed(Response.json(ErrorResponse.fromReasons(e.message).toJson).setStatus(BadRequest))
case Right(repo) =>
RepositoriesElasticsearch.create(repo).map { id =>
Response.json(repo.copy(id = Some(DocumentId.unwrap(id))).toJson).setStatus(Created)
Response.json(repo.copy(id = DocumentId.unwrap(id)).toJson).setStatus(Created)
}
}
.orDie
Expand All @@ -48,7 +48,7 @@ object Repositories {
.flatMap {
case Left(e) =>
ZIO.succeed(Response.json(ErrorResponse.fromReasons(e.message).toJson).setStatus(BadRequest))
case Right(repo) if repo.id.exists(_ != id) =>
case Right(repo) if repo.id.equals(id) =>
dbulaja98 marked this conversation as resolved.
Show resolved Hide resolved
ZIO.succeed(
Response
.json(
Expand All @@ -58,7 +58,7 @@ object Repositories {
)
case Right(repo) =>
(RepositoriesElasticsearch
.upsert(id, repo.copy(id = Some(id))) *> RepositoriesElasticsearch.findById(
.upsert(id, repo.copy(id = id)) *> RepositoriesElasticsearch.findById(
repo.organization,
id
)).map {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package example.external.github

import example.GitHubRepo
import example.external.github.model.RepoResponse
import sttp.client3.{SttpBackend, UriContext, basicRequest}
import zio.json.DecoderOps
import zio.{RIO, Task, ZIO}

object RepoFetcher {

def fetchAllByOrganization(
organization: String,
limit: Int = 100
): RIO[SttpBackend[Task, Any], List[GitHubRepo]] =
for {
client <- ZIO.service[SttpBackend[Task, Any]]
req = basicRequest.get(uri"https://api.github.com/orgs/$organization/repos?per_page=$limit")
res <- req.send(client)
} yield res.body.toOption
.map(_.fromJson[Array[RepoResponse]].fold(_ => Nil, _.map(GitHubRepo.fromResponse).toList))
dbulaja98 marked this conversation as resolved.
Show resolved Hide resolved
.getOrElse(Nil)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package example.external.github.model

import zio.json.{DeriveJsonDecoder, JsonDecoder, jsonField}

final case class RepoOwner(
@jsonField("login")
organization: String
)

object RepoOwner {
implicit val decoder: JsonDecoder[RepoOwner] = DeriveJsonDecoder.gen[RepoOwner]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package example.external.github.model

import zio.json.{DeriveJsonDecoder, JsonDecoder, jsonField}

final case class RepoResponse(
dbulaja98 marked this conversation as resolved.
Show resolved Hide resolved
id: Int,
name: String,
url: String,
description: Option[String],
@jsonField("updated_at")
updatedAt: String,
@jsonField("stargazers_count")
stars: Int,
forks: Int,
owner: RepoOwner
)

object RepoResponse {
implicit val decoder: JsonDecoder[RepoResponse] = DeriveJsonDecoder.gen[RepoResponse]
}