Skip to content

Commit

Permalink
Attributes: Move to generated DAO
Browse files Browse the repository at this point in the history
  • Loading branch information
mbryzek committed Aug 1, 2024
1 parent fdafea1 commit afa0e74
Show file tree
Hide file tree
Showing 10 changed files with 641 additions and 174 deletions.
36 changes: 17 additions & 19 deletions api/app/controllers/Attributes.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package controllers

import db.AttributesDao
import cats.data.Validated.{Invalid, Valid}
import db.InternalAttributesDao
import lib.Validation
import io.apibuilder.api.v0.models.AttributeForm
import io.apibuilder.api.v0.models.json._
import play.api.mvc._
import play.api.libs.json._
import io.apibuilder.api.v0.models.json.*
import models.AttributesModel
import play.api.mvc.*
import play.api.libs.json.*

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

@Singleton
class Attributes @Inject() (
val apiBuilderControllerComponents: ApiBuilderControllerComponents,
attributesDao: AttributesDao
attributesDao: InternalAttributesDao,
model: AttributesModel
) extends ApiBuilderController {

def get(
Expand All @@ -24,16 +28,16 @@ class Attributes @Inject() (
val attributes = attributesDao.findAll(
guid = guid,
name = name,
limit = limit,
limit = Some(limit),
offset = offset
)
Ok(Json.toJson(attributes))
Ok(Json.toJson(model.toModels(attributes)))
}

def getByName(name: String): Action[AnyContent] = Action { _ =>
attributesDao.findByName(name) match {
case None => NotFound
case Some(attribute) => Ok(Json.toJson(attribute))
case Some(attribute) => Ok(Json.toJson(model.toModel(attribute)))
}
}

Expand All @@ -42,16 +46,10 @@ class Attributes @Inject() (
case e: JsError => {
UnprocessableEntity(Json.toJson(Validation.invalidJson(e)))
}
case s: JsSuccess[AttributeForm] => {
val form = s.get
attributesDao.validate(form) match {
case Nil => {
val attribute = attributesDao.create(request.user, form)
Created(Json.toJson(attribute))
}
case errors => {
Conflict(Json.toJson(errors))
}
case JsSuccess(form: AttributeForm, _)=> {
attributesDao.create(request.user, form) match {
case Valid(attribute) => Created(Json.toJson(model.toModel(attribute)))
case Invalid(errors) => Conflict(Json.toJson(errors.toNonEmptyList.toList))
}
}
}
Expand All @@ -63,7 +61,7 @@ class Attributes @Inject() (
NotFound
}
case Some(attribute) => {
if (attribute.audit.createdBy.guid == request.user.guid) {
if (attribute.db.createdByGuid == request.user.guid) {
attributesDao.softDelete(request.user, attribute)
NoContent
} else {
Expand Down
10 changes: 5 additions & 5 deletions api/app/controllers/Organizations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import java.util.UUID

@Singleton
class Organizations @Inject() (
val apiBuilderControllerComponents: ApiBuilderControllerComponents,
attributesDao: AttributesDao,
organizationAttributeValuesDao: OrganizationAttributeValuesDao,
model: OrganizationsModel
val apiBuilderControllerComponents: ApiBuilderControllerComponents,
attributesDao: InternalAttributesDao,
organizationAttributeValuesDao: OrganizationAttributeValuesDao,
model: OrganizationsModel
) extends ApiBuilderController {

def get(
Expand Down Expand Up @@ -179,7 +179,7 @@ class Organizations @Inject() (
private def withAttribute(
name: String
) (
f: Attribute => Result
f: InternalAttribute => Result
) = {
attributesDao.findByName(name) match {
case None => {
Expand Down
142 changes: 0 additions & 142 deletions api/app/db/AttributesDao.scala

This file was deleted.

104 changes: 104 additions & 0 deletions api/app/db/InternalAttributesDao.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package db

import cats.data.Validated.{Invalid, Valid}
import cats.data.ValidatedNec
import cats.implicits.*
import db.generated.AttributesDao
import io.apibuilder.api.v0.models.{AttributeForm, User}
import lib.{UrlKey, Validation}

import java.util.UUID
import javax.inject.Inject

case class InternalAttribute(db: generated.Attribute) {
val guid: UUID = db.guid
val name: String = db.name
val description: Option[String] = db.description
}

case class ValidatedAttributeForm(
name: String,
description: Option[String] = None
)

class InternalAttributesDao @Inject()(
dao: AttributesDao
) {

private def validate(
form: AttributeForm
): ValidatedNec[io.apibuilder.api.v0.models.Error, ValidatedAttributeForm] = {
(
validateName(form.name),
validateDescription(form.description),
).mapN { case (n,d) => ValidatedAttributeForm(n, d) }
}

private def validateName(name: String): ValidatedNec[io.apibuilder.api.v0.models.Error, String] = {
val trimmed = name.trim
if (trimmed.isEmpty) {
Validation.singleError("Attribute name is required").invalidNec
} else {
UrlKey.validateNec(trimmed, "Name") match {
case Invalid(e) => {
Validation.singleError(e.toNonEmptyList.toList.mkString(", ")).invalidNec
}
case Valid(_) => {
findByName(trimmed) match {
case None => trimmed.validNec
case Some(_) => Validation.singleError("Attribute with this name already exists").invalidNec
}
}
}
}
}

private def validateDescription(desc: Option[String]): ValidatedNec[io.apibuilder.api.v0.models.Error, Option[String]] = {
desc.map(_.trim).filterNot(_.isEmpty).validNec
}

def create(user: User, form: AttributeForm): ValidatedNec[io.apibuilder.api.v0.models.Error, InternalAttribute] = {
validate(form).map { vForm =>
val guid = dao.insert(user.guid, db.generated.AttributeForm(
name = vForm.name,
description = vForm.description
))

findByGuid(guid).getOrElse {
sys.error("Failed to create attribute")
}
}
}

def softDelete(deletedBy: User, attributes: InternalAttribute): Unit = {
dao.delete(deletedBy.guid, attributes.db)
}

def findByGuid(guid: UUID): Option[InternalAttribute] = {
findAll(guid = Some(guid), limit = Some(1)).headOption
}

def findByName(name: String): Option[InternalAttribute] = {
findAll(name = Some(name), limit = Some(1)).headOption
}

def findAll(
guid: Option[UUID] = None,
name: Option[String] = None,
isDeleted: Option[Boolean] = Some(false),
limit: Option[Long],
offset: Long = 0
): Seq[InternalAttribute] = {
dao.findAll(
guid = guid,
limit = limit,
offset = offset
) { q =>
q.and(name.map { _ =>
"lower(trim(attributes.name)) = lower(trim({name}))"
}).bind("name", name)
.and(isDeleted.map(Filters.isDeleted("attributes", _)))
}.map(InternalAttribute(_))
}

}
6 changes: 3 additions & 3 deletions api/app/db/OrganizationAttributeValuesDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import javax.inject.{Inject, Singleton}
@Singleton
class OrganizationAttributeValuesDao @Inject() (
@NamedDatabase("default") db: Database,
attributesDao: AttributesDao
attributesDao: InternalAttributesDao
) {

private val dbHelpers = DbHelpers(db, "organization_attribute_values")
Expand Down Expand Up @@ -72,14 +72,14 @@ class OrganizationAttributeValuesDao @Inject() (
Validation.errors(attributeErrors ++ valueErrors)
}

def upsert(user: User, organization: InternalOrganization, attribute: Attribute, form: AttributeValueForm): AttributeValue = {
def upsert(user: User, organization: InternalOrganization, attribute: InternalAttribute, form: AttributeValueForm): AttributeValue = {
findByOrganizationGuidAndAttributeName(organization.guid, attribute.name) match {
case None => create(user, organization, attribute, form)
case Some(existing) => update(user, organization, existing, form)
}
}

def create(user: User, organization: InternalOrganization, attribute: Attribute, form: AttributeValueForm): AttributeValue = {
def create(user: User, organization: InternalOrganization, attribute: InternalAttribute, form: AttributeValueForm): AttributeValue = {
val errors = validate(organization, AttributeSummary(attribute.guid, attribute.name), form, None)
assert(errors.isEmpty, errors.map(_.message).mkString("\n"))

Expand Down
Loading

0 comments on commit afa0e74

Please sign in to comment.