Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/main/scala/api/invitations/Activate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@ 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

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()
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/api/storages/StorageSummaryResp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
42 changes: 29 additions & 13 deletions src/main/scala/db/repositories/InvitationsRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] =
Expand All @@ -34,13 +41,13 @@ 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)))
.provideDS
.map(_.headOption)
.someOrFail(InvalidInvitationHash(invitationHash))
(dbInvitation, storageName) = dbInvitationWithName
isMemberOrOwner <- ZIO.serviceWithZIO[StorageMembersRepo](_.checkForMembership(dbInvitation.storageId))
_ <- ZIO.unless(isMemberOrOwner) {
for
Expand All @@ -49,15 +56,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down