Skip to content

Commit

Permalink
feat: smithy4s
Browse files Browse the repository at this point in the history
  • Loading branch information
anderha committed Aug 25, 2022
1 parent db2517b commit 9c5afaa
Show file tree
Hide file tree
Showing 40 changed files with 767 additions and 383 deletions.
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,11 @@ out/doc
.DS_Store

.bin/postgis-volume
.bloop/
.bloop/

smithy-definition/output

app/smithy4s/*
app/smithy4s
app/de/innfactory/bootstrapplay2/definition
app/de/innfactory/bootstrapplay2/definition/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package de.innfactory.bootstrapplay2.application.controller

import cats.data.{EitherT, Kleisli}
import de.innfactory.bootstrapplay2.commons.{RequestContext, RequestContextWithUser}
import de.innfactory.bootstrapplay2.commons.application.actions.utils.UserUtils
import de.innfactory.bootstrapplay2.users.domain.interfaces.UserService
import de.innfactory.bootstrapplay2.users.domain.models.UserId
import de.innfactory.play.controller.{ErrorResult, ResultStatus}
import de.innfactory.smithy4play.{ContextRouteError, RoutingContext}
import play.api.Application
import de.innfactory.play.smithy4play.{AbstractBaseController, HttpHeaders, ImplicitLogContext}

import scala.concurrent.ExecutionContext

class BaseController(implicit ec: ExecutionContext, app: Application)
extends AbstractBaseController[ResultStatus, RequestContextWithUser, RequestContext]
with ImplicitLogContext
with BaseMapper {

private val userUtils = app.injector.instanceOf[UserUtils]
private val userService = app.injector.instanceOf[UserService]

override def AuthAction: Kleisli[ApplicationRouteResult, RequestContext, RequestContextWithUser] = Kleisli {
context =>
val result = for {
_ <- EitherT(userUtils.validateJwtToken(context.httpHeaders.authAsJwt))
userId <- EitherT(userUtils.extractUserId(context.httpHeaders.authAsJwt))
user <- userService.getUserByIdWithoutRequestContext(UserId(userId))
} yield RequestContextWithUser(context.httpHeaders, context.span, user)
result
}

override def errorHandler(e: ResultStatus): ContextRouteError =
e match {
case result: ErrorResult =>
new ContextRouteError {
override def message: String = result.message
override def additionalInfoToLog: Option[String] = result.additionalInfoToLog
override def additionalInfoErrorCode: Option[String] = result.additionalInfoErrorCode
override def statusCode: Int = result.statusCode
}
}

override def createRequestContextFromRoutingContext(r: RoutingContext): RequestContext =
new RequestContext(HttpHeaders(r.map))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package de.innfactory.bootstrapplay2.application.controller

import de.innfactory.play.smithy4play.PlayJsonToDocumentMapper
import io.scalaland.chimney.Transformer
import org.joda.time.DateTime
import play.api.libs.json.JsValue
import smithy4s.Document
import de.innfactory.bootstrapplay2.definition.DateWithTime

import scala.language.implicitConversions

trait BaseMapper {

implicit val transformJsValueToDocument: Transformer[JsValue, Document] = PlayJsonToDocumentMapper.mapToDocument
implicit val transformDocumentToJsValue: Transformer[Document, JsValue] = PlayJsonToDocumentMapper.documentToJsValue

implicit def unitMapper[T](any: T): Unit = ()

implicit def dateWithTimeToDateTime(dateWithTime: DateWithTime): DateTime =
DateTime.parse(dateWithTime.value)

implicit def dateTimeToDateWithTime(dateTime: DateTime): DateWithTime =
DateWithTime(dateTime.toString())

implicit def sequenceTransformer[T, R](seq: Seq[T])(implicit transform: T => R): List[R] =
seq.map(transform).toList

implicit val dateWithTimeToDateTimeTransformer: Transformer[DateWithTime, DateTime] =
(dateWithTime: DateWithTime) => dateWithTimeToDateTime(dateWithTime)

implicit val dateTimeToDateWithTimeTransformer: Transformer[DateTime, DateWithTime] =
(dateTime: DateTime) => dateTimeToDateWithTime(dateTime)
}
65 changes: 26 additions & 39 deletions app/de/innfactory/bootstrapplay2/commons/RequestContext.scala
Original file line number Diff line number Diff line change
@@ -1,56 +1,43 @@
package de.innfactory.bootstrapplay2.commons

import de.innfactory.bootstrapplay2.commons.application.actions.models.RequestWithUser
import de.innfactory.bootstrapplay2.commons.logging.TraceLogger
import de.innfactory.bootstrapplay2.users.domain.models.User
import de.innfactory.play.tracing.TraceRequest
import cats.implicits.toBifunctorOps
import de.innfactory.bootstrapplay2.users.domain.models.{User, UserId}
import de.innfactory.play.smithy4play.{HttpHeaders, TraceContext}
import io.opencensus.trace.Span
import org.joda.time.DateTime

import scala.util.Try

abstract class TraceContext {

def span: Span

private val traceLogger = new TraceLogger(span)

final def log: TraceLogger = traceLogger
trait ApplicationTraceContext extends TraceContext {

def timeOverride: Option[DateTime] = Try(
httpHeaders.getHeader("x-app-datetime").map(DateTime.parse)
).toEither
.leftMap(left => this.log.error("cannot parse x-app-time, because of error " + left.getMessage))
.toOption
.flatten
}

trait RequestContextUser[USER] {
def user: USER
class RequestContext(
rhttpHeaders: HttpHeaders,
rSpan: Option[Span] = None
) extends ApplicationTraceContext {
override def httpHeaders: HttpHeaders = rhttpHeaders
override def span: Option[Span] = rSpan
}

class RequestContext(rcSpan: Span, rcHeaders: Map[String, Seq[String]]) extends TraceContext {
def headers: Map[String, Seq[String]] = rcHeaders
override def span: Span = rcSpan
object RequestContext {
implicit def fromRequestContextWithUser(requestContextWithUser: RequestContextWithUser): RequestContext =
new RequestContext(requestContextWithUser.httpHeaders, requestContextWithUser.span)

def empty = new RequestContext(HttpHeaders(Map.empty))
}

case class RequestContextWithUser(
override val span: Span,
override val headers: Map[String, Seq[String]],
override val httpHeaders: HttpHeaders,
override val span: Option[Span],
user: User
) extends RequestContext(span, headers)
with RequestContextUser[User] {}
) extends RequestContext(httpHeaders, span)

object RequestContextWithUser {
implicit def toRequestContext(requestContextWithUser: RequestContextWithUser): RequestContext =
new RequestContext(requestContextWithUser.span, requestContextWithUser.headers)
}

object ReqConverterHelper {

def requestContext(implicit req: TraceRequest[_]): RequestContext = {
val headers = Try(req.request.headers.toMap).toOption.getOrElse(Map.empty)
new RequestContext(req.traceSpan, headers)
}

def requestContextWithUser[Q](implicit
req: RequestWithUser[Q]
): RequestContextWithUser = {
val headers = Try(req.request.headers.toMap).toOption.getOrElse(Map.empty)
RequestContextWithUser(req.traceSpan, headers, req.user)
}

implicit def toUserId(requestContextWithUser: RequestContextWithUser): UserId = requestContextWithUser.user.userId
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package de.innfactory.bootstrapplay2.commons.application.actions

import de.innfactory.bootstrapplay2.commons.application.actions.models.RequestWithTrace
import de.innfactory.bootstrapplay2.commons.logging.LogContext
import de.innfactory.bootstrapplay2.commons.tracing.Common._
import de.innfactory.play.smithy4play.LogContext
import de.innfactory.play.tracing.TraceRequest
import io.opencensus.scala.Tracing.{startSpan, startSpanWithRemoteParent, traceWithParent}
import io.opencensus.trace._
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package de.innfactory.bootstrapplay2.commons.application.actions

import com.google.inject.Inject
import de.innfactory.bootstrapplay2.commons.logging.LogContext
import de.innfactory.bootstrapplay2.commons.application.actions.models.RequestWithUser
import de.innfactory.play.smithy4play.LogContext
import de.innfactory.play.tracing.TracingAction
import play.api.Environment
import play.api.mvc._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import com.google.inject.Inject
import de.innfactory.bootstrapplay2.commons.results.Results
import de.innfactory.bootstrapplay2.commons.application.actions.models.RequestWithUser
import de.innfactory.bootstrapplay2.commons.application.actions.utils.UserUtils
import de.innfactory.bootstrapplay2.commons.jwt.JWTToken
import de.innfactory.bootstrapplay2.commons.implicits.EitherTF
import de.innfactory.bootstrapplay2.users.domain.interfaces.UserService
import de.innfactory.bootstrapplay2.users.domain.models.{User, UserId}
import de.innfactory.play.controller.ErrorResponse
import de.innfactory.play.smithy4play.JWTToken
import de.innfactory.play.tracing.{RequestWithTrace, UserExtractionActionBase}
import play.api.Environment
import play.api.mvc.Results.{FailedDependency, NotFound, Unauthorized}
Expand Down Expand Up @@ -43,29 +44,34 @@ private[actions] class UserExtractionAction @Inject() (
request: RequestWithTrace[A]
): EitherT[Future, Result, RequestWithUser[A]] =
for {
userRecord <- EitherT(getUser(id))
result <- EitherT(Future(validateUser(userRecord, request)))
userRecord <- getUser(id)
result <- validateUser(userRecord, request)
} yield result

private def getUser[A](id: String): Future[Either[Result, User]] =
private def getUser[A](id: String): EitherT[Future, Result, User] =
userService
.getUserByIdWithoutRequestContext(UserId(id))
.map(_.leftMap[Result](_ => NotFound(ErrorResponse.fromMessage("User not Found"))))
.leftMap[Result](_ => NotFound(ErrorResponse.fromMessage("User not Found")))

private def validateUser[A](userRecord: User, request: RequestWithTrace[A]): Either[Result, RequestWithUser[A]] =
Validated
.cond[Result, RequestWithUser[A]](
userRecord.emailVerified,
new RequestWithUser[A](
userRecord,
request,
request.traceSpan
),
FailedDependency(
ErrorResponse.fromMessage("Email not verified")
private def validateUser[A](
userRecord: User,
request: RequestWithTrace[A]
): EitherT[Future, Result, RequestWithUser[A]] =
EitherTF(
Validated
.cond[Result, RequestWithUser[A]](
userRecord.emailVerified,
new RequestWithUser[A](
userRecord,
request,
request.traceSpan
),
FailedDependency(
ErrorResponse.fromMessage("Email not verified")
)
)
)
.toEither
.toEither
)

private def extractUserIdFromRequest[A](request: Request[A]): Future[Results.Result[String]] = {
val token = request.headers.get("Authorization").map(JWTToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package de.innfactory.bootstrapplay2.commons.application.actions.utils

import com.google.inject.ImplementedBy
import de.innfactory.bootstrapplay2.commons.jwt.JWTToken
import de.innfactory.bootstrapplay2.commons.results.Results.Result
import de.innfactory.play.smithy4play.JWTToken

import scala.concurrent.Future

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package de.innfactory.bootstrapplay2.commons.application.actions.utils

import cats.implicits.catsSyntaxEitherId
import de.innfactory.bootstrapplay2.commons.jwt.JWTToken
import de.innfactory.bootstrapplay2.commons.results.Results.Result
import de.innfactory.bootstrapplay2.commons.results.errors.Errors.Forbidden
import de.innfactory.play.controller.ResultStatus
import de.innfactory.play.smithy4play.JWTToken
import play.libs.Json

import java.util.Base64
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package de.innfactory.bootstrapplay2.commons.implicits

import cats.data.EitherT
import cats.implicits.catsSyntaxEitherId
import de.innfactory.play.controller.ResultStatus
import de.innfactory.bootstrapplay2.commons.{RequestContext, TraceContext}
import de.innfactory.play.smithy4play.TraceContext
import io.opencensus.scala.Tracing.traceWithParent
import io.opencensus.trace.Span

import scala.concurrent.{ExecutionContext, Future}

Expand All @@ -15,16 +13,12 @@ object EitherTTracingImplicits {
def trace[A](
string: String
)(implicit rc: TraceContext, ec: ExecutionContext): EitherT[Future, ResultStatus, T] =
EitherT(traceWithParent(string, rc.span) { span =>
eitherT.value
})
rc.span
.map(span =>
EitherT(traceWithParent(string, span) { _ =>
eitherT.value
})
)
.getOrElse(eitherT)
}

def TracedT[A](
string: String
)(implicit rc: TraceContext, ec: ExecutionContext): EitherT[Future, ResultStatus, Span] =
EitherT(traceWithParent(string, rc.span) { span =>
Future(span.asRight[ResultStatus])
})

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package de.innfactory.bootstrapplay2.commons.implicits

import cats.data.EitherT
import cats.implicits.catsSyntaxEitherId
import de.innfactory.play.controller.ResultStatus
import de.innfactory.bootstrapplay2.commons.TraceContext
import de.innfactory.play.smithy4play.TraceContext
import io.opencensus.scala.Tracing.traceWithParent
import io.opencensus.trace.Span

import scala.concurrent.{ExecutionContext, Future}

Expand All @@ -15,16 +11,12 @@ object FutureTracingImplicits {
def trace(
string: String
)(implicit tc: TraceContext, ec: ExecutionContext): Future[T] =
traceWithParent(string, tc.span) { _ =>
future
}
tc.span
.map(span =>
traceWithParent(string, span) { _ =>
future
}
)
.getOrElse(future)
}

def TracedT[A](
string: String
)(implicit tc: TraceContext, ec: ExecutionContext): EitherT[Future, ResultStatus, Span] =
EitherT(traceWithParent(string, tc.span) { span =>
Future(span.asRight[ResultStatus])
})

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package de.innfactory.bootstrapplay2.commons.implicits

import de.innfactory.bootstrapplay2.commons.RequestContext

import de.innfactory.play.smithy4play.HttpHeaders
import io.opencensus.scala.Tracing.{startSpanWithRemoteParent, traceWithParent}
import io.opencensus.trace.{SpanContext, SpanId, TraceId, TraceOptions, Tracestate}
import play.api.mvc.{AnyContent, Request}
Expand Down Expand Up @@ -30,7 +30,7 @@ object RequestToRequestContextImplicit {
)

traceWithParent(spanString, span) { spanChild =>
val rc = new RequestContext(spanChild, request.headers.toMap)
val rc = new RequestContext(HttpHeaders(request.headers.toMap), Some(spanChild))
val result = f(rc)
result.map { r =>
spanChild.end()
Expand Down
Loading

0 comments on commit 9c5afaa

Please sign in to comment.