Skip to content

Commit a6519eb

Browse files
committed
FIX #808 - use sql to get home API list, tags & categories
1 parent 06ebf56 commit a6519eb

File tree

11 files changed

+971
-132
lines changed

11 files changed

+971
-132
lines changed

daikoku/app/controllers/HomeController.scala

+23
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ class HomeController(
8181
assets.at("index.html").apply(ctx.request)
8282
}
8383

84+
def status() =
85+
DaikokuActionMaybeWithoutUser.async { ctx =>
86+
for {
87+
maybeTenant <- env.dataStore.tenantRepo.findById(ctx.tenant.id)
88+
} yield {
89+
val isReady = maybeTenant.isDefined
90+
val databaseStatus = maybeTenant.map(_ => "OK").getOrElse("KO")
91+
val responseJson = Json.obj(
92+
"status" -> (if (isReady) "ready" else "initializing"),
93+
"version" -> BuildInfo.version,
94+
"database" -> Json.obj(
95+
"status" -> databaseStatus,
96+
),
97+
"timestamp" -> java.time.Instant.now().toString
98+
)
99+
100+
if (isReady)
101+
Ok(responseJson)
102+
else
103+
ServiceUnavailable(responseJson)
104+
}
105+
}
106+
84107
def health() =
85108
DaikokuActionMaybeWithGuest { ctx =>
86109
ctx.request.headers.get("Otoroshi-Health-Check-Logic-Test") match {

daikoku/app/domain/CommonServices.scala

+89-106
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,14 @@ import cats.implicits.catsSyntaxOptionId
55
import controllers.AppError
66
import fr.maif.otoroshi.daikoku.actions.DaikokuActionContext
77
import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent
8-
import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{
9-
TeamAdminOnly,
10-
_PublicUserAccess,
11-
_TeamAdminOnly,
12-
_TeamApiEditorOnly,
13-
_TeamMemberOnly,
14-
_TenantAdminAccessTenant,
15-
_UberPublicUserAccess
16-
}
17-
import fr.maif.otoroshi.daikoku.domain.NotificationAction.{
18-
ApiAccess,
19-
ApiSubscriptionDemand
20-
}
8+
import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{TeamAdminOnly, _PublicUserAccess, _TeamAdminOnly, _TeamApiEditorOnly, _TeamMemberOnly, _TenantAdminAccessTenant, _UberPublicUserAccess}
9+
import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ApiAccess, ApiSubscriptionDemand}
2110
import fr.maif.otoroshi.daikoku.env.Env
2211
import fr.maif.otoroshi.daikoku.logger.AppLogger
2312
import org.joda.time.DateTime
2413
import play.api.libs.json._
14+
import storage.drivers.postgres.PostgresDataStore
15+
import storage.drivers.postgres.pgimplicits.EnhancedRow
2516

2617
import scala.concurrent.{ExecutionContext, Future}
2718

@@ -313,6 +304,34 @@ object CommonServices {
313304
}
314305
}
315306

307+
def allVisibleApisSqlQuery(tenant: Tenant) =
308+
s"""
309+
|SELECT content
310+
|FROM apis
311+
|WHERE (
312+
| content ->> '_tenant' = '${tenant.id.value}' AND
313+
| (content ->> 'state' = 'published' OR
314+
| $$1 OR
315+
| content ->> 'team' IN ($$2))
316+
| AND
317+
| (case
318+
| WHEN $$3 THEN content ->> 'visibility' = '${ApiVisibility.Public.name}'
319+
| WHEN $$1 THEN TRUE
320+
| ELSE (content ->> 'visibility' IN ('${ApiVisibility.Public.name}', '${ApiVisibility.PublicWithAuthorizations.name}') OR (content ->> 'team' in ($$2)) OR (content -> 'authorizedTeams' ?| ARRAY[$$2]))
321+
| END) AND
322+
| (content ->> 'name' ~* COALESCE(NULLIF($$4, ''), '.*')) AND
323+
| (_deleted = false) AND
324+
| (COALESCE($$5, '') = '' OR content ->> 'team' = $$5) AND
325+
| (COALESCE($$6, '') = '' OR content -> 'tags' ? $$6) AND
326+
| (COALESCE($$7, '') = '' OR content -> 'categories' ? $$7) AND
327+
| (COALESCE($$8, '') = '' OR (content ->> '_id' IN (SELECT jsonb_array_elements_text(content -> 'apis')
328+
| FROM apis
329+
| WHERE _id = $$8))) AND
330+
| (content ->> 'isDefault')::boolean
331+
|)
332+
|ORDER BY LOWER(content ->> 'name')
333+
|""".stripMargin
334+
316335
def getVisibleApis(
317336
teamId: Option[String] = None,
318337
research: String,
@@ -348,44 +367,9 @@ object CommonServices {
348367
"status.status" -> "Pending"
349368
)
350369
)
351-
allVisibleApisSqlQuery =
352-
s"""
353-
|SELECT content
354-
|FROM apis
355-
|WHERE (
356-
| content ->> '_tenant' = '${ctx.tenant.id.value}' AND
357-
| (content ->> 'state' = 'published' OR
358-
| $$1 OR
359-
| content ->> 'team' IN ($$2))
360-
| AND
361-
| (case
362-
| WHEN $$3 THEN content ->> 'visibility' = '${ApiVisibility.Public.name}'
363-
| WHEN $$1 THEN TRUE
364-
| ELSE (content ->> 'visibility' IN ('${ApiVisibility.Public.name}', '${ApiVisibility.PublicWithAuthorizations.name}') OR (content ->> 'team' in ($$2)) OR (content -> 'authorizedTeams' ?| ARRAY[$$2]))
365-
| END) AND
366-
| (content ->> 'name' ~* COALESCE(NULLIF($$4, ''), '.*')) AND
367-
| (_deleted = false) AND
368-
| (COALESCE($$5, '') = '' OR content ->> 'team' = $$5) AND
369-
| (COALESCE($$6, '') = '' OR content -> 'tags' ? $$6) AND
370-
| (COALESCE($$7, '') = '' OR content -> 'categories' ? $$7) AND
371-
| (COALESCE($$8, '') = '' OR (content ->> '_id' IN (SELECT jsonb_array_elements_text(content -> 'apis')
372-
| FROM apis
373-
| WHERE _id = $$8))) AND
374-
| (content ->> 'isDefault')::boolean
375-
|)
376-
|ORDER BY LOWER(content ->> 'name')
377-
|""".stripMargin
378-
log = AppLogger.info(s"${Seq(
379-
java.lang.Boolean.valueOf(user.isDaikokuAdmin),
380-
myTeams.map(_.id.value).mkString(","),
381-
java.lang.Boolean.valueOf(user.isGuest),
382-
research,
383-
selectedTeam.orNull,
384-
selectedTag.orNull,
385-
selectedCat.orNull,
386-
groupOpt.orNull)}")
370+
387371
paginateApis <- apiRepo.queryPaginated(
388-
allVisibleApisSqlQuery,
372+
allVisibleApisSqlQuery(ctx.tenant),
389373
Seq(
390374
java.lang.Boolean.valueOf(user.isDaikokuAdmin),
391375
myTeams.map(_.id.value).mkString(","),
@@ -400,7 +384,7 @@ object CommonServices {
400384
producerTeams <- env.dataStore.teamRepo.forTenant(ctx.tenant)
401385
.query(
402386
s"""
403-
|with visible_apis as ($allVisibleApisSqlQuery)
387+
|with visible_apis as (${allVisibleApisSqlQuery(ctx.tenant)})
404388
|
405389
|SELECT DISTINCT(teams.content) FROM visible_apis
406390
|LEFT JOIN teams on teams._id = visible_apis.content ->> 'team'
@@ -479,70 +463,69 @@ object CommonServices {
479463
}
480464
}
481465
}
482-
def getAllTags(research: String, groupOpt: Option[String])(implicit
466+
def getAllTags(research: String, groupOpt: Option[String], limit: Int, offset: Int)(implicit
483467
ctx: DaikokuActionContext[JsValue],
484468
env: Env,
485469
ec: ExecutionContext
486470
): Future[Seq[String]] = {
471+
AppLogger.info(s"get all tag with limit $limit and offset $offset (resaerch = $research)")
487472
for {
488-
visibleApis <- getVisibleApis(research = "", limit = -1, offset = 0, groupOpt = groupOpt)
489-
} yield {
490-
visibleApis
491-
.map(apis =>
492-
apis.apis
493-
.flatMap(api =>
494-
api.api.tags.toSeq.filter(tag => tag.indexOf(research) != -1)
495-
)
496-
.foldLeft(Map.empty[String, Int])((map, tag) => {
497-
val nbOfMatching = map.get(tag) match {
498-
case Some(count) => count + 1
499-
case None => 1
500-
}
501-
map + (tag -> nbOfMatching)
502-
503-
})
504-
.toSeq
505-
.sortBy(_._2)
506-
.reverse
507-
.map(a => a._1)
508-
.take(5)
509-
)
510-
.getOrElse(Seq.empty)
511-
}
473+
myTeams <- env.dataStore.teamRepo.myTeams(ctx.tenant, ctx.user)
474+
tags <- env.dataStore.asInstanceOf[PostgresDataStore]
475+
.queryString(
476+
s"""
477+
|with visible_apis as (${allVisibleApisSqlQuery(ctx.tenant)})
478+
|
479+
|SELECT tag
480+
|FROM (SELECT DISTINCT jsonb_array_elements_text(content -> 'tags') AS tag
481+
| FROM visible_apis)_
482+
|WHERE tag ~* COALESCE($$9, '')
483+
|ORDER BY LOWER(tag)
484+
|LIMIT $$10 OFFSET $$11;
485+
|""".stripMargin,
486+
"tag",
487+
Seq(
488+
java.lang.Boolean.valueOf(ctx.user.isDaikokuAdmin),
489+
myTeams.map(_.id.value).mkString(","),
490+
java.lang.Boolean.valueOf(ctx.user.isGuest), "",
491+
null, null, null,
492+
groupOpt.orNull,
493+
research,
494+
if(limit == -1) null else java.lang.Integer.valueOf(limit),
495+
if(offset == -1) null else java.lang.Integer.valueOf(offset)))
496+
} yield tags
512497
}
513498

514-
def getAllCategories(research: String, groupOpt: Option[String])(implicit
515-
ctx: DaikokuActionContext[JsValue],
516-
env: Env,
517-
ec: ExecutionContext
499+
def getAllCategories(research: String, groupOpt: Option[String], limit: Int, offset: Int)(implicit
500+
ctx: DaikokuActionContext[JsValue],
501+
env: Env,
502+
ec: ExecutionContext
518503
): Future[Seq[String]] = {
519504
for {
520-
visibleApis <- getVisibleApis(research = "", limit = -1, offset = 0, groupOpt = groupOpt)
521-
} yield {
522-
visibleApis
523-
.map(apis =>
524-
apis.apis
525-
.flatMap(api =>
526-
api.api.categories.toSeq.filter(tag =>
527-
tag.indexOf(research) != -1
528-
)
529-
)
530-
.foldLeft(Map.empty[String, Int])((map, cat) => {
531-
val nbOfMatching = map.get(cat) match {
532-
case Some(count) => count + 1
533-
case None => 1
534-
}
535-
map + (cat -> nbOfMatching)
536-
537-
})
538-
.toSeq
539-
.sortBy(_._2)
540-
.reverse
541-
.map(a => a._1)
542-
.take(5)
543-
)
544-
.getOrElse(Seq.empty)
545-
}
505+
myTeams <- env.dataStore.teamRepo.myTeams(ctx.tenant, ctx.user)
506+
tags <- env.dataStore.asInstanceOf[PostgresDataStore]
507+
.queryString(
508+
s"""
509+
|with visible_apis as (${allVisibleApisSqlQuery(ctx.tenant)})
510+
|
511+
|SELECT category
512+
|FROM (SELECT DISTINCT jsonb_array_elements_text(content -> 'categories') AS category
513+
| FROM visible_apis)_
514+
|WHERE category ~* COALESCE($$9, '')
515+
|ORDER BY LOWER(category)
516+
|LIMIT $$10 OFFSET $$11;
517+
|""".stripMargin,
518+
"category",
519+
Seq(
520+
java.lang.Boolean.valueOf(ctx.user.isDaikokuAdmin),
521+
myTeams.map(_.id.value).mkString(","),
522+
java.lang.Boolean.valueOf(ctx.user.isGuest), "",
523+
null, null, null,
524+
groupOpt.orNull,
525+
research,
526+
if(limit == -1) null else java.lang.Integer.valueOf(limit),
527+
if(offset== -1) null else java.lang.Integer.valueOf(offset)))
528+
} yield tags
546529
}
547530

548531
case class ApiWithTranslation(api: Api, translation: JsObject)

daikoku/app/domain/SchemaDefinition.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -4189,9 +4189,9 @@ object SchemaDefinition {
41894189
Field(
41904190
"allTags",
41914191
ListType(StringType),
4192-
arguments = RESEARCH :: GROUP_ID :: Nil,
4192+
arguments = RESEARCH :: GROUP_ID :: LIMIT :: OFFSET :: Nil,
41934193
resolve = ctx => {
4194-
CommonServices.getAllTags(ctx.arg(RESEARCH), ctx.arg(GROUP_ID))(ctx.ctx._2, env, e)
4194+
CommonServices.getAllTags(ctx.arg(RESEARCH), ctx.arg(GROUP_ID), ctx.arg(LIMIT), ctx.arg(OFFSET))(ctx.ctx._2, env, e)
41954195
}
41964196
)
41974197
)
@@ -4202,10 +4202,10 @@ object SchemaDefinition {
42024202
Field(
42034203
"allCategories",
42044204
ListType(StringType),
4205-
arguments = RESEARCH :: GROUP_ID :: Nil,
4205+
arguments = RESEARCH :: GROUP_ID :: LIMIT :: OFFSET :: Nil,
42064206
resolve = ctx => {
42074207
CommonServices
4208-
.getAllCategories(ctx.arg(RESEARCH), ctx.arg(GROUP_ID))(ctx.ctx._2, env, e)
4208+
.getAllCategories(ctx.arg(RESEARCH), ctx.arg(GROUP_ID), ctx.arg(LIMIT), ctx.arg(OFFSET))(ctx.ctx._2, env, e)
42094209
}
42104210
)
42114211
)

daikoku/app/storage/api.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.apache.pekko.util.ByteString
77
import cats.data.OptionT
88
import fr.maif.otoroshi.daikoku.domain._
99
import fr.maif.otoroshi.daikoku.env.Env
10+
import io.vertx.sqlclient.Row
1011
import play.api.libs.json._
1112
import services.CmsPage
1213

@@ -383,7 +384,6 @@ trait Repo[Of, Id <: ValueType] {
383384
def queryOne(query: String, params: Seq[AnyRef] = Seq.empty)(implicit ec: ExecutionContext): Future[Option[Of]]
384385
def query(query: String, params: Seq[AnyRef] = Seq.empty)(implicit ec: ExecutionContext): Future[Seq[Of]]
385386
def queryPaginated(query: String, params: Seq[AnyRef] = Seq.empty, offset: Int, limit: Int)(implicit ec: ExecutionContext): Future[(Seq[Of], Long)]
386-
387387
}
388388

389389
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -604,4 +604,10 @@ trait DataStore {
604604
def importFromStream(source: Source[ByteString, _]): Future[Unit]
605605

606606
def clear(): Future[Unit]
607+
608+
def queryOneRaw(query: String, name: String, params: Seq[AnyRef] = Seq.empty)(implicit ec: ExecutionContext): Future[Option[JsValue]]
609+
610+
def queryRaw(query: String, name: String, params: Seq[AnyRef] = Seq.empty)(implicit ec: ExecutionContext): Future[Seq[JsValue]]
611+
612+
def queryString(query: String, name: String, params: Seq[AnyRef] = Seq.empty)(implicit ec: ExecutionContext): Future[Seq[String]]
607613
}

0 commit comments

Comments
 (0)