From f217c85de0a6c9908dd36b65ab6bc61e7a77ed66 Mon Sep 17 00:00:00 2001 From: Michael Bryzek Date: Thu, 1 Aug 2024 20:40:09 +0200 Subject: [PATCH] Move organization queries into dao generator --- .../controllers/ApiBuilderController.scala | 22 +- api/app/controllers/Applications.scala | 8 +- api/app/controllers/Domains.scala | 19 +- api/app/controllers/Healthchecks.scala | 6 +- api/app/controllers/MembershipRequests.scala | 14 +- api/app/controllers/Memberships.scala | 4 +- api/app/controllers/Organizations.scala | 33 +- api/app/controllers/Versions.scala | 20 +- ...ao.scala => InternalApplicationsDao.scala} | 17 +- api/app/db/InternalOrganizationsDao.scala | 233 +++++++++ api/app/db/MembershipRequestsDao.scala | 16 +- api/app/db/MembershipsDao.scala | 30 +- .../db/OrganizationAttributeValuesDao.scala | 8 +- api/app/db/OrganizationDomainsDao.scala | 12 +- api/app/db/OrganizationLogsDao.scala | 10 +- api/app/db/OrganizationsDao.scala | 316 ------------ api/app/db/SubscriptionsDao.scala | 2 +- api/app/db/VersionValidator.scala | 15 +- api/app/db/VersionsDao.scala | 18 +- api/app/db/WatchesDao.scala | 6 +- api/app/lib/Emails.scala | 33 +- api/app/models/ApplicationsModel.scala | 4 +- api/app/models/MembershipRequestsModel.scala | 16 +- api/app/models/MembershipsModel.scala | 16 +- api/app/models/OrganizationsModel.scala | 48 ++ api/app/models/SubscriptionModel.scala | 16 +- api/app/models/VersionsModel.scala | 8 +- api/app/models/WatchesModel.scala | 15 +- api/app/processor/DiffVersionProcessor.scala | 31 +- api/app/processor/EmailProcessor.scala | 37 +- .../processor/IndexApplicationProcessor.scala | 16 +- api/app/processor/UserCreatedProcessor.scala | 10 +- .../BatchDownloadApplicationsService.scala | 8 +- .../services/EmailVerificationsService.scala | 2 +- .../emails/applicationCreated.scala.html | 2 +- .../membershipRequestAccepted.scala.html | 2 +- .../membershipRequestDeclined.scala.html | 2 +- api/test/controllers/ApplicationsSpec.scala | 5 +- api/test/controllers/MockClient.scala | 4 +- api/test/controllers/WatchesSpec.scala | 6 +- api/test/db/ChangesDaoSpec.scala | 2 +- api/test/db/EmailVerificationsDaoSpec.scala | 8 +- api/test/db/Helpers.scala | 24 +- ...cala => InternalApplicationsDaoSpec.scala} | 6 +- ...ala => InternalOrganizationsDaoSpec.scala} | 31 +- api/test/db/ItemsDaoSpec.scala | 4 +- api/test/db/MembershipRequestsDaoSpec.scala | 38 +- api/test/db/OrganizationDomainsDaoSpec.scala | 13 +- api/test/db/SubscriptionsDaoSpec.scala | 2 +- api/test/db/VersionsDaoSpec.scala | 2 +- api/test/helpers/OrganizationHelpers.scala | 15 + api/test/lib/EmailsSpec.scala | 28 +- .../processor/PurgeDeletedProcessorSpec.scala | 4 +- api/test/util/Daos.scala | 4 +- app/app/controllers/Versions.scala | 4 +- dao/spec/psql-apibuilder.json | 77 ++- .../app/db/GeneratorInvocationsDao.scala | 10 +- generated/app/db/OrganizationsDao.scala | 463 ++++++++++++++++++ 58 files changed, 1158 insertions(+), 667 deletions(-) rename api/app/db/{ApplicationsDao.scala => InternalApplicationsDao.scala} (97%) create mode 100644 api/app/db/InternalOrganizationsDao.scala delete mode 100644 api/app/db/OrganizationsDao.scala create mode 100644 api/app/models/OrganizationsModel.scala rename api/test/db/{ApplicationsDaoSpec.scala => InternalApplicationsDaoSpec.scala} (97%) rename api/test/db/{OrganizationsDaoSpec.scala => InternalOrganizationsDaoSpec.scala} (90%) create mode 100644 api/test/helpers/OrganizationHelpers.scala create mode 100644 generated/app/db/OrganizationsDao.scala diff --git a/api/app/controllers/ApiBuilderController.scala b/api/app/controllers/ApiBuilderController.scala index 37301cc92..3aab1bdd1 100644 --- a/api/app/controllers/ApiBuilderController.scala +++ b/api/app/controllers/ApiBuilderController.scala @@ -1,15 +1,15 @@ package controllers -import javax.inject.Inject import com.google.inject.ImplementedBy -import db.{Authorization, MembershipsDao, OrganizationsDao} -import io.apibuilder.api.v0.models.{Organization, User} +import db.{Authorization, InternalOrganization, InternalOrganizationsDao, MembershipsDao} +import io.apibuilder.api.v0.models.User import io.apibuilder.api.v0.models.json._ import io.apibuilder.common.v0.models.MembershipRole import lib.{RequestAuthenticationUtil, Validation} import play.api.libs.json.{JsValue, Json} import play.api.mvc._ +import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} /** @@ -28,9 +28,9 @@ trait ApiBuilderController extends BaseController { def controllerComponents: ControllerComponents = apiBuilderControllerComponents.controllerComponents def membershipsDao: MembershipsDao = apiBuilderControllerComponents.membershipsDao - def organizationsDao: OrganizationsDao = apiBuilderControllerComponents.organizationsDao + def organizationsDao: InternalOrganizationsDao = apiBuilderControllerComponents.organizationsDao - def withOrg(auth: Authorization, orgKey: String)(f: Organization => Result): Result = { + def withOrg(auth: Authorization, orgKey: String)(f: InternalOrganization => Result): Result = { organizationsDao.findByKey(auth, orgKey) match { case None => Results.NotFound( jsonError(s"Organization[$orgKey] does not exist or you are not authorized to access it") @@ -42,7 +42,7 @@ trait ApiBuilderController extends BaseController { } } - def withOrgMember(user: User, orgKey: String)(f: Organization => Result): Result = { + def withOrgMember(user: User, orgKey: String)(f: InternalOrganization => Result): Result = { withOrg(Authorization.User(user.guid), orgKey) { org => withRole(org, user, MembershipRole.all) { f(org) @@ -50,7 +50,7 @@ trait ApiBuilderController extends BaseController { } } - def withOrgAdmin(user: User, orgKey: String)(f: Organization => Result): Result = { + def withOrgAdmin(user: User, orgKey: String)(f: InternalOrganization => Result): Result = { withOrg(Authorization.User(user.guid), orgKey) { org => withRole(org, user, Seq(MembershipRole.Admin)) { f(org) @@ -58,9 +58,9 @@ trait ApiBuilderController extends BaseController { } } - private def withRole(org: Organization, user: User, roles: Seq[MembershipRole])(f: => Result): Result = { + private def withRole(org: InternalOrganization, user: User, roles: Seq[MembershipRole])(f: => Result): Result = { val actualRoles = membershipsDao.findByOrganizationAndUserAndRoles( - Authorization.All, org, user, roles + Authorization.All, org.reference, user, roles ).map(_.role) if (actualRoles.isEmpty) { @@ -92,7 +92,7 @@ trait ApiBuilderControllerComponents { def identifiedActionBuilder: IdentifiedActionBuilder def controllerComponents: ControllerComponents def membershipsDao: MembershipsDao - def organizationsDao: OrganizationsDao + def organizationsDao: InternalOrganizationsDao } class ApiBuilderDefaultControllerComponents @Inject() ( @@ -100,7 +100,7 @@ class ApiBuilderDefaultControllerComponents @Inject() ( val anonymousActionBuilder: AnonymousActionBuilder, val identifiedActionBuilder: IdentifiedActionBuilder, val membershipsDao: MembershipsDao, - val organizationsDao: OrganizationsDao + val organizationsDao: InternalOrganizationsDao ) extends ApiBuilderControllerComponents case class AnonymousRequest[A]( diff --git a/api/app/controllers/Applications.scala b/api/app/controllers/Applications.scala index 4970c6f3a..05433a56c 100644 --- a/api/app/controllers/Applications.scala +++ b/api/app/controllers/Applications.scala @@ -15,10 +15,10 @@ import java.util.UUID @Singleton class Applications @Inject() ( - val apiBuilderControllerComponents: ApiBuilderControllerComponents, - applicationsDao: ApplicationsDao, - versionsDao: VersionsDao, - model: ApplicationsModel + val apiBuilderControllerComponents: ApiBuilderControllerComponents, + applicationsDao: InternalApplicationsDao, + versionsDao: VersionsDao, + model: ApplicationsModel ) extends ApiBuilderController { def get( diff --git a/api/app/controllers/Domains.scala b/api/app/controllers/Domains.scala index 745d8f19b..a23f3e72a 100644 --- a/api/app/controllers/Domains.scala +++ b/api/app/controllers/Domains.scala @@ -1,13 +1,13 @@ package controllers -import io.apibuilder.api.v0.models.json._ +import db.OrganizationDomainsDao import io.apibuilder.api.v0.models.Domain -import db.{MembershipsDao, OrganizationDomainsDao, OrganizationsDao} -import javax.inject.{Inject, Singleton} - +import io.apibuilder.api.v0.models.json._ import lib.Validation -import play.api.mvc._ import play.api.libs.json._ +import play.api.mvc._ + +import javax.inject.{Inject, Singleton} @Singleton class Domains @Inject() ( @@ -42,10 +42,11 @@ class Domains @Inject() ( def deleteByName(orgKey: String, name: String): Action[AnyContent] = Identified { request => withOrgAdmin(request.user, orgKey) { org => - org.domains.find(_.name == name).foreach { domain => - organizationDomainsDao.findAll(organizationGuid = Some(org.guid), domain = Some(domain.name)).foreach { orgDomain => - organizationDomainsDao.softDelete(request.user, orgDomain) - } + organizationDomainsDao.findAll( + organizationGuid = Some(org.guid), + domain = Some(name) + ).foreach { domain => + organizationDomainsDao.softDelete(request.user, domain) } NoContent } diff --git a/api/app/controllers/Healthchecks.scala b/api/app/controllers/Healthchecks.scala index 211f0328e..870c072a4 100644 --- a/api/app/controllers/Healthchecks.scala +++ b/api/app/controllers/Healthchecks.scala @@ -1,6 +1,6 @@ package controllers -import db.{Authorization, OrganizationsDao, VersionsDao} +import db.{Authorization, InternalOrganizationsDao, VersionsDao} import play.api.libs.json._ import play.api.mvc._ @@ -8,8 +8,8 @@ import javax.inject.{Inject, Named, Singleton} @Singleton class Healthchecks @Inject() ( - val apiBuilderControllerComponents: ApiBuilderControllerComponents, - organizationsDao: OrganizationsDao, + val apiBuilderControllerComponents: ApiBuilderControllerComponents, + organizationsDao: InternalOrganizationsDao, ) extends ApiBuilderController { private val Result = Json.toJson(Map("status" -> "healthy")) diff --git a/api/app/controllers/MembershipRequests.scala b/api/app/controllers/MembershipRequests.scala index c0f2066a4..ad026ea02 100644 --- a/api/app/controllers/MembershipRequests.scala +++ b/api/app/controllers/MembershipRequests.scala @@ -1,6 +1,6 @@ package controllers -import db.{MembershipRequestsDao, OrganizationsDao, UsersDao} +import db.{MembershipRequestsDao, InternalOrganizationsDao, UsersDao} import io.apibuilder.api.v0.models.json._ import io.apibuilder.api.v0.models.{Organization, User} import io.apibuilder.common.v0.models.MembershipRole @@ -14,11 +14,11 @@ import javax.inject.{Inject, Singleton} @Singleton class MembershipRequests @Inject() ( - val apiBuilderControllerComponents: ApiBuilderControllerComponents, - membershipRequestsDao: MembershipRequestsDao, - organizationsDao: OrganizationsDao, - usersDao: UsersDao, - model: MembershipRequestsModel + val apiBuilderControllerComponents: ApiBuilderControllerComponents, + membershipRequestsDao: MembershipRequestsDao, + organizationsDao: InternalOrganizationsDao, + usersDao: UsersDao, + model: MembershipRequestsModel ) extends ApiBuilderController { case class MembershipRequestForm( @@ -64,7 +64,7 @@ class MembershipRequests @Inject() ( Conflict(Json.toJson(Validation.error("Organization not found or not authorized to make changes to this org"))) } - case Some(org: Organization) => { + case Some(org) => { usersDao.findByGuid(form.user_guid) match { case None => { diff --git a/api/app/controllers/Memberships.scala b/api/app/controllers/Memberships.scala index 16d9baa8d..1ec4b7960 100644 --- a/api/app/controllers/Memberships.scala +++ b/api/app/controllers/Memberships.scala @@ -1,6 +1,6 @@ package controllers -import db.MembershipsDao +import db.{MembershipsDao, OrganizationReference} import io.apibuilder.api.v0.models.json._ import io.apibuilder.common.v0.models.MembershipRole import models.MembershipsModel @@ -46,7 +46,7 @@ class Memberships @Inject() ( membershipsDao.findByGuid(request.authorization, guid).flatMap(model.toModel) match { case None => NotFound case Some(membership) => { - if (membershipsDao.isUserAdmin(user = request.user, organization = membership.organization)) { + if (membershipsDao.isUserAdmin(user = request.user, organization = OrganizationReference(membership.organization))) { Ok(Json.toJson(membership)) } else { Unauthorized diff --git a/api/app/controllers/Organizations.scala b/api/app/controllers/Organizations.scala index 54ed736bc..894062011 100644 --- a/api/app/controllers/Organizations.scala +++ b/api/app/controllers/Organizations.scala @@ -4,17 +4,20 @@ import io.apibuilder.api.v0.models._ import io.apibuilder.api.v0.models.json._ import lib.Validation import db._ -import javax.inject.{Inject, Singleton} +import models.OrganizationsModel +import javax.inject.{Inject, Singleton} import play.api.mvc._ import play.api.libs.json._ + import java.util.UUID @Singleton class Organizations @Inject() ( val apiBuilderControllerComponents: ApiBuilderControllerComponents, attributesDao: AttributesDao, - organizationAttributeValuesDao: OrganizationAttributeValuesDao + organizationAttributeValuesDao: OrganizationAttributeValuesDao, + model: OrganizationsModel ) extends ApiBuilderController { def get( @@ -28,15 +31,17 @@ class Organizations @Inject() ( ): Action[AnyContent] = Anonymous { request => Ok( Json.toJson( - organizationsDao.findAll( - request.authorization, - userGuid = userGuid, - guid = guid, - key = key, - name = name, - namespace = namespace, - limit = Some(limit), - offset = offset + model.toModels( + organizationsDao.findAll( + request.authorization, + userGuid = userGuid, + guid = guid, + key = key, + name = name, + namespace = namespace, + limit = Some(limit), + offset = offset + ) ) ) ) @@ -44,7 +49,7 @@ class Organizations @Inject() ( def getByKey(key: String): Action[AnyContent] = Anonymous { request => withOrg(request.authorization, key) { org => - Ok(Json.toJson(org)) + Ok(Json.toJson(model.toModel(org))) } } @@ -58,7 +63,7 @@ class Organizations @Inject() ( val errors = organizationsDao.validate(form) if (errors.isEmpty) { val org = organizationsDao.createWithAdministrator(request.user, form) - Ok(Json.toJson(org)) + Ok(Json.toJson(model.toModel(org))) } else { Conflict(Json.toJson(errors)) } @@ -79,7 +84,7 @@ class Organizations @Inject() ( val errors = organizationsDao.validate(form, Some(existing)) if (errors.isEmpty) { val org = organizationsDao.update(request.user, existing, form) - Ok(Json.toJson(org)) + Ok(Json.toJson(model.toModel(org))) } else { Conflict(Json.toJson(errors)) } diff --git a/api/app/controllers/Versions.scala b/api/app/controllers/Versions.scala index b645fc291..844e64cc9 100644 --- a/api/app/controllers/Versions.scala +++ b/api/app/controllers/Versions.scala @@ -16,13 +16,13 @@ import javax.inject.{Inject, Singleton} @Singleton class Versions @Inject() ( - val apiBuilderControllerComponents: ApiBuilderControllerComponents, - apiBuilderServiceImportResolver: ApiBuilderServiceImportResolver, - applicationsDao: ApplicationsDao, - databaseServiceFetcher: DatabaseServiceFetcher, - versionsDao: VersionsDao, - versionValidator: VersionValidator, - model: VersionsModel, + val apiBuilderControllerComponents: ApiBuilderControllerComponents, + apiBuilderServiceImportResolver: ApiBuilderServiceImportResolver, + applicationsDao: InternalApplicationsDao, + databaseServiceFetcher: DatabaseServiceFetcher, + versionsDao: VersionsDao, + versionValidator: VersionValidator, + model: VersionsModel, ) extends ApiBuilderController { private val DefaultVisibility = Visibility.Organization @@ -205,7 +205,7 @@ class Versions @Inject() ( private def upsertVersion( user: User, - org: Organization, + org: InternalOrganization, versionName: String, form: VersionForm, original: Original, @@ -253,11 +253,11 @@ class Versions @Inject() ( } private def toServiceConfiguration( - org: Organization, + org: InternalOrganization, version: String ) = ServiceConfiguration( orgKey = org.key, - orgNamespace = org.namespace, + orgNamespace = org.db.namespace, version = version ) diff --git a/api/app/db/ApplicationsDao.scala b/api/app/db/InternalApplicationsDao.scala similarity index 97% rename from api/app/db/ApplicationsDao.scala rename to api/app/db/InternalApplicationsDao.scala index 3e63333ae..272c7d958 100644 --- a/api/app/db/ApplicationsDao.scala +++ b/api/app/db/InternalApplicationsDao.scala @@ -3,7 +3,7 @@ package db import anorm._ import cats.data.ValidatedNec import cats.implicits._ -import io.apibuilder.api.v0.models.{AppSortBy, ApplicationForm, Error, MoveForm, Organization, SortOrder, User, Version, Visibility} +import io.apibuilder.api.v0.models.{AppSortBy, ApplicationForm, Error, MoveForm, SortOrder, User, Version, Visibility} import io.apibuilder.common.v0.models.{Audit, ReferenceGuid} import io.apibuilder.task.v0.models.{EmailDataApplicationCreated, TaskType} import io.flow.postgresql.Query @@ -27,11 +27,10 @@ case class InternalApplication( audit: Audit ) -@Singleton -class ApplicationsDao @Inject() ( +class InternalApplicationsDao @Inject()( @NamedDatabase("default") db: Database, emailQueue: EmailProcessorQueue, - organizationsDao: OrganizationsDao, + organizationsDao: InternalOrganizationsDao, tasksDao: InternalTasksDao, ) { @@ -96,7 +95,7 @@ class ApplicationsDao @Inject() ( where guid = {guid}::uuid """ - private def validateOrganizationByKey(auth: Authorization, key: String): ValidatedNec[Error, Organization] = { + private def validateOrganizationByKey(auth: Authorization, key: String): ValidatedNec[Error, InternalOrganization] = { organizationsDao.findByKey(auth, key).toValidNec(Validation.singleError(s"Organization[$key] not found")) } @@ -105,7 +104,7 @@ class ApplicationsDao @Inject() ( authorization: Authorization, app: InternalApplication, form: MoveForm - ): ValidatedNec[Error, Organization] = { + ): ValidatedNec[Error, InternalOrganization] = { validateOrganizationByKey(authorization, form.orgKey).andThen { org => findByOrganizationKeyAndApplicationKey(Authorization.All, org.key, app.key) match { case None => org.validNec @@ -121,7 +120,7 @@ class ApplicationsDao @Inject() ( } def validate( - org: Organization, + org: InternalOrganization, form: ApplicationForm, existing: Option[InternalApplication] = None ): Seq[Error] = { @@ -250,7 +249,7 @@ class ApplicationsDao @Inject() ( def create( createdBy: User, - org: Organization, + org: InternalOrganization, form: ApplicationForm ): InternalApplication = { val errors = validate(org, form) @@ -288,7 +287,7 @@ class ApplicationsDao @Inject() ( findAll(Authorization.User(user.guid), key = Some(app.key), limit = Some(1)).nonEmpty } - private[db] def findByOrganizationAndName(authorization: Authorization, org: Organization, name: String): Option[InternalApplication] = { + private[db] def findByOrganizationAndName(authorization: Authorization, org: InternalOrganization, name: String): Option[InternalApplication] = { findAll(authorization, orgKey = Some(org.key), name = Some(name), limit = Some(1)).headOption } diff --git a/api/app/db/InternalOrganizationsDao.scala b/api/app/db/InternalOrganizationsDao.scala new file mode 100644 index 000000000..5b9e65926 --- /dev/null +++ b/api/app/db/InternalOrganizationsDao.scala @@ -0,0 +1,233 @@ +package db + +import io.apibuilder.api.v0.models._ +import io.apibuilder.common.v0.models.MembershipRole +import lib.{Misc, UrlKey, Validation} +import org.joda.time.DateTime +import play.api.inject.Injector + +import java.util.UUID +import javax.inject.Inject + +case class OrganizationReference(guid: UUID) +object OrganizationReference { + def apply(org: InternalOrganization): OrganizationReference = OrganizationReference(org.guid) + def apply(org: Organization): OrganizationReference = OrganizationReference(org.guid) +} + +case class InternalOrganization(db: generated.Organization) { + val guid: UUID = db.guid + val key: String = db.key + val name: String = db.name + val visibility: Visibility = Visibility(db.visibility) + val reference: OrganizationReference = OrganizationReference(guid) +} + +class InternalOrganizationsDao @Inject()( + dao: generated.OrganizationsDao, + injector: Injector, + organizationDomainsDao: OrganizationDomainsDao, + organizationLogsDao: OrganizationLogsDao +) { + + // TODO: resolve circular dependency + private def membershipsDao = injector.instanceOf[MembershipsDao] + + private val MinNameLength = 3 + + def validate( + form: OrganizationForm, + existing: Option[InternalOrganization] = None + ): Seq[io.apibuilder.api.v0.models.Error] = { + val nameErrors = if (form.name.length < MinNameLength) { + Seq(s"name must be at least $MinNameLength characters") + } else { + findAll(Authorization.All, name = Some(form.name), limit = Some(1)).headOption match { + case None => Nil + case Some(org) => { + if (existing.map(_.guid).contains(org.guid)) { + Nil + } else { + Seq(s"Org with the name '${form.name}' already exists") + } + } + } + } + + val keyErrors = form.key match { + case None => { + Nil + } + + case Some(key) => { + UrlKey.validate(key) match { + case Nil => { + findByKey(Authorization.All, key) match { + case None => Nil + case Some(found) => { + if (existing.map(_.guid).contains(found.guid)) { + Nil + } else { + Seq(s"Org with the key '${found.key}' already exists") + } + } + } + } + case errors => errors + } + } + } + + val namespaceErrors = findAll(Authorization.All, namespace = Some(form.namespace.trim), limit = Some(1)).headOption match { + case None => { + if (isDomainValid(form.namespace.trim)) { + Nil + } else { + Seq("Namespace is not valid. Expected a name like io.apibuilder") + } + } + case Some(_) => { + Nil + } + } + + val visibilityErrors = Visibility.fromString(form.visibility.toString) match { + case Some(_) => Nil + case None => Seq(s"Invalid visibility[${form.visibility.toString}]") + } + + val domainErrors = form.domains.getOrElse(Nil).filter(!isDomainValid(_)).map(d => s"Domain $d is not valid. Expected a domain name like apibuilder.io") + + Validation.errors(nameErrors ++ keyErrors ++ namespaceErrors ++ visibilityErrors ++ domainErrors) + } + + + // We just care that the domain does not have a space in it + private val DomainRx = """^[^\s]+$""".r + private[db] def isDomainValid(domain: String): Boolean = { + domain match { + case DomainRx() => true + case _ => false + } + } + + /** + * Creates the org and assigns the user as its administrator. + */ + def createWithAdministrator(user: User, form: OrganizationForm): InternalOrganization = { + dao.db.withTransaction { implicit c => + val org = create(c, user, form) + membershipsDao.create(c, user.guid, org.reference, user, MembershipRole.Admin) + organizationLogsDao.create(c, user.guid, org.reference, s"Created organization and joined as ${MembershipRole.Admin}") + org + } + } + + def findAllByEmailDomain(email: String): Seq[InternalOrganization] = { + Misc.emailDomain(email) match { + case None => Nil + case Some(domain) => { + organizationDomainsDao.findAll(domain = Some(domain)).flatMap { domain => + findByGuid(Authorization.All, domain.organizationGuid) + } + } + } + } + + def update(user: User, existing: InternalOrganization, form: OrganizationForm): InternalOrganization = { + val errors = validate(form, Some(existing)) + assert(errors.isEmpty, errors.map(_.message).mkString("\n")) + + dao.update(user.guid, existing.db, existing.db.form.copy( + name = form.name.trim, + key = form.key.getOrElse(UrlKey.generate(form.name)).trim, + namespace = form.namespace.trim, + visibility = form.visibility.toString, + )) + + // TODO: Figure out how we want to handle domains. Best option + // might be to remove domains from organization_form + + findByGuid(Authorization.All, existing.guid).getOrElse { + sys.error("Failed to update org") + } + } + + private def create(implicit c: java.sql.Connection, user: User, form: OrganizationForm): InternalOrganization = { + val errors = validate(form) + assert(errors.isEmpty, errors.map(_.message).mkString("\n")) + + val orgGuid = dao.insert(user.guid, db.generated.OrganizationForm( + key = form.key.getOrElse(UrlKey.generate(form.name)).trim, + name = form.name.trim, + namespace = form.namespace.trim, + visibility = form.visibility.toString, + )) + + form.domains.getOrElse(Nil).foreach { domainName => + organizationDomainsDao.create(c, user, orgGuid, domainName) + } + + InternalOrganization(dao.findByGuidWithConnection(c, orgGuid).getOrElse { + sys.error("Failed to create organization") + }) + } + + def softDelete(deletedBy: User, org: InternalOrganization): Unit = { + dao.delete(deletedBy.guid, org.db) + } + + def findByGuid(authorization: Authorization, guid: UUID): Option[InternalOrganization] = { + findAll(authorization, guid = Some(guid), limit = Some(1)).headOption + } + + def findByKey(authorization: Authorization, orgKey: String): Option[InternalOrganization] = { + findAll(authorization, key = Some(orgKey), limit = Some(1)).headOption + } + + def findAll( + authorization: Authorization, + guid: Option[UUID] = None, + guids: Option[Seq[UUID]] = None, + userGuid: Option[UUID] = None, + applicationGuid: Option[UUID] = None, + key: Option[String] = None, + name: Option[String] = None, + namespace: Option[String] = None, + isDeleted: Option[Boolean] = Some(false), + deletedAtBefore: Option[DateTime] = None, + limit: Option[Long], + offset: Long = 0 + ): Seq[InternalOrganization] = { + dao.findAll( + guid = guid, + guids = guids, + limit = limit, + offset = offset, + ) { q => + authorization.organizationFilter(q, "organizations.guid") + .equals("key", key) + .equals("lower(name)", name.map(_.toLowerCase().trim)) + .equals("lower(namespace)", namespace.map(_.toLowerCase().trim)) + .and(isDeleted.map { + case false => "deleted_at is null" + case true => "deleted_at is not null" + }) + .and( + userGuid.map { _ => + "organizations.guid in (select organization_guid from memberships where deleted_at is null and user_guid = {user_guid}::uuid)" + } + ).bind("user_guid", userGuid) + .and( + applicationGuid.map { _ => + "organizations.guid in (select organization_guid from applications where deleted_at is null and guid = {application_guid}::uuid)" + } + ).bind("application_guid", applicationGuid) + .and(deletedAtBefore.map { _ => + "deleted_at < {deleted_at_before}::timestamptz" + }).bind("deleted_at_before", deletedAtBefore) + .and(isDeleted.map(Filters.isDeleted("organizations", _))) + .orderBy("lower(name), created_at") + }.map(InternalOrganization(_)) + } +} diff --git a/api/app/db/MembershipRequestsDao.scala b/api/app/db/MembershipRequestsDao.scala index 2fad539dd..7f0970ead 100644 --- a/api/app/db/MembershipRequestsDao.scala +++ b/api/app/db/MembershipRequestsDao.scala @@ -1,7 +1,7 @@ package db import anorm._ -import io.apibuilder.api.v0.models.{MembershipRequest, Organization, User} +import io.apibuilder.api.v0.models.{MembershipRequest, User} import io.apibuilder.common.v0.models.{Audit, MembershipRole, ReferenceGuid} import io.apibuilder.task.v0.models.{EmailDataMembershipRequestAccepted, EmailDataMembershipRequestCreated, EmailDataMembershipRequestDeclined} import io.flow.postgresql.Query @@ -46,7 +46,7 @@ class MembershipRequestsDao @Inject() ( ({guid}::uuid, {organization_guid}::uuid, {user_guid}::uuid, {role}, {created_by_guid}::uuid) """ - def upsert(createdBy: User, organization: Organization, user: User, role: MembershipRole): InternalMembershipRequest = { + def upsert(createdBy: User, organization: InternalOrganization, user: User, role: MembershipRole): InternalMembershipRequest = { findByOrganizationAndUserGuidAndRole(Authorization.All, organization, user.guid, role) match { case Some(r: InternalMembershipRequest) => r case None => { @@ -55,7 +55,7 @@ class MembershipRequestsDao @Inject() ( } } - private[db] def create(createdBy: User, organization: Organization, user: User, role: MembershipRole): InternalMembershipRequest = { + private[db] def create(createdBy: User, organization: InternalOrganization, user: User, role: MembershipRole): InternalMembershipRequest = { val guid = UUID.randomUUID db.withTransaction { implicit c => SQL(InsertQuery).on( @@ -89,9 +89,9 @@ class MembershipRequestsDao @Inject() ( private def doAccept(createdBy: UUID, request: MembershipRequest, message: String): Unit = { db.withTransaction { implicit c => - organizationLogsDao.create(createdBy, request.organization, message) + organizationLogsDao.create(createdBy, OrganizationReference(request.organization), message) dbHelpers.delete(c, createdBy, request.guid) - membershipsDao.upsert(createdBy, request.organization, request.user, request.role) + membershipsDao.upsert(createdBy, OrganizationReference(request.organization), request.user, request.role) emailQueue.queueWithConnection(c, EmailDataMembershipRequestAccepted(request.organization.guid, request.user.guid, request.role)) } } @@ -105,7 +105,7 @@ class MembershipRequestsDao @Inject() ( val message = s"Declined membership request for ${request.user.email} to join as ${request.role}" db.withTransaction { implicit c => - organizationLogsDao.create(createdBy.guid, request.organization, message) + organizationLogsDao.create(createdBy.guid, OrganizationReference(request.organization), message) softDelete(createdBy, request) emailQueue.queueWithConnection(c, EmailDataMembershipRequestDeclined(request.organization.guid, request.user.guid)) } @@ -113,7 +113,7 @@ class MembershipRequestsDao @Inject() ( private def assertUserCanReview(user: User, request: MembershipRequest): Unit = { require( - membershipsDao.isUserAdmin(user, request.organization), + membershipsDao.isUserAdmin(user, OrganizationReference(request.organization)), s"User[${user.guid}] is not an administrator of org[${request.organization.guid}]" ) } @@ -124,7 +124,7 @@ class MembershipRequestsDao @Inject() ( def findByOrganizationAndUserGuidAndRole( authorization: Authorization, - org: Organization, + org: InternalOrganization, userGuid: UUID, role: MembershipRole ): Option[InternalMembershipRequest] = { diff --git a/api/app/db/MembershipsDao.scala b/api/app/db/MembershipsDao.scala index ebc8c5943..ef67d57d7 100644 --- a/api/app/db/MembershipsDao.scala +++ b/api/app/db/MembershipsDao.scala @@ -1,7 +1,7 @@ package db import anorm._ -import io.apibuilder.api.v0.models.{Organization, User} +import io.apibuilder.api.v0.models.User import io.apibuilder.common.v0.models.{Audit, MembershipRole, ReferenceGuid} import io.apibuilder.task.v0.models.EmailDataMembershipCreated import io.flow.postgresql.Query @@ -46,17 +46,17 @@ class MembershipsDao @Inject() ( join users on users.guid = memberships.user_guid """) - def upsert(createdBy: UUID, organization: Organization, user: User, role: MembershipRole): InternalMembership = { - val membership = findByOrganizationAndUserAndRole(Authorization.All, organization, user, role) match { + def upsert(createdBy: UUID, org: OrganizationReference, user: User, role: MembershipRole): InternalMembership = { + val membership = findByOrganizationAndUserAndRole(Authorization.All, org, user, role) match { case Some(r) => r - case None => create(createdBy, organization, user, role) + case None => create(createdBy, org, user, role) } // If we made this user an admin, and s/he already exists as a // member, remove the member role - this is akin to an upgrade // in membership from member to admin. if (role == MembershipRole.Admin) { - findByOrganizationAndUserAndRole(Authorization.All, organization, user, MembershipRole.Member).foreach { membership => + findByOrganizationAndUserAndRole(Authorization.All, org, user, MembershipRole.Member).foreach { membership => softDelete(user, membership) } } @@ -64,18 +64,18 @@ class MembershipsDao @Inject() ( membership } - private[db] def create(createdBy: UUID, organization: Organization, user: User, role: MembershipRole): InternalMembership = { + private[db] def create(createdBy: UUID, org: OrganizationReference, user: User, role: MembershipRole): InternalMembership = { db.withTransaction { implicit c => - create(c, createdBy, organization, user, role) + create(c, createdBy, org, user, role) } } - private[db] def create(implicit c: java.sql.Connection, createdBy: UUID, organization: Organization, user: User, role: MembershipRole): InternalMembership = { + private[db] def create(implicit c: java.sql.Connection, createdBy: UUID, org: OrganizationReference, user: User, role: MembershipRole): InternalMembership = { val guid = UUID.randomUUID SQL(InsertQuery).on( "guid" -> guid, - "organization_guid" -> organization.guid, + "organization_guid" -> org.guid, "user_guid" -> user.guid, "role" -> role.toString, "created_by_guid" -> createdBy @@ -103,7 +103,7 @@ class MembershipsDao @Inject() ( def isUserAdmin( user: User, - organization: Organization + organization: OrganizationReference ): Boolean = { isUserAdmin(userGuid = user.guid, organizationGuid = organization.guid) } @@ -120,7 +120,7 @@ class MembershipsDao @Inject() ( def isUserMember( user: User, - organization: Organization + organization: OrganizationReference ): Boolean = { isUserMember(userGuid = user.guid, organizationGuid = organization.guid) } @@ -142,19 +142,19 @@ class MembershipsDao @Inject() ( def findByOrganizationAndUserAndRole( authorization: Authorization, - organization: Organization, + org: OrganizationReference, user: User, role: MembershipRole ): Option[InternalMembership] = { findByOrganizationGuidAndUserGuidAndRole( authorization, - organizationGuid = organization.guid, + organizationGuid = org.guid, userGuid = user.guid, role = role ) } - def findByOrganizationGuidAndUserGuidAndRole( + private def findByOrganizationGuidAndUserGuidAndRole( authorization: Authorization, organizationGuid: UUID, userGuid: UUID, @@ -166,7 +166,7 @@ class MembershipsDao @Inject() ( def findByOrganizationAndUserAndRoles( authorization: Authorization, - organization: Organization, + organization: OrganizationReference, user: User, roles: Seq[MembershipRole] ): Seq[InternalMembership] = { diff --git a/api/app/db/OrganizationAttributeValuesDao.scala b/api/app/db/OrganizationAttributeValuesDao.scala index c95434bec..99fd124aa 100644 --- a/api/app/db/OrganizationAttributeValuesDao.scala +++ b/api/app/db/OrganizationAttributeValuesDao.scala @@ -43,7 +43,7 @@ class OrganizationAttributeValuesDao @Inject() ( """ def validate( - organization: Organization, + organization: InternalOrganization, attribute: AttributeSummary, form: AttributeValueForm, existing: Option[AttributeValue] @@ -72,14 +72,14 @@ class OrganizationAttributeValuesDao @Inject() ( Validation.errors(attributeErrors ++ valueErrors) } - def upsert(user: User, organization: Organization, attribute: Attribute, form: AttributeValueForm): AttributeValue = { + def upsert(user: User, organization: InternalOrganization, attribute: Attribute, form: AttributeValueForm): AttributeValue = { findByOrganizationGuidAndAttributeName(organization.guid, attribute.name) match { case None => create(user, organization, attribute, form) case Some(existing) => update(user, organization, existing, form) } } - def create(user: User, organization: Organization, attribute: Attribute, form: AttributeValueForm): AttributeValue = { + def create(user: User, organization: InternalOrganization, attribute: Attribute, form: AttributeValueForm): AttributeValue = { val errors = validate(organization, AttributeSummary(attribute.guid, attribute.name), form, None) assert(errors.isEmpty, errors.map(_.message).mkString("\n")) @@ -100,7 +100,7 @@ class OrganizationAttributeValuesDao @Inject() ( } } - def update(user: User, organization: Organization, existing: AttributeValue, form: AttributeValueForm): AttributeValue = { + def update(user: User, organization: InternalOrganization, existing: AttributeValue, form: AttributeValueForm): AttributeValue = { val errors = validate(organization, existing.attribute, form, Some(existing)) assert(errors.isEmpty, errors.map(_.message).mkString("\n")) diff --git a/api/app/db/OrganizationDomainsDao.scala b/api/app/db/OrganizationDomainsDao.scala index 1e7ca058e..51ee8c4e2 100644 --- a/api/app/db/OrganizationDomainsDao.scala +++ b/api/app/db/OrganizationDomainsDao.scala @@ -1,7 +1,7 @@ package db import anorm._ -import io.apibuilder.api.v0.models.{Domain, Organization, User} +import io.apibuilder.api.v0.models.{Domain, User} import io.flow.postgresql.Query import play.api.db._ @@ -26,16 +26,16 @@ class OrganizationDomainsDao @Inject() ( ({guid}::uuid, {organization_guid}::uuid, {domain}, {created_by_guid}::uuid) """ - def create(createdBy: User, org: Organization, domainName: String): OrganizationDomain = { + def create(createdBy: User, org: InternalOrganization, domainName: String): OrganizationDomain = { db.withConnection { implicit c => - create(c, createdBy, org, domainName) + create(c, createdBy, org.guid, domainName) } } - private[db] def create(implicit c: java.sql.Connection, createdBy: User, org: Organization, domainName: String): OrganizationDomain = { + private[db] def create(implicit c: java.sql.Connection, createdBy: User, orgGuid: UUID, domainName: String): OrganizationDomain = { val domain = OrganizationDomain( guid = UUID.randomUUID, - organizationGuid = org.guid, + organizationGuid = orgGuid, domain = Domain(domainName.trim) ) @@ -56,6 +56,7 @@ class OrganizationDomainsDao @Inject() ( def findAll( guid: Option[UUID] = None, organizationGuid: Option[UUID] = None, + organizationGuids: Option[Seq[UUID]] = None, domain: Option[String] = None, isDeleted: Option[Boolean] = Some(false) ): Seq[OrganizationDomain] = { @@ -63,6 +64,7 @@ class OrganizationDomainsDao @Inject() ( BaseQuery. equals("guid", guid). equals("organization_guid", organizationGuid). + optionalIn("organization_guid", organizationGuids). and( domain.map { _ => "domain = lower(trim({domain}))" diff --git a/api/app/db/OrganizationLogsDao.scala b/api/app/db/OrganizationLogsDao.scala index fe069cc4e..e37ebd739 100644 --- a/api/app/db/OrganizationLogsDao.scala +++ b/api/app/db/OrganizationLogsDao.scala @@ -29,16 +29,16 @@ class OrganizationLogsDao @Inject() ( ({guid}::uuid, {organization_guid}::uuid, {message}, {created_by_guid}::uuid) """ - def create(createdBy: UUID, organization: Organization, message: String): OrganizationLog = { + def create(createdBy: UUID, org: OrganizationReference, message: String): OrganizationLog = { db.withConnection { implicit c => - create(c, createdBy, organization, message) + create(c, createdBy, org, message) } } - private[db] def create(implicit c: java.sql.Connection, createdBy: UUID, organization: Organization, message: String): OrganizationLog = { + private[db] def create(implicit c: java.sql.Connection, createdBy: UUID, org: OrganizationReference, message: String): OrganizationLog = { val log = OrganizationLog( guid = UUID.randomUUID, - organizationGuid = organization.guid, + organizationGuid = org.guid, message = message ) @@ -54,7 +54,7 @@ class OrganizationLogsDao @Inject() ( def findAll( authorization: Authorization, - organization: Option[Organization], + organization: Option[OrganizationReference], limit: Long = 25, offset: Long = 0 ): Seq[OrganizationLog] = { diff --git a/api/app/db/OrganizationsDao.scala b/api/app/db/OrganizationsDao.scala deleted file mode 100644 index 00dc20de3..000000000 --- a/api/app/db/OrganizationsDao.scala +++ /dev/null @@ -1,316 +0,0 @@ -package db - -import anorm._ -import io.apibuilder.api.v0.models._ -import io.apibuilder.common.v0.models.{Audit, MembershipRole, ReferenceGuid} -import io.flow.postgresql.Query -import lib.{Misc, UrlKey, Validation} -import org.joda.time.DateTime -import play.api.db._ -import play.api.inject.Injector -import play.api.libs.json.Json - -import java.util.UUID -import javax.inject.{Inject, Singleton} - -@Singleton -class OrganizationsDao @Inject() ( - @NamedDatabase("default") db: Database, - injector: Injector, - organizationDomainsDao: OrganizationDomainsDao, - organizationLogsDao: OrganizationLogsDao -) { - - private val dbHelpers = DbHelpers(db, "organizations") - - // TODO: resolve circular dependency - private def membershipsDao = injector.instanceOf[MembershipsDao] - - private val MinNameLength = 3 - - private[db] val BaseQuery = Query(s""" - select guid, - name, - key, - visibility, - namespace, - ${AuditsDao.query("organizations")}, - coalesce( - (select to_json(array_agg(domain)) - from organization_domains - where deleted_at is null - and organization_guid = organizations.guid), - '[]'::json - )::text as domains - from organizations - """) - - private val InsertQuery = """ - insert into organizations - (guid, name, key, namespace, visibility, created_by_guid, updated_by_guid) - values - ({guid}::uuid, {name}, {key}, {namespace}, {visibility}, {user_guid}::uuid, {user_guid}::uuid) - """ - - private val UpdateQuery = """ - update organizations - set name = {name}, - key = {key}, - namespace = {namespace}, - visibility = {visibility}, - updated_by_guid = {user_guid}::uuid - where guid = {guid}::uuid - """ - - def validate( - form: OrganizationForm, - existing: Option[Organization] = None - ): Seq[io.apibuilder.api.v0.models.Error] = { - - val nameErrors = if (form.name.length < MinNameLength) { - Seq(s"name must be at least $MinNameLength characters") - } else { - findAll(Authorization.All, name = Some(form.name), limit = Some(1)).headOption match { - case None => Nil - case Some(org: Organization) => { - if (existing.map(_.guid).contains(org.guid)) { - Nil - } else { - Seq("Org with this name already exists") - } - } - } - } - - val keyErrors = form.key match { - case None => { - Nil - } - - case Some(key) => { - UrlKey.validate(key) match { - case Nil => { - findByKey(Authorization.All, key) match { - case None => Nil - case Some(found) => { - if (existing.map(_.guid).contains(found.guid)) { - Nil - } else { - Seq("Org with this key already exists") - } - } - } - } - case errors => errors - } - } - } - - val namespaceErrors = findAll(Authorization.All, namespace = Some(form.namespace.trim), limit = Some(1)).headOption match { - case None => { - if (isDomainValid(form.namespace.trim)) { - Nil - } else { - Seq("Namespace is not valid. Expected a name like io.apibuilder") - } - } - case Some(_) => { - Nil - } - } - - val visibilityErrors = Visibility.fromString(form.visibility.toString) match { - case Some(_) => Nil - case None => Seq(s"Invalid visibility[${form.visibility.toString}]") - } - - val domainErrors = form.domains.getOrElse(Nil).filter(!isDomainValid(_)).map(d => s"Domain $d is not valid. Expected a domain name like apibuilder.io") - - Validation.errors(nameErrors ++ keyErrors ++ namespaceErrors ++ visibilityErrors ++ domainErrors) - } - - - // We just care that the domain does not have a space in it - private val DomainRx = """^[^\s]+$""".r - private[db] def isDomainValid(domain: String): Boolean = { - domain match { - case DomainRx() => true - case _ => false - } - } - - /** - * Creates the org and assigns the user as its administrator. - */ - def createWithAdministrator(user: User, form: OrganizationForm): Organization = { - db.withTransaction { implicit c => - val org = create(c, user, form) - membershipsDao.create(c, user.guid, org, user, MembershipRole.Admin) - organizationLogsDao.create(c, user.guid, org, s"Created organization and joined as ${MembershipRole.Admin}") - org - } - } - - def findAllByEmailDomain(email: String): Seq[Organization] = { - Misc.emailDomain(email) match { - case None => Nil - case Some(domain) => { - organizationDomainsDao.findAll(domain = Some(domain)).flatMap { domain => - findByGuid(Authorization.All, domain.organizationGuid) - } - } - } - } - - def update(user: User, existing: Organization, form: OrganizationForm): Organization = { - val errors = validate(form, Some(existing)) - assert(errors.isEmpty, errors.map(_.message).mkString("\n")) - - db.withConnection { implicit c => - SQL(UpdateQuery).on( - "guid" -> existing.guid, - "name" -> form.name.trim, - "key" -> form.key.getOrElse(UrlKey.generate(form.name)).trim, - "namespace" -> form.namespace.trim, - "visibility" -> form.visibility.toString, - "user_guid" -> user.guid - ).execute() - } - - // TODO: Figure out how we want to handle domains. Best option - // might be to remove domains from organization_form - - findByGuid(Authorization.All, existing.guid).getOrElse { - sys.error("Failed to update org") - } - } - - private def create(implicit c: java.sql.Connection, user: User, form: OrganizationForm): Organization = { - val errors = validate(form) - assert(errors.isEmpty, errors.map(_.message).mkString("\n")) - - val org = Organization( - guid = UUID.randomUUID, - key = form.key.getOrElse(UrlKey.generate(form.name)).trim, - name = form.name.trim, - namespace = form.namespace.trim, - visibility = form.visibility, - domains = form.domains.getOrElse(Nil).map(Domain(_)), - audit = Audit( - createdAt = DateTime.now, - createdBy = ReferenceGuid(user.guid), - updatedAt = DateTime.now, - updatedBy = ReferenceGuid(user.guid) - ) - ) - - SQL(InsertQuery).on( - "guid" -> org.guid, - "name" -> org.name, - "namespace" -> org.namespace, - "visibility" -> org.visibility.toString, - "key" -> org.key, - "user_guid" -> user.guid - ).execute() - - org.domains.foreach { domain => - organizationDomainsDao.create(c, user, org, domain.name) - } - - org - } - - def softDelete(deletedBy: User, org: Organization): Unit = { - dbHelpers.delete(deletedBy, org.guid) - } - - def findByGuid(authorization: Authorization, guid: UUID): Option[Organization] = { - findAll(authorization, guid = Some(guid), limit = Some(1)).headOption - } - - def findByKey(authorization: Authorization, orgKey: String): Option[Organization] = { - findAll(authorization, key = Some(orgKey), limit = Some(1)).headOption - } - - def findAll( - authorization: Authorization, - guid: Option[UUID] = None, - guids: Option[Seq[UUID]] = None, - userGuid: Option[UUID] = None, - applicationGuid: Option[UUID] = None, - key: Option[String] = None, - name: Option[String] = None, - namespace: Option[String] = None, - isDeleted: Option[Boolean] = Some(false), - deletedAtBefore: Option[DateTime] = None, - limit: Option[Long], - offset: Long = 0 - ): Seq[Organization] = { - db.withConnection { implicit c => - authorization.organizationFilter(BaseQuery, "organizations.guid"). - equals("organizations.guid", guid). - optionalIn("organizations.guid", guids). - equals("key", key). - and( - userGuid.map { _ => - "organizations.guid in (select organization_guid from memberships where deleted_at is null and user_guid = {user_guid}::uuid)" - } - ).bind("user_guid", userGuid). - and( - applicationGuid.map { _ => - "organizations.guid in (select organization_guid from applications where deleted_at is null and guid = {application_guid}::uuid)" - } - ).bind("application_guid", applicationGuid). - and( - name.map { _ => - "lower(trim(name)) = lower(trim({name}))" - } - ).bind("name", name). - and( - namespace.map { _ => - "namespace = lower(trim({namespace}))" - } - ).bind("namespace", namespace). - and(deletedAtBefore.map { _ => - "deleted_at < {deleted_at_before}::timestamptz" - }).bind("deleted_at_before", deletedAtBefore). - and(isDeleted.map(Filters.isDeleted("organizations", _))). - orderBy("lower(name), created_at"). - optionalLimit(limit). - offset(offset). - as(parser.*) - } - } - - private val parser: RowParser[Organization] = { - import org.joda.time.DateTime - - SqlParser.get[UUID]("guid") ~ - SqlParser.str("key") ~ - SqlParser.str("name") ~ - SqlParser.str("namespace") ~ - SqlParser.str("visibility") ~ - SqlParser.str("domains") ~ - SqlParser.get[DateTime]("created_at") ~ - SqlParser.get[UUID]("created_by_guid") ~ - SqlParser.get[DateTime]("updated_at") ~ - SqlParser.get[UUID]("updated_by_guid") map { - case guid ~ key ~ name ~ namespace ~ visibility ~ domains ~ createdAt ~ createdByGuid ~ updatedAt ~ updatedByGuid => { - Organization( - guid = guid, - key = key, - name = name, - namespace = namespace, - visibility = Visibility.apply(visibility), - domains = Json.parse(domains).as[Seq[String]].map { n => Domain(name = n) }, - audit = Audit( - createdAt = createdAt, - createdBy = ReferenceGuid(createdByGuid), - updatedAt = updatedAt, - updatedBy = ReferenceGuid(updatedByGuid), - ) - ) - } - } - } -} diff --git a/api/app/db/SubscriptionsDao.scala b/api/app/db/SubscriptionsDao.scala index eb616b729..c3818d80a 100644 --- a/api/app/db/SubscriptionsDao.scala +++ b/api/app/db/SubscriptionsDao.scala @@ -32,7 +32,7 @@ class SubscriptionsDao @Inject() ( private val dbHelpers = DbHelpers(db, "subscriptions") // TODO: resolve cicrular dependency - private def organizationsDao = injector.instanceOf[OrganizationsDao] + private def organizationsDao = injector.instanceOf[InternalOrganizationsDao] private def subscriptionsDao = injector.instanceOf[SubscriptionsDao] private def usersDao = injector.instanceOf[UsersDao] diff --git a/api/app/db/VersionValidator.scala b/api/app/db/VersionValidator.scala index 5b74e46c0..ec48a3778 100644 --- a/api/app/db/VersionValidator.scala +++ b/api/app/db/VersionValidator.scala @@ -1,16 +1,17 @@ package db +import io.apibuilder.api.v0.models.User + import javax.inject.Inject -import io.apibuilder.api.v0.models.{Organization, User} class VersionValidator @Inject() ( - applicationsDao: ApplicationsDao, - membershipsDao: MembershipsDao + applicationsDao: InternalApplicationsDao, + membershipsDao: MembershipsDao ) { def validate( user: User, - org: Organization, + org: InternalOrganization, newApplicationKey: String, existingApplicationKey: Option[String] = None ): Seq[String] = { @@ -19,8 +20,8 @@ class VersionValidator @Inject() ( authErrors ++ keyErrors } - private def validateAuthorization(user: User, org: Organization): Seq[String] = { - if (membershipsDao.isUserMember(user, org)) { + private def validateAuthorization(user: User, org: InternalOrganization): Seq[String] = { + if (membershipsDao.isUserMember(user, OrganizationReference(org))) { Nil } else { Seq("You must be a member of this organization to update applications") @@ -28,7 +29,7 @@ class VersionValidator @Inject() ( } private def validateKey( - org: Organization, + org: InternalOrganization, newApplicationKey: String, existingApplicationKey: Option[String] ): Seq[String] = { diff --git a/api/app/db/VersionsDao.scala b/api/app/db/VersionsDao.scala index b8d2863b6..7c51ad461 100644 --- a/api/app/db/VersionsDao.scala +++ b/api/app/db/VersionsDao.scala @@ -33,13 +33,13 @@ case class InternalVersion( ) class VersionsDao @Inject() ( - @NamedDatabase("default") db: Database, - applicationsDao: ApplicationsDao, - originalsDao: OriginalsDao, - tasksDao: InternalTasksDao, - usersDao: UsersDao, - organizationsDao: OrganizationsDao, - versionsModel: VersionsModel + @NamedDatabase("default") db: Database, + applicationsDao: InternalApplicationsDao, + originalsDao: OriginalsDao, + tasksDao: InternalTasksDao, + usersDao: UsersDao, + organizationsDao: InternalOrganizationsDao, + versionsModel: VersionsModel ) extends ValidatedHelpers { private val logger: Logger = Logger(this.getClass) @@ -317,7 +317,7 @@ class VersionsDao @Inject() ( applicationsDao.findByGuid(Authorization.All, guid).toValidNec(s"Cannot find application where guid = '$guid'") } - private def validateOrg(guid: UUID): ValidatedNec[String, Organization] = { + private def validateOrg(guid: UUID): ValidatedNec[String, InternalOrganization] = { organizationsDao.findByGuid(Authorization.All, guid).toValidNec(s"Cannot find organization where guid = '$guid'") } @@ -348,7 +348,7 @@ class VersionsDao @Inject() ( ).mapN { case ((org, app), original) => val serviceConfig = ServiceConfiguration( orgKey = org.key, - orgNamespace = org.namespace, + orgNamespace = org.db.namespace, version = versionName ) logger.info(s"Migrating ${org.key}/${app.key}/$versionName versionGuid[$versionGuid] to latest API Builder spec version[${MigrateVersion.ServiceVersionNumber}] (with serviceConfig=$serviceConfig)") diff --git a/api/app/db/WatchesDao.scala b/api/app/db/WatchesDao.scala index 5fa91852b..dae1da67d 100644 --- a/api/app/db/WatchesDao.scala +++ b/api/app/db/WatchesDao.scala @@ -25,9 +25,9 @@ case class InternalWatch( ) @Singleton class WatchesDao @Inject() ( - @NamedDatabase("default") db: Database, - applicationsDao: ApplicationsDao, - usersDao: UsersDao + @NamedDatabase("default") db: Database, + applicationsDao: InternalApplicationsDao, + usersDao: UsersDao ) { private val dbHelpers = DbHelpers(db, "watches") diff --git a/api/app/lib/Emails.scala b/api/app/lib/Emails.scala index a28d055e8..5b8b37ad1 100644 --- a/api/app/lib/Emails.scala +++ b/api/app/lib/Emails.scala @@ -1,6 +1,6 @@ package lib -import db.{ApplicationsDao, Authorization, InternalApplication, MembershipsDao, SubscriptionsDao} +import db._ import io.apibuilder.api.v0.models._ import models.SubscriptionModel import play.api.Logging @@ -32,22 +32,23 @@ object Emails { @Singleton class Emails @Inject() ( email: EmailUtil, - applicationsDao: ApplicationsDao, + applicationsDao: InternalApplicationsDao, membershipsDao: MembershipsDao, subscriptionsDao: SubscriptionsDao, subscriptionModel: SubscriptionModel, ) extends Logging { def deliver( - context: Emails.Context, - org: Organization, - publication: Publication, - subject: String, - body: String - ) ( - implicit filter: Subscription => Boolean = { _ => true } - ): Unit = { - eachSubscription(context, org, publication, { subscription => + context: Emails.Context, + org: Organization, + publication: Publication, + subject: String, + body: String + ) ( + implicit filter: Subscription => Boolean = { _ => true } + ): Unit = { + + eachSubscription(context, OrganizationReference(org), publication, { subscription => val result = filter(subscription) // TODO: Should we use filter? email.sendHtml( to = Person(subscription.user), @@ -58,10 +59,10 @@ class Emails @Inject() ( } private def eachSubscription( - context: Emails.Context, - organization: Organization, - publication: Publication, - f: Subscription => Unit + context: Emails.Context, + organization: OrganizationReference, + publication: Publication, + f: Subscription => Unit ): Unit = { Pager.eachPage[Subscription] { offset => subscriptionModel.toModels( @@ -85,7 +86,7 @@ class Emails @Inject() ( private[lib] def isAuthorized( context: Emails.Context, - organization: Organization, + organization: OrganizationReference, user: User ): Boolean = { isAuthorized( diff --git a/api/app/models/ApplicationsModel.scala b/api/app/models/ApplicationsModel.scala index b80ed9c27..29f750da7 100644 --- a/api/app/models/ApplicationsModel.scala +++ b/api/app/models/ApplicationsModel.scala @@ -1,13 +1,13 @@ package models -import db.{Authorization, InternalApplication, OrganizationsDao} +import db.{Authorization, InternalApplication, InternalOrganizationsDao} import io.apibuilder.api.v0.models.Application import io.apibuilder.common.v0.models.Reference import javax.inject.Inject class ApplicationsModel @Inject()( - organizationsDao: OrganizationsDao, + organizationsDao: InternalOrganizationsDao, ) { def toModel(application: InternalApplication): Option[Application] = { toModels(Seq(application)).headOption diff --git a/api/app/models/MembershipRequestsModel.scala b/api/app/models/MembershipRequestsModel.scala index 1e83e6f97..90d6ce8d7 100644 --- a/api/app/models/MembershipRequestsModel.scala +++ b/api/app/models/MembershipRequestsModel.scala @@ -1,15 +1,16 @@ package models import cats.implicits._ -import db.{Authorization, InternalMembershipRequest, OrganizationsDao, UsersDao} +import db.{Authorization, InternalMembershipRequest, InternalOrganizationsDao, UsersDao} import io.apibuilder.api.v0.models.MembershipRequest import javax.inject.Inject class MembershipRequestsModel @Inject() ( - organizationsDao: OrganizationsDao, - usersDao: UsersDao - ) { + usersDao: UsersDao, + orgModel: OrganizationsModel +) { + def toModel(mr: InternalMembershipRequest): Option[MembershipRequest] = { toModels(Seq(mr)).headOption } @@ -19,11 +20,8 @@ class MembershipRequestsModel @Inject() ( guids = Some(requests.map(_.userGuid)) ).map { u => u.guid -> u }.toMap - val orgs = organizationsDao.findAll( - Authorization.All, - guids = Some(requests.map(_.organizationGuid)), - limit = None - ).map { o => o.guid -> o }.toMap + val orgs = orgModel.toModelByGuids(Authorization.All, requests.map(_.organizationGuid)) + .map { o => o.guid -> o }.toMap requests.flatMap { r => (users.get(r.userGuid), orgs.get(r.organizationGuid)).mapN { case (user, org) => diff --git a/api/app/models/MembershipsModel.scala b/api/app/models/MembershipsModel.scala index c7b749158..50ce26b69 100644 --- a/api/app/models/MembershipsModel.scala +++ b/api/app/models/MembershipsModel.scala @@ -1,15 +1,16 @@ package models import cats.implicits._ -import db.{Authorization, InternalMembership, OrganizationsDao, UsersDao} +import db.{Authorization, InternalMembership, UsersDao} import io.apibuilder.api.v0.models.Membership import javax.inject.Inject class MembershipsModel @Inject()( - organizationsDao: OrganizationsDao, - usersDao: UsersDao - ) { + usersDao: UsersDao, + orgModel: OrganizationsModel +) { + def toModel(mr: InternalMembership): Option[Membership] = { toModels(Seq(mr)).headOption } @@ -19,11 +20,8 @@ class MembershipsModel @Inject()( guids = Some(s.map(_.userGuid)) ).map { u => u.guid -> u }.toMap - val orgs = organizationsDao.findAll( - Authorization.All, - guids = Some(s.map(_.organizationGuid)), - limit = None - ).map { o => o.guid -> o }.toMap + val orgs = orgModel.toModelByGuids(Authorization.All, s.map(_.organizationGuid)) + .map { o => o.guid -> o }.toMap s.flatMap { r => (users.get(r.userGuid), orgs.get(r.organizationGuid)).mapN { case (user, org) => diff --git a/api/app/models/OrganizationsModel.scala b/api/app/models/OrganizationsModel.scala new file mode 100644 index 000000000..26a4156c7 --- /dev/null +++ b/api/app/models/OrganizationsModel.scala @@ -0,0 +1,48 @@ +package models + +import db.{Authorization, InternalOrganization, InternalOrganizationsDao, OrganizationDomainsDao} +import io.apibuilder.api.v0.models.Organization +import io.apibuilder.common.v0.models.{Audit, ReferenceGuid} + +import java.util.UUID +import javax.inject.Inject + +class OrganizationsModel @Inject()( + orgDao: InternalOrganizationsDao, + domainsDao: OrganizationDomainsDao, +) { + def toModelByGuids(auth: Authorization, guids: Seq[UUID]): Seq[Organization] = { + toModels(orgDao.findAll( + auth, + guids = Some(guids), + limit = None + )) + } + + def toModel(v: InternalOrganization): Organization = { + toModels(Seq(v)).head + } + + def toModels(orgs: Seq[InternalOrganization]): Seq[Organization] = { + val domains = domainsDao.findAll( + organizationGuids = Some(orgs.map(_.guid)), + ).groupBy(_.organizationGuid) + + orgs.map { org => + Organization( + guid = org.guid, + name = org.name, + key = org.key, + namespace = org.db.namespace, + visibility = org.visibility, + domains = domains.getOrElse(org.guid, Nil).map(_.domain), + audit = Audit( + createdAt = org.db.createdAt, + createdBy = ReferenceGuid(org.db.createdByGuid), + updatedAt = org.db.updatedAt, + updatedBy = ReferenceGuid(org.db.updatedByGuid), + ) + ) + } + } +} \ No newline at end of file diff --git a/api/app/models/SubscriptionModel.scala b/api/app/models/SubscriptionModel.scala index a739b44c4..97ee288d8 100644 --- a/api/app/models/SubscriptionModel.scala +++ b/api/app/models/SubscriptionModel.scala @@ -1,15 +1,16 @@ package models import cats.implicits._ -import db.{Authorization, InternalSubscription, OrganizationsDao, UsersDao} +import db.{Authorization, InternalSubscription, UsersDao} import io.apibuilder.api.v0.models.Subscription import javax.inject.Inject class SubscriptionModel @Inject()( - organizationsDao: OrganizationsDao, - usersDao: UsersDao - ) { + usersDao: UsersDao, + orgModel: OrganizationsModel, +) { + def toModel(mr: InternalSubscription): Option[Subscription] = { toModels(Seq(mr)).headOption } @@ -19,11 +20,8 @@ class SubscriptionModel @Inject()( guids = Some(subscriptions.map(_.userGuid)) ).map { u => u.guid -> u }.toMap - val orgs = organizationsDao.findAll( - Authorization.All, - guids = Some(subscriptions.map(_.organizationGuid)), - limit = None - ).map { o => o.guid -> o }.toMap + val orgs = orgModel.toModelByGuids(Authorization.All, subscriptions.map(_.organizationGuid)) + .map { o => o.guid -> o }.toMap subscriptions.flatMap { s => (users.get(s.userGuid), orgs.get(s.organizationGuid)).mapN { case (user, org) => diff --git a/api/app/models/VersionsModel.scala b/api/app/models/VersionsModel.scala index ed2f015ec..55743a876 100644 --- a/api/app/models/VersionsModel.scala +++ b/api/app/models/VersionsModel.scala @@ -4,7 +4,7 @@ import builder.api_json.upgrades.ServiceParser import cats.data.Validated.{Invalid, Valid} import cats.data.ValidatedNec import cats.implicits._ -import db.{ApplicationsDao, Authorization, InternalVersion, OrganizationsDao} +import db.{InternalApplicationsDao, Authorization, InternalVersion, InternalOrganizationsDao} import io.apibuilder.api.v0.models.Version import io.apibuilder.common.v0.models.Reference import io.apibuilder.spec.v0.models.Service @@ -12,9 +12,9 @@ import io.apibuilder.spec.v0.models.Service import javax.inject.Inject class VersionsModel @Inject()( - applicationsDao: ApplicationsDao, - organizationsDao: OrganizationsDao, - serviceParser: ServiceParser, + applicationsDao: InternalApplicationsDao, + organizationsDao: InternalOrganizationsDao, + serviceParser: ServiceParser, ) { def toModel(v: InternalVersion): Option[Version] = { toModels(Seq(v)).headOption diff --git a/api/app/models/WatchesModel.scala b/api/app/models/WatchesModel.scala index 37f70d5c5..f8dfa870c 100644 --- a/api/app/models/WatchesModel.scala +++ b/api/app/models/WatchesModel.scala @@ -1,16 +1,17 @@ package models import cats.implicits._ -import db.{ApplicationsDao, Authorization, InternalWatch, OrganizationsDao, UsersDao} +import db.{InternalApplicationsDao, Authorization, InternalWatch, InternalOrganizationsDao, UsersDao} import io.apibuilder.api.v0.models.Watch import javax.inject.Inject class WatchesModel @Inject()( - organizationsDao: OrganizationsDao, - applicationsDao: ApplicationsDao, - applicationsModel: ApplicationsModel, - usersDao: UsersDao + organizationsDao: InternalOrganizationsDao, + applicationsDao: InternalApplicationsDao, + applicationsModel: ApplicationsModel, + usersDao: UsersDao, + organizationsModel: OrganizationsModel, ) { def toModel(watch: InternalWatch): Option[Watch] = { toModels(Seq(watch)).headOption @@ -29,11 +30,11 @@ class WatchesModel @Inject()( ) ).map { o => o.guid -> o }.toMap - val organizations = organizationsDao.findAll( + val organizations = organizationsModel.toModels(organizationsDao.findAll( Authorization.All, guids = Some(applications.values.map(_.organization.guid).toSeq.distinct), limit = None - ).map { o => o.guid -> o }.toMap + )).map { o => o.guid -> o }.toMap watches.flatMap { w => (users.get(w.userGuid), diff --git a/api/app/processor/DiffVersionProcessor.scala b/api/app/processor/DiffVersionProcessor.scala index d9ebdb803..c6e11659d 100644 --- a/api/app/processor/DiffVersionProcessor.scala +++ b/api/app/processor/DiffVersionProcessor.scala @@ -7,23 +7,24 @@ import io.apibuilder.api.v0.models._ import io.apibuilder.task.v0.models.json._ import io.apibuilder.task.v0.models.{DiffVersionData, TaskType} import lib.{AppConfig, Emails, ServiceDiff} -import models.VersionsModel +import models.{OrganizationsModel, VersionsModel} import play.twirl.api.Html import java.util.UUID import javax.inject.Inject class DiffVersionProcessor @Inject()( - args: TaskProcessorArgs, - appConfig: AppConfig, - usersDao: UsersDao, - applicationsDao: ApplicationsDao, - organizationsDao: OrganizationsDao, - emails: Emails, - changesDao: ChangesDao, - versionsDao: VersionsDao, - versionsModel: VersionsModel, - watchesDao: WatchesDao, + args: TaskProcessorArgs, + appConfig: AppConfig, + usersDao: UsersDao, + applicationsDao: InternalApplicationsDao, + organizationsDao: InternalOrganizationsDao, + emails: Emails, + changesDao: ChangesDao, + versionsDao: VersionsDao, + versionsModel: VersionsModel, + watchesDao: WatchesDao, + orgModel: OrganizationsModel ) extends TaskProcessorWithData[DiffVersionData](args, TaskType.DiffVersion) { override def processRecord(id: String, data: DiffVersionData): ValidatedNec[String, Unit] = { @@ -69,7 +70,7 @@ class DiffVersionProcessor @Inject()( ) { (org, application, breakingDiffs, nonBreakingDiffs) => views.html.emails.versionUpserted( appConfig, - org, + orgModel.toModel(org), application, version, breakingDiffs = breakingDiffs, @@ -92,7 +93,7 @@ class DiffVersionProcessor @Inject()( ) { (org, application, breakingDiffs, nonBreakingDiffs) => views.html.emails.versionUpserted( appConfig, - org, + orgModel.toModel(org), application, version, breakingDiffs = breakingDiffs, @@ -107,7 +108,7 @@ class DiffVersionProcessor @Inject()( version: Version, diffs: Seq[Diff], )( - generateBody: (Organization, InternalApplication, Seq[Diff], Seq[Diff]) => Html, + generateBody: (InternalOrganization, InternalApplication, Seq[Diff], Seq[Diff]) => Html, ): Unit = { val (breakingDiffs, nonBreakingDiffs) = diffs.partition { case _: DiffBreaking => true @@ -119,7 +120,7 @@ class DiffVersionProcessor @Inject()( organizationsDao.findAll(Authorization.All, applicationGuid = Some(application.guid), limit = Some(1)).foreach { org => emails.deliver( context = Emails.Context.Application(application), - org = org, + org = orgModel.toModel(org), publication = publication, subject = s"${org.name}/${application.name}:${version.version} Updated", body = generateBody(org, application, breakingDiffs, nonBreakingDiffs).toString diff --git a/api/app/processor/EmailProcessor.scala b/api/app/processor/EmailProcessor.scala index 8caaaed94..63f791312 100644 --- a/api/app/processor/EmailProcessor.scala +++ b/api/app/processor/EmailProcessor.scala @@ -2,13 +2,13 @@ package processor import cats.data.ValidatedNec import cats.implicits._ -import db.{Authorization, InternalTasksDao, OrganizationsDao, UsersDao} +import db.{Authorization, InternalOrganizationsDao, InternalTasksDao, UsersDao} import io.apibuilder.api.v0.models.Publication import io.apibuilder.common.v0.models.MembershipRole import io.apibuilder.task.v0.models._ import io.apibuilder.task.v0.models.json._ import lib.{AppConfig, EmailUtil, Emails, Person} -import models.{MembershipRequestsModel, MembershipsModel} +import models.{MembershipRequestsModel, MembershipsModel, OrganizationsModel} import play.api.libs.json.Json import java.sql.Connection @@ -16,8 +16,8 @@ import java.util.UUID import javax.inject.Inject class EmailProcessorQueue @Inject() ( - internalTasksDao: InternalTasksDao - ) { + internalTasksDao: InternalTasksDao, +) { def queueWithConnection(c: Connection, data: EmailData): Unit = { val dataJson = Json.toJson(data) internalTasksDao.queueWithConnection(c, TaskType.Email, id = Json.asciiStringify(dataJson), data = dataJson) @@ -25,19 +25,20 @@ class EmailProcessorQueue @Inject() ( } class EmailProcessor @Inject()( - args: TaskProcessorArgs, - appConfig: AppConfig, - applicationsDao: db.ApplicationsDao, - email: EmailUtil, - emails: Emails, - emailVerificationsDao: db.EmailVerificationsDao, - membershipsDao: db.MembershipsDao, - membershipsModel: MembershipsModel, - membershipRequestsDao: db.MembershipRequestsDao, - membershipRequestsModel: MembershipRequestsModel, - organizationsDao: OrganizationsDao, - passwordResetRequestsDao: db.PasswordResetRequestsDao, - usersDao: UsersDao, + args: TaskProcessorArgs, + appConfig: AppConfig, + applicationsDao: db.InternalApplicationsDao, + email: EmailUtil, + emails: Emails, + emailVerificationsDao: db.EmailVerificationsDao, + membershipsDao: db.MembershipsDao, + membershipsModel: MembershipsModel, + membershipRequestsDao: db.MembershipRequestsDao, + membershipRequestsModel: MembershipRequestsModel, + organizationsDao: InternalOrganizationsDao, + passwordResetRequestsDao: db.PasswordResetRequestsDao, + usersDao: UsersDao, + orgModel: OrganizationsModel, ) extends TaskProcessorWithData[EmailData](args, TaskType.Email) { override def processRecord(id: String, data: EmailData): ValidatedNec[String, Unit] = { @@ -58,7 +59,7 @@ class EmailProcessor @Inject()( organizationsDao.findByGuid(Authorization.All, application.guid).foreach { org => emails.deliver( context = Emails.Context.OrganizationMember, - org = org, + org = orgModel.toModel(org), publication = Publication.ApplicationsCreate, subject = s"${org.name}: New Application Created - ${application.name}", body = views.html.emails.applicationCreated(appConfig, org, application).toString diff --git a/api/app/processor/IndexApplicationProcessor.scala b/api/app/processor/IndexApplicationProcessor.scala index 1ffd84eb2..1713b41ef 100644 --- a/api/app/processor/IndexApplicationProcessor.scala +++ b/api/app/processor/IndexApplicationProcessor.scala @@ -1,9 +1,9 @@ package processor -import cats.implicits._ import cats.data.ValidatedNec -import db.{ApplicationsDao, Authorization, InternalApplication, ItemsDao, OrganizationsDao} -import io.apibuilder.api.v0.models.{Application, ApplicationSummary, Organization} +import cats.implicits._ +import db._ +import io.apibuilder.api.v0.models.ApplicationSummary import io.apibuilder.common.v0.models.Reference import io.apibuilder.task.v0.models.TaskType @@ -12,10 +12,10 @@ import javax.inject.Inject class IndexApplicationProcessor @Inject()( - args: TaskProcessorArgs, - applicationsDao: ApplicationsDao, - itemsDao: ItemsDao, - organizationsDao: OrganizationsDao + args: TaskProcessorArgs, + applicationsDao: InternalApplicationsDao, + itemsDao: ItemsDao, + organizationsDao: InternalOrganizationsDao ) extends TaskProcessorWithGuid(args, TaskType.IndexApplication) { override def processRecord(applicationGuid: UUID): ValidatedNec[String, Unit] = { @@ -41,7 +41,7 @@ class IndexApplicationProcessor @Inject()( ().validNec } - private def getInfo(applicationGuid: UUID): Option[(Organization, InternalApplication)] = { + private def getInfo(applicationGuid: UUID): Option[(InternalOrganization, InternalApplication)] = { applicationsDao.findByGuid(Authorization.All, applicationGuid).flatMap { application => organizationsDao.findAll(Authorization.All, applicationGuid = Some(application.guid), limit = Some(1)).headOption.map { org => (org, application) diff --git a/api/app/processor/UserCreatedProcessor.scala b/api/app/processor/UserCreatedProcessor.scala index ccd817184..adcff44e1 100644 --- a/api/app/processor/UserCreatedProcessor.scala +++ b/api/app/processor/UserCreatedProcessor.scala @@ -11,11 +11,11 @@ import javax.inject.Inject class UserCreatedProcessor @Inject()( - args: TaskProcessorArgs, - usersDao: UsersDao, - organizationsDao: OrganizationsDao, - membershipRequestsDao: MembershipRequestsDao, - emailVerificationsDao: EmailVerificationsDao, + args: TaskProcessorArgs, + usersDao: UsersDao, + organizationsDao: InternalOrganizationsDao, + membershipRequestsDao: MembershipRequestsDao, + emailVerificationsDao: EmailVerificationsDao, ) extends TaskProcessorWithGuid(args, TaskType.UserCreated) { override def processRecord(userGuid: UUID): ValidatedNec[String, Unit] = { diff --git a/api/app/services/BatchDownloadApplicationsService.scala b/api/app/services/BatchDownloadApplicationsService.scala index 4bfde3f77..15798085f 100644 --- a/api/app/services/BatchDownloadApplicationsService.scala +++ b/api/app/services/BatchDownloadApplicationsService.scala @@ -1,7 +1,7 @@ package services import cats.data.ValidatedNec -import db.{ApplicationsDao, Authorization, VersionsDao} +import db.{InternalApplicationsDao, Authorization, VersionsDao} import cats.implicits._ import io.apibuilder.api.v0.models.{BatchDownloadApplicationForm, BatchDownloadApplications, BatchDownloadApplicationsForm, Version} import models.VersionsModel @@ -9,9 +9,9 @@ import models.VersionsModel import javax.inject.Inject class BatchDownloadApplicationsService @Inject() ( - applicationsDao: ApplicationsDao, - versionsDao: VersionsDao, - versionsModel: VersionsModel, + applicationsDao: InternalApplicationsDao, + versionsDao: VersionsDao, + versionsModel: VersionsModel, ) { def process( diff --git a/api/app/services/EmailVerificationsService.scala b/api/app/services/EmailVerificationsService.scala index c943add90..fc74c7687 100644 --- a/api/app/services/EmailVerificationsService.scala +++ b/api/app/services/EmailVerificationsService.scala @@ -15,7 +15,7 @@ class EmailVerificationsService @Inject()( emailVerificationConfirmationsDao: EmailVerificationConfirmationsDao, membershipRequestsDao: MembershipRequestsDao, membershipRequestsModel: MembershipRequestsModel, - organizationsDao: OrganizationsDao + organizationsDao: InternalOrganizationsDao ) { def confirm(user: Option[User], verification: EmailVerification): ValidatedNec[String, Unit] = { diff --git a/api/app/views/emails/applicationCreated.scala.html b/api/app/views/emails/applicationCreated.scala.html index 5ee077a42..6534f0fff 100644 --- a/api/app/views/emails/applicationCreated.scala.html +++ b/api/app/views/emails/applicationCreated.scala.html @@ -1,6 +1,6 @@ @( appConfig: lib.AppConfig, - org: io.apibuilder.api.v0.models.Organization, + org: db.InternalOrganization, application: db.InternalApplication ) diff --git a/api/app/views/emails/membershipRequestAccepted.scala.html b/api/app/views/emails/membershipRequestAccepted.scala.html index cfa538153..62fa2c96e 100644 --- a/api/app/views/emails/membershipRequestAccepted.scala.html +++ b/api/app/views/emails/membershipRequestAccepted.scala.html @@ -1,6 +1,6 @@ @import io.apibuilder.common.v0.models.MembershipRole @( - org: io.apibuilder.api.v0.models.Organization, + org: db.InternalOrganization, user: io.apibuilder.api.v0.models.User, role: MembershipRole ) diff --git a/api/app/views/emails/membershipRequestDeclined.scala.html b/api/app/views/emails/membershipRequestDeclined.scala.html index 877b7be25..e0de34e89 100644 --- a/api/app/views/emails/membershipRequestDeclined.scala.html +++ b/api/app/views/emails/membershipRequestDeclined.scala.html @@ -1,5 +1,5 @@ @( - org: io.apibuilder.api.v0.models.Organization, + org: db.InternalOrganization, user: io.apibuilder.api.v0.models.User ) diff --git a/api/test/controllers/ApplicationsSpec.scala b/api/test/controllers/ApplicationsSpec.scala index 3b797faeb..49a5bd489 100644 --- a/api/test/controllers/ApplicationsSpec.scala +++ b/api/test/controllers/ApplicationsSpec.scala @@ -1,8 +1,9 @@ package controllers +import db.InternalOrganization import io.apibuilder.api.v0.models.{Application, MoveForm, Organization} -import java.util.UUID +import java.util.UUID import org.scalatestplus.play.guice.GuiceOneServerPerSuite import org.scalatestplus.play.PlaySpec @@ -10,7 +11,7 @@ class ApplicationsSpec extends PlaySpec with MockClient with GuiceOneServerPerSu import scala.concurrent.ExecutionContext.Implicits.global - private def getByKey(org: Organization, key: String): Option[Application] = { + private def getByKey(org: InternalOrganization, key: String): Option[Application] = { await(client.applications.get(org.key, key = Some(key), limit = 1)).headOption } diff --git a/api/test/controllers/MockClient.scala b/api/test/controllers/MockClient.scala index 7de2cccb8..fd4a71ed3 100644 --- a/api/test/controllers/MockClient.scala +++ b/api/test/controllers/MockClient.scala @@ -1,7 +1,7 @@ package controllers import java.util.UUID -import db.{Authorization, InternalApplication} +import db.{Authorization, InternalApplication, InternalOrganization} import io.apibuilder.api.v0.Client import io.apibuilder.api.v0.errors.UnitResponse import io.apibuilder.api.v0.models._ @@ -106,7 +106,7 @@ trait MockClient extends db.Helpers } def createSubscriptionForm( - org: Organization = createOrganization(), + org: InternalOrganization = createOrganization(), user: User = createUser() ) = SubscriptionForm( organizationKey = org.key, diff --git a/api/test/controllers/WatchesSpec.scala b/api/test/controllers/WatchesSpec.scala index 2243fb2a4..09342ddaf 100644 --- a/api/test/controllers/WatchesSpec.scala +++ b/api/test/controllers/WatchesSpec.scala @@ -1,6 +1,6 @@ package controllers -import db.InternalApplication +import db.{InternalApplication, InternalOrganization} import io.apibuilder.api.v0.models.{Organization, User, Watch, WatchForm} import org.scalatestplus.play.PlaySpec import org.scalatestplus.play.guice.GuiceOneServerPerSuite @@ -9,7 +9,7 @@ class WatchesSpec extends PlaySpec with MockClient with GuiceOneServerPerSuite { import scala.concurrent.ExecutionContext.Implicits.global - lazy val org: Organization = createOrganization() + private lazy val org: InternalOrganization = createOrganization() def createWatch( form: WatchForm = createWatchForm() @@ -19,7 +19,7 @@ class WatchesSpec extends PlaySpec with MockClient with GuiceOneServerPerSuite { def createWatchForm( user: User = createUser(), - org: Organization = createOrganization(), + org: InternalOrganization = createOrganization(), application: Option[InternalApplication] = None ): WatchForm = WatchForm( userGuid = user.guid, diff --git a/api/test/db/ChangesDaoSpec.scala b/api/test/db/ChangesDaoSpec.scala index 6412def25..b8bcdab20 100644 --- a/api/test/db/ChangesDaoSpec.scala +++ b/api/test/db/ChangesDaoSpec.scala @@ -17,7 +17,7 @@ class ChangesDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { private def createChange( description: String = "Breaking difference - " + UUID.randomUUID.toString, - org: Organization = createOrganization() + org: InternalOrganization = createOrganization() ): Change = { val app = createApplication(org = org) val fromVersion = createVersion(application = app, version = "1.0.0") diff --git a/api/test/db/EmailVerificationsDaoSpec.scala b/api/test/db/EmailVerificationsDaoSpec.scala index 28c55d31d..d9105c038 100644 --- a/api/test/db/EmailVerificationsDaoSpec.scala +++ b/api/test/db/EmailVerificationsDaoSpec.scala @@ -104,16 +104,16 @@ class EmailVerificationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with H userCreatedProcessor.processRecord(user.guid) userCreatedProcessor.processRecord(nonMatchingUser.guid) - membershipsDao.isUserMember(user, org) must be(false) - membershipsDao.isUserMember(nonMatchingUser, org) must be(false) + membershipsDao.isUserMember(user, org.reference) must be(false) + membershipsDao.isUserMember(nonMatchingUser, org.reference) must be(false) val verification = emailVerificationsDao.upsert(testUser, user, user.email) expectValid { service.confirm(Some(testUser), verification) } - membershipsDao.isUserMember(user, org) must be(true) - membershipsDao.isUserMember(nonMatchingUser, org) must be(false) + membershipsDao.isUserMember(user, org.reference) must be(true) + membershipsDao.isUserMember(nonMatchingUser, org.reference) must be(false) } } diff --git a/api/test/db/Helpers.scala b/api/test/db/Helpers.scala index 06d1768f7..406e8560a 100644 --- a/api/test/db/Helpers.scala +++ b/api/test/db/Helpers.scala @@ -29,13 +29,13 @@ trait Helpers extends util.Daos with RandomHelpers { } } - def upsertOrganization(name: String): Organization = { + def upsertOrganization(name: String): InternalOrganization = { organizationsDao.findAll(Authorization.All, name = Some(name), limit = Some(1)).headOption.getOrElse { createOrganization(name = Some(name)) } } - def upsertOrganizationByKey(key: String): Organization = { + def upsertOrganizationByKey(key: String): InternalOrganization = { organizationsDao.findByKey(Authorization.All, key).getOrElse { createOrganization(key = Some(key)) } @@ -47,7 +47,7 @@ trait Helpers extends util.Daos with RandomHelpers { key: Option[String] = None, namespace: Option[String] = None, visibility: Visibility = Visibility.Organization - ): Organization = { + ): InternalOrganization = { createOrganization( form = createOrganizationForm( name = name.getOrElse("z-test-org-" + UUID.randomUUID.toString), @@ -62,7 +62,7 @@ trait Helpers extends util.Daos with RandomHelpers { def createOrganization( form: OrganizationForm, createdBy: User - ): Organization = { + ): InternalOrganization = { organizationsDao.createWithAdministrator(createdBy, form) } @@ -81,7 +81,7 @@ trait Helpers extends util.Daos with RandomHelpers { ) def createApplication( - org: Organization = createOrganization(), + org: InternalOrganization = createOrganization(), form: ApplicationForm = createApplicationForm() ): InternalApplication = { applicationsDao.create(testUser, org, form) @@ -108,7 +108,7 @@ trait Helpers extends util.Daos with RandomHelpers { ) def upsertApplicationByOrganizationAndKey( - org: Organization, + org: InternalOrganization, key: String, ): InternalApplication = { applicationsDao.findByOrganizationKeyAndApplicationKey( @@ -129,7 +129,7 @@ trait Helpers extends util.Daos with RandomHelpers { } def createApplicationByKey( - org: Organization = testOrg, + org: InternalOrganization = testOrg, key: String = "test-" + UUID.randomUUID.toString, ): InternalApplication = { createApplication( @@ -156,7 +156,7 @@ trait Helpers extends util.Daos with RandomHelpers { } def createMembership( - org: Organization, + org: InternalOrganization, user: User = createRandomUser(), role: MembershipRole = MembershipRole.Admin ): InternalMembership = { @@ -165,13 +165,13 @@ trait Helpers extends util.Daos with RandomHelpers { ).get membershipRequestsDao.accept(testUser, request) - membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, org, user, role).getOrElse { + membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, org.reference, user, role).getOrElse { sys.error("membership could not be created") } } def createSubscription( - org: Organization, + org: InternalOrganization, user: User = createRandomUser(), publication: Publication = Publication.all.head ): InternalSubscription = { @@ -233,9 +233,9 @@ trait Helpers extends util.Daos with RandomHelpers { name = None ) - lazy val gilt: Organization = upsertOrganization("Gilt Test Org") + lazy val gilt: InternalOrganization = upsertOrganization("Gilt Test Org") - lazy val testOrg: Organization = upsertOrganization("Test Org %s".format(UUID.randomUUID)) + lazy val testOrg: InternalOrganization = upsertOrganization("Test Org %s".format(UUID.randomUUID)) lazy val testUser: User = createUser() diff --git a/api/test/db/ApplicationsDaoSpec.scala b/api/test/db/InternalApplicationsDaoSpec.scala similarity index 97% rename from api/test/db/ApplicationsDaoSpec.scala rename to api/test/db/InternalApplicationsDaoSpec.scala index 0517495de..10c3bbe69 100644 --- a/api/test/db/ApplicationsDaoSpec.scala +++ b/api/test/db/InternalApplicationsDaoSpec.scala @@ -8,7 +8,7 @@ import play.api.libs.json.Json import java.util.UUID -class ApplicationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { +class InternalApplicationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { private val Original = io.apibuilder.api.v0.models.Original( `type` = OriginalType.ApiJson, @@ -19,7 +19,7 @@ class ApplicationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Help private def upsertApplication( nameOption: Option[String] = None, - org: Organization = testOrg, + org: InternalOrganization = testOrg, visibility: Visibility = Visibility.Organization ): InternalApplication = { val n = nameOption.getOrElse("Test %s".format(UUID.randomUUID)) @@ -33,7 +33,7 @@ class ApplicationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Help } } - private def findByKey(org: Organization, key: String): Option[InternalApplication] = { + private def findByKey(org: InternalOrganization, key: String): Option[InternalApplication] = { applicationsDao.findAll(Authorization.All, orgKey = Some(org.key), key = Some(key), limit = Some(1)).headOption } diff --git a/api/test/db/OrganizationsDaoSpec.scala b/api/test/db/InternalOrganizationsDaoSpec.scala similarity index 90% rename from api/test/db/OrganizationsDaoSpec.scala rename to api/test/db/InternalOrganizationsDaoSpec.scala index 6c423d77a..d815b7002 100644 --- a/api/test/db/OrganizationsDaoSpec.scala +++ b/api/test/db/InternalOrganizationsDaoSpec.scala @@ -1,12 +1,13 @@ package db -import java.util.UUID - +import helpers.OrganizationHelpers import io.apibuilder.api.v0.models.{OrganizationForm, Visibility} import org.scalatestplus.play.PlaySpec import org.scalatestplus.play.guice.GuiceOneAppPerSuite -class OrganizationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { +import java.util.UUID + +class InternalOrganizationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with OrganizationHelpers { "create" in { gilt.name must be("Gilt Test Org") @@ -28,7 +29,7 @@ class OrganizationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Hel name = org.name, key = Some(org.key), visibility = org.visibility, - namespace = org.namespace + namespace = org.db.namespace ) "name" in { @@ -42,8 +43,8 @@ class OrganizationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Hel } "namespace" in { - val updated = organizationsDao.update(testUser, org, form.copy(namespace = org.namespace + "2")) - updated.namespace must be(org.namespace + "2") + val updated = organizationsDao.update(testUser, org, form.copy(namespace = org.db.namespace + "2")) + updated.db.namespace must be(org.db.namespace + "2") } "visibility" in { @@ -62,7 +63,7 @@ class OrganizationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Hel val org = organizationsDao.createWithAdministrator(user, createOrganizationForm(name = name)) org.name must be(name) - membershipsDao.isUserAdmin(user, org) must be(true) + membershipsDao.isUserAdmin(user, org.reference) must be(true) } "domain" must { @@ -85,9 +86,9 @@ class OrganizationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Hel "creates with domains" in { initialize() - org.domains.map(_.name).mkString(" ") must be(domains.mkString(" ")) + toModel(org).domains.map(_.name).mkString(" ") must be(domains.mkString(" ")) val fetched = organizationsDao.findByGuid(Authorization.All, org.guid).get - fetched.domains.map(_.name).sorted.mkString(" ") must be(domains.sorted.mkString(" ")) + toModel(fetched).domains.map(_.name).sorted.mkString(" ") must be(domains.sorted.mkString(" ")) } "defaults visibility to organization" in { @@ -134,10 +135,10 @@ class OrganizationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Hel "by namespace" in { initialize() - organizationsDao.findAll(Authorization.All, namespace = Some(org1.namespace), limit = None).map(_.guid) must be(Seq(org1.guid)) - organizationsDao.findAll(Authorization.All, namespace = Some(org1.namespace.toUpperCase), limit = None).map(_.guid) must be(Seq(org1.guid)) - organizationsDao.findAll(Authorization.All, namespace = Some(org1.namespace.toLowerCase), limit = None).map(_.guid) must be(Seq(org1.guid)) - organizationsDao.findAll(Authorization.All, namespace = Some(org2.namespace), limit = None).map(_.guid) must be(Seq(org2.guid)) + organizationsDao.findAll(Authorization.All, namespace = Some(org1.db.namespace), limit = None).map(_.guid) must be(Seq(org1.guid)) + organizationsDao.findAll(Authorization.All, namespace = Some(org1.db.namespace.toUpperCase), limit = None).map(_.guid) must be(Seq(org1.guid)) + organizationsDao.findAll(Authorization.All, namespace = Some(org1.db.namespace.toLowerCase), limit = None).map(_.guid) must be(Seq(org1.guid)) + organizationsDao.findAll(Authorization.All, namespace = Some(org2.db.namespace), limit = None).map(_.guid) must be(Seq(org2.guid)) organizationsDao.findAll(Authorization.All, namespace = Some(UUID.randomUUID.toString), limit = None) must be(Nil) } @@ -161,14 +162,14 @@ class OrganizationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Hel "validates name" in { organizationsDao.validate(createOrganizationForm(name = "this is a long name")) must be(Nil) organizationsDao.validate(createOrganizationForm(name = "a")).head.message must be("name must be at least 3 characters") - organizationsDao.validate(createOrganizationForm(name = gilt.name)).head.message must be("Org with this name already exists") + organizationsDao.validate(createOrganizationForm(name = gilt.name)).head.message must be("Org with the name 'Gilt Test Org' already exists") organizationsDao.validate(createOrganizationForm(name = gilt.name), Some(gilt)) must be(Nil) } "validates key" in { organizationsDao.validate(createOrganizationForm(name = UUID.randomUUID.toString, key = Some("a"))).head.message must be("Key must be at least 3 characters") - organizationsDao.validate(createOrganizationForm(name = UUID.randomUUID.toString, key = Some(gilt.key))).head.message must be("Org with this key already exists") + organizationsDao.validate(createOrganizationForm(name = UUID.randomUUID.toString, key = Some(gilt.key))).head.message must be("Org with the key 'gilt-test-org' already exists") organizationsDao.validate(createOrganizationForm(name = UUID.randomUUID.toString, key = Some(gilt.key)), Some(gilt)) must be(Nil) } diff --git a/api/test/db/ItemsDaoSpec.scala b/api/test/db/ItemsDaoSpec.scala index aae6fb20c..4f2b09a6d 100644 --- a/api/test/db/ItemsDaoSpec.scala +++ b/api/test/db/ItemsDaoSpec.scala @@ -10,7 +10,7 @@ import org.scalatestplus.play.guice.GuiceOneAppPerSuite class ItemsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { private def upsertItem( - org: Organization = createOrganization(), + org: InternalOrganization = createOrganization(), guid: UUID = UUID.randomUUID, label: String = "Test", description: Option[String] = None, @@ -73,7 +73,7 @@ class ItemsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { "orgKey" in { val org = createOrganization() - val item = upsertItem(org = org) + val item = upsertItem(org) val guids = itemsDao.findAll(Authorization.All, q = Some(s"org:${org.key}")).map(_.guid) guids.contains(item.guid) must be(true) itemsDao.findAll(Authorization.All, q = Some(s"org:${org.key}2")) must be(Nil) diff --git a/api/test/db/MembershipRequestsDaoSpec.scala b/api/test/db/MembershipRequestsDaoSpec.scala index 97a9c913e..038863072 100644 --- a/api/test/db/MembershipRequestsDaoSpec.scala +++ b/api/test/db/MembershipRequestsDaoSpec.scala @@ -10,46 +10,46 @@ import java.util.UUID class MembershipRequestsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { - private lazy val org: Organization = createOrganization() + private lazy val org: InternalOrganization = createOrganization() private lazy val member: User = upsertUser("gilt-member@bryzek.com") private def membershipRequestsModel: MembershipRequestsModel = app.injector.instanceOf[MembershipRequestsModel] "create member" in { val thisOrg = createOrganization() - membershipsDao.isUserMember(member, thisOrg) must equal(false) - membershipsDao.isUserAdmin(member, thisOrg) must equal(false) + membershipsDao.isUserMember(member, thisOrg.reference) must equal(false) + membershipsDao.isUserAdmin(member, thisOrg.reference) must equal(false) val request = membershipRequestsDao.upsert(testUser, thisOrg, member, MembershipRole.Member) request.organizationGuid must equal(thisOrg.guid) request.userGuid must equal(member.guid) request.role must equal(MembershipRole.Member) - membershipsDao.isUserMember(member, thisOrg) must equal(false) - membershipsDao.isUserAdmin(member, thisOrg) must equal(false) + membershipsDao.isUserMember(member, thisOrg.reference) must equal(false) + membershipsDao.isUserAdmin(member, thisOrg.reference) must equal(false) membershipRequestsDao.accept(testUser, membershipRequestsModel.toModel(request).value) - membershipsDao.isUserMember(member, thisOrg) must equal(true) - membershipsDao.isUserAdmin(member, thisOrg) must equal(false) + membershipsDao.isUserMember(member, thisOrg.reference) must equal(true) + membershipsDao.isUserAdmin(member, thisOrg.reference) must equal(false) } "create admin" in { val thisOrg = createOrganization() - membershipsDao.isUserMember(member, thisOrg) must equal(false) - membershipsDao.isUserAdmin(member, thisOrg) must equal(false) + membershipsDao.isUserMember(member, thisOrg.reference) must equal(false) + membershipsDao.isUserAdmin(member, thisOrg.reference) must equal(false) val request = membershipRequestsDao.upsert(testUser, thisOrg, member, MembershipRole.Admin) request.organizationGuid must equal(thisOrg.guid) request.userGuid must equal(member.guid) request.role must equal(MembershipRole.Admin) - membershipsDao.isUserMember(member, thisOrg) must equal(false) - membershipsDao.isUserAdmin(member, thisOrg) must equal(false) + membershipsDao.isUserMember(member, thisOrg.reference) must equal(false) + membershipsDao.isUserAdmin(member, thisOrg.reference) must equal(false) membershipRequestsDao.accept(testUser, membershipRequestsModel.toModel(request).value) - membershipsDao.isUserMember(member, thisOrg) must equal(true) - membershipsDao.isUserAdmin(member, thisOrg) must equal(true) + membershipsDao.isUserMember(member, thisOrg.reference) must equal(true) + membershipsDao.isUserAdmin(member, thisOrg.reference) must equal(true) } "findByGuid" in { @@ -100,12 +100,12 @@ class MembershipRequestsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with d val newOrg = createOrganization() val request = membershipRequestsDao.upsert(testUser, newOrg, member, MembershipRole.Member) - membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, newOrg, member, MembershipRole.Member) must be(None) + membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, newOrg.reference, member, MembershipRole.Member) must be(None) membershipRequestsDao.accept(testUser, membershipRequestsModel.toModel(request).value) - membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, newOrg, member, MembershipRole.Member).get.userGuid must equal(member.guid) + membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, newOrg.reference, member, MembershipRole.Member).get.userGuid must equal(member.guid) - organizationLogsDao.findAll(Authorization.All, organization = Some(newOrg), limit = 1).map(_.message) must equal( + organizationLogsDao.findAll(Authorization.All, organization = Some(newOrg.reference), limit = 1).map(_.message) must equal( Seq("Accepted membership request for %s to join as member".format(member.email)) ) } @@ -114,11 +114,11 @@ class MembershipRequestsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with d val newOrg = createOrganization() val request = membershipRequestsDao.upsert(testUser, newOrg, member, MembershipRole.Member) - membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, newOrg, member, MembershipRole.Member) must be(None) + membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, newOrg.reference, member, MembershipRole.Member) must be(None) membershipRequestsDao.decline(testUser, membershipRequestsModel.toModel(request).value) - membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, newOrg, member, MembershipRole.Member) must be(None) - organizationLogsDao.findAll(Authorization.All, organization = Some(newOrg), limit = 1).map(_.message) must equal( + membershipsDao.findByOrganizationAndUserAndRole(Authorization.All, newOrg.reference, member, MembershipRole.Member) must be(None) + organizationLogsDao.findAll(Authorization.All, organization = Some(newOrg.reference), limit = 1).map(_.message) must equal( Seq("Declined membership request for %s to join as member".format(member.email)) ) } diff --git a/api/test/db/OrganizationDomainsDaoSpec.scala b/api/test/db/OrganizationDomainsDaoSpec.scala index 567e2e526..b1bac34d6 100644 --- a/api/test/db/OrganizationDomainsDaoSpec.scala +++ b/api/test/db/OrganizationDomainsDaoSpec.scala @@ -1,11 +1,12 @@ package db -import java.util.UUID +import helpers.OrganizationHelpers +import java.util.UUID import org.scalatestplus.play.PlaySpec import org.scalatestplus.play.guice.GuiceOneAppPerSuite -class OrganizationDomainsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { +class OrganizationDomainsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with OrganizationHelpers { "create" in { val domainName = UUID.randomUUID.toString + ".org" @@ -27,8 +28,12 @@ class OrganizationDomainsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with val org2 = createOrganization() organizationDomainsDao.create(testUser, org2, domainName) - organizationsDao.findByGuid(Authorization.All, org1.guid).get.domains.map(_.name) must be(Seq(domainName)) - organizationsDao.findByGuid(Authorization.All, org2.guid).get.domains.map(_.name) must be(Seq(domainName)) + def getDomains(orgGuid: UUID) = toModel( + organizationsDao.findByGuid(Authorization.All, orgGuid).get + ).domains.map(_.name) + + getDomains(org1.guid) must be(Seq(domainName)) + getDomains(org2.guid) must be(Seq(domainName)) } "findAll" in { diff --git a/api/test/db/SubscriptionsDaoSpec.scala b/api/test/db/SubscriptionsDaoSpec.scala index ef3a235f9..48ad43464 100644 --- a/api/test/db/SubscriptionsDaoSpec.scala +++ b/api/test/db/SubscriptionsDaoSpec.scala @@ -7,7 +7,7 @@ import org.scalatestplus.play.guice.GuiceOneAppPerSuite class SubscriptionsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { - private lazy val org: Organization = createOrganization() + private lazy val org: InternalOrganization = createOrganization() "when a user loses admin role, we remove subscriptions that require admin" in { val user = createRandomUser() diff --git a/api/test/db/VersionsDaoSpec.scala b/api/test/db/VersionsDaoSpec.scala index 5cedb7057..bc0aa7405 100644 --- a/api/test/db/VersionsDaoSpec.scala +++ b/api/test/db/VersionsDaoSpec.scala @@ -68,7 +68,7 @@ class VersionsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers val serviceConfig = ServiceConfiguration( orgKey = testOrg.key, - orgNamespace = testOrg.namespace, + orgNamespace = testOrg.db.namespace, version = "0.0.2" ) diff --git a/api/test/helpers/OrganizationHelpers.scala b/api/test/helpers/OrganizationHelpers.scala new file mode 100644 index 000000000..a87c26f33 --- /dev/null +++ b/api/test/helpers/OrganizationHelpers.scala @@ -0,0 +1,15 @@ +package helpers + +import db.InternalOrganization +import io.apibuilder.api.v0.models.{BatchDownloadApplicationForm, BatchDownloadApplicationsForm, Organization} +import models.OrganizationsModel +import org.scalatestplus.play.PlaySpec + +trait OrganizationHelpers extends db.Helpers { + + private def orgModel: OrganizationsModel = injector.instanceOf[OrganizationsModel] + def toModel(org: InternalOrganization): Organization = { + orgModel.toModel(org) + } + +} diff --git a/api/test/lib/EmailsSpec.scala b/api/test/lib/EmailsSpec.scala index d6c025bfe..ccc555202 100644 --- a/api/test/lib/EmailsSpec.scala +++ b/api/test/lib/EmailsSpec.scala @@ -33,9 +33,9 @@ class EmailsSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { "Context.Application for private app" in { val app = createApplication(org = org) - emails.isAuthorized(Emails.Context.Application(app), org, randomUser) must be(false) - emails.isAuthorized(Emails.Context.Application(app), org, orgMember) must be(true) - emails.isAuthorized(Emails.Context.Application(app), org, formerMember) must be(false) + emails.isAuthorized(Emails.Context.Application(app), org.reference, randomUser) must be(false) + emails.isAuthorized(Emails.Context.Application(app), org.reference, orgMember) must be(true) + emails.isAuthorized(Emails.Context.Application(app), org.reference, formerMember) must be(false) } "Context.Application for public app" in { @@ -44,23 +44,23 @@ class EmailsSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { createApplicationForm(visibility = Visibility.Public) ) - emails.isAuthorized(Emails.Context.Application(app), org, randomUser) must be(true) - emails.isAuthorized(Emails.Context.Application(app), org, orgMember) must be(true) - emails.isAuthorized(Emails.Context.Application(app), org, formerMember) must be(true) + emails.isAuthorized(Emails.Context.Application(app), org.reference, randomUser) must be(true) + emails.isAuthorized(Emails.Context.Application(app), org.reference, orgMember) must be(true) + emails.isAuthorized(Emails.Context.Application(app), org.reference, formerMember) must be(true) } "Context.OrganizationAdmin" in { - emails.isAuthorized(Emails.Context.OrganizationAdmin, org, randomUser) must be(false) - emails.isAuthorized(Emails.Context.OrganizationAdmin, org, orgMember) must be(false) - emails.isAuthorized(Emails.Context.OrganizationAdmin, org, orgAdmin) must be(true) - emails.isAuthorized(Emails.Context.OrganizationAdmin, org, formerMember) must be(false) + emails.isAuthorized(Emails.Context.OrganizationAdmin, org.reference, randomUser) must be(false) + emails.isAuthorized(Emails.Context.OrganizationAdmin, org.reference, orgMember) must be(false) + emails.isAuthorized(Emails.Context.OrganizationAdmin, org.reference, orgAdmin) must be(true) + emails.isAuthorized(Emails.Context.OrganizationAdmin, org.reference, formerMember) must be(false) } "Context.OrganizationMember" in { - emails.isAuthorized(Emails.Context.OrganizationMember, org, randomUser) must be(false) - emails.isAuthorized(Emails.Context.OrganizationMember, org, orgMember) must be(true) - emails.isAuthorized(Emails.Context.OrganizationMember, org, orgAdmin) must be(true) - emails.isAuthorized(Emails.Context.OrganizationMember, org, formerMember) must be(false) + emails.isAuthorized(Emails.Context.OrganizationMember, org.reference, randomUser) must be(false) + emails.isAuthorized(Emails.Context.OrganizationMember, org.reference, orgMember) must be(true) + emails.isAuthorized(Emails.Context.OrganizationMember, org.reference, orgAdmin) must be(true) + emails.isAuthorized(Emails.Context.OrganizationMember, org.reference, formerMember) must be(false) } } diff --git a/api/test/processor/PurgeDeletedProcessorSpec.scala b/api/test/processor/PurgeDeletedProcessorSpec.scala index 70edecd0f..5d620c86c 100644 --- a/api/test/processor/PurgeDeletedProcessorSpec.scala +++ b/api/test/processor/PurgeDeletedProcessorSpec.scala @@ -1,7 +1,7 @@ package processor import anorm.SqlParser -import db.{Helpers, InternalApplication} +import db.{Helpers, InternalApplication, InternalOrganization} import io.apibuilder.api.v0.models.Organization import io.flow.postgresql.Query import org.joda.time.DateTime @@ -39,7 +39,7 @@ class PurgeDeletedProcessorSpec extends PlaySpec with GuiceOneAppPerSuite with H "organization" in { def isAppDeleted(app: InternalApplication): Boolean = isDeleted("applications", app.guid) - def setup(): (Organization, InternalApplication) = { + def setup(): (InternalOrganization, InternalApplication) = { val org = createOrganization() val app = createApplication(org) softDelete("applications", app.guid, DateTime.now.minusYears(1)) diff --git a/api/test/util/Daos.scala b/api/test/util/Daos.scala index 23773e8af..25b0dbad9 100644 --- a/api/test/util/Daos.scala +++ b/api/test/util/Daos.scala @@ -12,7 +12,7 @@ trait Daos { def app: Application def injector: Injector = app.injector - def applicationsDao: ApplicationsDao = injector.instanceOf[db.ApplicationsDao] + def applicationsDao: InternalApplicationsDao = injector.instanceOf[db.InternalApplicationsDao] def attributesDao: AttributesDao = injector.instanceOf[db.AttributesDao] def changesDao: ChangesDao = injector.instanceOf[db.ChangesDao] def databaseServiceFetcher: DatabaseServiceFetcher = injector.instanceOf[DatabaseServiceFetcher] @@ -25,7 +25,7 @@ trait Daos { def organizationAttributeValuesDao: OrganizationAttributeValuesDao = injector.instanceOf[db.OrganizationAttributeValuesDao] def organizationDomainsDao: OrganizationDomainsDao = injector.instanceOf[db.OrganizationDomainsDao] def organizationLogsDao: OrganizationLogsDao = injector.instanceOf[db.OrganizationLogsDao] - def organizationsDao: OrganizationsDao = injector.instanceOf[db.OrganizationsDao] + def organizationsDao: InternalOrganizationsDao = injector.instanceOf[db.InternalOrganizationsDao] def originalsDao: OriginalsDao = injector.instanceOf[db.OriginalsDao] def passwordResetRequestsDao: PasswordResetRequestsDao = injector.instanceOf[db.PasswordResetRequestsDao] def sessionsDao: SessionsDao = injector.instanceOf[SessionsDao] diff --git a/app/app/controllers/Versions.scala b/app/app/controllers/Versions.scala index 935d95cc5..b4a875753 100644 --- a/app/app/controllers/Versions.scala +++ b/app/app/controllers/Versions.scala @@ -15,8 +15,8 @@ import scala.concurrent.duration.* import scala.concurrent.{Await, ExecutionContext, Future} class Versions @Inject() ( - val apiBuilderControllerComponents: ApiBuilderControllerComponents, - apiClientProvider: ApiClientProvider + val apiBuilderControllerComponents: ApiBuilderControllerComponents, + apiClientProvider: ApiClientProvider ) extends ApiBuilderController { private val DefaultVersion = "0.0.1-dev" diff --git a/dao/spec/psql-apibuilder.json b/dao/spec/psql-apibuilder.json index 637842fbf..5181ae18c 100644 --- a/dao/spec/psql-apibuilder.json +++ b/dao/spec/psql-apibuilder.json @@ -12,15 +12,19 @@ { "name": "psql", "value": { - "pkey": "id", - "hash_code": {}, + "pkey": "guid", "audit": { "created": { - "at": { "type": "date-time-iso8601" } + "at": { "type": "date-time-iso8601" }, + "by": { "name": "created_by_guid", "type": "uuid" } }, "updated": { "at": { "type": "date-time-iso8601" }, - "by": { "name": "updated_by_guid", "type": "string" } + "by": { "name": "updated_by_guid", "type": "uuid" } + }, + "deleted": { + "at": { "type": "date-time-iso8601", "required": false }, + "by": { "name": "deleted_by_guid", "type": "uuid", "required": false } } } } @@ -28,6 +32,36 @@ ], "models": { + "organization": { + "fields": [ + { "name": "guid", "type": "uuid" }, + { "name": "name", "type": "string" }, + { "name": "key", "type": "string" }, + { "name": "namespace", "type": "string" }, + { "name": "visibility", "type": "string" } + ], + "attributes": [ + { + "name": "scala", + "value": { + "pkey_generator": { + "method": "java.util.UUID.randomUUID", + "returns": "uuid" + } + } + }, + { + "name": "psql", + "value": { + "indexes": [ + { "fields": ["guid"], "where": "deleted_at is null" }, + { "fields": ["key"], "where": "deleted_at is null" } + ] + } + } + ] + }, + "task": { "fields": [ { "name": "id", "type": "string" }, @@ -44,12 +78,23 @@ { "name": "psql", "value": { + "pkey": "id", + "hash_code": {}, "on_conflict": { "fields": ["type_id", "type"] }, "indexes": [ { "fields": ["num_attempts", "next_attempt_at"] } - ] + ], + "audit": { + "created": { + "at": { "type": "date-time-iso8601" } + }, + "updated": { + "at": { "type": "date-time-iso8601" }, + "by": { "name": "updated_by_guid", "type": "string" } + } + } } } ] @@ -65,23 +110,11 @@ { "name": "psql", "value": { + "pkey": "id", + "hash_code": {}, "indexes": [ { "fields": ["user_guid"] } - ], - "audit": { - "created": { - "at": { "type": "date-time-iso8601" }, - "by": { "name": "created_by_guid", "type": "uuid" } - }, - "updated": { - "at": { "type": "date-time-iso8601" }, - "by": { "name": "updated_by_guid", "type": "uuid" } - }, - "deleted": { - "at": { "type": "date-time-iso8601", "required": false }, - "by": { "name": "deleted_by_guid", "type": "uuid", "required": false } - } - } + ] } } ] @@ -98,7 +131,7 @@ { "name": "scala", "value": { - "id_generator": { + "pkey_generator": { "class": "com.mbryzek.util.IdGenerator", "prefix": "gni" } @@ -107,6 +140,8 @@ { "name": "psql", "value": { + "pkey": "id", + "hash_code": {}, "audit": { "created": { "at": { "type": "date-time-iso8601" } diff --git a/generated/app/db/GeneratorInvocationsDao.scala b/generated/app/db/GeneratorInvocationsDao.scala index 018ded529..73bff235b 100644 --- a/generated/app/db/GeneratorInvocationsDao.scala +++ b/generated/app/db/GeneratorInvocationsDao.scala @@ -196,12 +196,12 @@ class GeneratorInvocationsDao @javax.inject.Inject() (override val db: play.api. import anorm.postgresql.* - private val idGenerator: com.mbryzek.util.IdGenerator = { + private val pkeyGenerator: com.mbryzek.util.IdGenerator = { com.mbryzek.util.IdGenerator("gni") } - def randomId: String = { - idGenerator.randomId() + def randomPkey: String = { + pkeyGenerator.randomId() } private val InsertQuery: io.flow.postgresql.Query = { @@ -244,7 +244,7 @@ class GeneratorInvocationsDao @javax.inject.Inject() (override val db: play.api. user: java.util.UUID, form: GeneratorInvocationForm ): String = { - val id = randomId + val id = randomPkey bindQuery(InsertQuery, user, form) .bind("created_at", org.joda.time.DateTime.now) .bind("id", id) @@ -267,7 +267,7 @@ class GeneratorInvocationsDao @javax.inject.Inject() (override val db: play.api. forms: Seq[GeneratorInvocationForm] ): Seq[String] = { forms.map { f => - val id = randomId + val id = randomPkey (id, Seq(anorm.NamedParameter("created_at", org.joda.time.DateTime.now)) ++ toNamedParameter(user, id, f)) }.toList match { case Nil => Nil diff --git a/generated/app/db/OrganizationsDao.scala b/generated/app/db/OrganizationsDao.scala new file mode 100644 index 000000000..4aa3499eb --- /dev/null +++ b/generated/app/db/OrganizationsDao.scala @@ -0,0 +1,463 @@ +package db.generated + +case class Organization( + guid: java.util.UUID, + name: String, + key: String, + namespace: String, + visibility: String, + createdAt: org.joda.time.DateTime, + createdByGuid: java.util.UUID, + updatedAt: org.joda.time.DateTime, + updatedByGuid: java.util.UUID, + deletedAt: Option[org.joda.time.DateTime], + deletedByGuid: Option[java.util.UUID] +) { + def form: OrganizationForm = { + OrganizationForm( + name = name, + key = key, + namespace = namespace, + visibility = visibility, + ) + } +} + +case class OrganizationForm( + name: String, + key: String, + namespace: String, + visibility: String +) + +case object OrganizationsTable { + val SchemaName: String = "public" + + val TableName: String = "organizations" + + val QualifiedName: String = "public.organizations" + + sealed trait Column { + def name: String + } + + object Columns { + case object Guid extends Column { + override val name: String = "guid" + } + + case object Name extends Column { + override val name: String = "name" + } + + case object Key extends Column { + override val name: String = "key" + } + + case object Namespace extends Column { + override val name: String = "namespace" + } + + case object Visibility extends Column { + override val name: String = "visibility" + } + + case object CreatedAt extends Column { + override val name: String = "created_at" + } + + case object CreatedByGuid extends Column { + override val name: String = "created_by_guid" + } + + case object UpdatedAt extends Column { + override val name: String = "updated_at" + } + + case object UpdatedByGuid extends Column { + override val name: String = "updated_by_guid" + } + + case object DeletedAt extends Column { + override val name: String = "deleted_at" + } + + case object DeletedByGuid extends Column { + override val name: String = "deleted_by_guid" + } + + val all: List[Column] = List(Guid, Name, Key, Namespace, Visibility, CreatedAt, CreatedByGuid, UpdatedAt, UpdatedByGuid, DeletedAt, DeletedByGuid) + } +} + +trait BaseOrganizationsDao { + import anorm.* + + import anorm.JodaParameterMetaData.* + + import anorm.postgresql.* + + def db: play.api.db.Database + + private val BaseQuery: io.flow.postgresql.Query = { + io.flow.postgresql.Query(""" + | select guid::text, + | name, + | key, + | namespace, + | visibility, + | created_at, + | created_by_guid::text, + | updated_at, + | updated_by_guid::text, + | deleted_at, + | deleted_by_guid::text + | from public.organizations + |""".stripMargin.stripTrailing + ) + } + + def findAll( + guid: Option[java.util.UUID] = None, + guids: Option[Seq[java.util.UUID]] = None, + limit: Option[Long], + offset: Long = 0, + orderBy: Option[io.flow.postgresql.OrderBy] = None + )(implicit customQueryModifier: io.flow.postgresql.Query => io.flow.postgresql.Query = identity): Seq[Organization] = { + db.withConnection { c => + findAllWithConnection(c, guid, guids, limit, offset, orderBy) + } + } + + def findAllWithConnection( + c: java.sql.Connection, + guid: Option[java.util.UUID] = None, + guids: Option[Seq[java.util.UUID]] = None, + limit: Option[Long], + offset: Long = 0, + orderBy: Option[io.flow.postgresql.OrderBy] = None + )(implicit customQueryModifier: io.flow.postgresql.Query => io.flow.postgresql.Query = identity): Seq[Organization] = { + customQueryModifier(BaseQuery) + .equals("organizations.guid", guid) + .optionalIn("organizations.guid", guids) + .optionalLimit(limit) + .offset(offset) + .orderBy(orderBy.flatMap(_.sql)) + .as(parser.*)(c) + } + + def iterateAll( + guid: Option[java.util.UUID] = None, + guids: Option[Seq[java.util.UUID]] = None, + pageSize: Long = 1000 + )(implicit customQueryModifier: io.flow.postgresql.Query => io.flow.postgresql.Query = identity): Iterator[Organization] = { + assert(pageSize > 0, "pageSize must be > 0") + + def iterate(lastValue: Option[Organization]): Iterator[Organization] = { + val page: Seq[Organization] = db.withConnection { c => + customQueryModifier(BaseQuery) + .equals("organizations.guid", guid) + .optionalIn("organizations.guid", guids) + .greaterThan("organizations.guid", lastValue.map(_.guid)) + .orderBy("organizations.guid") + .limit(pageSize) + .as(parser.*)(c) + } + if (page.length >= pageSize) { + page.iterator ++ iterate(page.lastOption) + } else { + page.iterator + } + } + + iterate(None) + } + + def findByGuid(guid: java.util.UUID): Option[Organization] = { + db.withConnection { c => + findByGuidWithConnection(c, guid) + } + } + + def findByGuidWithConnection( + c: java.sql.Connection, + guid: java.util.UUID + ): Option[Organization] = { + findAllWithConnection( + c = c, + guid = Some(guid), + limit = Some(1) + ).headOption + } + + private val parser: anorm.RowParser[Organization] = { + anorm.SqlParser.str("guid") ~ + anorm.SqlParser.str("name") ~ + anorm.SqlParser.str("key") ~ + anorm.SqlParser.str("namespace") ~ + anorm.SqlParser.str("visibility") ~ + anorm.SqlParser.get[org.joda.time.DateTime]("created_at") ~ + anorm.SqlParser.str("created_by_guid") ~ + anorm.SqlParser.get[org.joda.time.DateTime]("updated_at") ~ + anorm.SqlParser.str("updated_by_guid") ~ + anorm.SqlParser.get[org.joda.time.DateTime]("deleted_at").? ~ + anorm.SqlParser.str("deleted_by_guid").? map { case guid ~ name ~ key ~ namespace ~ visibility ~ createdAt ~ createdByGuid ~ updatedAt ~ updatedByGuid ~ deletedAt ~ deletedByGuid => + Organization( + guid = java.util.UUID.fromString(guid), + name = name, + key = key, + namespace = namespace, + visibility = visibility, + createdAt = createdAt, + createdByGuid = java.util.UUID.fromString(createdByGuid), + updatedAt = updatedAt, + updatedByGuid = java.util.UUID.fromString(updatedByGuid), + deletedAt = deletedAt, + deletedByGuid = deletedByGuid.map { v => java.util.UUID.fromString(v) } + ) + } + } +} + +class OrganizationsDao @javax.inject.Inject() (override val db: play.api.db.Database) extends BaseOrganizationsDao { + import anorm.JodaParameterMetaData.* + + import anorm.postgresql.* + + def randomPkey: java.util.UUID = { + java.util.UUID.randomUUID + } + + private val InsertQuery: io.flow.postgresql.Query = { + io.flow.postgresql.Query(""" + | insert into public.organizations + | (guid, name, key, namespace, visibility, created_at, created_by_guid, updated_at, updated_by_guid) + | values + | ({guid}::uuid, {name}, {key}, {namespace}, {visibility}, {created_at}::timestamptz, {created_by_guid}::uuid, {updated_at}::timestamptz, {updated_by_guid}::uuid) + """.stripMargin) + } + + private val UpdateQuery: io.flow.postgresql.Query = { + io.flow.postgresql.Query(""" + | update public.organizations + | set name = {name}, + | key = {key}, + | namespace = {namespace}, + | visibility = {visibility}, + | updated_at = {updated_at}::timestamptz, + | updated_by_guid = {updated_by_guid}::uuid + | where guid = {guid}::uuid + """.stripMargin) + } + + private val DeleteQuery: io.flow.postgresql.Query = { + io.flow.postgresql.Query("update public.organizations set deleted_at = {deleted_at}::timestamptz, deleted_by_guid = {deleted_by_guid}::uuid") + } + + def insert( + user: java.util.UUID, + form: OrganizationForm + ): java.util.UUID = { + db.withConnection { c => + insert(c, user, form) + } + } + + def insert( + c: java.sql.Connection, + user: java.util.UUID, + form: OrganizationForm + ): java.util.UUID = { + val id = randomPkey + bindQuery(InsertQuery, user, form) + .bind("created_at", org.joda.time.DateTime.now) + .bind("created_by_guid", user) + .bind("guid", id) + .execute(c) + id + } + + def insertBatch( + user: java.util.UUID, + forms: Seq[OrganizationForm] + ): Seq[java.util.UUID] = { + db.withConnection { c => + insertBatch(c, user, forms) + } + } + + def insertBatch( + c: java.sql.Connection, + user: java.util.UUID, + forms: Seq[OrganizationForm] + ): Seq[java.util.UUID] = { + forms.map { f => + val guid = randomPkey + (guid, Seq(anorm.NamedParameter("created_at", org.joda.time.DateTime.now)) ++ toNamedParameter(user, guid, f)) + }.toList match { + case Nil => Nil + case one :: rest => { + anorm.BatchSql(InsertQuery.sql(), one._2, rest.map(_._2)*).execute()(c) + Seq(one._1) ++ rest.map(_._1) + } + } + } + + def update( + user: java.util.UUID, + organization: Organization, + form: OrganizationForm + ): Unit = { + db.withConnection { c => + update(c, user, organization, form) + } + } + + def update( + c: java.sql.Connection, + user: java.util.UUID, + organization: Organization, + form: OrganizationForm + ): Unit = { + updateByGuid( + c = c, + user = user, + guid = organization.guid, + form = form + ) + } + + def updateByGuid( + user: java.util.UUID, + guid: java.util.UUID, + form: OrganizationForm + ): Unit = { + db.withConnection { c => + updateByGuid(c, user, guid, form) + } + } + + def updateByGuid( + c: java.sql.Connection, + user: java.util.UUID, + guid: java.util.UUID, + form: OrganizationForm + ): Unit = { + bindQuery(UpdateQuery, user, form) + .bind("guid", guid) + .bind("updated_by_guid", user) + .execute(c) + () + } + + def updateBatch( + user: java.util.UUID, + forms: Seq[(java.util.UUID, OrganizationForm)] + ): Unit = { + db.withConnection { c => + updateBatch(c, user, forms) + } + } + + def updateBatch( + c: java.sql.Connection, + user: java.util.UUID, + forms: Seq[(java.util.UUID, OrganizationForm)] + ): Unit = { + forms.map { case (guid, f) => toNamedParameter(user, guid, f) }.toList match { + case Nil => // no-op + case first :: rest => anorm.BatchSql(UpdateQuery.sql(), first, rest*).execute()(c) + } + } + + def delete( + user: java.util.UUID, + organization: Organization + ): Unit = { + db.withConnection { c => + delete(c, user, organization) + } + } + + def delete( + c: java.sql.Connection, + user: java.util.UUID, + organization: Organization + ): Unit = { + deleteByGuid( + c = c, + user = user, + guid = organization.guid + ) + } + + def deleteByGuid( + user: java.util.UUID, + guid: java.util.UUID + ): Unit = { + db.withConnection { c => + deleteByGuid(c, user, guid) + } + } + + def deleteByGuid( + c: java.sql.Connection, + user: java.util.UUID, + guid: java.util.UUID + ): Unit = { + DeleteQuery.equals("guid", guid) + .bind("deleted_at", org.joda.time.DateTime.now) + .bind("deleted_by_guid", user) + .execute(c) + } + + def deleteAllByGuids( + user: java.util.UUID, + guids: Seq[java.util.UUID] + ): Unit = { + db.withConnection { c => + deleteAllByGuids(c, user, guids) + } + } + + def deleteAllByGuids( + c: java.sql.Connection, + user: java.util.UUID, + guids: Seq[java.util.UUID] + ): Unit = { + DeleteQuery.in("guid", guids) + .bind("deleted_at", org.joda.time.DateTime.now) + .bind("deleted_by_guid", user) + .execute(c) + } + + private def bindQuery( + query: io.flow.postgresql.Query, + user: java.util.UUID, + form: OrganizationForm + ): io.flow.postgresql.Query = { + query + .bind("name", form.name) + .bind("key", form.key) + .bind("namespace", form.namespace) + .bind("visibility", form.visibility) + .bind("updated_at", org.joda.time.DateTime.now) + .bind("updated_by_guid", user) + } + + private def toNamedParameter( + user: java.util.UUID, + guid: java.util.UUID, + form: OrganizationForm + ): Seq[anorm.NamedParameter] = { + Seq( + anorm.NamedParameter("guid", guid.toString), + anorm.NamedParameter("name", form.name), + anorm.NamedParameter("key", form.key), + anorm.NamedParameter("namespace", form.namespace), + anorm.NamedParameter("visibility", form.visibility), + anorm.NamedParameter("updated_at", org.joda.time.DateTime.now), + anorm.NamedParameter("updated_by_guid", user.toString) + ) + } +} \ No newline at end of file