From d697e60d522d35f2c5927986850b23b49b624478 Mon Sep 17 00:00:00 2001 From: TheBugYouCantFix Date: Mon, 21 Jul 2025 01:56:47 +0300 Subject: [PATCH 1/4] refactor: rm ownerId --- src/main/scala/api/storages/StorageSummaryResp.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/api/storages/StorageSummaryResp.scala b/src/main/scala/api/storages/StorageSummaryResp.scala index 2cb814ff..4c31a9c7 100644 --- a/src/main/scala/api/storages/StorageSummaryResp.scala +++ b/src/main/scala/api/storages/StorageSummaryResp.scala @@ -3,8 +3,8 @@ package api.storages import db.tables.DbStorage import domain.{StorageId, UserId} -final case class StorageSummaryResp(id: StorageId, ownerId: UserId, name: String) +final case class StorageSummaryResp(id: StorageId, name: String) object StorageSummaryResp: def fromDb(dbStorage: DbStorage): StorageSummaryResp = - StorageSummaryResp(dbStorage.id, dbStorage.ownerId, dbStorage.name) + StorageSummaryResp(dbStorage.id, dbStorage.name) From 9b7b4f2a240c5b7b7f6690e9f046e3b80f61b21a Mon Sep 17 00:00:00 2001 From: TheBugYouCantFix Date: Mon, 21 Jul 2025 01:57:28 +0300 Subject: [PATCH 2/4] feat: make activate endpoint return storage id & name --- src/main/scala/api/invitations/Activate.scala | 10 ++++- .../db/repositories/InvitationsRepo.scala | 41 +++++++++++++------ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/main/scala/api/invitations/Activate.scala b/src/main/scala/api/invitations/Activate.scala index fb3d5c39..c4b3b03e 100644 --- a/src/main/scala/api/invitations/Activate.scala +++ b/src/main/scala/api/invitations/Activate.scala @@ -2,9 +2,13 @@ package api.invitations import api.Authentication.{AuthenticatedUser, zSecuredServerLogic} import api.EndpointErrorVariants.{invalidInvitationHashVariant, serverErrorVariant} +import api.storages.StorageSummaryResp import db.repositories.{InvitationsRepo, StorageMembersRepo} import domain.{InternalServerError, InvalidInvitationHash} +import io.circe.generic.auto.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.ztapir.* import zio.ZIO @@ -12,12 +16,14 @@ private type ActivateEnv = InvitationsRepo & StorageMembersRepo val activate: ZServerEndpoint[ActivateEnv, Any] = invitationEndpoint .post .in(path[String]("invitationHash") / "activate") + .out(jsonBody[StorageSummaryResp]) .errorOut(oneOf(invalidInvitationHashVariant, serverErrorVariant)) .zSecuredServerLogic(activateHandler) def activateHandler(invitationHash: String): - ZIO[AuthenticatedUser & ActivateEnv, InvalidInvitationHash | InternalServerError, Unit] = - ZIO.serviceWithZIO[InvitationsRepo](_.activate(invitationHash)).debug + ZIO[AuthenticatedUser & ActivateEnv, InvalidInvitationHash | InternalServerError, StorageSummaryResp] = + ZIO.serviceWithZIO[InvitationsRepo](_.activate(invitationHash)) + .map{case (storageId, storageName) => StorageSummaryResp(storageId, storageName)} .mapError { case e: InvalidInvitationHash => e case _ => InternalServerError() diff --git a/src/main/scala/db/repositories/InvitationsRepo.scala b/src/main/scala/db/repositories/InvitationsRepo.scala index cd86f089..fddf7b3f 100644 --- a/src/main/scala/db/repositories/InvitationsRepo.scala +++ b/src/main/scala/db/repositories/InvitationsRepo.scala @@ -2,23 +2,30 @@ package db.repositories import api.Authentication.AuthenticatedUser import db.{DbError, handleDbError} -import db.tables.{DbStorageInvitation, storageInvitationTable} +import db.tables.{DbStorage, DbStorageInvitation, storageInvitationTable} import domain.{InternalServerError, InvalidInvitationHash, StorageAccessForbidden, StorageId} import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.util.concurrent.TimeUnit +import io.getquill.* + +import javax.sql.DataSource import com.augustnagro.magnum.magzio.* -import zio.{URLayer, Layer, System, ZIO, ZLayer, Clock} +import zio.{Clock, Layer, System, URLayer, ZIO, ZLayer} trait InvitationsRepo: def create(storageId: StorageId): ZIO[AuthenticatedUser & StorageMembersRepo, DbError | InternalServerError | StorageAccessForbidden, String] def activate(hash: String): - ZIO[AuthenticatedUser & StorageMembersRepo, DbError | InvalidInvitationHash, Unit] + ZIO[AuthenticatedUser & StorageMembersRepo, DbError | InvalidInvitationHash, (StorageId, String)] -private final case class InvitationsRepoLive(xa: Transactor, secretKey: InvitationsSecretKey) +private final case class InvitationsRepoLive(xa: Transactor, dataSource: DataSource, secretKey: InvitationsSecretKey) extends Repo[DbStorageInvitation, DbStorageInvitation, Null] with InvitationsRepo: + private given DataSource = dataSource + import db.QuillConfig.ctx.* + import db.QuillConfig.provideDS + import InvitationQueries.* override def create(storageId: StorageId): ZIO[AuthenticatedUser & StorageMembersRepo, DbError | InternalServerError, String] = @@ -34,13 +41,12 @@ private final case class InvitationsRepoLive(xa: Transactor, secretKey: Invitati yield invitationHash override def activate(invitationHash: String): - ZIO[AuthenticatedUser & StorageMembersRepo, DbError | InvalidInvitationHash, Unit] = + ZIO[AuthenticatedUser & StorageMembersRepo, DbError | InvalidInvitationHash, (StorageId, String)] = for - dbInvitation <- xa.transact { - findAll(Spec[DbStorageInvitation] - .where(sql"${storageInvitationTable.invitation} = $invitationHash") - ).headOption - }.mapError(handleDbError).someOrFail(InvalidInvitationHash(invitationHash)) + dbInvitationWithName <- run(getInvitationWithStorageNameByHashQ(lift(invitationHash)).value) + .provideDS + .someOrFail(InvalidInvitationHash(invitationHash)) + (dbInvitation, storageName) = dbInvitationWithName isMemberOrOwner <- ZIO.serviceWithZIO[StorageMembersRepo](_.checkForMembership(dbInvitation.storageId)) _ <- ZIO.unless(isMemberOrOwner) { for @@ -49,15 +55,24 @@ private final case class InvitationsRepoLive(xa: Transactor, secretKey: Invitati _ <- ZIO.serviceWithZIO[StorageMembersRepo](_.addMemberToStorageById(dbInvitation.storageId, userId)) yield () } - yield () + yield (dbInvitation.storageId, storageName) + +object InvitationQueries: + inline def getInvitationWithStorageNameByHashQ(inline invitationHash: String): Query[(DbStorageInvitation, String)] = + query[DbStorageInvitation] + .filter(_.invitation == invitationHash) + .join(query[DbStorage]) + .on(_.storageId == _.id) + .map((i, s) => (i, s.name)) object InvitationsRepo: - val layer: URLayer[Transactor & InvitationsSecretKey, InvitationsRepoLive] = + val layer: URLayer[Transactor & DataSource & InvitationsSecretKey, InvitationsRepoLive] = ZLayer.fromZIO( for xa <- ZIO.service[Transactor] + dataSource <- ZIO.service[DataSource] secretKey <- ZIO.service[InvitationsSecretKey] - yield InvitationsRepoLive(xa, secretKey) + yield InvitationsRepoLive(xa, dataSource, secretKey) ) final case class InvitationsSecretKey(value: String) From b6a0228ae14eb7cb88487887fe9696ac9bab0ac7 Mon Sep 17 00:00:00 2001 From: TheBugYouCantFix Date: Mon, 21 Jul 2025 02:03:33 +0300 Subject: [PATCH 3/4] fix: tests --- .../scala/integration/api/storages/GetAllStoragesTests.scala | 1 - .../scala/integration/api/storages/GetStorageSummaryTests.scala | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/test/scala/integration/api/storages/GetAllStoragesTests.scala b/src/test/scala/integration/api/storages/GetAllStoragesTests.scala index 568bb384..12e44cc4 100644 --- a/src/test/scala/integration/api/storages/GetAllStoragesTests.scala +++ b/src/test/scala/integration/api/storages/GetAllStoragesTests.scala @@ -50,7 +50,6 @@ object GetAllStoragesTests extends ZIOIntegrationTestSpec: storages <- ZIO.fromEither(decode[Vector[StorageSummaryResp]](bodyStr)) yield assertTrue(resp.status == Status.Ok) && assertTrue(storages.map(_.name).hasSameElementsAs(storageNames)) - && assertTrue(storages.forall(_.ownerId == user.userId)) }, test("When authorized with membered storages should get 200 and all storages") { for diff --git a/src/test/scala/integration/api/storages/GetStorageSummaryTests.scala b/src/test/scala/integration/api/storages/GetStorageSummaryTests.scala index 55fca6d3..02b6a7ab 100644 --- a/src/test/scala/integration/api/storages/GetStorageSummaryTests.scala +++ b/src/test/scala/integration/api/storages/GetStorageSummaryTests.scala @@ -54,7 +54,6 @@ object GetStorageSummaryTests extends ZIOIntegrationTestSpec: yield assertTrue(resp.status == Status.Ok) && assertTrue(storage.id == storageId) && assertTrue(storage.name == storageName) - && assertTrue(storage.ownerId == user.userId) }, test("When authorized and user is a member of the storage should get 200 and the storage") { for @@ -77,7 +76,6 @@ object GetStorageSummaryTests extends ZIOIntegrationTestSpec: yield assertTrue(resp.status == Status.Ok) && assertTrue(storage.id == storageId) && assertTrue(storage.name == storageName) - && assertTrue(storage.ownerId == creator.userId) }, test("When authorized but user is neither the owner nor a member should get 404") { for From d1f81490b78aad6eabe89d00921efa3119b78816 Mon Sep 17 00:00:00 2001 From: TheBugYouCantFix Date: Mon, 21 Jul 2025 02:21:17 +0300 Subject: [PATCH 4/4] fix: error --- src/main/scala/db/repositories/InvitationsRepo.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/db/repositories/InvitationsRepo.scala b/src/main/scala/db/repositories/InvitationsRepo.scala index fddf7b3f..1217260d 100644 --- a/src/main/scala/db/repositories/InvitationsRepo.scala +++ b/src/main/scala/db/repositories/InvitationsRepo.scala @@ -43,8 +43,9 @@ private final case class InvitationsRepoLive(xa: Transactor, dataSource: DataSou override def activate(invitationHash: String): ZIO[AuthenticatedUser & StorageMembersRepo, DbError | InvalidInvitationHash, (StorageId, String)] = for - dbInvitationWithName <- run(getInvitationWithStorageNameByHashQ(lift(invitationHash)).value) + dbInvitationWithName <- run(getInvitationWithStorageNameByHashQ(lift(invitationHash))) .provideDS + .map(_.headOption) .someOrFail(InvalidInvitationHash(invitationHash)) (dbInvitation, storageName) = dbInvitationWithName isMemberOrOwner <- ZIO.serviceWithZIO[StorageMembersRepo](_.checkForMembership(dbInvitation.storageId))