diff --git a/api/app/controllers/EmailVerificationConfirmationForms.scala b/api/app/controllers/EmailVerificationConfirmationForms.scala deleted file mode 100644 index afb7a5a0c..000000000 --- a/api/app/controllers/EmailVerificationConfirmationForms.scala +++ /dev/null @@ -1,45 +0,0 @@ -package controllers - -import cats.data.Validated.{Invalid, Valid} -import db.InternalEmailVerificationsDao -import io.apibuilder.api.v0.models.EmailVerificationConfirmationForm -import io.apibuilder.api.v0.models.json._ -import lib.Validation -import play.api.libs.json._ -import play.api.mvc._ -import services.EmailVerificationsService - -import javax.inject.{Inject, Singleton} - -@Singleton -class EmailVerificationConfirmationForms @Inject() ( - val apiBuilderControllerComponents: ApiBuilderControllerComponents, - emailVerificationsDao: InternalEmailVerificationsDao, - service: EmailVerificationsService -) extends ApiBuilderController { - - def post(): Action[JsValue] = Anonymous(parse.json) { request => - request.body.validate[EmailVerificationConfirmationForm] match { - case e: JsError => { - Conflict(Json.toJson(Validation.invalidJson(e))) - } - case JsSuccess(form: EmailVerificationConfirmationForm, _) => { - val token = form.token - emailVerificationsDao.findByToken(token) match { - case None => Conflict(Json.toJson("Token not found or has already expired")) - case Some(verification) => { - service.confirm(request.user, verification) match { - case Invalid(e) => { - Conflict(Json.toJson(e.toNonEmptyList.toList.mkString(", "))) - } - case Valid(_) => { - NoContent - } - } - } - } - } - } - } - -} diff --git a/api/app/controllers/PasswordResetRequests.scala b/api/app/controllers/PasswordResetRequests.scala deleted file mode 100644 index 66ab2bece..000000000 --- a/api/app/controllers/PasswordResetRequests.scala +++ /dev/null @@ -1,34 +0,0 @@ -package controllers - -import db.{InternalUsersDao, InternalPasswordResetsDao} -import io.apibuilder.api.v0.models.PasswordResetRequest -import io.apibuilder.api.v0.models.json.* -import lib.Validation -import models.UsersModel -import play.api.libs.json.* -import play.api.mvc.* - -import javax.inject.{Inject, Singleton} - -@Singleton -class PasswordResetRequests @Inject() ( - val apiBuilderControllerComponents: ApiBuilderControllerComponents, - passwordResetRequestsDao: InternalPasswordResetsDao, - usersDao: InternalUsersDao, -) extends ApiBuilderController { - - def post(): Action[JsValue] = Anonymous(parse.json) { request => - request.body.validate[PasswordResetRequest] match { - case e: JsError => { - Conflict(Json.toJson(Validation.invalidJson(e))) - } - case JsSuccess(data: PasswordResetRequest, _) => { - usersDao.findByEmail(data.email).map { user => - passwordResetRequestsDao.create(request.user, user) - } - NoContent - } - } - } - -} diff --git a/api/app/controllers/PasswordResets.scala b/api/app/controllers/PasswordResets.scala deleted file mode 100644 index e0d8e9dbe..000000000 --- a/api/app/controllers/PasswordResets.scala +++ /dev/null @@ -1,46 +0,0 @@ -package controllers - -import cats.data.Validated.{Invalid, Valid} -import db.{InternalUserPasswordsDao, InternalUsersDao, InternalPasswordResetsDao} -import io.apibuilder.api.v0.models.PasswordReset -import io.apibuilder.api.v0.models.json.* -import lib.Validation -import play.api.mvc.* -import util.SessionHelper -import play.api.libs.json.* - -import javax.inject.{Inject, Singleton} - -@Singleton -class PasswordResets @Inject() ( - val apiBuilderControllerComponents: ApiBuilderControllerComponents, - passwordResetRequestsDao: InternalPasswordResetsDao, - sessionHelper: SessionHelper, - usersDao: InternalUsersDao, - userPasswordsDao: InternalUserPasswordsDao, -) extends ApiBuilderController { - - def post(): Action[JsValue] = Anonymous(parse.json) { request => - request.body.validate[PasswordReset] match { - case e: JsError => { - Conflict(Json.toJson(Validation.invalidJson(e))) - } - case s: JsSuccess[PasswordReset] => { - val form = s.get - passwordResetRequestsDao.findByToken(form.token) match { - case None => { - Conflict(Json.toJson(Validation.error("Token not found"))) - } - - case Some(pr) => { - passwordResetRequestsDao.resetPassword(request.user, pr, form.password) match { - case Valid(user) => Ok(Json.toJson(sessionHelper.createAuthentication(user))) - case Invalid(errors) => Conflict(Json.toJson(errors.toNonEmptyList.toList)) - } - } - } - } - } - } - -} diff --git a/api/app/db/InternalEmailVerificationConfirmationsDao.scala b/api/app/db/InternalEmailVerificationConfirmationsDao.scala deleted file mode 100644 index 6b9f09e27..000000000 --- a/api/app/db/InternalEmailVerificationConfirmationsDao.scala +++ /dev/null @@ -1,55 +0,0 @@ -package db - -import db.generated.EmailVerificationConfirmationsDao -import io.flow.postgresql.{OrderBy, Query} -import org.joda.time.DateTime -import play.api.db.* - -import java.util.UUID -import javax.inject.Inject - -private[db] case class InternalEmailVerificationConfirmation(db: generated.EmailVerificationConfirmation) { - val guid: UUID = db.guid - val emailVerificationGuid: UUID = db.emailVerificationGuid - val createdAt: DateTime = db.createdAt -} - -class InternalEmailVerificationConfirmationsDao @Inject()( - dao: EmailVerificationConfirmationsDao -) { - - def upsert(createdBy: UUID, verification: InternalEmailVerification): InternalEmailVerificationConfirmation = { - findAll(emailVerificationGuid = Some(verification.guid), limit = Some(1)).headOption.getOrElse { - val guid = dao.insert(createdBy, generated.EmailVerificationConfirmationForm( - emailVerificationGuid = verification.guid - )) - - findByGuid(guid).getOrElse { - sys.error("Failed to create email verification confirmation") - } - } - } - - def findByGuid(guid: UUID): Option[InternalEmailVerificationConfirmation] = { - findAll(guid = Some(guid), limit = Some(1)).headOption - } - - def findAll( - guid: Option[UUID] = None, - emailVerificationGuid: Option[UUID] = None, - isDeleted: Option[Boolean] = Some(false), - limit: Option[Long], - offset: Long = 0 - ): Seq[InternalEmailVerificationConfirmation] = { - dao.findAll( - guid = guid, - emailVerificationGuid = emailVerificationGuid, - limit = limit, - offset = offset, - orderBy = Some(OrderBy("created_at")) - )( using (q: Query) => { - q.and(isDeleted.map(Filters.isDeleted("email_verification_confirmations", _))) - }).map(InternalEmailVerificationConfirmation(_)) - } - -} diff --git a/api/app/db/InternalEmailVerificationsDao.scala b/api/app/db/InternalEmailVerificationsDao.scala deleted file mode 100644 index ee9a4b755..000000000 --- a/api/app/db/InternalEmailVerificationsDao.scala +++ /dev/null @@ -1,88 +0,0 @@ -package db - -import db.generated.EmailVerificationsDao -import io.apibuilder.task.v0.models.EmailDataEmailVerificationCreated -import io.flow.postgresql.{OrderBy, Query} -import lib.TokenGenerator -import org.joda.time.DateTime -import processor.EmailProcessorQueue - -import java.util.UUID -import javax.inject.Inject - -case class InternalEmailVerification(db: generated.EmailVerification) { - val guid: UUID = db.guid - val userGuid: UUID = db.userGuid - val email: String = db.email - val token: String = db.token - val expiresAt: DateTime = db.expiresAt -} - -class InternalEmailVerificationsDao @Inject()( - dao: EmailVerificationsDao, - emailQueue: EmailProcessorQueue, -) { - - private val TokenLength = 80 - private val HoursUntilTokenExpires = 168 - - def upsert(createdBy: InternalUser, user: InternalUser, email: String): InternalEmailVerification = { - findAll(userGuid = Some(user.guid), email = Some(email), isExpired = Some(false), limit = Some(1)).headOption.getOrElse { - create(createdBy, user, email) - } - } - - private[db] def create(createdBy: InternalUser, user: InternalUser, email: String): InternalEmailVerification = { - val guid = dao.db.withTransaction { c => - val guid = dao.insert(c, createdBy.guid, generated.EmailVerificationForm( - userGuid = user.guid, - email = email.trim, - token = TokenGenerator.generate(TokenLength), - expiresAt = DateTime.now.plusHours(HoursUntilTokenExpires) - )) - emailQueue.queueWithConnection(c, EmailDataEmailVerificationCreated(guid)) - guid - } - - findByGuid(guid).getOrElse { - sys.error("Failed to create email verification") - } - } - - def softDelete(deletedBy: InternalUser, verification: InternalEmailVerification): Unit = { - dao.delete(deletedBy.guid, verification.db) - } - - def findByGuid(guid: UUID): Option[InternalEmailVerification] = { - findAll(guid = Some(guid), limit = Some(1)).headOption - } - - def findByToken(token: String): Option[InternalEmailVerification] = { - findAll(token = Some(token), limit = Some(1)).headOption - } - - private[db] def findAll( - guid: Option[UUID] = None, - userGuid: Option[UUID] = None, - email: Option[String] = None, - token: Option[String] = None, - isExpired: Option[Boolean] = None, - isDeleted: Option[Boolean] = Some(false), - limit: Option[Long], - offset: Long = 0 - ): Seq[InternalEmailVerification] = { - dao.findAll( - guid = guid, - token = token, - userGuid = userGuid, - limit = limit, - offset = offset, - orderBy = Some(OrderBy("created_at")) - )( using (q: Query) => { - q.equals("lower(email)", email.map(_.toLowerCase)) - .and(isExpired.map(Filters.isExpired("email_verifications", _))) - .and(isDeleted.map(Filters.isDeleted("email_verifications", _))) - }).map(InternalEmailVerification(_)) - } - -} diff --git a/api/app/db/InternalPasswordResetsDao.scala b/api/app/db/InternalPasswordResetsDao.scala deleted file mode 100644 index 374b822c0..000000000 --- a/api/app/db/InternalPasswordResetsDao.scala +++ /dev/null @@ -1,104 +0,0 @@ -package db - -import anorm.JodaParameterMetaData.* -import anorm.* -import db.generated.PasswordResetsDao -import cats.implicits.* -import cats.data.ValidatedNec -import io.apibuilder.api.v0.models.{Error, User} -import io.apibuilder.task.v0.models.EmailDataPasswordResetRequestCreated -import io.flow.postgresql.{Query, OrderBy} -import lib.{TokenGenerator, Validation} -import org.joda.time.DateTime -import play.api.db.* -import processor.EmailProcessorQueue - -import java.util.UUID -import javax.inject.{Inject, Singleton} - -case class InternalPasswordReset(db: generated.PasswordReset) { - val guid: UUID = db.guid - val userGuid: UUID = db.userGuid - val token: String = db.token - val expiresAt: DateTime = db.expiresAt -} - -class InternalPasswordResetsDao @Inject()( - dao: PasswordResetsDao, - emailQueue: EmailProcessorQueue, - userPasswordsDao: InternalUserPasswordsDao, - usersDao: InternalUsersDao -) { - - private val TokenLength = 80 - private val HoursUntilTokenExpires = 72 - - def create(createdBy: Option[InternalUser], user: InternalUser): InternalPasswordReset = { - val guid = dao.db.withTransaction { implicit c => - val guid = dao.insert(c, createdBy.getOrElse(user).guid, generated.PasswordResetForm( - userGuid = user.guid, - token = TokenGenerator.generate(TokenLength), - expiresAt = DateTime.now.plusHours(HoursUntilTokenExpires), - )) - emailQueue.queueWithConnection(c, EmailDataPasswordResetRequestCreated(guid)) - guid - } - - findByGuid(guid).getOrElse { - sys.error("Failed to create password reset") - } - } - - private def isExpired(pr: InternalPasswordReset): Boolean = { - pr.expiresAt.isBeforeNow - } - - def resetPassword(user: Option[InternalUser], pr: InternalPasswordReset, newPassword: String): ValidatedNec[Error, InternalUser] = { - if (isExpired(pr)) { - Validation.singleError("Password reset is expired").invalidNec - } else { - usersDao.findByGuid(pr.userGuid).toValidNec(Validation.singleError("User not found")).andThen { prUser => - val updatingUser = user.getOrElse(prUser) - userPasswordsDao.create(updatingUser, prUser.guid, newPassword).map { _ => - softDelete(updatingUser, pr) - prUser - } - } - } - } - - def softDelete(deletedBy: InternalUser, pr: InternalPasswordReset): Unit = { - dao.delete(deletedBy.guid, pr.db) - } - - def findByGuid(guid: UUID): Option[InternalPasswordReset] = { - findAll(guid = Some(guid), limit = Some(1)).headOption - } - - def findByToken(token: String): Option[InternalPasswordReset] = { - findAll(token = Some(token), limit = Some(1)).headOption - } - - def findAll( - guid: Option[UUID] = None, - userGuid: Option[UUID] = None, - token: Option[String] = None, - isExpired: Option[Boolean] = None, - isDeleted: Option[Boolean] = Some(false), - limit: Option[Long], - offset: Long = 0 - ): Seq[InternalPasswordReset] = { - dao.findAll( - guid = guid, - token = token, - userGuid = userGuid, - limit = limit, - offset = offset, - orderBy = Some(OrderBy("created_at")) - )( using (q: Query) => { - q.and(isDeleted.map(Filters.isDeleted("password_resets", _))) - .and(isExpired.map(Filters.isExpired("password_resets", _))) - }).map(InternalPasswordReset(_)) - } - -} diff --git a/api/app/db/InternalUsersDao.scala b/api/app/db/InternalUsersDao.scala index 6fcfbc55e..0b6ee6c83 100644 --- a/api/app/db/InternalUsersDao.scala +++ b/api/app/db/InternalUsersDao.scala @@ -41,7 +41,6 @@ case class ValidatedUserForm( class InternalUsersDao @Inject()( dao: UsersDao, - emailVerificationsDao: InternalEmailVerificationsDao, userPasswordsDao: InternalUserPasswordsDao, internalTasksDao: InternalTasksDao, ) { @@ -121,11 +120,6 @@ class InternalUsersDao @Inject()( nickname = vForm.nickname, )) - // TODO: Move to inside a transaction - if (user.email.toLowerCase != vForm.email.toLowerCase) { - emailVerificationsDao.upsert(updatingUser, user, form.email) - } - findByGuid(user.guid).getOrElse { sys.error("Failed to update user") } diff --git a/api/app/processor/EmailProcessor.scala b/api/app/processor/EmailProcessor.scala index 2e41acc05..852d9bf13 100644 --- a/api/app/processor/EmailProcessor.scala +++ b/api/app/processor/EmailProcessor.scala @@ -30,13 +30,11 @@ class EmailProcessor @Inject()( applicationsDao: db.InternalApplicationsDao, email: EmailUtil, emails: Emails, - emailVerificationsDao: db.InternalEmailVerificationsDao, membershipsDao: db.InternalMembershipsDao, membershipsModel: MembershipsModel, membershipRequestsDao: db.InternalMembershipRequestsDao, membershipRequestsModel: MembershipRequestsModel, organizationsDao: InternalOrganizationsDao, - passwordResetRequestsDao: db.InternalPasswordResetsDao, usersDao: InternalUsersDao, orgModel: OrganizationsModel, ) extends TaskProcessorWithData[EmailData](args, TaskType.Email) { @@ -44,12 +42,12 @@ class EmailProcessor @Inject()( override def processRecord(id: String, data: EmailData): ValidatedNec[String, Unit] = { data match { case EmailDataApplicationCreated(guid) => applicationCreated(guid).validNec - case EmailDataEmailVerificationCreated(guid) => emailVerificationCreated(guid).validNec + case EmailDataEmailVerificationCreated(guid) => s"EmailDataEmailVerificationCreated is no longer supported".invalidNec case EmailDataMembershipCreated(guid) => membershipCreated(guid).validNec case EmailDataMembershipRequestCreated(guid) => membershipRequestCreated(guid).validNec case EmailDataMembershipRequestAccepted(orgGuid, userGuid, role) => membershipRequestAccepted(orgGuid, userGuid, role).validNec case EmailDataMembershipRequestDeclined(orgGuid, userGuid) => membershipRequestDeclined(orgGuid, userGuid).validNec - case EmailDataPasswordResetRequestCreated(guid) => passwordResetRequestCreated(guid).validNec + case EmailDataPasswordResetRequestCreated(guid) => s"EmailDataPasswordResetRequestCreated is no longer supported".invalidNec case EmailDataUndefinedType(description) => s"Invalid email data type '$description'".invalidNec } } @@ -68,18 +66,6 @@ class EmailProcessor @Inject()( } } - private def emailVerificationCreated(guid: UUID): Unit = { - emailVerificationsDao.findByGuid(guid).foreach { verification => - usersDao.findByGuid(verification.userGuid).foreach { user => - email.sendHtml( - to = Person(email = verification.email, name = user.name), - subject = "Verify your email address", - body = views.html.emails.emailVerificationCreated(appConfig, verification).toString - ) - } - } - } - private def membershipCreated(guid: UUID): Unit = { membershipsDao.findByGuid(Authorization.All, guid) .flatMap(membershipsModel.toModel) @@ -132,15 +118,4 @@ class EmailProcessor @Inject()( } } - private def passwordResetRequestCreated(guid: UUID): Unit = { - passwordResetRequestsDao.findByGuid(guid).foreach { request => - usersDao.findByGuid(request.userGuid).foreach { user => - email.sendHtml( - to = Person(user), - subject = "Reset your password", - body = views.html.emails.passwordResetRequestCreated(appConfig, request.token).toString - ) - } - } - } } \ No newline at end of file diff --git a/api/app/processor/UserCreatedProcessor.scala b/api/app/processor/UserCreatedProcessor.scala index 241906422..22eeb8a86 100644 --- a/api/app/processor/UserCreatedProcessor.scala +++ b/api/app/processor/UserCreatedProcessor.scala @@ -15,7 +15,6 @@ class UserCreatedProcessor @Inject()( usersDao: InternalUsersDao, organizationsDao: InternalOrganizationsDao, membershipRequestsDao: InternalMembershipRequestsDao, - emailVerificationsDao: InternalEmailVerificationsDao, ) extends TaskProcessorWithGuid(args, TaskType.UserCreated) { override def processRecord(userGuid: UUID): ValidatedNec[String, Unit] = { @@ -23,7 +22,6 @@ class UserCreatedProcessor @Inject()( organizationsDao.findAllByEmailDomain(user.email).foreach { org => membershipRequestsDao.upsert(user, org, user, MembershipRole.Member) } - emailVerificationsDao.upsert(user, user, user.email) } ().validNec } diff --git a/api/app/services/EmailVerificationsService.scala b/api/app/services/EmailVerificationsService.scala deleted file mode 100644 index 7cf948cd4..000000000 --- a/api/app/services/EmailVerificationsService.scala +++ /dev/null @@ -1,43 +0,0 @@ -package services - -import cats.data.ValidatedNec -import cats.implicits._ -import db._ -import io.apibuilder.api.v0.models.User -import io.apibuilder.common.v0.models.MembershipRole -import models.MembershipRequestsModel - -import javax.inject.{Inject, Singleton} - -@Singleton -class EmailVerificationsService @Inject()( - emailVerificationConfirmationsDao: InternalEmailVerificationConfirmationsDao, - membershipRequestsDao: InternalMembershipRequestsDao, - membershipRequestsModel: MembershipRequestsModel, - organizationsDao: InternalOrganizationsDao -) { - - def confirm(user: Option[InternalUser], verification: InternalEmailVerification): ValidatedNec[String, Unit] = { - validateExpiration(verification).map { _ => - val updatingUserGuid = user.map(_.guid).getOrElse(verification.userGuid) - - emailVerificationConfirmationsDao.upsert(updatingUserGuid, verification) - organizationsDao.findAllByEmailDomain(verification.email).foreach { org => - membershipRequestsDao.findByOrganizationAndUserGuidAndRole(Authorization.All, org, verification.userGuid, MembershipRole.Member) - .flatMap(membershipRequestsModel.toModel) - .foreach { request => - membershipRequestsDao.acceptViaEmailVerification(updatingUserGuid, request, verification.email) - } - } - } - } - - private def validateExpiration(verification: InternalEmailVerification): ValidatedNec[String, Unit] = { - if (verification.expiresAt.isBeforeNow) { - s"Token for verificationGuid[${verification.guid}] is expired".invalidNec - } else { - ().validNec - } - } - -} diff --git a/api/app/views/emails/emailVerificationCreated.scala.html b/api/app/views/emails/emailVerificationCreated.scala.html deleted file mode 100644 index 7dc9e1d1f..000000000 --- a/api/app/views/emails/emailVerificationCreated.scala.html +++ /dev/null @@ -1,8 +0,0 @@ -@( - appConfig: lib.AppConfig, - verification: db.InternalEmailVerification -) - -
diff --git a/api/app/views/emails/passwordResetRequestCreated.scala.html b/api/app/views/emails/passwordResetRequestCreated.scala.html deleted file mode 100644 index 424f03f24..000000000 --- a/api/app/views/emails/passwordResetRequestCreated.scala.html +++ /dev/null @@ -1,8 +0,0 @@ -@( - appConfig: lib.AppConfig, - token: String -) - - diff --git a/api/conf/routes b/api/conf/routes index bc15ace6d..c83d6db9b 100644 --- a/api/conf/routes +++ b/api/conf/routes @@ -10,7 +10,6 @@ GET /authentications/session/:id controllers GET /changes controllers.Changes.get(org_key: _root_.scala.Option[String], application_key: _root_.scala.Option[String], from: _root_.scala.Option[String], to: _root_.scala.Option[String], type: _root_.scala.Option[String], limit: Long ?= 25L, offset: Long ?= 0L) POST /domains/:orgKey controllers.Domains.post(orgKey: String) DELETE /domains/:orgKey/:name controllers.Domains.deleteByName(orgKey: String, name: String) -POST /email_verification_confirmations controllers.EmailVerificationConfirmationForms.post() GET /generator_services controllers.GeneratorServices.get(guid: _root_.scala.Option[_root_.java.util.UUID], uri: _root_.scala.Option[String], generator_key: _root_.scala.Option[String], limit: Long ?= 100L, offset: Long ?= 0L) GET /generator_services/:guid controllers.GeneratorServices.getByGuid(guid: _root_.java.util.UUID) POST /generator_services controllers.GeneratorServices.post() @@ -36,8 +35,6 @@ GET /organizations/:key/attributes controllers GET /organizations/:key/attributes/:name controllers.Organizations.getAttributesByKeyAndName(key: String, name: String) PUT /organizations/:key/attributes/:name controllers.Organizations.putAttributesByKeyAndName(key: String, name: String) DELETE /organizations/:key/attributes/:name controllers.Organizations.deleteAttributesByKeyAndName(key: String, name: String) -POST /password_resets controllers.PasswordResets.post() -POST /password_reset_requests controllers.PasswordResetRequests.post() GET /subscriptions controllers.Subscriptions.get(guid: _root_.scala.Option[_root_.java.util.UUID], organization_key: _root_.scala.Option[String], user_guid: _root_.scala.Option[_root_.java.util.UUID], publication: _root_.scala.Option[io.apibuilder.api.v0.models.Publication], limit: Long ?= 25L, offset: Long ?= 0L) GET /subscriptions/:guid controllers.Subscriptions.getByGuid(guid: _root_.java.util.UUID) POST /subscriptions controllers.Subscriptions.post() diff --git a/api/test/controllers/PasswordResetsSpec.scala b/api/test/controllers/PasswordResetsSpec.scala deleted file mode 100644 index 8f83a04d8..000000000 --- a/api/test/controllers/PasswordResetsSpec.scala +++ /dev/null @@ -1,59 +0,0 @@ -package controllers - -import io.apibuilder.api.v0.models.{Authentication, PasswordReset} -import java.util.UUID - -import org.scalatestplus.play.guice.GuiceOneServerPerSuite -import org.scalatestplus.play.PlaySpec - -import scala.concurrent.Future - -class PasswordResetsSpec extends PlaySpec with MockClient with GuiceOneServerPerSuite { - - import scala.concurrent.ExecutionContext.Implicits.global - - private def resetPassword(token: String, pwd: String): Future[Authentication] = { - client.passwordResets.post( - PasswordReset(token = token, password = pwd) - ) - } - - "POST /password_resets" in { - val user = createUser() - createPasswordRequest(user.email) - val pr = passwordResetsDao.findAll(userGuid = Some(user.guid), limit = None).head - - val pwd = "some password" - userPasswordsDao.isValid(user.guid, pwd) must equal(false) - - val result = await(resetPassword(pr.token, pwd)) - result.user.guid must equal(user.guid) - - userPasswordsDao.isValid(user.guid, pwd) must equal(true) - - // Make sure token cannot be reused - expectErrors { - resetPassword(pr.token, pwd) - }.errors.map(_.message) must equal(Seq(s"Token not found")) - - } - - "POST /password_resets validates password" in { - val user = createUser() - createPasswordRequest(user.email) - val pr = passwordResetsDao.findAll(userGuid = Some(user.guid), limit = None).head - - expectErrors { - resetPassword(pr.token, "foo") - }.errors.map(_.message) must equal(Seq(s"Password must be at least 5 characters")) - - } - - "POST /password_reset_requests/:token validates token" in { - expectErrors { - resetPassword(UUID.randomUUID.toString, "testing") - }.errors.map(_.message) must equal(Seq(s"Token not found")) - - } - -} diff --git a/api/test/db/InternalEmailVerificationConfirmationsDaoSpec.scala b/api/test/db/InternalEmailVerificationConfirmationsDaoSpec.scala deleted file mode 100644 index be973144c..000000000 --- a/api/test/db/InternalEmailVerificationConfirmationsDaoSpec.scala +++ /dev/null @@ -1,41 +0,0 @@ -package db - -import java.util.UUID - -import org.scalatestplus.play.PlaySpec -import org.scalatestplus.play.guice.GuiceOneAppPerSuite - -class InternalEmailVerificationConfirmationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with db.Helpers { - - private def emailVerificationConfirmationsDao: InternalEmailVerificationConfirmationsDao = injector.instanceOf[db.InternalEmailVerificationConfirmationsDao] - - "upsert" in { - val user = createRandomUser() - val verification = emailVerificationsDao.create(testUser, user, user.email) - - val conf = emailVerificationConfirmationsDao.upsert(testUser.guid, verification) - conf.emailVerificationGuid must be(verification.guid) - - val conf2 = emailVerificationConfirmationsDao.upsert(testUser.guid, verification) - conf2.guid must be(conf.guid) - } - - "findAll" in { - val user1 = createRandomUser() - val verification1 = emailVerificationsDao.create(testUser, user1, user1.email) - val conf1 = emailVerificationConfirmationsDao.upsert(testUser.guid, verification1) - - val user2 = createRandomUser() - val verification2 = emailVerificationsDao.create(testUser, user2, user2.email) - val conf2 = emailVerificationConfirmationsDao.upsert(testUser.guid, verification2) - - emailVerificationConfirmationsDao.findAll(guid = Some(conf1.guid), limit = None).map(_.guid) must be(Seq(conf1.guid)) - emailVerificationConfirmationsDao.findAll(guid = Some(conf2.guid), limit = None).map(_.guid) must be(Seq(conf2.guid)) - emailVerificationConfirmationsDao.findAll(guid = Some(UUID.randomUUID), limit = None).map(_.guid) must be(Nil) - - emailVerificationConfirmationsDao.findAll(emailVerificationGuid = Some(verification1.guid), limit = None).map(_.guid) must be(Seq(conf1.guid)) - emailVerificationConfirmationsDao.findAll(emailVerificationGuid = Some(verification2.guid), limit = None).map(_.guid) must be(Seq(conf2.guid)) - emailVerificationConfirmationsDao.findAll(emailVerificationGuid = Some(UUID.randomUUID), limit = None).map(_.guid) must be(Nil) - } - -} diff --git a/api/test/db/InternalEmailVerificationsDaoSpec.scala b/api/test/db/InternalEmailVerificationsDaoSpec.scala deleted file mode 100644 index e7eab9e2e..000000000 --- a/api/test/db/InternalEmailVerificationsDaoSpec.scala +++ /dev/null @@ -1,119 +0,0 @@ -package db - -import helpers.ValidatedTestHelpers -import io.apibuilder.api.v0.models.UserForm -import org.scalatestplus.play.PlaySpec -import org.scalatestplus.play.guice.GuiceOneAppPerSuite -import processor.UserCreatedProcessor -import services.EmailVerificationsService - -import java.util.UUID - -class InternalEmailVerificationsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with Helpers with ValidatedTestHelpers { - - private def userCreatedProcessor: UserCreatedProcessor = injector.instanceOf[UserCreatedProcessor] - private def service: EmailVerificationsService = injector.instanceOf[EmailVerificationsService] - - "upsert" in { - val user = createRandomUser() - - // Let actor create the email verification - // TODO: Change to eventually - Thread.sleep(1500) - val verification1 = emailVerificationsDao.upsert(testUser, user, user.email) - val verification2 = emailVerificationsDao.upsert(testUser, user, user.email) - verification2.guid must be(verification1.guid) - - emailVerificationsDao.softDelete(testUser, verification1) - val verification3 = emailVerificationsDao.upsert(testUser, user, user.email) - verification3.guid != verification1.guid must be(true) - - val verificationWithDifferentEmail = emailVerificationsDao.upsert(testUser, user, "other-" + user.email) - verificationWithDifferentEmail.guid != verification3.guid must be(true) - } - - "create" in { - val user = createRandomUser() - val verification = emailVerificationsDao.create(testUser, user, user.email) - verification.userGuid must be(user.guid) - verification.email must be(user.email) - } - - "findByGuid" in { - val user = createRandomUser() - val verification = emailVerificationsDao.create(testUser, user, user.email) - - emailVerificationsDao.findByGuid(verification.guid).map(_.userGuid) must be(Some(user.guid)) - emailVerificationsDao.findByGuid(UUID.randomUUID) must be(None) - } - - "findByToken" in { - val user = createRandomUser() - val verification = emailVerificationsDao.create(testUser, user, user.email) - - emailVerificationsDao.findByToken(verification.token).map(_.userGuid) must be(Some(user.guid)) - emailVerificationsDao.findByToken(UUID.randomUUID.toString) must be(None) - } - - "findAll" in { - val user1 = createRandomUser() - val verification1 = emailVerificationsDao.create(testUser, user1, user1.email) - - val user2 = createRandomUser() - val verification2 = emailVerificationsDao.create(testUser, user2, user2.email) - - emailVerificationsDao.findAll(userGuid = Some(user1.guid), limit = None).map(_.userGuid).distinct must be(Seq(user1.guid)) - emailVerificationsDao.findAll(userGuid = Some(user2.guid), limit = None).map(_.userGuid).distinct must be(Seq(user2.guid)) - emailVerificationsDao.findAll(userGuid = Some(UUID.randomUUID), limit = None).map(_.userGuid).distinct must be(Nil) - - emailVerificationsDao.findAll(isExpired = Some(false), userGuid = Some(user1.guid), limit = None).map(_.userGuid).distinct must be(Seq(user1.guid)) - emailVerificationsDao.findAll(isExpired = Some(true), userGuid = Some(user1.guid), limit = None).map(_.userGuid).distinct must be(Nil) - - emailVerificationsDao.findAll(email = Some(user1.email), limit = None).map(_.userGuid).distinct must be(Seq(user1.guid)) - emailVerificationsDao.findAll(email = Some(user1.email.toUpperCase), limit = None).map(_.userGuid).distinct must be(Seq(user1.guid)) - emailVerificationsDao.findAll(email = Some(user2.email), limit = None).map(_.userGuid).distinct must be(Seq(user2.guid)) - emailVerificationsDao.findAll(email = Some(UUID.randomUUID.toString), limit = None).map(_.userGuid).distinct must be(Nil) - - emailVerificationsDao.findAll(guid = Some(verification1.guid), limit = None).map(_.userGuid).distinct must be(Seq(user1.guid)) - emailVerificationsDao.findAll(guid = Some(verification2.guid), limit = None).map(_.userGuid).distinct must be(Seq(user2.guid)) - emailVerificationsDao.findAll(guid = Some(UUID.randomUUID), limit = None).map(_.userGuid).distinct must be(Nil) - - emailVerificationsDao.findAll(token = Some(verification1.token), limit = None).map(_.userGuid).distinct must be(Seq(user1.guid)) - emailVerificationsDao.findAll(token = Some(verification2.token), limit = None).map(_.userGuid).distinct must be(Seq(user2.guid)) - emailVerificationsDao.findAll(token = Some("bad"), limit = None).map(_.userGuid).distinct must be(Nil) - } - - "membership requests confirm auto approves pending membership requests based on org email domain" in { - val org = createOrganization() - val domain = UUID.randomUUID.toString + ".com" - - organizationDomainsDao.create(testUser, org, domain) - - val prefix = "test-user-" + UUID.randomUUID.toString - - val user = createUser(UserForm( - email = prefix + "@" + domain, - password = "testing" - )) - - val nonMatchingUser = createUser(UserForm( - email = prefix + "@other." + domain, - password = "testing" - )) - - userCreatedProcessor.processRecord(user.guid) - userCreatedProcessor.processRecord(nonMatchingUser.guid) - - 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.reference) must be(true) - membershipsDao.isUserMember(nonMatchingUser, org.reference) must be(false) - } - -} diff --git a/api/test/db/InternalPasswordResetsDaoSpec.scala b/api/test/db/InternalPasswordResetsDaoSpec.scala deleted file mode 100644 index 4c7ae2020..000000000 --- a/api/test/db/InternalPasswordResetsDaoSpec.scala +++ /dev/null @@ -1,73 +0,0 @@ -package db - -import helpers.ValidatedTestHelpers - -import java.util.UUID -import org.scalatestplus.play.PlaySpec -import org.scalatestplus.play.guice.GuiceOneAppPerSuite -import services.EmailVerificationsService - -class InternalPasswordResetsDaoSpec extends PlaySpec with GuiceOneAppPerSuite with Helpers with ValidatedTestHelpers { - - private val service = app.injector.instanceOf[EmailVerificationsService] - - "create" in { - val user = createRandomUser() - val pr = passwordResetsDao.create(Some(testUser), user) - pr.userGuid must be(user.guid) - } - - "isExpired" in { - val user = createRandomUser() - val verification = emailVerificationsDao.create(testUser, user, user.email) - expectValid { - service.confirm(None, verification) - } - } - - "resetPassword" in { - val user = createRandomUser() - val pr = passwordResetsDao.create(Some(testUser), user) - - val newPassword = "testing" - userPasswordsDao.isValid(user.guid, newPassword) must be(false) - passwordResetsDao.resetPassword(None, pr, newPassword) - userPasswordsDao.isValid(user.guid, newPassword) must be(true) - } - - "findByGuid" in { - val user = createRandomUser() - val pr = passwordResetsDao.create(None, user) - - passwordResetsDao.findByGuid(pr.guid).map(_.userGuid) must be(Some(user.guid)) - passwordResetsDao.findByGuid(UUID.randomUUID) must be(None) - } - - "findByToken" in { - val user = createRandomUser() - val pr = passwordResetsDao.create(None, user) - - passwordResetsDao.findByToken(pr.token).map(_.userGuid) must be(Some(user.guid)) - passwordResetsDao.findByToken(UUID.randomUUID.toString) must be(None) - } - - "findAll" in { - val user1 = createRandomUser() - val pr1 = passwordResetsDao.create(None, user1) - - val user2 = createRandomUser() - val pr2 = passwordResetsDao.create(None, user2) - - passwordResetsDao.findAll(isExpired = Some(false), guid = Some(pr1.guid), limit = None).map(_.userGuid) must be(Seq(user1.guid)) - passwordResetsDao.findAll(isExpired = Some(true), guid = Some(pr1.guid), limit = None).map(_.userGuid) must be(Nil) - - passwordResetsDao.findAll(guid = Some(pr1.guid), limit = None).map(_.userGuid) must be(Seq(user1.guid)) - passwordResetsDao.findAll(guid = Some(pr2.guid), limit = None).map(_.userGuid) must be(Seq(user2.guid)) - passwordResetsDao.findAll(guid = Some(UUID.randomUUID), limit = None).map(_.userGuid) must be(Nil) - - passwordResetsDao.findAll(token = Some(pr1.token), limit = None).map(_.userGuid) must be(Seq(user1.guid)) - passwordResetsDao.findAll(token = Some(pr2.token), limit = None).map(_.userGuid) must be(Seq(user2.guid)) - passwordResetsDao.findAll(token = Some("bad"), limit = None).map(_.userGuid) must be(Nil) - } - -} diff --git a/api/test/services/EmailVerificationsServiceSpec.scala b/api/test/services/EmailVerificationsServiceSpec.scala deleted file mode 100644 index 267b2f467..000000000 --- a/api/test/services/EmailVerificationsServiceSpec.scala +++ /dev/null @@ -1,48 +0,0 @@ -package services - -import db.{DbUtils, InternalEmailVerificationConfirmationsDao, Helpers} -import helpers.ValidatedTestHelpers -import io.flow.postgresql.Query -import org.scalatestplus.play.PlaySpec -import org.scalatestplus.play.guice.GuiceOneAppPerSuite - -class EmailVerificationsServiceSpec extends PlaySpec with GuiceOneAppPerSuite with Helpers with ValidatedTestHelpers with DbUtils { - - private def emailVerificationConfirmationsDao: InternalEmailVerificationConfirmationsDao = injector.instanceOf[InternalEmailVerificationConfirmationsDao] - private def service: EmailVerificationsService = injector.instanceOf[EmailVerificationsService] - - "isExpired" must { - val user = createRandomUser() - - "expired" in { - val verification = emailVerificationsDao.upsert(testUser, user, user.email) - execute( - Query("update email_verifications set expires_at = now() - interval '1 month'") - .equals("guid", verification.guid) - ) - val v2 = emailVerificationsDao.findByGuid(verification.guid).get - - expectInvalid { - service.confirm(None, v2) - } mustBe Seq(s"Token for verificationGuid[${v2.guid}] is expired") - } - - "not expired" in { - val verification = emailVerificationsDao.upsert(testUser, user, user.email) - - expectValid { - service.confirm(None, verification) - } - } - } - - "confirm" in { - val user = createRandomUser() - val verification = emailVerificationsDao.upsert(testUser, user, user.email) - emailVerificationConfirmationsDao.findAll(emailVerificationGuid = Some(verification.guid), limit = None) must be(Nil) - - service.confirm(None, verification) - emailVerificationConfirmationsDao.findAll(emailVerificationGuid = Some(verification.guid), limit = None).map(_.emailVerificationGuid) must be(Seq(verification.guid)) - } - -} diff --git a/api/test/util/Daos.scala b/api/test/util/Daos.scala index c59946db9..30341743e 100644 --- a/api/test/util/Daos.scala +++ b/api/test/util/Daos.scala @@ -16,7 +16,6 @@ trait Daos { def attributesDao: InternalAttributesDao = injector.instanceOf[db.InternalAttributesDao] def changesDao: InternalChangesDao = injector.instanceOf[db.InternalChangesDao] def databaseServiceFetcher: DatabaseServiceFetcher = injector.instanceOf[DatabaseServiceFetcher] - def emailVerificationsDao: InternalEmailVerificationsDao = injector.instanceOf[db.InternalEmailVerificationsDao] def itemsDao: ItemsDao = injector.instanceOf[db.ItemsDao] def membershipRequestsDao: InternalMembershipRequestsDao = injector.instanceOf[db.InternalMembershipRequestsDao] def membershipsDao: InternalMembershipsDao = injector.instanceOf[db.InternalMembershipsDao] @@ -27,7 +26,6 @@ trait Daos { def organizationLogsDao: OrganizationLogsDao = injector.instanceOf[db.OrganizationLogsDao] def organizationsDao: InternalOrganizationsDao = injector.instanceOf[db.InternalOrganizationsDao] def originalsDao: InternalOriginalsDao = injector.instanceOf[db.InternalOriginalsDao] - def passwordResetsDao: InternalPasswordResetsDao = injector.instanceOf[db.InternalPasswordResetsDao] def sessionsDao: SessionsDao = injector.instanceOf[SessionsDao] def subscriptionsDao: InternalSubscriptionsDao = injector.instanceOf[db.InternalSubscriptionsDao] diff --git a/spec/apibuilder-api.json b/spec/apibuilder-api.json index 28c9a9aeb..4b1233363 100644 --- a/spec/apibuilder-api.json +++ b/spec/apibuilder-api.json @@ -2,7 +2,6 @@ "name": "apibuilder api", "description": "Host API documentation for applications providing REST APIs, facilitating the design of good resource first APIs.", "base_url": "https://api.apibuilder.io", - "info": { "contact": { "name": "Michael Bryzek", @@ -14,566 +13,1103 @@ "url": "http://opensource.org/licenses/MIT" } }, - "unions": { "diff": { - "interfaces": ["diff"], + "interfaces": [ + "diff" + ], "description": "Represents a single diff in an application", "discriminator": "type", "types": [ - { "type": "diff_breaking" }, - { "type": "diff_non_breaking" } + { + "type": "diff_breaking" + }, + { + "type": "diff_non_breaking" + } ] }, - "item_detail": { "description": "Identifies the specific type of item that was indexed by search", "discriminator": "type", "types": [ - { "type": "application_summary", "description": "Represents that the item indexed was an application" } + { + "type": "application_summary", + "description": "Represents that the item indexed was an application" + } ] } - }, - "imports": [ - { "uri": "https://app.apibuilder.io/apicollective/apibuilder-spec/latest/service.json" }, - { "uri": "https://app.apibuilder.io/apicollective/apibuilder-common/latest/service.json" }, - { "uri": "https://app.apibuilder.io/apicollective/apibuilder-generator/latest/service.json" } + { + "uri": "https://app.apibuilder.io/apicollective/apibuilder-spec/latest/service.json" + }, + { + "uri": "https://app.apibuilder.io/apicollective/apibuilder-common/latest/service.json" + }, + { + "uri": "https://app.apibuilder.io/apicollective/apibuilder-generator/latest/service.json" + } ], - "interfaces": { "diff": { "description": "Represents a single breaking diff of an application version. A breaking diff indicates that it is possible for an existing client to now experience an error or invalid data due to the diff.", "fields": [ - { "name": "description", "type": "string", "example": "model removed: user" }, - { "name": "is_material", "type": "boolean", "example": "True if this is a material change (eg something important). False otherwise. This is used to drive the publication 'versions.material_change'" } + { + "name": "description", + "type": "string", + "example": "model removed: user" + }, + { + "name": "is_material", + "type": "boolean", + "example": "True if this is a material change (eg something important). False otherwise. This is used to drive the publication 'versions.material_change'" + } ] } }, - "enums": { "visibility": { "description": "Controls who is able to view this version", "values": [ - { "name": "user", "description": "Only the creator can view this application" }, - { "name": "organization", "description": "Any member of the organization can view this application" }, - { "name": "public", "description": "Anybody, including non logged in users, can view this application" } + { + "name": "user", + "description": "Only the creator can view this application" + }, + { + "name": "organization", + "description": "Any member of the organization can view this application" + }, + { + "name": "public", + "description": "Anybody, including non logged in users, can view this application" + } ] }, - "publication": { "description": "A publication represents something that a user can subscribe to. An example would be subscribing to an email alert whenever a new version of an application is created.", "values": [ - { "name": "membership_requests.create", "description": "For organizations for which I am an administrator, email me whenever a user applies to join the org." }, - { "name": "memberships.create", "description": "For organizations for which I am a member, email me whenever a user joins the org." }, - { "name": "applications.create", "description": "For organizations for which I am a member, email me whenever an application is created." }, - { "name": "versions.create", "description": "For applications that I watch, email me whenever a version is created." }, - { "name": "versions.material_change", "description": "For applications that I watch, email me whenever there is a material change. Generally, this means avoiding email for simple changes (like only a version change)." } + { + "name": "membership_requests.create", + "description": "For organizations for which I am an administrator, email me whenever a user applies to join the org." + }, + { + "name": "memberships.create", + "description": "For organizations for which I am a member, email me whenever a user joins the org." + }, + { + "name": "applications.create", + "description": "For organizations for which I am a member, email me whenever an application is created." + }, + { + "name": "versions.create", + "description": "For applications that I watch, email me whenever a version is created." + }, + { + "name": "versions.material_change", + "description": "For applications that I watch, email me whenever there is a material change. Generally, this means avoiding email for simple changes (like only a version change)." + } ] }, - "original_type": { "values": [ - { "name": "api_json", "description": "The original is in the api.json format" }, - { "name": "avro_idl", "description": "The original is in Avro Idl format" }, - { "name": "service_json", "description": "This is the canonical service spec for apibuilder itself. See https://www.apibuilder.io/apicollective/apibuilder-spec/latest#model-service" }, - { "name": "swagger", "description": "The original in the Swagger JSON or YAML format" } + { + "name": "api_json", + "description": "The original is in the api.json format" + }, + { + "name": "avro_idl", + "description": "The original is in Avro Idl format" + }, + { + "name": "service_json", + "description": "This is the canonical service spec for apibuilder itself. See https://www.apibuilder.io/apicollective/apibuilder-spec/latest#model-service" + }, + { + "name": "swagger", + "description": "The original in the Swagger JSON or YAML format" + } ] }, - "app_sort_by": { "values": [ - { "name": "name" }, - { "name": "created_at" }, - { "name": "updated_at" }, - { "name": "visibility" } + { + "name": "name" + }, + { + "name": "created_at" + }, + { + "name": "updated_at" + }, + { + "name": "visibility" + } ] }, - "sort_order": { "values": [ - { "name": "asc" }, - { "name": "desc" } + { + "name": "asc" + }, + { + "name": "desc" + } ] } }, - "models": { - "validation": { "description": "Used only to validate json files - used as a resource where http status code defines success", "fields": [ - { "name": "valid", "type": "boolean", "description": "If true, the json schema is valid. If false, there is at least one validation error." }, - { "name": "errors", "type": "[string]", "default": "[]", "description": "Contains any validation errors that result from parsing the json document. If empty, the document is valid." } + { + "name": "valid", + "type": "boolean", + "description": "If true, the json schema is valid. If false, there is at least one validation error." + }, + { + "name": "errors", + "type": "[string]", + "default": "[]", + "description": "Contains any validation errors that result from parsing the json document. If empty, the document is valid." + } ] }, - "attribute": { "description": "Attributes are globally unique key which allow users to specify additional content to pass in to the code generators.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this attribute." }, - { "name": "name", "type": "string", "description": "The name of the attribute. Globally unique and an 'identifier' (lower case, url safe, etc.)" }, - { "name": "description", "type": "string", "description": "Optional description - a good description here will indicate which code generators it applies to and what effect it will have on those code generators.", "required": false }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this attribute." + }, + { + "name": "name", + "type": "string", + "description": "The name of the attribute. Globally unique and an 'identifier' (lower case, url safe, etc.)" + }, + { + "name": "description", + "type": "string", + "description": "Optional description - a good description here will indicate which code generators it applies to and what effect it will have on those code generators.", + "required": false + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "attribute_form": { "fields": [ - { "name": "name", "type": "string" }, - { "name": "description", "type": "string", "required": false } + { + "name": "name", + "type": "string" + }, + { + "name": "description", + "type": "string", + "required": false + } ] }, - "attribute_summary": { "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this attribute." }, - { "name": "name", "type": "string", "description": "The name of the attribute. Globally unique and an 'identifier' (lower case, url safe, etc.)" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this attribute." + }, + { + "name": "name", + "type": "string", + "description": "The name of the attribute. Globally unique and an 'identifier' (lower case, url safe, etc.)" + } ] }, - "attribute_value": { "description": "Attribute values can be set at different levels. Initially we support setting organization wide attributes, but in the future plan to support setting attribute values with each version of the application.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this attribute value." }, - { "name": "attribute", "type": "attribute_summary" }, - { "name": "value", "type": "string" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this attribute value." + }, + { + "name": "attribute", + "type": "attribute_summary" + }, + { + "name": "value", + "type": "string" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "attribute_value_form": { "fields": [ - { "name": "value", "type": "string" } + { + "name": "value", + "type": "string" + } ] }, - "user": { "description": "A user is a top level person interacting with the api doc server.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this user." }, - { "name": "email", "type": "string" }, - { "name": "nickname", "type": "string", "description": "Public unique identifier for this user." }, - { "name": "name", "type": "string", "required": false }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this user." + }, + { + "name": "email", + "type": "string" + }, + { + "name": "nickname", + "type": "string", + "description": "Public unique identifier for this user." + }, + { + "name": "name", + "type": "string", + "required": false + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "session": { "description": "Represents a user sessions (e.g. user logged into site)", "fields": [ - { "name": "id", "type": "string", "description": "Cryptographically secure session id" }, - { "name": "expires_at", "type": "date-time-iso8601" } + { + "name": "id", + "type": "string", + "description": "Cryptographically secure session id" + }, + { + "name": "expires_at", + "type": "date-time-iso8601" + } ] }, - "authentication": { "description": "Represents the result of a successful authorization", "fields": [ - { "name": "user", "type": "user" }, - { "name": "session", "type": "session" } + { + "name": "user", + "type": "user" + }, + { + "name": "session", + "type": "session" + } ] }, - "user_summary": { "description": "Summary of a user sufficient for display", "fields": [ - { "name": "guid", "type": "uuid" }, - { "name": "nickname", "type": "string" } + { + "name": "guid", + "type": "uuid" + }, + { + "name": "nickname", + "type": "string" + } ] }, - "user_form": { "fields": [ - { "name": "email", "type": "string" }, - { "name": "password", "type": "string" }, - { "name": "nickname", "type": "string", "required": false, "description": "Defaults to a unique identifier based on email address" }, - { "name": "name", "type": "string", "required": false } + { + "name": "email", + "type": "string" + }, + { + "name": "password", + "type": "string" + }, + { + "name": "nickname", + "type": "string", + "required": false, + "description": "Defaults to a unique identifier based on email address" + }, + { + "name": "name", + "type": "string", + "required": false + } ] }, - "user_update_form": { "fields": [ - { "name": "email", "type": "string" }, - { "name": "nickname", "type": "string" }, - { "name": "name", "type": "string", "required": false } + { + "name": "email", + "type": "string" + }, + { + "name": "nickname", + "type": "string" + }, + { + "name": "name", + "type": "string", + "required": false + } ] }, - "token": { "description": "A token gives a user access to the API.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this token." }, - { "name": "user", "type": "user", "description": "The user to which this token belongs." }, - { "name": "masked_token", "type": "string", "description": "The masked from of the token." }, - { "name": "description", "type": "string", "description": "optional description to help the user manage the token.", "required": false }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this token." + }, + { + "name": "user", + "type": "user", + "description": "The user to which this token belongs." + }, + { + "name": "masked_token", + "type": "string", + "description": "The masked from of the token." + }, + { + "name": "description", + "type": "string", + "description": "optional description to help the user manage the token.", + "required": false + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "cleartext_token": { "description": "Separate resource used only for the few actions that require the full token.", "fields": [ - { "name": "token", "type": "string", "description": "The actual token. Guaranteed to be globally unique." } + { + "name": "token", + "type": "string", + "description": "The actual token. Guaranteed to be globally unique." + } ] }, - "token_form": { "fields": [ - { "name": "user_guid", "type": "uuid", "description": "The user for which we are creating the token." }, - { "name": "description", "type": "string", "required": false } + { + "name": "user_guid", + "type": "uuid", + "description": "The user for which we are creating the token." + }, + { + "name": "description", + "type": "string", + "required": false + } ] }, - "organization": { "description": "An organization is used to group a set of applications together.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this organization." }, - { "name": "key", "type": "string", "description": "Used as a unique key in the URL path. Key is automatically derived from the organization name." }, - { "name": "name", "type": "string", "description": "The name of this organization." }, - { "name": "namespace", "type": "string", "description": "Global namespace for this organization.", "example": "io.apicollective" }, - { "name": "visibility", "type": "visibility" }, - { "name": "domains", "type": "[domain]", "default": "[]" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this organization." + }, + { + "name": "key", + "type": "string", + "description": "Used as a unique key in the URL path. Key is automatically derived from the organization name." + }, + { + "name": "name", + "type": "string", + "description": "The name of this organization." + }, + { + "name": "namespace", + "type": "string", + "description": "Global namespace for this organization.", + "example": "io.apicollective" + }, + { + "name": "visibility", + "type": "visibility" + }, + { + "name": "domains", + "type": "[domain]", + "default": "[]" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "organization_form": { "fields": [ - { "name": "name", "type": "string" }, - { "name": "key", "type": "string", "required": false, "description": "Defaults to a url friendy version of the name" }, - { "name": "namespace", "type": "string", "description": "Global namespace for this organization.", "example": "io.apicollective" }, - { "name": "visibility", "type": "visibility", "description": "Public organizations will be listed in apibuilder directory. Organizations with visibility organization will only be visible to members of that org.", "default": "organization" }, - { "name": "domains", "type": "[string]", "required": false, "example": "apibuilder.io" } + { + "name": "name", + "type": "string" + }, + { + "name": "key", + "type": "string", + "required": false, + "description": "Defaults to a url friendy version of the name" + }, + { + "name": "namespace", + "type": "string", + "description": "Global namespace for this organization.", + "example": "io.apicollective" + }, + { + "name": "visibility", + "type": "visibility", + "description": "Public organizations will be listed in apibuilder directory. Organizations with visibility organization will only be visible to members of that org.", + "default": "organization" + }, + { + "name": "domains", + "type": "[string]", + "required": false, + "example": "apibuilder.io" + } ] }, - "domain": { "description": "Represents a single domain name (e.g. www.apibuilder.io). When a new user registers and confirms their email, we automatically associate that user with a member of the organization associated with their domain. For example, if you confirm your account with an email address of example@somedomain.com, we will automatically create a membership request on your behalf to join the organization with domain somedomain.com.", "fields": [ - { "name": "name", "type": "string", "description": "The domain name", "example": "www.apibuilder.io" } + { + "name": "name", + "type": "string", + "description": "The domain name", + "example": "www.apibuilder.io" + } ] }, - "membership": { "description": "A membership represents a user in a specific role to an organization. Memberships cannot be created directly. Instead you first create a membership request, then that request is either accepted or declined.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this membership." }, - { "name": "user", "type": "user" }, - { "name": "organization", "type": "organization" }, - { "name": "role", "type": "io.apibuilder.common.v0.enums.membership_role", "description": "The role this user plays for this organization. Typically member or admin.", "example": "member" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this membership." + }, + { + "name": "user", + "type": "user" + }, + { + "name": "organization", + "type": "organization" + }, + { + "name": "role", + "type": "io.apibuilder.common.v0.enums.membership_role", + "description": "The role this user plays for this organization. Typically member or admin.", + "example": "member" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "membership_request": { "description": "A membership request represents a user requesting to join an organization with a specific role (e.g. as a member or an admin). Membership requests can be reviewed by any current admin of the organization who can either accept or decline the request.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this membership request." }, - { "name": "user", "type": "user" }, - { "name": "organization", "type": "organization" }, - { "name": "role", "type": "io.apibuilder.common.v0.enums.membership_role", "description": "The requested role for membership to this organization. Typically member or admin.", "example": "member" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this membership request." + }, + { + "name": "user", + "type": "user" + }, + { + "name": "organization", + "type": "organization" + }, + { + "name": "role", + "type": "io.apibuilder.common.v0.enums.membership_role", + "description": "The requested role for membership to this organization. Typically member or admin.", + "example": "member" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "application": { "description": "An application has a name and multiple versions of its API.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this application." }, - { "name": "organization", "type": "io.apibuilder.common.v0.models.reference" }, - { "name": "name", "type": "string", "description": "The unique name for this application." }, - { "name": "key", "type": "string", "description": "Used as a unique key in the URL path. Key is automatically derived from the application name." }, - { "name": "visibility", "type": "visibility", "description": "Controls who is able to view this application" }, - { "name": "description", "type": "string", "required": false }, - { "name": "last_updated_at", "type": "date-time-iso8601", "description": "The updated_at of this application or created_at of it's latest version" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this application." + }, + { + "name": "organization", + "type": "io.apibuilder.common.v0.models.reference" + }, + { + "name": "name", + "type": "string", + "description": "The unique name for this application." + }, + { + "name": "key", + "type": "string", + "description": "Used as a unique key in the URL path. Key is automatically derived from the application name." + }, + { + "name": "visibility", + "type": "visibility", + "description": "Controls who is able to view this application" + }, + { + "name": "description", + "type": "string", + "required": false + }, + { + "name": "last_updated_at", + "type": "date-time-iso8601", + "description": "The updated_at of this application or created_at of it's latest version" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "application_form": { "fields": [ - { "name": "name", "type": "string" }, - { "name": "key", "type": "string", "required": false, "description": "Defaults to a key generated from the application name" }, - { "name": "description", "type": "string", "required": false }, - { "name": "visibility", "type": "visibility" } + { + "name": "name", + "type": "string" + }, + { + "name": "key", + "type": "string", + "required": false, + "description": "Defaults to a key generated from the application name" + }, + { + "name": "description", + "type": "string", + "required": false + }, + { + "name": "visibility", + "type": "visibility" + } ] }, - "application_summary": { "description": "Summary of an application sufficient for display and links", "fields": [ - { "name": "guid", "type": "uuid" }, - { "name": "organization", "type": "io.apibuilder.common.v0.models.reference" }, - { "name": "key", "type": "string" } + { + "name": "guid", + "type": "uuid" + }, + { + "name": "organization", + "type": "io.apibuilder.common.v0.models.reference" + }, + { + "name": "key", + "type": "string" + } ] }, - - "original": { "description": "Represents the original input used to create an application version", "fields": [ - { "name": "type", "type": "original_type" }, - { "name": "data", "type": "string" } + { + "name": "type", + "type": "original_type" + }, + { + "name": "data", + "type": "string" + } ] }, - "original_form": { "fields": [ - { "name": "type", "type": "original_type", "required": false, "description": "If not specified, we set the type by inspecting the data" }, - { "name": "data", "type": "string" } + { + "name": "type", + "type": "original_type", + "required": false, + "description": "If not specified, we set the type by inspecting the data" + }, + { + "name": "data", + "type": "string" + } ] }, - "version": { "description": "Represents a unique version of the application.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this version." }, - { "name": "organization", "type": "io.apibuilder.common.v0.models.reference" }, - { "name": "application", "type": "io.apibuilder.common.v0.models.reference" }, - { "name": "version", "type": "string", "description": "The tag for this version. Can be anything, but if semver style version number is used, we automatically correctly sort by version number to find latest. Otherwise latest version is considered to be the most recently created.", "example": "1.0.0" }, - { "name": "original", "type": "original", "required": false, "description": "The original uploaded file describing this version, if available" }, - { "name": "service", "type": "io.apibuilder.spec.v0.models.service", "description": "spec/spec.json description of this API" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this version." + }, + { + "name": "organization", + "type": "io.apibuilder.common.v0.models.reference" + }, + { + "name": "application", + "type": "io.apibuilder.common.v0.models.reference" + }, + { + "name": "version", + "type": "string", + "description": "The tag for this version. Can be anything, but if semver style version number is used, we automatically correctly sort by version number to find latest. Otherwise latest version is considered to be the most recently created.", + "example": "1.0.0" + }, + { + "name": "original", + "type": "original", + "required": false, + "description": "The original uploaded file describing this version, if available" + }, + { + "name": "service", + "type": "io.apibuilder.spec.v0.models.service", + "description": "spec/spec.json description of this API" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "version_form": { "fields": [ - { "name": "original_form", "type": "original_form" }, - { "name": "visibility", "type": "visibility", "required": false, "description": "If provided, updates the visibility for all versions of this application" } + { + "name": "original_form", + "type": "original_form" + }, + { + "name": "visibility", + "type": "visibility", + "required": false, + "description": "If provided, updates the visibility for all versions of this application" + } ] }, - "code": { "description": "Generated source code.", "plural": "code", "fields": [ - { "name": "generator", "type": "generator_with_service" }, - { "name": "source", "type": "string", "description": "The actual source code.", "deprecation": { "description": "Use files instead" } }, - { "name": "files", "type": "[io.apibuilder.generator.v0.models.file]", "description": "A collection of source files", "default": "[]" } + { + "name": "generator", + "type": "generator_with_service" + }, + { + "name": "source", + "type": "string", + "description": "The actual source code.", + "deprecation": { + "description": "Use files instead" + } + }, + { + "name": "files", + "type": "[io.apibuilder.generator.v0.models.file]", + "description": "A collection of source files", + "default": "[]" + } ] }, - "code_form": { "fields": [ - { "name": "attributes", "type": "[io.apibuilder.generator.v0.models.attribute]" } + { + "name": "attributes", + "type": "[io.apibuilder.generator.v0.models.attribute]" + } ] }, - "subscription": { "description": "Represents a user that is currently subscribed to a publication", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this subscription record" }, - { "name": "organization", "type": "organization" }, - { "name": "user", "type": "user" }, - { "name": "publication", "type": "publication" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this subscription record" + }, + { + "name": "organization", + "type": "organization" + }, + { + "name": "user", + "type": "user" + }, + { + "name": "publication", + "type": "publication" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "subscription_form": { "fields": [ - { "name": "organization_key", "type": "string" }, - { "name": "user_guid", "type": "uuid" }, - { "name": "publication", "type": "publication" } + { + "name": "organization_key", + "type": "string" + }, + { + "name": "user_guid", + "type": "uuid" + }, + { + "name": "publication", + "type": "publication" + } ] }, - "watch": { "description": "Users can watch individual applications which enables features like receiving an email notification when there is a new version of an application.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this watch" }, - { "name": "user", "type": "user" }, - { "name": "organization", "type": "organization" }, - { "name": "application", "type": "application" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this watch" + }, + { + "name": "user", + "type": "user" + }, + { + "name": "organization", + "type": "organization" + }, + { + "name": "application", + "type": "application" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "watch_form": { "fields": [ - { "name": "user_guid", "type": "uuid" }, - { "name": "organization_key", "type": "string" }, - { "name": "application_key", "type": "string" } - ] - }, - - "email_verification_confirmation_form": { - "description": "Data used to confirm an email address. The token is an internal unique identifier used to lookup the specific email address and user account for which we sent an email verification email.", - "fields": [ - { "name": "token", "type": "string" } - ] - }, - - "password_reset_request": { - "description": "Create a password reset request - e.g. an email containing a one time URL to change a password", - "fields": [ - { "name": "email", "type": "string", "description": "The email address for which we generate the password reset." } - ] - }, - - "password_reset": { - "description": "Allows a user to change their password with authentication from a token.", - "fields": [ - { "name": "token", "type": "string", "description": "Unique one time use token to change a password" }, - { "name": "password", "type": "string" } + { + "name": "user_guid", + "type": "uuid" + }, + { + "name": "organization_key", + "type": "string" + }, + { + "name": "application_key", + "type": "string" + } ] }, - "error": { "fields": [ - { "name": "code", "type": "string", "description": "Machine readable code for this specific error message" }, - { "name": "message", "type": "string", "description": "Description of the error" } + { + "name": "code", + "type": "string", + "description": "Machine readable code for this specific error message" + }, + { + "name": "message", + "type": "string", + "description": "Description of the error" + } ] }, - "diff_breaking": { "description": "Represents a single breaking diff of an application version. A breaking diff indicates that it is possible for an existing client to now experience an error or invalid data due to the diff.", "fields": [ - { "name": "description", "type": "string", "example": "model removed: user" }, - { "name": "is_material", "type": "boolean", "example": "True if this is a material change (eg something important). False otherwise. This is used to drive the publication 'versions.material_change'" } + { + "name": "description", + "type": "string", + "example": "model removed: user" + }, + { + "name": "is_material", + "type": "boolean", + "example": "True if this is a material change (eg something important). False otherwise. This is used to drive the publication 'versions.material_change'" + } ] }, - "diff_non_breaking": { "description": "Represents a single NON breaking diff of an application version.", "fields": [ - { "name": "description", "type": "string", "example": "model: user optional field 'name' added" }, - { "name": "is_material", "type": "boolean", "example": "True if this is a material change (eg something important). False otherwise. This is used to drive the publication 'versions.material_change'" } + { + "name": "description", + "type": "string", + "example": "model: user optional field 'name' added" + }, + { + "name": "is_material", + "type": "boolean", + "example": "True if this is a material change (eg something important). False otherwise. This is used to drive the publication 'versions.material_change'" + } ] }, - "item": { "description": "When searching for content, the results of the search will be a list of items. Each item will have enough information to render for the user, including a type and item_guid to enable creating the appropriate link.", "fields": [ - { "name": "guid", "type": "uuid", "description": "Unique identifer for this item. By using a UUID, you can combine with the type to figure out the URI for the resource" }, - { "name": "detail", "type": "item_detail" }, - { "name": "label", "type": "string" }, - { "name": "description", "type": "string", "required": false } + { + "name": "guid", + "type": "uuid", + "description": "Unique identifer for this item. By using a UUID, you can combine with the type to figure out the URI for the resource" + }, + { + "name": "detail", + "type": "item_detail" + }, + { + "name": "label", + "type": "string" + }, + { + "name": "description", + "type": "string", + "required": false + } ] }, - "change": { "description": "Represents a single change from one version of a service to another", "fields": [ - { "name": "guid", "type": "uuid" }, - { "name": "organization", "type": "io.apibuilder.common.v0.models.reference" }, - { "name": "application", "type": "io.apibuilder.common.v0.models.reference" }, - { "name": "from_version", "type": "change_version" }, - { "name": "to_version", "type": "change_version" }, - { "name": "diff", "type": "diff" }, - { "name": "changed_at", "type": "date-time-iso8601", "description": "Records the timestamp of when the actual change occurred (vs. when we created the changed record)" }, - { "name": "changed_by", "type": "user_summary", "description": "Records who made the actual change" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } - ] - }, - + { + "name": "guid", + "type": "uuid" + }, + { + "name": "organization", + "type": "io.apibuilder.common.v0.models.reference" + }, + { + "name": "application", + "type": "io.apibuilder.common.v0.models.reference" + }, + { + "name": "from_version", + "type": "change_version" + }, + { + "name": "to_version", + "type": "change_version" + }, + { + "name": "diff", + "type": "diff" + }, + { + "name": "changed_at", + "type": "date-time-iso8601", + "description": "Records the timestamp of when the actual change occurred (vs. when we created the changed record)" + }, + { + "name": "changed_by", + "type": "user_summary", + "description": "Records who made the actual change" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } + ] + }, "change_version": { "description": "Represents a simpler model of a version specifically for the use case of displaying changes", "fields": [ - { "name": "guid", "type": "uuid" }, - { "name": "version", "type": "string" } + { + "name": "guid", + "type": "uuid" + }, + { + "name": "version", + "type": "string" + } ] }, - "generator_service": { "description": "Defines a service that provides one or more code generators", "fields": [ - { "name": "guid", "type": "uuid" }, - { "name": "uri", "type": "string" }, - { "name": "audit", "type": "io.apibuilder.common.v0.models.audit" } + { + "name": "guid", + "type": "uuid" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "audit", + "type": "io.apibuilder.common.v0.models.audit" + } ] }, - "generator_service_form": { "fields": [ - { "name": "uri", "type": "string" } + { + "name": "uri", + "type": "string" + } ] }, - "generator_with_service": { "description": "Wraps a service and a generator providing easier access for applications.", "fields": [ - { "name": "service", "type": "generator_service" }, - { "name": "generator", "type": "io.apibuilder.generator.v0.models.generator" } + { + "name": "service", + "type": "generator_service" + }, + { + "name": "generator", + "type": "io.apibuilder.generator.v0.models.generator" + } ] }, - "generator_form": { "fields": [ - { "name": "service_guid", "type": "uuid" }, - { "name": "generator", "type": "io.apibuilder.generator.v0.models.generator" } + { + "name": "service_guid", + "type": "uuid" + }, + { + "name": "generator", + "type": "io.apibuilder.generator.v0.models.generator" + } ] }, - "move_form": { "fields": [ - { "name": "org_key", "type": "string", "description": "The key of the organization to which we are moving this item" } + { + "name": "org_key", + "type": "string", + "description": "The key of the organization to which we are moving this item" + } ] }, - "application_metadata": { "fields": [ - { "name": "guid", "type": "uuid", "description": "Internal unique identifier for this application." }, - { "name": "key", "type": "string", "description": "Used as a unique key in the URL path. Key is automatically derived from the application name." } + { + "name": "guid", + "type": "uuid", + "description": "Internal unique identifier for this application." + }, + { + "name": "key", + "type": "string", + "description": "Used as a unique key in the URL path. Key is automatically derived from the application name." + } ] }, - "application_metadata_version": { "description": "Describes the versions associated with a given application", "fields": [ - { "name": "version", "type": "string", "example": "0.2.5" } + { + "name": "version", + "type": "string", + "example": "0.2.5" + } ] }, - "batch_download_applications": { "plural": "batch_download_applications", "fields": [ - { "name": "applications", "type": "[version]" } + { + "name": "applications", + "type": "[version]" + } ] }, - "batch_download_applications_form": { "fields": [ - { "name": "applications", "type": "[batch_download_application_form]" } + { + "name": "applications", + "type": "[batch_download_application_form]" + } ] }, - "batch_download_application_form": { "fields": [ - { "name": "application_key", "type": "string" }, - { "name": "version", "type": "string", "default": "latest", "required": false } + { + "name": "application_key", + "type": "string" + }, + { + "name": "version", + "type": "string", + "default": "latest", + "required": false + } ] }, - "batch_versions_latest_form": { "description": "Form to request the latest version for multiple applications in a single API call.", "fields": [ - { "name": "application_keys", "type": "[string]", "description": "List of application keys to look up" } + { + "name": "application_keys", + "type": "[string]", + "description": "List of application keys to look up" + } ] }, - "batch_version_latest": { "description": "The latest version for a single application, if any versions exist.", "fields": [ - { "name": "application_key", "type": "string" }, - { "name": "latest_version", "type": "string", "required": false, "description": "The latest version string, or absent if no versions exist for this application" } + { + "name": "application_key", + "type": "string" + }, + { + "name": "latest_version", + "type": "string", + "required": false, + "description": "The latest version string, or absent if no versions exist for this application" + } ] }, - "batch_versions_latest": { "plural": "batch_versions_latest", "description": "Response containing the latest version for each requested application.", "fields": [ - { "name": "applications", "type": "[batch_version_latest]" } + { + "name": "applications", + "type": "[batch_version_latest]" + } ] } }, - "resources": { - "io.apibuilder.generator.v0.models.healthcheck": { "path": "/_internal_", "operations": [ @@ -581,471 +1117,798 @@ "method": "GET", "path": "/healthcheck", "responses": { - "200": { "type": "io.apibuilder.generator.v0.models.healthcheck" } + "200": { + "type": "io.apibuilder.generator.v0.models.healthcheck" + } } } ] }, - "validation": { "description": "Public resource that can accept JSON input and returns a validation object describing any validation errors.", "operations": [ { "method": "POST", - "body": { "type": "string" }, + "body": { + "type": "string" + }, "responses": { - "200": { "type": "validation" } + "200": { + "type": "validation" + } } - } + } ] }, - "authentication": { "operations": [ { "method": "GET", "path": "/session/:id", "responses": { - "200": { "type": "authentication" }, - "404": { "type": "unit" } + "200": { + "type": "authentication" + }, + "404": { + "type": "unit" + } } } ] }, - "user": { "operations": [ { "method": "GET", "description": "Search for a specific user. You must specify at least 1 parameter - either a guid, email or token - and will receive back either 0 or 1 users.", "parameters": [ - { "name": "guid", "type": "uuid", "description": "Find user with this guid. Exact match", "required": false }, - { "name": "email", "type": "string", "description": "Find user with this email address. Case in-sensitive. Exact match", "required": false }, - { "name": "nickname", "type": "string", "description": "Find user with the specified nickname. For users that register via GitHub, this will be their GitHub username. Case in-sensitive. Exact match", "required": false }, - { "name": "token", "type": "string", "description": "Find the user with this API token. Exact match", "required": false } + { + "name": "guid", + "type": "uuid", + "description": "Find user with this guid. Exact match", + "required": false + }, + { + "name": "email", + "type": "string", + "description": "Find user with this email address. Case in-sensitive. Exact match", + "required": false + }, + { + "name": "nickname", + "type": "string", + "description": "Find user with the specified nickname. For users that register via GitHub, this will be their GitHub username. Case in-sensitive. Exact match", + "required": false + }, + { + "name": "token", + "type": "string", + "description": "Find the user with this API token. Exact match", + "required": false + } ], "responses": { - "200": { "type": "[user]" } + "200": { + "type": "[user]" + } } }, - { "method": "GET", "description": "Returns information about the user with this guid.", "path": "/:guid", "responses": { - "200": { "type": "user" }, - "404": { "type": "unit" } + "200": { + "type": "user" + }, + "404": { + "type": "unit" + } } }, - { "method": "POST", "path": "/authenticate", "description": "Used to authenticate a user with an email address and password. Successful authentication returns an instance of the user model. Failed authorizations of any kind are returned as a generic error with code user_authorization_failed.", "parameters": [ - { "name": "email", "type": "string" }, - { "name": "password", "type": "string" } + { + "name": "email", + "type": "string" + }, + { + "name": "password", + "type": "string" + } ], "responses": { - "200": { "type": "authentication" }, - "409": { "type": "[error]" } + "200": { + "type": "authentication" + }, + "409": { + "type": "[error]" + } } }, - { "method": "POST", "path": "/authenticate_github", "description": "Used to authenticate a user using a github access token. This is equivalent to running the following command to get the user info: curl -H 'Authorization: Bearer code' https://api.github.com/user", "parameters": [ - { "name": "token", "type": "string", "description": "The GitHub assigned oauth token" } + { + "name": "token", + "type": "string", + "description": "The GitHub assigned oauth token" + } ], "responses": { - "200": { "type": "authentication" }, - "409": { "type": "[error]" } + "200": { + "type": "authentication" + }, + "409": { + "type": "[error]" + } } }, - { "method": "POST", "description": "Create a new user.", - "body": { "type": "user_form" }, + "body": { + "type": "user_form" + }, "responses": { - "200": { "type": "user" }, - "409": { "type": "[error]" } + "200": { + "type": "user" + }, + "409": { + "type": "[error]" + } } }, - { "method": "PUT", "path": "/:guid", "description": "Updates information about the user with the specified guid.", - "body": { "type": "user_update_form" }, + "body": { + "type": "user_update_form" + }, "responses": { - "200": { "type": "user" }, - "409": { "type": "[error]" } + "200": { + "type": "user" + }, + "409": { + "type": "[error]" + } } } ] }, - "token": { "operations": [ { "method": "GET", "path": "/users/:user_guid", "parameters": [ - { "name": "user_guid", "type": "uuid" }, - { "name": "guid", "type": "uuid", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } - ], + { + "name": "user_guid", + "type": "uuid" + }, + { + "name": "guid", + "type": "uuid", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } + ], "responses": { - "200": { "type": "[token]" } + "200": { + "type": "[token]" + } } }, - { "method": "GET", "description": "Used to fetch the clear text token.", "path": "/:guid/cleartext", "responses": { - "200": { "type": "cleartext_token" }, - "404": { "type": "unit" } + "200": { + "type": "cleartext_token" + }, + "404": { + "type": "unit" + } } }, - { "method": "POST", "description": "Create a new API token for this user", - "body": { "type": "token_form" }, + "body": { + "type": "token_form" + }, "responses": { - "201": { "type": "token" }, - "409": { "type": "[error]" } + "201": { + "type": "token" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "path": "/:guid", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } } ] }, - "organization": { "operations": [ { "method": "GET", "description": "Search all organizations. Results are always paginated.", "parameters": [ - { "name": "guid", "type": "uuid", "required": false, "description": "Finds the organization with this guid, if any" }, - { "name": "user_guid", "type": "uuid", "required": false, "description": "If specified, restricts to organizations that this user is specifically a member of (e.g. will exclude public organizations with which the user does not have a direct membership)." }, - { "name": "key", "type": "string", "description": "Find organizations with this key. Case in-sensitive. Exact match", "required": false }, - { "name": "name", "type": "string", "description": "Find organizations with this name. Case in-sensitive. Exact match", "required": false }, - { "name": "namespace", "type": "string", "description": "Find organizations with this namespace. Case in-sensitive. Exact match", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "guid", + "type": "uuid", + "required": false, + "description": "Finds the organization with this guid, if any" + }, + { + "name": "user_guid", + "type": "uuid", + "required": false, + "description": "If specified, restricts to organizations that this user is specifically a member of (e.g. will exclude public organizations with which the user does not have a direct membership)." + }, + { + "name": "key", + "type": "string", + "description": "Find organizations with this key. Case in-sensitive. Exact match", + "required": false + }, + { + "name": "name", + "type": "string", + "description": "Find organizations with this name. Case in-sensitive. Exact match", + "required": false + }, + { + "name": "namespace", + "type": "string", + "description": "Find organizations with this namespace. Case in-sensitive. Exact match", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[organization]" } + "200": { + "type": "[organization]" + } } }, - { "method": "GET", "path": "/:key", "description": "Returns the organization with this key.", "responses": { - "200": { "type": "organization" }, - "404": { "type": "unit" } + "200": { + "type": "organization" + }, + "404": { + "type": "unit" + } } }, - { "method": "POST", "description": "Create a new organization.", - "body": { "type": "organization_form" }, + "body": { + "type": "organization_form" + }, "responses": { - "200": { "type": "organization" }, - "409": { "type": "[error]" } + "200": { + "type": "organization" + }, + "409": { + "type": "[error]" + } } }, - { "method": "PUT", "path": "/:key", "description": "Update an organization.", - "body": { "type": "organization_form" }, + "body": { + "type": "organization_form" + }, "responses": { - "200": { "type": "organization" }, - "409": { "type": "[error]" } + "200": { + "type": "organization" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "path": "/:key", "description": "Deletes an organization and all of its associated applications.", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } }, - { "method": "GET", "path": "/:key/attributes", "description": "Returns all attribute values for this organization. Results are always paginated.", "parameters": [ - { "name": "name", "type": "string", "description": "Find the values for the attribute with this name.", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "name", + "type": "string", + "description": "Find the values for the attribute with this name.", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[attribute_value]" } + "200": { + "type": "[attribute_value]" + } } }, - { "method": "GET", "path": "/:key/attributes/:name", "description": "Returns the attribute value with this name.", "responses": { - "200": { "type": "attribute_value" }, - "404": { "type": "unit" } + "200": { + "type": "attribute_value" + }, + "404": { + "type": "unit" + } } }, - { "method": "PUT", "path": "/:key/attributes/:name", "description": "Create or update a new attribute value.", - "body": { "type": "attribute_value_form" }, - "responses": { - "200": { "type": "attribute_value" }, - "201": { "type": "attribute_value" }, - "404": { "type": "unit" }, - "409": { "type": "[error]" } + "body": { + "type": "attribute_value_form" + }, + "responses": { + "200": { + "type": "attribute_value" + }, + "201": { + "type": "attribute_value" + }, + "404": { + "type": "unit" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "path": "/:key/attributes/:name", "description": "Deletes the attribute value with the specified name. Only the user who created an attribute value can delete it.", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } } ] }, - "attribute": { "operations": [ { "method": "GET", "description": "Search all attributes. Results are always paginated.", "parameters": [ - { "name": "guid", "type": "uuid", "description": "Find the watch with this guid.", "required": false }, - { "name": "name", "type": "string", "description": "Find attributes with this name. Case in-sensitive. Exact match", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "guid", + "type": "uuid", + "description": "Find the watch with this guid.", + "required": false + }, + { + "name": "name", + "type": "string", + "description": "Find attributes with this name. Case in-sensitive. Exact match", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[attribute]" } + "200": { + "type": "[attribute]" + } } }, - { "method": "GET", "path": "/:name", "description": "Returns the attribute with this name.", "responses": { - "200": { "type": "attribute" }, - "404": { "type": "unit" } + "200": { + "type": "attribute" + }, + "404": { + "type": "unit" + } } }, - { "method": "POST", "description": "Create a new attribute.", - "body": { "type": "attribute_form" }, - "responses": { - "201": { "type": "attribute" }, - "401": { "type": "unit" }, - "409": { "type": "[error]" } + "body": { + "type": "attribute_form" + }, + "responses": { + "201": { + "type": "attribute" + }, + "401": { + "type": "unit" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "path": "/:name", "description": "Deletes the attribute with this name. Only the user who created an attribute can delete it.", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } } ] }, - "domain": { "path": "/domains/:orgKey", "operations": [ { "method": "POST", "description": "Add a domain to this organization", - "body": { "type": "domain" }, + "body": { + "type": "domain" + }, "responses": { - "200": { "type": "domain" }, - "409": { "type": "[error]" } + "200": { + "type": "domain" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "description": "Remove this domain from this organization", "path": "/:name", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } } ] }, - "membership": { "operations": [ { "method": "GET", "description": "Search all memberships. Results are always paginated.", "parameters": [ - { "name": "org_guid", "type": "uuid", "required": false }, - { "name": "org_key", "type": "string", "required": false }, - { "name": "user_guid", "type": "uuid", "required": false }, - { "name": "role", "type": "io.apibuilder.common.v0.enums.membership_role", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "org_guid", + "type": "uuid", + "required": false + }, + { + "name": "org_key", + "type": "string", + "required": false + }, + { + "name": "user_guid", + "type": "uuid", + "required": false + }, + { + "name": "role", + "type": "io.apibuilder.common.v0.enums.membership_role", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[membership]" } + "200": { + "type": "[membership]" + } } }, - { "method": "GET", "path": "/:guid", "responses": { - "200": { "type": "membership" }, - "404": { "type": "unit" } + "200": { + "type": "membership" + }, + "404": { + "type": "unit" + } } }, - { "method": "DELETE", "path": "/:guid", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } } ] }, - "membership_request": { "operations": [ { "method": "GET", "description": "Search all membership requests. Results are always paginated.", "parameters": [ - { "name": "org_guid", "type": "uuid", "required": false }, - { "name": "org_key", "type": "string", "required": false }, - { "name": "user_guid", "type": "uuid", "required": false }, - { "name": "role", "type": "io.apibuilder.common.v0.enums.membership_role", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "org_guid", + "type": "uuid", + "required": false + }, + { + "name": "org_key", + "type": "string", + "required": false + }, + { + "name": "user_guid", + "type": "uuid", + "required": false + }, + { + "name": "role", + "type": "io.apibuilder.common.v0.enums.membership_role", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[membership_request]" } + "200": { + "type": "[membership_request]" + } } }, - { "method": "POST", "description": "Create a membership request", "parameters": [ - { "name": "org_guid", "type": "uuid" }, - { "name": "user_guid", "type": "uuid" }, - { "name": "role", "type": "io.apibuilder.common.v0.enums.membership_role" } + { + "name": "org_guid", + "type": "uuid" + }, + { + "name": "user_guid", + "type": "uuid" + }, + { + "name": "role", + "type": "io.apibuilder.common.v0.enums.membership_role" + } ], "responses": { - "200": { "type": "membership_request" }, - "409": { "type": "[error]" } + "200": { + "type": "membership_request" + }, + "409": { + "type": "[error]" + } } }, - { "method": "POST", "path": "/:guid/accept", "description": "Accepts this membership request. User will become a member of the specified organization.", "responses": { - "204": { "type": "unit" }, - "409": { "type": "[error]" } + "204": { + "type": "unit" + }, + "409": { + "type": "[error]" + } } }, - { "method": "POST", "path": "/:guid/decline", "description": "Declines this membership request. User will NOT become a member of the specified organization.", "responses": { - "204": { "type": "unit" }, - "409": { "type": "[error]" } + "204": { + "type": "unit" + }, + "409": { + "type": "[error]" + } } } ] }, - "batch_download_applications": { "path": "/:orgKey/batch/download/applications", "operations": [ { "method": "POST", "description": "Retrieve multiple applications in one api call.", - "body": { "type": "batch_download_applications_form" }, + "body": { + "type": "batch_download_applications_form" + }, "responses": { - "201": { "type": "batch_download_applications" }, - "409": { "type": "[error]" } + "201": { + "type": "batch_download_applications" + }, + "409": { + "type": "[error]" + } } } ] }, - "batch_versions_latest": { "path": "/:orgKey/batch/versions/latest", "operations": [ { "method": "POST", "description": "Retrieve the latest version for multiple applications in one API call.", - "body": { "type": "batch_versions_latest_form" }, + "body": { + "type": "batch_versions_latest_form" + }, "responses": { - "200": { "type": "batch_versions_latest" } + "200": { + "type": "batch_versions_latest" + } } } ] }, - "application": { "path": "/:orgKey", "operations": [ @@ -1054,13 +1917,31 @@ "description": "Returns the versions assocoated with the specified application. The latest version is the first result returned.", "path": "/metadata/:applicationKey/versions", "parameters": [ - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[application_metadata_version]" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "200": { + "type": "[application_metadata_version]" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } }, { @@ -1068,88 +1949,163 @@ "description": "Returns the latest version number as a string", "path": "/metadata/:applicationKey/versions/latest.txt", "responses": { - "200": { "type": "string" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "200": { + "type": "string" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } }, - { "method": "GET", "description": "Search all applications. Results are always paginated.", "parameters": [ - { "name": "orgKey", "type": "string", "description": "The organization key for which to search applications" }, - { "name": "name", "type": "string", "required": false, "description": "The name of an application. Case in-sensitive. Exact match" }, - { "name": "guid", "type": "uuid", "required": false, "description": "The guid of an application. Exact match" }, - { "name": "key", "type": "string", "required": false, "description": "The key of an application. Case in-sensitive. Exact match" }, - { "name": "has_version", "type": "boolean", "description": "If true, we return applications that have at least one version. If false, we return applications that have no versions in the system", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 }, - { "name": "sort_by", "type": "app_sort_by", "required": false }, - { "name": "order", "type": "sort_order", "required": false } + { + "name": "orgKey", + "type": "string", + "description": "The organization key for which to search applications" + }, + { + "name": "name", + "type": "string", + "required": false, + "description": "The name of an application. Case in-sensitive. Exact match" + }, + { + "name": "guid", + "type": "uuid", + "required": false, + "description": "The guid of an application. Exact match" + }, + { + "name": "key", + "type": "string", + "required": false, + "description": "The key of an application. Case in-sensitive. Exact match" + }, + { + "name": "has_version", + "type": "boolean", + "description": "If true, we return applications that have at least one version. If false, we return applications that have no versions in the system", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + }, + { + "name": "sort_by", + "type": "app_sort_by", + "required": false + }, + { + "name": "order", + "type": "sort_order", + "required": false + } ], "responses": { - "200": { "type": "[application]" } + "200": { + "type": "[application]" + } } }, - { "method": "POST", "description": "Create an application.", - "body": { "type": "application_form" }, + "body": { + "type": "application_form" + }, "responses": { - "200": { "type": "application" }, - "409": { "type": "[error]" } + "200": { + "type": "application" + }, + "409": { + "type": "[error]" + } } }, - { "method": "PUT", "description": "Updates an application.", "path": "/:applicationKey", - "body": { "type": "application_form" }, + "body": { + "type": "application_form" + }, "responses": { - "200": { "type": "application" }, - "409": { "type": "[error]" } + "200": { + "type": "application" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "description": "Deletes a specific application and its associated versions.", "path": "/:applicationKey", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } }, - { "method": "POST", "description": "Moves application to a new organization.", "path": "/:applicationKey/move", - "body": { "type": "move_form" }, + "body": { + "type": "move_form" + }, "responses": { - "200": { "type": "application" }, - "409": { "type": "[error]" } + "200": { + "type": "application" + }, + "409": { + "type": "[error]" + } } } ] }, - "code": { "path": "/:orgKey/:applicationKey/:version", - "operations": [ { "method": "POST", "path": "/form", "description": "Create an invocation form. This is useful primarily for debugging when you want to see exactly what content is being forwarded to the generator", - "body": { "type": "code_form" }, + "body": { + "type": "code_form" + }, "responses": { - "200": { "type": "io.apibuilder.generator.v0.models.invocation_form" }, - "409": { "type": "[error]" } + "200": { + "type": "io.apibuilder.generator.v0.models.invocation_form" + }, + "409": { + "type": "[error]" + } } }, { @@ -1157,407 +2113,714 @@ "path": "/:generatorKey", "description": "Generate code for a specific version of an application.", "parameters": [ - { "name": "orgKey", "type": "string", "description": "The organization key for which to generate code" }, - { "name": "applicationKey", "type": "string", "description": "The application key for which to generate code" }, - { "name": "version", "type": "string", "description": "The version of this application. Can be 'latest'" }, - { "name": "generatorKey", "type": "string", "description": "The key of the generator to invoke" } + { + "name": "orgKey", + "type": "string", + "description": "The organization key for which to generate code" + }, + { + "name": "applicationKey", + "type": "string", + "description": "The application key for which to generate code" + }, + { + "name": "version", + "type": "string", + "description": "The version of this application. Can be 'latest'" + }, + { + "name": "generatorKey", + "type": "string", + "description": "The key of the generator to invoke" + } ], "responses": { - "200": { "type": "code" }, - "409": { "type": "[error]", "description": "If the target, version, and/or generator are invalid." } + "200": { + "type": "code" + }, + "409": { + "type": "[error]", + "description": "If the target, version, and/or generator are invalid." + } } }, { "method": "POST", - "path": "/:generatorKey", + "path": "/:generatorKey", "description": "Generate code for a specific version of an application.", "parameters": [ - { "name": "orgKey", "type": "string", "description": "The organization key for which to generate code" }, - { "name": "applicationKey", "type": "string", "description": "The application key for which to generate code" }, - { "name": "version", "type": "string", "description": "The version of this application. Can be 'latest'" }, - { "name": "generatorKey", "type": "string", "description": "The key of the generator to invoke" } + { + "name": "orgKey", + "type": "string", + "description": "The organization key for which to generate code" + }, + { + "name": "applicationKey", + "type": "string", + "description": "The application key for which to generate code" + }, + { + "name": "version", + "type": "string", + "description": "The version of this application. Can be 'latest'" + }, + { + "name": "generatorKey", + "type": "string", + "description": "The key of the generator to invoke" + } ], - "body": { "type": "code_form" }, - "responses": { - "200": { "type": "code" }, - "409": { "type": "[error]", "description": "If the target, version, and/or generator are invalid." } + "body": { + "type": "code_form" + }, + "responses": { + "200": { + "type": "code" + }, + "409": { + "type": "[error]", + "description": "If the target, version, and/or generator are invalid." + } } } ] }, - "version": { "path": "/:orgKey", - "operations": [ { "method": "GET", "path": "/:applicationKey", "description": "Search all versions of this application. Results are always paginated.", "parameters": [ - { "name": "orgKey", "type": "string", "description": "The organization key for which to search versions" }, - { "name": "applicationKey", "type": "string", "description": "The application key for which to search versions" }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "orgKey", + "type": "string", + "description": "The organization key for which to search versions" + }, + { + "name": "applicationKey", + "type": "string", + "description": "The application key for which to search versions" + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[version]" } + "200": { + "type": "[version]" + } } }, - { "method": "GET", "description": "Retrieve a specific version of an application.", "path": "/:applicationKey/:version", "parameters": [ - { "name": "version", "type": "string", "description": "The version of tthis application to download, or the keyword latest to get the latest version", "example": "latest" } + { + "name": "version", + "type": "string", + "description": "The version of tthis application to download, or the keyword latest to get the latest version", + "example": "latest" + } ], "responses": { - "200": { "type": "version" }, - "404": { "type": "unit" } + "200": { + "type": "version" + }, + "404": { + "type": "unit" + } } }, - { "method": "POST", "path": "/:version", "description": "Create a new version for an application", - "body": { "type": "version_form" }, + "body": { + "type": "version_form" + }, "responses": { - "200": { "type": "version" }, - "409": { "type": "[error]" } + "200": { + "type": "version" + }, + "409": { + "type": "[error]" + } } }, - { "method": "PUT", "description": "Upsert a version of an application", "path": "/:applicationKey/:version", - "body": { "type": "version_form" }, + "body": { + "type": "version_form" + }, "responses": { - "200": { "type": "version" }, - "409": { "type": "[error]" } + "200": { + "type": "version" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "description": "Deletes a specific version.", "path": "/:applicationKey/:version", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } }, - { "method": "GET", "description": "Generates an example JSON document of the type with the specified name.", "path": "/:applicationKey/:version/example/:type_name", "parameters": [ - { "name": "version", "type": "string", "description": "The version of this application to download, or the keyword latest to get the latest version", "example": "latest" }, - { "name": "type_name", "type": "string", "description": "The name of the type (e.g. model name) for which you would like to generate a sample json document" }, - { "name": "sub_type_name", "type": "string", "description": "The name of the sub-type for which you would like to generate a sample json document, e.g. a specific type of a union", "required": false }, - { "name": "optional_fields", "type": "boolean", "description": "If true, we generate sample data for all optional fields. Otherwise the generated sample will contain only required fields, where applicable", "required": false } + { + "name": "version", + "type": "string", + "description": "The version of this application to download, or the keyword latest to get the latest version", + "example": "latest" + }, + { + "name": "type_name", + "type": "string", + "description": "The name of the type (e.g. model name) for which you would like to generate a sample json document" + }, + { + "name": "sub_type_name", + "type": "string", + "description": "The name of the sub-type for which you would like to generate a sample json document, e.g. a specific type of a union", + "required": false + }, + { + "name": "optional_fields", + "type": "boolean", + "description": "If true, we generate sample data for all optional fields. Otherwise the generated sample will contain only required fields, where applicable", + "required": false + } ], "responses": { - "200": { "type": "object" }, - "404": { "type": "unit" } + "200": { + "type": "object" + }, + "404": { + "type": "unit" + } } } - ] }, - "generator_service": { - "operations": [ { "method": "GET", "description": "List all generator services", "parameters": [ - { "name": "guid", "type": "uuid", "required": false }, - { "name": "uri", "type": "string", "required": false }, - { "name": "generator_key", "type": "string", "required": false }, - { "name": "limit", "type": "long", "default": 100, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "guid", + "type": "uuid", + "required": false + }, + { + "name": "uri", + "type": "string", + "required": false + }, + { + "name": "generator_key", + "type": "string", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 100, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[generator_service]" } + "200": { + "type": "[generator_service]" + } } }, - { "method": "GET", "path": "/:guid", "responses": { - "200": { "type": "generator_service" }, - "404": { "type": "unit" } + "200": { + "type": "generator_service" + }, + "404": { + "type": "unit" + } } }, - { "method": "POST", - "body": { "type": "generator_service_form" }, + "body": { + "type": "generator_service_form" + }, "responses": { - "200": { "type": "generator_service" }, - "409": { "type": "[error]" } + "200": { + "type": "generator_service" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "path": "/:guid", "description": "Deletes a generator service.", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } } ] - }, - "generator_with_service": { "path": "/generators", - "operations": [ { "method": "GET", "description": "List all available generators", "parameters": [ - { "name": "guid", "type": "uuid", "required": false, "description": "Filter to generator with this guid" }, - { "name": "service_guid", "type": "uuid", "required": false, "description": "Filter to generator from this service" }, - { "name": "service_uri", "type": "string", "required": false, "description": "Filter to generator from this service URI" }, - { "name": "attribute_name", "type": "string", "required": false, "description": "Filter to generators that use this attribute" }, - { "name": "key", "type": "string", "required": false, "description": "Filter to generator with this key" }, - { "name": "limit", "type": "long", "default": 100, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "guid", + "type": "uuid", + "required": false, + "description": "Filter to generator with this guid" + }, + { + "name": "service_guid", + "type": "uuid", + "required": false, + "description": "Filter to generator from this service" + }, + { + "name": "service_uri", + "type": "string", + "required": false, + "description": "Filter to generator from this service URI" + }, + { + "name": "attribute_name", + "type": "string", + "required": false, + "description": "Filter to generators that use this attribute" + }, + { + "name": "key", + "type": "string", + "required": false, + "description": "Filter to generator with this key" + }, + { + "name": "limit", + "type": "long", + "default": 100, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[generator_with_service]" } + "200": { + "type": "[generator_with_service]" + } } }, - { "method": "GET", "path": "/:key", "responses": { - "200": { "type": "generator_with_service" }, - "404": { "type": "unit" } + "200": { + "type": "generator_with_service" + }, + "404": { + "type": "unit" + } } } ] }, - "subscription": { "operations": [ { "method": "GET", "description": "Search subscriptions. Always paginated.", "parameters": [ - { "name": "guid", "type": "uuid", "description": "Find the subscription with this guid.", "required": false }, - { "name": "organization_key", "type": "string", "description": "Find subscriptions for this organization.", "required": false }, - { "name": "user_guid", "type": "uuid", "description": "Find subscriptions for this user.", "required": false }, - { "name": "publication", "type": "publication", "description": "Find subscriptions for this publication.", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "guid", + "type": "uuid", + "description": "Find the subscription with this guid.", + "required": false + }, + { + "name": "organization_key", + "type": "string", + "description": "Find subscriptions for this organization.", + "required": false + }, + { + "name": "user_guid", + "type": "uuid", + "description": "Find subscriptions for this user.", + "required": false + }, + { + "name": "publication", + "type": "publication", + "description": "Find subscriptions for this publication.", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[subscription]" } + "200": { + "type": "[subscription]" + } } }, - { "method": "GET", "description": "Returns information about a specific subscription.", "path": "/:guid", "responses": { - "200": { "type": "subscription" }, - "404": { "type": "unit" } + "200": { + "type": "subscription" + }, + "404": { + "type": "unit" + } } }, - { "method": "POST", "description": "Create a new subscription.", - "body": { "type": "subscription_form" }, + "body": { + "type": "subscription_form" + }, "responses": { - "201": { "type": "subscription" }, - "409": { "type": "[error]" } + "201": { + "type": "subscription" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "path": "/:guid", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } } ] }, - "watch": { "operations": [ { "method": "GET", "description": "Search attributes. Always paginated.", "parameters": [ - { "name": "guid", "type": "uuid", "description": "Find the watch with this guid.", "required": false }, - { "name": "user_guid", "type": "uuid", "description": "Find attributes for this user.", "required": false }, - { "name": "organization_key", "type": "string", "description": "Find attributes for this organization.", "required": false }, - { "name": "application_key", "type": "string", "description": "Find attributes for this application.", "required": false }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "guid", + "type": "uuid", + "description": "Find the watch with this guid.", + "required": false + }, + { + "name": "user_guid", + "type": "uuid", + "description": "Find attributes for this user.", + "required": false + }, + { + "name": "organization_key", + "type": "string", + "description": "Find attributes for this organization.", + "required": false + }, + { + "name": "application_key", + "type": "string", + "description": "Find attributes for this application.", + "required": false + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[watch]" } + "200": { + "type": "[watch]" + } } }, - { "method": "GET", "description": "Returns information about a specific watch.", "path": "/:guid", "responses": { - "200": { "type": "watch" }, - "404": { "type": "unit" } + "200": { + "type": "watch" + }, + "404": { + "type": "unit" + } } }, - { "method": "GET", "description": "Quick check if a user is watching a specific application.", "path": "/check", "parameters": [ - { "name": "user_guid", "type": "uuid", "description": "The user for which we are checking. API returns false if the user guid is not provided", "required": false }, - { "name": "organization_key", "type": "string" }, - { "name": "application_key", "type": "string" } + { + "name": "user_guid", + "type": "uuid", + "description": "The user for which we are checking. API returns false if the user guid is not provided", + "required": false + }, + { + "name": "organization_key", + "type": "string" + }, + { + "name": "application_key", + "type": "string" + } ], "responses": { - "200": { "type": "boolean" } + "200": { + "type": "boolean" + } } }, - { "method": "POST", "description": "Create a new watch.", - "body": { "type": "watch_form" }, + "body": { + "type": "watch_form" + }, "responses": { - "201": { "type": "watch" }, - "409": { "type": "[error]" } + "201": { + "type": "watch" + }, + "409": { + "type": "[error]" + } } }, - { "method": "DELETE", "path": "/:guid", "responses": { - "204": { "type": "unit" }, - "401": { "type": "unit" }, - "404": { "type": "unit" } - } - } - ] - }, - - "email_verification_confirmation_form": { - "path": "/email_verification_confirmations", - "operations": [ - { - "method": "POST", - "description": "Validate an email address using a token.", - "body": { "type": "email_verification_confirmation_form" }, - "responses": { - "204": { "type": "unit" }, - "409": { "type": "[error]" } - } - } - ] - }, - - "password_reset_request": { - "operations": [ - { - "method": "POST", - "description": "Create a new password reset. This will send the user an email with a link to reset their password.", - "body": { "type": "password_reset_request" }, - "responses": { - "204": { "type": "unit" }, - "409": { "type": "[error]" } - } - } - ] - }, - - "password_reset": { - "operations": [ - { - "method": "POST", - "description": "Change the password for this token. If the token is invalid, has been used, or otherwise no longer can be applied, errors will be returned as 409s. A 204 represents that the user has successfully changed their password.", - "body": { "type": "password_reset" }, - "responses": { - "200": { "type": "authentication" }, - "409": { "type": "[error]" } + "204": { + "type": "unit" + }, + "401": { + "type": "unit" + }, + "404": { + "type": "unit" + } } } - ] }, - "change": { - "operations": [ { "method": "GET", "parameters": [ - { "name": "org_key", "type": "string", "description": "Filter changes to those made for the organization with this key.", "required": false }, - { "name": "application_key", "type": "string", "description": "Filter changes to those made for the application with this key.", "required": false }, - { "name": "from", "type": "string", "description": "Filter changes to those made from this version.", "required": false, "example": "0.1.3" }, - { "name": "to", "type": "string", "description": "Filter changes to those made to this version.", "required": false, "example": "0.1.4" }, - { "name": "type", "type": "string", "description": "The type of diff to return.", "required": false, "example": "breaking or non_breaking" }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "org_key", + "type": "string", + "description": "Filter changes to those made for the organization with this key.", + "required": false + }, + { + "name": "application_key", + "type": "string", + "description": "Filter changes to those made for the application with this key.", + "required": false + }, + { + "name": "from", + "type": "string", + "description": "Filter changes to those made from this version.", + "required": false, + "example": "0.1.3" + }, + { + "name": "to", + "type": "string", + "description": "Filter changes to those made to this version.", + "required": false, + "example": "0.1.4" + }, + { + "name": "type", + "type": "string", + "description": "The type of diff to return.", + "required": false, + "example": "breaking or non_breaking" + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[change]" } + "200": { + "type": "[change]" + } } } ] - }, - "item": { - "operations": [ { "method": "GET", "parameters": [ - { "name": "q", "type": "string", "required": false, "description": "The search query. At the moment, we do case insensitive exact match search." }, - { "name": "limit", "type": "long", "default": 25, "description": "The number of records to return", "minimum": 1, "maximum": 100 }, - { "name": "offset", "type": "long", "default": 0, "description": "Used to paginate. First page of results is 0.", "minimum": 0 } + { + "name": "q", + "type": "string", + "required": false, + "description": "The search query. At the moment, we do case insensitive exact match search." + }, + { + "name": "limit", + "type": "long", + "default": 25, + "description": "The number of records to return", + "minimum": 1, + "maximum": 100 + }, + { + "name": "offset", + "type": "long", + "default": 0, + "description": "Used to paginate. First page of results is 0.", + "minimum": 0 + } ], "responses": { - "200": { "type": "[item]" } + "200": { + "type": "[item]" + } } }, - { "method": "GET", "path": "/:guid", "responses": { - "200": { "type": "item" }, - "404": { "type": "unit" } + "200": { + "type": "item" + }, + "404": { + "type": "unit" + } } } - ] } } - -} +} \ No newline at end of file