Skip to content

Commit

Permalink
WIP - create new table to control subscriptions transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
quentinovega committed Sep 11, 2024
1 parent 76d9cd7 commit a15520c
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 17 deletions.
35 changes: 20 additions & 15 deletions daikoku/app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2083,10 +2083,11 @@ class ApiController(

(for {
cypheredInfos <- EitherT.fromOption[Future][AppError, String](ctx.request.getQueryString("token"), AppError.EntityNotFound("token"))
infosAsString <- EitherT.pure[Future, AppError](decrypt(env.config.cypherSecret, cypheredInfos, ctx.tenant))
infos <- EitherT.pure[Future, AppError](Json.parse(infosAsString))
_ <- EitherT.cond[Future][AppError, Unit]((infos \ "createdOn").as(DateTimeFormat).plusDays(1).isAfter(DateTime.now()), (), AppError.ForbiddenAction) //give reason
subscription <- EitherT.fromOptionF[Future, AppError, ApiSubscription](env.dataStore.apiSubscriptionRepo.forTenant(ctx.tenant).findByIdNotDeleted((infos \ "subscription").as[String]), AppError.SubscriptionNotFound)
transferToken <- EitherT.pure[Future, AppError](decrypt(env.config.cypherSecret, cypheredInfos, ctx.tenant))
transfer <- EitherT.fromOptionF[Future, AppError, ApiSubscriptionTransfer](env.dataStore.apiSubscriptionTransferRepo.forTenant(ctx.tenant).findOneNotDeleted(Json.obj("token" -> transferToken)),
AppError.Unauthorized)
_ <- EitherT.cond[Future][AppError, Unit](transfer.date.plusDays(1).isAfter(DateTime.now()), (), AppError.ForbiddenAction) //give reason
subscription <- EitherT.fromOptionF[Future, AppError, ApiSubscription](env.dataStore.apiSubscriptionRepo.forTenant(ctx.tenant).findByIdNotDeleted(transfer.subscription), AppError.SubscriptionNotFound)
team <- EitherT.fromOptionF[Future, AppError, Team](env.dataStore.teamRepo.forTenant(ctx.tenant).findByIdNotDeleted(subscription.team), AppError.TeamNotFound)
usagePlan <- EitherT.fromOptionF[Future, AppError, UsagePlan](env.dataStore.usagePlanRepo.forTenant(ctx.tenant).findByIdNotDeleted(subscription.plan), AppError.PlanNotFound)
api <- EitherT.fromOptionF[Future, AppError, Api](env.dataStore.apiRepo.forTenant(ctx.tenant).findByIdNotDeleted(subscription.api), AppError.ApiNotFound)
Expand Down Expand Up @@ -2115,13 +2116,17 @@ class ApiController(
AppError.SubscriptionNotFound)
_ <- EitherT.cond[Future][AppError, Unit](subscription.parent.isEmpty, (), AppError.EntityConflict("Subscription is part of aggregation"))

json = Json.obj(
"tenant" -> ctx.tenant.id.asJson,
"subscription" -> subscription.id.asJson,
"createdOn" -> DateTime.now().getMillis,
"by" -> ctx.user.id.asJson
transfer = ApiSubscriptionTransfer(
id = DatastoreId(IdGenerator.token(16)),
tenant = ctx.tenant.id,
token = IdGenerator.token,
subscription = subscription.id,
by = ctx.user.id,
date = DateTime.now()
)
cipheredToken = encrypt(env.config.cypherSecret, Json.stringify(json), ctx.tenant)
cipheredToken = encrypt(env.config.cypherSecret, transfer.token, ctx.tenant)
_ <- EitherT.liftF[Future, AppError, Boolean](env.dataStore.apiSubscriptionTransferRepo.forTenant(ctx.tenant).delete(Json.obj("subscription" -> subscription.id.asJson)))
_ <- EitherT.liftF[Future, AppError, Boolean](env.dataStore.apiSubscriptionTransferRepo.forTenant(ctx.tenant).save(transfer))
link <- EitherT.pure[Future, AppError](s"${env.getDaikokuUrl(ctx.tenant, "/subscriptions/_retrieve")}?token=$cipheredToken")
} yield Ok(Json.obj("link" -> link)))
.leftMap(_.render())
Expand All @@ -2136,11 +2141,11 @@ class ApiController(
AuditTrailEvent(s"@{user.name} has ask to transfer subscription @{subscriptionId} to team @{teamId}"))(teamId, ctx) { team => {

(for {
token <- EitherT.fromOption[Future][AppError, String]((ctx.request.body \"token").asOpt[String], AppError.EntityNotFound("token"))
infos <- EitherT.pure[Future, AppError](Json.parse(Cypher.decrypt(env.config.cypherSecret, token, ctx.tenant)))
subscriptionFromInfo = (infos \ "subscription").as[String]
_ <- EitherT.cond[Future][AppError, Unit](subscriptionFromInfo == subscriptionId, (), AppError.EntityConflict("Subscription"))
subscription <- EitherT.fromOptionF[Future, AppError, ApiSubscription](env.dataStore.apiSubscriptionRepo.forTenant(ctx.tenant).findByIdNotDeleted(subscriptionFromInfo), AppError.SubscriptionNotFound)
transferToken <- EitherT.pure[Future, AppError](decrypt(env.config.cypherSecret, (ctx.request.body \ "token").as[String], ctx.tenant))
transfer <- EitherT.fromOptionF[Future, AppError, ApiSubscriptionTransfer](env.dataStore.apiSubscriptionTransferRepo.forTenant(ctx.tenant).findOneNotDeleted(Json.obj("token" -> transferToken)),
AppError.Unauthorized)
_ <- EitherT.cond[Future][AppError, Unit](transfer.subscription.value == subscriptionId, (), AppError.EntityConflict("Subscription"))
subscription <- EitherT.fromOptionF[Future, AppError, ApiSubscription](env.dataStore.apiSubscriptionRepo.forTenant(ctx.tenant).findByIdNotDeleted(transfer.subscription), AppError.SubscriptionNotFound)
_ <- EitherT.cond[Future][AppError, Unit](subscription.parent.isEmpty, (), AppError.EntityConflict("Subscription is part of aggregation"))
api <- EitherT.fromOptionF[Future, AppError, Api](env.dataStore.apiRepo.forTenant(ctx.tenant).findByIdNotDeleted(subscription.api), AppError.ApiNotFound)
plan <- EitherT.fromOptionF[Future, AppError, UsagePlan](env.dataStore.usagePlanRepo.forTenant(ctx.tenant).findByIdNotDeleted(subscription.plan), AppError.PlanNotFound)
Expand Down
11 changes: 11 additions & 0 deletions daikoku/app/domain/apikeyEntities.scala
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,14 @@ case class StepValidator(
) extends CanJson[StepValidator] {
override def asJson: JsValue = json.StepValidatorFormat.writes(this)
}

case class ApiSubscriptionTransfer(
id: DatastoreId,
tenant: TenantId,
deleted: Boolean = false,
token: String,
subscription: ApiSubscriptionId,
date: DateTime,
by: UserId) extends CanJson[ApiSubscriptionTransfer] {
override def asJson: JsValue = json.ApiSubscriptionTransferFormat.writes(this)
}
32 changes: 32 additions & 0 deletions daikoku/app/domain/json.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4761,6 +4761,38 @@ object json {
)
}

val ApiSubscriptionTransferFormat = new Format[ApiSubscriptionTransfer] {

override def reads(json: JsValue): JsResult[ApiSubscriptionTransfer] =
Try {
ApiSubscriptionTransfer(
id = (json \ "_id").as(DatastoreIdFormat),
tenant = (json \ "_tenant").as(TenantIdFormat),
deleted = (json \ "_deleted").as[Boolean],
token = (json \ "token").as[String],
subscription = (json \ "subscription").as(ApiSubscriptionIdFormat),
by = (json \ "by").as(UserIdFormat),
date = (json \ "date").as(DateTimeFormat)
)
} match {
case Failure(e) =>
AppLogger.error(e.getMessage, e)
JsError(e.getMessage)
case Success(value) => JsSuccess(value)
}

override def writes(o: ApiSubscriptionTransfer): JsValue =
Json.obj(
"_id" -> o.id.asJson,
"_tenant" -> o.tenant.asJson,
"_deleted" -> o.deleted,
"token" -> o.token,
"subscription" -> o.subscription.asJson,
"by" -> o.by.asJson,
"date" -> DateTimeFormat.writes(o.date)
)
}

val SetOtoroshiServicesIdFormat =
Format(
Reads.set(OtoroshiServiceIdFormat),
Expand Down
4 changes: 4 additions & 0 deletions daikoku/app/storage/api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ trait UserRepo extends Repo[User, UserId]
trait EvolutionRepo extends Repo[Evolution, DatastoreId]
trait ReportsInfoRepo extends Repo[ReportsInfo, DatastoreId]

trait ApiSubscriptionTransferRepo extends TenantCapableRepo[ApiSubscriptionTransfer, DatastoreId]

trait TeamRepo extends TenantCapableRepo[Team, TeamId] {
def myTeams(tenant: Tenant, user: User)(implicit
env: Env,
Expand Down Expand Up @@ -571,6 +573,8 @@ trait DataStore {

def reportsInfoRepo: ReportsInfoRepo

def apiSubscriptionTransferRepo: ApiSubscriptionTransferRepo

def exportAsStream(pretty: Boolean, exportAuditTrail: Boolean = true)(implicit
ec: ExecutionContext,
mat: Materializer,
Expand Down
50 changes: 48 additions & 2 deletions daikoku/app/storage/drivers/postgres/PostgresDataStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,19 @@ case class PostgresTenantCapableUsagePlanRepo(
_tenantRepo(tenant)
}

case class PostgresTenantCapableApiSubscriptionTransferRepo(
_repo: () => PostgresRepo[ApiSubscriptionTransfer, DatastoreId],
_tenantRepo: TenantId => PostgresTenantAwareRepo[ApiSubscriptionTransfer, DatastoreId]
) extends PostgresTenantCapableRepo[ApiSubscriptionTransfer, DatastoreId]
with ApiSubscriptionTransferRepo {
override def repo(): PostgresRepo[ApiSubscriptionTransfer, DatastoreId] = _repo()

override def tenantRepo(
tenant: TenantId
): PostgresTenantAwareRepo[ApiSubscriptionTransfer, DatastoreId] =
_tenantRepo(tenant)
}

case class PostgresTenantCapableConsumptionRepo(
_repo: () => PostgresRepo[ApiKeyConsumption, DatastoreId],
_tenantRepo: TenantId => PostgresTenantAwareRepo[
Expand Down Expand Up @@ -413,7 +426,8 @@ class PostgresDataStore(configuration: Configuration, env: Env, pgPool: PgPool)
"step_validators" -> true,
"usage_plans" -> true,
"assets" -> true,
"reports_info" -> true
"reports_info" -> true,
"api_subscription_transfers" -> true
)

private lazy val poolOptions: PoolOptions = new PoolOptions()
Expand Down Expand Up @@ -614,6 +628,12 @@ class PostgresDataStore(configuration: Configuration, env: Env, pgPool: PgPool)
t => new PostgresTenantUsagePlanRepo(env, reactivePg, t)
)

private val _apiSubscriptionTransferRepo: ApiSubscriptionTransferRepo =
PostgresTenantCapableApiSubscriptionTransferRepo(
() => new PostgresApiSubscriptionTransferRepo(env, reactivePg),
t => new PostgresTenantApiSubscriptionTransferRepo(env, reactivePg, t)
)

override def tenantRepo: TenantRepo = _tenantRepo

override def userRepo: UserRepo = _userRepo
Expand Down Expand Up @@ -666,6 +686,8 @@ class PostgresDataStore(configuration: Configuration, env: Env, pgPool: PgPool)

override def usagePlanRepo: UsagePlanRepo = _usagePlanRepo

override def apiSubscriptionTransferRepo: ApiSubscriptionTransferRepo = _apiSubscriptionTransferRepo

override def start(): Future[Unit] = {
Future.successful(())
}
Expand Down Expand Up @@ -788,7 +810,8 @@ class PostgresDataStore(configuration: Configuration, env: Env, pgPool: PgPool)
assetRepo.forAllTenant(),
stepValidatorRepo.forAllTenant(),
subscriptionDemandRepo.forAllTenant(),
usagePlanRepo.forAllTenant()
usagePlanRepo.forAllTenant(),
apiSubscriptionTransferRepo.forAllTenant()
)

if (exportAuditTrail) {
Expand Down Expand Up @@ -912,6 +935,10 @@ class PostgresDataStore(configuration: Configuration, env: Env, pgPool: PgPool)
emailVerificationRepo
.forAllTenant()
.save(json.EmailVerificationFormat.reads(payload).get)
case ("apisubscriptiontransfers", payload) =>
apiSubscriptionTransferRepo
.forAllTenant()
.save(json.ApiSubscriptionTransferFormat.reads(payload).get)
case (typ, _) =>
logger.error(s"Unknown type: $typ")
FastFuture.successful(false)
Expand Down Expand Up @@ -1108,6 +1135,16 @@ class PostgresTenantUsagePlanRepo(
override def extractId(value: UsagePlan): String = value.id.value
}

class PostgresTenantApiSubscriptionTransferRepo(env: Env, reactivePg: ReactivePg, tenant: TenantId)
extends PostgresTenantAwareRepo[ApiSubscriptionTransfer, DatastoreId](env, reactivePg, tenant) {

override def tableName: String = "api_subscription_transfers"

override def format: Format[ApiSubscriptionTransfer] = json.ApiSubscriptionTransferFormat

override def extractId(value: ApiSubscriptionTransfer): String = value.id.value
}

class PostgresTenantCmsPageRepo(
env: Env,
reactivePg: ReactivePg,
Expand Down Expand Up @@ -1372,6 +1409,15 @@ class PostgresUsagePlanRepo(env: Env, reactivePg: ReactivePg)
override def extractId(value: UsagePlan): String = value.id.value
}

class PostgresApiSubscriptionTransferRepo(env: Env, reactivePg: ReactivePg)
extends PostgresRepo[ApiSubscriptionTransfer, DatastoreId](env, reactivePg) {
override def tableName: String = "api_subscription_transfers"

override def format: Format[ApiSubscriptionTransfer] = json.ApiSubscriptionTransferFormat

override def extractId(value: ApiSubscriptionTransfer): String = value.id.value
}

class PostgresApiRepo(env: Env, reactivePg: ReactivePg)
extends PostgresRepo[Api, ApiId](env, reactivePg) {
override def tableName: String = "apis"
Expand Down

0 comments on commit a15520c

Please sign in to comment.