diff --git a/github4s/src/main/scala/github4s/Github.scala b/github4s/src/main/scala/github4s/Github.scala index 027ebb27c..7b8db3e56 100644 --- a/github4s/src/main/scala/github4s/Github.scala +++ b/github4s/src/main/scala/github4s/Github.scala @@ -18,12 +18,13 @@ package github4s import cats.effect.Sync import github4s.algebras._ +import github4s.interpreters.StaticAccessToken import github4s.modules._ import org.http4s.client.Client class Github[F[_]: Sync]( client: Client[F], - accessToken: Option[String] + accessToken: AccessToken[F] )(implicit config: GithubConfig) extends GithubAPIs[F] { @@ -48,5 +49,5 @@ object Github { client: Client[F], accessToken: Option[String] = None )(implicit config: GithubConfig): Github[F] = - new Github[F](client, accessToken) + new Github[F](client, new StaticAccessToken(accessToken)) } diff --git a/github4s/src/main/scala/github4s/algebras/AccessToken.scala b/github4s/src/main/scala/github4s/algebras/AccessToken.scala new file mode 100644 index 000000000..4bbf90862 --- /dev/null +++ b/github4s/src/main/scala/github4s/algebras/AccessToken.scala @@ -0,0 +1,17 @@ +package github4s.algebras + +import github4s.GHResponse + +/** + * Source of static or expiring github tokens + * + * For github app authentication you'd want to create a token source + * which calls github's installation authentication api with a jwt token, generated from a private key + * These tokens have a 1h lifetime, so it's a good idea to handle expired tokens here as well + * + * @see https://docs.github.com/en/free-pro-team@latest/developers/apps/authenticating-with-github-apps + */ +trait AccessToken[F[_]] { + + def withAccessToken[T](f: Option[String] => F[GHResponse[T]]): F[GHResponse[T]] +} diff --git a/github4s/src/main/scala/github4s/http/HttpClient.scala b/github4s/src/main/scala/github4s/http/HttpClient.scala index 058384faa..39eac4bea 100644 --- a/github4s/src/main/scala/github4s/http/HttpClient.scala +++ b/github4s/src/main/scala/github4s/http/HttpClient.scala @@ -23,6 +23,7 @@ import cats.syntax.either._ import cats.syntax.functor._ import github4s.GHError._ import github4s._ +import github4s.algebras.AccessToken import github4s.domain.Pagination import github4s.http.Http4sSyntax._ import io.circe.{Decoder, Encoder} @@ -31,73 +32,83 @@ import org.http4s.circe.jsonOf import org.http4s.client.Client import org.http4s.{EntityDecoder, Request, Response, Status} -class HttpClient[F[_]: Sync](client: Client[F], val config: GithubConfig) { +class HttpClient[F[_]: Sync]( + client: Client[F], + val config: GithubConfig, + accessTokens: AccessToken[F] +) { import HttpClient._ + import accessTokens._ def get[Res: Decoder]( - accessToken: Option[String] = None, method: String, headers: Map[String, String] = Map.empty, params: Map[String, String] = Map.empty, pagination: Option[Pagination] = None ): F[GHResponse[Res]] = - run[Unit, Res]( - RequestBuilder(url = buildURL(method)) - .withAuth(accessToken) - .withHeaders(headers) - .withParams( - params ++ pagination.fold(Map.empty[String, String])(p => - Map("page" -> p.page.toString, "per_page" -> p.per_page.toString) + withAccessToken { accessToken => + run[Unit, Res]( + RequestBuilder(url = buildURL(method)) + .withAuth(accessToken) + .withHeaders(headers) + .withParams( + params ++ pagination.fold(Map.empty[String, String])(p => + Map("page" -> p.page.toString, "per_page" -> p.per_page.toString) + ) ) - ) - ) + ) + } def getWithoutResponse( - accessToken: Option[String] = None, url: String, headers: Map[String, String] = Map.empty ): F[GHResponse[Unit]] = - runWithoutResponse[Unit]( - RequestBuilder(buildURL(url)).withHeaders(headers).withAuth(accessToken) + withAccessToken(accessToken => + runWithoutResponse[Unit]( + RequestBuilder(buildURL(url)).withHeaders(headers).withAuth(accessToken) + ) ) def patch[Req: Encoder, Res: Decoder]( - accessToken: Option[String] = None, method: String, headers: Map[String, String] = Map.empty, data: Req ): F[GHResponse[Res]] = - run[Req, Res]( - RequestBuilder(buildURL(method)).patchMethod - .withAuth(accessToken) - .withHeaders(headers) - .withData(data) + withAccessToken(accessToken => + run[Req, Res]( + RequestBuilder(buildURL(method)).patchMethod + .withAuth(accessToken) + .withHeaders(headers) + .withData(data) + ) ) def put[Req: Encoder, Res: Decoder]( - accessToken: Option[String] = None, url: String, headers: Map[String, String] = Map(), data: Req ): F[GHResponse[Res]] = - run[Req, Res]( - RequestBuilder(buildURL(url)).putMethod - .withAuth(accessToken) - .withHeaders(headers) - .withData(data) + withAccessToken(accessToken => + run[Req, Res]( + RequestBuilder(buildURL(url)).putMethod + .withAuth(accessToken) + .withHeaders(headers) + .withData(data) + ) ) def post[Req: Encoder, Res: Decoder]( - accessToken: Option[String] = None, url: String, headers: Map[String, String] = Map.empty, data: Req ): F[GHResponse[Res]] = - run[Req, Res]( - RequestBuilder(buildURL(url)).postMethod - .withAuth(accessToken) - .withHeaders(headers) - .withData(data) + withAccessToken(accessToken => + run[Req, Res]( + RequestBuilder(buildURL(url)).postMethod + .withAuth(accessToken) + .withHeaders(headers) + .withData(data) + ) ) def postAuth[Req: Encoder, Res: Decoder]( @@ -119,36 +130,39 @@ class HttpClient[F[_]: Sync](client: Client[F], val config: GithubConfig) { ) def delete( - accessToken: Option[String] = None, url: String, headers: Map[String, String] = Map.empty ): F[GHResponse[Unit]] = - run[Unit, Unit]( - RequestBuilder(buildURL(url)).deleteMethod.withHeaders(headers).withAuth(accessToken) + withAccessToken(accessToken => + run[Unit, Unit]( + RequestBuilder(buildURL(url)).deleteMethod.withHeaders(headers).withAuth(accessToken) + ) ) def deleteWithResponse[Res: Decoder]( - accessToken: Option[String] = None, url: String, headers: Map[String, String] = Map.empty ): F[GHResponse[Res]] = - run[Unit, Res]( - RequestBuilder(buildURL(url)).deleteMethod - .withAuth(accessToken) - .withHeaders(headers) + withAccessToken(accessToken => + run[Unit, Res]( + RequestBuilder(buildURL(url)).deleteMethod + .withAuth(accessToken) + .withHeaders(headers) + ) ) def deleteWithBody[Req: Encoder, Res: Decoder]( - accessToken: Option[String] = None, url: String, headers: Map[String, String] = Map.empty, data: Req ): F[GHResponse[Res]] = - run[Req, Res]( - RequestBuilder(buildURL(url)).deleteMethod - .withAuth(accessToken) - .withHeaders(headers) - .withData(data) + withAccessToken(accessToken => + run[Req, Res]( + RequestBuilder(buildURL(url)).deleteMethod + .withAuth(accessToken) + .withHeaders(headers) + .withData(data) + ) ) private def buildURL(method: String): String = s"${config.baseUrl}$method" diff --git a/github4s/src/main/scala/github4s/interpreters/ActivitiesInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/ActivitiesInterpreter.scala index c7fb1b02f..58af8e077 100644 --- a/github4s/src/main/scala/github4s/interpreters/ActivitiesInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/ActivitiesInterpreter.scala @@ -23,8 +23,7 @@ import github4s.Encoders._ import github4s.GHResponse import github4s.http.HttpClient -class ActivitiesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option[String]) - extends Activities[F] { +class ActivitiesInterpreter[F[_]](implicit client: HttpClient[F]) extends Activities[F] { private val timelineHeader = ("Accept" -> "application/vnd.github.v3.star+json") @@ -35,7 +34,6 @@ class ActivitiesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: O headers: Map[String, String] ): F[GHResponse[Subscription]] = client.put[SubscriptionRequest, Subscription]( - accessToken = accessToken, url = s"notifications/threads/$id/subscription", headers = headers, data = SubscriptionRequest(subscribed, ignored) @@ -49,7 +47,6 @@ class ActivitiesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: O headers: Map[String, String] ): F[GHResponse[List[Stargazer]]] = client.get[List[Stargazer]]( - accessToken, s"repos/$owner/$repo/stargazers", if (timeline) headers + timelineHeader else headers, pagination = pagination @@ -64,7 +61,6 @@ class ActivitiesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: O headers: Map[String, String] ): F[GHResponse[List[StarredRepository]]] = client.get[List[StarredRepository]]( - accessToken, s"users/$username/starred", if (timeline) headers + timelineHeader else headers, Map( diff --git a/github4s/src/main/scala/github4s/interpreters/GistsInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/GistsInterpreter.scala index 3d5d40105..1fc1e92d3 100644 --- a/github4s/src/main/scala/github4s/interpreters/GistsInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/GistsInterpreter.scala @@ -16,15 +16,14 @@ package github4s.interpreters -import github4s.algebras.Gists import github4s.Decoders._ -import github4s.domain._ import github4s.Encoders._ import github4s.GHResponse +import github4s.algebras.Gists +import github4s.domain._ import github4s.http.HttpClient -class GistsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option[String]) - extends Gists[F] { +class GistsInterpreter[F[_]](implicit client: HttpClient[F]) extends Gists[F] { override def newGist( description: String, @@ -33,7 +32,6 @@ class GistsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option headers: Map[String, String] ): F[GHResponse[Gist]] = client.post[NewGistRequest, Gist]( - accessToken, "gists", headers, data = NewGistRequest(description, public, files) @@ -45,7 +43,6 @@ class GistsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option headers: Map[String, String] ): F[GHResponse[Gist]] = client.get[Gist]( - accessToken, ("gists" :: gistId :: sha.toList).mkString("/"), headers ) @@ -57,7 +54,6 @@ class GistsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option headers: Map[String, String] ): F[GHResponse[Gist]] = client.patch[EditGistRequest, Gist]( - accessToken, s"gists/$gistId", headers, data = EditGistRequest(description, files) diff --git a/github4s/src/main/scala/github4s/interpreters/GitDataInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/GitDataInterpreter.scala index 1be11f026..5ff421fa3 100644 --- a/github4s/src/main/scala/github4s/interpreters/GitDataInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/GitDataInterpreter.scala @@ -16,16 +16,15 @@ package github4s.interpreters -import github4s.http.HttpClient -import github4s.algebras.GitData import cats.data.NonEmptyList -import github4s.GHResponse -import github4s.domain._ import github4s.Decoders._ import github4s.Encoders._ +import github4s.GHResponse +import github4s.algebras.GitData +import github4s.domain._ +import github4s.http.HttpClient -class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option[String]) - extends GitData[F] { +class GitDataInterpreter[F[_]](implicit client: HttpClient[F]) extends GitData[F] { override def getReference( owner: String, repo: String, @@ -34,7 +33,6 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti headers: Map[String, String] ): F[GHResponse[NonEmptyList[Ref]]] = client.get[NonEmptyList[Ref]]( - accessToken, s"repos/$owner/$repo/git/refs/$ref", headers, pagination = pagination @@ -48,7 +46,6 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti headers: Map[String, String] ): F[GHResponse[Ref]] = client.post[CreateReferenceRequest, Ref]( - accessToken, s"repos/$owner/$repo/git/refs", headers, CreateReferenceRequest(ref, sha) @@ -63,7 +60,6 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti headers: Map[String, String] ): F[GHResponse[Ref]] = client.patch[UpdateReferenceRequest, Ref]( - accessToken, s"repos/$owner/$repo/git/refs/$ref", headers, UpdateReferenceRequest(sha, force) @@ -75,7 +71,7 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti sha: String, headers: Map[String, String] ): F[GHResponse[RefCommit]] = - client.get[RefCommit](accessToken, s"repos/$owner/$repo/git/commits/$sha", headers) + client.get[RefCommit](s"repos/$owner/$repo/git/commits/$sha", headers) override def createCommit( owner: String, @@ -87,7 +83,6 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti headers: Map[String, String] ): F[GHResponse[RefCommit]] = client.post[NewCommitRequest, RefCommit]( - accessToken, s"repos/$owner/$repo/git/commits", headers, NewCommitRequest(message, tree, parents, author) @@ -99,7 +94,7 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti fileSha: String, headers: Map[String, String] ): F[GHResponse[BlobContent]] = - client.get[BlobContent](accessToken, s"repos/$owner/$repo/git/blobs/$fileSha", headers) + client.get[BlobContent](s"repos/$owner/$repo/git/blobs/$fileSha", headers) override def createBlob( owner: String, @@ -109,7 +104,6 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti headers: Map[String, String] ): F[GHResponse[RefInfo]] = client.post[NewBlobRequest, RefInfo]( - accessToken, s"repos/$owner/$repo/git/blobs", headers, NewBlobRequest(content, encoding) @@ -123,7 +117,6 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti headers: Map[String, String] ): F[GHResponse[TreeResult]] = client.get[TreeResult]( - accessToken, s"repos/$owner/$repo/git/trees/$sha", headers, (if (recursive) Map("recursive" -> "1") else Map.empty) @@ -137,7 +130,6 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti headers: Map[String, String] ): F[GHResponse[TreeResult]] = client.post[NewTreeRequest, TreeResult]( - accessToken, s"repos/$owner/$repo/git/trees", headers, NewTreeRequest(treeDataList, baseTree) @@ -154,7 +146,6 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti headers: Map[String, String] ): F[GHResponse[Tag]] = client.post[NewTagRequest, Tag]( - accessToken, s"repos/$owner/$repo/git/tags", headers, NewTagRequest(tag, message, objectSha, objectType, author) diff --git a/github4s/src/main/scala/github4s/interpreters/IssuesInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/IssuesInterpreter.scala index 9a0c5513c..543c2f100 100644 --- a/github4s/src/main/scala/github4s/interpreters/IssuesInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/IssuesInterpreter.scala @@ -16,18 +16,17 @@ package github4s.interpreters -import github4s.http.HttpClient -import github4s.algebras.Issues -import github4s.GHResponse -import github4s.domain._ import java.net.URLEncoder.encode import java.time.ZonedDateTime import github4s.Decoders._ import github4s.Encoders._ +import github4s.GHResponse +import github4s.algebras.Issues +import github4s.domain._ +import github4s.http.HttpClient -class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option[String]) - extends Issues[F] { +class IssuesInterpreter[F[_]](implicit client: HttpClient[F]) extends Issues[F] { override def listIssues( owner: String, @@ -36,7 +35,7 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[List[Issue]]] = client - .get[List[Issue]](accessToken, s"repos/$owner/$repo/issues", headers, pagination = pagination) + .get[List[Issue]](s"repos/$owner/$repo/issues", headers, pagination = pagination) override def getIssue( owner: String, @@ -44,7 +43,7 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio number: Int, headers: Map[String, String] ): F[GHResponse[Issue]] = - client.get[Issue](accessToken, s"repos/$owner/$repo/issues/$number", headers) + client.get[Issue](s"repos/$owner/$repo/issues/$number", headers) override def searchIssues( query: String, @@ -52,7 +51,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[SearchIssuesResult]] = client.get[SearchIssuesResult]( - accessToken = accessToken, method = "search/issues", headers = headers, params = Map("q" -> s"${encode(query, "utf-8")}+${searchParams.map(_.value).mkString("+")}") @@ -69,7 +67,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[Issue]] = client.post[NewIssueRequest, Issue]( - accessToken, s"repos/$owner/$repo/issues", headers, data = NewIssueRequest(title, body, labels, assignees, milestone) @@ -88,7 +85,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[Issue]] = client.patch[EditIssueRequest, Issue]( - accessToken, s"repos/$owner/$repo/issues/$issue", headers, data = EditIssueRequest(state, title, body, labels, assignees, milestone) @@ -103,7 +99,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio ): F[GHResponse[List[Comment]]] = client .get[List[Comment]]( - accessToken, s"repos/$owner/$repo/issues/$number/comments", headers, pagination = pagination @@ -117,7 +112,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[Comment]] = client.post[CommentData, Comment]( - accessToken, s"repos/$owner/$repo/issues/$number/comments", headers, CommentData(body) @@ -132,7 +126,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio ): F[GHResponse[Comment]] = client .patch[CommentData, Comment]( - accessToken, s"repos/$owner/$repo/issues/comments/$id", headers, CommentData(body) @@ -144,7 +137,7 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio id: Long, headers: Map[String, String] ): F[GHResponse[Unit]] = - client.delete(accessToken, s"repos/$owner/$repo/issues/comments/$id", headers) + client.delete(s"repos/$owner/$repo/issues/comments/$id", headers) override def listLabelsRepository( owner: String, @@ -153,7 +146,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[List[Label]]] = client.get[List[Label]]( - accessToken, s"repos/$owner/$repo/labels", headers = headers, pagination = pagination @@ -167,7 +159,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[List[Label]]] = client.get[List[Label]]( - accessToken, s"repos/$owner/$repo/issues/$number/labels", headers, pagination = pagination @@ -180,7 +171,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[Label]] = client.post[Label, Label]( - accessToken, s"repos/$owner/$repo/labels", headers, label @@ -193,7 +183,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[Label]] = client.patch[Label, Label]( - accessToken, s"repos/$owner/$repo/labels/${label.name}", headers, label @@ -206,7 +195,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[Unit]] = client.delete( - accessToken, s"repos/$owner/$repo/labels/$label", headers ) @@ -219,7 +207,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[List[Label]]] = client.post[List[String], List[Label]]( - accessToken, s"repos/$owner/$repo/issues/$number/labels", headers, labels @@ -233,7 +220,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[List[Label]]] = client.deleteWithResponse[List[Label]]( - accessToken, s"repos/$owner/$repo/issues/$number/labels/$label", headers ) @@ -245,7 +231,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[List[User]]] = client.get[List[User]]( - accessToken, s"repos/$owner/$repo/assignees", headers, pagination = pagination @@ -261,7 +246,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[List[Milestone]]] = client.get[List[Milestone]]( - accessToken, s"repos/$owner/$repo/milestones", headers, pagination = pagination, @@ -282,7 +266,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[Milestone]] = client.post[MilestoneData, Milestone]( - accessToken, s"repos/$owner/$repo/milestones", headers, MilestoneData(title, state, description, due_on) @@ -294,7 +277,7 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio number: Int, headers: Map[String, String] ): F[GHResponse[Milestone]] = - client.get[Milestone](accessToken, s"repos/$owner/$repo/milestones/$number", headers) + client.get[Milestone](s"repos/$owner/$repo/milestones/$number", headers) override def updateMilestone( owner: String, @@ -307,7 +290,6 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio headers: Map[String, String] ): F[GHResponse[Milestone]] = client.patch[MilestoneData, Milestone]( - accessToken, s"repos/$owner/$repo/milestones/$milestone_number", headers, data = MilestoneData(title, state, description, due_on) @@ -319,5 +301,5 @@ class IssuesInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Optio milestone_number: Int, headers: Map[String, String] ): F[GHResponse[Unit]] = - client.delete(accessToken, s"repos/$owner/$repo/milestones/$milestone_number", headers) + client.delete(s"repos/$owner/$repo/milestones/$milestone_number", headers) } diff --git a/github4s/src/main/scala/github4s/interpreters/OrganizationsInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/OrganizationsInterpreter.scala index 25efd4cae..e20a839d0 100644 --- a/github4s/src/main/scala/github4s/interpreters/OrganizationsInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/OrganizationsInterpreter.scala @@ -16,14 +16,13 @@ package github4s.interpreters +import github4s.Decoders._ import github4s.GHResponse -import github4s.http.HttpClient import github4s.algebras.Organizations import github4s.domain._ -import github4s.Decoders._ +import github4s.http.HttpClient -class OrganizationsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option[String]) - extends Organizations[F] { +class OrganizationsInterpreter[F[_]](implicit client: HttpClient[F]) extends Organizations[F] { override def listMembers( org: String, @@ -33,7 +32,6 @@ class OrganizationsInterpreter[F[_]](implicit client: HttpClient[F], accessToken headers: Map[String, String] ): F[GHResponse[List[User]]] = client.get[List[User]]( - accessToken, s"orgs/$org/members", headers, Map( @@ -50,7 +48,6 @@ class OrganizationsInterpreter[F[_]](implicit client: HttpClient[F], accessToken headers: Map[String, String] ): F[GHResponse[List[User]]] = client.get[List[User]]( - accessToken, s"orgs/$org/outside_collaborators", headers, Map("filter" -> filter).collect { case (key, Some(value)) => key -> value }, diff --git a/github4s/src/main/scala/github4s/interpreters/ProjectsInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/ProjectsInterpreter.scala index 21ade30c6..0782f70c7 100644 --- a/github4s/src/main/scala/github4s/interpreters/ProjectsInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/ProjectsInterpreter.scala @@ -16,15 +16,14 @@ package github4s.interpreters +import github4s.Decoders._ import github4s.GHResponse import github4s.algebras.Projects import github4s.domain.{Card, Column, Pagination, Project} import github4s.http.HttpClient -import github4s.Decoders._ class ProjectsInterpreter[F[_]](implicit - client: HttpClient[F], - accessToken: Option[String] + client: HttpClient[F] ) extends Projects[F] { override def listProjects( @@ -34,7 +33,6 @@ class ProjectsInterpreter[F[_]](implicit headers: Map[String, String] ): F[GHResponse[List[Project]]] = client.get[List[Project]]( - accessToken, s"orgs/$org/projects", headers, state.fold(Map.empty[String, String])(s => Map("state" -> s)), @@ -49,7 +47,6 @@ class ProjectsInterpreter[F[_]](implicit headers: Map[String, String] ): F[GHResponse[List[Project]]] = client.get[List[Project]]( - accessToken, s"repos/$owner/$repo/projects", headers, state.fold(Map.empty[String, String])(s => Map("state" -> s)), @@ -62,7 +59,6 @@ class ProjectsInterpreter[F[_]](implicit headers: Map[String, String] ): F[GHResponse[List[Column]]] = client.get[List[Column]]( - accessToken, s"projects/$project_id/columns", headers, Map(), @@ -76,7 +72,6 @@ class ProjectsInterpreter[F[_]](implicit headers: Map[String, String] ): F[GHResponse[List[Card]]] = client.get[List[Card]]( - accessToken, s"projects/columns/$column_id/cards", headers, archived_state.fold(Map.empty[String, String])(s => Map("archived_state" -> s)), diff --git a/github4s/src/main/scala/github4s/interpreters/PullRequestsInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/PullRequestsInterpreter.scala index ef0b30468..eceef6e77 100644 --- a/github4s/src/main/scala/github4s/interpreters/PullRequestsInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/PullRequestsInterpreter.scala @@ -16,15 +16,14 @@ package github4s.interpreters -import github4s.http.HttpClient -import github4s.algebras.PullRequests -import github4s.GHResponse -import github4s.domain._ import github4s.Decoders._ import github4s.Encoders._ +import github4s.GHResponse +import github4s.algebras.PullRequests +import github4s.domain._ +import github4s.http.HttpClient -class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option[String]) - extends PullRequests[F] { +class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F]) extends PullRequests[F] { override def getPullRequest( owner: String, @@ -32,7 +31,7 @@ class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: number: Int, headers: Map[String, String] ): F[GHResponse[PullRequest]] = - client.get[PullRequest](accessToken, s"repos/$owner/$repo/pulls/$number", headers) + client.get[PullRequest](s"repos/$owner/$repo/pulls/$number", headers) override def listPullRequests( owner: String, @@ -42,7 +41,6 @@ class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: headers: Map[String, String] ): F[GHResponse[List[PullRequest]]] = client.get[List[PullRequest]]( - accessToken, s"repos/$owner/$repo/pulls", headers, filters.map(_.tupled).toMap, @@ -58,7 +56,6 @@ class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: ): F[GHResponse[List[PullRequestFile]]] = client .get[List[PullRequestFile]]( - accessToken, s"repos/$owner/$repo/pulls/$number/files", headers, Map.empty, @@ -81,7 +78,7 @@ class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: CreatePullRequestIssue(issue, head, base, maintainerCanModify) } client - .post[CreatePullRequest, PullRequest](accessToken, s"repos/$owner/$repo/pulls", headers, data) + .post[CreatePullRequest, PullRequest](s"repos/$owner/$repo/pulls", headers, data) } override def listReviews( @@ -92,7 +89,6 @@ class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: headers: Map[String, String] ): F[GHResponse[List[PullRequestReview]]] = client.get[List[PullRequestReview]]( - accessToken, s"repos/$owner/$repo/pulls/$pullRequest/reviews", headers, Map.empty, @@ -107,7 +103,6 @@ class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: headers: Map[String, String] ): F[GHResponse[PullRequestReview]] = client.get[PullRequestReview]( - accessToken, s"repos/$owner/$repo/pulls/$pullRequest/reviews/$review", headers ) @@ -121,7 +116,6 @@ class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: ): F[GHResponse[PullRequestReview]] = client .post[CreatePRReviewRequest, PullRequestReview]( - accessToken, s"repos/$owner/$repo/pulls/$pullRequest/reviews", headers, createPRReviewRequest diff --git a/github4s/src/main/scala/github4s/interpreters/RepositoriesInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/RepositoriesInterpreter.scala index 7fb5cde66..529dbd7b5 100644 --- a/github4s/src/main/scala/github4s/interpreters/RepositoriesInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/RepositoriesInterpreter.scala @@ -28,15 +28,14 @@ import github4s.domain._ import github4s.http.HttpClient class RepositoriesInterpreter[F[_]: Functor](implicit - client: HttpClient[F], - accessToken: Option[String] + client: HttpClient[F] ) extends Repositories[F] { override def get( owner: String, repo: String, headers: Map[String, String] ): F[GHResponse[Repository]] = - client.get[Repository](accessToken, s"repos/$owner/$repo", headers) + client.get[Repository](s"repos/$owner/$repo", headers) override def listOrgRepos( org: String, @@ -45,7 +44,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[List[Repository]]] = client.get[List[Repository]]( - accessToken, s"orgs/$org/repos", headers, `type`.fold(Map.empty[String, String])(t => Map("type" -> t)), @@ -59,7 +57,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[List[Repository]]] = client.get[List[Repository]]( - accessToken, s"users/$user/repos", headers, `type`.fold(Map.empty[String, String])(t => Map("type" -> t)), @@ -75,7 +72,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[NonEmptyList[Content]]] = client.get[NonEmptyList[Content]]( - accessToken, s"repos/$owner/$repo/contents/$path", headers, ref.fold(Map.empty[String, String])(r => Map("ref" -> r)), @@ -94,7 +90,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[WriteFileResponse]] = client.put[WriteFileRequest, WriteFileResponse]( - accessToken, s"repos/$owner/$repo/contents/$path", headers, WriteFileRequest(message, content.toBase64, None, branch, committer, author) @@ -113,7 +108,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[WriteFileResponse]] = client.put[WriteFileRequest, WriteFileResponse]( - accessToken, s"repos/$owner/$repo/contents/$path", headers, WriteFileRequest(message, content.toBase64, Some(sha), branch, committer, author) @@ -131,7 +125,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[WriteFileResponse]] = client.deleteWithBody[DeleteFileRequest, WriteFileResponse]( - accessToken, s"repos/$owner/$repo/contents/$path", headers, DeleteFileRequest(message, sha, branch, committer, author) @@ -149,7 +142,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[List[Commit]]] = client.get[List[Commit]]( - accessToken, s"repos/$owner/$repo/commits", headers, Map( @@ -172,7 +164,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[List[Branch]]] = client.get[List[Branch]]( - accessToken, s"repos/$owner/$repo/branches", headers, Map( @@ -191,7 +182,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[List[User]]] = client.get[List[User]]( - accessToken, s"repos/$owner/$repo/contributors", headers, Map( @@ -210,7 +200,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[List[User]]] = client.get[List[User]]( - accessToken, s"repos/$owner/$repo/collaborators", headers, Map( @@ -229,7 +218,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit ): F[GHResponse[Boolean]] = client .getWithoutResponse( - accessToken, s"repos/$owner/$repo/collaborators/$username", headers ) @@ -243,7 +231,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit ): F[GHResponse[UserRepoPermission]] = client .get[UserRepoPermission]( - accessToken, s"repos/$owner/$repo/collaborators/$username/permission", headers, Map.empty @@ -255,7 +242,7 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[Option[Release]]] = client - .get[Option[Release]](accessToken, s"repos/$owner/$repo/releases/latest", headers, Map.empty) + .get[Option[Release]](s"repos/$owner/$repo/releases/latest", headers, Map.empty) override def getRelease( releaseId: Long, @@ -265,7 +252,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit ): F[GHResponse[Option[Release]]] = client .get[Option[Release]]( - accessToken, s"repos/$owner/$repo/releases/$releaseId", headers, Map.empty @@ -278,7 +264,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[List[Release]]] = client.get[List[Release]]( - accessToken, s"repos/$owner/$repo/releases", headers, Map.empty, @@ -297,7 +282,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[Release]] = client.post[NewReleaseRequest, Release]( - accessToken, s"repos/$owner/$repo/releases", headers, NewReleaseRequest(tagName, name, body, targetCommitish, draft, prerelease) @@ -309,7 +293,7 @@ class RepositoriesInterpreter[F[_]: Functor](implicit ref: String, headers: Map[String, String] ): F[GHResponse[CombinedStatus]] = - client.get[CombinedStatus](accessToken, s"repos/$owner/$repo/commits/$ref/status", headers) + client.get[CombinedStatus](s"repos/$owner/$repo/commits/$ref/status", headers) override def listStatuses( owner: String, @@ -319,7 +303,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[List[Status]]] = client.get[List[Status]]( - accessToken, s"repos/$owner/$repo/commits/$ref/statuses", headers, pagination = pagination @@ -336,7 +319,6 @@ class RepositoriesInterpreter[F[_]: Functor](implicit headers: Map[String, String] ): F[GHResponse[Status]] = client.post[NewStatusRequest, Status]( - accessToken, s"repos/$owner/$repo/statuses/$sha", headers, NewStatusRequest(state, target_url, description, context) diff --git a/github4s/src/main/scala/github4s/interpreters/StaticAccessToken.scala b/github4s/src/main/scala/github4s/interpreters/StaticAccessToken.scala new file mode 100644 index 000000000..d1a915bb5 --- /dev/null +++ b/github4s/src/main/scala/github4s/interpreters/StaticAccessToken.scala @@ -0,0 +1,17 @@ +package github4s.interpreters + +import github4s.GHResponse +import github4s.algebras.AccessToken + +/** + * A simple static version + */ +class StaticAccessToken[F[_]](accessToken: Option[String]) extends AccessToken[F] { + + override def withAccessToken[T](f: Option[String] => F[GHResponse[T]]): F[GHResponse[T]] = + f(accessToken) +} + +object StaticAccessToken { + def noToken[F[_]] = new StaticAccessToken[F](None) +} diff --git a/github4s/src/main/scala/github4s/interpreters/TeamsInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/TeamsInterpreter.scala index 854131b08..fa8c36335 100644 --- a/github4s/src/main/scala/github4s/interpreters/TeamsInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/TeamsInterpreter.scala @@ -22,13 +22,12 @@ import github4s.algebras.Teams import github4s.domain._ import github4s.http.HttpClient -class TeamsInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option[String]) - extends Teams[F] { +class TeamsInterpreter[F[_]](implicit client: HttpClient[F]) extends Teams[F] { override def listTeams( org: String, pagination: Option[Pagination], headers: Map[String, String] ): F[GHResponse[List[Team]]] = - client.get[List[Team]](accessToken, method = s"orgs/$org/teams", headers, Map.empty, pagination) + client.get[List[Team]](method = s"orgs/$org/teams", headers, Map.empty, pagination) } diff --git a/github4s/src/main/scala/github4s/interpreters/UsersInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/UsersInterpreter.scala index d970e6130..90d25a455 100644 --- a/github4s/src/main/scala/github4s/interpreters/UsersInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/UsersInterpreter.scala @@ -22,14 +22,13 @@ import github4s.GHResponse import github4s.domain._ import github4s.Decoders._ -class UsersInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option[String]) - extends Users[F] { +class UsersInterpreter[F[_]](implicit client: HttpClient[F]) extends Users[F] { override def get(username: String, headers: Map[String, String]): F[GHResponse[User]] = - client.get[User](accessToken, s"users/$username", headers) + client.get[User](s"users/$username", headers) override def getAuth(headers: Map[String, String]): F[GHResponse[User]] = - client.get[User](accessToken, "user", headers) + client.get[User]("user", headers) override def getUsers( since: Int, @@ -37,7 +36,7 @@ class UsersInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option headers: Map[String, String] ): F[GHResponse[List[User]]] = client - .get[List[User]](accessToken, "users", headers, Map("since" -> since.toString), pagination) + .get[List[User]]("users", headers, Map("since" -> since.toString), pagination) override def getFollowing( username: String, @@ -45,5 +44,5 @@ class UsersInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Option headers: Map[String, String] ): F[GHResponse[List[User]]] = client - .get[List[User]](accessToken, s"users/$username/following", headers, pagination = pagination) + .get[List[User]](s"users/$username/following", headers, pagination = pagination) } diff --git a/github4s/src/main/scala/github4s/modules/GithubAPIs.scala b/github4s/src/main/scala/github4s/modules/GithubAPIs.scala index 82b30da8d..fc0f99e63 100644 --- a/github4s/src/main/scala/github4s/modules/GithubAPIs.scala +++ b/github4s/src/main/scala/github4s/modules/GithubAPIs.scala @@ -26,11 +26,10 @@ import org.http4s.client.Client class GithubAPIv3[F[_]: Sync]( client: Client[F], config: GithubConfig, - accessToken: Option[String] = None + accessToken: AccessToken[F] ) extends GithubAPIs[F] { - implicit val httpClient = new HttpClient[F](client, config) - implicit val at = accessToken + implicit val httpClient = new HttpClient[F](client, config, accessToken) override val users: Users[F] = new UsersInterpreter[F] override val repos: Repositories[F] = new RepositoriesInterpreter[F] @@ -45,3 +44,9 @@ class GithubAPIv3[F[_]: Sync]( override val projects: Projects[F] = new ProjectsInterpreter[F] } + +object GithubAPIv3 { + + def noAuth[F[_]: Sync](client: Client[F], config: GithubConfig): GithubAPIv3[F] = + new GithubAPIv3[F](client, config, StaticAccessToken.noToken) +} diff --git a/github4s/src/test/scala/github4s/unit/ActivitiesSpec.scala b/github4s/src/test/scala/github4s/unit/ActivitiesSpec.scala index aa3ad0e89..3b718311a 100644 --- a/github4s/src/test/scala/github4s/unit/ActivitiesSpec.scala +++ b/github4s/src/test/scala/github4s/unit/ActivitiesSpec.scala @@ -25,8 +25,6 @@ import github4s.utils.BaseSpec class ActivitiesSpec extends BaseSpec { - implicit val token = sampleToken - "Activity.setThreadSub" should "call to httpClient.put with the right parameters" in { val response: IO[GHResponse[Subscription]] = diff --git a/github4s/src/test/scala/github4s/unit/AuthSpec.scala b/github4s/src/test/scala/github4s/unit/AuthSpec.scala index 67d5de311..3582aead2 100644 --- a/github4s/src/test/scala/github4s/unit/AuthSpec.scala +++ b/github4s/src/test/scala/github4s/unit/AuthSpec.scala @@ -26,8 +26,6 @@ import github4s.interpreters.AuthInterpreter class AuthSpec extends BaseSpec { - implicit val token = sampleToken - "Auth.newAuth" should "call to httpClient.postAuth with the right parameters" in { val response: IO[GHResponse[Authorization]] = diff --git a/github4s/src/test/scala/github4s/unit/GistSpec.scala b/github4s/src/test/scala/github4s/unit/GistSpec.scala index f4bb132f7..218d22cd7 100644 --- a/github4s/src/test/scala/github4s/unit/GistSpec.scala +++ b/github4s/src/test/scala/github4s/unit/GistSpec.scala @@ -25,8 +25,6 @@ import github4s.utils.BaseSpec class GistSpec extends BaseSpec { - implicit val token = sampleToken - "Gist.newGist" should "call to httpClient.post with the right parameters" in { val response: IO[GHResponse[Gist]] = diff --git a/github4s/src/test/scala/github4s/unit/GitDataSpec.scala b/github4s/src/test/scala/github4s/unit/GitDataSpec.scala index 88d5491a4..50a6b7bb9 100644 --- a/github4s/src/test/scala/github4s/unit/GitDataSpec.scala +++ b/github4s/src/test/scala/github4s/unit/GitDataSpec.scala @@ -26,8 +26,6 @@ import github4s.utils.BaseSpec class GitDataSpec extends BaseSpec { - implicit val token = sampleToken - "GitData.getReference" should "call to httpClient.get with the right parameters" in { val response: IO[GHResponse[NonEmptyList[Ref]]] = diff --git a/github4s/src/test/scala/github4s/unit/IssuesSpec.scala b/github4s/src/test/scala/github4s/unit/IssuesSpec.scala index d9a5294e1..43d90d670 100644 --- a/github4s/src/test/scala/github4s/unit/IssuesSpec.scala +++ b/github4s/src/test/scala/github4s/unit/IssuesSpec.scala @@ -28,8 +28,6 @@ import github4s.utils.BaseSpec class IssuesSpec extends BaseSpec { - implicit val token = sampleToken - "Issues.listIssues" should "call httpClient.get with the right parameters" in { val response: IO[GHResponse[List[Issue]]] = IO(GHResponse(List(issue).asRight, okStatusCode, Map.empty)) diff --git a/github4s/src/test/scala/github4s/unit/OrganizationsSpec.scala b/github4s/src/test/scala/github4s/unit/OrganizationsSpec.scala index 18139b644..64bc69b36 100644 --- a/github4s/src/test/scala/github4s/unit/OrganizationsSpec.scala +++ b/github4s/src/test/scala/github4s/unit/OrganizationsSpec.scala @@ -25,8 +25,6 @@ import github4s.utils.BaseSpec class OrganizationsSpec extends BaseSpec { - implicit val token = sampleToken - "Organization.listMembers" should "call to httpClient.get with the right parameters" in { val response: IO[GHResponse[List[User]]] = diff --git a/github4s/src/test/scala/github4s/unit/ProjectSpec.scala b/github4s/src/test/scala/github4s/unit/ProjectSpec.scala index e10256e51..6fb1afcc1 100644 --- a/github4s/src/test/scala/github4s/unit/ProjectSpec.scala +++ b/github4s/src/test/scala/github4s/unit/ProjectSpec.scala @@ -25,8 +25,6 @@ import github4s.utils.BaseSpec class ProjectSpec extends BaseSpec { - implicit val token = sampleToken - "Project.listProjects" should "call to httpClient.get with the right parameters" in { val response: IO[GHResponse[List[Project]]] = diff --git a/github4s/src/test/scala/github4s/unit/PullRequestsSpec.scala b/github4s/src/test/scala/github4s/unit/PullRequestsSpec.scala index 635ac3f9b..1be951d64 100644 --- a/github4s/src/test/scala/github4s/unit/PullRequestsSpec.scala +++ b/github4s/src/test/scala/github4s/unit/PullRequestsSpec.scala @@ -25,8 +25,6 @@ import github4s.utils.BaseSpec class PullRequestsSpec extends BaseSpec { - implicit val token = sampleToken - "PullRequests.get" should "call to httpClient.get with the right parameters" in { val response: IO[GHResponse[PullRequest]] = diff --git a/github4s/src/test/scala/github4s/unit/ReposSpec.scala b/github4s/src/test/scala/github4s/unit/ReposSpec.scala index cf72b89c7..bd1a667ac 100644 --- a/github4s/src/test/scala/github4s/unit/ReposSpec.scala +++ b/github4s/src/test/scala/github4s/unit/ReposSpec.scala @@ -27,8 +27,6 @@ import github4s.utils.BaseSpec class ReposSpec extends BaseSpec { - implicit val token = sampleToken - "Repos.get" should "call to httpClient.get with the right parameters" in { val response: IO[GHResponse[Repository]] = IO(GHResponse(repo.asRight, okStatusCode, Map.empty)) diff --git a/github4s/src/test/scala/github4s/unit/TeamsSpec.scala b/github4s/src/test/scala/github4s/unit/TeamsSpec.scala index 0db7f4018..51bf61305 100644 --- a/github4s/src/test/scala/github4s/unit/TeamsSpec.scala +++ b/github4s/src/test/scala/github4s/unit/TeamsSpec.scala @@ -25,8 +25,6 @@ import github4s.utils.BaseSpec class TeamsSpec extends BaseSpec { - implicit val token = sampleToken - "Teams.listTeam" should "call to httpClient.get with the right parameters" in { val response: IO[GHResponse[List[Team]]] = IO(GHResponse(List(team).asRight, okStatusCode, Map.empty)) diff --git a/github4s/src/test/scala/github4s/unit/UserSpec.scala b/github4s/src/test/scala/github4s/unit/UserSpec.scala index 0598f14bc..e69263cc0 100644 --- a/github4s/src/test/scala/github4s/unit/UserSpec.scala +++ b/github4s/src/test/scala/github4s/unit/UserSpec.scala @@ -25,8 +25,6 @@ import github4s.utils.BaseSpec class UserSpec extends BaseSpec { - implicit val token = sampleToken - "UsersInterpreter.get" should "call to httpClient.get with the right parameters" in { val response: IO[GHResponse[User]] = IO(GHResponse(user.asRight, okStatusCode, Map.empty)) diff --git a/github4s/src/test/scala/github4s/utils/BaseSpec.scala b/github4s/src/test/scala/github4s/utils/BaseSpec.scala index 3d2515b62..40ecd2739 100644 --- a/github4s/src/test/scala/github4s/utils/BaseSpec.scala +++ b/github4s/src/test/scala/github4s/utils/BaseSpec.scala @@ -19,6 +19,7 @@ package github4s.utils import cats.effect.IO import github4s.domain.Pagination import github4s.http.HttpClient +import github4s.interpreters.StaticAccessToken import github4s.{GHResponse, GithubConfig} import io.circe.{Decoder, Encoder} import org.http4s.client.Client @@ -38,7 +39,8 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory ) @com.github.ghik.silencer.silent("deprecated") - class HttpClientTest extends HttpClient[IO](mock[Client[IO]], implicitly) + class HttpClientTest + extends HttpClient[IO](mock[Client[IO]], implicitly, new StaticAccessToken(sampleToken)) def httpClientMockGet[Out]( url: String, @@ -49,13 +51,12 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory val httpClientMock = mock[HttpClientTest] (httpClientMock .get[Out]( - _: Option[String], _: String, _: Map[String, String], _: Map[String, String], _: Option[Pagination] )(_: Decoder[Out])) - .expects(sampleToken, url, headers ++ headerUserAgent, params, *, *) + .expects(url, headers ++ headerUserAgent, params, *, *) .returns(response) httpClientMock } @@ -66,8 +67,8 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory ): HttpClient[IO] = { val httpClientMock = mock[HttpClientTest] (httpClientMock - .getWithoutResponse(_: Option[String], _: String, _: Map[String, String])) - .expects(sampleToken, url, headerUserAgent) + .getWithoutResponse(_: String, _: Map[String, String])) + .expects(url, headerUserAgent) .returns(response) httpClientMock } @@ -79,11 +80,11 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory ): HttpClient[IO] = { val httpClientMock = mock[HttpClientTest] (httpClientMock - .post[In, Out](_: Option[String], _: String, _: Map[String, String], _: In)( + .post[In, Out](_: String, _: Map[String, String], _: In)( _: Encoder[In], _: Decoder[Out] )) - .expects(sampleToken, url, headerUserAgent, req, *, *) + .expects(url, headerUserAgent, req, *, *) .returns(response) httpClientMock } @@ -126,11 +127,11 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory ): HttpClient[IO] = { val httpClientMock = mock[HttpClientTest] (httpClientMock - .patch[In, Out](_: Option[String], _: String, _: Map[String, String], _: In)( + .patch[In, Out](_: String, _: Map[String, String], _: In)( _: Encoder[In], _: Decoder[Out] )) - .expects(sampleToken, url, headerUserAgent, req, *, *) + .expects(url, headerUserAgent, req, *, *) .returns(response) httpClientMock } @@ -142,11 +143,11 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory ): HttpClient[IO] = { val httpClientMock = mock[HttpClientTest] (httpClientMock - .put[In, Out](_: Option[String], _: String, _: Map[String, String], _: In)( + .put[In, Out](_: String, _: Map[String, String], _: In)( _: Encoder[In], _: Decoder[Out] )) - .expects(sampleToken, url, headerUserAgent, req, *, *) + .expects(url, headerUserAgent, req, *, *) .returns(response) httpClientMock } @@ -154,8 +155,8 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory def httpClientMockDelete(url: String, response: IO[GHResponse[Unit]]): HttpClient[IO] = { val httpClientMock = mock[HttpClientTest] (httpClientMock - .delete(_: Option[String], _: String, _: Map[String, String])) - .expects(sampleToken, url, headerUserAgent) + .delete(_: String, _: Map[String, String])) + .expects(url, headerUserAgent) .returns(response) httpClientMock } @@ -166,10 +167,10 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory ): HttpClient[IO] = { val httpClientMock = mock[HttpClientTest] (httpClientMock - .deleteWithResponse[Out](_: Option[String], _: String, _: Map[String, String])( + .deleteWithResponse[Out](_: String, _: Map[String, String])( _: Decoder[Out] )) - .expects(sampleToken, url, headerUserAgent, *) + .expects(url, headerUserAgent, *) .returns(response) httpClientMock } @@ -181,11 +182,11 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory ): HttpClient[IO] = { val httpClientMock = mock[HttpClientTest] (httpClientMock - .deleteWithBody[In, Out](_: Option[String], _: String, _: Map[String, String], _: In)( + .deleteWithBody[In, Out](_: String, _: Map[String, String], _: In)( _: Encoder[In], _: Decoder[Out] )) - .expects(sampleToken, url, headerUserAgent, req, *, *) + .expects(url, headerUserAgent, req, *, *) .returns(response) httpClientMock } diff --git a/microsite/docs/docs.md b/microsite/docs/docs.md index 35bcb7ab1..20c5f46cf 100755 --- a/microsite/docs/docs.md +++ b/microsite/docs/docs.md @@ -11,6 +11,10 @@ permalink: docs In order to access the Github API, you will need to have [an access token][access-token] with the appropriate scopes (i.e. if you want to create gists, your token will need to have the gist scope). +A personal token can be valid forever. However, the ones issued for [Github Apps][github-app] have to be re-issued every hour. +To solve that issue, github4s provides [AccessToken][access-token-scala] which you can hook into to provide authentication for your app using third-party libraries such as JWT implementations, for example. +You can find the default implementation for static tokens in [StaticAccessToken][static-access-token-scala]. + ## Github4s Github4s uses [Tagless Final encoding](https://typelevel.org/blog/2017/12/27/optimizing-final-tagless.html). @@ -240,6 +244,9 @@ headers which will be added to every request sent to the GitHub API. The user ag added by default. [access-token]: https://github.com/settings/tokens +[github-app]: https://github.com/settings/apps +[access-token-scala]: https://github.com/47degrees/github4s/blob/master/github4s/src/main/scala/github4s/algebras/AccessToken.scala +[access-token-scala]: https://github.com/47degrees/github4s/blob/master/github4s/src/main/scala/github4s/interpreters/StaticAccessToken.scala [cats-sync]: https://typelevel.org/cats-effect/typeclasses/sync.html [monix-task]: https://monix.io/docs/3x/eval/task.html [http4s-client]: https://http4s.org/v0.21/client/