Skip to content

Commit

Permalink
Merge pull request #3 from aukgit/feature/play-mvc/2/run-failed-fix
Browse files Browse the repository at this point in the history
Rest Web Api - Partial SOLID implementation
  • Loading branch information
aukgit authored Apr 24, 2020
2 parents ff47cb6 + 2122ac3 commit df03923
Show file tree
Hide file tree
Showing 258 changed files with 6,417 additions and 1,167 deletions.
96 changes: 95 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,98 @@
- Sub Projects Example by Play : https://www.playframework.com/documentation/2.8.x/sbtSubProjects
- Sample Shared Project Example: https://bit.ly/2xzhCxz
- Play pot change : https://bit.ly/3clAq23 , sbt "run port"
- Organizing Build https://www.scala-sbt.org/0.13/docs/Organizing-Build.html
- Organizing Build https://www.scala-sbt.org/0.13/docs/Organizing-Build.html
- Learning Generics: https://gist.github.com/jkpl/5279ee05cca8cc1ec452fc26ace5b68b
- Reflection Programming : https://bit.ly/2XPJo3G
`a(any): A(generic) if classTag[A].runtimeClass.isInstance(a)`
- Class[T] : https://bit.ly/2VqpVF2
- Variance : https://medium.com/@wiemzin/variances-in-scala-9c7d17af9dc4
- Scala Generic with Shapeless: https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/
- Reflection : https://bit.ly/350JYx7 | https://bit.ly/3bwnjem | https://bit.ly/2VBq60l | https://bit.ly/2x4GHA8

## Logger References
- Example of logging : https://bit.ly/2RNFowy
- Slick logger disable : https://bit.ly/3erk9KZ
- Logger configuration examples : https://bit.ly/2XIj0bO
```scala

object Cmd extends CommandUtils {
def commands: Map[String, () => Command] = Map(
"archive" -> ArchiveCommand,
"convert-month" -> ConvertMonthCommand,
"convert-year" -> ConvertYearCommand,
"get" -> GetCommand.apply,
"info" -> InfoCommand,
"update" -> UpdateCommand
)

def run(args: Array[String], isDev: Boolean): Unit = {
if (args.length < 1) {
println("Usage: <command> [parameters...]")
println("Available commands: " + commands.keys.toVector.sorted.mkString(", "))
sys.exit(-1)
}

val cmdName = args(0)
val cmd: Command = commands.getOrElse(cmdName, exitError("Unknown command: " + cmdName))()

configureLogback(
if (isDev) "logback-dev.xml"
else if (cmd.isVerbose) "logback-prod-verbose.xml" else "logback-prod.xml")

val params: Array[String] = args.drop(1)
val log: Logger = LoggerFactory.getLogger("main")
log.info("Run: " + args.mkString(" "))
cmd.run(log, params)
}

private def configureLogback(configFilename: String): Unit = {
val configurator: JoranConfigurator = new JoranConfigurator
val ctx: LoggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
configurator.setContext(ctx)
ctx.reset()
configurator.doConfigure(getClass.getClassLoader.getResource(configFilename))
}

def main(args: Array[String]) {
run(args, isDev = false)
}
}

```


## Circle JSON Example
- https://www.programcreek.com/scala/io.circe.Encoder

```scala

import io.circe.{ Decoder, Encoder }
import shapeless.Unwrapped

trait AnyValCirceEncoding {
implicit def anyValEncoder[V, U](implicit ev: V <:< AnyVal,
V: Unwrapped.Aux[V, U],
encoder: Encoder[U]): Encoder[V] = {
val _ = ev
encoder.contramap(V.unwrap)
}

implicit def anyValDecoder[V, U](implicit ev: V <:< AnyVal,
V: Unwrapped.Aux[V, U],
decoder: Decoder[U]): Decoder[V] = {
val _ = ev
decoder.map(V.wrap)
}
}

object AnyValCirceEncoding extends AnyValCirceEncoding

object CirceSupport
extends de.heikoseeberger.akkahttpcirce.FailFastCirceSupport
with AnyValCirceEncoding
```


# Important Issue Tracking
- https://github.com/circe/circe/issues/1442
22 changes: 21 additions & 1 deletion app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package controllers

import java.io.File

import javax.inject.Inject
import play.api.libs.json.Json
import play.api.mvc._
Expand All @@ -9,6 +11,10 @@ import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
import play.api.{Environment, Play}
import shared.com.ortb.model.wrappers.http.HttpSuccessActionWrapper
import shared.io.helpers.PathHelper
import shared.io.loggers.AppLogger

class ApiController @Inject()(

Expand All @@ -25,9 +31,23 @@ class ApiController @Inject()(
def campaigns : Action[AnyContent] = Action { implicit request =>
val appManager = new AppManager
val repositories = new Repositories(appManager)
val campaigns = repositories.campaignRepository.getAll.toArray
val campaigns = repositories.campaignRepository.getAllAsList.toArray
AppLogger.logEntitiesNonFuture(isExecute = true, campaigns)
val json = campaigns.asJson.spaces2
println(json)
Ok(json)
}

def resourcePath : Action[AnyContent] = Action { implicit request =>
val path = PathHelper.getResourcePath
val rootPath = Environment.simple().resource("")
val js = Json.obj(
("path", path),
("Environment.simple().resource(\"\")", rootPath.toString),
)

println(js)
AppLogger.debug("Hello Debu WWW g")
Ok(js)
}
}
17 changes: 17 additions & 0 deletions app/controllers/apis/CampaignsApiController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package controllers.apis

import controllers.webapi.core.AbstractRestWebApi
import javax.inject.Inject
import play.api.mvc._
import services.CampaignService
import services.core.AbstractBasicPersistentService
import shared.com.ortb.persistent.schema.Tables._

class CampaignsApiController @Inject()(
campaignService : CampaignService,
components : ControllerComponents)
extends AbstractRestWebApi[Campaign, CampaignRow, Int](components) {

override val service : AbstractBasicPersistentService[Campaign, CampaignRow, Int] =
campaignService
}
118 changes: 118 additions & 0 deletions app/controllers/controllerRoutes/AbstractGenericRouter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package controllers.controllerRoutes

import java.security.spec.InvalidParameterSpecException

import controllers.controllerRoutes.traits.RouterActionPerformByIds
import controllers.webapi.core.AbstractRestWebApi
import play.api.mvc.{ Action, AnyContent }
import play.api.routing.Router.Routes
import play.api.routing.SimpleRouter
import play.api.routing.sird._
import shared.com.ortb.enumeration.ControllerDefaultActionType
import shared.com.ortb.model.results.ResultWithBooleanModel
import shared.com.ortb.model.wrappers.http.ControllerGenericActionWrapper
import shared.io.helpers.NumberHelper

import scala.reflect.runtime.universe._

abstract class AbstractGenericRouter[TTable, TRow, TKey : TypeTag](
controller : AbstractRestWebApi[TTable, TRow, TKey])
extends SimpleRouter
with RouterActionPerformByIds {

val routingActionWrapper : ControllerGenericActionWrapper = ControllerGenericActionWrapper(
ControllerDefaultActionType.Routing)

override def routes : Routes = {
try {
case GET(p"/") =>
controller.getAll()

case POST(p"/create") =>
controller.add()

case POST(p"/createMany") =>
controller.addEntities()

case POST(p"/createMany/$addTimes") =>
performAddTimes(addTimes)

case GET(p"/$id") =>
performGetById(id)

case PUT(p"/$id") =>
performUpdateById(id)

} catch {
case e : Exception =>
controller.handleError(e, routingActionWrapper)
throw e
}
}

private def performAddTimes(addTimes : String) : Action[AnyContent] = {
val addTimesAsInt = NumberHelper.isInt(addTimes)
if (addTimesAsInt.isSuccess) {
controller.addEntitiesBySinge(addTimesAsInt.result.get)
}
else {
performIntegerConversionFailedAction(addTimesAsInt)
}
}

protected def performIntegerConversionFailedAction(
stringToInt : ResultWithBooleanModel[Int]) : Action[AnyContent] = {
val httpFailedActionWrapper = controller.createHttpFailedActionWrapper(
s"Couldn't convert [$stringToInt] to integer.",
actionWrapper = routingActionWrapper
)
controller.performBadResponseAsAction(
Some(httpFailedActionWrapper))
}

protected def performGetByIdAsInteger(id : String) : Action[AnyContent] = {
val idAsInt = NumberHelper.isInt(id)

if (idAsInt.isSuccess) {
controller.byId(idAsInt.result.get.asInstanceOf[TKey])
}
else {
performIntegerConversionFailedAction(idAsInt)
}
}

protected def performUpdateByIdAsInteger(id : String) : Action[AnyContent] = {
val idAsInt = NumberHelper.isInt(id)
// TODO make it more DRY
if (idAsInt.isSuccess) {
controller.update(idAsInt.result.get.asInstanceOf[TKey])
}
else {
performIntegerConversionFailedAction(idAsInt)
}
}

override def performUpdateById(
id : String) : Action[AnyContent] = {
val typeOfKey = typeOf[TKey]
typeOfKey match {
case i if i =:= typeOf[Int] =>
performUpdateByIdAsInteger(id)
case i if i =:= typeOf[String] =>
controller.update(id.asInstanceOf[TKey])
case _ => throw new InvalidParameterSpecException("Id is not type of Integer or String")
}
}

override def performGetById(
id : String) : Action[AnyContent] = {
val typeOfKey = typeOf[TKey]
typeOfKey match {
case i if i =:= typeOf[Int] =>
performGetByIdAsInteger(id)
case i if i =:= typeOf[String] =>
controller.byId(id.asInstanceOf[TKey])
case _ => throw new InvalidParameterSpecException("Id is not type of Integer or String")
}
}
}
9 changes: 9 additions & 0 deletions app/controllers/controllerRoutes/CampaignApiRouter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package controllers.controllerRoutes

import controllers.apis.CampaignsApiController
import javax.inject.Inject
import shared.com.ortb.persistent.schema.Tables._

class CampaignApiRouter @Inject()(
campaignsApiController : CampaignsApiController) extends
AbstractGenericRouter[Campaign, CampaignRow, Int](campaignsApiController)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package controllers.controllerRoutes.traits

import play.api.mvc.{ Action, AnyContent }

trait RouterActionPerformByIds {
def performGetById(id : String) : Action[AnyContent]

def performUpdateById(id : String) : Action[AnyContent]
}
44 changes: 44 additions & 0 deletions app/controllers/webapi/core/AbstractRestWebApi.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package controllers.webapi.core

import controllers.webapi.core.traits._
import controllers.webapi.core.traits.implementations._
import controllers.webapi.core.traits.implementations.actions._
import play.api.mvc._
import shared.com.ortb.model.wrappers.http._
import shared.io.loggers.AppLogger

abstract class AbstractRestWebApi[TTable, TRow, TKey](
val components : ControllerComponents)
extends AbstractController(components)
with RestWebApiContracts[TTable, TRow, TKey]
with RestWebApiHandleErrorImplementation[TTable, TRow, TKey]
with RestWebApiBodyProcessorImplementation[TTable, TRow, TKey]
with RestWebApiResponsePerformImplementation[TTable, TRow, TKey]
with RestWebApiAddActionImplementation[TTable, TRow, TKey]
with RestWebApiAddOrUpdateActionImplementation[TTable, TRow, TKey]
with RestWebApiDeleteActionImplementation[TTable, TRow, TKey]
with RestWebApiUpdateActionImplementation[TTable, TRow, TKey]
with RestWebApiAddEntitiesActionImplementation[TTable, TRow, TKey]
with RestWebApiGetByIdActionImplementation[TTable, TRow, TKey]
with RestWebApiGetAllActionImplementation[TTable, TRow, TKey]
with RestWebApiMessagesImplementation[TTable, TRow, TKey]
with RestWebApiPropertiesImplementation[TTable, TRow, TKey] {

def getRequestUri(request : Request[AnyContent]) : String = {
request.uri
}

def createHttpFailedActionWrapper(
message : String,
actionWrapper : ControllerGenericActionWrapper,
methodName : String = ""
) : HttpFailedActionWrapper[TRow, TKey] = {
AppLogger.error(s"${ message } $methodName")
val httpFailedActionWrapper = HttpFailedActionWrapper[TRow, TKey](
additionalMessage = Some(message),
methodName = Some(methodName),
controllerGenericActionWrapper = actionWrapper)

httpFailedActionWrapper
}
}
Loading

0 comments on commit df03923

Please sign in to comment.