Skip to content

Commit

Permalink
Merge bf4c07c into e47fbdb
Browse files Browse the repository at this point in the history
  • Loading branch information
johnduffell committed Oct 31, 2018
2 parents e47fbdb + bf4c07c commit 4142c93
Show file tree
Hide file tree
Showing 26 changed files with 457 additions and 276 deletions.
Expand Up @@ -9,23 +9,23 @@ import com.gu.effects.{GetFromS3, RawEffects}
import com.gu.identity.IdentityCookieToIdentityUser.{CookieValuesToIdentityUser, IdentityId, IdentityUser}
import com.gu.identity.{IdentityCookieToIdentityUser, IdentityTestUserConfig, IsIdentityTestUser}
import com.gu.salesforce.SalesforceAuthenticate.{SFAuthConfig, SFAuthTestConfig}
import com.gu.salesforce.SalesforceClient.StringHttpRequest
import com.gu.salesforce.SalesforceGenericIdLookup.{FieldName, LookupValue, SfObjectType, TSalesforceGenericIdLookup}
import com.gu.salesforce.cases.SalesforceCase
import com.gu.salesforce.cases.SalesforceCase.Create.WireNewCase
import com.gu.salesforce.cases.SalesforceCase.GetMostRecentCaseByContactId.TGetMostRecentCaseByContactId
import com.gu.salesforce.cases.SalesforceCase.{CaseId, CaseSubject, CaseWithId, ContactId, SubscriptionId}
import com.gu.salesforce.{JsonHttp, SalesforceClient, SalesforceGenericIdLookup}
import com.gu.salesforce.{SalesforceClient, SalesforceGenericIdLookup}
import com.gu.util.Logging
import com.gu.util.apigateway.ApiGatewayHandler.{LambdaIO, Operation}
import com.gu.util.apigateway.ResponseModels.ApiResponse
import com.gu.util.apigateway.{ApiGatewayHandler, ApiGatewayRequest, ApiGatewayResponse}
import com.gu.util.config.LoadConfigModule.StringFromS3
import com.gu.util.config._
import com.gu.util.reader.Types._
import com.gu.util.resthttp.JsonHttp.StringHttpRequest
import com.gu.util.resthttp.RestRequestMaker.BodyAsString
import com.gu.util.resthttp.Types.ClientFailableOp
import com.gu.util.resthttp.{HttpOp, Types}
import com.gu.util.resthttp.{HttpOp, JsonHttp, Types}
import okhttp3.{Request, Response}
import play.api.libs.json._

Expand Down Expand Up @@ -160,11 +160,11 @@ object Handler extends Logging {
(for {
identityAndSfRequests <- sfBackendForIdentityCookieHeader(apiGatewayRequest.headers)
raiseCaseDetail <- apiGatewayRequest.bodyAsCaseClass[RaiseCaseDetail]()
lookupByIdOp = SalesforceGenericIdLookup(identityAndSfRequests.sfClient.wrap(JsonHttp.get))
mostRecentCaseOp = SalesforceCase.GetMostRecentCaseByContactId(identityAndSfRequests.sfClient.wrap(JsonHttp.get))
createCaseOp = SalesforceCase.Create(identityAndSfRequests.sfClient.wrap(JsonHttp.post))
lookupByIdOp = SalesforceGenericIdLookup(identityAndSfRequests.sfClient.wrapWith(JsonHttp.get))
mostRecentCaseOp = SalesforceCase.GetMostRecentCaseByContactId(identityAndSfRequests.sfClient.wrapWith(JsonHttp.get))
createCaseOp = SalesforceCase.Create(identityAndSfRequests.sfClient.wrapWith(JsonHttp.post))
wiredCreateCaseOp = buildWireNewCaseForSalesforce.tupled andThen createCaseOp
sfUpdateOp = SalesforceCase.Update(identityAndSfRequests.sfClient.wrap(JsonHttp.patch))
sfUpdateOp = SalesforceCase.Update(identityAndSfRequests.sfClient.wrapWith(JsonHttp.patch))
updateReasonOnRecentCaseOp = updateCaseReason(sfUpdateOp)_
newOrResumeCaseOp = newOrResumeCase(wiredCreateCaseOp, updateReasonOnRecentCaseOp, raiseCaseDetail)_
wiredRaiseCase = raiseCase(lookupByIdOp, mostRecentCaseOp, newOrResumeCaseOp)_
Expand Down Expand Up @@ -214,11 +214,11 @@ object Handler extends Logging {
(for {
identityAndSfRequests <- sfBackendForIdentityCookieHeader(apiGatewayRequest.headers)
pathParams <- apiGatewayRequest.pathParamsAsCaseClass[CasePathParams]()
lookupByIdOp = SalesforceGenericIdLookup(identityAndSfRequests.sfClient.wrap(JsonHttp.get))
getCaseByIdOp = SalesforceCase.GetById[CaseWithContactId](identityAndSfRequests.sfClient.wrap(JsonHttp.get))_
lookupByIdOp = SalesforceGenericIdLookup(identityAndSfRequests.sfClient.wrapWith(JsonHttp.get))
getCaseByIdOp = SalesforceCase.GetById[CaseWithContactId](identityAndSfRequests.sfClient.wrapWith(JsonHttp.get))_
_ <- verifyCaseBelongsToUser(lookupByIdOp, getCaseByIdOp)(identityAndSfRequests.identityUser.id, pathParams.caseId)
requestBody <- apiGatewayRequest.bodyAsCaseClass[JsValue]()
sfUpdateOp = SalesforceCase.Update(identityAndSfRequests.sfClient.wrap(JsonHttp.patch))
sfUpdateOp = SalesforceCase.Update(identityAndSfRequests.sfClient.wrapWith(JsonHttp.patch))
_ <- sfUpdateOp(pathParams.caseId, requestBody).toApiGatewayOp("update case")
} yield ApiGatewayResponse.successfulExecution).apiResponse

Expand Down
Expand Up @@ -6,12 +6,12 @@ import com.gu.cancellation.sf_cases.Handler.{CasePathParams, SfBackendForIdentit
import com.gu.cancellation.sf_cases.TypeConvert._
import com.gu.effects.{GetFromS3, RawEffects}
import com.gu.identity.IdentityCookieToIdentityUser.{IdentityId, IdentityUser}
import com.gu.salesforce.JsonHttp
import com.gu.salesforce.cases.SalesforceCase
import com.gu.salesforce.cases.SalesforceCase.CaseWithId
import com.gu.test.EffectsTest
import com.gu.util.apigateway.ApiGatewayHandler.LambdaIO
import com.gu.util.apigateway.{ApiGatewayRequest, ApiGatewayResponse}
import com.gu.util.resthttp.JsonHttp
import org.scalatest.{FlatSpec, Matchers}
import play.api.libs.json._

Expand All @@ -29,7 +29,7 @@ class EndToEndHandlerEffectsTest extends FlatSpec with Matchers {
(for {
identityAndSfRequests <- sfBackendForIdentityCookieHeader(apiGatewayRequest.headers)
pathParams <- apiGatewayRequest.pathParamsAsCaseClass[CasePathParams]()
sfGet = SalesforceCase.GetById[JsValue](identityAndSfRequests.sfClient.wrap(JsonHttp.get))_
sfGet = SalesforceCase.GetById[JsValue](identityAndSfRequests.sfClient.wrapWith(JsonHttp.get))_
getCaseResponse <- sfGet(pathParams.caseId).toApiGatewayOp("get case detail")
} yield ApiGatewayResponse("200", getCaseResponse)).apiResponse

Expand Down
@@ -0,0 +1,31 @@
package com.gu.identity

import com.gu.identityBackfill.Types.EmailAddress
import com.gu.identityBackfill.salesforce.UpdateSalesforceIdentityId.IdentityId
import com.gu.util.resthttp.HttpOp.HttpOpWrapper
import com.gu.util.resthttp.RestRequestMaker
import com.gu.util.resthttp.RestRequestMaker._
import play.api.libs.json.{JsValue, Json, Reads}

object CreateGuestAccount {

case class WireGuestRegistrationResponse(userId: String)
implicit val reads = Json.reads[WireGuestRegistrationResponse]

case class WireGuestRegistrationRequest(primaryEmailAddress: String)
implicit val writes = Json.writes[WireGuestRegistrationRequest]

case class WireIdentityResponse(status: String, guestRegistrationRequest: WireGuestRegistrationResponse)
implicit val userResponseReads: Reads[WireIdentityResponse] = Json.reads[WireIdentityResponse]

def toRequest(emailAddress: EmailAddress): PostRequest =
PostRequest(WireGuestRegistrationRequest(emailAddress.value), RelativePath("/guest"))

def toResponse(wireGuestRegistrationResponse: WireIdentityResponse): IdentityId =
IdentityId(wireGuestRegistrationResponse.guestRegistrationRequest.userId)

val wrapper: HttpOpWrapper[EmailAddress, PostRequest, JsValue, IdentityId] =
HttpOpWrapper[EmailAddress, PostRequest, JsValue, IdentityId](toRequest, RestRequestMaker.toResult[WireIdentityResponse](_).map(toResponse))

}

Expand Up @@ -3,18 +3,17 @@ package com.gu.identity
import com.gu.identity.GetByEmail.RawWireModel.{User, UserResponse}
import com.gu.identityBackfill.Types.EmailAddress
import com.gu.identityBackfill.salesforce.UpdateSalesforceIdentityId.IdentityId
import com.gu.util.config.ConfigLocation
import okhttp3.{HttpUrl, Request, Response}
import play.api.libs.json.{Json, Reads}
import scalaz.syntax.std.either._
import scalaz.{-\/, \/, \/-}
import com.gu.util.resthttp.HttpOp.HttpOpWrapper
import com.gu.util.resthttp.RestRequestMaker
import com.gu.util.resthttp.RestRequestMaker.{GetRequestWithParams, RelativePath, UrlParams}
import com.gu.util.resthttp.Types.{ClientFailableOp, ClientSuccess, GenericError}
import play.api.libs.json.{JsValue, Json, Reads}

object GetByEmail {

sealed trait ApiError
case class OtherError(message: String) extends ApiError
case object NotFound extends ApiError
case object NotValidated extends ApiError
sealed trait IdentityAccount
case class IdentityAccountWithValidatedEmail(identityId: IdentityId) extends IdentityAccount
case object IdentityAccountWithUnvalidatedEmail extends IdentityAccount

object RawWireModel {

Expand All @@ -29,43 +28,29 @@ object GetByEmail {

}

def identityIdFromUser(user: User) =
IdentityId(user.id)
def emailAddressToParams(emailAddress: EmailAddress): GetRequestWithParams =
GetRequestWithParams(RelativePath(s"/user"), UrlParams(Map("emailAddress" -> emailAddress.value)))

def userFromResponse(userResponse: UserResponse): ApiError \/ User =
val jsToWireModel: JsValue => ClientFailableOp[UserResponse] = RestRequestMaker.toResult[UserResponse]

def userFromResponse(userResponse: UserResponse): ClientFailableOp[User] =
userResponse match {
case UserResponse("ok", user) => \/-(user)
case _ => -\/(OtherError("not an OK response from api"))
case UserResponse("ok", user) => ClientSuccess(user)
case error => GenericError(s"not an OK response from api: $error")
}

def apply(getResponse: Request => Response, identityConfig: IdentityConfig)(email: EmailAddress): ApiError \/ IdentityId = {

val url = HttpUrl.parse(identityConfig.baseUrl + "/user").newBuilder().addQueryParameter("emailAddress", email.value).build()
val response = getResponse(new Request.Builder().url(url).addHeader("X-GU-ID-Client-Access-Token", "Bearer " + identityConfig.apiToken).build())

def wireToDomainModel(userResponse: UserResponse): ClientFailableOp[IdentityAccount] = {
for {
_ <- response.code match {
case 200 => \/-(())
case 404 => -\/(NotFound)
case code => -\/(OtherError(s"failed http with ${code}"))
}
body = response.body.byteStream
userResponse <- Json.parse(body).validate[UserResponse].asEither.disjunction.leftMap(err => OtherError(err.mkString(", ")))
user <- userFromResponse(userResponse)
_ <- if (user.statusFields.userEmailValidated) \/-(()) else -\/(NotValidated)
identityId = identityIdFromUser(user)
identityId = if (user.statusFields.userEmailValidated) IdentityAccountWithValidatedEmail(IdentityId(user.id)) else IdentityAccountWithUnvalidatedEmail
} yield identityId

}

}

case class IdentityConfig(
baseUrl: String,
apiToken: String
)
val wrapper: HttpOpWrapper[EmailAddress, GetRequestWithParams, JsValue, IdentityAccount] =
HttpOpWrapper[EmailAddress, GetRequestWithParams, JsValue, IdentityAccount](
fromNewParam = emailAddressToParams,
toNewResponse = jsToWireModel.andThen(_.flatMap(wireToDomainModel))
)

object IdentityConfig {
implicit val reads: Reads[IdentityConfig] = Json.reads[IdentityConfig]
implicit val location = ConfigLocation[IdentityConfig](path = "identity", version = 1)
}
@@ -0,0 +1,41 @@
package com.gu.identity

import com.gu.util.config.ConfigLocation
import com.gu.util.resthttp.HttpOp
import com.gu.util.resthttp.JsonHttp.StringHttpRequest
import com.gu.util.resthttp.RestRequestMaker.{BodyAsString, toClientFailableOp}
import okhttp3.{HttpUrl, Request, Response}
import play.api.libs.json.{Json, Reads}

object IdentityClient {

def apply(
response: Request => Response,
config: IdentityConfig
): HttpOp[StringHttpRequest, BodyAsString] =
HttpOp(response).flatMap {
toClientFailableOp
}.setupRequest[StringHttpRequest] {
withAuth(config)
}

def withAuth(identityConfig: IdentityConfig)(requestInfo: StringHttpRequest): Request = {
val builder = requestInfo.requestMethod.builder
val authedBuilder = builder.addHeader("X-GU-ID-Client-Access-Token", s"Bearer ${identityConfig.apiToken}")
val url = requestInfo.urlParams.value.foldLeft(HttpUrl.parse(identityConfig.baseUrl + requestInfo.relativePath.value).newBuilder()) {
case (nextBuilder, (key, value)) => nextBuilder.addQueryParameter(key, value)
}.build()
authedBuilder.url(url).build()
}

}

case class IdentityConfig(
baseUrl: String,
apiToken: String
)

object IdentityConfig {
implicit val reads: Reads[IdentityConfig] = Json.reads[IdentityConfig]
implicit val location = ConfigLocation[IdentityConfig](path = "identity", version = 1)
}
Expand Up @@ -4,30 +4,32 @@ import java.io.{InputStream, OutputStream}

import com.amazonaws.services.lambda.runtime.Context
import com.gu.effects.{GetFromS3, RawEffects}
import com.gu.identity.{GetByEmail, IdentityConfig}
import com.gu.identity.{CreateGuestAccount, GetByEmail, IdentityClient, IdentityConfig}
import com.gu.identityBackfill.IdentityBackfillSteps.DomainRequest
import com.gu.identityBackfill.TypeConvert._
import com.gu.identityBackfill.Types.EmailAddress
import com.gu.identityBackfill.WireRequestToDomainObject.WireModel.IdentityBackfillRequest
import com.gu.identityBackfill.salesforce.ContactSyncCheck.RecordTypeId
import com.gu.identityBackfill.salesforce.UpdateSalesforceIdentityId.IdentityId
import com.gu.identityBackfill.salesforce._
import com.gu.identityBackfill.zuora.{AddIdentityIdToAccount, CountZuoraAccountsForIdentityId, GetZuoraAccountsForEmail, GetZuoraSubTypeForAccount}
import com.gu.salesforce.SalesforceAuthenticate.SFAuthConfig
import com.gu.salesforce.SalesforceClient.StringHttpRequest
import com.gu.salesforce.SalesforceClient
import com.gu.salesforce.TypesForSFEffectsData.SFContactId
import com.gu.salesforce.{JsonHttp, SalesforceClient}
import com.gu.util.apigateway.ApiGatewayHandler.{LambdaIO, Operation}
import com.gu.util.apigateway.ResponseModels.ApiResponse
import com.gu.util.apigateway.{ApiGatewayHandler, ApiGatewayResponse}
import com.gu.util.apigateway.{ApiGatewayHandler, ApiGatewayRequest, ApiGatewayResponse, ResponseModels}
import com.gu.util.config.LoadConfigModule.StringFromS3
import com.gu.util.config.{LoadConfigModule, Stage}
import com.gu.util.reader.Types.ApiGatewayOp.{ContinueProcessing, ReturnWithResponse}
import com.gu.util.reader.Types._
import com.gu.util.resthttp.JsonHttp.StringHttpRequest
import com.gu.util.resthttp.RestRequestMaker.{GetRequest, PatchRequest}
import com.gu.util.resthttp.Types.ClientFailableOp
import com.gu.util.resthttp.{HttpOp, LazyClientFailableOp, RestRequestMaker}
import com.gu.util.resthttp.{HttpOp, JsonHttp, LazyClientFailableOp, RestRequestMaker}
import com.gu.util.zuora.{ZuoraQuery, ZuoraRestConfig, ZuoraRestRequestMaker}
import okhttp3.{Request, Response}
import play.api.libs.json.JsValue
import scalaz.\/
import play.api.libs.json.{JsValue, Json, Reads}

object Handler {

Expand All @@ -53,25 +55,28 @@ object Handler {
) = {
val zuoraRequests = ZuoraRestRequestMaker(response, zuoraRestConfig)
val zuoraQuerier = ZuoraQuery(zuoraRequests)
val getByEmail: EmailAddress => GetByEmail.ApiError \/ IdentityId = GetByEmail(response, identityConfig)
val identityClient = IdentityClient(response, identityConfig)
val createGuestAccount = identityClient.wrapWith(JsonHttp.post).wrapWith(CreateGuestAccount.wrapper)
val getByEmail = identityClient.wrapWith(JsonHttp.getWithParams).wrapWith(GetByEmail.wrapper)
val countZuoraAccounts: IdentityId => ClientFailableOp[Int] = CountZuoraAccountsForIdentityId(zuoraQuerier)

lazy val sfAuth: LazyClientFailableOp[HttpOp[StringHttpRequest, RestRequestMaker.BodyAsString]] = SalesforceClient(response, sfConfig)
lazy val sfPatch = sfAuth.map(_.wrap(JsonHttp.patch))
lazy val sfGet = sfAuth.map(_.wrap(JsonHttp.get))
lazy val sfPatch = sfAuth.map(_.wrapWith(JsonHttp.patch))
lazy val sfGet = sfAuth.map(_.wrapWith(JsonHttp.get))

Operation(
steps = IdentityBackfillSteps(
steps = WireRequestToDomainObject(IdentityBackfillSteps(
PreReqCheck(
getByEmail,
getByEmail.runRequest,
GetZuoraAccountsForEmail(zuoraQuerier) _ andThen PreReqCheck.getSingleZuoraAccountForEmail,
countZuoraAccounts andThen PreReqCheck.noZuoraAccountsForIdentityId,
GetZuoraSubTypeForAccount(zuoraQuerier) _ andThen PreReqCheck.acceptableReaderType,
syncableSFToIdentity(sfGet, stage)
),
createGuestAccount.runRequest,
AddIdentityIdToAccount(zuoraRequests),
updateSalesforceIdentityId(sfPatch)
),
)),
healthcheck = () => Healthcheck(
getByEmail,
countZuoraAccounts,
Expand Down Expand Up @@ -129,15 +134,49 @@ object Handler {

object Healthcheck {
def apply(
getByEmail: EmailAddress => \/[GetByEmail.ApiError, IdentityId],
getByEmail: HttpOp[EmailAddress, GetByEmail.IdentityAccount],
countZuoraAccountsForIdentityId: IdentityId => ClientFailableOp[Int],
sfAuth: LazyClientFailableOp[Any]
): ApiResponse =
(for {
identityId <- getByEmail(EmailAddress("john.duffell@guardian.co.uk"))
maybeIdentityId <- getByEmail.runRequest(EmailAddress("john.duffell@guardian.co.uk"))
.toApiGatewayOp("problem with email").withLogging("healthcheck getByEmail")
identityId <- maybeIdentityId match {
case GetByEmail.IdentityAccountWithValidatedEmail(identityId) => ContinueProcessing(identityId)
case other =>
logger.error(s"failed healthcheck with $other")
ReturnWithResponse(ApiGatewayResponse.internalServerError("test identity id was not present"))
}
_ <- countZuoraAccountsForIdentityId(identityId).toApiGatewayOp("get zuora accounts for identity id")
_ <- sfAuth.value.toApiGatewayOp("Failed to authenticate with Salesforce")
} yield ApiGatewayResponse.successfulExecution).apiResponse

}

object WireRequestToDomainObject {

object WireModel {

case class IdentityBackfillRequest(
emailAddress: String,
dryRun: Boolean
)
implicit val identityBackfillRequest: Reads[IdentityBackfillRequest] = Json.reads[IdentityBackfillRequest]

}

def apply(
steps: DomainRequest => ResponseModels.ApiResponse
): ApiGatewayRequest => ResponseModels.ApiResponse = req =>
(for {
wireInput <- req.bodyAsCaseClass[IdentityBackfillRequest]()
mergeRequest = toDomainRequest(wireInput)
} yield steps(mergeRequest)).apiResponse

def toDomainRequest(request: IdentityBackfillRequest): DomainRequest =
DomainRequest(
EmailAddress(request.emailAddress),
request.dryRun
)

}

0 comments on commit 4142c93

Please sign in to comment.