<commit_before>package services

import javax.inject.Inject

import com.google.inject.ImplementedBy
import play.api.Logger
import play.api.libs.json.{JsError, JsSuccess, Json}
import play.api.libs.ws.WSClient

import scala.concurrent.{ExecutionContext, Future}

case class AccessTokenResponse(access_token: String, expires_in: Long, scope: String, refresh_token: String, token_type: String)

case class RefreshTokenResponse(access_token: String, expires_in: Long)

@ImplementedBy(classOf[OAuth2ServiceImpl])
trait OAuth2Service {
  def convertCode(code: String, userId: Long, empref: String): Future[AccessTokenResponse]

  def refreshAccessToken(refreshToken: String): Future[Option[RefreshTokenResponse]]
}

class OAuth2ServiceImpl @Inject()(ws: WSClient)(implicit ec: ExecutionContext) extends OAuth2Service {

  import ServiceConfig.config._

  implicit val atrFormat = Json.format[AccessTokenResponse]

  def convertCode(code: String, userId: Long, empref: String): Future[AccessTokenResponse] = {
    val params = Map(
      "grant_type" -> "authorization_code",
      "code" -> code,
      "redirect_uri" -> taxservice.callbackURL,
      "client_id" -> client.id,
      "client_secret" -> client.secret
    ).map { case (k, v) => k -> Seq(v) }

    ws.url(taxservice.accessTokenUri).post(params).map { response =>
      response.status match {
        case 200 => response.json.validate[AccessTokenResponse] match {
          case JsSuccess(resp, _) => resp
          case JsError(errs) => throw new Exception(s"could not decode token response: $errs")
        }

        case s =>
          Logger.warn("Request to exchange code for token failed")
          Logger.warn(s"Response is $s with body: '${response.body}'")
          throw new Exception(s"Request to exchange code for token failed with ${response.body}")
      }
    }
  }

  implicit val rtrFormat = Json.format[RefreshTokenResponse]

  def refreshAccessToken(refreshToken: String): Future[Option[RefreshTokenResponse]] = {
    val params = Map(
      "grant_type" -> "refresh_token",
      "refresh_token" -> refreshToken,
      "client_id" -> client.id,
      "client_secret" -> client.secret
    ).map { case (k, v) => k -> Seq(v) }

    ws.url(taxservice.accessTokenUri).post(params).map { response =>
      response.status match {
        case 200 => response.json.validate[RefreshTokenResponse].asOpt

        case s =>
          Logger.error(response.body)
          None
      }
    }
  }<commit_msg>Bit of refactoring of the way params are created in the OAuth2Service to remove some duplication<commit_after>package services

import javax.inject.Inject

import com.google.inject.ImplementedBy
import play.api.Logger
import play.api.libs.json.{JsError, JsSuccess, Json}
import play.api.libs.ws.WSClient

import scala.concurrent.{ExecutionContext, Future}

case class AccessTokenResponse(access_token: String, expires_in: Long, scope: String, refresh_token: String, token_type: String)

case class RefreshTokenResponse(access_token: String, expires_in: Long)

@ImplementedBy(classOf[OAuth2ServiceImpl])
trait OAuth2Service {
  def convertCode(code: String, userId: Long, empref: String): Future[AccessTokenResponse]

  def refreshAccessToken(refreshToken: String): Future[Option[RefreshTokenResponse]]
}

class OAuth2ServiceImpl @Inject()(ws: WSClient)(implicit ec: ExecutionContext) extends OAuth2Service {

  import ServiceConfig.config._

  val clientDetails = Map(
    "client_id" -> client.id,
    "client_secret" -> client.secret
  )

  implicit val atrFormat = Json.format[AccessTokenResponse]

  def mkParams(ps: (String, String)*): Map[String, Seq[String]] = {
    (clientDetails ++ ps).map { case (k, v) => k -> Seq(v) }
  }

  def convertCode(code: String, userId: Long, empref: String): Future[AccessTokenResponse] = {
    val params = mkParams(
      "grant_type" -> "authorization_code",
      "code" -> code,
      "redirect_uri" -> taxservice.callbackURL
    )

    ws.url(taxservice.accessTokenUri).post(params).map { response =>
      response.status match {
        case 200 => response.json.validate[AccessTokenResponse] match {
          case JsSuccess(resp, _) => resp
          case JsError(errs) => throw new Exception(s"could not decode token response: $errs")
        }

        case s =>
          Logger.warn("Request to exchange code for token failed")
          Logger.warn(s"Response is $s with body: '${response.body}'")
          throw new Exception(s"Request to exchange code for token failed with ${response.body}")
      }
    }
  }

  implicit val rtrFormat = Json.format[RefreshTokenResponse]

  def refreshAccessToken(refreshToken: String): Future[Option[RefreshTokenResponse]] = {
    val params = mkParams(
      "grant_type" -> "refresh_token",
      "refresh_token" -> refreshToken
    )

    ws.url(taxservice.accessTokenUri).post(params).map { response =>
      response.status match {
        case 200 => response.json.validate[RefreshTokenResponse].asOpt

        case s =>
          Logger.error(response.body)
          None
      }
    }
  }
{'fraction_non_alphanumeric': 0.11321499013806706, 'fraction_numerical': 0.004142011834319527, 'mean_word_length': 3.6780442804428044, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4659163', 'n_tokens_mistral': 1519, 'n_tokens_neox': 1484, 'n_words': 363}