Skip to content

Commit

Permalink
Move role to apibuilder spec to enforce type (#931)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbryzek authored Jun 21, 2024
1 parent 4d4d72e commit 706cbce
Show file tree
Hide file tree
Showing 46 changed files with 760 additions and 526 deletions.
10 changes: 5 additions & 5 deletions .apibuilder/.tracked_files
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ apicollective:
apibuilder-api:
anorm_2_8_parsers:
- api/app/generated/ApicollectiveApibuilderApiV0Conversions.scala
- api/app/generated/ApicollectiveApibuilderApiV0Parsers.scala
play_2_8_client:
- generated/app/ApicollectiveApibuilderApiV0Client.scala
play_2_x_routes:
Expand All @@ -13,6 +14,10 @@ apicollective:
apibuilder-common:
anorm_2_8_parsers:
- api/app/generated/ApicollectiveApibuilderCommonV0Conversions.scala
play_2_8_client:
- generated/app/ApicollectiveApibuilderCommonV0Client.scala
play_2_8_mock_client:
- generated/app/ApicollectiveApibuilderCommonV0MockClient.scala
play_2_x_json:
- generated/app/ApicollectiveApibuilderCommonV0Models.scala
apibuilder-generator:
Expand All @@ -22,11 +27,6 @@ apicollective:
- generated/app/ApicollectiveApibuilderGeneratorV0Client.scala
play_2_8_mock_client:
- generated/app/ApicollectiveApibuilderGeneratorV0MockClient.scala
apibuilder-internal:
anorm_2_8_parsers:
- api/app/generated/ApicollectiveApibuilderInternalV0Conversions.scala
play_2_x_json:
- generated/app/ApicollectiveApibuilderInternalV0Models.scala
apibuilder-spec:
anorm_2_8_parsers:
- api/app/generated/ApicollectiveApibuilderSpecV0Conversions.scala
Expand Down
3 changes: 2 additions & 1 deletion .apibuilder/config
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ code:
apibuilder-common:
version: latest
generators:
play_2_x_json: generated/app
play_2_8_client: generated/app
play_2_8_mock_client: generated/app
anorm_2_8_parsers: api/app/generated
apibuilder-generator:
version: latest
Expand Down
16 changes: 8 additions & 8 deletions api/app/controllers/ApiBuilderController.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package controllers

import javax.inject.Inject

import com.google.inject.ImplementedBy
import db.{Authorization, MembershipsDao, OrganizationsDao}
import io.apibuilder.api.v0.models.{Organization, User}
import io.apibuilder.api.v0.models.json._
import lib.{RequestAuthenticationUtil, Role, Validation}
import io.apibuilder.common.v0.models.MembershipRole
import lib.{RequestAuthenticationUtil, Validation}
import play.api.libs.json.{JsValue, Json}
import play.api.mvc._

Expand Down Expand Up @@ -44,30 +44,30 @@ trait ApiBuilderController extends BaseController {

def withOrgMember(user: User, orgKey: String)(f: Organization => Result): Result = {
withOrg(Authorization.User(user.guid), orgKey) { org =>
withRole(org, user, Role.All) {
withRole(org, user, MembershipRole.all) {
f(org)
}
}
}

def withOrgAdmin(user: User, orgKey: String)(f: Organization => Result): Result = {
withOrg(Authorization.User(user.guid), orgKey) { org =>
withRole(org, user, Seq(Role.Admin)) {
withRole(org, user, Seq(MembershipRole.Admin)) {
f(org)
}
}
}

private[this] def withRole(org: Organization, user: User, roles: Seq[Role])(f: => Result): Result = {
private[this] def withRole(org: Organization, user: User, roles: Seq[MembershipRole])(f: => Result): Result = {
val actualRoles = membershipsDao.findByOrganizationAndUserAndRoles(
Authorization.All, org, user, roles
).map(_.role)

if (actualRoles.isEmpty) {
val msg: String = if (roles.contains(Role.Admin)) {
s"an '${Role.Admin}'"
val msg: String = if (roles.contains(MembershipRole.Admin)) {
s"an '${MembershipRole.Admin}'"
} else {
s"a '${Role.Member}'"
s"a '${MembershipRole.Member}'"
}
Results.Unauthorized(
jsonError(s"Must be $msg of the organization")
Expand Down
14 changes: 8 additions & 6 deletions api/app/controllers/MembershipRequests.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package controllers

import io.apibuilder.api.v0.models.{Organization, User}
import io.apibuilder.api.v0.models.json._
import lib.{Role, Validation}
import db.{MembershipRequestsDao, OrganizationsDao, UsersDao}
import io.apibuilder.api.v0.models.json._
import io.apibuilder.api.v0.models.{Organization, User}
import io.apibuilder.common.v0.models.MembershipRole
import lib.Validation
import play.api.libs.json._

import java.util.UUID
import javax.inject.{Inject, Singleton}

Expand Down Expand Up @@ -32,7 +34,7 @@ class MembershipRequests @Inject() (
organizationGuid: Option[UUID],
organizationKey: Option[String],
userGuid: Option[UUID],
role: Option[String],
role: Option[MembershipRole],
limit: Long = 25,
offset: Long = 0
) = Identified { request =>
Expand Down Expand Up @@ -68,12 +70,12 @@ class MembershipRequests @Inject() (
}

case Some(user: User) => {
Role.fromString(form.role) match {
MembershipRole.fromString(form.role) match {
case None => {
Conflict(Json.toJson(Validation.error("Invalid role")))
}

case Some(role: Role) => {
case Some(role) => {
val mr = membershipRequestsDao.upsert(request.user, org, user, role)
Ok(Json.toJson(mr))
}
Expand Down
18 changes: 10 additions & 8 deletions api/app/controllers/Memberships.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package controllers

import io.apibuilder.api.v0.models.json._
import db.MembershipsDao
import io.apibuilder.api.v0.models.json._
import io.apibuilder.common.v0.models.MembershipRole
import play.api.libs.json.Json

import java.util.UUID
import javax.inject.{Inject, Singleton}
import play.api.libs.json.Json

@Singleton
class Memberships @Inject() (
Expand All @@ -13,12 +15,12 @@ class Memberships @Inject() (
) extends ApiBuilderController {

def get(
organizationGuid: Option[UUID],
organizationKey: Option[String],
userGuid: Option[UUID],
role: Option[String],
limit: Long = 25,
offset: Long = 0
organizationGuid: Option[UUID],
organizationKey: Option[String],
userGuid: Option[UUID],
role: Option[MembershipRole],
limit: Long = 25,
offset: Long = 0
) = Identified { request =>
Ok(
Json.toJson(
Expand Down
5 changes: 3 additions & 2 deletions api/app/db/EmailVerificationsDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package db
import anorm.JodaParameterMetaData._
import anorm._
import io.apibuilder.api.v0.models.User
import io.apibuilder.common.v0.models.MembershipRole
import io.apibuilder.task.v0.models.EmailDataEmailVerificationCreated
import io.flow.postgresql.Query
import lib.{Role, TokenGenerator}
import lib.TokenGenerator
import org.joda.time.DateTime
import play.api.db._
import processor.EmailProcessorQueue
Expand Down Expand Up @@ -90,7 +91,7 @@ class EmailVerificationsDao @Inject() (

emailVerificationConfirmationsDao.upsert(updatingUserGuid, verification)
organizationsDao.findAllByEmailDomain(verification.email).foreach { org =>
membershipRequestsDao.findByOrganizationAndUserGuidAndRole(Authorization.All, org, verification.userGuid, Role.Member).foreach { request =>
membershipRequestsDao.findByOrganizationAndUserGuidAndRole(Authorization.All, org, verification.userGuid, MembershipRole.Member).foreach { request =>
membershipRequestsDao.acceptViaEmailVerification(updatingUserGuid, request, verification.email)
}
}
Expand Down
41 changes: 14 additions & 27 deletions api/app/db/MembershipRequestsDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package db

import anorm._
import io.apibuilder.api.v0.models.{MembershipRequest, Organization, User}
import io.apibuilder.common.v0.models.MembershipRole
import io.apibuilder.task.v0.models.{EmailDataMembershipRequestAccepted, EmailDataMembershipRequestCreated, EmailDataMembershipRequestDeclined}
import io.flow.postgresql.Query
import lib.Role
import play.api.db._
import processor.EmailProcessorQueue

Expand Down Expand Up @@ -51,7 +51,7 @@ class MembershipRequestsDao @Inject() (
({guid}::uuid, {organization_guid}::uuid, {user_guid}::uuid, {role}, {created_by_guid}::uuid)
"""

def upsert(createdBy: User, organization: Organization, user: User, role: Role): MembershipRequest = {
def upsert(createdBy: User, organization: Organization, user: User, role: MembershipRole): MembershipRequest = {
findByOrganizationAndUserGuidAndRole(Authorization.All, organization, user.guid, role) match {
case Some(r: MembershipRequest) => r
case None => {
Expand All @@ -60,14 +60,14 @@ class MembershipRequestsDao @Inject() (
}
}

private[db] def create(createdBy: User, organization: Organization, user: User, role: Role): MembershipRequest = {
private[db] def create(createdBy: User, organization: Organization, user: User, role: MembershipRole): MembershipRequest = {
val guid = UUID.randomUUID
db.withTransaction { implicit c =>
SQL(InsertQuery).on(
"guid" -> guid,
"organization_guid" -> organization.guid,
"user_guid" -> user.guid,
"role" -> role.key,
"role" -> role.toString,
"created_by_guid" -> createdBy.guid
).execute()
emailQueue.queueWithConnection(c, EmailDataMembershipRequestCreated(guid))
Expand All @@ -85,29 +85,19 @@ class MembershipRequestsDao @Inject() (
*/
def accept(createdBy: User, request: MembershipRequest): Unit = {
assertUserCanReview(createdBy, request)
val r = Role.fromString(request.role).getOrElse {
sys.error(s"Invalid role[${request.role}]")
}
doAccept(createdBy.guid, request, s"Accepted membership request for ${request.user.email} to join as ${r.name}")
doAccept(createdBy.guid, request, s"Accepted membership request for ${request.user.email} to join as ${request.role}")
}

private[db] def acceptViaEmailVerification(createdBy: UUID, request: MembershipRequest, email: String): Unit = {
val r = Role.fromString(request.role).getOrElse {
sys.error(s"Invalid role[${request.role}]")
}
doAccept(createdBy, request, s"$email joined as ${r.name} by verifying their email address")
doAccept(createdBy, request, s"$email joined as ${request.role} by verifying their email address")
}

private[this] def doAccept(createdBy: UUID, request: MembershipRequest, message: String): Unit = {
val r = Role.fromString(request.role).getOrElse {
sys.error(s"Invalid role[${request.role}]")
}

db.withTransaction { implicit c =>
organizationLogsDao.create(createdBy, request.organization, message)
dbHelpers.delete(c, createdBy, request.guid)
membershipsDao.upsert(createdBy, request.organization, request.user, r)
emailQueue.queueWithConnection(c, EmailDataMembershipRequestAccepted(request.organization.guid, request.user.guid, r.toString))
membershipsDao.upsert(createdBy, request.organization, request.user, request.role)
emailQueue.queueWithConnection(c, EmailDataMembershipRequestAccepted(request.organization.guid, request.user.guid, request.role))
}
}

Expand All @@ -117,15 +107,12 @@ class MembershipRequestsDao @Inject() (
*/
def decline(createdBy: User, request: MembershipRequest): Unit = {
assertUserCanReview(createdBy, request)
val r = Role.fromString(request.role).getOrElse {
sys.error(s"Invalid role[${request.role}]")
}

val message = s"Declined membership request for ${request.user.email} to join as ${r.name}"
val message = s"Declined membership request for ${request.user.email} to join as ${request.role}"
db.withTransaction { implicit c =>
organizationLogsDao.create(createdBy.guid, request.organization, message)
softDelete(createdBy, request)
emailQueue.queueWithConnection(c, EmailDataMembershipRequestDeclined(request.organization.guid, request.user.guid, r.toString))
emailQueue.queueWithConnection(c, EmailDataMembershipRequestDeclined(request.organization.guid, request.user.guid))
}
}

Expand All @@ -144,13 +131,13 @@ class MembershipRequestsDao @Inject() (
authorization: Authorization,
org: Organization,
userGuid: UUID,
role: Role
role: MembershipRole
): Option[MembershipRequest] = {
findAll(
authorization,
organizationGuid = Some(org.guid),
userGuid = Some(userGuid),
role = Some(role.key),
role = Some(role),
limit = 1
).headOption
}
Expand All @@ -165,7 +152,7 @@ class MembershipRequestsDao @Inject() (
organizationGuid: Option[UUID] = None,
organizationKey: Option[String] = None,
userGuid: Option[UUID] = None,
role: Option[String] = None,
role: Option[MembershipRole] = None,
isDeleted: Option[Boolean] = Some(false),
limit: Long = 25,
offset: Long = 0
Expand All @@ -182,7 +169,7 @@ class MembershipRequestsDao @Inject() (
"membership_requests.organization_guid = (select guid from organizations where deleted_at is null and key = {organization_key})"
}
).bind("organization_key", organizationKey).
equals("membership_requests.role", role).
equals("membership_requests.role", role.map(_.toString)).
and(isDeleted.map(Filters.isDeleted("membership_requests", _))).
orderBy("membership_requests.created_at desc").
limit(limit).
Expand Down
Loading

0 comments on commit 706cbce

Please sign in to comment.