Skip to content
This repository has been archived by the owner on Dec 10, 2018. It is now read-only.

Commit

Permalink
iss #22: [tmp] github client
Browse files Browse the repository at this point in the history
  • Loading branch information
maizy committed Aug 7, 2014
1 parent 8a49226 commit ee918aa
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 26 deletions.
65 changes: 59 additions & 6 deletions app/hedgehog/clients/github/Client.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,76 @@
package hedgehog.clients.github

import scala.concurrent.{Future, Promise}
import hedgehog.models.{AccountSettings, Repo}
import play.api.libs.ws.WS
import play.api.libs.json.{JsSuccess, JsError}

import hedgehog.models.{AccountSettings, Repo}

/**
* Copyright (c) Nikita Kovaliov, maizy.ru, 2014
* See LICENSE.txt for details.
*/
class Client(val config: Config, implicit val context: scala.concurrent.ExecutionContext) {

val PER_PAGE = 100

def getRepos(accountSettings: AccountSettings): Future[Seq[Repo]] = {
val finish = Promise[Seq[Repo]]()
val resultPromise = Promise[Seq[Repo]]()

val proxyError: PartialFunction[Throwable, Unit] = {
//TODO: wrap errors
case err => resultPromise.failure(err)
}
val getFirstPage = getPage(accountSettings, page = 1, perPage = PER_PAGE)

finish success List(new Repo("a", accountSettings.account),
new Repo("b", accountSettings.account))
getFirstPage onSuccess {
case PageRes(res, None, None) => resultPromise.success(res)
case PageRes(firstPageRes, _, Some(last)) => {
require(last > 1)
val otherPagesRes = Future.sequence(
for {
page <- 2 to last
} yield getPage(accountSettings, page, perPage = PER_PAGE)
)
otherPagesRes onSuccess {
case otherPages => resultPromise success (firstPageRes ++ otherPages.flatMap(_.repos))
}
otherPagesRes onFailure proxyError
}
}

// finish failure new ConfigError("ou!")
getFirstPage onFailure proxyError
resultPromise.future
}

finish.future
private def getPage(accountSettings: AccountSettings, page: Int, perPage: Int): Future[PageRes] = {
val account = accountSettings.account
val url = config.replaceBaseUrl(account.apiReposUrl)
val httpClient = WS.url(url).withQueryString(
"per_page" -> perPage.toString,
"page" -> page.toString
)
httpClient.get map {
case response =>
response.status match {
case 200 => {
GithubReades.repoSeqReads.reads(response.json) match {
case JsSuccess(res, _) => {
val nextLastTMP = if (account.name == "hhru") Some(2) else None //FIXME: tmp
PageRes(res, nextPage = nextLastTMP, lastPage = nextLastTMP)
}
case JsError(err) => throw new Exception(s"Unable to parse json $err")
}
}
//TODO: custom exceptions
case code: Int if code >= 400 && code < 599 => throw new Exception("no data")
case _ => throw new Exception("unknown error")
}
}
}
}


private case class PageRes(repos: Seq[Repo], nextPage: Option[Int], lastPage: Option[Int]) {
val currentPage: Option[Int] = nextPage map (_ - 1)
}
44 changes: 44 additions & 0 deletions app/hedgehog/clients/github/GithubReades.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package hedgehog.clients.github

import play.api.libs.functional.syntax._
import play.api.libs.json.{Reads, __}

import hedgehog.models.{GithubOrg, Account, GithubUser, Repo, ProgrammingLang}
/**
* Copyright (c) Nikita Kovaliov, maizy.ru, 2014
* See LICENSE.txt for details.
*/
object GithubReades {

val accountReads: Reads[Account] = (
(__ \ "type").read[String] ~
(__ \ "login").read[String] ~
(__ \ "avatar_url").readNullable[String]
) apply {
(accountType, login, avatarUrl) => accountType match {
case "User" => GithubUser(login, avatarUrl)
case "Organization" => GithubOrg(login, avatarUrl)
case _ => throw new FormatError(s"Unknown owner type "+ accountType)
}
}

val repoReads: Reads[Repo] = (
(__ \ "name").read[String] ~
(__ \ "owner").read[Account](accountReads) ~
(__ \ "description").readNullable[String] ~
(__ \ "private").read[Boolean] ~
(__ \ "language").readNullable[String]
) apply {
(name, owner, description, isPrivate, lang) => {
Repo(
name,
owner,
description = description,
isPrivate = Some(isPrivate),
primaryLang = lang.map(l => ProgrammingLang(l.toLowerCase))
)
}
}

val repoSeqReads: Reads[Seq[Repo]] = Reads.seq[Repo](repoReads)
}
3 changes: 2 additions & 1 deletion app/hedgehog/clients/github/errors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ package hedgehog.clients.github
* See LICENSE.txt for details.
*/
class Error(msg: String) extends Exception(msg)
class FetchError(msg: String) extends Exception(msg)
class FetchError(msg: String) extends Error(msg)
class FormatError(msg: String) extends Error(msg)
11 changes: 8 additions & 3 deletions app/hedgehog/models/Account.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package hedgehog.models

import play.api.libs.json.{Json, JsValue, Writes}
import hedgehog.UrlUtils.urlEncodePathSegment
import play.api.libs.json._
import play.api.libs.functional.syntax._

import hedgehog.UrlUtils.urlEncodePathSegment

/**
* Copyright (c) Nikita Kovaliov, maizy.ru, 2014
Expand Down Expand Up @@ -30,6 +31,8 @@ trait Account {

def getRepoUrl(repo: Repo): String =
s"$webProfileUrl/${urlEncodePathSegment(repo.fullName)}"

def apiReposUrl: String
}


Expand All @@ -45,15 +48,16 @@ object AccountJson {
)
}
}
}

}

case class GithubUser(
name: String,
override val avatarUrl: Option[String] = None)
extends Account {

val accountType = AccountType.User
val apiReposUrl = s"$GITHUB_BASE_API_URL/users/${urlEncodePathSegment(name)}/repos"
}


Expand All @@ -63,4 +67,5 @@ case class GithubOrg(
extends Account {

val accountType = AccountType.Org
val apiReposUrl = s"$GITHUB_BASE_API_URL/orgs/${urlEncodePathSegment(name)}/repos"
}
20 changes: 9 additions & 11 deletions app/hedgehog/models/Repo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,23 @@ import play.api.libs.json._
* Copyright (c) Nikita Kovaliov, maizy.ru, 2013-2014
* See LICENSE.txt for details.
*/
class Repo(
val name: String,
val owner: Account,
val description: Option[Boolean] = None,
val isPrivate: Option[Boolean] = None,
val primaryLang: Option[ProgrammingLang] = None,
val langsStat: Option[Seq[ProgrammingLangStat]] = None) {
case class Repo(
name: String,
owner: Account,
description: Option[String] = None,
isPrivate: Option[Boolean] = None,
primaryLang: Option[ProgrammingLang] = None,
langsStat: Option[Seq[ProgrammingLangStat]] = None) {
def fullName = s"${owner.name}/$name"
lazy val langsStatIndex = langsStat.map(seq => seq.map{stat => (stat.lang.code, stat)}.toMap)
def url: String = owner.getRepoUrl(this)
}


object Repo {
//FIXME
def getCurrentRepos: Seq[Repo] = List()

implicit val writer = new Writes[Repo] {
def writes(r: Repo) : JsValue = {
implicit val repoWrites = new Writes[Repo] {
def writes(r: Repo): JsValue = {
//FIXME: as documented
Json.obj(
"name" -> r.name,
Expand Down
1 change: 1 addition & 0 deletions app/hedgehog/models/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ package hedgehog
package object models {
val GITHUB_HOST = "github.com"
val GITHUB_BASE_WEB_URL = s"https://$GITHUB_HOST"
val GITHUB_BASE_API_URL = s"https://api.$GITHUB_HOST"
}
10 changes: 5 additions & 5 deletions app/hedgehog/models/programming_lang.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ package hedgehog.models
* See LICENSE.txt for details.
*
*/
class ProgrammingLang(val code: String)
case class ProgrammingLang(code: String)


class ProgrammingLangStat(
val lang: ProgrammingLang,
val extensions: Seq[String],
val bytes: Option[Long])
case class ProgrammingLangStat(
lang: ProgrammingLang,
extensions: Seq[String],
bytes: Option[Long])

0 comments on commit ee918aa

Please sign in to comment.