Skip to content

Commit e1da485

Browse files
Better separation
1 parent d20bc0f commit e1da485

File tree

13 files changed

+379
-105
lines changed

13 files changed

+379
-105
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package controllers
2+
3+
import models.Api
4+
import old.play.Env
5+
import old.play.api.libs.ws._
6+
import play.api.libs.json.{JsObject, Json}
7+
import play.api.mvc.Controller
8+
import utils.AdminApiAction
9+
10+
import scala.concurrent.Future
11+
12+
object AdminApiController extends Controller {
13+
14+
def createApiFromSwaggerUrl(tenant: String) = AdminApiAction.async(parse.json) { ctx =>
15+
16+
implicit val ec = Env.httpCallsExecContext
17+
18+
(ctx.request.body \ "url").asOpt[String] match {
19+
case None => Future.successful(NotFound("Not Found"))
20+
case Some(url) => {
21+
WS.url(url).get().flatMap { resp =>
22+
val swagger = (resp.json.as[JsObject] \ "swagger").as[String]
23+
swagger match {
24+
case "2.0" => Api(ctx.tenant.id, resp.json).save().map { api =>
25+
Ok(Json.prettyPrint(api.json)).as("application/json")
26+
}
27+
case _ =>
28+
Future.successful(InternalServerError(s"Swagger version $swagger not supported"))
29+
}
30+
}
31+
}
32+
}
33+
}
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package controllers
2+
3+
import models.{Api, Tenant}
4+
import old.play.Env
5+
import play.api.mvc._
6+
import utils.DeveloperAction
7+
8+
import scala.concurrent.Future
9+
10+
object CoreApiController extends Controller {
11+
12+
def callSandboxApi(tenant: String, group: String, path: String) = DeveloperAction.async { ctx =>
13+
callApi(ctx.tenant, group, path, _.prodHosts.head, ctx.request)
14+
}
15+
16+
def callProdApi(tenant: String, group: String, path: String) = DeveloperAction.async { ctx =>
17+
callApi(ctx.tenant, group, path, _.prodHosts.head, ctx.request)
18+
}
19+
20+
private def callApi(tenant: Tenant, group: String, path: String, hostF: Api => String, req: Request[AnyContent]): Future[Result] = {
21+
implicit val ec = Env.dataStoreExecContext
22+
Api.findByTenantAndGroupAndPath(tenant.id, group, s"/$path").flatMap {
23+
case None => Future.successful(NotFound("API not found")) // TODO : based on accept type
24+
case Some(api) => api.call(group, path, hostF, req)(Env.httpCallsExecContext)
25+
}
26+
}
27+
}

app/controllers/HomeController.scala

Lines changed: 7 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,18 @@
11
package controllers
22

3-
import akka.stream.scaladsl.Source
4-
import akka.util.ByteString
5-
import models.{Api, Tenant}
6-
import old.play.api.libs.concurrent.Execution.Implicits._
7-
import old.play.api.libs.ws._
8-
import play.api.Logger
9-
import play.api.libs.json._
10-
import play.api.libs.ws.StreamedBody
113
import play.api.mvc._
12-
13-
import scala.concurrent.Future
14-
import scala.concurrent.duration.Duration
4+
import utils.PublicAction
155

166
object HomeController extends Controller {
177

18-
def index = Action.async {
19-
WS.url("http://freegeoip.net/json/").get.map { resp =>
20-
Ok(Json.prettyPrint(resp.json)).as("application/json")
21-
}
22-
}
23-
24-
def createApiFromSwaggerUrl(urlOpt: Option[String]) = Action.async { req =>
25-
urlOpt match {
26-
case None => Future.successful(NotFound("Not Found"))
27-
case Some(url) => {
28-
WS.url(url).get().map { resp =>
29-
val swagger = (resp.json.as[JsObject] \ "swagger").as[String]
30-
swagger match {
31-
case "2.0" =>
32-
val api = Api(Tenant.tenants.head.id, resp.json).save()
33-
Ok(Json.prettyPrint(resp.json)).as("application/json")
34-
case _ => InternalServerError(s"Swagger version $swagger not supported")
35-
}
36-
}
37-
}
38-
}
39-
}
40-
41-
def callSandboxApi(tenant: String, group: String, path: String) = Action.async { req =>
42-
callApi(tenant, group, path, _.prodHosts.head, req)
43-
}
44-
def callProdApi(tenant: String, group: String, path: String) = Action.async { req =>
45-
callApi(tenant, group, path, _.prodHosts.head, req)
46-
}
47-
48-
private def callApi(tenantRoot: String, group: String, path: String, hostF: Api => String, req: Request[AnyContent]): Future[Result] = {
49-
Tenant.findByRoot(tenantRoot).flatMap {
50-
case None => Future.successful(NotFound("Tenant not found")) // TODO : based on accept type
51-
case Some(tenant) => {
52-
Api.findByTenantAndGroupAndPath(tenant.id, group, "/" + path).flatMap {
53-
case None => Future.successful(NotFound("API not found")) // TODO : based on accept type
54-
case Some(api) => {
55-
val scheme = api.schemes.headOption.getOrElse("http")
56-
val host = hostF(api)
57-
val url = s"$scheme://$host:${api.port}/$path"
58-
val queryString = req.queryString.toSeq.flatMap { case (key, values) => values.map(v => (key, v)) }
59-
val headers = req.headers.toSimpleMap.+(("Host", s"$host:${api.port}")).toSeq
60-
val sbody = StreamedBody(req.body.asRaw.flatMap(_.asBytes()).map(Source.single).getOrElse(Source.empty[ByteString]))
61-
Logger.debug(s"curl -X ${req.method.toUpperCase()} ${headers.map(h => s"--H '${h._1}: ${h._2}'").mkString(" ")} '$url?${queryString.map(h => s"${h._1}=${h._2}").mkString("&")}'")
62-
WS.url(url)
63-
.withRequestTimeout(Duration("5s"))
64-
.withMethod(req.method)
65-
.withQueryString(queryString:_*)
66-
.withHeaders(headers:_*)
67-
.withBody(sbody)
68-
.stream()
69-
.map { resp =>
70-
val responseHeaders = resp.headers.headers.toSeq.flatMap { case (key, values) => values.map(v => (key, v))}
71-
Status(resp.headers.status)
72-
.chunked(resp.body)
73-
.withHeaders(responseHeaders:_*)
74-
} recover {
75-
case error => InternalServerError(s"Api Mngr error : ${error.getMessage}") // TODO : based on accept type
76-
}
77-
}
78-
}
79-
}
80-
}
8+
def index = PublicAction {
9+
// WS.url("http://freegeoip.net/json/").get.map { resp =>
10+
// Ok(Json.prettyPrint(resp.json)).as("application/json")
11+
// }
12+
Ok("Api Mngr")
8113
}
8214

83-
def notImplemented = Action {
15+
def notImplemented = PublicAction {
8416
NotImplemented("Not implemented yet ...")
8517
}
8618

app/models/Api.scala

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@ package models
22

33
import java.util.UUID
44

5-
import play.api.libs.json.{JsArray, JsObject, JsValue}
5+
import akka.stream.scaladsl.Source
6+
import akka.util.ByteString
7+
import controllers.HomeController._
8+
import old.play.api.libs.ws._
9+
import play.api.Logger
10+
import play.api.libs.json.{JsArray, JsObject, JsValue, Json}
11+
import play.api.libs.ws.StreamedBody
12+
import play.api.mvc.{AnyContent, Request, Result}
613

7-
import scala.concurrent.Future
14+
import scala.concurrent.{ExecutionContext, Future}
15+
import scala.concurrent.duration.Duration
816

917
case class Api(
1018
id: String,
@@ -20,11 +28,49 @@ case class Api(
2028
schemes: Seq[String],
2129
swagger: JsValue) {
2230

23-
// println(this.tenantId, this.group, this.root.toLowerCase)
31+
def save(): Future[Api] = Api.save(this)
2432

25-
def save(): Future[Unit] = {
26-
Api.apis = Api.apis :+ this
27-
Future.successful(())
33+
def json: JsValue = Json.obj(
34+
"id" -> this.id,
35+
"tenantId" -> this.tenantId,
36+
"name" -> this.name,
37+
"description" -> this.description,
38+
"version" -> this.version,
39+
"prodHosts" -> this.prodHosts,
40+
"sandboxHosts" -> this.sandboxHosts,
41+
"port" -> this.port,
42+
"group" -> this.group,
43+
"root" -> this.root,
44+
"schemes" -> this.schemes,
45+
"swagger" -> this.swagger
46+
)
47+
48+
def call(group: String, path: String, hostF: Api => String, req: Request[AnyContent])(implicit ec: ExecutionContext): Future[Result] = {
49+
val api = this
50+
val scheme = api.schemes.headOption.getOrElse("http")
51+
val host = hostF(api)
52+
val url = s"$scheme://$host:${api.port}/$path"
53+
val queryString = req.queryString.toSeq.flatMap { case (key, values) => values.map(v => (key, v)) }
54+
val headers = req.headers.toSimpleMap.+(("Host", s"$host:${api.port}")).toSeq
55+
val sbody = StreamedBody(req.body.asRaw.flatMap(_.asBytes()).map(Source.single).getOrElse(Source.empty[ByteString])) // Stream IN
56+
Logger.debug(s"curl -X ${req.method.toUpperCase()} ${headers.map(h => s"--H '${h._1}: ${h._2}'").mkString(" ")} '$url?${queryString.map(h => s"${h._1}=${h._2}").mkString("&")}'")
57+
WS.url(url)
58+
.withRequestTimeout(Duration("5s")) // TODO : from API config
59+
.withMethod(req.method)
60+
.withQueryString(queryString:_*)
61+
.withHeaders(headers:_*)
62+
.withBody(sbody)
63+
.stream()
64+
.map { resp =>
65+
val headers = resp.headers.headers.toSeq.flatMap { case (key, values) => values.map(v => (key, v))}.filter(_._1 != "Content-Type")
66+
val contentType = resp.headers.headers.get("Content-Type").flatMap(_.headOption).getOrElse("application/pouet") // TODO : or not
67+
Status(resp.headers.status)
68+
.chunked(resp.body) // Stream OUT
69+
.withHeaders(headers:_*)
70+
.as(contentType)
71+
} recover {
72+
case error => InternalServerError(s"Api Mngr error : ${error.getMessage}") // TODO : based on accept type
73+
}
2874
}
2975
}
3076

@@ -33,15 +79,16 @@ object Api {
3379
private var apis = Seq.empty[Api]
3480

3581
def findByTenantAndGroupAndPath(tenantId: String, group: String, path: String): Future[Option[Api]] = {
36-
37-
Future.successful(apis.find(api => {
38-
println(s"${api.tenantId} == ${tenantId}, ${api.group} == $group, ${path.toLowerCase()}.startsWith(${api.root.toLowerCase})")
39-
api.tenantId == tenantId && api.group == group && path.toLowerCase().startsWith(api.root.toLowerCase)
40-
}))
82+
Future.successful(apis.find(api => api.tenantId == tenantId && api.group == group && path.toLowerCase().startsWith(api.root.toLowerCase)))
4183
}
4284

4385
def findById(id: String): Future[Option[Api]] = Future.successful(apis.find(_.id == id))
4486

87+
def save(api: Api): Future[Api] = {
88+
Api.apis = Api.apis :+ api
89+
Future.successful(api)
90+
}
91+
4592
def apply(tenantId: String, blob: JsValue, group: String = "_"): Api = {
4693
val swagger = blob.as[JsObject]
4794
val basePath = (swagger \ "basePath").as[String]
@@ -58,7 +105,7 @@ object Api {
58105
version = version,
59106
prodHosts = Seq(host),
60107
sandboxHosts = Seq(host),
61-
port = 80,
108+
port = 80, // TODO : split port
62109
group = group,
63110
root = basePath,
64111
schemes = schemes,

app/utils/actions.scala

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package utils
2+
3+
import models.Tenant
4+
import play.api.mvc.{ActionBuilder, Request, Result, Results}
5+
6+
import scala.concurrent.Future
7+
8+
object PublicAction extends ActionBuilder[Request] {
9+
override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]): Future[Result] = {
10+
block(request)
11+
}
12+
}
13+
14+
case class AdminCallCtx[A]()
15+
16+
object AdminAction extends ActionBuilder[AdminCallCtx] {
17+
override def invokeBlock[A](request: Request[A], block: (AdminCallCtx[A]) => Future[Result]): Future[Result] = {
18+
// TODO : check if admin
19+
???
20+
}
21+
}
22+
23+
case class AdminApiCallCtx[A](tenant: Tenant, request: Request[A])
24+
25+
object AdminApiAction extends ActionBuilder[AdminApiCallCtx] {
26+
27+
implicit val ec = old.play.Env.httpRequestExecContext
28+
29+
override def invokeBlock[A](request: Request[A], block: (AdminApiCallCtx[A]) => Future[Result]): Future[Result] = {
30+
// TODO : check if admin
31+
val tenantRoot = request.path.tail.split("/")(0)
32+
Tenant.findByRoot(tenantRoot).flatMap {
33+
case None => Future.successful(Results.NotFound(s"Tenant $tenantRoot not found")) // TODO : based on accept type
34+
case Some(tenant) => block(AdminApiCallCtx(tenant, request))
35+
}
36+
}
37+
}
38+
39+
case class DeveloperCallCtx[A](tenant: Tenant, request: Request[A])
40+
41+
object DeveloperAction extends ActionBuilder[DeveloperCallCtx] {
42+
43+
implicit val ec = old.play.Env.httpRequestExecContext
44+
45+
override def invokeBlock[A](request: Request[A], block: (DeveloperCallCtx[A]) => Future[Result]): Future[Result] = {
46+
// TODO : check if dev
47+
val tenantRoot = request.path.tail.split("/")(0)
48+
Tenant.findByRoot(tenantRoot).flatMap {
49+
case None => Future.successful(Results.NotFound(s"Tenant $tenantRoot not found")) // TODO : based on accept type
50+
case Some(tenant) => block(DeveloperCallCtx(tenant, request))
51+
}
52+
}
53+
}

conf/routes

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@
22
# ~~~~
33

44
GET / controllers.HomeController.index
5-
GET /create controllers.HomeController.createApiFromSwaggerUrl(url: Option[String])
6-
GET /admin-ui controllers.HomeController.notImplemented
7-
GET /admin-api controllers.HomeController.notImplemented
5+
POST /:tenant/create controllers.AdminApiController.createApiFromSwaggerUrl(tenant)
6+
GET /:tenant/admin-ui controllers.HomeController.notImplemented1(tenant)
7+
GET /:tenant/admin-api controllers.HomeController.notImplemented1(tenant)
88

99
GET /:tenant/admin controllers.HomeController.notImplemented1(tenant)
1010
GET /:tenant/developers controllers.HomeController.notImplemented1(tenant)
1111

12-
GET /:tenant/sandbox/:group/*api controllers.HomeController.callSandboxApi(tenant, group, api)
13-
PUT /:tenant/sandbox/:group/*api controllers.HomeController.callSandboxApi(tenant, group, api)
14-
POST /:tenant/sandbox/:group/*api controllers.HomeController.callSandboxApi(tenant, group, api)
15-
PATCH /:tenant/sandbox/:group/*api controllers.HomeController.callSandboxApi(tenant, group, api)
16-
DELETE /:tenant/sandbox/:group/*api controllers.HomeController.callSandboxApi(tenant, group, api)
17-
OPTIONS /:tenant/sandbox/:group/*api controllers.HomeController.callSandboxApi(tenant, group, api)
18-
HEAD /:tenant/sandbox/:group/*api controllers.HomeController.callSandboxApi(tenant, group, api)
12+
GET /:tenant/sandbox/:group/*api controllers.CoreApiController.callSandboxApi(tenant, group, api)
13+
PUT /:tenant/sandbox/:group/*api controllers.CoreApiController.callSandboxApi(tenant, group, api)
14+
POST /:tenant/sandbox/:group/*api controllers.CoreApiController.callSandboxApi(tenant, group, api)
15+
PATCH /:tenant/sandbox/:group/*api controllers.CoreApiController.callSandboxApi(tenant, group, api)
16+
DELETE /:tenant/sandbox/:group/*api controllers.CoreApiController.callSandboxApi(tenant, group, api)
17+
OPTIONS /:tenant/sandbox/:group/*api controllers.CoreApiController.callSandboxApi(tenant, group, api)
18+
HEAD /:tenant/sandbox/:group/*api controllers.CoreApiController.callSandboxApi(tenant, group, api)
1919

2020

21-
GET /:tenant/:group/*api controllers.HomeController.callProdApi(tenant, group, api)
22-
PUT /:tenant/:group/*api controllers.HomeController.callProdApi(tenant, group, api)
23-
POST /:tenant/:group/*api controllers.HomeController.callProdApi(tenant, group, api)
24-
PATCH /:tenant/:group/*api controllers.HomeController.callProdApi(tenant, group, api)
25-
DELETE /:tenant/:group/*api controllers.HomeController.callProdApi(tenant, group, api)
26-
OPTIONS /:tenant/:group/*api controllers.HomeController.callProdApi(tenant, group, api)
27-
HEAD /:tenant/:group/*api controllers.HomeController.callProdApi(tenant, group, api)
21+
GET /:tenant/:group/*api controllers.CoreApiController.callProdApi(tenant, group, api)
22+
PUT /:tenant/:group/*api controllers.CoreApiController.callProdApi(tenant, group, api)
23+
POST /:tenant/:group/*api controllers.CoreApiController.callProdApi(tenant, group, api)
24+
PATCH /:tenant/:group/*api controllers.CoreApiController.callProdApi(tenant, group, api)
25+
DELETE /:tenant/:group/*api controllers.CoreApiController.callProdApi(tenant, group, api)
26+
OPTIONS /:tenant/:group/*api controllers.CoreApiController.callProdApi(tenant, group, api)
27+
HEAD /:tenant/:group/*api controllers.CoreApiController.callProdApi(tenant, group, api)
2828

2929
# Map static resources from the /public folder to the /assets URL path
3030
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)

javascript/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/node_modules

0 commit comments

Comments
 (0)