diff --git a/article-api/src/main/scala/no/ndla/articleapi/db/HtmlMigration.scala b/article-api/src/main/scala/no/ndla/articleapi/db/HtmlMigration.scala index da56d7c79..46ae83372 100644 --- a/article-api/src/main/scala/no/ndla/articleapi/db/HtmlMigration.scala +++ b/article-api/src/main/scala/no/ndla/articleapi/db/HtmlMigration.scala @@ -28,7 +28,7 @@ abstract class HtmlMigration extends DocumentMigration { jsoupDocumentToString(converted) } - protected def convertColumn(document: String): String = { + def convertColumn(document: String): String = { val oldArticle = parser.parse(document).flatMap(_.as[Article]).toTry.get val convertedContent = oldArticle.content.map(c => { val converted = convertContent(c.content, c.language) diff --git a/article-api/src/main/scala/no/ndla/articleapi/db/migration/V56__DisclaimerToLanguageFields.scala b/article-api/src/main/scala/no/ndla/articleapi/db/migration/V56__DisclaimerToLanguageFields.scala new file mode 100644 index 000000000..4dad184e3 --- /dev/null +++ b/article-api/src/main/scala/no/ndla/articleapi/db/migration/V56__DisclaimerToLanguageFields.scala @@ -0,0 +1,17 @@ +/* + * Part of NDLA article-api + * Copyright (C) 2025 NDLA + * + * See LICENSE + * + */ + +package no.ndla.articleapi.db.migration + +import no.ndla.database.LanguageFieldMigration + +class V56__DisclaimerToLanguageFields extends LanguageFieldMigration { + override val columnName: String = "document" + override val tableName: String = "contentdata" + override val fieldName: String = "disclaimer" +} diff --git a/article-api/src/main/scala/no/ndla/articleapi/service/ConverterService.scala b/article-api/src/main/scala/no/ndla/articleapi/service/ConverterService.scala index 6cce27e91..d5526b65b 100644 --- a/article-api/src/main/scala/no/ndla/articleapi/service/ConverterService.scala +++ b/article-api/src/main/scala/no/ndla/articleapi/service/ConverterService.scala @@ -260,8 +260,8 @@ trait ConverterService { val metaImage = findByLanguageOrBestEffort(article.metaImage, language).map(toApiArticleMetaImage) val copyright = toApiCopyright(article.copyright) val disclaimer = article.disclaimer - .flatMap(d => findByLanguageOrBestEffort(d, language)) - .map(d => DisclaimerDTO(d.disclaimer, d.language)) + .findByLanguageOrBestEffort(language) + .map(DisclaimerDTO.fromLanguageValue) Success( api.ArticleV2DTO( diff --git a/article-api/src/main/scala/no/ndla/articleapi/validation/ContentValidator.scala b/article-api/src/main/scala/no/ndla/articleapi/validation/ContentValidator.scala index 90fc36721..cef746ff5 100644 --- a/article-api/src/main/scala/no/ndla/articleapi/validation/ContentValidator.scala +++ b/article-api/src/main/scala/no/ndla/articleapi/validation/ContentValidator.scala @@ -14,6 +14,7 @@ import no.ndla.common.errors.{ValidationException, ValidationMessage} import no.ndla.common.model.NDLADate import no.ndla.common.model.domain.article.{Article, Copyright} import no.ndla.common.model.domain.* +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.language.model.{Iso639, LanguageField} import no.ndla.mapping.License.getLicense import no.ndla.validation.HtmlTagRules.{allLegalTags, stringToJsoupDocument} @@ -50,7 +51,7 @@ trait ContentValidator { def validateArticle(article: Article, isImported: Boolean): Try[Article] = { val validationErrors = validateArticleContent(article.content) ++ article.introduction.flatMap(i => validateIntroduction(i)) ++ - validateArticleDisclaimer(article.disclaimer.getOrElse(Seq.empty)) ++ + validateArticleDisclaimer(article.disclaimer) ++ validateMetaDescription(article.metaDescription, isImported) ++ validateTitle(article.title) ++ validateCopyright(article.copyright) ++ @@ -90,12 +91,12 @@ trait ContentValidator { }) ++ validateNonEmpty("content", contents) } - private def validateArticleDisclaimer(disclaimers: Seq[Disclaimer]): Seq[ValidationMessage] = { - disclaimers.flatMap(disclaimer => { + private def validateArticleDisclaimer(disclaimers: OptLanguageFields[String]): Seq[ValidationMessage] = { + disclaimers.mapExisting { disclaimer => val field = s"disclaimer.${disclaimer.language}" - TextValidator.validate(field, disclaimer.disclaimer, allLegalTags).toList ++ - validateLanguage("content.language", disclaimer.language) - }) + TextValidator.validate(field, disclaimer.value, allLegalTags).toList ++ + validateLanguage("disclaimer.language", disclaimer.language) + }.flatten } private def rootElementContainsOnlySectionBlocks(field: String, html: String): Option[ValidationMessage] = { @@ -149,7 +150,7 @@ trait ContentValidator { private def validateTitle(titles: Seq[LanguageField[String]]): Seq[ValidationMessage] = { titles.flatMap(title => { - val field = s"title.$language" + val field = s"title.language" TextValidator.validate(field, title.value, inlineHtmlTags).toList ++ validateLanguage("title.language", title.language) ++ validateLength("title", title.value, 256) diff --git a/article-api/src/test/scala/no/ndla/articleapi/TestData.scala b/article-api/src/test/scala/no/ndla/articleapi/TestData.scala index 790eaaf63..1362a6db2 100644 --- a/article-api/src/test/scala/no/ndla/articleapi/TestData.scala +++ b/article-api/src/test/scala/no/ndla/articleapi/TestData.scala @@ -15,6 +15,7 @@ import no.ndla.common.model import no.ndla.common.model.NDLADate import no.ndla.common.model.domain.article.{Article, Copyright} import no.ndla.common.model.domain.* +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.mapping.License trait TestData { @@ -155,7 +156,7 @@ trait TestData { relatedContent = Seq.empty, revisionDate = Some(NDLADate.now().withNano(0)), slug = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val sampleDomainArticle: Article = Article( @@ -181,7 +182,7 @@ trait TestData { relatedContent = Seq.empty, revisionDate = None, slug = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val sampleDomainArticle2: Article = Article( @@ -207,7 +208,7 @@ trait TestData { relatedContent = Seq.empty, revisionDate = None, slug = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val sampleArticleWithByNcSa: Article = sampleArticleWithPublicDomain.copy(copyright = byNcSaCopyright) @@ -245,7 +246,7 @@ trait TestData { relatedContent = Seq.empty, revisionDate = None, slug = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val apiArticleWithHtmlFaultV2: api.ArticleV2DTO = api.ArticleV2DTO( @@ -316,7 +317,7 @@ trait TestData { relatedContent = Seq.empty, revisionDate = None, slug = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) } diff --git a/article-api/src/test/scala/no/ndla/articleapi/db/migration/V55__DisclaimerToLanguageFieldsTest.scala b/article-api/src/test/scala/no/ndla/articleapi/db/migration/V55__DisclaimerToLanguageFieldsTest.scala new file mode 100644 index 000000000..0294fff25 --- /dev/null +++ b/article-api/src/test/scala/no/ndla/articleapi/db/migration/V55__DisclaimerToLanguageFieldsTest.scala @@ -0,0 +1,38 @@ +/* + * Part of NDLA article-api + * Copyright (C) 2024 NDLA + * + * See LICENSE + */ + +package no.ndla.articleapi.db.migration + +import no.ndla.articleapi.{TestEnvironment, UnitSuite} +import no.ndla.common.model.domain.{ArticleContent, Title} + +class V55__DisclaimerToLanguageFieldsTest extends UnitSuite with TestEnvironment { + test("That old disclaimers are migrated to new language fields") { + val oldDocument = + """{"disclaimer":[{"disclaimer":"Dette er bokmål","language":"nb"},{"disclaimer":"Dette er nynorsk","language":"nn"}]}""" + val expectedResult = """{"disclaimer":{"nb":"Dette er bokmål","nn":"Dette er nynorsk"}}""" + val migration = new V56__DisclaimerToLanguageFields + val result = migration.convertColumn(oldDocument) + result should be(expectedResult) + } + + test("That no old disclaimers are migrated to new language fields") { + val oldDocument = """{}""" + val expectedResult = """{"disclaimer":{}}""" + val migration = new V56__DisclaimerToLanguageFields + val result = migration.convertColumn(oldDocument) + result should be(expectedResult) + } + + test("That null disclaimers are migrated to new language fields") { + val oldDocument = """{"disclaimer":null}""" + val expectedResult = """{"disclaimer":{}}""" + val migration = new V56__DisclaimerToLanguageFields + val result = migration.convertColumn(oldDocument) + result should be(expectedResult) + } +} diff --git a/article-api/src/test/scala/no/ndla/articleapi/validation/ContentValidatorTest.scala b/article-api/src/test/scala/no/ndla/articleapi/validation/ContentValidatorTest.scala index e9e6298e5..376801f77 100644 --- a/article-api/src/test/scala/no/ndla/articleapi/validation/ContentValidatorTest.scala +++ b/article-api/src/test/scala/no/ndla/articleapi/validation/ContentValidatorTest.scala @@ -22,6 +22,7 @@ import no.ndla.common.model.domain.{ Title } import no.ndla.common.model.domain.article.Copyright +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.mapping.License.{CC_BY_SA, NA} import scala.util.Failure @@ -83,7 +84,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment { test("validateArticle should throw an error if disclaimer contains illegal HTML tags") { val article = TestData.sampleArticleWithByNcSa.copy( content = Seq(ArticleContent(validDocument, "nb")), - disclaimer = Some(Seq(Disclaimer("

hei

", "nb"))) + disclaimer = OptLanguageFields.withValue("

hei

", "nb") ) val Failure(error: ValidationException) = contentValidator.validateArticle(article, false) @@ -98,14 +99,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment { test("validateArticle should not throw an error if disclaimer contains legal HTML tags") { val article = TestData.sampleArticleWithByNcSa.copy( content = Seq(ArticleContent(validDocument, "nb")), - disclaimer = Some( - Seq( - Disclaimer( - validDisclaimer, - "nb" - ) - ) - ) + disclaimer = OptLanguageFields.withValue(validDisclaimer, "nb") ) contentValidator.validateArticle(article, false).isSuccess should be(true) } @@ -113,7 +107,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment { test("validateArticle should not throw an error if disclaimer contains plain text") { val article = TestData.sampleArticleWithByNcSa.copy( content = Seq(ArticleContent(validDocument, "nb")), - disclaimer = Some(Seq(Disclaimer("disclaimer", "nb"))) + disclaimer = OptLanguageFields.withValue("disclaimer", "nb") ) contentValidator.validateArticle(article, false).isSuccess should be(true) } diff --git a/common/src/main/scala/no/ndla/common/model/api/DisclaimerDTO.scala b/common/src/main/scala/no/ndla/common/model/api/DisclaimerDTO.scala index 750c1a57b..60ef2514a 100644 --- a/common/src/main/scala/no/ndla/common/model/api/DisclaimerDTO.scala +++ b/common/src/main/scala/no/ndla/common/model/api/DisclaimerDTO.scala @@ -10,6 +10,7 @@ package no.ndla.common.model.api import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} import io.circe.{Decoder, Encoder} +import no.ndla.language.model.WithLanguageAndValue import sttp.tapir.Schema.annotations.description case class DisclaimerDTO( @@ -18,6 +19,8 @@ case class DisclaimerDTO( ) object DisclaimerDTO { + def fromLanguageValue(lv: WithLanguageAndValue[String]): DisclaimerDTO = DisclaimerDTO(lv.value, lv.language) + implicit def encoder: Encoder[DisclaimerDTO] = deriveEncoder implicit def decoder: Decoder[DisclaimerDTO] = deriveDecoder } diff --git a/common/src/main/scala/no/ndla/common/model/domain/article/Article.scala b/common/src/main/scala/no/ndla/common/model/domain/article/Article.scala index f41cf6220..cbb162c41 100644 --- a/common/src/main/scala/no/ndla/common/model/domain/article/Article.scala +++ b/common/src/main/scala/no/ndla/common/model/domain/article/Article.scala @@ -11,8 +11,9 @@ package no.ndla.common.model.domain.article import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} import io.circe.{Decoder, Encoder} import no.ndla.common.model.{NDLADate, RelatedContentLink} -import no.ndla.common.model.domain._ -import no.ndla.common.implicits._ +import no.ndla.common.model.domain.* +import no.ndla.common.implicits.* +import no.ndla.common.model.domain.language.OptLanguageFields case class Article( id: Option[Long], @@ -37,7 +38,7 @@ case class Article( relatedContent: Seq[RelatedContent], revisionDate: Option[NDLADate], slug: Option[String], - disclaimer: Option[Seq[Disclaimer]] + disclaimer: OptLanguageFields[String] ) extends Content object Article { diff --git a/common/src/main/scala/no/ndla/common/model/domain/draft/Draft.scala b/common/src/main/scala/no/ndla/common/model/domain/draft/Draft.scala index e4f3cce33..a348d7e20 100644 --- a/common/src/main/scala/no/ndla/common/model/domain/draft/Draft.scala +++ b/common/src/main/scala/no/ndla/common/model/domain/draft/Draft.scala @@ -9,9 +9,10 @@ package no.ndla.common.model.domain.draft import io.circe.{Decoder, Encoder} import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} -import no.ndla.common.implicits._ +import no.ndla.common.implicits.* import no.ndla.common.model.{NDLADate, RelatedContentLink} -import no.ndla.common.model.domain._ +import no.ndla.common.model.domain.* +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.language.Language.getSupportedLanguages case class Draft( @@ -46,7 +47,7 @@ case class Draft( priority: Priority, started: Boolean, qualityEvaluation: Option[QualityEvaluation], - disclaimer: Option[Seq[Disclaimer]] + disclaimer: OptLanguageFields[String] ) extends Content { def supportedLanguages: Seq[String] = diff --git a/common/src/main/scala/no/ndla/common/model/domain/language/LanguageFields.scala b/common/src/main/scala/no/ndla/common/model/domain/language/LanguageFields.scala index ba4fb89f7..2d9080f80 100644 --- a/common/src/main/scala/no/ndla/common/model/domain/language/LanguageFields.scala +++ b/common/src/main/scala/no/ndla/common/model/domain/language/LanguageFields.scala @@ -1,5 +1,5 @@ /* - * Part of NDLA backend.common.main + * Part of NDLA common * Copyright (C) 2025 NDLA * * See LICENSE @@ -13,7 +13,7 @@ import io.circe.syntax.EncoderOps import no.ndla.language.Language import no.ndla.language.model.{BaseWithLanguageAndValue, WithLanguageAndValue} -case class LanguageFields[T: Encoder: Decoder](internal: Map[String, T]) { +case class LanguageFields[T](internal: Map[String, T]) { def getWithLanguageFields: Seq[WithLanguageAndValue[T]] = internal.map { case (language, value) => BaseWithLanguageAndValue(language, value) }.toSeq @@ -24,16 +24,14 @@ case class LanguageFields[T: Encoder: Decoder](internal: Map[String, T]) { } object LanguageFields { - def empty[T: Encoder: Decoder]: LanguageFields[T] = LanguageFields(Map.empty) - def fromFields[T]( - fields: Seq[WithLanguageAndValue[T]] - )(implicit encoder: Encoder[T], decoder: Decoder[T]): LanguageFields[T] = { + def empty[T]: LanguageFields[T] = LanguageFields(Map.empty) + def fromFields[T](fields: Seq[WithLanguageAndValue[T]]): LanguageFields[T] = { val underlyingMap = fields.map(f => f.language -> f.value).toMap LanguageFields(underlyingMap) } implicit def encoder[T: Encoder]: Encoder[LanguageFields[T]] = Encoder.instance { lf => lf.internal.asJson } - implicit def decoder[T: Decoder: Encoder]: Decoder[LanguageFields[T]] = Decoder.instance { json => + implicit def decoder[T: Decoder]: Decoder[LanguageFields[T]] = Decoder.instance { json => json.as[Map[String, T]].map { m => LanguageFields(m) } } diff --git a/common/src/main/scala/no/ndla/common/model/domain/language/OptLanguageFields.scala b/common/src/main/scala/no/ndla/common/model/domain/language/OptLanguageFields.scala index ea27e0bc9..65b340119 100644 --- a/common/src/main/scala/no/ndla/common/model/domain/language/OptLanguageFields.scala +++ b/common/src/main/scala/no/ndla/common/model/domain/language/OptLanguageFields.scala @@ -1,5 +1,5 @@ /* - * Part of NDLA backend.common.main + * Part of NDLA common * Copyright (C) 2025 NDLA * * See LICENSE @@ -11,11 +11,14 @@ package no.ndla.common.model.domain.language import io.circe.syntax.EncoderOps import io.circe.{Decoder, Encoder, Json} import no.ndla.common.model.domain.language.OptionalLanguageValue.{NotWantedKey, NotWantedKeyT} -import no.ndla.language.model.WithLanguageAndValue +import no.ndla.language.Language +import no.ndla.language.model.{BaseWithLanguageAndValue, WithLanguageAndValue} +import sttp.tapir.Schema case class OptLanguageFields[T: Encoder: Decoder]( internal: Map[String, Either[NotWantedKeyT, Option[T]]] ) { + def get(language: String): Option[OptionalLanguageValue[T]] = { val res = internal.get(language) res match { @@ -26,13 +29,81 @@ case class OptLanguageFields[T: Encoder: Decoder]( } } + def map[R](f: WithLanguageAndValue[Option[T]] => R): Seq[R] = internal.map { case (language, value) => + value match { + case Right(Some(value)) => f(BaseWithLanguageAndValue(language, Some(value))) + case _ => f(BaseWithLanguageAndValue(language, None)) + } + }.toSeq + + def mapExisting[R](f: WithLanguageAndValue[T] => R): Seq[R] = internal.flatMap { case (language, value) => + value match { + case Right(Some(value)) => Some(f(BaseWithLanguageAndValue(language, value))) + case _ => None + } + }.toSeq + + def getWithLanguageFields: Seq[WithLanguageAndValue[T]] = internal.flatMap { case (language, value) => + value match { + case Right(Some(value)) => Some(BaseWithLanguageAndValue(language, value)) + case _ => None + } + }.toSeq + + def findByLanguageOrBestEffort(language: String): Option[WithLanguageAndValue[T]] = { + get(language) match { + case Some(Exists(value)) => Some(BaseWithLanguageAndValue(language, value)) + case Some(NotWanted()) => None + case None => Language.findByLanguageOrBestEffort(getWithLanguageFields, language) + } + } + def withUnwanted(language: String): OptLanguageFields[T] = { val updated: Map[String, Either[NotWantedKeyT, Option[T]]] = internal.updated(language, Left(NotWantedKey)) OptLanguageFields(updated) } + + def withValue(value: T, language: String): OptLanguageFields[T] = { + val updated: Map[String, Either[NotWantedKeyT, Option[T]]] = internal.updated(language, Right(Some(value))) + OptLanguageFields(updated) + } } object OptLanguageFields { + implicit val s1: Schema[OptLanguageFields[String]] = Schema.any + + def withUnwanted[T: Encoder: Decoder](language: String): OptLanguageFields[T] = { + val underlyingMap = Map(language -> Left(NotWantedKey)) + OptLanguageFields(underlyingMap) + } + + def withValue[T: Encoder: Decoder](value: T, language: String): OptLanguageFields[T] = { + val underlyingMap = Map(language -> Right(Some(value))) + OptLanguageFields(underlyingMap) + } + + implicit class optStringLanguageFields(s: OptLanguageFields[String]) { + def withOptValue(value: Option[String], language: String): OptLanguageFields[String] = { + value match { + case Some("") => s.withUnwanted(language) + case Some(v) => s.withValue(v, language) + case None => this.s + } + } + + def withOptValue(value: Option[String], language: Option[String]): OptLanguageFields[String] = language match { + case None => this.s + case Some(lang) => this.withOptValue(value, lang) + } + } + + def fromMaybeString(value: Option[String], language: String): OptLanguageFields[String] = { + value match { + case Some("") => withUnwanted(language) + case Some(v) => withValue(v, language) + case None => empty + } + } def fromFields[T]( fields: Seq[WithLanguageAndValue[T]] @@ -41,6 +112,8 @@ object OptLanguageFields { OptLanguageFields(underlyingMap) } + def empty[T: Encoder: Decoder]: OptLanguageFields[T] = OptLanguageFields(Map.empty) + implicit def eitherEncoder[T](implicit e: Encoder[T]): Encoder[Either[NotWantedKeyT, Option[T]]] = Encoder.instance { case Right(value) => value.asJson case Left(_) => Json.obj(NotWantedKey -> Json.True) diff --git a/common/src/main/scala/no/ndla/common/model/domain/language/OptionalLanguageValue.scala b/common/src/main/scala/no/ndla/common/model/domain/language/OptionalLanguageValue.scala index 0904023d0..ccb34279c 100644 --- a/common/src/main/scala/no/ndla/common/model/domain/language/OptionalLanguageValue.scala +++ b/common/src/main/scala/no/ndla/common/model/domain/language/OptionalLanguageValue.scala @@ -10,20 +10,22 @@ package no.ndla.common.model.domain.language import io.circe.syntax.EncoderOps import io.circe.{Decoder, Encoder, HCursor, Json} +import sttp.tapir.Schema sealed trait OptionalLanguageValue[T] -case class Exists[T: Encoder: Decoder](value: T) extends OptionalLanguageValue[T] -case class NotWanted[T]() extends OptionalLanguageValue[T] +case class Exists[T](value: T) extends OptionalLanguageValue[T] +case class NotWanted[T]() extends OptionalLanguageValue[T] object OptionalLanguageValue { type NotWantedKeyT = "__notwanted__" - final val NotWantedKey = "__notwanted__" + final val NotWantedKey: NotWantedKeyT = "__notwanted__" + implicit val NotWantedSchema: Schema[NotWantedKeyT] = Schema.string implicit def encoder[T](implicit valueEncoder: Encoder[T]): Encoder[OptionalLanguageValue[T]] = Encoder.instance { case Exists(value) => Json.obj("value" -> value.asJson) case NotWanted() => Json.obj(NotWantedKey -> Json.True) } - implicit def decoder[T: Encoder: Decoder]: Decoder[OptionalLanguageValue[T]] = + implicit def decoder[T: Decoder]: Decoder[OptionalLanguageValue[T]] = (c: HCursor) => { c.downField(NotWantedKey).as[Option[Boolean]].flatMap { case Some(true) => Right(NotWanted()) diff --git a/common/src/test/scala/no/ndla/common/model/domain/LanguageFieldsTest.scala b/common/src/test/scala/no/ndla/common/model/domain/LanguageFieldsTest.scala index 4a8d474b3..474d359c3 100644 --- a/common/src/test/scala/no/ndla/common/model/domain/LanguageFieldsTest.scala +++ b/common/src/test/scala/no/ndla/common/model/domain/LanguageFieldsTest.scala @@ -44,6 +44,20 @@ class LanguageFieldsTest extends UnitTestSuite { languageFields.findByLanguageOrBestEffort("nn") should be(Some(BaseWithLanguageAndValue("nn", "nynorsk"))) } + test("That language fields are found by language or best effort according to language priority when opt") { + val fields = Seq( + BaseWithLanguageAndValue("nb", "bokmål"), + BaseWithLanguageAndValue("en", "english") + ) + + val languageFields = OptLanguageFields.fromFields(fields).withUnwanted("nn") + + languageFields.findByLanguageOrBestEffort("nb") should be(Some(BaseWithLanguageAndValue("nb", "bokmål"))) + languageFields.findByLanguageOrBestEffort("en") should be(Some(BaseWithLanguageAndValue("en", "english"))) + languageFields.findByLanguageOrBestEffort("sma") should be(Some(BaseWithLanguageAndValue("nb", "bokmål"))) + languageFields.findByLanguageOrBestEffort("nn") should be(None) + } + test("That the LanguageFields type is able to differentiate between a missing and not needed field") { val fields = Seq( @@ -53,26 +67,20 @@ class LanguageFieldsTest extends UnitTestSuite { val languageFields = LanguageFields.fromFields(fields) val jsonString = CirceUtil.toJsonString(languageFields) + val result = CirceUtil.unsafeParseAs[LanguageFields[OptionalLanguageValue[String]]](jsonString) - val result = CirceUtil.unsafeParseAs[LanguageFields[OptionalLanguageValue[String]]](jsonString) result should be(languageFields) - result.get("nb") should be(Some(BaseWithLanguageAndValue("nb", Exists("bokmål")))) result.get("nn") should be(Some(BaseWithLanguageAndValue("nn", NotWanted()))) } test("That the OptLanguageFields type is able to differentiate between a missing and not needed field") { - - val fields = Seq( - BaseWithLanguageAndValue[String]("nb", "bokmål") - ) - + val fields = Seq(BaseWithLanguageAndValue[String]("nb", "bokmål")) val languageFields = OptLanguageFields.fromFields(fields).withUnwanted("en") val jsonString = CirceUtil.toJsonString(languageFields) + val result = CirceUtil.unsafeParseAs[OptLanguageFields[String]](jsonString) - val result = CirceUtil.unsafeParseAs[OptLanguageFields[String]](jsonString) result should be(languageFields) - result.get("nb") should be(Some(Exists("bokmål"))) result.get("nn") should be(None) result.get("en") should be(Some(NotWanted())) diff --git a/database/src/main/scala/no/ndla/database/DocumentMigration.scala b/database/src/main/scala/no/ndla/database/DocumentMigration.scala index e3820532d..4f266db26 100644 --- a/database/src/main/scala/no/ndla/database/DocumentMigration.scala +++ b/database/src/main/scala/no/ndla/database/DocumentMigration.scala @@ -32,5 +32,5 @@ abstract class DocumentMigration extends TableMigration[DocumentRow] { .update() } - protected def convertColumn(value: String): String + def convertColumn(value: String): String } diff --git a/database/src/main/scala/no/ndla/database/LanguageFieldMigration.scala b/database/src/main/scala/no/ndla/database/LanguageFieldMigration.scala new file mode 100644 index 000000000..0e05130dd --- /dev/null +++ b/database/src/main/scala/no/ndla/database/LanguageFieldMigration.scala @@ -0,0 +1,41 @@ +/* + * Part of NDLA database + * Copyright (C) 2025 NDLA + * + * See LICENSE + * + */ + +package no.ndla.database + +import io.circe.{Json, parser} + +abstract class LanguageFieldMigration extends DocumentMigration { + protected def fieldName: String + protected def oldSubfieldName: String = fieldName + + private def convertOldLanguageField(fields: Vector[Json]): Json = { + fields.foldLeft(Json.obj()) { (acc, disclaimer) => + val language = disclaimer.hcursor.downField("language").as[String].toTry.get + val text = disclaimer.hcursor.downField(oldSubfieldName).as[String].toTry.get + acc.mapObject(_.add(language, Json.fromString(text))) + } + } + + private def addEmptyLanguageField(obj: Json): String = { + obj.withObject(_.add(fieldName, Json.obj()).toJson).noSpaces + } + + override def convertColumn(document: String): String = { + val oldArticle = parser.parse(document).toTry.get + oldArticle.hcursor.downField(fieldName).focus match { + case None => addEmptyLanguageField(oldArticle) + case Some(f) if f.isNull => addEmptyLanguageField(oldArticle) + case Some(disclaimers) => + val disclaimerVector = disclaimers.asArray.get + val converted = convertOldLanguageField(disclaimerVector) + val newArticle = oldArticle.withObject(_.remove(fieldName).add(fieldName, converted).toJson) + newArticle.noSpaces + } + } +} diff --git a/draft-api/src/main/scala/no/ndla/draftapi/db/HtmlMigration.scala b/draft-api/src/main/scala/no/ndla/draftapi/db/HtmlMigration.scala index 2433d1aab..7706bb81c 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/db/HtmlMigration.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/db/HtmlMigration.scala @@ -28,7 +28,7 @@ abstract class HtmlMigration extends DocumentMigration { jsoupDocumentToString(converted) } - protected def convertColumn(document: String): String = { + def convertColumn(document: String): String = { val oldArticle = parser.parse(document).flatMap(_.as[Draft]).toTry.get val convertedContent = oldArticle.content.map(c => { val converted = convertContent(c.content, c.language) diff --git a/draft-api/src/main/scala/no/ndla/draftapi/db/migration/V67__DisclaimerToLanguageFields.scala b/draft-api/src/main/scala/no/ndla/draftapi/db/migration/V67__DisclaimerToLanguageFields.scala new file mode 100644 index 000000000..bae99a214 --- /dev/null +++ b/draft-api/src/main/scala/no/ndla/draftapi/db/migration/V67__DisclaimerToLanguageFields.scala @@ -0,0 +1,17 @@ +/* + * Part of NDLA article-api + * Copyright (C) 2025 NDLA + * + * See LICENSE + * + */ + +package no.ndla.draftapi.db.migration + +import no.ndla.database.LanguageFieldMigration + +class V67__DisclaimerToLanguageFields extends LanguageFieldMigration { + override val columnName: String = "document" + override val tableName: String = "articledata" + override val fieldName: String = "disclaimer" +} diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/ConverterService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/ConverterService.scala index 31057106b..3a7e1bd4b 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/ConverterService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/ConverterService.scala @@ -16,6 +16,7 @@ import no.ndla.common.model.api.{Delete, DisclaimerDTO, DraftCopyrightDTO, Missi import no.ndla.common.model.domain.{ArticleContent, Priority, Responsible} import no.ndla.common.model.domain.draft.DraftStatus.{IMPORTED, PLANNED} import no.ndla.common.model.domain.draft.{Comment, Draft, DraftStatus} +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.common.model.{NDLADate, RelatedContentLink, api as commonApi, domain as common} import no.ndla.common.{Clock, UUIDUtil, model} import no.ndla.draftapi.Props @@ -55,7 +56,7 @@ trait ConverterService { val domainContent = newArticle.content .map(content => common.ArticleContent(removeUnknownEmbedTagAttribute(content), newArticle.language)) .toSeq - val domainDisclaimer = newArticle.disclaimer.map { d => Seq(common.Disclaimer(d, newArticle.language)) } + val domainDisclaimer = OptLanguageFields.fromMaybeString(newArticle.disclaimer, newArticle.language) val status = externalIds match { case Nil => common.Status(PLANNED, Set.empty) @@ -391,8 +392,7 @@ trait ConverterService { val metaImage = findByLanguageOrBestEffort(article.metaImage, language).map(toApiArticleMetaImage) val revisionMetas = article.revisionMeta.map(toApiRevisionMeta) val responsible = article.responsible.map(toApiResponsible) - val disclaimer = - article.disclaimer.flatMap { d => findByLanguageOrBestEffort(d, language).map(toApiArticleDisclaimer) } + val disclaimer = article.disclaimer.findByLanguageOrBestEffort(language).map(DisclaimerDTO.fromLanguageValue) Success( api.ArticleDTO( @@ -650,7 +650,8 @@ trait ConverterService { article.tags, article.introduction, article.metaDescription, - article.visualElement + article.visualElement, + article.disclaimer ) langFields.foldRight(false)((curr, res) => res || curr.isDefined || metaImageExists) @@ -794,15 +795,7 @@ trait ConverterService { .flatten ) - val updatedDisclaimer = articleWithNewContent.disclaimer match { - case None => toMergeInto.disclaimer - case Some(newDisclaimer) => - val updated = mergeLanguageFields( - toMergeInto.disclaimer.getOrElse(Seq.empty), - maybeLang.map(lang => toDomainDisclaimer(DisclaimerDTO(newDisclaimer, lang))).toSeq - ) - Option.when(updated.nonEmpty)(updated) - } + val updatedDisclaimer = toMergeInto.disclaimer.withOptValue(articleWithNewContent.disclaimer, maybeLang) val updatedContents = mergeLanguageFields( toMergeInto.content, @@ -958,7 +951,7 @@ trait ConverterService { priority = priority, started = false, qualityEvaluation = qualityEvaluationToDomain(article.qualityEvaluation), - disclaimer = article.disclaimer.map { d => Seq(common.Disclaimer(d, lang)) } + disclaimer = OptLanguageFields.fromMaybeString(article.disclaimer, lang) ) } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/validation/ContentValidator.scala b/draft-api/src/main/scala/no/ndla/draftapi/validation/ContentValidator.scala index c9ff0c12b..917de37f2 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/validation/ContentValidator.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/validation/ContentValidator.scala @@ -11,6 +11,7 @@ import no.ndla.common.errors.{ValidationException, ValidationMessage} import no.ndla.common.model.NDLADate import no.ndla.common.model.domain.* import no.ndla.common.model.domain.draft.* +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.draftapi.Props import no.ndla.draftapi.integration.ArticleApiClient import no.ndla.draftapi.model.api.{ContentIdDTO, NotFoundException, UpdatedArticleDTO} @@ -84,7 +85,7 @@ trait ContentValidator { if (shouldValidateEntireArticle) article.content.flatMap(c => validateArticleContent(c)) ++ article.introduction.flatMap(i => validateIntroduction(i)) ++ - validateArticleDisclaimer(article.disclaimer.getOrElse(Seq.empty)) ++ + validateArticleDisclaimer(article.disclaimer) ++ article.metaDescription.flatMap(m => validateMetaDescription(m)) ++ validateTitles(article.title) ++ article.copyright.map(x => validateCopyright(x)).toSeq.flatten ++ @@ -165,11 +166,12 @@ trait ContentValidator { validateLanguage("content.language", content.language) } - private def validateArticleDisclaimer(disclaimers: Seq[Disclaimer]): Seq[ValidationMessage] = { - disclaimers.flatMap(disclaimer => { - TextValidator.validate("disclaimer", disclaimer.disclaimer, allLegalTags).toList ++ + private def validateArticleDisclaimer(disclaimers: OptLanguageFields[String]): Seq[ValidationMessage] = { + disclaimers.mapExisting { disclaimer => + val field = s"disclaimer.${disclaimer.language}" + TextValidator.validate(field, disclaimer.value, allLegalTags).toList ++ validateLanguage("disclaimer.language", disclaimer.language) - }) + }.flatten } private def rootElementContainsOnlySectionBlocks(field: String, html: String): Option[ValidationMessage] = { diff --git a/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala b/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala index 27054f7a1..311a38c7a 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala @@ -13,6 +13,7 @@ import no.ndla.common.model.api.{DraftCopyrightDTO, Missing} import no.ndla.common.model.domain.Priority import no.ndla.common.model.domain.draft.Draft import no.ndla.common.model.domain.draft.DraftStatus.* +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.common.model.{NDLADate, api as commonApi, domain as common} import no.ndla.draftapi.integration.{LearningPath, Title} import no.ndla.draftapi.model.api.* @@ -322,7 +323,7 @@ object TestData { Priority.Unspecified, false, None, - None + disclaimer = OptLanguageFields.empty ) val sampleArticleWithPublicDomain: Draft = Draft( @@ -357,7 +358,7 @@ object TestData { Priority.Unspecified, false, None, - None + disclaimer = OptLanguageFields.empty ) val sampleDomainArticle: Draft = Draft( @@ -394,7 +395,7 @@ object TestData { Priority.Unspecified, false, None, - None + disclaimer = OptLanguageFields.empty ) val newArticle: NewArticleDTO = api.NewArticleDTO( @@ -485,7 +486,7 @@ object TestData { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val apiArticleWithHtmlFaultV2: api.ArticleDTO = api.ArticleDTO( diff --git a/draft-api/src/test/scala/no/ndla/draftapi/service/ConverterServiceTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/service/ConverterServiceTest.scala index 3b4c02ecf..0fa6a0601 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/service/ConverterServiceTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/service/ConverterServiceTest.scala @@ -14,6 +14,7 @@ import no.ndla.common.model.api.{Delete, Missing, UpdateWith} import no.ndla.common.model.domain.* import no.ndla.common.model.domain.draft.DraftStatus.* import no.ndla.common.model.domain.draft.{Comment, Draft, DraftCopyright, DraftStatus} +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.common.model.{NDLADate, api as commonApi} import no.ndla.draftapi.model.api import no.ndla.draftapi.model.api.{NewCommentDTO, UpdatedCommentDTO} @@ -333,7 +334,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = Some(Seq(Disclaimer("Disclaimer test", "nb"))) + disclaimer = OptLanguageFields.withValue("Disclaimer test", "nb") ) val updatedNothing = TestData.blankUpdatedArticle.copy( @@ -381,7 +382,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = Some(Seq(Disclaimer("Disclaimer test", "nb"))) + disclaimer = OptLanguageFields.withValue("Disclaimer test", "nb") ) val expectedArticle = Draft( @@ -416,7 +417,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = Some(Seq(Disclaimer("NyDisclaimer test", "nb"))) + disclaimer = OptLanguageFields.withValue("NyDisclaimer test", "nb") ) val updatedEverything = TestData.blankUpdatedArticle.copy( @@ -482,7 +483,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val expectedArticle = Draft( @@ -525,7 +526,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val updatedEverything = TestData.blankUpdatedArticle.copy( @@ -1127,7 +1128,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = Some(Seq(Disclaimer("articleDisclaimer", "nb"))) + disclaimer = OptLanguageFields.withValue("articleDisclaimer", "nb") ) val article = common.model.domain.article.Article( id = Some(articleId), @@ -1153,7 +1154,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { relatedContent = Seq.empty, revisionDate = None, slug = Some("kjempe-slug"), - disclaimer = Some(Seq(Disclaimer("articleDisclaimer", "nb"))) + disclaimer = OptLanguageFields.withValue("articleDisclaimer", "nb") ) val result = service.toArticleApiArticle(draft) diff --git a/draft-api/src/test/scala/no/ndla/draftapi/service/StateTransitionRulesTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/service/StateTransitionRulesTest.scala index bbad36f92..832324fab 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/service/StateTransitionRulesTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/service/StateTransitionRulesTest.scala @@ -11,6 +11,7 @@ import no.ndla.common.errors.{ValidationException, ValidationMessage} import no.ndla.common.model.domain.{Priority, Responsible, Status} import no.ndla.common.model.domain.draft.Draft import no.ndla.common.model.domain.draft.DraftStatus.* +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.common.model.{NDLADate, domain as common} import no.ndla.draftapi.integration.{SearchHit, Title} import no.ndla.draftapi.model.domain.StateTransition @@ -356,7 +357,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val article = common.article.Article( id = Some(articleId), @@ -381,7 +382,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { relatedContent = Seq.empty, revisionDate = None, slug = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val status = common.Status(END_CONTROL, Set.empty) @@ -479,7 +480,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val status = common.Status(PLANNED, Set.empty) val transitionsToTest = StateTransitionRules.StateTransitions.filter(_.to == PUBLISHED) @@ -536,7 +537,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val status = common.Status(PLANNED, Set.empty) val transitionsToTest = StateTransitionRules.StateTransitions.filter(_.to == ARCHIVED) @@ -597,7 +598,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val status = common.Status(PLANNED, Set.empty) val transitionsToTest = StateTransitionRules.StateTransitions.filter(_.to == UNPUBLISHED) @@ -659,7 +660,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val status = common.Status(PUBLISHED, Set.empty) val transitionToTest: StateTransition = PUBLISHED -> IN_PROGRESS diff --git a/draft-api/src/test/scala/no/ndla/draftapi/validation/ContentValidatorTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/validation/ContentValidatorTest.scala index f75a74233..3f920efd8 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/validation/ContentValidatorTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/validation/ContentValidatorTest.scala @@ -10,6 +10,7 @@ package no.ndla.draftapi.validation import no.ndla.common.errors.{ValidationException, ValidationMessage} import no.ndla.common.model.domain.* import no.ndla.common.model.domain.draft.{Comment, Draft, DraftCopyright, RevisionMeta} +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.draftapi.{TestData, TestEnvironment, UnitSuite} import no.ndla.mapping.License.CC_BY_SA @@ -63,7 +64,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment { test("validateArticle should throw an error if disclaimer contains illegal HTML tags") { val article = articleToValidate.copy( content = Seq(ArticleContent(validDocument, "nb")), - disclaimer = Some(Seq(Disclaimer("

hei

", "nb"))) + disclaimer = OptLanguageFields.withValue("

hei

", "nb") ) val Failure(error: ValidationException) = contentValidator.validateArticle(article) error should be( @@ -77,7 +78,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment { test("validateArticle should not throw an error if disclaimer contains legal HTML tags") { val article = articleToValidate.copy( content = Seq(ArticleContent(validDocument, "nb")), - disclaimer = Some(Seq(Disclaimer(validDisclaimer, "nb"))) + disclaimer = OptLanguageFields.withValue(validDisclaimer, "nb") ) contentValidator.validateArticle(article).isSuccess should be(true) diff --git a/integration-tests/src/test/scala/no/ndla/integrationtests/draftapi/articleapi/ArticleApiClientTest.scala b/integration-tests/src/test/scala/no/ndla/integrationtests/draftapi/articleapi/ArticleApiClientTest.scala index a370e954b..4740350f0 100644 --- a/integration-tests/src/test/scala/no/ndla/integrationtests/draftapi/articleapi/ArticleApiClientTest.scala +++ b/integration-tests/src/test/scala/no/ndla/integrationtests/draftapi/articleapi/ArticleApiClientTest.scala @@ -10,6 +10,7 @@ package no.ndla.integrationtests.draftapi.articleapi import no.ndla.articleapi.ArticleApiProperties import no.ndla.common.model.domain.Priority import no.ndla.common.model.domain.draft.Draft +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.common.model.{NDLADate, domain as common} import no.ndla.draftapi.model.api.ContentIdDTO import no.ndla.integrationtests.UnitSuite @@ -126,7 +127,7 @@ class ArticleApiClientTest priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val exampleToken = diff --git a/language/src/main/scala/no/ndla/language/model/WithLanguageAndValue.scala b/language/src/main/scala/no/ndla/language/model/WithLanguageAndValue.scala index 64c4b823e..e3787e360 100644 --- a/language/src/main/scala/no/ndla/language/model/WithLanguageAndValue.scala +++ b/language/src/main/scala/no/ndla/language/model/WithLanguageAndValue.scala @@ -1,5 +1,5 @@ /* - * Part of NDLA backend.language.main + * Part of NDLA language * Copyright (C) 2025 NDLA * * See LICENSE 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 index 82d4f17d8..fd0f7dc72 100644 --- 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 @@ -17,7 +17,7 @@ class V39__MadeAvailableForThePublished extends DocumentMigration { override val columnName: String = "document" override val tableName: String = "learningpaths" - protected def convertColumn(document: String): String = { + def convertColumn(document: String): String = { val oldLp = CirceUtil.unsafeParseAs[LearningPath](document) val madeAvailable = oldLp.status match { case UNLISTED | PUBLISHED => Some(oldLp.lastUpdated) diff --git a/search-api/src/main/scala/no/ndla/searchapi/controller/parameters/GetSearchQueryParams.scala b/search-api/src/main/scala/no/ndla/searchapi/controller/parameters/GetSearchQueryParams.scala index 97d123830..fd951e3c6 100644 --- a/search-api/src/main/scala/no/ndla/searchapi/controller/parameters/GetSearchQueryParams.scala +++ b/search-api/src/main/scala/no/ndla/searchapi/controller/parameters/GetSearchQueryParams.scala @@ -1,5 +1,5 @@ /* - * Part of NDLA backend.search-api.main + * Part of NDLA search-api * Copyright (C) 2024 NDLA * * See LICENSE diff --git a/search-api/src/test/scala/no/ndla/searchapi/TestData.scala b/search-api/src/test/scala/no/ndla/searchapi/TestData.scala index 3bf3d0147..2613dfb6c 100644 --- a/search-api/src/test/scala/no/ndla/searchapi/TestData.scala +++ b/search-api/src/test/scala/no/ndla/searchapi/TestData.scala @@ -38,6 +38,7 @@ import no.ndla.common.model.domain.concept.{ WordClass } import no.ndla.common.model.domain.draft.{Draft, DraftCopyright, DraftStatus, RevisionMeta, RevisionStatus} +import no.ndla.common.model.domain.language.OptLanguageFields import no.ndla.common.model.domain.learningpath.{ LearningPath, LearningPathStatus, @@ -56,9 +57,9 @@ import no.ndla.searchapi.model.grep.{ GrepKjerneelement, GrepKompetansemaal, GrepLaererplan, + GrepTextObj, GrepTitle, - GrepTverrfagligTema, - GrepTextObj + GrepTverrfagligTema } import no.ndla.searchapi.model.search.* import no.ndla.searchapi.model.search.settings.{MultiDraftSearchSettings, SearchSettings} @@ -211,7 +212,7 @@ object TestData { Seq.empty, None, slug = None, - None + disclaimer = OptLanguageFields.empty ) val sampleDomainArticle: Article = Article( @@ -237,7 +238,7 @@ object TestData { Seq.empty, None, slug = None, - None + disclaimer = OptLanguageFields.empty ) val sampleDomainArticle2: Article = Article( @@ -263,7 +264,7 @@ object TestData { Seq.empty, None, slug = None, - None + disclaimer = OptLanguageFields.empty ) val sampleArticleWithByNcSa: Article = @@ -544,9 +545,9 @@ object TestData { conceptIds = Seq.empty, availability = Availability.everyone, relatedContent = Seq.empty, - None, + revisionDate = None, slug = None, - None + disclaimer = OptLanguageFields.empty ) val emptyDomainDraft: Draft = Draft( @@ -581,7 +582,7 @@ object TestData { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - None + disclaimer = OptLanguageFields.empty ) val draftStatus: Status = Status(DraftStatus.PLANNED, Set.empty) @@ -644,7 +645,7 @@ object TestData { priority = Priority.Unspecified, started = false, qualityEvaluation = None, - disclaimer = None + disclaimer = OptLanguageFields.empty ) val sampleDraftWithByNcSa: Draft = sampleDraftWithPublicDomain.copy(copyright = Some(draftByNcSaCopyright)) diff --git a/search-api/src/test/scala/no/ndla/searchapi/model/api/grep/GrepResultDTOTest.scala b/search-api/src/test/scala/no/ndla/searchapi/model/api/grep/GrepResultDTOTest.scala index 38948027e..3516ff679 100644 --- a/search-api/src/test/scala/no/ndla/searchapi/model/api/grep/GrepResultDTOTest.scala +++ b/search-api/src/test/scala/no/ndla/searchapi/model/api/grep/GrepResultDTOTest.scala @@ -1,5 +1,5 @@ /* - * Part of NDLA backend.search-api.test + * Part of NDLA search-api * Copyright (C) 2025 NDLA * * See LICENSE