-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 04b59b2
Showing
20 changed files
with
1,136 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
*.iml | ||
*.class | ||
*.log | ||
target/ | ||
project/target/ | ||
project/boot/ | ||
dist/ | ||
boot/ | ||
logs/ | ||
out/ | ||
tmp/ | ||
.history/ | ||
.idea/ | ||
.idea_modules/ | ||
.DS_STORE | ||
.cache | ||
.settings | ||
.project | ||
.classpath | ||
version.properties |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
style = IntelliJ | ||
align = more | ||
maxColumn = 150 | ||
align.openParenCallSite = false | ||
align.openParenDefnSite = false | ||
danglingParentheses = true | ||
continuationIndent.defnSite = 13 | ||
newlines.alwaysBeforeTopLevelStatements = true | ||
spaces.afterKeywordBeforeParen = true | ||
includeCurlyBraceInSelectChains = false | ||
newlines.alwaysBeforeElseAfterCurlyIf = false | ||
optIn.breakChainOnFirstMethodDot = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#GithubRank challenge | ||
|
||
Write a server that will return all contributors to a specific organization on GitHub ordered by their contributions. | ||
There are can be multiple pages with results. And you have to consider rate limiting of GitHub. | ||
|
||
###To run locally | ||
Set Github token as an environment variable of name GH_TOKEN | ||
|
||
Run server with command: | ||
sbt run | ||
|
||
###How to test | ||
Server listens on port 8080. | ||
Call server with url /org/ORGANIZATION/contributors specifying any organization that exist on GitHub. | ||
For example: http://localhost:8080/org/akka/contributors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
|
||
val Http4sVersion = "0.20.0" | ||
val CirceVersion = "0.13.0" | ||
val SttpVersion = "2.0.7" | ||
val ScalaCacheVersion = "0.28.0" | ||
|
||
|
||
lazy val root = (project in file(".")) | ||
.settings( | ||
organization := "serhii", | ||
name := "task", | ||
version := "0.0.1-SNAPSHOT", | ||
scalaVersion := "2.12.11", | ||
scalacOptions ++= Seq( | ||
"-deprecation", | ||
"-encoding", "UTF-8", | ||
"-language:higherKinds", | ||
"-language:postfixOps", | ||
"-feature", | ||
"-Ypartial-unification", | ||
), | ||
libraryDependencies ++= Seq( | ||
"org.http4s" %% "http4s-blaze-server" % Http4sVersion, | ||
"org.http4s" %% "http4s-blaze-client" % Http4sVersion, | ||
"org.http4s" %% "http4s-circe" % Http4sVersion, | ||
"org.http4s" %% "http4s-dsl" % Http4sVersion, | ||
"io.circe" %% "circe-generic" % CirceVersion, | ||
"com.softwaremill.sttp.client" %% "core" % SttpVersion, | ||
"com.softwaremill.sttp.client" %% "circe" % SttpVersion, | ||
"ch.qos.logback" % "logback-classic" % "1.2.3", | ||
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.0", | ||
"com.softwaremill.sttp.client" %% "async-http-client-backend-cats" % SttpVersion, | ||
"org.scalatest" %% "scalatest" % "3.1.1" % Test, | ||
"org.scalamock" %% "scalamock" % "4.4.0" % Test, | ||
"com.typesafe" % "config" % "1.3.3" | ||
) | ||
) | ||
|
||
scalafmtOnCompile in ThisBuild := true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=1.3.8 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
server { | ||
host = "0.0.0.0" | ||
port = 8080 | ||
} | ||
|
||
github.key = ${GH_TOKEN} | ||
|
||
client { | ||
concurrent-requests = 10 | ||
request-timeout = 10 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<configuration> | ||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
<encoder> | ||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
</encoder> | ||
</appender> | ||
|
||
|
||
|
||
|
||
<root level="INFO"> | ||
<appender-ref ref="STDOUT" /> | ||
</root> | ||
|
||
<logger name="org.http4s"/> | ||
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package serhii | ||
|
||
import cats.effect.IO._ | ||
import cats.effect.{ExitCode, IO, IOApp} | ||
import com.typesafe.config.ConfigFactory | ||
import com.typesafe.scalalogging.LazyLogging | ||
import serhii.http.HttpServer | ||
import serhii.service.GitHubClientImpl | ||
import sttp.client.asynchttpclient.cats.AsyncHttpClientCatsBackend | ||
|
||
object Main extends IOApp with LazyLogging { | ||
|
||
private val config = ConfigFactory.load() | ||
|
||
override def run(args: List[String]): IO[ExitCode] = { | ||
AsyncHttpClientCatsBackend.resource().use { implicit backend => | ||
new HttpServer( | ||
config.getString("server.host"), | ||
config.getInt("server.port"), | ||
gitHub = new GitHubClientImpl[IO]( | ||
config.getString("github.key"), | ||
config.getInt("client.concurrent-requests"), | ||
config.getInt("client.request-timeout") | ||
) | ||
).server.redeemWith(ex => { | ||
logger.error(ex.getMessage) | ||
IO.raiseError(ex) | ||
}, _ => IO.pure(ExitCode.Success)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package serhii.http | ||
|
||
import cats.effect.Sync | ||
import cats.implicits._ | ||
import com.typesafe.scalalogging.LazyLogging | ||
import org.http4s.HttpRoutes | ||
import org.http4s.circe.CirceEntityEncoder._ | ||
import org.http4s.dsl.Http4sDsl | ||
import org.http4s.headers.`Retry-After` | ||
import serhii.service.GitHubClient | ||
import serhii.utils.Errors.{RateLimited, UnknownGitHubError} | ||
|
||
class Http4sRoutes[F[_]: Sync](gitHub: GitHubClient[F]) extends Http4sDsl[F] with LazyLogging { | ||
|
||
def gitHubRoutes: HttpRoutes[F] = HttpRoutes.of[F] { | ||
case GET -> Root / "org" / organization / "contributors" => | ||
val escapedOrganization = java.net.URLEncoder.encode(organization, "UTF-8") | ||
(for { | ||
repositories <- gitHub.getRepos(escapedOrganization) | ||
contributors <- gitHub.getContributors(repositories) | ||
} yield Ok(contributors)).flatten.recoverWith({ | ||
case RateLimited(time) => | ||
ServiceUnavailable(`Retry-After`.unsafeFromLong(time)) | ||
case UnknownGitHubError(error) => | ||
logger.error("Unknown error happened during calling GitHub: " + error) | ||
InternalServerError() | ||
}) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package serhii.http | ||
|
||
import cats.effect.Sync | ||
import org.http4s.implicits._ | ||
import org.http4s.{HttpApp, HttpRoutes} | ||
import serhii.service.GitHubClient | ||
|
||
final class HttpApi[F[_]: Sync](gitHub: GitHubClient[F]) { | ||
|
||
private val httpRoutes: HttpRoutes[F] = | ||
new Http4sRoutes(gitHub).gitHubRoutes | ||
|
||
val httpApp: HttpApp[F] = httpRoutes.orNotFound | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package serhii.http | ||
|
||
import cats.effect.{ConcurrentEffect, Sync, Timer} | ||
import org.http4s.server.blaze.BlazeServerBuilder | ||
import serhii.service.GitHubClient | ||
|
||
class HttpServer[F[_]: Sync: ConcurrentEffect: Timer](host: String, port: Int, gitHub: GitHubClient[F]) { | ||
val httpApi = new HttpApi(gitHub) | ||
|
||
val server: F[Unit] = BlazeServerBuilder[F].bindHttp(port, host).withHttpApp(httpApi.httpApp).serve.compile.drain | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package serhii.model | ||
|
||
import io.circe._ | ||
import io.circe.generic.semiauto._ | ||
|
||
case class Contributor(login: String, contributions: Int) | ||
|
||
object Contributor { | ||
implicit val jsonDecoder: Decoder[Contributor] = deriveDecoder[Contributor] | ||
implicit val jsonEncoder: Encoder[Contributor] = (c: Contributor) => | ||
Json.obj(("contributor_login", Json.fromString(c.login)), ("contributions", Json.fromInt(c.contributions))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package serhii.model | ||
|
||
import io.circe.{Decoder, HCursor} | ||
|
||
case class GitHubRepository(contributorsUrl: String) | ||
case object GitHubRepository { | ||
implicit def jsonDecoder: Decoder[GitHubRepository] = (c: HCursor) => { | ||
for { | ||
url <- c.downField("contributors_url").as[String] | ||
} yield { | ||
new GitHubRepository(url) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package serhii.service | ||
|
||
import serhii.model.{Contributor, GitHubRepository} | ||
import sttp.client.{NothingT, SttpBackend} | ||
|
||
trait GitHubClient[F[_]] { | ||
|
||
implicit val backend: SttpBackend[F, Nothing, NothingT] | ||
|
||
val token: String | ||
val concurrency: Int | ||
val timeout: Int | ||
|
||
def getRepos(organization: String): F[Vector[GitHubRepository]] | ||
def getContributors(repositories: Vector[GitHubRepository]): F[Vector[Contributor]] | ||
} |
Oops, something went wrong.