From ed9bfd62a3f191f9fc083e4adc5c8be650afebca Mon Sep 17 00:00:00 2001 From: Jonas Natten Date: Fri, 22 Nov 2024 15:32:57 +0100 Subject: [PATCH 1/2] learningpath-api: Adds `madeAvailable` field to learningpaths This field updates whenever a learningpath's status has been set to UNLISTED or PUBLISHED. --- .../domain/learningpath/LearningPath.scala | 3 ++- .../model/api/LearningPathV2.scala | 3 ++- .../service/ConverterService.scala | 3 ++- .../service/UpdateService.scala | 16 +++++++++++++-- .../service/ConverterServiceTest.scala | 3 +++ .../service/UpdateServiceTest.scala | 20 +++++++++++++++++++ 6 files changed, 43 insertions(+), 5 deletions(-) diff --git a/common/src/main/scala/no/ndla/common/model/domain/learningpath/LearningPath.scala b/common/src/main/scala/no/ndla/common/model/domain/learningpath/LearningPath.scala index 4d1fef9b6..1aaae8fc6 100644 --- a/common/src/main/scala/no/ndla/common/model/domain/learningpath/LearningPath.scala +++ b/common/src/main/scala/no/ndla/common/model/domain/learningpath/LearningPath.scala @@ -31,7 +31,8 @@ case class LearningPath( owner: String, copyright: LearningpathCopyright, learningsteps: Option[Seq[LearningStep]] = None, - message: Option[Message] = None + message: Option[Message] = None, + madeAvailable: Option[NDLADate] = None ) extends Content { def supportedLanguages: Seq[String] = { diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/LearningPathV2.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/LearningPathV2.scala index 0a3f73b4b..456d8a5ff 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/LearningPathV2.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/LearningPathV2.scala @@ -35,7 +35,8 @@ case class LearningPathV2( @description("True if authenticated user may edit this learningpath") canEdit: Boolean, @description("The supported languages for this learningpath") supportedLanguages: Seq[String], @description("Visible if administrator or owner of LearningPath") ownerId: Option[String], - @description("Message set by administrator. Visible if administrator or owner of LearningPath") message: Option[Message] + @description("Message set by administrator. Visible if administrator or owner of LearningPath") message: Option[Message], + @description("The date when this learningpath was made available to the public.") madeAvailable: Option[NDLADate] ) object LearningPathV2 { diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ConverterService.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ConverterService.scala index 02689a68f..716f3e28a 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ConverterService.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ConverterService.scala @@ -152,7 +152,8 @@ trait ConverterService { lp.canEdit(userInfo), supportedLanguages, owner, - message + message, + lp.madeAvailable ) ) } else diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/UpdateService.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/UpdateService.scala index e790501da..e081ea6d7 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/UpdateService.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/UpdateService.scala @@ -160,10 +160,22 @@ trait UpdateService { case _ => valid.message } - val updatedLearningPath = learningPathRepository.update( - valid.copy(message = newMessage, status = status, lastUpdated = clock.now()) + val madeAvailable = valid.madeAvailable.orElse { + status match { + case LearningPathStatus.PUBLISHED | LearningPathStatus.UNLISTED => Some(clock.now()) + case _ => None + } + } + + val toUpdateWith = valid.copy( + message = newMessage, + status = status, + lastUpdated = clock.now(), + madeAvailable = madeAvailable ) + val updatedLearningPath = learningPathRepository.update(toUpdateWith) + updateSearchAndTaxonomy(updatedLearningPath, owner.tokenUser) .flatMap(_ => converterService.asApiLearningpathV2( diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ConverterServiceTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ConverterServiceTest.scala index bef96ac75..fb32a484b 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ConverterServiceTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ConverterServiceTest.scala @@ -63,6 +63,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { true, List("nb"), None, + None, None ) val domainLearningStep: LearningStep = @@ -137,6 +138,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { canEdit = true, List("nb", "en"), None, + None, None ) ) @@ -188,6 +190,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { true, List("nb", "en"), None, + None, None ) ) diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/UpdateServiceTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/UpdateServiceTest.scala index a76a191b0..34ba83d50 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/UpdateServiceTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/UpdateServiceTest.scala @@ -406,6 +406,26 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { verify(searchIndexService, times(1)).indexDocument(any[LearningPath]) } + test("That updateLearningPathStatusV2 updates madeAvailable when going to UNLISTED") { + when(learningPathRepository.withIdIncludingDeleted(eqTo(PUBLISHED_ID))(any[DBSession])) + .thenReturn(Some(PUBLISHED_LEARNINGPATH)) + when(learningPathRepository.update(any[LearningPath])(any[DBSession])) + .thenReturn(PUBLISHED_LEARNINGPATH.copy(status = learningpath.LearningPathStatus.PRIVATE)) + when(learningPathRepository.learningPathsWithIsBasedOn(PUBLISHED_ID)).thenReturn(List()) + val nowDate = NDLADate.fromUnixTime(1337) + when(clock.now()).thenReturn(nowDate) + val user = PRIVATE_OWNER.copy(permissions = Set(LEARNINGPATH_API_ADMIN)).toCombined + + service.updateLearningPathStatusV2(PUBLISHED_ID, LearningPathStatus.UNLISTED, user, "nb").failIfFailure + + val expectedLearningPath = PUBLISHED_LEARNINGPATH.copy( + status = LearningPathStatus.UNLISTED, + lastUpdated = nowDate, + madeAvailable = Some(nowDate) + ) + verify(learningPathRepository, times(1)).update(eqTo(expectedLearningPath))(any) + } + test( "That updateLearningPathStatusV2 updates the status when the given user is not the owner, but is admin and the status is PUBLISHED" ) { From 3fbb176c823885336066d22eb6abf10a5c821dfc Mon Sep 17 00:00:00 2001 From: Jonas Natten Date: Mon, 25 Nov 2024 09:12:49 +0100 Subject: [PATCH 2/2] learningpath-api: Add migration for `madeAvailable` field This sets `madeAvailable` to the same as `lastUpdated` for those learningpaths that are `UNLISTED` or `PUBLISHED` --- .../V39__MadeAvailableForThePublished.scala | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migration/V39__MadeAvailableForThePublished.scala diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migration/V39__MadeAvailableForThePublished.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migration/V39__MadeAvailableForThePublished.scala new file mode 100644 index 000000000..82d4f17d8 --- /dev/null +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migration/V39__MadeAvailableForThePublished.scala @@ -0,0 +1,30 @@ +/* + * Part of NDLA learningpath-api + * Copyright (C) 2024 NDLA + * + * See LICENSE + * + */ + +package no.ndla.learningpathapi.db.migration + +import no.ndla.common.CirceUtil +import no.ndla.common.model.domain.learningpath.LearningPath +import no.ndla.common.model.domain.learningpath.LearningPathStatus.{PUBLISHED, UNLISTED} +import no.ndla.database.DocumentMigration + +class V39__MadeAvailableForThePublished extends DocumentMigration { + override val columnName: String = "document" + override val tableName: String = "learningpaths" + + protected def convertColumn(document: String): String = { + val oldLp = CirceUtil.unsafeParseAs[LearningPath](document) + val madeAvailable = oldLp.status match { + case UNLISTED | PUBLISHED => Some(oldLp.lastUpdated) + case _ => None + } + + val newLearningPath = oldLp.copy(madeAvailable = madeAvailable) + CirceUtil.toJsonString(newLearningPath) + } +}