diff --git a/CHANGELOG.md b/CHANGELOG.md index cb7f086..7525955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.12.0](https://github.com/hawks-atlanta/metadata-scala/compare/v0.11.1...v0.12.0) (2023-10-22) + + +### Features + +* Unshare file ([#89](https://github.com/hawks-atlanta/metadata-scala/issues/89)) ([be58b4a](https://github.com/hawks-atlanta/metadata-scala/commit/be58b4ad112ebe141f88781e8ea7d04713f1bd7d)) + + + ## [0.11.1](https://github.com/hawks-atlanta/metadata-scala/compare/v0.11.0...v0.11.1) (2023-10-14) @@ -34,12 +43,3 @@ -## [0.10.2](https://github.com/hawks-atlanta/metadata-scala/compare/v0.10.1...v0.10.2) (2023-09-29) - - -### Bug Fixes - -* Ignore ready state validations for directories ([#73](https://github.com/hawks-atlanta/metadata-scala/issues/73)) ([fa5467f](https://github.com/hawks-atlanta/metadata-scala/commit/fa5467f86bda9312a6dad474bbdd3f5360a875c9)) - - - diff --git a/docs/bruno/shared-with-who/shared-with-who.bru b/docs/bruno/shared-with-who/shared-with-who.bru index 5997c33..79a412f 100644 --- a/docs/bruno/shared-with-who/shared-with-who.bru +++ b/docs/bruno/shared-with-who/shared-with-who.bru @@ -5,7 +5,7 @@ meta { } get { - url: {{BASE_URL}}/files/shared_with_who/22a7c6ca-8e57-46d2-9977-43dcbfcac760 + url: {{BASE_URL}}/files/shared_with_who/76c5a635-08c4-46c9-89ef-89c120e2e55f body: none auth: none } diff --git a/docs/bruno/unshare-file/unshare-file.bru b/docs/bruno/unshare-file/unshare-file.bru new file mode 100644 index 0000000..d16dbac --- /dev/null +++ b/docs/bruno/unshare-file/unshare-file.bru @@ -0,0 +1,17 @@ +meta { + name: unshare-file + type: http + seq: 1 +} + +post { + url: {{BASE_URL}}/files/unshare/5c8e5e9d-7d82-450f-9fe3-43fc73aa5a39/76c5a635-08c4-46c9-89ef-89c120e2e55f + body: json + auth: none +} + +body:json { + { + "otherUserUUID": "e1139bb6-d291-4170-8fb0-94dd787fa84b" + } +} diff --git a/src/main/scala/files_metadata/application/FilesMetaUseCases.scala b/src/main/scala/files_metadata/application/FilesMetaUseCases.scala index d565ba2..d921238 100644 --- a/src/main/scala/files_metadata/application/FilesMetaUseCases.scala +++ b/src/main/scala/files_metadata/application/FilesMetaUseCases.scala @@ -228,4 +228,31 @@ class FilesMetaUseCases { repository.updateFileParent( fileUUID, newParentUUID ) } + + def unShareFile( + ownerUUID: UUID, + fileUUID: UUID, + otherUserUUID: UUID + ): Unit = { + val fileMeta = repository.getFileMeta( fileUUID ) + + if (fileMeta.ownerUuid != ownerUUID) { + throw DomainExceptions.FileNotOwnedException( + "You don't own the file" + ) + } + if (ownerUUID == otherUserUUID) { + throw DomainExceptions.FileNotOwnedException( + "You cannot un-share a file with yourself" + ) + } + + if (!repository.isFileDirectlySharedWithUser( fileUUID, otherUserUUID )) { + throw DomainExceptions.FileAlreadySharedException( + "The file is not shared with the given user" + ) + } + + repository.unShareFile( fileUUID, otherUserUUID ) + } } diff --git a/src/main/scala/files_metadata/domain/FilesMetaRepository.scala b/src/main/scala/files_metadata/domain/FilesMetaRepository.scala index b0bc88e..a1a35e0 100644 --- a/src/main/scala/files_metadata/domain/FilesMetaRepository.scala +++ b/src/main/scala/files_metadata/domain/FilesMetaRepository.scala @@ -44,6 +44,7 @@ trait FilesMetaRepository { def updateFileName( fileUUID: UUID, newName: String ): Unit def updateFileParent( fileUUID: UUID, parentUUID: Option[UUID] ): Unit + def unShareFile( fileUUID: UUID, userUUID: UUID ): Unit // --- Delete --- def deleteFileMeta( ownerUuid: UUID, uuid: UUID ): Unit diff --git a/src/main/scala/files_metadata/infrastructure/FilesMetaPostgresRepository.scala b/src/main/scala/files_metadata/infrastructure/FilesMetaPostgresRepository.scala index dda7d48..c82290c 100644 --- a/src/main/scala/files_metadata/infrastructure/FilesMetaPostgresRepository.scala +++ b/src/main/scala/files_metadata/infrastructure/FilesMetaPostgresRepository.scala @@ -589,5 +589,36 @@ class FilesMetaPostgresRepository extends FilesMetaRepository { } } + override def unShareFile( fileUUID: UUID, userUUID: UUID ): Unit = { + val connection: Connection = pool.getConnection() + connection.setAutoCommit( false ) + + try { + val shareStatement = connection.prepareStatement( + "DELETE FROM shared_files WHERE file_uuid =? AND user_uuid =?" + ) + shareStatement.setObject( 1, fileUUID ) + shareStatement.setObject( 2, userUUID ) + shareStatement.executeUpdate() + + val checkSharedStatement = connection.prepareStatement( + "SELECT * FROM shared_files WHERE file_uuid = ?" + ) + checkSharedStatement.setObject( 1, fileUUID ) + val resultSet = checkSharedStatement.executeQuery() + if (!resultSet.next()) { + val updateStatement = connection.prepareStatement( + "UPDATE files SET is_shared = false WHERE uuid = ?" + ) + updateStatement.setObject( 1, fileUUID ) + updateStatement.executeUpdate() + } + + connection.commit() + } finally { + connection.close() + } + } + override def deleteFileMeta( ownerUuid: UUID, uuid: UUID ): Unit = ??? } diff --git a/src/main/scala/files_metadata/infrastructure/MetadataControllers.scala b/src/main/scala/files_metadata/infrastructure/MetadataControllers.scala index 4dc5106..ee755cc 100644 --- a/src/main/scala/files_metadata/infrastructure/MetadataControllers.scala +++ b/src/main/scala/files_metadata/infrastructure/MetadataControllers.scala @@ -622,4 +622,47 @@ class MetadataControllers { case e: Exception => _handleException( e ) } } + + def UnShareFileController( + request: cask.Request, + ownerUUID: String, + fileUUID: String + ): cask.Response[Obj] = { + try { + val decoded: ShareReqSchema = read[ShareReqSchema]( + request.text() + ) + + val isOwnerUUIDValid = CommonValidator.validateUUID( ownerUUID ) + val isFileUUIDValid = CommonValidator.validateUUID( fileUUID ) + + val validationRule: Validator[ShareReqSchema] = + ShareReqSchema.shareSchemaValidator + val validationResult = validate[ShareReqSchema]( decoded )( + validationRule + ) + if (!isOwnerUUIDValid || !isFileUUIDValid || validationResult.isFailure) { + return cask.Response( + ujson.Obj( + "error" -> true, + "message" -> "Fields validation failed" + ), + statusCode = 400 + ) + } + + useCases.unShareFile( + ownerUUID = UUID.fromString( ownerUUID ), + fileUUID = UUID.fromString( fileUUID ), + otherUserUUID = UUID.fromString( decoded.otherUserUUID ) + ) + + cask.Response( + None, + statusCode = 204 + ) + } catch { + case e: Exception => _handleException( e ) + } + } } diff --git a/src/main/scala/files_metadata/infrastructure/MetadataRoutes.scala b/src/main/scala/files_metadata/infrastructure/MetadataRoutes.scala index a692552..68b822f 100644 --- a/src/main/scala/files_metadata/infrastructure/MetadataRoutes.scala +++ b/src/main/scala/files_metadata/infrastructure/MetadataRoutes.scala @@ -132,5 +132,20 @@ case class MetadataRoutes() extends cask.Routes { ) } + private val unShareMetadataEndpoint = + s"$basePath/unshare/:ownerUUID/:fileUUID" + + @cask.post( unShareMetadataEndpoint ) + def UnShareMetadataHandler( + request: cask.Request, + ownerUUID: String, + fileUUID: String + ): cask.Response[Obj] = { + StdoutLogger.logAndReturnEndpointResponse( + unShareMetadataEndpoint, + controllers.UnShareFileController( request, ownerUUID, fileUUID ) + ) + } + initialize() } diff --git a/src/test/scala/files_metadata/FilesTestsUtils.scala b/src/test/scala/files_metadata/FilesTestsUtils.scala index 81e92ca..35dddba 100644 --- a/src/test/scala/files_metadata/FilesTestsUtils.scala +++ b/src/test/scala/files_metadata/FilesTestsUtils.scala @@ -108,6 +108,21 @@ object FilesTestsUtils { ) } + def UnShareFile( + ownerUUID: String, + fileUUID: String, + payload: util.HashMap[String, Any] + ): Response = { + `given`() + .port( 8080 ) + .contentType( "application/json" ) + .body( payload ) + .when() + .post( + s"${ UnShareFileTestsData.API_PREFIX }/$ownerUUID/$fileUUID" + ) + } + def generateShareFilePayload( otherUserUUID: UUID ): util.HashMap[String, Any] = { @@ -116,6 +131,14 @@ object FilesTestsUtils { shareFilePayload } + def generateUnshareFilePayload( + otherUserUUID: UUID + ): util.HashMap[String, Any] = { + val unShareFilePayload = new util.HashMap[String, Any]() + unShareFilePayload.put( "otherUserUUID", otherUserUUID.toString ) + unShareFilePayload + } + def GetSharedWithUser( userUUID: String ): Response = { `given`() .port( 8080 ) diff --git a/src/test/scala/files_metadata/UnshareFile.scala b/src/test/scala/files_metadata/UnshareFile.scala new file mode 100644 index 0000000..fde7abc --- /dev/null +++ b/src/test/scala/files_metadata/UnshareFile.scala @@ -0,0 +1,150 @@ +package org.hawksatlanta.metadata +package files_metadata + +import java.util.UUID + +import org.junit.runner.manipulation.Alphanumeric +import org.junit.runner.OrderWith +import org.junit.Before +import org.junit.Test +import org.scalatestplus.junit.JUnitSuite + +object UnShareFileTestsData { + val API_PREFIX: String = "/api/v1/files/unshare" + val OWNER_USER_UUID: UUID = UUID.randomUUID() + val OTHER_USER_UUID: UUID = UUID.randomUUID() + + private var unsharePayload: java.util.HashMap[String, Any] = _ + var savedDirectoryUUID: UUID = _ + var savedFileUUID: UUID = _ + var sharedFileUUID: UUID = _ + var sharedDirectoryUUID: UUID = _ + + def getUnsharePayload(): java.util.HashMap[String, Any] = { + if (unsharePayload == null) { + unsharePayload = FilesTestsUtils.generateUnshareFilePayload( + otherUserUUID = OTHER_USER_UUID + ) + } + + unsharePayload.clone().asInstanceOf[java.util.HashMap[String, Any]] + } +} + +@OrderWith( classOf[Alphanumeric] ) +class UnShareFileTests extends JUnitSuite { + def saveFilesAndShareToUnhare(): Unit = { + // Save a file to share + val saveFilePayload = FilesTestsUtils.generateFilePayload( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID, + parentDirUUID = None + ) + + val saveFileResponse = FilesTestsUtils.SaveFile( saveFilePayload ) + UnShareFileTestsData.savedFileUUID = + UUID.fromString( saveFileResponse.jsonPath().get( "uuid" ) ) + + // Save a directory to share + val saveDirectoryPayload = FilesTestsUtils.generateDirectoryPayload( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID, + parentDirUUID = None + ) + + val saveDirectoryResponse = + FilesTestsUtils.SaveFile( saveDirectoryPayload ) + UnShareFileTestsData.savedDirectoryUUID = + UUID.fromString( saveDirectoryResponse.jsonPath().get( "uuid" ) ) + + val ShareFilePayload = FilesTestsUtils.generateShareFilePayload( + otherUserUUID = UnShareFileTestsData.OTHER_USER_UUID + ) + val ShareFileResponse = FilesTestsUtils.ShareFile( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID.toString, + fileUUID = UnShareFileTestsData.savedFileUUID.toString, + payload = ShareFilePayload + ) + val ShareDirectoryPayload = FilesTestsUtils.generateShareFilePayload( + otherUserUUID = UnShareFileTestsData.OTHER_USER_UUID + ) + val ShareDirectoryResponse = FilesTestsUtils.ShareFile( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID.toString, + fileUUID = UnShareFileTestsData.savedDirectoryUUID.toString, + payload = ShareDirectoryPayload + ) + + } + + @Before + def startHttpServer(): Unit = { + FilesTestsUtils.StartHttpServer() + } + @Test + def T1_UnShareFileBadRequest(): Unit = { + saveFilesAndShareToUnhare() + // 1. Bad other user + val requestBody = ShareFileTestsData.getSharePayload() + requestBody.put( "otherUserUUID", "Not an UUID" ) + val response = FilesTestsUtils.UnShareFile( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID.toString, + fileUUID = UnShareFileTestsData.savedFileUUID.toString, + payload = requestBody + ) + assert( response.statusCode() == 400 ) + assert( response.jsonPath().getBoolean( "error" ) ) + // 2. Bad ownerUserUUID + val response2 = FilesTestsUtils.UnShareFile( + ownerUUID = "Not an UUID", + fileUUID = UnShareFileTestsData.savedFileUUID.toString, + payload = UnShareFileTestsData.getUnsharePayload() + ) + assert( response2.statusCode() == 400 ) + assert( response2.jsonPath().getBoolean( "error" ) ) + // 3. Bad fileUUID + val response3 = FilesTestsUtils.UnShareFile( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID.toString, + fileUUID = "Not an UUID", + payload = UnShareFileTestsData.getUnsharePayload() + ) + assert( response3.statusCode() == 400 ) + assert( response3.jsonPath().getBoolean( "error" ) ) + } + @Test + def T2_UnshareSuccess(): Unit = { + // Unshare the File + val fileResponse = FilesTestsUtils.UnShareFile( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID.toString, + fileUUID = UnShareFileTestsData.savedFileUUID.toString, + payload = UnShareFileTestsData.getUnsharePayload() + ) + assert( fileResponse.statusCode() == 204 ) + // Unshare the File + val fileResponse2 = FilesTestsUtils.UnShareFile( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID.toString, + fileUUID = UnShareFileTestsData.savedDirectoryUUID.toString, + payload = UnShareFileTestsData.getUnsharePayload() + ) + assert( fileResponse2.statusCode() == 204 ) + } + + @Test + def T3_UnshareFileNotFound(): Unit = { + val response = FilesTestsUtils.UnShareFile( + ownerUUID = UnShareFileTestsData.OWNER_USER_UUID.toString, + fileUUID = UUID.randomUUID().toString, + payload = UnShareFileTestsData.getUnsharePayload() + ) + assert( response.statusCode() == 404 ) + assert( response.jsonPath().getBoolean( "error" ) ) + } + + @Test + def T4_ShareFileForbidden(): Unit = { + val response = FilesTestsUtils.UnShareFile( + ownerUUID = UnShareFileTestsData.OTHER_USER_UUID.toString, + fileUUID = UnShareFileTestsData.savedFileUUID.toString, + payload = UnShareFileTestsData.getUnsharePayload() + ) + assert( response.statusCode() == 403 ) + assert( response.jsonPath().getBoolean( "error" ) ) + } +} diff --git a/version.json b/version.json index 9e20280..239eb2a 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.11.1" + "version": "0.12.0" } \ No newline at end of file