diff --git a/common/src/main/scala/no/ndla/common/model/domain/learningpath/Introduction.scala b/common/src/main/scala/no/ndla/common/model/domain/learningpath/Introduction.scala new file mode 100644 index 000000000..ab8cf754a --- /dev/null +++ b/common/src/main/scala/no/ndla/common/model/domain/learningpath/Introduction.scala @@ -0,0 +1,23 @@ +/* + * Part of NDLA common + * Copyright (C) 2024 NDLA + * + * See LICENSE + * + */ + +package no.ndla.common.model.domain.learningpath + +import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} +import io.circe.{Decoder, Encoder} +import no.ndla.language.model.LanguageField + +case class Introduction(introduction: String, language: String) extends LanguageField[String] { + override def value: String = introduction + override def isEmpty: Boolean = introduction.isEmpty +} + +object Introduction { + implicit val encoder: Encoder[Introduction] = deriveEncoder + implicit val decoder: Decoder[Introduction] = deriveDecoder +} diff --git a/common/src/main/scala/no/ndla/common/model/domain/learningpath/LearningStep.scala b/common/src/main/scala/no/ndla/common/model/domain/learningpath/LearningStep.scala index a1a6ad239..11b415dfb 100644 --- a/common/src/main/scala/no/ndla/common/model/domain/learningpath/LearningStep.scala +++ b/common/src/main/scala/no/ndla/common/model/domain/learningpath/LearningStep.scala @@ -20,6 +20,7 @@ case class LearningStep( learningPathId: Option[Long], seqNo: Int, title: Seq[Title], + introduction: Seq[Introduction], description: Seq[Description], embedUrl: Seq[EmbedUrl], `type`: StepType, diff --git a/language/src/main/scala/no/ndla/language/Language.scala b/language/src/main/scala/no/ndla/language/Language.scala index c40e5de41..e2f572025 100644 --- a/language/src/main/scala/no/ndla/language/Language.scala +++ b/language/src/main/scala/no/ndla/language/Language.scala @@ -51,7 +51,7 @@ object Language { "und" ) - def mergeLanguageFields[A <: LanguageField[_]](existing: Seq[A], updated: Seq[A]): Seq[A] = { + def mergeLanguageFields[A <: LanguageField[?]](existing: Seq[A], updated: Seq[A]): Seq[A] = { val toKeep = existing.filterNot(item => updated.map(_.language).contains(item.language)) (toKeep ++ updated).filterNot(_.isEmpty) } diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/LearningStepV2.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/LearningStepV2.scala index 63d1c88e7..f14d264a3 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/LearningStepV2.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/LearningStepV2.scala @@ -20,6 +20,7 @@ case class LearningStepV2( @description("The revision number for this learningstep") revision: Int, @description("The sequence number for the step. The first step has seqNo 0.") seqNo: Int, @description("The title of the learningstep") title: Title, + @description("The introduction of the learningstep") introduction: Option[Introduction], @description("The description of the learningstep") description: Option[Description], @description("The embed content for the learningstep") embedUrl: Option[EmbedUrlV2], @description("Determines if the title of the step should be displayed in viewmode") showTitle: Boolean, diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/NewLearningStepV2.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/NewLearningStepV2.scala index caa304c8b..5505c472d 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/NewLearningStepV2.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/NewLearningStepV2.scala @@ -15,6 +15,7 @@ import sttp.tapir.Schema.annotations.description @description("Information about a new learningstep") case class NewLearningStepV2( @description("The titles of the learningstep") title: String, + @description("The introduction of the learningstep") introduction: Option[String], @description("The descriptions of the learningstep") description: Option[String], @description("The chosen language") language: String, @description("The embed content for the learningstep") embedUrl: Option[EmbedUrlV2], diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/UpdatedLearningStepV2.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/UpdatedLearningStepV2.scala index c26c9144e..74403240c 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/UpdatedLearningStepV2.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/UpdatedLearningStepV2.scala @@ -16,6 +16,7 @@ import sttp.tapir.Schema.annotations.description case class UpdatedLearningStepV2( @description("The revision number for this learningstep") revision: Int, @description("The title of the learningstep") title: Option[String], + @description("The introduction of the learningstep") introduction: Option[String], @description("The chosen language") language: String, @description("The description of the learningstep") description: Option[String], @description("The embed content for the learningstep") embedUrl: Option[EmbedUrlV2], diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/DBLearningStep.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/DBLearningStep.scala index 0f662515c..c62c82fba 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/DBLearningStep.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/DBLearningStep.scala @@ -27,6 +27,7 @@ object DBLearningStep extends SQLSyntaxSupport[LearningStep] { Some(rs.long(ls.c("learning_path_id"))), meta.seqNo, meta.title, + meta.introduction, meta.description, meta.embedUrl, meta.`type`, 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 716f3e28a..b6599ad89 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 @@ -15,6 +15,7 @@ import no.ndla.common.implicits.OptionImplicit import no.ndla.common.model.domain.learningpath import no.ndla.common.model.domain.learningpath.{ Description, + Introduction, EmbedType, EmbedUrl, LearningPath, @@ -241,6 +242,10 @@ trait ConverterService { } def asDomainLearningStep(newLearningStep: NewLearningStepV2, learningPath: LearningPath): Try[LearningStep] = { + val introduction = newLearningStep.introduction + .map(Introduction(_, newLearningStep.language)) + .toSeq + val description = newLearningStep.description .map(Description(_, newLearningStep.language)) .toSeq @@ -264,6 +269,7 @@ trait ConverterService { learningPath.id, newSeqNo, Seq(common.Title(newLearningStep.title, newLearningStep.language)), + introduction, description, embedUrl, StepType.valueOfOrError(newLearningStep.`type`), @@ -303,6 +309,12 @@ trait ConverterService { mergeLanguageFields(existing.title, Seq(common.Title(value, updated.language))) } + val introductions = updated.introduction match { + case None => existing.introduction + case Some(value) => + mergeLanguageFields(existing.introduction, Seq(Introduction(value, updated.language))) + } + val descriptions = updated.description match { case None => existing.description case Some(value) => @@ -321,6 +333,7 @@ trait ConverterService { existing.copy( revision = Some(updated.revision), title = titles, + introduction = introductions, description = descriptions, embedUrl = embedUrls, showTitle = updated.showTitle.getOrElse(existing.showTitle), @@ -493,6 +506,9 @@ trait ConverterService { val title = findByLanguageOrBestEffort(ls.title, language) .map(asApiTitle) .getOrElse(api.Title("", DefaultLanguage)) + val introduction = + findByLanguageOrBestEffort(ls.introduction, language) + .map(asApiIntroduction) val description = findByLanguageOrBestEffort(ls.description, language) .map(asApiDescription) @@ -506,6 +522,7 @@ trait ConverterService { ls.revision.get, ls.seqNo, title, + introduction, description, embedUrl, ls.showTitle, @@ -606,6 +623,10 @@ trait ConverterService { api.Title(title.title, title.language) } + private def asApiIntroduction(introduction: Introduction): api.Introduction = { + api.Introduction(introduction.introduction, introduction.language) + } + private def asApiDescription(description: Description): api.Description = { api.Description(description.description, description.language) } diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/validation/LearningStepValidator.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/validation/LearningStepValidator.scala index 23e31e202..17b8826d1 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/validation/LearningStepValidator.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/validation/LearningStepValidator.scala @@ -9,7 +9,7 @@ package no.ndla.learningpathapi.validation import no.ndla.common.errors.{ValidationException, ValidationMessage} -import no.ndla.common.model.domain.learningpath.{Description, EmbedUrl, LearningStep} +import no.ndla.common.model.domain.learningpath.{Description, EmbedUrl, Introduction, LearningStep} import scala.util.{Failure, Success, Try} @@ -34,12 +34,31 @@ trait LearningStepValidator { def validateLearningStep(newLearningStep: LearningStep, allowUnknownLanguage: Boolean): Seq[ValidationMessage] = { titleValidator.validate(newLearningStep.title, allowUnknownLanguage) ++ + validateIntroduction(newLearningStep.introduction, allowUnknownLanguage) ++ validateDescription(newLearningStep.description, allowUnknownLanguage) ++ validateEmbedUrl(newLearningStep.embedUrl, allowUnknownLanguage) ++ validateLicense(newLearningStep.license).toList ++ validateThatDescriptionOrEmbedUrlOrBothIsDefined(newLearningStep).toList } + def validateIntroduction( + introductions: Seq[Introduction], + allowUnknownLanguage: Boolean + ): Seq[ValidationMessage] = { + if (introductions.isEmpty) { + List() + } else { + introductions.flatMap(introduction => { + basicHtmlTextValidator + .validate("introduction", introduction.introduction) + .toList ::: + languageValidator + .validate("language", introduction.language, allowUnknownLanguage) + .toList + }) + } + } + def validateDescription(descriptions: Seq[Description], allowUnknownLanguage: Boolean): Seq[ValidationMessage] = { if (descriptions.isEmpty) { List() diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/TestData.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/TestData.scala index 0fa66c9d1..8649399a4 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/TestData.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/TestData.scala @@ -49,6 +49,7 @@ object TestData { None, 1, List(common.Title("Step1Title", "nb")), + List.empty, List(Description("Step1Description", "nb")), List(), StepType.INTRODUCTION, @@ -62,6 +63,7 @@ object TestData { None, 2, List(common.Title("Step2Title", "nb")), + List.empty, List(Description("Step2Description", "nb")), List(), learningpath.StepType.TEXT, diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/repository/LearningPathRepositoryComponentIntegrationTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/repository/LearningPathRepositoryComponentIntegrationTest.scala index 7c15c4f58..4f2984dd5 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/repository/LearningPathRepositoryComponentIntegrationTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/repository/LearningPathRepositoryComponentIntegrationTest.scala @@ -14,6 +14,7 @@ import no.ndla.common.model.domain.learningpath.{ Description, EmbedType, EmbedUrl, + Introduction, LearningPath, LearningPathStatus, LearningPathVerificationStatus, @@ -71,6 +72,7 @@ class LearningPathRepositoryComponentIntegrationTest None, 0, List(Title("UNIT-TEST", "unknown")), + List(Introduction("UNIT-TEST", "unknown")), List(Description("UNIT-TEST", "unknown")), List(EmbedUrl("http://www.vg.no", "unknown", EmbedType.OEmbed)), StepType.TEXT, 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 fb32a484b..aa609d960 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 @@ -67,7 +67,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { None ) val domainLearningStep: LearningStep = - LearningStep(None, None, None, None, 1, List(), List(), List(), StepType.INTRODUCTION, None) + LearningStep(None, None, None, None, 1, List(), List(), List(), List(), StepType.INTRODUCTION, None) val domainLearningStep2: LearningStep = LearningStep( Some(1), @@ -76,6 +76,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { None, 1, List(Title("tittel", "nb")), + List(), List(Description("deskripsjon", "nb")), List(), StepType.INTRODUCTION, @@ -243,6 +244,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { 1, 1, api.Title("tittel", DefaultLanguage), + None, Some(api.Description("deskripsjon", DefaultLanguage)), None, showTitle = false, @@ -288,6 +290,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { 1, 1, api.Title("tittel", DefaultLanguage), + None, Some(api.Description("deskripsjon", DefaultLanguage)), None, showTitle = false, @@ -509,7 +512,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { test("asDomainLearningStep should work with learningpaths no matter the amount of steps") { val newLs = - NewLearningStepV2("Tittel", Some("Beskrivelse"), "nb", Some(api.EmbedUrlV2("", "oembed")), true, "TEXT", None) + NewLearningStepV2("Tittel", Some("Beskrivelse"), None, "nb", Some(api.EmbedUrlV2("", "oembed")), true, "TEXT", None) val lpId = 5591L val lp1 = TestData.sampleDomainLearningPath.copy(id = Some(lpId), learningsteps = None) val lp2 = TestData.sampleDomainLearningPath.copy(id = Some(lpId), learningsteps = Some(Seq.empty)) diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ReadServiceTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ReadServiceTest.scala index 3fb3a84ed..2a9c839c7 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ReadServiceTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ReadServiceTest.scala @@ -86,6 +86,7 @@ class ReadServiceTest extends UnitSuite with UnitTestEnvironment { List(Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, showTitle = true, @@ -101,6 +102,7 @@ class ReadServiceTest extends UnitSuite with UnitTestEnvironment { List(Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, showTitle = false, @@ -116,6 +118,7 @@ class ReadServiceTest extends UnitSuite with UnitTestEnvironment { List(Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, showTitle = false, 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 34ba83d50..e493dfb79 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 @@ -60,6 +60,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { List(common.Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, showTitle = true, @@ -75,9 +76,9 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { List(common.Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, - showTitle = false, status = StepStatus.ACTIVE ) @@ -90,6 +91,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { List(common.Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, showTitle = true, @@ -105,9 +107,9 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { List(common.Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, - showTitle = false, status = StepStatus.ACTIVE ) @@ -120,6 +122,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { List(common.Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, showTitle = true, @@ -135,17 +138,17 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { List(common.Title("Tittel", "nb")), List(), List(), + List(), StepType.TEXT, None, - showTitle = false, status = StepStatus.ACTIVE ) val NEW_STEPV2: NewLearningStepV2 = - NewLearningStepV2("Tittel", Some("Beskrivelse"), "nb", Some(api.EmbedUrlV2("", "oembed")), true, "TEXT", None) + NewLearningStepV2("Tittel", Some("Beskrivelse"), None, "nb", Some(api.EmbedUrlV2("", "oembed")), true, "TEXT", None) val UPDATED_STEPV2: UpdatedLearningStepV2 = - UpdatedLearningStepV2(1, Option("Tittel"), "nb", Some("Beskrivelse"), None, Some(false), None, None) + UpdatedLearningStepV2(1, Option("Tittel"), None, "nb", Some("Beskrivelse"), None, Some(false), None, None) val rubio: Author = Author("author", "Little Marco") val license = "publicdomain" @@ -1175,7 +1178,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { when(clock.now()).thenReturn(newDate) when(learningPathRepository.learningPathsWithIsBasedOn(any[Long])).thenReturn(List.empty) - val updatedLs = UpdatedLearningStepV2(1, Some("Dårlig tittel"), "nb", None, None, None, None, None) + val updatedLs = UpdatedLearningStepV2(1, Some("Dårlig tittel"), None, "nb", None, None, None, None, None) service.updateLearningStepV2(PUBLISHED_ID, STEP1.id.get, updatedLs, PUBLISHED_OWNER.toCombined) val updatedPath = PUBLISHED_LEARNINGPATH.copy( status = LearningPathStatus.UNLISTED, @@ -1230,7 +1233,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { when(clock.now()).thenReturn(newDate) when(learningPathRepository.learningPathsWithIsBasedOn(any[Long])).thenReturn(List.empty) - val updatedLs = UpdatedLearningStepV2(1, Some("Dårlig tittel"), "nb", None, None, None, None, None) + val updatedLs = UpdatedLearningStepV2(1, Some("Dårlig tittel"), None, "nb", None, None, None, None, None) service.updateLearningStepV2(PRIVATE_ID, STEP1.id.get, updatedLs, PRIVATE_OWNER.toCombined) val updatedPath = PRIVATE_LEARNINGPATH.copy( lastUpdated = newDate, @@ -1257,7 +1260,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { ) when(clock.now()).thenReturn(newDate) - val updatedLs = UpdatedLearningStepV2(1, Some("Dårlig tittel"), "nb", None, None, None, None, None) + val updatedLs = UpdatedLearningStepV2(1, Some("Dårlig tittel"), None, "nb", None, None, None, None, None) service.updateLearningStepV2( PUBLISHED_ID, STEP1.id.get, diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/search/SearchServiceTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/search/SearchServiceTest.scala index 0ebba9dec..689762e10 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/search/SearchServiceTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/search/SearchServiceTest.scala @@ -73,6 +73,7 @@ class SearchServiceTest learningPathId = None, seqNo = 0, title = List(), + introduction = List(), description = List(), embedUrl = List(), `type` = StepType.INTRODUCTION, diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/validation/LearningStepValidatorTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/validation/LearningStepValidatorTest.scala index 87dbcc364..c29ee4ab6 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/validation/LearningStepValidatorTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/validation/LearningStepValidatorTest.scala @@ -9,7 +9,15 @@ package no.ndla.learningpathapi.validation import no.ndla.common.errors.ValidationMessage -import no.ndla.common.model.domain.learningpath.{Description, EmbedType, EmbedUrl, LearningStep, StepStatus, StepType} +import no.ndla.common.model.domain.learningpath.{ + Description, + EmbedType, + EmbedUrl, + Introduction, + LearningStep, + StepStatus, + StepType +} import no.ndla.common.model.domain.Title import no.ndla.learningpathapi.* import org.mockito.Mockito.when @@ -27,6 +35,7 @@ class LearningStepValidatorTest extends UnitSuite with TestEnvironment { learningPathId = None, seqNo = 0, title = List(Title("Gyldig tittel", "nb")), + introduction = List(Introduction("

Gyldig introduksjon

", "nb")), description = List(Description("Gyldig description", "nb")), embedUrl = List(EmbedUrl("https://www.ndla.no/123", "nb", EmbedType.OEmbed)), `type` = StepType.TEXT, diff --git a/search-api/src/test/scala/no/ndla/searchapi/service/search/LearningPathIndexServiceTest.scala b/search-api/src/test/scala/no/ndla/searchapi/service/search/LearningPathIndexServiceTest.scala index 344f0aa8f..336dfaf51 100644 --- a/search-api/src/test/scala/no/ndla/searchapi/service/search/LearningPathIndexServiceTest.scala +++ b/search-api/src/test/scala/no/ndla/searchapi/service/search/LearningPathIndexServiceTest.scala @@ -61,11 +61,11 @@ class LearningPathIndexServiceTest learningPathId = Some(1L), seqNo = 1, title = Seq(Title("hei", "nb")), + introduction = Seq(), description = Seq(LPDescription("hei", "nb")), embedUrl = Seq(EmbedUrl("hei", "nb", EmbedType.OEmbed)), `type` = StepType.TEXT, license = Some("hei"), - showTitle = false, status = StepStatus.ACTIVE ) )