diff --git a/src/main/scala/api/recipes/publicationRequests/Create.scala b/src/main/scala/api/recipes/publicationRequests/Create.scala index b6defd8..3b44525 100644 --- a/src/main/scala/api/recipes/publicationRequests/Create.scala +++ b/src/main/scala/api/recipes/publicationRequests/Create.scala @@ -16,12 +16,12 @@ import sttp.tapir.generic.auto.* import sttp.tapir.ztapir.* import zio.ZIO -final case class CannotPublishRecipeWithCustomIngredients( +final case class CannotPublishRecipeWithPrivateIngredients( ingredients: Seq[IngredientId], - message: String = "Cannot publish recipe with custom ingredients", + message: String = "Cannot publish recipe with private ingredients", ) -object CannotPublishRecipeWithCustomIngredients: - val variant = BadRequest.variantJson[CannotPublishRecipeWithCustomIngredients] +object CannotPublishRecipeWithPrivateIngredients: + val variant = BadRequest.variantJson[CannotPublishRecipeWithPrivateIngredients] final case class RecipeAlreadyPublished( recipeId: RecipeId, @@ -50,7 +50,7 @@ private val create: ZServerEndpoint[CreateEnv, Any] = .errorOut(oneOf( serverErrorVariant, recipeNotFoundVariant, - CannotPublishRecipeWithCustomIngredients.variant, + CannotPublishRecipeWithPrivateIngredients.variant, RecipeAlreadyPending.variant, RecipeAlreadyPublished.variant, )) @@ -60,7 +60,7 @@ private def createHandler(recipeId: RecipeId): ZIO[ AuthenticatedUser & CreateEnv, InternalServerError | RecipeAlreadyPublished | RecipeAlreadyPending - | CannotPublishRecipeWithCustomIngredients | RecipeNotFound, + | CannotPublishRecipeWithPrivateIngredients | RecipeNotFound, PublicationRequestId ] = for @@ -81,15 +81,15 @@ private def createHandler(recipeId: RecipeId): .when(alreadyPending) userId <- ZIO.serviceWith[AuthenticatedUser](_.userId) - customIngredientIdsInRecipe <- run( - IngredientsQueries.customIngredientsQ(lift(userId)) + privateIngredientIdsInRecipe <- run( + IngredientsQueries.privateIngredientsQ(lift(userId)) .filter(i => liftQuery(recipe.ingredients).contains(i.id)) .map(_.id) ).provideDS(using dataSource) .orElseFail(InternalServerError()) - _ <- ZIO.fail(CannotPublishRecipeWithCustomIngredients(customIngredientIdsInRecipe)) - .when(customIngredientIdsInRecipe.nonEmpty) + _ <- ZIO.fail(CannotPublishRecipeWithPrivateIngredients(privateIngredientIdsInRecipe)) + .when(privateIngredientIdsInRecipe.nonEmpty) reqId <- ZIO.serviceWithZIO[RecipePublicationRequestsRepo](_ .createPublicationRequest(recipeId) diff --git a/src/main/scala/api/recipes/publicationRequests/GetAll.scala b/src/main/scala/api/recipes/publicationRequests/GetAll.scala index 300ce44..494d763 100644 --- a/src/main/scala/api/recipes/publicationRequests/GetAll.scala +++ b/src/main/scala/api/recipes/publicationRequests/GetAll.scala @@ -3,25 +3,24 @@ package api.recipes.publicationRequests import api.Authentication.{AuthenticatedUser, zSecuredServerLogic} import api.EndpointErrorVariants.{recipeNotFoundVariant, serverErrorVariant} import api.PublicationRequestStatusResp -import api.moderation.ModerationHistoryResponse import db.repositories.{RecipePublicationRequestsRepo, RecipesRepo} import domain.{InternalServerError, RecipeId, RecipeNotFound} + import io.circe.Decoder import io.circe.derivation.Configuration import io.circe.generic.auto.* +import java.time.OffsetDateTime import sttp.tapir.generic.auto.* import sttp.tapir.json.circe.* import sttp.tapir.ztapir.* import zio.ZIO -import java.time.OffsetDateTime - final case class RecipeModerationHistoryResponse( - createdAt: OffsetDateTime, - updatedAt: OffsetDateTime, - status: PublicationRequestStatusResp, - reason: Option[String] - ) + createdAt: OffsetDateTime, + updatedAt: OffsetDateTime, + status: PublicationRequestStatusResp, +) + private type GetAllEnv = RecipePublicationRequestsRepo & RecipesRepo val getAll: ZServerEndpoint[GetAllEnv, Any] = @@ -38,9 +37,10 @@ private def getAllHandler(recipeId: RecipeId): List[RecipeModerationHistoryResponse] ] = for - isUserOwner <- ZIO.serviceWithZIO[RecipesRepo](_.isUserOwner(recipeId)) + userIsOwner <- ZIO.serviceWithZIO[RecipesRepo](_.isUserOwner(recipeId)) .orElseFail(InternalServerError()) - _ <- ZIO.fail(RecipeNotFound(recipeId)).unless(isUserOwner) + _ <- ZIO.fail(RecipeNotFound(recipeId)) + .unless(userIsOwner) dbRequests <- ZIO.serviceWithZIO[RecipePublicationRequestsRepo](_.getAllByRecipeId(recipeId)) .orElseFail(InternalServerError()) @@ -49,9 +49,7 @@ private def getAllHandler(recipeId: RecipeId): dbReq => RecipeModerationHistoryResponse( dbReq.createdAt, dbReq.updatedAt, PublicationRequestStatusResp.fromDomain(dbReq.status.toDomain(dbReq.reason)), - dbReq.reason ) ) .sortBy(_.updatedAt) - yield res diff --git a/src/main/scala/api/storages/GetAll.scala b/src/main/scala/api/storages/GetAll.scala index d1d38ef..7b20694 100644 --- a/src/main/scala/api/storages/GetAll.scala +++ b/src/main/scala/api/storages/GetAll.scala @@ -3,7 +3,7 @@ package api.storages import api.Authentication.{zSecuredServerLogic, AuthenticatedUser} import api.EndpointErrorVariants.serverErrorVariant import db.repositories.StoragesRepo -import domain.{UserId, StorageId, InternalServerError} +import domain.{StorageId, InternalServerError} import io.circe.generic.auto.* import sttp.tapir.generic.auto.* diff --git a/src/main/scala/db/repositories/IngredientPublicationRequestRepo.scala b/src/main/scala/db/repositories/IngredientPublicationRequestRepo.scala index 73619d2..509654a 100644 --- a/src/main/scala/db/repositories/IngredientPublicationRequestRepo.scala +++ b/src/main/scala/db/repositories/IngredientPublicationRequestRepo.scala @@ -1,14 +1,12 @@ package db.repositories import api.Authentication.AuthenticatedUser -import api.PublicationRequestStatusResp -import api.moderation.ModerationHistoryResponse import db.DbError import db.tables.DbIngredient import db.tables.publication.{DbIngredientPublicationRequest, DbPublicationRequestStatus} import domain.{IngredientId, PublicationRequestId, PublicationRequestStatus, UserId} -import io.getquill.* +import io.getquill.* import javax.sql.DataSource import java.util.UUID import zio.{IO, RLayer, ZIO, ZLayer} diff --git a/src/main/scala/db/repositories/IngredientsRepo.scala b/src/main/scala/db/repositories/IngredientsRepo.scala index 95265b3..de8e363 100644 --- a/src/main/scala/db/repositories/IngredientsRepo.scala +++ b/src/main/scala/db/repositories/IngredientsRepo.scala @@ -98,7 +98,10 @@ object IngredientsQueries: ingredientsQ.filter(_.isPublished) inline def customIngredientsQ(inline userId: UserId): EntityQuery[DbIngredient] = - ingredientsQ.filter(i => i.ownerId.contains(userId)) + ingredientsQ.filter(_.ownerId.contains(userId)) + + inline def privateIngredientsQ(inline userId: UserId): EntityQuery[DbIngredient] = + customIngredientsQ(userId).filter(!_.isPublished) inline def visibleIngredientsQ(inline userId: UserId): EntityQuery[DbIngredient] = ingredientsQ.filter(i => i.isPublished || i.ownerId.contains(userId)) diff --git a/src/test/scala/integration/api/recipes/RequestRecipePublicationTests.scala b/src/test/scala/integration/api/recipes/RequestRecipePublicationTests.scala index 066bcdf..50bc3e3 100644 --- a/src/test/scala/integration/api/recipes/RequestRecipePublicationTests.scala +++ b/src/test/scala/integration/api/recipes/RequestRecipePublicationTests.scala @@ -12,6 +12,8 @@ import io.circe.parser.decode import zio.* import zio.http.* import zio.test.* +import db.repositories.IngredientsRepo +import api.recipes.publicationRequests.CannotPublishRecipeWithPrivateIngredients object RequestRecipePublicationTests extends ZIOIntegrationTestSpec: private def endpointPath(recipeId: RecipeId): URL = @@ -58,6 +60,70 @@ object RequestRecipePublicationTests extends ZIOIntegrationTestSpec: yield assertTrue(resp.status == Status.Created) && assertTrue(request.is(_.some).recipeId == recipeId) && assertTrue(request.is(_.some).status == DbPublicationRequestStatus.Pending) - } + }, + test("When requesting publication of recipe with no ingredients, pending request should be created") { + for + user <- registerUser + + recipeId <- createCustomRecipe(user, Vector.empty) + + resp <- requestRecipePublication(user, recipeId) + + bodyStr <- resp.body.asString + requestId <- ZIO.fromOption(bodyStr.toUUID) + request <- ZIO.serviceWithZIO[RecipePublicationRequestsRepo](_.get(requestId)) + yield assertTrue(resp.status == Status.Created) + && assertTrue(request.is(_.some).recipeId == recipeId) + && assertTrue(request.is(_.some).status == DbPublicationRequestStatus.Pending) + }, + test("""When requesting publication of recipe with published custom ingredients, + pending request should be created""") { + for + user <- registerUser + + n <- Gen.int(2, 8).runHead.some + ingredientIds <- ZIO.foreach(1 to n)(_ => for + ingredientId <- createCustomIngredient(user) + _ <- ZIO.serviceWithZIO[IngredientsRepo](_.publish(ingredientId)) + yield ingredientId) + recipeId <- createCustomRecipe(user, ingredientIds.toVector) + + resp <- requestRecipePublication(user, recipeId) + + bodyStr <- resp.body.asString + requestId <- ZIO.fromOption(bodyStr.toUUID) + request <- ZIO.serviceWithZIO[RecipePublicationRequestsRepo](_.get(requestId)) + yield assertTrue(resp.status == Status.Created) + && assertTrue(request.is(_.some).recipeId == recipeId) + && assertTrue(request.is(_.some).status == DbPublicationRequestStatus.Pending) + }, + test("""When requesting publication of recipe with private ingredients, + should get 400 Cannot publish recipe with private ingredients""") { + for + user <- registerUser + + n <- Gen.int(2, 8).runHead.some + publishedCustomIngredients <- ZIO.foreach(1 to n)(_ => for + ingredientId <- createCustomIngredient(user) + _ <- ZIO.serviceWithZIO[IngredientsRepo](_.publish(ingredientId)) + yield ingredientId) + m <- Gen.int(2, 8).runHead.some + publiсIngredients <- createNPublicIngredients(m) + + k <- Gen.int(2, 8).runHead.some + privateIngredients <- ZIO.foreach(1 to n)(_ => createCustomIngredient(user)) + + ingredientIds = publishedCustomIngredients ++ publiсIngredients ++ privateIngredients + + recipeId <- createCustomRecipe(user, ingredientIds.toVector) + + resp <- requestRecipePublication(user, recipeId) + + bodyStr <- resp.body.asString + error = decode[CannotPublishRecipeWithPrivateIngredients](bodyStr) + yield assertTrue(resp.status == Status.BadRequest) + && assertTrue(error.isRight) + && assertTrue(error.forall(_.ingredients hasSameElementsAs privateIngredients)) + }, ).provideLayer(testLayer)