Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import sttp.tapir.Schema.annotations.description

import java.util.UUID
import no.ndla.common.DeriveHelpers
import no.ndla.common.model.api.UpdateOrDelete

case class OwnerDTO(
@description("Name of the owner")
Expand Down Expand Up @@ -124,6 +125,8 @@ case class NewFolderDTO(
)

case class UpdatedFolderDTO(
@description("Id of parent folder")
parentId: UpdateOrDelete[String],
@description("Folder name")
name: Option[String],
@description("Status of the folder (private, shared)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class FolderRepository(using clock: Clock, dbUtility: DBUtility) extends StrictL
withSQL {
update(Folder)
.set(
column("parent_id") -> folder.parentId,
column("name") -> folder.name,
column("status") -> folder.status.toString,
column("shared") -> folder.shared,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.typesafe.scalalogging.StrictLogging
import no.ndla.common.{Clock, model}
import no.ndla.common.errors.ValidationException
import no.ndla.common.implicits.*
import no.ndla.common.model.api.{Delete, Missing, UpdateWith}
import no.ndla.common.model.api.myndla.UpdatedMyNDLAUserDTO
import no.ndla.common.model.domain.myndla
import no.ndla.common.model.domain.myndla.{
Expand Down Expand Up @@ -73,7 +74,21 @@ class FolderConverterService(using clock: Clock) extends StrictLogging {
loop(domainFolder, breadcrumbs, feideUser)
}

def mergeFolder(existing: domain.Folder, updated: api.UpdatedFolderDTO): domain.Folder = {
def mergeFolder(existing: domain.Folder, updated: api.UpdatedFolderDTO): Try[domain.Folder] = {
val parentId = updated.parentId match {
case Delete => None
case Missing => existing.parentId
case UpdateWith(value) => toUUIDValidated(Some(value), "parentId") match {
case Success(uuid) => Some(uuid)
case failure => throw failure.failed.get
}
}
val rank =
if (parentId != existing.parentId) {
0
} else {
existing.rank
}
val name = updated.name.getOrElse(existing.name)
val status = updated.status.flatMap(FolderStatus.valueOf).getOrElse(existing.status)
val description = updated.description.orElse(existing.description)
Expand All @@ -85,20 +100,22 @@ class FolderConverterService(using clock: Clock) extends StrictLogging {
case _ => None
}

domain.Folder(
id = existing.id,
resources = existing.resources,
subfolders = existing.subfolders,
feideId = existing.feideId,
parentId = existing.parentId,
name = name,
status = status,
rank = existing.rank,
created = existing.created,
updated = clock.now(),
shared = shared,
description = description,
user = existing.user,
Success(
domain.Folder(
id = existing.id,
resources = existing.resources,
subfolders = existing.subfolders,
feideId = existing.feideId,
parentId = parentId,
name = name,
status = status,
rank = rank,
created = existing.created,
updated = clock.now(),
shared = shared,
description = description,
user = existing.user,
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,14 @@ class FolderWriteService(using
_ <- isOperationAllowedOrAccessDenied(feideId, feideAccessToken, updatedFolder)
existingFolder <- folderRepository.folderWithId(id)
_ <- existingFolder.isOwner(feideId)
converted <- Try(folderConverterService.mergeFolder(existingFolder, updatedFolder))
converted <- folderConverterService.mergeFolder(existingFolder, updatedFolder)
maybeSiblings <- getFolderWithDirectChildren(converted.parentId, feideId)
_ <- validateUpdatedFolder(converted.name, converted.parentId, maybeSiblings, converted)
updated <- folderRepository.updateFolder(id, feideId, converted)
crumbs <- folderReadService.getBreadcrumbs(updated)(using ReadOnlyAutoSession)
siblingsToSort <- getFolderWithDirectChildren(updated.parentId, feideId)
sortRequest = FolderSortRequestDTO(sortedIds = siblingsToSort.childrenFolders.map(_.id))
_ <- performSort(siblingsToSort.childrenFolders, sortRequest, feideId, sharedFolderSort = false)
feideUser <- userRepository.userWithFeideId(feideId)
api <- folderConverterService.toApiFolder(updated, crumbs, feideUser, isOwner = true)
} yield api
Expand Down
110 changes: 87 additions & 23 deletions myndla-api/src/test/scala/no/ndla/myndlaapi/e2e/FolderTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package no.ndla.myndlaapi.e2e

import no.ndla.common.configuration.Prop
import no.ndla.common.model.NDLADate
import no.ndla.common.model.api.UpdateWith
import no.ndla.common.model.domain.ResourceType.Article
import no.ndla.common.model.domain.myndla.FolderStatus
import no.ndla.common.{CirceUtil, Clock}
Expand Down Expand Up @@ -128,10 +129,14 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit
)
}

def createFolder(feideId: String, name: String): api.FolderDTO = {
def createFolder(feideId: String, name: String, parentId: Option[String]): api.FolderDTO = {
import io.circe.generic.auto.*
val newFolderData =
api.NewFolderDTO(name = name, status = Some(FolderStatus.SHARED.toString), parentId = None, description = None)
val newFolderData = api.NewFolderDTO(
name = name,
status = Some(FolderStatus.SHARED.toString),
parentId = parentId,
description = None,
)
val body = CirceUtil.toJsonString(newFolderData)

val newFolder = simpleHttpClient.send(
Expand All @@ -147,11 +152,29 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit
CirceUtil.unsafeParseAs[api.FolderDTO](newFolder.body)
}

def getFolders(feideId: String): api.UserFolderDTO = {
def updateFolder(feideId: String, folderId: UUID, updatedFolder: api.UpdatedFolderDTO): api.FolderDTO = {
import io.circe.generic.auto.*
val folders = simpleHttpClient.send(
quickRequest.get(uri"$myndlaApiFolderUrl/").header("FeideAuthorization", s"Bearer $feideId")
val body = CirceUtil.toJsonString(updatedFolder)

val updatedFolderResponse = simpleHttpClient.send(
quickRequest
.patch(uri"$myndlaApiFolderUrl/$folderId")
.header("FeideAuthorization", s"Bearer $feideId")
.contentType("application/json")
.body(body)
)
if (!updatedFolderResponse.isSuccess) fail(
s"Failed to update folder $folderId failed with code ${updatedFolderResponse.code} and body:\n${updatedFolderResponse.body}"
)

CirceUtil.unsafeParseAs[api.FolderDTO](updatedFolderResponse.body)
}

def getFolders(feideId: String, includeSubfolders: Boolean): api.UserFolderDTO = {
import io.circe.generic.auto.*
var uri = uri"$myndlaApiFolderUrl/"
if (includeSubfolders) uri = uri.addParam("include-subfolders", "true")
val folders = simpleHttpClient.send(quickRequest.get(uri).header("FeideAuthorization", s"Bearer $feideId"))
if (!folders.isSuccess)
fail(s"Fetching all folders for $feideId failed with code ${folders.code} and body:\n${folders.body}")

Expand Down Expand Up @@ -250,12 +273,12 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit
when(myndlaApi.componentRegistry.feideApiClient.getFeideID(eqTo(Some(feideId1)))).thenReturn(Success(feideId1))
when(myndlaApi.componentRegistry.feideApiClient.getFeideGroups(any)).thenReturn(Success(Seq.empty))

val f1 = createFolder(feideId1, "folder1")
val f2 = createFolder(feideId1, "folder2")
val f3 = createFolder(feideId1, "folder3")
val f4 = createFolder(feideId1, "folder4")
val f1 = createFolder(feideId1, "folder1", None)
val f2 = createFolder(feideId1, "folder2", None)
val f3 = createFolder(feideId1, "folder3", None)
val f4 = createFolder(feideId1, "folder4", None)

val foldersForU1 = getFolders(feideId1)
val foldersForU1 = getFolders(feideId1, false)
foldersForU1.sharedFolders.length should be(0)
foldersForU1.folders.length should be(4)
foldersForU1.folders.head.id should be(f1.id)
Expand All @@ -269,7 +292,7 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit

sortFolders(feideId1, List(f4.id, f2.id, f3.id, f1.id))

val foldersForU1Sorted = getFolders(feideId1)
val foldersForU1Sorted = getFolders(feideId1, false)
foldersForU1Sorted.sharedFolders.length should be(0)
foldersForU1Sorted.folders.length should be(4)
foldersForU1Sorted.folders.head.id should be(f4.id)
Expand All @@ -289,12 +312,12 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit
when(myndlaApi.componentRegistry.feideApiClient.getFeideID(eqTo(Some(feideId2)))).thenReturn(Success(feideId2))
when(myndlaApi.componentRegistry.feideApiClient.getFeideGroups(any)).thenReturn(Success(Seq.empty))

val f1 = createFolder(feideId1, "folder1")
val f2 = createFolder(feideId1, "folder2")
val f3 = createFolder(feideId1, "folder3")
val f4 = createFolder(feideId1, "folder4")
val f1 = createFolder(feideId1, "folder1", None)
val f2 = createFolder(feideId1, "folder2", None)
val f3 = createFolder(feideId1, "folder3", None)
val f4 = createFolder(feideId1, "folder4", None)

val foldersForU1 = getFolders(feideId1)
val foldersForU1 = getFolders(feideId1, false)
foldersForU1.sharedFolders.length should be(0)
foldersForU1.folders.length should be(4)
foldersForU1.folders.head.id should be(f1.id)
Expand All @@ -308,7 +331,7 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit

sortFolders(feideId1, List(f4.id, f2.id, f3.id, f1.id))

val foldersForU1Sorted = getFolders(feideId1)
val foldersForU1Sorted = getFolders(feideId1, false)
foldersForU1Sorted.sharedFolders.length should be(0)
foldersForU1Sorted.folders.length should be(4)
foldersForU1Sorted.folders.head.id should be(f4.id)
Expand All @@ -320,13 +343,13 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit
foldersForU1Sorted.folders(3).id should be(f1.id)
foldersForU1Sorted.folders(3).rank should be(4)

val foldersForU2 = getFolders(feideId2)
val foldersForU2 = getFolders(feideId2, false)
foldersForU2.sharedFolders.length should be(0)

saveFolder(feideId2, f1.id)
saveFolder(feideId2, f3.id)

val foldersForU2AfterSave = getFolders(feideId2)
val foldersForU2AfterSave = getFolders(feideId2, false)
foldersForU2AfterSave.sharedFolders.length should be(2)
foldersForU2AfterSave.sharedFolders.head.id should be(f1.id)
foldersForU2AfterSave.sharedFolders.head.rank should be(1)
Expand All @@ -335,7 +358,7 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit

sortFolders(feideId2, List(f3.id, f1.id), sortShared = true)

val foldersForU2AfterSort = getFolders(feideId2)
val foldersForU2AfterSort = getFolders(feideId2, false)
foldersForU2AfterSort.sharedFolders.length should be(2)
foldersForU2AfterSort.sharedFolders.head.id should be(f3.id)
foldersForU2AfterSort.sharedFolders.head.rank should be(1)
Expand All @@ -349,9 +372,9 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit
when(myndlaApi.componentRegistry.feideApiClient.getFeideID(eqTo(Some(feideId1)))).thenReturn(Success(feideId1))
when(myndlaApi.componentRegistry.feideApiClient.getFeideGroups(any)).thenReturn(Success(Seq.empty))

val f1 = createFolder(feideId1, "folder1")
val f1 = createFolder(feideId1, "folder1", None)

val foldersForU1 = getFolders(feideId1)
val foldersForU1 = getFolders(feideId1, false)
foldersForU1.sharedFolders.length should be(0)
foldersForU1.folders.length should be(1)
foldersForU1.folders.head.id should be(f1.id)
Expand Down Expand Up @@ -404,4 +427,45 @@ class FolderTest extends DatabaseIntegrationSuite with RedisIntegrationSuite wit
folderAfterDelete.resources.map(_.id) should be(List(res3.id, res2.id, res5.id, res4.id))

}

test("Saving and then moving folder to different parent") {
val feideId1 = "feide1"
when(myndlaApi.componentRegistry.feideApiClient.getFeideID(eqTo(Some(feideId1)))).thenReturn(Success(feideId1))
when(myndlaApi.componentRegistry.feideApiClient.getFeideGroups(any)).thenReturn(Success(Seq.empty))

/*
f1
├─ f2
├─ f3
└─ f4
*/
val f1 = createFolder(feideId1, "folder1", None)
val f2 = createFolder(feideId1, "folder2", Some(f1.id.toString))
val f3 = createFolder(feideId1, "folder3", Some(f1.id.toString))
val f4 = createFolder(feideId1, "folder4", Some(f3.id.toString))

// Move f4 to be child of f1
val updated = updateFolder(
feideId = feideId1,
folderId = f4.id,
updatedFolder =
api.UpdatedFolderDTO(parentId = UpdateWith(f1.id.toString), name = None, status = None, description = None),
)
updated.parentId should be(Some(f1.id))

val foldersForU1Sorted = getFolders(feideId1, true)
foldersForU1Sorted.sharedFolders.length should be(0)
foldersForU1Sorted.folders.length should be(1)
foldersForU1Sorted.folders.head.id should be(f1.id)
foldersForU1Sorted.folders.head.rank should be(1)
foldersForU1Sorted.folders.head.subfolders.length should be(3)
val subfoldersOfF1 = foldersForU1Sorted.folders.head.subfolders.sortBy(_.rank)
subfoldersOfF1.head.id should be(f2.id)
subfoldersOfF1.head.rank should be(1)
subfoldersOfF1(1).id should be(f4.id) // rank is reset to 0 when moving
subfoldersOfF1(1).rank should be(2)
subfoldersOfF1(2).id should be(f3.id)
subfoldersOfF1(2).rank should be(3)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package no.ndla.myndlaapi.service

import no.ndla.common.model.NDLADate
import no.ndla.common.model.api.{Delete, Missing, UpdateWith}
import no.ndla.common.model.api.myndla.{MyNDLAGroupDTO, MyNDLAUserDTO, UpdatedMyNDLAUserDTO}
import no.ndla.common.model.domain.ResourceType
import no.ndla.common.model.domain.myndla.{FolderStatus, MyNDLAGroup, MyNDLAUser, UserRole}
Expand Down Expand Up @@ -253,14 +254,27 @@ class FolderConverterServiceTest extends UnitTestSuite with TestEnvironment {
description = Some("hei"),
user = None,
)
val updatedWithData =
UpdatedFolderDTO(name = Some("newNamae"), status = Some("shared"), description = Some("halla"))
val updatedWithoutData = UpdatedFolderDTO(name = None, status = None, description = None)
val updatedWithData = UpdatedFolderDTO(
parentId = Missing,
name = Some("newNamae"),
status = Some("shared"),
description = Some("halla"),
)
val updatedWithoutData = UpdatedFolderDTO(parentId = Missing, name = None, status = None, description = None)
val updatedWithGarbageData = UpdatedFolderDTO(
parentId = Missing,
name = Some("huehueuheasdasd+++"),
status = Some("det å joike er noe kult"),
description = Some("jog ska visa deg garbage jog"),
)
val newParentUUID = UUID.randomUUID()
val updatedWithNewParent = UpdatedFolderDTO(
parentId = UpdateWith[String](newParentUUID.toString),
name = None,
status = None,
description = None,
)
val updatedWithNoParent = UpdatedFolderDTO(parentId = Delete, name = None, status = None, description = None)

val expected1 =
existing.copy(name = "newNamae", status = FolderStatus.SHARED, shared = Some(shared), description = Some("halla"))
Expand All @@ -270,14 +284,20 @@ class FolderConverterServiceTest extends UnitTestSuite with TestEnvironment {
status = FolderStatus.PRIVATE,
description = Some("jog ska visa deg garbage jog"),
)
val expected4 = existing.copy(parentId = Some(newParentUUID), rank = 0)
val expected5 = existing.copy(parentId = None, rank = 0)

val result1 = service.mergeFolder(existing, updatedWithData)
val result2 = service.mergeFolder(existing, updatedWithoutData)
val result3 = service.mergeFolder(existing, updatedWithGarbageData)

result1 should be(expected1)
result2 should be(expected2)
result3 should be(expected3)
val result4 = service.mergeFolder(existing, updatedWithNewParent)
val result5 = service.mergeFolder(existing, updatedWithNoParent)

result1 should be(Success(expected1))
result2 should be(Success(expected2))
result3 should be(Success(expected3))
result4 should be(Success(expected4))
result5 should be(Success(expected5))
}

test("that mergeFolder works correctly for shared field and folder status update") {
Expand All @@ -302,8 +322,8 @@ class FolderConverterServiceTest extends UnitTestSuite with TestEnvironment {
)
val existingShared = existingBase.copy(status = FolderStatus.SHARED, shared = Some(sharedBefore))
val existingPrivate = existingBase.copy(status = FolderStatus.PRIVATE, shared = None)
val updatedShared = UpdatedFolderDTO(name = None, status = Some("shared"), description = None)
val updatedPrivate = UpdatedFolderDTO(name = None, status = Some("private"), description = None)
val updatedShared = UpdatedFolderDTO(parentId = Missing, name = None, status = Some("shared"), description = None)
val updatedPrivate = UpdatedFolderDTO(parentId = Missing, name = None, status = Some("private"), description = None)
val expected1 = existingBase.copy(status = FolderStatus.SHARED, shared = Some(sharedBefore))
val expected2 = existingBase.copy(status = FolderStatus.PRIVATE, shared = None)
val expected3 = existingBase.copy(status = FolderStatus.SHARED, shared = Some(sharedNow))
Expand All @@ -313,10 +333,10 @@ class FolderConverterServiceTest extends UnitTestSuite with TestEnvironment {
val result2 = service.mergeFolder(existingShared, updatedPrivate)
val result3 = service.mergeFolder(existingPrivate, updatedShared)
val result4 = service.mergeFolder(existingPrivate, updatedPrivate)
result1 should be(expected1)
result2 should be(expected2)
result3 should be(expected3)
result4 should be(expected4)
result1 should be(Success(expected1))
result2 should be(Success(expected2))
result3 should be(Success(expected3))
result4 should be(Success(expected4))
}

test("that toApiResource converts correctly") {
Expand Down
Loading