Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
quentinovega committed Oct 26, 2023
1 parent a7922d9 commit 333257e
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 7 deletions.
31 changes: 31 additions & 0 deletions daikoku/app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import akka.http.scaladsl.util.FastFuture
import akka.stream.Materializer
import akka.stream.scaladsl.{Flow, JsonFraming, Sink, Source}
import akka.util.ByteString
import cats.Id
import cats.data.EitherT
import cats.implicits.{catsSyntaxOptionId, toTraverseOps}
import controllers.AppError
import controllers.AppError._
import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionContext, DaikokuActionMaybeWithGuest}
import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent
import fr.maif.otoroshi.daikoku.audit.config.ElasticAnalyticsConfig
import fr.maif.otoroshi.daikoku.ctrls.authorizations.async._
import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ApiAccess, ApiSubscriptionDemand}
import fr.maif.otoroshi.daikoku.domain.UsagePlanVisibility.Private
Expand Down Expand Up @@ -4252,4 +4254,33 @@ class ApiController(
value.merge
}
}

def getApiSubscriptionsUsage(teamId: String) =
DaikokuAction.async(parse.json) { ctx =>
TeamAdminOnly(AuditTrailEvent(s"@{user.name} has accessed to subscription usage for his team @{team.id}"))(teamId, ctx) { team =>

val subsIds = (ctx.request.body \ "subscriptions").as[JsArray]

for {
subscriptions <- env.dataStore.apiSubscriptionRepo.forTenant(ctx.tenant).find(Json.obj("_id" -> Json.obj("$in" -> subsIds)))
planIds = subscriptions.map(_.plan.asJson).distinct
plans <- env.dataStore.usagePlanRepo.forTenant(ctx.tenant).find(Json.obj("_id" -> Json.obj("$in" -> JsArray(planIds))))
test = subscriptions.groupBy(sub => sub.plan).toSeq
r <- Future.sequence(test.map { case (planId, subs) => getOtoroshiUsage(subs, plans.find(_.id == planId))(ctx.tenant)})
} yield Ok(JsArray(r.flatMap(_.value)))

}
}

private def getOtoroshiUsage(subscriptions: Seq[ApiSubscription], plan: Option[UsagePlan])(implicit tenant: Tenant): Future[JsArray] = {

val value1: EitherT[Future, JsArray, JsArray] = plan match {
case Some(value) => for {
otoroshi <- EitherT.fromOption[Future](tenant.otoroshiSettings.find(oto => value.otoroshiTarget.exists(_.otoroshiSettings == oto.id)), Json.arr())
usages <- otoroshiClient.getSubscriptionLastUsage(subscriptions)(otoroshi, tenant)
} yield usages
case None => EitherT.pure[Future, JsArray](Json.arr())
}
value1.merge
}
}
3 changes: 2 additions & 1 deletion daikoku/app/domain/SchemaDefinition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ object SchemaDefinition {

lazy val OtoroshiSettingsType = deriveObjectType[(DataStore, DaikokuActionContext[JsValue]), OtoroshiSettings](
ObjectTypeDescription("Settings to communicate with an instance of Otoroshi"),
ReplaceField("id", Field("id", StringType, resolve = _.value.id.value))
ReplaceField("id", Field("id", StringType, resolve = _.value.id.value)),
ReplaceField("elasticConfig", Field("elasticConfig", OptionType(ElasticAnalyticsConfigType), resolve = _.value.elasticConfig))
)
lazy val MailerSettingsType: InterfaceType[(DataStore, DaikokuActionContext[JsValue]), MailerSettings] = InterfaceType(
"MailerSettings",
Expand Down
10 changes: 7 additions & 3 deletions daikoku/app/domain/json.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ object json {
.getOrElse("admin-api-apikey-id"),
clientSecret = (json \ "clientSecret")
.asOpt[String]
.getOrElse("admin-api-apikey-sectet")
.getOrElse("admin-api-apikey-secret"),
elasticConfig = (json \ "elasticConfig").asOpt(ElasticAnalyticsConfig.format)
)
)
} recover {
Expand All @@ -120,7 +121,8 @@ object json {
"url" -> o.url,
"host" -> o.host,
"clientId" -> o.clientId,
"clientSecret" -> o.clientSecret
"clientSecret" -> o.clientSecret,
"elasticConfig" -> o.elasticConfig.map(ElasticAnalyticsConfig.format.writes).getOrElse(JsNull).as[JsValue]
)
}

Expand Down Expand Up @@ -480,7 +482,9 @@ object json {
Try {
JsSuccess(OtoroshiSettingsId(json.as[String]))
} recover {
case e => JsError(e.getMessage)
case e =>
AppLogger.error(e.getMessage, e)
JsError(e.getMessage)
} get

override def writes(o: OtoroshiSettingsId): JsValue = JsString(o.value)
Expand Down
3 changes: 2 additions & 1 deletion daikoku/app/domain/tenantEntities.scala
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@ case class OtoroshiSettings(id: OtoroshiSettingsId,
url: String,
host: String,
clientId: String = "admin-api-apikey-id",
clientSecret: String = "admin-api-apikey-secret")
clientSecret: String = "admin-api-apikey-secret",
elasticConfig: Option[ElasticAnalyticsConfig] = None)
extends CanJson[OtoroshiSettings] {
def asJson: JsValue = json.OtoroshiSettingsFormat.writes(this)
def toUiPayload(): JsValue = {
Expand Down
1 change: 1 addition & 0 deletions daikoku/app/utils/admin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ abstract class AdminApiController[Of, Id <: ValueType](
}

def findById(id: String) = DaikokuApiAction.async { ctx =>
println("hi")
val notDeleted: Boolean =
ctx.request.queryString.get("notDeleted").exists(_ == "true")
notDeleted match {
Expand Down
35 changes: 34 additions & 1 deletion daikoku/app/utils/otoroshi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package fr.maif.otoroshi.daikoku.utils

import akka.http.scaladsl.util.FastFuture
import akka.stream.Materializer
import cats.data.EitherT
import cats.implicits.catsSyntaxOptionId
import controllers.AppError
import controllers.AppError.OtoroshiError
import fr.maif.otoroshi.daikoku.audit.config.ElasticAnalyticsConfig
import fr.maif.otoroshi.daikoku.domain.json.ActualOtoroshiApiKeyFormat
import fr.maif.otoroshi.daikoku.domain.{ActualOtoroshiApiKey, OtoroshiSettings}
import fr.maif.otoroshi.daikoku.domain.{ActualOtoroshiApiKey, ApiSubscription, OtoroshiSettings, Tenant, json}
import fr.maif.otoroshi.daikoku.env.Env
import fr.maif.otoroshi.daikoku.logger.AppLogger
import play.api.libs.json._
import play.api.libs.ws.{WSAuthScheme, WSRequest}
import play.api.mvc._
Expand Down Expand Up @@ -285,4 +289,33 @@ class OtoroshiClient(env: Env) {
}
}
}

def getSubscriptionLastUsage(subscriptions: Seq[ApiSubscription])(implicit otoroshiSettings: OtoroshiSettings, tenant: Tenant): EitherT[Future, JsArray, JsArray] = {
otoroshiSettings.elasticConfig match {
case Some(value) => EitherT.pure[Future, JsArray](Json.arr())
case None => for {
elasticConfig <- EitherT.fromOptionF(getElasticConfig(), Json.arr())
log = AppLogger.warn(s"$elasticConfig")
updatedSettings = otoroshiSettings.copy(elasticConfig = elasticConfig.some)
updatedTenant = tenant.copy(otoroshiSettings = tenant.otoroshiSettings.filter(_.id != otoroshiSettings.id) + updatedSettings)
log2 = AppLogger.warn(s"$updatedSettings")
log3 = AppLogger.warn(s"${Json.prettyPrint(updatedTenant.asJson)}")
done <- EitherT.liftF(env.dataStore.tenantRepo.save(updatedTenant))
log4 = AppLogger.warn(s"$done")
r <- getSubscriptionLastUsage(subscriptions)(updatedSettings, updatedTenant)
} yield r
}
}

private def getElasticConfig()(implicit otoroshiSettings: OtoroshiSettings): Future[Option[ElasticAnalyticsConfig]] = {
client(s"/api/globalconfig").get().map(resp => {
if (resp.status == 200) {
val config = resp.json.as[JsObject]
val elasticReadConfig = (config \ "elasticReadsConfig").asOpt(ElasticAnalyticsConfig.format)
elasticReadConfig
} else {
None
}
})
}
}
1 change: 1 addition & 0 deletions daikoku/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ DELETE /api/teams/:teamId/subscriptions/_clean fr.maif.otoroshi.daiko
POST /api/teams/:teamId/subscriptions/:id/_rotation fr.maif.otoroshi.daikoku.ctrls.ApiController.toggleApiKeyRotation(teamId, id)
POST /api/teams/:teamId/subscriptions/:id/_refresh fr.maif.otoroshi.daikoku.ctrls.ApiController.regenerateApiKeySecret(teamId, id)
POST /api/subscriptions/_init fr.maif.otoroshi.daikoku.ctrls.ApiController.initSubscriptions()
POST /api/teams/:teamId/subscriptions/_lastUsage fr.maif.otoroshi.daikoku.ctrls.ApiController.getApiSubscriptionsUsage(teamId)
POST /api/apis/_init fr.maif.otoroshi.daikoku.ctrls.ApiController.initApis()
GET /api/teams/:teamId/subscription/:id/informations fr.maif.otoroshi.daikoku.ctrls.ApiController.getSubscriptionInformations(teamId, id)
GET /api/teams/:teamId/apis/:apiId/:version/subscriptions fr.maif.otoroshi.daikoku.ctrls.ApiController.getApiSubscriptions(teamId, apiId, version)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,47 @@ export const TeamApiSubscriptions = ({ api }: TeamApiSubscriptionsProps) => {
const { confirm, openFormModal, openSubMetadataModal, } = useContext(ModalContext);

const plansQuery = useQuery(['plans'], () => Services.getAllPlanOfApi(api.team, api._id, api.currentVersion))
const subscriptionsQuery = useQuery(['subscriptions'], () => client!.query<{ apiApiSubscriptions: Array<ApiSubscriptionGql>; }>({
query: Services.graphql.getApiSubscriptions,
fetchPolicy: "no-cache",
variables: {
apiId: api._id,
teamId: currentTeam._id,
version: api.currentVersion
}
}).then(({ data: { apiApiSubscriptions } }) => {
if (!filters || (!filters.tags.length && !Object.keys(filters.metadata).length && !filters.clientIds.length)) {
return apiApiSubscriptions
} else {
const filterByMetadata = (subscription: ApiSubscriptionGql) => {
const meta = { ...(subscription.metadata || {}), ...(subscription.customMetadata || {}) };

return !Object.keys(meta) || (!filters.metadata.length || filters.metadata.every(item => {
const value = meta[item.key]
return value && value.includes(item.value)
}))
}

const filterByTags = (subscription: ApiSubscriptionGql) => {
return filters.tags.every(tag => subscription.tags.includes(tag))
}

const filterByClientIds = (subscription: ApiSubscriptionGql) => {
return filters.clientIds.includes(subscription.apiKey.clientId)
}

return apiApiSubscriptions
.filter(filterByMetadata)
.filter(filterByTags)
.filter(filterByClientIds)
}
})
)
const lastUsagesQuery = useQuery({
queryKey: ['usages'],
queryFn: () => Services.getSubscriptionsLastUsages(api.team, subscriptionsQuery.data?.map(s => s._id) || []),
enabled: !!subscriptionsQuery.data && !isError(subscriptionsQuery.data)
})

useEffect(() => {
document.title = `${currentTeam.name} - ${translate('Subscriptions')}`;
Expand Down
2 changes: 1 addition & 1 deletion daikoku/javascript/src/components/frontend/team/MyHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useNavigate} from 'react-router-dom';
import { I18nContext, updateTeam } from '../../../core';
import * as Services from '../../../services';
import { converter } from '../../../services/showdown';
import { IApiWithAuthorization, isError, IState, ITenant, IUserSimple } from '../../../types';
import { IApiWithAuthorization, isError, IState, ITenant, IUsagePlan, IUserSimple } from '../../../types';
import { ApiList } from './ApiList';
import { api as API, CanIDoAction, manage, Spinner } from '../../utils';

Expand Down
7 changes: 7 additions & 0 deletions daikoku/javascript/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
IApiKey,
IOtoroshiApiKey,
} from '../types/api';
import { base64 } from 'js-md5';

const HEADERS = {
Accept: 'application/json',
Expand Down Expand Up @@ -1941,3 +1942,9 @@ export const cancelProcess = (teamId: string, demandId: string) =>

export const fetchInvoices = (teamId: string, apiId: string, planId: string, callback: string) =>
customFetch(`/api/teams/${teamId}/apis/${apiId}/plan/${planId}/invoices?callback=${callback}`);

export const getSubscriptionsLastUsages = (teamId: string, subscriptions: Array<string>): PromiseWithError<any> =>
customFetch(`/api/teams/${teamId}/subscriptions/_lastUsage`, {
method: 'POST',
body: JSON.stringify({subscriptions})
})

0 comments on commit 333257e

Please sign in to comment.