diff --git a/article-api/src/main/scala/no/ndla/articleapi/ArticleApiProperties.scala b/article-api/src/main/scala/no/ndla/articleapi/ArticleApiProperties.scala
index 2c25984a68..1c943c193d 100644
--- a/article-api/src/main/scala/no/ndla/articleapi/ArticleApiProperties.scala
+++ b/article-api/src/main/scala/no/ndla/articleapi/ArticleApiProperties.scala
@@ -45,42 +45,6 @@ class ArticleApiProperties extends BaseProps with DatabaseProps {
def RedisHost: String = propOrElse("REDIS_HOST", "redis")
def RedisPort: Int = propOrElse("REDIS_PORT", "6379").toInt
- def oldCreatorTypes: List[String] = List(
- "opphavsmann",
- "fotograf",
- "kunstner",
- "forfatter",
- "manusforfatter",
- "innleser",
- "oversetter",
- "regissør",
- "illustratør",
- "medforfatter",
- "komponist"
- )
-
- def creatorTypes: List[String] = List(
- "originator",
- "photographer",
- "artist",
- "writer",
- "scriptwriter",
- "reader",
- "translator",
- "director",
- "illustrator",
- "cowriter",
- "composer"
- )
-
- def oldProcessorTypes: List[String] =
- List("bearbeider", "tilrettelegger", "redaksjonelt", "språklig", "ide", "sammenstiller", "korrektur")
- def processorTypes: List[String] =
- List("processor", "facilitator", "editorial", "linguistic", "idea", "compiler", "correction")
-
- def oldRightsholderTypes: List[String] = List("rettighetshaver", "forlag", "distributør", "leverandør")
- def rightsholderTypes: List[String] = List("rightsholder", "publisher", "distributor", "supplier")
-
// When converting a content node, the converter may run several times over the content to make sure
// everything is converted. This value defines a maximum number of times the converter runs on a node
def maxConvertionRounds = 5
diff --git a/article-api/src/main/scala/no/ndla/articleapi/db/migration/V58__FixContributorTypes.scala b/article-api/src/main/scala/no/ndla/articleapi/db/migration/V58__FixContributorTypes.scala
new file mode 100644
index 0000000000..3f0b9261e5
--- /dev/null
+++ b/article-api/src/main/scala/no/ndla/articleapi/db/migration/V58__FixContributorTypes.scala
@@ -0,0 +1,56 @@
+/*
+ * Part of NDLA article-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.articleapi.db.migration
+
+import io.circe.{ACursor, parser}
+import io.circe.generic.auto.*
+import io.circe.syntax.EncoderOps
+import no.ndla.common.model.domain.{Author, ContributorType}
+import no.ndla.database.DocumentMigration
+
+class V58__FixContributorTypes extends DocumentMigration {
+ override val tableName: String = "contentdata"
+ override val columnName: String = "document"
+
+ override def convertColumn(value: String): String = {
+ val oldDocument = parser.parse(value).toTry.get
+ val copyright = oldDocument.hcursor.downField("copyright")
+ val creators = convertList(copyright, "creators")
+ val processors = convertList(copyright, "processors")
+ val rightsholders = convertList(copyright, "rightsholders")
+
+ val newDocument = oldDocument.hcursor
+ .downField("copyright")
+ .withFocus(_.mapObject(_.remove("creators").add("creators", creators.asJson)))
+ .withFocus(_.mapObject(_.remove("processors").add("processors", processors.asJson)))
+ .withFocus(_.mapObject(_.remove("rightsholders").add("rightsholders", rightsholders.asJson)))
+ newDocument.top.get.noSpaces
+ }
+
+ private def convertList(cursor: ACursor, fieldName: String): List[Author] = {
+ val field = cursor.downField(fieldName)
+ if (field.succeeded) {
+ field.as[Option[List[OldAuthor]]].toTry.get match {
+ case None => List.empty
+ case Some(authors) => convertAuthors(authors)
+ }
+ } else List.empty
+ }
+
+ private def convertAuthors(authors: List[OldAuthor]): List[Author] = {
+ authors.map(author => {
+ ContributorType.valueOf(author.`type`.toLowerCase) match {
+ case None => Author(ContributorType.mapping(author.`type`.toLowerCase), author.name)
+ case Some(a) => Author(a, author.name)
+ }
+ })
+ }
+}
+
+case class OldAuthor(`type`: String, name: String)
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 184cd43e9b..22ae4c484d 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
@@ -162,7 +162,7 @@ trait ContentValidator {
val field = s"title.language"
TextValidator.validate(field, title.value, inlineHtmlTags).toList ++
validateLanguage("title.language", title.language) ++
- validateLength("title", title.value, 256)
+ validateLength("title", title.value, 0, 256)
}) ++ validateNonEmpty("title", titles)
}
@@ -171,9 +171,11 @@ trait ContentValidator {
val allAuthors = copyright.creators ++ copyright.processors ++ copyright.rightsholders
val licenseCorrelationMessage = validateAuthorLicenseCorrelation(copyright.license, allAuthors)
val contributorsMessages =
- copyright.creators.flatMap(a => validateAuthor(a, "copyright.creators", props.creatorTypes)) ++
- copyright.processors.flatMap(a => validateAuthor(a, "copyright.processors", props.processorTypes)) ++
- copyright.rightsholders.flatMap(a => validateAuthor(a, "copyright.rightsholders", props.rightsholderTypes))
+ copyright.creators.flatMap(a => validateAuthor(a, "copyright.creators", ContributorType.creators)) ++
+ copyright.processors.flatMap(a => validateAuthor(a, "copyright.processors", ContributorType.processors)) ++
+ copyright.rightsholders.flatMap(a =>
+ validateAuthor(a, "copyright.rightsholders", ContributorType.rightsholders)
+ )
val originMessage =
copyright.origin
.map(origin => TextValidator.validate("copyright.origin", origin, Set.empty))
@@ -195,18 +197,22 @@ trait ContentValidator {
if (license == "N/A" || authors.nonEmpty) Seq() else Seq(errorMessage(license))
}
- private def validateAuthor(author: Author, fieldPath: String, allowedTypes: Seq[String]): Seq[ValidationMessage] = {
- TextValidator.validate(s"$fieldPath.type", author.`type`, Set.empty).toList ++
- TextValidator.validate(s"$fieldPath.name", author.name, Set.empty).toList ++
- validateAuthorType(s"$fieldPath.type", author.`type`, allowedTypes).toList
+ private def validateAuthor(
+ author: Author,
+ fieldPath: String,
+ allowedTypes: Seq[ContributorType]
+ ): Seq[ValidationMessage] = {
+ TextValidator.validate(s"$fieldPath.name", author.name, Set.empty).toList ++
+ validateAuthorType(s"$fieldPath.type", author.`type`, allowedTypes).toList ++
+ validateLength(s"$fieldPath.name", author.name, 1, 256)
}
private def validateAuthorType(
fieldPath: String,
- `type`: String,
- allowedTypes: Seq[String]
+ `type`: ContributorType,
+ allowedTypes: Seq[ContributorType]
): Option[ValidationMessage] = {
- if (allowedTypes.contains(`type`.toLowerCase)) {
+ if (allowedTypes.contains(`type`)) {
None
} else {
Some(ValidationMessage(fieldPath, s"Author is of illegal type. Must be one of ${allowedTypes.mkString(", ")}"))
@@ -273,9 +279,16 @@ trait ContentValidator {
}
}
- private def validateLength(fieldPath: String, content: String, maxLength: Int): Option[ValidationMessage] = {
+ private def validateLength(fieldPath: String, content: String, minLength: Int, maxLength: Int) = {
if (content.length > maxLength)
Some(ValidationMessage(fieldPath, s"This field exceeds the maximum permitted length of $maxLength characters"))
+ else if (content.length < minLength)
+ Some(
+ ValidationMessage(
+ fieldPath,
+ s"This field is shorter than the minimum permitted length of $minLength characters"
+ )
+ )
else
None
}
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 ba899e8cf2..6a19fa6844 100644
--- a/article-api/src/test/scala/no/ndla/articleapi/TestData.scala
+++ b/article-api/src/test/scala/no/ndla/articleapi/TestData.scala
@@ -28,7 +28,7 @@ trait TestData {
Copyright(
License.CC_BY_NC_SA.toString,
Some("Gotham City"),
- List(Author("Writer", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -39,7 +39,7 @@ trait TestData {
Copyright(
License.Copyrighted.toString,
Some("New York"),
- List(Author("Writer", "Clark Kent")),
+ List(Author(ContributorType.Writer, "Clark Kent")),
List(),
List(),
None,
@@ -59,7 +59,7 @@ trait TestData {
copyright = model.api.CopyrightDTO(
model.api.LicenseDTO("licence", None, None),
Some("origin"),
- Seq(model.api.AuthorDTO("developer", "Per")),
+ Seq(model.api.AuthorDTO(ContributorType.Editorial, "Per")),
List(),
List(),
None,
diff --git a/article-api/src/test/scala/no/ndla/articleapi/controller/InternControllerTest.scala b/article-api/src/test/scala/no/ndla/articleapi/controller/InternControllerTest.scala
index 18352225dc..c44e43d3e7 100644
--- a/article-api/src/test/scala/no/ndla/articleapi/controller/InternControllerTest.scala
+++ b/article-api/src/test/scala/no/ndla/articleapi/controller/InternControllerTest.scala
@@ -10,7 +10,7 @@ package no.ndla.articleapi.controller
import no.ndla.articleapi.{TestEnvironment, UnitSuite}
import no.ndla.common.model.domain.article.Article
-import no.ndla.common.model.domain.Author
+import no.ndla.common.model.domain.{Author, ContributorType}
import no.ndla.tapirtesting.TapirControllerTest
import org.mockito.ArgumentMatchers.{any, eq as eqTo}
import org.mockito.Mockito.{doReturn, never, reset, times, verify, verifyNoMoreInteractions, when}
@@ -20,7 +20,7 @@ import sttp.client3.quick.*
import scala.util.{Failure, Success}
class InternControllerTest extends UnitSuite with TestEnvironment with TapirControllerTest {
- val author: Author = Author("forfatter", "Henrik")
+ val author: Author = Author(ContributorType.Writer, "Henrik")
val controller: TapirController = new InternController
diff --git a/article-api/src/test/scala/no/ndla/articleapi/db/migration/V58__FixContributorTypesTest.scala b/article-api/src/test/scala/no/ndla/articleapi/db/migration/V58__FixContributorTypesTest.scala
new file mode 100644
index 0000000000..df0ea52250
--- /dev/null
+++ b/article-api/src/test/scala/no/ndla/articleapi/db/migration/V58__FixContributorTypesTest.scala
@@ -0,0 +1,153 @@
+/*
+ * Part of NDLA article-api
+ * Copyright (C) 2024 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.articleapi.db.migration
+
+import io.circe.parser
+import no.ndla.articleapi.{TestEnvironment, UnitSuite}
+
+class V58__FixContributorTypesTest extends UnitSuite with TestEnvironment {
+ test("That copyright has correct contributor types") {
+ val migration = new V58__FixContributorTypes
+ val oldDocument =
+ """
+ |{
+ | "id": 1,
+ | "tags": [],
+ | "title": [
+ | {
+ | "title": "Title",
+ | "language": "nb"
+ | }
+ | ],
+ | "content": [
+ | {
+ | "content": "",
+ | "language": "nb"
+ | }
+ | ],
+ | "created": "2017-05-29T09:43:41.000Z",
+ | "updated": "2017-07-18T10:21:08.000Z",
+ | "revision": 1,
+ | "copyright": {
+ | "origin": "",
+ | "license": "CC-BY-SA-4.0",
+ | "validTo": null,
+ | "processed": false,
+ | "validFrom": null,
+ | "creators": [
+ | {
+ | "type": "Forfatter",
+ | "name": "Sissel Paaske"
+ | }
+ | ],
+ | "processors": [],
+ | "rightsholders": [
+ | {
+ | "type": "Supplier",
+ | "name": "Cerpus AS"
+ | }
+ | ]
+ | },
+ | "grepCodes": [],
+ | "metaImage": [],
+ | "published": "2017-07-18T10:21:08.000Z",
+ | "updatedBy": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "conceptIds": [],
+ | "disclaimer": {},
+ | "articleType": "standard",
+ | "availability": "everyone",
+ | "introduction": [
+ | {
+ | "language": "nb",
+ | "introduction": "Introduction."
+ | }
+ | ],
+ | "revisionDate": "2030-01-01T00:00:00.000Z",
+ | "visualElement": [],
+ | "relatedContent": [],
+ | "metaDescription": [
+ | {
+ | "content": "Metabeskrivelse",
+ | "language": "nb"
+ | }
+ | ],
+ | "requiredLibraries": []
+ |}
+ |""".stripMargin
+ val newDocument =
+ """
+ |{
+ | "id": 1,
+ | "tags": [],
+ | "title": [
+ | {
+ | "title": "Title",
+ | "language": "nb"
+ | }
+ | ],
+ | "content": [
+ | {
+ | "content": "",
+ | "language": "nb"
+ | }
+ | ],
+ | "created": "2017-05-29T09:43:41.000Z",
+ | "updated": "2017-07-18T10:21:08.000Z",
+ | "revision": 1,
+ | "copyright": {
+ | "origin": "",
+ | "license": "CC-BY-SA-4.0",
+ | "validTo": null,
+ | "processed": false,
+ | "validFrom": null,
+ | "creators": [
+ | {
+ | "type": "writer",
+ | "name": "Sissel Paaske"
+ | }
+ | ],
+ | "processors": [],
+ | "rightsholders": [
+ | {
+ | "type": "supplier",
+ | "name": "Cerpus AS"
+ | }
+ | ]
+ | },
+ | "grepCodes": [],
+ | "metaImage": [],
+ | "published": "2017-07-18T10:21:08.000Z",
+ | "updatedBy": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "conceptIds": [],
+ | "disclaimer": {},
+ | "articleType": "standard",
+ | "availability": "everyone",
+ | "introduction": [
+ | {
+ | "language": "nb",
+ | "introduction": "Introduction."
+ | }
+ | ],
+ | "revisionDate": "2030-01-01T00:00:00.000Z",
+ | "visualElement": [],
+ | "relatedContent": [],
+ | "metaDescription": [
+ | {
+ | "content": "Metabeskrivelse",
+ | "language": "nb"
+ | }
+ | ],
+ | "requiredLibraries": []
+ |}
+ |""".stripMargin
+
+ val expected = parser.parse(newDocument).toTry.get
+ migration.convertColumn(oldDocument) should be(expected.noSpaces)
+ }
+}
diff --git a/article-api/src/test/scala/no/ndla/articleapi/service/ConverterServiceTest.scala b/article-api/src/test/scala/no/ndla/articleapi/service/ConverterServiceTest.scala
index 486c1fb286..6e853ddb6a 100644
--- a/article-api/src/test/scala/no/ndla/articleapi/service/ConverterServiceTest.scala
+++ b/article-api/src/test/scala/no/ndla/articleapi/service/ConverterServiceTest.scala
@@ -17,6 +17,7 @@ import no.ndla.common.model.api.{LicenseDTO, UpdateWith}
import no.ndla.common.model.domain.{
Author,
Availability,
+ ContributorType,
Description,
Introduction,
RequiredLibrary,
@@ -39,7 +40,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment {
val service = new ConverterService
val contentTitle: Title = Title("", "und")
- val author: Author = Author("forfatter", "Henrik")
+ val author: Author = Author(ContributorType.Writer, "Henrik")
val tag: Tag = Tag(List("asdf"), "nb")
val requiredLibrary: RequiredLibrary = RequiredLibrary("", "", "")
val nodeId = "1234"
diff --git a/article-api/src/test/scala/no/ndla/articleapi/service/search/ArticleSearchServiceTest.scala b/article-api/src/test/scala/no/ndla/articleapi/service/search/ArticleSearchServiceTest.scala
index 71fc46ba58..e49ca86bc2 100644
--- a/article-api/src/test/scala/no/ndla/articleapi/service/search/ArticleSearchServiceTest.scala
+++ b/article-api/src/test/scala/no/ndla/articleapi/service/search/ArticleSearchServiceTest.scala
@@ -40,7 +40,7 @@ class ArticleSearchServiceTest
Copyright(
CC_BY_NC_SA.toString,
Some("Gotham City"),
- List(Author("Forfatter", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -52,7 +52,7 @@ class ArticleSearchServiceTest
Copyright(
PublicDomain.toString,
Some("Metropolis"),
- List(Author("Forfatter", "Bruce Wayne")),
+ List(Author(ContributorType.Writer, "Bruce Wayne")),
List(),
List(),
None,
@@ -64,7 +64,7 @@ class ArticleSearchServiceTest
Copyright(
Copyrighted.toString,
Some("New York"),
- List(Author("Forfatter", "Clark Kent")),
+ List(Author(ContributorType.Writer, "Clark Kent")),
List(),
List(),
None,
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 003e6b7709..7030cf7232 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
@@ -14,6 +14,7 @@ import no.ndla.common.model.domain.{
ArticleContent,
ArticleMetaImage,
Author,
+ ContributorType,
Description,
Introduction,
RequiredLibrary,
@@ -206,7 +207,8 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
test("validateArticle throws an exception on an article with an invalid license") {
val article = TestData.sampleArticleWithByNcSa.copy(
- copyright = Copyright("beerware", None, Seq(Author("Writer", "John doe")), Seq(), Seq(), None, None, false)
+ copyright =
+ Copyright("beerware", None, Seq(Author(ContributorType.Writer, "John doe")), Seq(), Seq(), None, None, false)
)
contentValidator.validateArticle(article, false).isFailure should be(true)
}
@@ -214,7 +216,8 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
test("validateArticle does not throw an exception on an article with a valid license") {
val article =
TestData.sampleArticleWithByNcSa.copy(
- copyright = Copyright("CC-BY-SA-4.0", None, Seq(Author("Writer", "test")), Seq(), Seq(), None, None, false)
+ copyright =
+ Copyright("CC-BY-SA-4.0", None, Seq(Author(ContributorType.Writer, "test")), Seq(), Seq(), None, None, false)
)
contentValidator.validateArticle(article, false).isSuccess should be(true)
}
@@ -224,7 +227,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
copyright = Copyright(
"CC-BY-SA-4.0",
Some("
origin
"),
- Seq(Author("Writer", "John Doe")),
+ Seq(Author(ContributorType.Writer, "John Doe")),
Seq(),
Seq(),
None,
@@ -238,43 +241,80 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
test("validateArticle does not throw an exception on an article with plain text in copyright origin") {
val article =
TestData.sampleArticleWithByNcSa.copy(
- copyright = Copyright("CC-BY-SA-4.0", None, Seq(Author("Writer", "John doe")), Seq(), Seq(), None, None, false)
+ copyright = Copyright(
+ "CC-BY-SA-4.0",
+ None,
+ Seq(Author(ContributorType.Writer, "John doe")),
+ Seq(),
+ Seq(),
+ None,
+ None,
+ false
+ )
)
contentValidator.validateArticle(article, false).isSuccess should be(true)
}
test("validateArticle does not throw an exception on an article with plain text in authors field") {
val article = TestData.sampleArticleWithByNcSa.copy(
- copyright = Copyright("CC-BY-SA-4.0", None, Seq(Author("Writer", "John Doe")), Seq(), Seq(), None, None, false)
+ copyright = Copyright(
+ "CC-BY-SA-4.0",
+ None,
+ Seq(Author(ContributorType.Writer, "John Doe")),
+ Seq(),
+ Seq(),
+ None,
+ None,
+ false
+ )
)
contentValidator.validateArticle(article, false).isSuccess should be(true)
}
test("validateArticle throws an exception on an article with html in authors field") {
val article = TestData.sampleArticleWithByNcSa.copy(
- copyright = Copyright("CC-BY-SA", None, Seq(Author("Writer", "john
")), Seq(), Seq(), None, None, false)
+ copyright = Copyright(
+ "CC-BY-SA",
+ None,
+ Seq(Author(ContributorType.Writer, "john
")),
+ Seq(),
+ Seq(),
+ None,
+ None,
+ false
+ )
)
contentValidator.validateArticle(article, false).isFailure should be(true)
}
test("validateArticle does not throw an exception on an article with correct author type") {
val article = TestData.sampleArticleWithByNcSa.copy(
- copyright = Copyright("CC-BY-SA-4.0", None, Seq(Author("Writer", "John Doe")), Seq(), Seq(), None, None, false)
+ copyright = Copyright(
+ "CC-BY-SA-4.0",
+ None,
+ Seq(Author(ContributorType.Writer, "John Doe")),
+ Seq(),
+ Seq(),
+ None,
+ None,
+ false
+ )
)
contentValidator.validateArticle(article, false).isSuccess should be(true)
}
- test("validateArticle throws an exception on an article with invalid author type") {
+ test("validateArticle throws an exception on an article with empty author name") {
val article = TestData.sampleArticleWithByNcSa.copy(
- copyright = Copyright("CC-BY-SA-4.0", None, Seq(Author("invalid", "John Doe")), Seq(), Seq(), None, None, false)
+ copyright =
+ Copyright("CC-BY-SA-4.0", None, Seq(Author(ContributorType.Writer, "")), Seq(), Seq(), None, None, false)
)
val result = contentValidator.validateArticle(article, false)
result.isSuccess should be(false)
result.failed.get.asInstanceOf[ValidationException].errors.length should be(1)
result.failed.get.asInstanceOf[ValidationException].errors.head.message should be(
- "Author is of illegal type. Must be one of originator, photographer, artist, writer, scriptwriter, reader, translator, director, illustrator, cowriter, composer"
+ "This field is shorter than the minimum permitted length of 1 characters"
)
- result.failed.get.asInstanceOf[ValidationException].errors.head.field should be("copyright.creators.type")
+ result.failed.get.asInstanceOf[ValidationException].errors.head.field should be("copyright.creators.name")
}
test("Validation should not fail with language=unknown if allowUnknownLanguage is set") {
@@ -413,8 +453,9 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
}
test("an article with one or more copyright holder can pass validation, regardless of license") {
- val copyright = Copyright(CC_BY_SA.toString, None, Seq(Author("reader", "test")), Seq(), Seq(), None, None, false)
- val article = TestData.sampleArticleWithByNcSa.copy(copyright = copyright)
+ val copyright =
+ Copyright(CC_BY_SA.toString, None, Seq(Author(ContributorType.Reader, "test")), Seq(), Seq(), None, None, false)
+ val article = TestData.sampleArticleWithByNcSa.copy(copyright = copyright)
contentValidator.validateArticle(article, false).isSuccess should be(true)
}
diff --git a/audio-api/src/main/scala/no/ndla/audioapi/AudioApiProperties.scala b/audio-api/src/main/scala/no/ndla/audioapi/AudioApiProperties.scala
index d6a9b63603..bfef2718a5 100644
--- a/audio-api/src/main/scala/no/ndla/audioapi/AudioApiProperties.scala
+++ b/audio-api/src/main/scala/no/ndla/audioapi/AudioApiProperties.scala
@@ -64,37 +64,6 @@ class AudioApiProperties extends BaseProps with DatabaseProps with StrictLogging
val AudioFilesUrlSuffix = "audio/files"
- val creatorTypeMap: Map[String, String] = Map(
- "opphavsmann" -> "originator",
- "fotograf" -> "photographer",
- "kunstner" -> "artist",
- "forfatter" -> "writer",
- "manusforfatter" -> "scriptwriter",
- "innleser" -> "reader",
- "oversetter" -> "translator",
- "regissør" -> "director",
- "illustratør" -> "illustrator",
- "medforfatter" -> "cowriter",
- "komponist" -> "composer"
- )
-
- val processorTypeMap: Map[String, String] = Map(
- "bearbeider" -> "processor",
- "tilrettelegger" -> "facilitator",
- "redaksjonelt" -> "editorial",
- "språklig" -> "linguistic",
- "ide" -> "idea",
- "sammenstiller" -> "compiler",
- "korrektur" -> "correction"
- )
-
- val rightsholderTypeMap: Map[String, String] = Map(
- "rettighetshaver" -> "rightsholder",
- "forlag" -> "publisher",
- "distributør" -> "distributor",
- "leverandør" -> "supplier"
- )
-
lazy val Domain: String = propOrElse("BACKEND_API_DOMAIN", Domains.get(Environment))
lazy val RawImageApiUrl: String = s"$Domain/image-api/raw/id"
diff --git a/audio-api/src/main/scala/no/ndla/audioapi/db/migration/V22__FixContributorTypes.scala b/audio-api/src/main/scala/no/ndla/audioapi/db/migration/V22__FixContributorTypes.scala
new file mode 100644
index 0000000000..1148605a5a
--- /dev/null
+++ b/audio-api/src/main/scala/no/ndla/audioapi/db/migration/V22__FixContributorTypes.scala
@@ -0,0 +1,56 @@
+/*
+ * Part of NDLA audio-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.audioapi.db.migration
+
+import io.circe.{ACursor, parser}
+import io.circe.generic.auto.*
+import io.circe.syntax.EncoderOps
+import no.ndla.common.model.domain.{Author, ContributorType}
+import no.ndla.database.DocumentMigration
+
+class V22__FixContributorTypes extends DocumentMigration {
+ override val tableName: String = "audiodata"
+ override val columnName: String = "document"
+
+ override def convertColumn(value: String): String = {
+ val oldDocument = parser.parse(value).toTry.get
+ val copyright = oldDocument.hcursor.downField("copyright")
+ val creators = convertList(copyright, "creators")
+ val processors = convertList(copyright, "processors")
+ val rightsholders = convertList(copyright, "rightsholders")
+
+ val newDocument = oldDocument.hcursor
+ .downField("copyright")
+ .withFocus(_.mapObject(_.remove("creators").add("creators", creators.asJson)))
+ .withFocus(_.mapObject(_.remove("processors").add("processors", processors.asJson)))
+ .withFocus(_.mapObject(_.remove("rightsholders").add("rightsholders", rightsholders.asJson)))
+ newDocument.top.get.noSpaces
+ }
+
+ private def convertList(cursor: ACursor, fieldName: String): List[Author] = {
+ val field = cursor.downField(fieldName)
+ if (field.succeeded) {
+ field.as[Option[List[OldAuthor]]].toTry.get match {
+ case None => List.empty
+ case Some(authors) => convertAuthors(authors)
+ }
+ } else List.empty
+ }
+
+ private def convertAuthors(authors: List[OldAuthor]): List[Author] = {
+ authors.map(author => {
+ ContributorType.valueOf(author.`type`.toLowerCase) match {
+ case None => Author(ContributorType.mapping(author.`type`.toLowerCase), author.name)
+ case Some(a) => Author(a, author.name)
+ }
+ })
+ }
+}
+
+case class OldAuthor(`type`: String, name: String)
diff --git a/audio-api/src/main/scala/no/ndla/audioapi/service/ConverterService.scala b/audio-api/src/main/scala/no/ndla/audioapi/service/ConverterService.scala
index 62f75c6d2b..602a2c5f6b 100644
--- a/audio-api/src/main/scala/no/ndla/audioapi/service/ConverterService.scala
+++ b/audio-api/src/main/scala/no/ndla/audioapi/service/ConverterService.scala
@@ -8,7 +8,7 @@
package no.ndla.audioapi.service
-import cats.implicits._
+import cats.implicits.*
import com.typesafe.scalalogging.StrictLogging
import no.ndla.audioapi.Props
import no.ndla.audioapi.model.api.{CouldNotFindLanguageException, TagDTO}
@@ -16,7 +16,7 @@ import no.ndla.audioapi.model.domain.{AudioMetaInformation, AudioType, PodcastMe
import no.ndla.audioapi.model.{api, domain}
import no.ndla.common.Clock
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.{NDLADate, api => commonApi, domain => common}
+import no.ndla.common.model.{NDLADate, api as commonApi, domain as common}
import no.ndla.language.Language.findByLanguageOrBestEffort
import no.ndla.language.model.WithLanguage
import no.ndla.mapping.License.getLicense
@@ -25,11 +25,11 @@ import no.ndla.network.tapir.auth.TokenUser
import scala.util.{Failure, Success, Try}
trait ConverterService {
- this: Clock with Props =>
+ this: Clock & Props =>
val converterService: ConverterService
class ConverterService extends StrictLogging {
- import props._
+ import props.*
def updateSeries(existingSeries: domain.Series, updatedSeries: api.NewSeriesDTO): domain.Series = {
val newTitle = common.Title(updatedSeries.title, updatedSeries.language)
diff --git a/audio-api/src/main/scala/no/ndla/audioapi/service/ValidationService.scala b/audio-api/src/main/scala/no/ndla/audioapi/service/ValidationService.scala
index fcf6344791..8cb6f1e891 100644
--- a/audio-api/src/main/scala/no/ndla/audioapi/service/ValidationService.scala
+++ b/audio-api/src/main/scala/no/ndla/audioapi/service/ValidationService.scala
@@ -8,13 +8,13 @@
package no.ndla.audioapi.service
-import cats.implicits._
+import cats.implicits.*
import no.ndla.audioapi.Props
import no.ndla.audioapi.model.domain
-import no.ndla.audioapi.model.domain._
+import no.ndla.audioapi.model.domain.*
import no.ndla.common.errors.{ValidationException, ValidationMessage}
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag, Title, UploadedFile}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title, UploadedFile}
import no.ndla.language.model.Iso639
import no.ndla.mapping.License.getLicense
import org.jsoup.Jsoup
@@ -26,11 +26,10 @@ import javax.imageio.ImageIO
import scala.util.{Failure, Success, Try}
trait ValidationService {
- this: ConverterService with Props =>
+ this: ConverterService & Props =>
val validationService: ValidationService
class ValidationService {
- import props._
def validatePodcastEpisodes(
episodes: Seq[(Long, Option[AudioMetaInformation])],
@@ -190,7 +189,7 @@ trait ValidationService {
ImageIO.read(url)
}
- def validatePodcastCoverPhoto(fieldName: String, coverPhoto: domain.CoverPhoto): Seq[ValidationMessage] = {
+ private def validatePodcastCoverPhoto(fieldName: String, coverPhoto: domain.CoverPhoto): Seq[ValidationMessage] = {
val imageUrl = converterService.getPhotoUrl(coverPhoto)
val image = readImage(imageUrl)
val imageHeight = image.getHeight
@@ -282,21 +281,25 @@ trait ValidationService {
Some(copyright.license),
copyright.rightsholders ++ copyright.processors ++ copyright.creators
) ++
- copyright.creators.flatMap(a => validateAuthor("copyright.creators", a, creatorTypeMap.values.toList)) ++
- copyright.processors.flatMap(a => validateAuthor("copyright.processors", a, processorTypeMap.values.toList)) ++
+ copyright.creators.flatMap(a => validateAuthor("copyright.creators", a, ContributorType.creators)) ++
+ copyright.processors.flatMap(a => validateAuthor("copyright.processors", a, ContributorType.processors)) ++
copyright.rightsholders.flatMap(a =>
- validateAuthor("copyright.rightsholders", a, rightsholderTypeMap.values.toList)
+ validateAuthor("copyright.rightsholders", a, ContributorType.rightsholders)
) ++
copyright.origin.flatMap(origin => containsNoHtml("copyright.origin", origin))
}
- def validateLicense(license: String): Seq[ValidationMessage] = {
+ private def validateLicense(license: String): Seq[ValidationMessage] = {
getLicense(license) match {
case None => Seq(ValidationMessage("license.license", s"$license is not a valid license"))
case _ => Seq()
}
}
- private def validateAuthorLicenseCorrelation(license: Option[String], authors: Seq[Author]) = {
+
+ private def validateAuthorLicenseCorrelation(
+ license: Option[String],
+ authors: Seq[Author]
+ ): Seq[ValidationMessage] = {
val errorMessage = (lic: String) =>
ValidationMessage("license.license", s"At least one copyright holder is required when license is $lic")
license match {
@@ -305,14 +308,22 @@ trait ValidationService {
}
}
- def validateAuthor(fieldPath: String, author: Author, allowedTypes: Seq[String]): Seq[ValidationMessage] = {
- containsNoHtml(s"$fieldPath.type", author.`type`).toList ++
- containsNoHtml(s"$fieldPath.name", author.name).toList ++
- validateAuthorType(fieldPath, author.`type`, allowedTypes).toList
+ private def validateAuthor(
+ fieldPath: String,
+ author: Author,
+ allowedTypes: Seq[ContributorType]
+ ): Seq[ValidationMessage] = {
+ containsNoHtml(s"$fieldPath.name", author.name).toList ++
+ validateAuthorType(s"$fieldPath.type", author.`type`, allowedTypes).toList ++
+ validateMinimumLength(s"$fieldPath.name", author.name, 1)
}
- def validateAuthorType(fieldPath: String, `type`: String, allowedTypes: Seq[String]): Option[ValidationMessage] = {
- if (allowedTypes.contains(`type`.toLowerCase)) {
+ private def validateAuthorType(
+ fieldPath: String,
+ `type`: ContributorType,
+ allowedTypes: Seq[ContributorType]
+ ): Option[ValidationMessage] = {
+ if (allowedTypes.contains(`type`)) {
None
} else {
Some(ValidationMessage(fieldPath, s"Author is of illegal type. Must be one of ${allowedTypes.mkString(", ")}"))
@@ -348,7 +359,7 @@ trait ValidationService {
private def languageCodeSupported639(languageCode: String): Boolean = Iso639.get(languageCode).isSuccess
- def validateNonEmpty(fieldPath: String, sequence: Seq[Any]): Option[ValidationMessage] = {
+ private def validateNonEmpty(fieldPath: String, sequence: Seq[Any]): Option[ValidationMessage] = {
if (sequence.nonEmpty) {
None
} else {
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/TestData.scala b/audio-api/src/test/scala/no/ndla/audioapi/TestData.scala
index d5fb6d3816..3b4434b2fc 100644
--- a/audio-api/src/test/scala/no/ndla/audioapi/TestData.scala
+++ b/audio-api/src/test/scala/no/ndla/audioapi/TestData.scala
@@ -12,6 +12,7 @@ import no.ndla.audioapi.model.Sort
import no.ndla.audioapi.model.domain.{AudioMetaInformation, AudioType, SearchSettings}
import no.ndla.audioapi.model.domain
import no.ndla.audioapi.model.api
+import no.ndla.common.model.domain.ContributorType
import no.ndla.common.model.domain.article.Copyright
import no.ndla.common.model.{NDLADate, api as commonApi, domain as common}
import no.ndla.mapping.License
@@ -39,9 +40,9 @@ object TestData {
val sampleCopyright: Copyright = Copyright(
license = "CC-BY-4.0",
origin = Some("origin"),
- creators = Seq(common.Author("originator", "ole")),
- processors = Seq(common.Author("processor", "dole")),
- rightsholders = Seq(common.Author("rightsholder", "doffen")),
+ creators = Seq(common.Author(ContributorType.Originator, "ole")),
+ processors = Seq(common.Author(ContributorType.Processor, "dole")),
+ rightsholders = Seq(common.Author(ContributorType.RightsHolder, "doffen")),
validFrom = None,
validTo = None,
false
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/controller/AudioControllerTest.scala b/audio-api/src/test/scala/no/ndla/audioapi/controller/AudioControllerTest.scala
index 3ed8361f78..7b07edc8d9 100644
--- a/audio-api/src/test/scala/no/ndla/audioapi/controller/AudioControllerTest.scala
+++ b/audio-api/src/test/scala/no/ndla/audioapi/controller/AudioControllerTest.scala
@@ -15,6 +15,7 @@ import no.ndla.audioapi.model.{api, domain}
import no.ndla.audioapi.{TestData, TestEnvironment, UnitSuite}
import no.ndla.common.CirceUtil.unsafeParseAs
import no.ndla.common.model.api.{CopyrightDTO, LicenseDTO}
+import no.ndla.mapping.License
import no.ndla.tapirtesting.TapirControllerTest
import org.mockito.ArgumentMatchers.{eq as eqTo, *}
import org.mockito.ArgumentCaptor
@@ -66,14 +67,14 @@ class AudioControllerTest extends UnitSuite with TestEnvironment with Retries wi
val fileBody: Array[Byte] = Array[Byte](0x49, 0x44, 0x33)
val sampleNewAudioMeta: String =
- """
+ s"""
|{
| "title": "Test",
| "language": "nb",
| "audioFile": "test.mp3",
| "copyright": {
| "license": {
- | "license": "by-sa"
+ | "license": "${License.CC_BY_SA.toString}"
| },
| "origin": "",
| "creators": [],
@@ -115,7 +116,7 @@ class AudioControllerTest extends UnitSuite with TestEnvironment with Retries wi
1,
TitleDTO("title", "nb"),
AudioDTO("", "", -1, "nb"),
- CopyrightDTO(LicenseDTO("by", None, None), None, Seq(), Seq(), Seq(), None, None, false),
+ CopyrightDTO(LicenseDTO(License.CC_BY.toString, None, None), None, Seq(), Seq(), Seq(), None, None, false),
TagDTO(Seq(), "nb"),
Seq("nb"),
"podcast",
@@ -142,7 +143,7 @@ class AudioControllerTest extends UnitSuite with TestEnvironment with Retries wi
test("That POST / returns 500 if an unexpected error occurs") {
val runtimeMock = mock[RuntimeException](withSettings.strictness(Strictness.LENIENT))
- doNothing.when(runtimeMock).printStackTrace()
+ doNothing().when(runtimeMock).printStackTrace()
when(runtimeMock.getMessage).thenReturn("Something (not really) wrong (this is a test hehe)")
when(writeService.storeNewAudio(any[NewAudioMetaInformationDTO], any, any))
@@ -292,7 +293,7 @@ class AudioControllerTest extends UnitSuite with TestEnvironment with Retries wi
}
test("That deleting language returns audio if exists") {
- import io.circe.generic.auto._
+ import io.circe.generic.auto.*
when(writeService.deleteAudioLanguageVersion(1, "nb"))
.thenReturn(Success(Some(TestData.DefaultApiImageMetaInformation)))
@@ -329,7 +330,7 @@ class AudioControllerTest extends UnitSuite with TestEnvironment with Retries wi
1,
TitleDTO("one", "nb"),
AudioDTO("", "", -1, "nb"),
- CopyrightDTO(LicenseDTO("by", None, None), None, Seq(), Seq(), Seq(), None, None, false),
+ CopyrightDTO(LicenseDTO(License.CC_BY.toString, None, None), None, Seq(), Seq(), Seq(), None, None, false),
TagDTO(Seq(), "nb"),
Seq("nb"),
"podcast",
@@ -351,7 +352,7 @@ class AudioControllerTest extends UnitSuite with TestEnvironment with Retries wi
.get(uri"http://localhost:$serverPort/audio-api/v1/audio/ids/?ids=1,2,3")
)
response.code.code should be(200)
- import io.circe.generic.auto._
+ import io.circe.generic.auto.*
val parsedBody = unsafeParseAs[List[api.AudioMetaInformationDTO]](response.body)
parsedBody should be(expectedResult)
@@ -388,7 +389,7 @@ class AudioControllerTest extends UnitSuite with TestEnvironment with Retries wi
1,
TitleDTO("title", "nb"),
AudioDTO("", "", -1, "nb"),
- CopyrightDTO(LicenseDTO("by", None, None), None, Seq(), Seq(), Seq(), None, None, false),
+ CopyrightDTO(LicenseDTO(License.CC_BY.toString, None, None), None, Seq(), Seq(), Seq(), None, None, false),
TagDTO(Seq(), "nb"),
Seq("nb"),
"podcast",
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/controller/HealthControllerTest.scala b/audio-api/src/test/scala/no/ndla/audioapi/controller/HealthControllerTest.scala
index f2273e629b..cf3e79b8ec 100644
--- a/audio-api/src/test/scala/no/ndla/audioapi/controller/HealthControllerTest.scala
+++ b/audio-api/src/test/scala/no/ndla/audioapi/controller/HealthControllerTest.scala
@@ -13,7 +13,7 @@ import no.ndla.audioapi.model.domain.*
import no.ndla.audioapi.{TestEnvironment, UnitSuite}
import no.ndla.common.model.NDLADate
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title}
import no.ndla.mapping.License
import no.ndla.tapirtesting.TapirControllerTest
import org.mockito.Mockito.when
@@ -35,7 +35,7 @@ class HealthControllerTest extends UnitSuite with TestEnvironment with TapirCont
Copyright(
License.Copyrighted.toString,
Some("New York"),
- Seq(Author("Forfatter", "Clark Kent")),
+ Seq(Author(ContributorType.Writer, "Clark Kent")),
Seq(),
Seq(),
None,
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/db/migration/V22__FixContributorTypesTest.scala b/audio-api/src/test/scala/no/ndla/audioapi/db/migration/V22__FixContributorTypesTest.scala
new file mode 100644
index 0000000000..dede562694
--- /dev/null
+++ b/audio-api/src/test/scala/no/ndla/audioapi/db/migration/V22__FixContributorTypesTest.scala
@@ -0,0 +1,125 @@
+/*
+ * Part of NDLA audio-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.audioapi.db.migration
+
+import io.circe.parser
+import no.ndla.audioapi.{TestEnvironment, UnitSuite}
+
+class V22__FixContributorTypesTest extends UnitSuite with TestEnvironment {
+ test("That copyright has correct contributor types") {
+ val migration = new V22__FixContributorTypes
+ val oldDocument =
+ """
+ |{
+ | "tags": [
+ | {
+ | "tags": [
+ | "testtag",
+ | "testtttt"
+ | ],
+ | "language": "nb"
+ | }
+ | ],
+ | "titles": [
+ | {
+ | "title": "test",
+ | "language": "nb"
+ | }
+ | ],
+ | "created": "2023-10-04T20:30:18.167Z",
+ | "updated": "2023-10-04T20:30:18.167Z",
+ | "audioType": "standard",
+ | "copyright": {
+ | "license": "CC-BY-SA-4.0",
+ | "processed": true,
+ | "creators": [
+ | {
+ | "type": "Komponist",
+ | "name": "Apekatt"
+ | }
+ | ],
+ | "processors": [],
+ | "rightsholders": [
+ | {
+ | "type": "Supplier",
+ | "name": "NRK"
+ | }
+ | ]
+ | },
+ | "filePaths": [
+ | {
+ | "filePath": "T8BBtfD.mp3\"",
+ | "fileSize": 6289221,
+ | "language": "nb",
+ | "mimeType": "audio/mpeg"
+ | }
+ | ],
+ | "updatedBy": "fsexOCfJFGOKuy1C2e71OsvQwq0NWKAK",
+ | "manuscript": [],
+ | "podcastMeta": [],
+ | "supportedLanguages": null
+ |}
+ |""".stripMargin
+ val newDocument =
+ """
+ |{
+ | "tags": [
+ | {
+ | "tags": [
+ | "testtag",
+ | "testtttt"
+ | ],
+ | "language": "nb"
+ | }
+ | ],
+ | "titles": [
+ | {
+ | "title": "test",
+ | "language": "nb"
+ | }
+ | ],
+ | "created": "2023-10-04T20:30:18.167Z",
+ | "updated": "2023-10-04T20:30:18.167Z",
+ | "audioType": "standard",
+ | "copyright": {
+ | "license": "CC-BY-SA-4.0",
+ | "processed": true,
+ | "creators": [
+ | {
+ | "type": "composer",
+ | "name": "Apekatt"
+ | }
+ | ],
+ | "processors": [],
+ | "rightsholders": [
+ | {
+ | "type": "supplier",
+ | "name": "NRK"
+ | }
+ | ]
+ | },
+ | "filePaths": [
+ | {
+ | "filePath": "T8BBtfD.mp3\"",
+ | "fileSize": 6289221,
+ | "language": "nb",
+ | "mimeType": "audio/mpeg"
+ | }
+ | ],
+ | "updatedBy": "fsexOCfJFGOKuy1C2e71OsvQwq0NWKAK",
+ | "manuscript": [],
+ | "podcastMeta": [],
+ | "supportedLanguages": null
+ |}
+ |""".stripMargin
+
+ val expected = parser.parse(newDocument).toTry.get
+ migration.convertColumn(oldDocument) should be(expected.noSpaces)
+ }
+}
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/service/ConverterServiceTest.scala b/audio-api/src/test/scala/no/ndla/audioapi/service/ConverterServiceTest.scala
index f97fa6d6d1..a3bc2d4436 100644
--- a/audio-api/src/test/scala/no/ndla/audioapi/service/ConverterServiceTest.scala
+++ b/audio-api/src/test/scala/no/ndla/audioapi/service/ConverterServiceTest.scala
@@ -12,7 +12,7 @@ import no.ndla.audioapi.model.domain.*
import no.ndla.audioapi.model.{api, domain}
import no.ndla.audioapi.{TestEnvironment, UnitSuite}
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title}
import no.ndla.common.model.{NDLADate, api as commonApi, domain as common}
import no.ndla.mapping.License
import no.ndla.mapping.License.CC_BY_SA
@@ -29,7 +29,7 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment {
Copyright(
License.Copyrighted.toString,
Some("New York"),
- Seq(Author("Forfatter", "Clark Kent")),
+ Seq(Author(ContributorType.Writer, "Clark Kent")),
Seq(),
Seq(),
None,
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/service/ValidationServiceTest.scala b/audio-api/src/test/scala/no/ndla/audioapi/service/ValidationServiceTest.scala
index 9e39fae058..680443707c 100644
--- a/audio-api/src/test/scala/no/ndla/audioapi/service/ValidationServiceTest.scala
+++ b/audio-api/src/test/scala/no/ndla/audioapi/service/ValidationServiceTest.scala
@@ -12,7 +12,7 @@ import no.ndla.audioapi.model.domain.{AudioType, CoverPhoto, PodcastMeta}
import no.ndla.audioapi.{TestEnvironment, UnitSuite}
import no.ndla.common.errors.ValidationMessage
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag}
import no.ndla.mapping.License.CC_BY
import org.mockito.Mockito.{doReturn, reset, spy, when}
@@ -87,7 +87,16 @@ class ValidationServiceTest extends UnitSuite with TestEnvironment {
test("validateCopyright should succeed if a copyright holder is provided") {
val copyright =
- Copyright(CC_BY.toString, None, Seq(Author("artist", "test")), Seq.empty, Seq.empty, None, None, false)
+ Copyright(
+ CC_BY.toString,
+ None,
+ Seq(Author(ContributorType.Artist, "test")),
+ Seq.empty,
+ Seq.empty,
+ None,
+ None,
+ false
+ )
val result = validationService.validateCopyright(copyright)
result.length should be(0)
}
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/service/WriteServiceTest.scala b/audio-api/src/test/scala/no/ndla/audioapi/service/WriteServiceTest.scala
index 97f358ec82..fcff0bfcd4 100644
--- a/audio-api/src/test/scala/no/ndla/audioapi/service/WriteServiceTest.scala
+++ b/audio-api/src/test/scala/no/ndla/audioapi/service/WriteServiceTest.scala
@@ -16,7 +16,7 @@ import no.ndla.audioapi.{TestData, TestEnvironment, UnitSuite}
import no.ndla.common.errors.{NotFoundException, ValidationException, ValidationMessage}
import no.ndla.common.model
import no.ndla.common.model.api.{CopyrightDTO, LicenseDTO}
-import no.ndla.common.model.domain.UploadedFile
+import no.ndla.common.model.domain.{ContributorType, UploadedFile}
import no.ndla.common.model.{NDLADate, domain as common}
import no.ndla.mapping.License
import org.mockito.ArgumentMatchers.{any, eq as eqTo}
@@ -71,7 +71,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
val publicDomain: common.article.Copyright = common.article.Copyright(
License.PublicDomain.toString,
Some("Metropolis"),
- List(common.Author("Forfatter", "Bruce Wayne")),
+ List(common.Author(ContributorType.Writer, "Bruce Wayne")),
Seq(),
Seq(),
None,
@@ -599,7 +599,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
val updated = writeService.deleteAudioLanguageVersion(audioId, "nn")
verify(audioRepository, times(1)).update(expectedAudio, audioId)
- updated.get.head.supportedLanguages should not contain ("nn")
+ updated.get.head.supportedLanguages should not contain "nn"
}
test("That deleting last language version deletes entire image") {
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/service/search/AudioSearchServiceTest.scala b/audio-api/src/test/scala/no/ndla/audioapi/service/search/AudioSearchServiceTest.scala
index 691d0bc6a9..51d1e4a75d 100644
--- a/audio-api/src/test/scala/no/ndla/audioapi/service/search/AudioSearchServiceTest.scala
+++ b/audio-api/src/test/scala/no/ndla/audioapi/service/search/AudioSearchServiceTest.scala
@@ -14,7 +14,7 @@ import no.ndla.audioapi.model.{Sort, domain}
import no.ndla.audioapi.{TestData, TestEnvironment, UnitSuite}
import no.ndla.common.model.NDLADate
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title}
import no.ndla.mapping.License
import no.ndla.scalatestsuite.IntegrationSuite
import org.mockito.ArgumentMatchers.any
@@ -26,7 +26,7 @@ class AudioSearchServiceTest
extends IntegrationSuite(EnableElasticsearchContainer = true)
with UnitSuite
with TestEnvironment {
- import props._
+ import props.*
e4sClient = Elastic4sClientFactory.getClient(elasticSearchHost.getOrElse("http://localhost:9200"))
@@ -43,7 +43,7 @@ class AudioSearchServiceTest
Copyright(
License.CC_BY_NC_SA.toString,
Some("Gotham City"),
- List(Author("Forfatter", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
Seq(),
Seq(),
None,
@@ -54,7 +54,7 @@ class AudioSearchServiceTest
val publicDomain: Copyright = Copyright(
License.PublicDomain.toString,
Some("Metropolis"),
- List(Author("Forfatter", "Bruce Wayne")),
+ List(Author(ContributorType.Writer, "Bruce Wayne")),
Seq(),
Seq(),
None,
@@ -66,7 +66,7 @@ class AudioSearchServiceTest
Copyright(
License.Copyrighted.toString,
Some("New York"),
- List(Author("Forfatter", "Clark Kent")),
+ List(Author(ContributorType.Writer, "Clark Kent")),
Seq(),
Seq(),
None,
@@ -585,7 +585,7 @@ class AudioSearchServiceTest
)
)
result1.results.map(_.id) should be(Seq(2, 3, 4, 5, 6, 7))
- result1.results(0).title.language should be("nb")
+ result1.results.head.title.language should be("nb")
result1.results(1).title.language should be("nb")
result1.results(2).title.language should be("en")
result1.results(3).title.language should be("nb")
@@ -601,7 +601,7 @@ class AudioSearchServiceTest
)
)
result2.results.map(_.id) should be(Seq(2, 3, 4, 5, 6, 7))
- result2.results(0).title.language should be("nb")
+ result2.results.head.title.language should be("nb")
result2.results(1).title.language should be("nb")
result2.results(2).title.language should be("nb")
result2.results(3).title.language should be("nb")
diff --git a/audio-api/src/test/scala/no/ndla/audioapi/service/search/SearchConverterServiceTest.scala b/audio-api/src/test/scala/no/ndla/audioapi/service/search/SearchConverterServiceTest.scala
index be08595f0b..cf9841f5d5 100644
--- a/audio-api/src/test/scala/no/ndla/audioapi/service/search/SearchConverterServiceTest.scala
+++ b/audio-api/src/test/scala/no/ndla/audioapi/service/search/SearchConverterServiceTest.scala
@@ -14,7 +14,7 @@ import no.ndla.audioapi.model.{api, domain}
import no.ndla.audioapi.{TestEnvironment, UnitSuite}
import no.ndla.common.model.NDLADate
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title}
import no.ndla.mapping.License
import no.ndla.search.model.{SearchableLanguageList, SearchableLanguageValues}
@@ -26,7 +26,7 @@ class SearchConverterServiceTest extends UnitSuite with TestEnvironment {
Copyright(
License.CC_BY_NC_SA.toString,
Some("Gotham City"),
- List(Author("Forfatter", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
Seq(),
Seq(),
None,
diff --git a/common/src/main/scala/no/ndla/common/model/api/AuthorDTO.scala b/common/src/main/scala/no/ndla/common/model/api/AuthorDTO.scala
index 506299d513..eb994141e6 100644
--- a/common/src/main/scala/no/ndla/common/model/api/AuthorDTO.scala
+++ b/common/src/main/scala/no/ndla/common/model/api/AuthorDTO.scala
@@ -11,12 +11,15 @@ package no.ndla.common.model.api
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
import no.ndla.common.model.domain
+import no.ndla.common.model.domain.ContributorType
import sttp.tapir.Schema.annotations.description
@description("Information about an author")
case class AuthorDTO(
- @description("The description of the author. Eg. Photographer or Supplier") `type`: String,
- @description("The name of the of the author") name: String
+ @description("The description of the author. Eg. Photographer or Supplier")
+ `type`: ContributorType,
+ @description("The name of the of the author")
+ name: String
) {
def toDomain: domain.Author = domain.Author(
`type` = this.`type`,
diff --git a/common/src/main/scala/no/ndla/common/model/domain/Author.scala b/common/src/main/scala/no/ndla/common/model/domain/Author.scala
index 8e37a06134..5b860a5b5b 100644
--- a/common/src/main/scala/no/ndla/common/model/domain/Author.scala
+++ b/common/src/main/scala/no/ndla/common/model/domain/Author.scala
@@ -12,7 +12,7 @@ import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import no.ndla.common.model.api
-case class Author(`type`: String, name: String) {
+case class Author(`type`: ContributorType, name: String) {
def toApi: api.AuthorDTO = api.AuthorDTO(
`type` = this.`type`,
name = this.name
diff --git a/common/src/main/scala/no/ndla/common/model/domain/ContributorType.scala b/common/src/main/scala/no/ndla/common/model/domain/ContributorType.scala
new file mode 100644
index 0000000000..b5d4d03f13
--- /dev/null
+++ b/common/src/main/scala/no/ndla/common/model/domain/ContributorType.scala
@@ -0,0 +1,105 @@
+/*
+ * Part of NDLA common
+ * Copyright (C) 2022 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.common.model.domain
+
+import com.scalatsi.{TSNamedType, TSType}
+import com.scalatsi.TypescriptType.{TSLiteralString, TSUnion}
+import enumeratum.*
+import no.ndla.common.CirceUtil.CirceEnumWithErrors
+import no.ndla.common.errors.ValidationException
+import sttp.tapir.Schema
+import sttp.tapir.codec.enumeratum.*
+
+sealed abstract class ContributorType(override val entryName: String) extends EnumEntry
+
+object ContributorType extends Enum[ContributorType] with CirceEnumWithErrors[ContributorType] {
+ case object Artist extends ContributorType("artist")
+ case object CoWriter extends ContributorType("cowriter")
+ case object Compiler extends ContributorType("compiler")
+ case object Composer extends ContributorType("composer")
+ case object Correction extends ContributorType("correction")
+ case object Director extends ContributorType("director")
+ case object Distributor extends ContributorType("distributor")
+ case object Editorial extends ContributorType("editorial")
+ case object Facilitator extends ContributorType("facilitator")
+ case object Idea extends ContributorType("idea")
+ case object Illustrator extends ContributorType("illustrator")
+ case object Linguistic extends ContributorType("linguistic")
+ case object Originator extends ContributorType("originator")
+ case object Photographer extends ContributorType("photographer")
+ case object Processor extends ContributorType("processor")
+ case object Publisher extends ContributorType("publisher")
+ case object Reader extends ContributorType("reader")
+ case object RightsHolder extends ContributorType("rightsholder")
+ case object ScriptWriter extends ContributorType("scriptwriter")
+ case object Supplier extends ContributorType("supplier")
+ case object Translator extends ContributorType("translator")
+ case object Writer extends ContributorType("writer")
+
+ override def values: IndexedSeq[ContributorType] = findValues
+
+ def all: Seq[String] = ContributorType.values.map(_.entryName)
+ def valueOf(s: String): Option[ContributorType] = ContributorType.withNameOption(s)
+
+ def valueOfOrError(s: String): ContributorType =
+ valueOf(s).getOrElse(
+ throw ValidationException(
+ "articleType",
+ s"'$s' is not a valid article type. Valid options are ${all.mkString(",")}."
+ )
+ )
+
+ def creators: Seq[ContributorType] = Seq(
+ Artist,
+ CoWriter,
+ Composer,
+ Director,
+ Illustrator,
+ Originator,
+ Photographer,
+ Reader,
+ ScriptWriter,
+ Translator,
+ Writer
+ )
+ def processors: Seq[ContributorType] = Seq(Compiler, Correction, Editorial, Facilitator, Idea, Linguistic, Processor)
+ def rightsholders: Seq[ContributorType] = Seq(Distributor, Publisher, RightsHolder, Supplier)
+ def contributors: Seq[ContributorType] = creators ++ processors ++ rightsholders
+
+ // TODO: Remove when all data are converted
+ val mapping: Map[String, ContributorType] = Map(
+ "bearbeider" -> ContributorType.Processor,
+ "distributør" -> ContributorType.Distributor,
+ "forfatter" -> ContributorType.Writer,
+ "forlag" -> ContributorType.Publisher,
+ "fotograf" -> ContributorType.Photographer,
+ "ide" -> ContributorType.Idea,
+ "illustratør" -> ContributorType.Illustrator,
+ "innleser" -> ContributorType.Reader,
+ "komponist" -> ContributorType.Composer,
+ "korrektur" -> ContributorType.Correction,
+ "kunstner" -> ContributorType.Artist,
+ "leverandør" -> ContributorType.Supplier,
+ "manusforfatter" -> ContributorType.ScriptWriter,
+ "medforfatter" -> ContributorType.CoWriter,
+ "opphaver" -> ContributorType.Originator,
+ "opphavsmann" -> ContributorType.Originator,
+ "oversetter" -> ContributorType.Translator,
+ "redaksjonelt" -> ContributorType.Editorial,
+ "regissør" -> ContributorType.Director,
+ "rettighetsholder" -> ContributorType.RightsHolder,
+ "sammenstiller" -> ContributorType.Compiler,
+ "språklig" -> ContributorType.Linguistic,
+ "tilrettelegger" -> ContributorType.Facilitator
+ ).withDefaultValue(ContributorType.Writer)
+
+ implicit def schema: Schema[ContributorType] = schemaForEnumEntry[ContributorType]
+ implicit val enumTsType: TSNamedType[ContributorType] =
+ TSType.alias[ContributorType]("ContributorType", TSUnion(values.map(e => TSLiteralString(e.entryName))))
+}
diff --git a/concept-api/src/main/scala/no/ndla/conceptapi/db/migration/V27__FixContributorTypes.scala b/concept-api/src/main/scala/no/ndla/conceptapi/db/migration/V27__FixContributorTypes.scala
new file mode 100644
index 0000000000..876e8825af
--- /dev/null
+++ b/concept-api/src/main/scala/no/ndla/conceptapi/db/migration/V27__FixContributorTypes.scala
@@ -0,0 +1,59 @@
+/*
+ * Part of NDLA concept-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.conceptapi.db.migration
+
+import io.circe.{ACursor, parser}
+import io.circe.generic.auto.*
+import io.circe.syntax.EncoderOps
+import no.ndla.common.model.domain.{Author, ContributorType}
+import no.ndla.database.DocumentMigration
+
+class V27__FixContributorTypes extends DocumentMigration {
+ override val tableName: String = "conceptdata"
+ override val columnName: String = "document"
+
+ override def convertColumn(value: String): String = {
+ val oldDocument = parser.parse(value).toTry.get
+ val copyright = oldDocument.hcursor.downField("copyright")
+ val creators = convertList(copyright, "creators")
+ val processors = convertList(copyright, "processors")
+ val rightsholders = convertList(copyright, "rightsholders")
+
+ val newDocument =
+ if (copyright.succeeded)
+ oldDocument.hcursor
+ .downField("copyright")
+ .withFocus(_.mapObject(_.remove("creators").add("creators", creators.asJson)))
+ .withFocus(_.mapObject(_.remove("processors").add("processors", processors.asJson)))
+ .withFocus(_.mapObject(_.remove("rightsholders").add("rightsholders", rightsholders.asJson)))
+ else oldDocument.hcursor
+ newDocument.top.getOrElse(oldDocument).noSpaces
+ }
+
+ private def convertList(cursor: ACursor, fieldName: String): List[Author] = {
+ val field = cursor.downField(fieldName)
+ if (field.succeeded) {
+ field.as[Option[List[OldAuthor]]].toTry.get match {
+ case None => List.empty
+ case Some(authors) => convertAuthors(authors)
+ }
+ } else List.empty
+ }
+
+ private def convertAuthors(authors: List[OldAuthor]): List[Author] = {
+ authors.map(author => {
+ ContributorType.valueOf(author.`type`.toLowerCase) match {
+ case None => Author(ContributorType.mapping(author.`type`.toLowerCase), author.name)
+ case Some(a) => Author(a, author.name)
+ }
+ })
+ }
+}
+
+case class OldAuthor(`type`: String, name: String)
diff --git a/concept-api/src/main/scala/no/ndla/conceptapi/db/migration/V28__FixContributorTypesPublished.scala b/concept-api/src/main/scala/no/ndla/conceptapi/db/migration/V28__FixContributorTypesPublished.scala
new file mode 100644
index 0000000000..4c52c43bed
--- /dev/null
+++ b/concept-api/src/main/scala/no/ndla/conceptapi/db/migration/V28__FixContributorTypesPublished.scala
@@ -0,0 +1,18 @@
+/*
+ * Part of NDLA concept-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.conceptapi.db.migration
+
+class V28__FixContributorTypesPublished extends V27__FixContributorTypes {
+ override val tableName: String = "publishedconceptdata"
+ override val columnName: String = "document"
+
+ override def convertColumn(value: String): String = {
+ super.convertColumn(value)
+ }
+}
diff --git a/concept-api/src/main/scala/no/ndla/conceptapi/validation/ContentValidator.scala b/concept-api/src/main/scala/no/ndla/conceptapi/validation/ContentValidator.scala
index 165028426e..928163c904 100644
--- a/concept-api/src/main/scala/no/ndla/conceptapi/validation/ContentValidator.scala
+++ b/concept-api/src/main/scala/no/ndla/conceptapi/validation/ContentValidator.scala
@@ -8,7 +8,7 @@
package no.ndla.conceptapi.validation
-import no.ndla.common.model.domain.{Author, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Title}
import no.ndla.common.errors.{ValidationException, ValidationMessage}
import no.ndla.common.model.domain.concept.{Concept, ConceptContent, ConceptMetaImage, ConceptStatus, VisualElement}
import no.ndla.common.model.domain.draft.DraftCopyright
@@ -93,8 +93,11 @@ trait ContentValidator {
val licenseMessage = copyright.license.map(validateLicense).toSeq.flatten
val allAuthors = copyright.creators ++ copyright.processors ++ copyright.rightsholders
val licenseCorrelationMessage = validateAuthorLicenseCorrelation(copyright.license, allAuthors)
- val contributorsMessages = copyright.creators.flatMap(validateAuthor) ++ copyright.processors
- .flatMap(validateAuthor) ++ copyright.rightsholders.flatMap(validateAuthor)
+ val contributorsMessages =
+ copyright.creators.flatMap(a => validateAuthor(a, ContributorType.creators)) ++ copyright.processors
+ .flatMap(a => validateAuthor(a, ContributorType.processors)) ++ copyright.rightsholders.flatMap(a =>
+ validateAuthor(a, ContributorType.rightsholders)
+ )
val originMessage =
copyright.origin
.map(origin => TextValidator.validate("copyright.origin", origin, Set.empty))
@@ -124,9 +127,22 @@ trait ContentValidator {
}
}
- private def validateAuthor(author: Author): Seq[ValidationMessage] = {
- TextValidator.validate("author.type", author.`type`, Set.empty).toList ++
- TextValidator.validate("author.name", author.name, Set.empty).toList
+ private def validateAuthor(author: Author, allowedTypes: Seq[ContributorType]): Seq[ValidationMessage] = {
+ TextValidator.validate("author.name", author.name, Set.empty).toList ++
+ validateAuthorType("author.type", author.`type`, allowedTypes) ++
+ validateMinimumLength("author.name", author.name, 1)
+ }
+
+ private def validateAuthorType(
+ fieldPath: String,
+ `type`: ContributorType,
+ allowedTypes: Seq[ContributorType]
+ ): Option[ValidationMessage] = {
+ if (allowedTypes.contains(`type`)) {
+ None
+ } else {
+ Some(ValidationMessage(fieldPath, s"Author is of illegal type. Must be one of ${allowedTypes.mkString(", ")}"))
+ }
}
private def validateLanguage(fieldPath: String, languageCode: String): Option[ValidationMessage] = {
diff --git a/concept-api/src/test/scala/no/ndla/conceptapi/db/migration/V27__FixContributorTypesTest.scala b/concept-api/src/test/scala/no/ndla/conceptapi/db/migration/V27__FixContributorTypesTest.scala
new file mode 100644
index 0000000000..6c00400b0e
--- /dev/null
+++ b/concept-api/src/test/scala/no/ndla/conceptapi/db/migration/V27__FixContributorTypesTest.scala
@@ -0,0 +1,171 @@
+/*
+ * Part of NDLA concept-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.conceptapi.db.migration
+
+import io.circe.parser
+import no.ndla.conceptapi.{TestEnvironment, UnitSuite}
+
+class V27__FixContributorTypesTest extends UnitSuite with TestEnvironment {
+ test("That copyright has correct contributor types") {
+ val migration = new V27__FixContributorTypes
+ val oldDocument =
+ """
+ |{
+ | "id": 4851,
+ | "tags": [
+ | {
+ | "tags": [
+ | "CNC",
+ | "Teknologi- og industrifag"
+ | ],
+ | "language": "nn"
+ | },
+ | {
+ | "tags": [
+ | "CNC",
+ | "Teknologi- og industrifag"
+ | ],
+ | "language": "nb"
+ | }
+ | ],
+ | "title": [
+ | {
+ | "title": "moturs",
+ | "language": "nn"
+ | },
+ | {
+ | "title": "moturs",
+ | "language": "nb"
+ | }
+ | ],
+ | "status": {
+ | "other": [],
+ | "current": "FOR_APPROVAL"
+ | },
+ | "content": [
+ | {
+ | "content": "Moturs beskriv ei sirkelforma rørsle i motsett retning av urvisaren.\n\nFor programmering i G-kode blir kommando G03 nytta for rørsle i koordinatsystemet, og M03 blir nytta for rotasjonsretninga til spindelen.",
+ | "language": "nn"
+ | },
+ | {
+ | "content": "Moturs beskriver en sirkelformet bevegelse i motsatt retning av urviseren
For programmering i G-kode benyttes kommando G03 for bevegelse i koordinatsystemet, og M03 benyttes for spindelens rotasjonsretning.",
+ | "language": "nb"
+ | }
+ | ],
+ | "created": "2023-02-27T13:31:42.000Z",
+ | "updated": "2025-02-18T08:21:46.910Z",
+ | "revision": 18,
+ | "glossData": null,
+ | "metaImage": [],
+ | "updatedBy": [
+ | "-jME11plz_kZ-T8vqKlztgmn",
+ | "TQ4PTLdcsPWRhfNxGVfRAlt9",
+ | "lwkLpeEV_VUmCkly1SJ3WTkg",
+ | "455VO7UP9CLDSUU5TLugvgnc",
+ | "mFfrGSyCOBDWB2FiWlB_fdw7"
+ | ],
+ | "conceptType": "concept",
+ | "editorNotes": [
+ | {
+ | "note": "Updated concept",
+ | "user": "mFfrGSyCOBDWB2FiWlB_fdw7",
+ | "status": {
+ | "other": [],
+ | "current": "FOR_APPROVAL"
+ | },
+ | "timestamp": "2025-02-18T08:21:46.911Z"
+ | }
+ | ],
+ | "responsible": {
+ | "lastUpdated": "2023-06-20T05:55:50.000Z",
+ | "responsibleId": "mFfrGSyCOBDWB2FiWlB_fdw7"
+ | },
+ | "visualElement": []
+ |}
+ |""".stripMargin
+ val newDocument =
+ """
+ |{
+ | "id": 4851,
+ | "tags": [
+ | {
+ | "tags": [
+ | "CNC",
+ | "Teknologi- og industrifag"
+ | ],
+ | "language": "nn"
+ | },
+ | {
+ | "tags": [
+ | "CNC",
+ | "Teknologi- og industrifag"
+ | ],
+ | "language": "nb"
+ | }
+ | ],
+ | "title": [
+ | {
+ | "title": "moturs",
+ | "language": "nn"
+ | },
+ | {
+ | "title": "moturs",
+ | "language": "nb"
+ | }
+ | ],
+ | "status": {
+ | "other": [],
+ | "current": "FOR_APPROVAL"
+ | },
+ | "content": [
+ | {
+ | "content": "Moturs beskriv ei sirkelforma rørsle i motsett retning av urvisaren.\n\nFor programmering i G-kode blir kommando G03 nytta for rørsle i koordinatsystemet, og M03 blir nytta for rotasjonsretninga til spindelen.",
+ | "language": "nn"
+ | },
+ | {
+ | "content": "Moturs beskriver en sirkelformet bevegelse i motsatt retning av urviseren
For programmering i G-kode benyttes kommando G03 for bevegelse i koordinatsystemet, og M03 benyttes for spindelens rotasjonsretning.",
+ | "language": "nb"
+ | }
+ | ],
+ | "created": "2023-02-27T13:31:42.000Z",
+ | "updated": "2025-02-18T08:21:46.910Z",
+ | "revision": 18,
+ | "glossData": null,
+ | "metaImage": [],
+ | "updatedBy": [
+ | "-jME11plz_kZ-T8vqKlztgmn",
+ | "TQ4PTLdcsPWRhfNxGVfRAlt9",
+ | "lwkLpeEV_VUmCkly1SJ3WTkg",
+ | "455VO7UP9CLDSUU5TLugvgnc",
+ | "mFfrGSyCOBDWB2FiWlB_fdw7"
+ | ],
+ | "conceptType": "concept",
+ | "editorNotes": [
+ | {
+ | "note": "Updated concept",
+ | "user": "mFfrGSyCOBDWB2FiWlB_fdw7",
+ | "status": {
+ | "other": [],
+ | "current": "FOR_APPROVAL"
+ | },
+ | "timestamp": "2025-02-18T08:21:46.911Z"
+ | }
+ | ],
+ | "responsible": {
+ | "lastUpdated": "2023-06-20T05:55:50.000Z",
+ | "responsibleId": "mFfrGSyCOBDWB2FiWlB_fdw7"
+ | },
+ | "visualElement": []
+ |}
+ |""".stripMargin
+
+ val expected = parser.parse(newDocument).toTry.get
+ migration.convertColumn(oldDocument) should be(expected.noSpaces)
+ }
+}
diff --git a/concept-api/src/test/scala/no/ndla/conceptapi/db/migration/V28__FixContributorTypesPublishedTest.scala b/concept-api/src/test/scala/no/ndla/conceptapi/db/migration/V28__FixContributorTypesPublishedTest.scala
new file mode 100644
index 0000000000..32c5a842bd
--- /dev/null
+++ b/concept-api/src/test/scala/no/ndla/conceptapi/db/migration/V28__FixContributorTypesPublishedTest.scala
@@ -0,0 +1,211 @@
+/*
+ * Part of NDLA concept-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.conceptapi.db.migration
+
+import io.circe.parser
+import no.ndla.conceptapi.{TestEnvironment, UnitSuite}
+
+class V28__FixContributorTypesPublishedTest extends UnitSuite with TestEnvironment {
+ test("That copyright has correct contributor types") {
+ val migration = new V28__FixContributorTypesPublished
+ val oldDocument =
+ """
+ |{
+ | "id": 4851,
+ | "tags": [
+ | {
+ | "tags": [
+ | "CNC",
+ | "Teknologi- og industrifag"
+ | ],
+ | "language": "nn"
+ | },
+ | {
+ | "tags": [
+ | "CNC",
+ | "Teknologi- og industrifag"
+ | ],
+ | "language": "nb"
+ | }
+ | ],
+ | "title": [
+ | {
+ | "title": "moturs",
+ | "language": "nn"
+ | },
+ | {
+ | "title": "moturs",
+ | "language": "nb"
+ | }
+ | ],
+ | "status": {
+ | "other": [],
+ | "current": "FOR_APPROVAL"
+ | },
+ | "content": [
+ | {
+ | "content": "Moturs beskriv ei sirkelforma rørsle i motsett retning av urvisaren.\n\nFor programmering i G-kode blir kommando G03 nytta for rørsle i koordinatsystemet, og M03 blir nytta for rotasjonsretninga til spindelen.",
+ | "language": "nn"
+ | },
+ | {
+ | "content": "Moturs beskriver en sirkelformet bevegelse i motsatt retning av urviseren
For programmering i G-kode benyttes kommando G03 for bevegelse i koordinatsystemet, og M03 benyttes for spindelens rotasjonsretning.",
+ | "language": "nb"
+ | }
+ | ],
+ | "created": "2023-02-27T13:31:42.000Z",
+ | "updated": "2025-02-18T08:21:46.910Z",
+ | "revision": 18,
+ | "copyright": {
+ | "origin": null,
+ | "license": "CC-BY-SA-4.0",
+ | "validTo": null,
+ | "processed": false,
+ | "validFrom": null,
+ | "creators": [
+ | {
+ | "type": "Forfatter",
+ | "name": "Roger Rosmo"
+ | }
+ | ],
+ | "processors": [
+ | {
+ | "type": "korrektur",
+ | "name": "Anne Vagstein"
+ | }
+ | ],
+ | "rightsholders": []
+ | },
+ | "glossData": null,
+ | "metaImage": [],
+ | "updatedBy": [
+ | "-jME11plz_kZ-T8vqKlztgmn",
+ | "TQ4PTLdcsPWRhfNxGVfRAlt9",
+ | "lwkLpeEV_VUmCkly1SJ3WTkg",
+ | "455VO7UP9CLDSUU5TLugvgnc",
+ | "mFfrGSyCOBDWB2FiWlB_fdw7"
+ | ],
+ | "conceptType": "concept",
+ | "editorNotes": [
+ | {
+ | "note": "Updated concept",
+ | "user": "mFfrGSyCOBDWB2FiWlB_fdw7",
+ | "status": {
+ | "other": [],
+ | "current": "FOR_APPROVAL"
+ | },
+ | "timestamp": "2025-02-18T08:21:46.911Z"
+ | }
+ | ],
+ | "responsible": {
+ | "lastUpdated": "2023-06-20T05:55:50.000Z",
+ | "responsibleId": "mFfrGSyCOBDWB2FiWlB_fdw7"
+ | },
+ | "visualElement": []
+ |}
+ |""".stripMargin
+ val newDocument =
+ """
+ |{
+ | "id": 4851,
+ | "tags": [
+ | {
+ | "tags": [
+ | "CNC",
+ | "Teknologi- og industrifag"
+ | ],
+ | "language": "nn"
+ | },
+ | {
+ | "tags": [
+ | "CNC",
+ | "Teknologi- og industrifag"
+ | ],
+ | "language": "nb"
+ | }
+ | ],
+ | "title": [
+ | {
+ | "title": "moturs",
+ | "language": "nn"
+ | },
+ | {
+ | "title": "moturs",
+ | "language": "nb"
+ | }
+ | ],
+ | "status": {
+ | "other": [],
+ | "current": "FOR_APPROVAL"
+ | },
+ | "content": [
+ | {
+ | "content": "Moturs beskriv ei sirkelforma rørsle i motsett retning av urvisaren.\n\nFor programmering i G-kode blir kommando G03 nytta for rørsle i koordinatsystemet, og M03 blir nytta for rotasjonsretninga til spindelen.",
+ | "language": "nn"
+ | },
+ | {
+ | "content": "Moturs beskriver en sirkelformet bevegelse i motsatt retning av urviseren
For programmering i G-kode benyttes kommando G03 for bevegelse i koordinatsystemet, og M03 benyttes for spindelens rotasjonsretning.",
+ | "language": "nb"
+ | }
+ | ],
+ | "created": "2023-02-27T13:31:42.000Z",
+ | "updated": "2025-02-18T08:21:46.910Z",
+ | "revision": 18,
+ | "copyright": {
+ | "origin": null,
+ | "license": "CC-BY-SA-4.0",
+ | "validTo": null,
+ | "processed": false,
+ | "validFrom": null,
+ | "creators": [
+ | {
+ | "type": "writer",
+ | "name": "Roger Rosmo"
+ | }
+ | ],
+ | "processors": [
+ | {
+ | "type": "correction",
+ | "name": "Anne Vagstein"
+ | }
+ | ],
+ | "rightsholders": []
+ | },
+ | "glossData": null,
+ | "metaImage": [],
+ | "updatedBy": [
+ | "-jME11plz_kZ-T8vqKlztgmn",
+ | "TQ4PTLdcsPWRhfNxGVfRAlt9",
+ | "lwkLpeEV_VUmCkly1SJ3WTkg",
+ | "455VO7UP9CLDSUU5TLugvgnc",
+ | "mFfrGSyCOBDWB2FiWlB_fdw7"
+ | ],
+ | "conceptType": "concept",
+ | "editorNotes": [
+ | {
+ | "note": "Updated concept",
+ | "user": "mFfrGSyCOBDWB2FiWlB_fdw7",
+ | "status": {
+ | "other": [],
+ | "current": "FOR_APPROVAL"
+ | },
+ | "timestamp": "2025-02-18T08:21:46.911Z"
+ | }
+ | ],
+ | "responsible": {
+ | "lastUpdated": "2023-06-20T05:55:50.000Z",
+ | "responsibleId": "mFfrGSyCOBDWB2FiWlB_fdw7"
+ | },
+ | "visualElement": []
+ |}
+ |""".stripMargin
+
+ val expected = parser.parse(newDocument).toTry.get
+ migration.convertColumn(oldDocument) should be(expected.noSpaces)
+ }
+}
diff --git a/concept-api/src/test/scala/no/ndla/conceptapi/service/ConverterServiceTest.scala b/concept-api/src/test/scala/no/ndla/conceptapi/service/ConverterServiceTest.scala
index d5597af984..aeeb23b194 100644
--- a/concept-api/src/test/scala/no/ndla/conceptapi/service/ConverterServiceTest.scala
+++ b/concept-api/src/test/scala/no/ndla/conceptapi/service/ConverterServiceTest.scala
@@ -10,7 +10,7 @@ package no.ndla.conceptapi.service
import no.ndla.common.model.api.{Delete, Missing, UpdateWith}
import no.ndla.common.model.domain.concept.*
-import no.ndla.common.model.domain.{Responsible, concept}
+import no.ndla.common.model.domain.{ContributorType, Responsible, concept}
import no.ndla.common.model.{NDLADate, api as commonApi, domain as common}
import no.ndla.conceptapi.model.api
import no.ndla.conceptapi.model.api.{NewConceptDTO, NotFoundException, UpdatedConceptDTO}
@@ -122,9 +122,9 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment {
commonApi.DraftCopyrightDTO(
None,
None,
- Seq(commonApi.AuthorDTO("Photographer", "Photographer")),
- Seq(commonApi.AuthorDTO("Photographer", "Photographer")),
- Seq(commonApi.AuthorDTO("Photographer", "Photographer")),
+ Seq(commonApi.AuthorDTO(ContributorType.Photographer, "Photographer")),
+ Seq(commonApi.AuthorDTO(ContributorType.Photographer, "Photographer")),
+ Seq(commonApi.AuthorDTO(ContributorType.Photographer, "Photographer")),
None,
None,
false
@@ -147,9 +147,9 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment {
common.draft.DraftCopyright(
None,
None,
- Seq(common.Author("Photographer", "Photographer")),
- Seq(common.Author("Photographer", "Photographer")),
- Seq(common.Author("Photographer", "Photographer")),
+ Seq(common.Author(ContributorType.Photographer, "Photographer")),
+ Seq(common.Author(ContributorType.Photographer, "Photographer")),
+ Seq(common.Author(ContributorType.Photographer, "Photographer")),
None,
None,
false
diff --git a/concept-api/src/test/scala/no/ndla/conceptapi/service/StateTransitionRulesTest.scala b/concept-api/src/test/scala/no/ndla/conceptapi/service/StateTransitionRulesTest.scala
index f3bd4b1793..a8ebb03a09 100644
--- a/concept-api/src/test/scala/no/ndla/conceptapi/service/StateTransitionRulesTest.scala
+++ b/concept-api/src/test/scala/no/ndla/conceptapi/service/StateTransitionRulesTest.scala
@@ -9,7 +9,7 @@ package no.ndla.conceptapi.service
import no.ndla.common.model.domain.concept.{Concept, ConceptContent, ConceptStatus, ConceptType, Status}
import no.ndla.common.model.domain.draft.DraftCopyright
-import no.ndla.common.model.domain.{Author, Responsible, Tag, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Responsible, Tag, Title}
import no.ndla.conceptapi.{TestData, TestEnvironment, UnitSuite}
import no.ndla.conceptapi.model.domain.StateTransition
import org.mockito.ArgumentMatchers.any
@@ -30,7 +30,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment {
DraftCopyright(
license = Some("CC-BY-4.0"),
origin = None,
- creators = Seq(Author("writer", "Ape Katt")),
+ creators = Seq(Author(ContributorType.Writer, "Ape Katt")),
processors = Seq.empty,
rightsholders = Seq.empty,
validFrom = None,
@@ -76,7 +76,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment {
DraftCopyright(
license = Some("CC-BY-4.0"),
origin = None,
- creators = Seq(Author("writer", "Ape Katt")),
+ creators = Seq(Author(ContributorType.Writer, "Ape Katt")),
processors = Seq.empty,
rightsholders = Seq.empty,
validFrom = None,
@@ -122,7 +122,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment {
DraftCopyright(
license = Some("CC-BY-4.0"),
origin = None,
- creators = Seq(Author("writer", "Ape Katt")),
+ creators = Seq(Author(ContributorType.Writer, "Ape Katt")),
processors = Seq.empty,
rightsholders = Seq.empty,
validFrom = None,
@@ -167,7 +167,7 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment {
DraftCopyright(
license = Some("CC-BY-4.0"),
origin = None,
- creators = Seq(Author("writer", "Katronk")),
+ creators = Seq(Author(ContributorType.Writer, "Katronk")),
processors = Seq.empty,
rightsholders = Seq.empty,
validFrom = None,
diff --git a/concept-api/src/test/scala/no/ndla/conceptapi/service/WriteServiceTest.scala b/concept-api/src/test/scala/no/ndla/conceptapi/service/WriteServiceTest.scala
index 75ae00a750..b2efdac9d5 100644
--- a/concept-api/src/test/scala/no/ndla/conceptapi/service/WriteServiceTest.scala
+++ b/concept-api/src/test/scala/no/ndla/conceptapi/service/WriteServiceTest.scala
@@ -8,7 +8,7 @@
package no.ndla.conceptapi.service
-import no.ndla.common.model.domain.{Responsible, Tag, Title}
+import no.ndla.common.model.domain.{ContributorType, Responsible, Tag, Title}
import no.ndla.common.model.api as commonApi
import no.ndla.conceptapi.model.api.ConceptResponsibleDTO
import no.ndla.conceptapi.model.api
@@ -137,7 +137,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
commonApi.DraftCopyrightDTO(
None,
Some("https://ndla.no"),
- Seq(commonApi.AuthorDTO("Opphavsmann", "Katrine")),
+ Seq(commonApi.AuthorDTO(ContributorType.Originator, "Katrine")),
List(),
List(),
None,
@@ -167,7 +167,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
commonApi.DraftCopyrightDTO(
None,
Some("https://ndla.no"),
- Seq(commonApi.AuthorDTO("Opphavsmann", "Katrine")),
+ Seq(commonApi.AuthorDTO(ContributorType.Originator, "Katrine")),
List(),
List(),
None,
diff --git a/concept-api/src/test/scala/no/ndla/conceptapi/service/search/DraftConceptSearchServiceTest.scala b/concept-api/src/test/scala/no/ndla/conceptapi/service/search/DraftConceptSearchServiceTest.scala
index 0200340527..43d2db78cf 100644
--- a/concept-api/src/test/scala/no/ndla/conceptapi/service/search/DraftConceptSearchServiceTest.scala
+++ b/concept-api/src/test/scala/no/ndla/conceptapi/service/search/DraftConceptSearchServiceTest.scala
@@ -10,7 +10,7 @@ package no.ndla.conceptapi.service.search
import no.ndla.common.configuration.Constants.EmbedTagName
import no.ndla.common.model.domain.draft.DraftCopyright
-import no.ndla.common.model.domain.{Author, Responsible, Tag, Title, concept}
+import no.ndla.common.model.domain.{Author, ContributorType, Responsible, Tag, Title, concept}
import no.ndla.conceptapi.*
import no.ndla.conceptapi.model.domain.*
import no.ndla.conceptapi.model.search.DraftSearchSettings
@@ -54,7 +54,7 @@ class DraftConceptSearchServiceTest extends IntegrationSuite(EnableElasticsearch
val byNcSa: DraftCopyright = DraftCopyright(
Some(License.CC_BY_NC_SA.toString),
Some("Gotham City"),
- List(Author("Forfatter", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -65,7 +65,7 @@ class DraftConceptSearchServiceTest extends IntegrationSuite(EnableElasticsearch
val publicDomain: DraftCopyright = DraftCopyright(
Some(License.PublicDomain.toString),
Some("Metropolis"),
- List(Author("Forfatter", "Bruce Wayne")),
+ List(Author(ContributorType.Writer, "Bruce Wayne")),
List(),
List(),
None,
@@ -76,7 +76,7 @@ class DraftConceptSearchServiceTest extends IntegrationSuite(EnableElasticsearch
val copyrighted: DraftCopyright = DraftCopyright(
Some(License.Copyrighted.toString),
Some("New York"),
- List(Author("Forfatter", "Clark Kent")),
+ List(Author(ContributorType.Writer, "Clark Kent")),
List(),
List(),
None,
diff --git a/concept-api/src/test/scala/no/ndla/conceptapi/service/search/PublishedConceptSearchServiceTest.scala b/concept-api/src/test/scala/no/ndla/conceptapi/service/search/PublishedConceptSearchServiceTest.scala
index 2734c32ffd..74e3df3cc5 100644
--- a/concept-api/src/test/scala/no/ndla/conceptapi/service/search/PublishedConceptSearchServiceTest.scala
+++ b/concept-api/src/test/scala/no/ndla/conceptapi/service/search/PublishedConceptSearchServiceTest.scala
@@ -10,7 +10,7 @@ package no.ndla.conceptapi.service.search
import no.ndla.common.configuration.Constants.EmbedTagName
import no.ndla.common.model.domain.draft.DraftCopyright
-import no.ndla.common.model.domain.{Author, Tag, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title}
import no.ndla.conceptapi.model.domain.*
import no.ndla.conceptapi.model.search
import no.ndla.conceptapi.*
@@ -50,7 +50,7 @@ class PublishedConceptSearchServiceTest
val byNcSa: DraftCopyright = DraftCopyright(
Some(License.CC_BY_NC_SA.toString),
Some("Gotham City"),
- List(Author("Forfatter", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -61,7 +61,7 @@ class PublishedConceptSearchServiceTest
val publicDomain: DraftCopyright = DraftCopyright(
Some(License.PublicDomain.toString),
Some("Metropolis"),
- List(Author("Forfatter", "Bruce Wayne")),
+ List(Author(ContributorType.Writer, "Bruce Wayne")),
List(),
List(),
None,
@@ -72,7 +72,7 @@ class PublishedConceptSearchServiceTest
val copyrighted: DraftCopyright = DraftCopyright(
Some(License.Copyrighted.toString),
Some("New York"),
- List(Author("Forfatter", "Clark Kent")),
+ List(Author(ContributorType.Writer, "Clark Kent")),
List(),
List(),
None,
diff --git a/concept-api/src/test/scala/no/ndla/conceptapi/validation/ContentValidatorTest.scala b/concept-api/src/test/scala/no/ndla/conceptapi/validation/ContentValidatorTest.scala
index 7129c2eb74..c51d107206 100644
--- a/concept-api/src/test/scala/no/ndla/conceptapi/validation/ContentValidatorTest.scala
+++ b/concept-api/src/test/scala/no/ndla/conceptapi/validation/ContentValidatorTest.scala
@@ -11,7 +11,7 @@ package no.ndla.conceptapi.validation
import no.ndla.common.errors.{ValidationException, ValidationMessage}
import no.ndla.common.model.domain.concept.{Concept, ConceptContent}
import no.ndla.common.model.domain.draft.DraftCopyright
-import no.ndla.common.model.domain.{Author, Responsible, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Responsible, Title}
import no.ndla.conceptapi.{TestData, TestEnvironment, UnitSuite}
import scala.util.{Failure, Success}
@@ -67,7 +67,18 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
test("Copyright validation succeeds if license is included and copyright holders are not empty") {
val concept = baseConcept.copy(copyright =
- Some(DraftCopyright(Some("CC-BY-4.0"), None, Seq(Author("creator", "test")), Seq(), Seq(), None, None, false))
+ Some(
+ DraftCopyright(
+ Some("CC-BY-4.0"),
+ None,
+ Seq(Author(ContributorType.Writer, "test")),
+ Seq(),
+ Seq(),
+ None,
+ None,
+ false
+ )
+ )
)
val result = contentValidator.validateConcept(concept)
result should be(Success(concept))
diff --git a/draft-api/src/main/scala/no/ndla/draftapi/db/migration/V69__FixContributorTypes.scala b/draft-api/src/main/scala/no/ndla/draftapi/db/migration/V69__FixContributorTypes.scala
new file mode 100644
index 0000000000..c3f4a2660b
--- /dev/null
+++ b/draft-api/src/main/scala/no/ndla/draftapi/db/migration/V69__FixContributorTypes.scala
@@ -0,0 +1,59 @@
+/*
+ * Part of NDLA draft-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.draftapi.db.migration
+
+import io.circe.syntax.EncoderOps
+import io.circe.generic.auto.*
+import io.circe.{ACursor, parser}
+import no.ndla.common.model.domain.{Author, ContributorType}
+import no.ndla.database.DocumentMigration
+
+class V69__FixContributorTypes extends DocumentMigration {
+ override val tableName: String = "articledata"
+ override val columnName: String = "document"
+
+ override def convertColumn(value: String): String = {
+ val oldDocument = parser.parse(value).toTry.get
+ val copyright = oldDocument.hcursor.downField("copyright")
+ val creators = convertList(copyright, "creators")
+ val processors = convertList(copyright, "processors")
+ val rightsholders = convertList(copyright, "rightsholders")
+
+ val newDocument =
+ if (copyright.succeeded)
+ oldDocument.hcursor
+ .downField("copyright")
+ .withFocus(_.mapObject(_.remove("creators").add("creators", creators.asJson)))
+ .withFocus(_.mapObject(_.remove("processors").add("processors", processors.asJson)))
+ .withFocus(_.mapObject(_.remove("rightsholders").add("rightsholders", rightsholders.asJson)))
+ else oldDocument.hcursor
+ newDocument.top.getOrElse(oldDocument).noSpaces
+ }
+
+ private def convertList(cursor: ACursor, fieldName: String): List[Author] = {
+ val field = cursor.downField(fieldName)
+ if (field.succeeded) {
+ field.as[Option[List[OldAuthor]]].toTry.get match {
+ case None => List.empty
+ case Some(authors) => convertAuthors(authors)
+ }
+ } else List.empty
+ }
+
+ private def convertAuthors(authors: List[OldAuthor]): List[Author] = {
+ authors.map(author => {
+ ContributorType.valueOf(author.`type`.toLowerCase) match {
+ case None => Author(ContributorType.mapping(author.`type`.toLowerCase), author.name)
+ case Some(a) => Author(a, author.name)
+ }
+ })
+ }
+}
+
+case class OldAuthor(`type`: String, name: String)
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 7e3c1187c3..b9a18c210f 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
@@ -251,9 +251,10 @@ trait ContentValidator {
private def validateCopyright(copyright: DraftCopyright): Seq[ValidationMessage] = {
val licenseMessage = copyright.license.map(validateLicense).toSeq.flatten
- val contributorsMessages = copyright.creators.flatMap(validateAuthor) ++ copyright.processors.flatMap(
- validateAuthor
- ) ++ copyright.rightsholders.flatMap(validateAuthor)
+ val contributorsMessages =
+ copyright.creators.flatMap(a => validateAuthor(a, ContributorType.creators)) ++ copyright.processors.flatMap(
+ a => validateAuthor(a, ContributorType.processors)
+ ) ++ copyright.rightsholders.flatMap(a => validateAuthor(a, ContributorType.rightsholders))
val originMessage =
copyright.origin.map(origin => TextValidator.validate("copyright.origin", origin, Set.empty)).toSeq.flatten
@@ -267,9 +268,22 @@ trait ContentValidator {
}
}
- private def validateAuthor(author: Author): Seq[ValidationMessage] = {
- TextValidator.validate("author.type", author.`type`, Set.empty).toList ++
- TextValidator.validate("author.name", author.name, Set.empty).toList
+ private def validateAuthor(author: Author, allowedTypes: Seq[ContributorType]): Seq[ValidationMessage] = {
+ TextValidator.validate("author.name", author.name, Set.empty).toList ++
+ validateAuthorType("author.type", author.`type`, allowedTypes).toList ++
+ validateMinimumLength("author.name", author.name, 1)
+ }
+
+ private def validateAuthorType(
+ fieldPath: String,
+ `type`: ContributorType,
+ allowedTypes: Seq[ContributorType]
+ ): Option[ValidationMessage] = {
+ if (allowedTypes.contains(`type`)) {
+ None
+ } else {
+ Some(ValidationMessage(fieldPath, s"Author is of illegal type. Must be one of ${allowedTypes.mkString(", ")}"))
+ }
}
private def validateTags(tags: Seq[Tag]): Seq[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 58bda894a6..4331974f68 100644
--- a/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala
+++ b/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala
@@ -11,7 +11,7 @@ package no.ndla.draftapi
import no.ndla.common.configuration.Constants.EmbedTagName
import no.ndla.common.model
import no.ndla.common.model.api.{DraftCopyrightDTO, Missing}
-import no.ndla.common.model.domain.{Priority, Title}
+import no.ndla.common.model.domain.{ContributorType, Priority, Title}
import no.ndla.common.model.domain.draft.Draft
import no.ndla.common.model.domain.draft.DraftStatus.*
import no.ndla.common.model.domain.language.OptLanguageFields
@@ -58,7 +58,7 @@ object TestData {
private val byNcSaCopyright = common.draft.DraftCopyright(
Some(CC_BY_NC_SA.toString),
Some("Gotham City"),
- List(common.Author("Forfatter", "DC Comics")),
+ List(common.Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -68,7 +68,7 @@ object TestData {
private val copyrighted = common.draft.DraftCopyright(
Some(License.Copyrighted.toString),
Some("New York"),
- List(common.Author("Forfatter", "Clark Kent")),
+ List(common.Author(ContributorType.Writer, "Clark Kent")),
List(),
List(),
None,
@@ -90,7 +90,7 @@ object TestData {
DraftCopyrightDTO(
Some(commonApi.LicenseDTO("licence", None, None)),
Some("origin"),
- Seq(commonApi.AuthorDTO("developer", "Per")),
+ Seq(commonApi.AuthorDTO(ContributorType.Artist, "Per")),
List(),
List(),
None,
diff --git a/draft-api/src/test/scala/no/ndla/draftapi/db/migration/V69__FixContributorTypesTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/db/migration/V69__FixContributorTypesTest.scala
new file mode 100644
index 0000000000..a9508e32aa
--- /dev/null
+++ b/draft-api/src/test/scala/no/ndla/draftapi/db/migration/V69__FixContributorTypesTest.scala
@@ -0,0 +1,607 @@
+/*
+ * Part of NDLA draft-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.draftapi.db.migration
+
+import io.circe.parser
+import no.ndla.draftapi.{TestEnvironment, UnitSuite}
+
+class V69__FixContributorTypesTest extends UnitSuite with TestEnvironment {
+ val migration = new V69__FixContributorTypes
+
+ test("That copyright has correct contributor types") {
+ val oldDocument =
+ """
+ |{
+ | "id": null,
+ | "slug": null,
+ | "tags": [
+ | {
+ | "tags": [
+ | "negative tall",
+ | "regnerekkefølge",
+ | "tallregning"
+ | ],
+ | "language": "nb"
+ | },
+ | {
+ | "tags": [
+ | "regnerekkefølge",
+ | "talsystem"
+ | ],
+ | "language": "nn"
+ | }
+ | ],
+ | "notes": [],
+ | "title": [
+ | {
+ | "title": "Tallregning",
+ | "language": "nb"
+ | },
+ | {
+ | "title": "Talrekning",
+ | "language": "nn"
+ | }
+ | ],
+ | "status": {
+ | "other": [
+ | "IMPORTED"
+ | ],
+ | "current": "PUBLISHED"
+ | },
+ | "content": [
+ | {
+ | "content": "Tall er grunnlaget for all matematikk. Det er derfor veldig viktig å ha god tallforståelse for å gjøre det bra i matematikk.
Tellestreker, hulemalerier og helleristninger viser at mennesker som levde for mange tusen år siden, brukte tall i sitt daglige liv. Arkeologer har funnet tellestreker som er over 30 000 år gamle. Strekene er systematisk risset inn og er antakelig blitt brukt under opptelling av gjenstander, dager eller andre objekter.
Vår sivilisasjon oppsto i Mesopotamia, landområdene mellom og rundt Eufrat og Tigris (nå Irak, nordøstlige Syria og sørøstlige Tyrkia), for ca. 5000 år siden. Her ble skrivekunsten oppfunnet. Menneskene som levde her, brukte kileskrift. De skrev på leirtavler og presset de kileformede tegnene inn i våt leire. På denne måten førte de blant annet regnskap over den handelen som utviklet seg mellom byene. Egypterne kjente til kileskriften, men utviklet sine egne skrifttegn, hieroglyfene. Utgravinger viser at det på denne tiden var mennesker som drev med addisjon, subtraksjon, multiplikasjon og divisjon.
Senere laget både grekerne og romerne sine tallsystemer. Men det tallsystemet vi bruker i dag, med de ti symbolene 0, 1, 2, 3, 4, 5, 6, 7, 8 og 9, har sin opprinnelse i India. I de tidligste kulturene var tallet 0 og de negative tallene ikke kjent. Det var først på 1200-tallet at matematikere begynte å innføre disse tallene. Det tok likevel enda flere hundre år før de ble fullt ut akseptert. Matematikere diskuterte om negative tall virkelig eksisterte, og helt fram mot 1800-tallet var det matematikere som ikke ville akseptere beregninger som inneholdt negative tall. Problemet med å forstå negative tall henger sammen med at tall ikke er noe konkret. Tall er abstrakte matematiske begreper. Vi må knytte tallene til noe konkret for å få en følelse av å forstå dem.
For oss som har vokst opp med bankvesen og gradestokk, er det lettere å forstå de negative tallene. Vi vet at vi kan komme til å bruke mer penger enn vi har på konto. På kontoutskriften fra banken står det da et tall med minus foran, og vi skjønner at vi står i gjeld til banken! Vi vet også at når det er kuldegrader ute, leser vi det av som negative tall på gradestokken. De negative tallene blir da konkrete, og vi føler at vi forstår dem.
",
+ | "language": "nb"
+ | },
+ | {
+ | "content": "Tal er grunnlaget for all matematikk. Det er derfor veldig viktig å ha god talforståing for å gjere det bra i faget.
Teljestrekar, holemåleri og helleristingar syner at menneske som levde for mange tusen år sidan, brukte tal i dagleglivet. Arkeologar har funne teljestrekar som er over 30 000 år gamle. Strekane er systematisk rissa inn og er antakeleg blitt brukte under opptelling av gjenstandar, dagar eller andre objekt.
Sivilisasjonen vår oppstod i Mesopotamia, landområda mellom og rundt Eufrat og Tigris (no Irak, nordaustlege Syria og søraustlege Tyrkia), for ca. 5000 år sidan. Her blei skrivekunsten funnen opp. Menneska som levde der, brukte kileskrift. Dei skreiv på leirtavler og pressa dei kileforma teikna inn i våt leire. På denne måten førte dei mellom anna rekneskap over den handelen som utvikla seg mellom byane. Egyptarane kjende til kileskrifta, men utvikla sine eigne skriftteikn, hieroglyfane. Utgravingar syner at det på denne tida var menneske som dreiv med addisjon, subtraksjon, multiplikasjon og divisjon.
Seinare laga både grekarane og romarane sine talsystem. Men det talsystemet vi bruker i dag, med dei ti symbola 0, 1, 2, 3, 4, 5, 6, 7, 8 og 9, har opphav i India. I dei tidlegaste kulturane var talet 0 og dei negative tala ikkje kjende. Det var først på 1200-talet at matematikarar byrja å innføre desse tala. Det tok likevel endå fleire hundre år før dei vart fullt ut aksepterte. Matematikarar diskuterte om negative tal verkeleg eksisterte, og heilt fram mot 1800-talet var det matematikarar som ikkje ville akseptere utrekningar som inneheldt negative tal. Problemet med å forstå negative tal heng saman med at tal ikkje er noko konkret. Tal er abstrakte matematiske omgrep. Vi må knyte tala til noko konkret for å få ei kjensle av å forstå dei.
For oss som har vakse opp med bankvesen og gradestokk, er det lettare å forstå dei negative tala. Vi veit at om vi kan komme til å bruke meir pengar enn vi har på konto. På kontoutskrifta frå banken står det då eit tal med minus framfor, og vi skjønner at vi står i gjeld til banken! Vi veit også at når det er kuldegradar ute, les vi det av som negative tal på gradestokken. Dei negative tala blir då konkrete, og vi kjenner at vi forstår dei.
",
+ | "language": "nn"
+ | }
+ | ],
+ | "created": "2018-02-13T17:54:22.000Z",
+ | "started": false,
+ | "updated": "2018-02-13T17:56:35.000Z",
+ | "comments": [],
+ | "priority": "unspecified",
+ | "revision": null,
+ | "copyright": {
+ | "origin": null,
+ | "license": "CC-BY-NC-SA-4.0",
+ | "validTo": null,
+ | "processed": false,
+ | "validFrom": null,
+ | "creators": [
+ | {
+ | "type": "Forfatter",
+ | "name": "Olav Kristensen"
+ | },
+ | {
+ | "type": "Bearbeider",
+ | "name": "Stein Aanensen"
+ | }
+ | ],
+ | "processors": [
+ | {
+ | "type": "Editorial",
+ | "name": "Elisabet Romedal"
+ | }
+ | ],
+ | "rightsholders": []
+ | },
+ | "grepCodes": [],
+ | "metaImage": [
+ | {
+ | "altText": "Bannerbilde for temaet tall og algebra i faget 1T. Bilde.",
+ | "imageId": "1519",
+ | "language": "nb"
+ | },
+ | {
+ | "altText": "Bannerbilde for temaet tall og algebra i faget 1T. Bilde.",
+ | "imageId": "1519",
+ | "language": "nn"
+ | }
+ | ],
+ | "published": "2018-02-13T17:56:35.000Z",
+ | "updatedBy": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "conceptIds": [],
+ | "disclaimer": {},
+ | "articleType": "topic-article",
+ | "responsible": null,
+ | "availability": "everyone",
+ | "editorLabels": [],
+ | "introduction": [
+ | {
+ | "language": "nb",
+ | "introduction": "I temaet tallregning skal vi vurdere, velge og bruke matematiske metoder og verktøy til å løse problemer fra ulike fag og samfunnsområder, og reflektere over, vurdere og presentere løsningene på en hensiktsmessig måte."
+ | },
+ | {
+ | "language": "nn",
+ | "introduction": "I temaet talrekning skal vi vurdere, velje og bruke matematiske metodar og verktøy til å løyse problem frå ulike fag og samfunnsområde, og reflektere over, vurdere og presentere løysingane på ein føremålstenleg måte."
+ | }
+ | ],
+ | "revisionMeta": [
+ | {
+ | "id": "0b82b045-f1e4-48af-b122-68f63f86e274",
+ | "note": "Automatisk revisjonsdato satt av systemet.",
+ | "status": "needs-revision",
+ | "revisionDate": "2030-01-01T00:00:00.000Z"
+ | }
+ | ],
+ | "visualElement": [
+ | {
+ | "language": "nb",
+ | "resource": ""
+ | },
+ | {
+ | "language": "nn",
+ | "resource": ""
+ | }
+ | ],
+ | "relatedContent": [],
+ | "metaDescription": [
+ | {
+ | "content": "Tallregning handler om å behandle tall i praktisk regning, bruke regningsartene, addisjon, subtraksjon, divisjon og multiplikasjon.",
+ | "language": "nb"
+ | },
+ | {
+ | "content": "Tallrekning handlar om å handtere tal i praktisk rekning, bruke rekningsartane, addisjon, subtraksjon, divisjon og multiplikasjon.",
+ | "language": "nn"
+ | }
+ | ],
+ | "qualityEvaluation": null,
+ | "requiredLibraries": [],
+ | "previousVersionsNotes": [
+ | {
+ | "note": "Status endret",
+ | "user": "nd3KiThoNIQ0KX5flWoFxUqr",
+ | "status": {
+ | "other": [
+ | "IMPORTED"
+ | ],
+ | "current": "PUBLISHED"
+ | },
+ | "timestamp": "2020-06-08T14:02:49.000Z"
+ | }
+ | ]
+ |}
+ |""".stripMargin
+ val newDocument =
+ """
+ |{
+ | "id": null,
+ | "slug": null,
+ | "tags": [
+ | {
+ | "tags": [
+ | "negative tall",
+ | "regnerekkefølge",
+ | "tallregning"
+ | ],
+ | "language": "nb"
+ | },
+ | {
+ | "tags": [
+ | "regnerekkefølge",
+ | "talsystem"
+ | ],
+ | "language": "nn"
+ | }
+ | ],
+ | "notes": [],
+ | "title": [
+ | {
+ | "title": "Tallregning",
+ | "language": "nb"
+ | },
+ | {
+ | "title": "Talrekning",
+ | "language": "nn"
+ | }
+ | ],
+ | "status": {
+ | "other": [
+ | "IMPORTED"
+ | ],
+ | "current": "PUBLISHED"
+ | },
+ | "content": [
+ | {
+ | "content": "Tall er grunnlaget for all matematikk. Det er derfor veldig viktig å ha god tallforståelse for å gjøre det bra i matematikk.
Tellestreker, hulemalerier og helleristninger viser at mennesker som levde for mange tusen år siden, brukte tall i sitt daglige liv. Arkeologer har funnet tellestreker som er over 30 000 år gamle. Strekene er systematisk risset inn og er antakelig blitt brukt under opptelling av gjenstander, dager eller andre objekter.
Vår sivilisasjon oppsto i Mesopotamia, landområdene mellom og rundt Eufrat og Tigris (nå Irak, nordøstlige Syria og sørøstlige Tyrkia), for ca. 5000 år siden. Her ble skrivekunsten oppfunnet. Menneskene som levde her, brukte kileskrift. De skrev på leirtavler og presset de kileformede tegnene inn i våt leire. På denne måten førte de blant annet regnskap over den handelen som utviklet seg mellom byene. Egypterne kjente til kileskriften, men utviklet sine egne skrifttegn, hieroglyfene. Utgravinger viser at det på denne tiden var mennesker som drev med addisjon, subtraksjon, multiplikasjon og divisjon.
Senere laget både grekerne og romerne sine tallsystemer. Men det tallsystemet vi bruker i dag, med de ti symbolene 0, 1, 2, 3, 4, 5, 6, 7, 8 og 9, har sin opprinnelse i India. I de tidligste kulturene var tallet 0 og de negative tallene ikke kjent. Det var først på 1200-tallet at matematikere begynte å innføre disse tallene. Det tok likevel enda flere hundre år før de ble fullt ut akseptert. Matematikere diskuterte om negative tall virkelig eksisterte, og helt fram mot 1800-tallet var det matematikere som ikke ville akseptere beregninger som inneholdt negative tall. Problemet med å forstå negative tall henger sammen med at tall ikke er noe konkret. Tall er abstrakte matematiske begreper. Vi må knytte tallene til noe konkret for å få en følelse av å forstå dem.
For oss som har vokst opp med bankvesen og gradestokk, er det lettere å forstå de negative tallene. Vi vet at vi kan komme til å bruke mer penger enn vi har på konto. På kontoutskriften fra banken står det da et tall med minus foran, og vi skjønner at vi står i gjeld til banken! Vi vet også at når det er kuldegrader ute, leser vi det av som negative tall på gradestokken. De negative tallene blir da konkrete, og vi føler at vi forstår dem.
",
+ | "language": "nb"
+ | },
+ | {
+ | "content": "Tal er grunnlaget for all matematikk. Det er derfor veldig viktig å ha god talforståing for å gjere det bra i faget.
Teljestrekar, holemåleri og helleristingar syner at menneske som levde for mange tusen år sidan, brukte tal i dagleglivet. Arkeologar har funne teljestrekar som er over 30 000 år gamle. Strekane er systematisk rissa inn og er antakeleg blitt brukte under opptelling av gjenstandar, dagar eller andre objekt.
Sivilisasjonen vår oppstod i Mesopotamia, landområda mellom og rundt Eufrat og Tigris (no Irak, nordaustlege Syria og søraustlege Tyrkia), for ca. 5000 år sidan. Her blei skrivekunsten funnen opp. Menneska som levde der, brukte kileskrift. Dei skreiv på leirtavler og pressa dei kileforma teikna inn i våt leire. På denne måten førte dei mellom anna rekneskap over den handelen som utvikla seg mellom byane. Egyptarane kjende til kileskrifta, men utvikla sine eigne skriftteikn, hieroglyfane. Utgravingar syner at det på denne tida var menneske som dreiv med addisjon, subtraksjon, multiplikasjon og divisjon.
Seinare laga både grekarane og romarane sine talsystem. Men det talsystemet vi bruker i dag, med dei ti symbola 0, 1, 2, 3, 4, 5, 6, 7, 8 og 9, har opphav i India. I dei tidlegaste kulturane var talet 0 og dei negative tala ikkje kjende. Det var først på 1200-talet at matematikarar byrja å innføre desse tala. Det tok likevel endå fleire hundre år før dei vart fullt ut aksepterte. Matematikarar diskuterte om negative tal verkeleg eksisterte, og heilt fram mot 1800-talet var det matematikarar som ikkje ville akseptere utrekningar som inneheldt negative tal. Problemet med å forstå negative tal heng saman med at tal ikkje er noko konkret. Tal er abstrakte matematiske omgrep. Vi må knyte tala til noko konkret for å få ei kjensle av å forstå dei.
For oss som har vakse opp med bankvesen og gradestokk, er det lettare å forstå dei negative tala. Vi veit at om vi kan komme til å bruke meir pengar enn vi har på konto. På kontoutskrifta frå banken står det då eit tal med minus framfor, og vi skjønner at vi står i gjeld til banken! Vi veit også at når det er kuldegradar ute, les vi det av som negative tal på gradestokken. Dei negative tala blir då konkrete, og vi kjenner at vi forstår dei.
",
+ | "language": "nn"
+ | }
+ | ],
+ | "created": "2018-02-13T17:54:22.000Z",
+ | "started": false,
+ | "updated": "2018-02-13T17:56:35.000Z",
+ | "comments": [],
+ | "priority": "unspecified",
+ | "revision": null,
+ | "copyright": {
+ | "origin": null,
+ | "license": "CC-BY-NC-SA-4.0",
+ | "validTo": null,
+ | "processed": false,
+ | "validFrom": null,
+ | "creators": [
+ | {
+ | "type": "writer",
+ | "name": "Olav Kristensen"
+ | },
+ | {
+ | "type": "processor",
+ | "name": "Stein Aanensen"
+ | }
+ | ],
+ | "processors": [
+ | {
+ | "type": "editorial",
+ | "name": "Elisabet Romedal"
+ | }
+ | ],
+ | "rightsholders": []
+ | },
+ | "grepCodes": [],
+ | "metaImage": [
+ | {
+ | "altText": "Bannerbilde for temaet tall og algebra i faget 1T. Bilde.",
+ | "imageId": "1519",
+ | "language": "nb"
+ | },
+ | {
+ | "altText": "Bannerbilde for temaet tall og algebra i faget 1T. Bilde.",
+ | "imageId": "1519",
+ | "language": "nn"
+ | }
+ | ],
+ | "published": "2018-02-13T17:56:35.000Z",
+ | "updatedBy": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "conceptIds": [],
+ | "disclaimer": {},
+ | "articleType": "topic-article",
+ | "responsible": null,
+ | "availability": "everyone",
+ | "editorLabels": [],
+ | "introduction": [
+ | {
+ | "language": "nb",
+ | "introduction": "I temaet tallregning skal vi vurdere, velge og bruke matematiske metoder og verktøy til å løse problemer fra ulike fag og samfunnsområder, og reflektere over, vurdere og presentere løsningene på en hensiktsmessig måte."
+ | },
+ | {
+ | "language": "nn",
+ | "introduction": "I temaet talrekning skal vi vurdere, velje og bruke matematiske metodar og verktøy til å løyse problem frå ulike fag og samfunnsområde, og reflektere over, vurdere og presentere løysingane på ein føremålstenleg måte."
+ | }
+ | ],
+ | "revisionMeta": [
+ | {
+ | "id": "0b82b045-f1e4-48af-b122-68f63f86e274",
+ | "note": "Automatisk revisjonsdato satt av systemet.",
+ | "status": "needs-revision",
+ | "revisionDate": "2030-01-01T00:00:00.000Z"
+ | }
+ | ],
+ | "visualElement": [
+ | {
+ | "language": "nb",
+ | "resource": ""
+ | },
+ | {
+ | "language": "nn",
+ | "resource": ""
+ | }
+ | ],
+ | "relatedContent": [],
+ | "metaDescription": [
+ | {
+ | "content": "Tallregning handler om å behandle tall i praktisk regning, bruke regningsartene, addisjon, subtraksjon, divisjon og multiplikasjon.",
+ | "language": "nb"
+ | },
+ | {
+ | "content": "Tallrekning handlar om å handtere tal i praktisk rekning, bruke rekningsartane, addisjon, subtraksjon, divisjon og multiplikasjon.",
+ | "language": "nn"
+ | }
+ | ],
+ | "qualityEvaluation": null,
+ | "requiredLibraries": [],
+ | "previousVersionsNotes": [
+ | {
+ | "note": "Status endret",
+ | "user": "nd3KiThoNIQ0KX5flWoFxUqr",
+ | "status": {
+ | "other": [
+ | "IMPORTED"
+ | ],
+ | "current": "PUBLISHED"
+ | },
+ | "timestamp": "2020-06-08T14:02:49.000Z"
+ | }
+ | ]
+ |}
+ |""".stripMargin
+
+ val expected = parser.parse(newDocument).toTry.get
+ migration.convertColumn(oldDocument) should be(expected.noSpaces)
+ }
+
+ test("That migration handles broken copyright data") {
+ val oldDocument =
+ """
+ |{
+ | "id": null,
+ | "slug": null,
+ | "tags": [
+ | {
+ | "tags": [
+ | "negative tall",
+ | "regnerekkefølge",
+ | "tallregning"
+ | ],
+ | "language": "nb"
+ | },
+ | {
+ | "tags": [
+ | "regnerekkefølge",
+ | "talsystem"
+ | ],
+ | "language": "nn"
+ | }
+ | ],
+ | "notes": [],
+ | "title": [
+ | {
+ | "title": "Tallregning",
+ | "language": "nb"
+ | },
+ | {
+ | "title": "Talrekning",
+ | "language": "nn"
+ | }
+ | ],
+ | "status": {
+ | "other": [
+ | "IMPORTED"
+ | ],
+ | "current": "PUBLISHED"
+ | },
+ | "content": [
+ | {
+ | "content": "Tall er grunnlaget for all matematikk. Det er derfor veldig viktig å ha god tallforståelse for å gjøre det bra i matematikk.
Tellestreker, hulemalerier og helleristninger viser at mennesker som levde for mange tusen år siden, brukte tall i sitt daglige liv. Arkeologer har funnet tellestreker som er over 30 000 år gamle. Strekene er systematisk risset inn og er antakelig blitt brukt under opptelling av gjenstander, dager eller andre objekter.
Vår sivilisasjon oppsto i Mesopotamia, landområdene mellom og rundt Eufrat og Tigris (nå Irak, nordøstlige Syria og sørøstlige Tyrkia), for ca. 5000 år siden. Her ble skrivekunsten oppfunnet. Menneskene som levde her, brukte kileskrift. De skrev på leirtavler og presset de kileformede tegnene inn i våt leire. På denne måten førte de blant annet regnskap over den handelen som utviklet seg mellom byene. Egypterne kjente til kileskriften, men utviklet sine egne skrifttegn, hieroglyfene. Utgravinger viser at det på denne tiden var mennesker som drev med addisjon, subtraksjon, multiplikasjon og divisjon.
Senere laget både grekerne og romerne sine tallsystemer. Men det tallsystemet vi bruker i dag, med de ti symbolene 0, 1, 2, 3, 4, 5, 6, 7, 8 og 9, har sin opprinnelse i India. I de tidligste kulturene var tallet 0 og de negative tallene ikke kjent. Det var først på 1200-tallet at matematikere begynte å innføre disse tallene. Det tok likevel enda flere hundre år før de ble fullt ut akseptert. Matematikere diskuterte om negative tall virkelig eksisterte, og helt fram mot 1800-tallet var det matematikere som ikke ville akseptere beregninger som inneholdt negative tall. Problemet med å forstå negative tall henger sammen med at tall ikke er noe konkret. Tall er abstrakte matematiske begreper. Vi må knytte tallene til noe konkret for å få en følelse av å forstå dem.
For oss som har vokst opp med bankvesen og gradestokk, er det lettere å forstå de negative tallene. Vi vet at vi kan komme til å bruke mer penger enn vi har på konto. På kontoutskriften fra banken står det da et tall med minus foran, og vi skjønner at vi står i gjeld til banken! Vi vet også at når det er kuldegrader ute, leser vi det av som negative tall på gradestokken. De negative tallene blir da konkrete, og vi føler at vi forstår dem.
",
+ | "language": "nb"
+ | },
+ | {
+ | "content": "Tal er grunnlaget for all matematikk. Det er derfor veldig viktig å ha god talforståing for å gjere det bra i faget.
Teljestrekar, holemåleri og helleristingar syner at menneske som levde for mange tusen år sidan, brukte tal i dagleglivet. Arkeologar har funne teljestrekar som er over 30 000 år gamle. Strekane er systematisk rissa inn og er antakeleg blitt brukte under opptelling av gjenstandar, dagar eller andre objekt.
Sivilisasjonen vår oppstod i Mesopotamia, landområda mellom og rundt Eufrat og Tigris (no Irak, nordaustlege Syria og søraustlege Tyrkia), for ca. 5000 år sidan. Her blei skrivekunsten funnen opp. Menneska som levde der, brukte kileskrift. Dei skreiv på leirtavler og pressa dei kileforma teikna inn i våt leire. På denne måten førte dei mellom anna rekneskap over den handelen som utvikla seg mellom byane. Egyptarane kjende til kileskrifta, men utvikla sine eigne skriftteikn, hieroglyfane. Utgravingar syner at det på denne tida var menneske som dreiv med addisjon, subtraksjon, multiplikasjon og divisjon.
Seinare laga både grekarane og romarane sine talsystem. Men det talsystemet vi bruker i dag, med dei ti symbola 0, 1, 2, 3, 4, 5, 6, 7, 8 og 9, har opphav i India. I dei tidlegaste kulturane var talet 0 og dei negative tala ikkje kjende. Det var først på 1200-talet at matematikarar byrja å innføre desse tala. Det tok likevel endå fleire hundre år før dei vart fullt ut aksepterte. Matematikarar diskuterte om negative tal verkeleg eksisterte, og heilt fram mot 1800-talet var det matematikarar som ikkje ville akseptere utrekningar som inneheldt negative tal. Problemet med å forstå negative tal heng saman med at tal ikkje er noko konkret. Tal er abstrakte matematiske omgrep. Vi må knyte tala til noko konkret for å få ei kjensle av å forstå dei.
For oss som har vakse opp med bankvesen og gradestokk, er det lettare å forstå dei negative tala. Vi veit at om vi kan komme til å bruke meir pengar enn vi har på konto. På kontoutskrifta frå banken står det då eit tal med minus framfor, og vi skjønner at vi står i gjeld til banken! Vi veit også at når det er kuldegradar ute, les vi det av som negative tal på gradestokken. Dei negative tala blir då konkrete, og vi kjenner at vi forstår dei.
",
+ | "language": "nn"
+ | }
+ | ],
+ | "created": "2018-02-13T17:54:22.000Z",
+ | "started": false,
+ | "updated": "2018-02-13T17:56:35.000Z",
+ | "comments": [],
+ | "priority": "unspecified",
+ | "revision": null,
+ | "copyright": null,
+ | "grepCodes": [],
+ | "metaImage": [
+ | {
+ | "altText": "Bannerbilde for temaet tall og algebra i faget 1T. Bilde.",
+ | "imageId": "1519",
+ | "language": "nb"
+ | },
+ | {
+ | "altText": "Bannerbilde for temaet tall og algebra i faget 1T. Bilde.",
+ | "imageId": "1519",
+ | "language": "nn"
+ | }
+ | ],
+ | "published": "2018-02-13T17:56:35.000Z",
+ | "updatedBy": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "conceptIds": [],
+ | "disclaimer": {},
+ | "articleType": "topic-article",
+ | "responsible": null,
+ | "availability": "everyone",
+ | "editorLabels": [],
+ | "introduction": [
+ | {
+ | "language": "nb",
+ | "introduction": "I temaet tallregning skal vi vurdere, velge og bruke matematiske metoder og verktøy til å løse problemer fra ulike fag og samfunnsområder, og reflektere over, vurdere og presentere løsningene på en hensiktsmessig måte."
+ | },
+ | {
+ | "language": "nn",
+ | "introduction": "I temaet talrekning skal vi vurdere, velje og bruke matematiske metodar og verktøy til å løyse problem frå ulike fag og samfunnsområde, og reflektere over, vurdere og presentere løysingane på ein føremålstenleg måte."
+ | }
+ | ],
+ | "revisionMeta": [
+ | {
+ | "id": "0b82b045-f1e4-48af-b122-68f63f86e274",
+ | "note": "Automatisk revisjonsdato satt av systemet.",
+ | "status": "needs-revision",
+ | "revisionDate": "2030-01-01T00:00:00.000Z"
+ | }
+ | ],
+ | "visualElement": [
+ | {
+ | "language": "nb",
+ | "resource": ""
+ | },
+ | {
+ | "language": "nn",
+ | "resource": ""
+ | }
+ | ],
+ | "relatedContent": [],
+ | "metaDescription": [
+ | {
+ | "content": "Tallregning handler om å behandle tall i praktisk regning, bruke regningsartene, addisjon, subtraksjon, divisjon og multiplikasjon.",
+ | "language": "nb"
+ | },
+ | {
+ | "content": "Tallrekning handlar om å handtere tal i praktisk rekning, bruke rekningsartane, addisjon, subtraksjon, divisjon og multiplikasjon.",
+ | "language": "nn"
+ | }
+ | ],
+ | "qualityEvaluation": null,
+ | "requiredLibraries": [],
+ | "previousVersionsNotes": [
+ | {
+ | "note": "Status endret",
+ | "user": "nd3KiThoNIQ0KX5flWoFxUqr",
+ | "status": {
+ | "other": [
+ | "IMPORTED"
+ | ],
+ | "current": "PUBLISHED"
+ | },
+ | "timestamp": "2020-06-08T14:02:49.000Z"
+ | }
+ | ]
+ |}
+ |""".stripMargin
+ val newDocument =
+ """
+ |{
+ | "id": null,
+ | "slug": null,
+ | "tags": [
+ | {
+ | "tags": [
+ | "negative tall",
+ | "regnerekkefølge",
+ | "tallregning"
+ | ],
+ | "language": "nb"
+ | },
+ | {
+ | "tags": [
+ | "regnerekkefølge",
+ | "talsystem"
+ | ],
+ | "language": "nn"
+ | }
+ | ],
+ | "notes": [],
+ | "title": [
+ | {
+ | "title": "Tallregning",
+ | "language": "nb"
+ | },
+ | {
+ | "title": "Talrekning",
+ | "language": "nn"
+ | }
+ | ],
+ | "status": {
+ | "other": [
+ | "IMPORTED"
+ | ],
+ | "current": "PUBLISHED"
+ | },
+ | "content": [
+ | {
+ | "content": "Tall er grunnlaget for all matematikk. Det er derfor veldig viktig å ha god tallforståelse for å gjøre det bra i matematikk.
Tellestreker, hulemalerier og helleristninger viser at mennesker som levde for mange tusen år siden, brukte tall i sitt daglige liv. Arkeologer har funnet tellestreker som er over 30 000 år gamle. Strekene er systematisk risset inn og er antakelig blitt brukt under opptelling av gjenstander, dager eller andre objekter.
Vår sivilisasjon oppsto i Mesopotamia, landområdene mellom og rundt Eufrat og Tigris (nå Irak, nordøstlige Syria og sørøstlige Tyrkia), for ca. 5000 år siden. Her ble skrivekunsten oppfunnet. Menneskene som levde her, brukte kileskrift. De skrev på leirtavler og presset de kileformede tegnene inn i våt leire. På denne måten førte de blant annet regnskap over den handelen som utviklet seg mellom byene. Egypterne kjente til kileskriften, men utviklet sine egne skrifttegn, hieroglyfene. Utgravinger viser at det på denne tiden var mennesker som drev med addisjon, subtraksjon, multiplikasjon og divisjon.
Senere laget både grekerne og romerne sine tallsystemer. Men det tallsystemet vi bruker i dag, med de ti symbolene 0, 1, 2, 3, 4, 5, 6, 7, 8 og 9, har sin opprinnelse i India. I de tidligste kulturene var tallet 0 og de negative tallene ikke kjent. Det var først på 1200-tallet at matematikere begynte å innføre disse tallene. Det tok likevel enda flere hundre år før de ble fullt ut akseptert. Matematikere diskuterte om negative tall virkelig eksisterte, og helt fram mot 1800-tallet var det matematikere som ikke ville akseptere beregninger som inneholdt negative tall. Problemet med å forstå negative tall henger sammen med at tall ikke er noe konkret. Tall er abstrakte matematiske begreper. Vi må knytte tallene til noe konkret for å få en følelse av å forstå dem.
For oss som har vokst opp med bankvesen og gradestokk, er det lettere å forstå de negative tallene. Vi vet at vi kan komme til å bruke mer penger enn vi har på konto. På kontoutskriften fra banken står det da et tall med minus foran, og vi skjønner at vi står i gjeld til banken! Vi vet også at når det er kuldegrader ute, leser vi det av som negative tall på gradestokken. De negative tallene blir da konkrete, og vi føler at vi forstår dem.
",
+ | "language": "nb"
+ | },
+ | {
+ | "content": "Tal er grunnlaget for all matematikk. Det er derfor veldig viktig å ha god talforståing for å gjere det bra i faget.
Teljestrekar, holemåleri og helleristingar syner at menneske som levde for mange tusen år sidan, brukte tal i dagleglivet. Arkeologar har funne teljestrekar som er over 30 000 år gamle. Strekane er systematisk rissa inn og er antakeleg blitt brukte under opptelling av gjenstandar, dagar eller andre objekt.
Sivilisasjonen vår oppstod i Mesopotamia, landområda mellom og rundt Eufrat og Tigris (no Irak, nordaustlege Syria og søraustlege Tyrkia), for ca. 5000 år sidan. Her blei skrivekunsten funnen opp. Menneska som levde der, brukte kileskrift. Dei skreiv på leirtavler og pressa dei kileforma teikna inn i våt leire. På denne måten førte dei mellom anna rekneskap over den handelen som utvikla seg mellom byane. Egyptarane kjende til kileskrifta, men utvikla sine eigne skriftteikn, hieroglyfane. Utgravingar syner at det på denne tida var menneske som dreiv med addisjon, subtraksjon, multiplikasjon og divisjon.
Seinare laga både grekarane og romarane sine talsystem. Men det talsystemet vi bruker i dag, med dei ti symbola 0, 1, 2, 3, 4, 5, 6, 7, 8 og 9, har opphav i India. I dei tidlegaste kulturane var talet 0 og dei negative tala ikkje kjende. Det var først på 1200-talet at matematikarar byrja å innføre desse tala. Det tok likevel endå fleire hundre år før dei vart fullt ut aksepterte. Matematikarar diskuterte om negative tal verkeleg eksisterte, og heilt fram mot 1800-talet var det matematikarar som ikkje ville akseptere utrekningar som inneheldt negative tal. Problemet med å forstå negative tal heng saman med at tal ikkje er noko konkret. Tal er abstrakte matematiske omgrep. Vi må knyte tala til noko konkret for å få ei kjensle av å forstå dei.
For oss som har vakse opp med bankvesen og gradestokk, er det lettare å forstå dei negative tala. Vi veit at om vi kan komme til å bruke meir pengar enn vi har på konto. På kontoutskrifta frå banken står det då eit tal med minus framfor, og vi skjønner at vi står i gjeld til banken! Vi veit også at når det er kuldegradar ute, les vi det av som negative tal på gradestokken. Dei negative tala blir då konkrete, og vi kjenner at vi forstår dei.
",
+ | "language": "nn"
+ | }
+ | ],
+ | "created": "2018-02-13T17:54:22.000Z",
+ | "started": false,
+ | "updated": "2018-02-13T17:56:35.000Z",
+ | "comments": [],
+ | "priority": "unspecified",
+ | "revision": null,
+ | "copyright": null,
+ | "grepCodes": [],
+ | "metaImage": [
+ | {
+ | "altText": "Bannerbilde for temaet tall og algebra i faget 1T. Bilde.",
+ | "imageId": "1519",
+ | "language": "nb"
+ | },
+ | {
+ | "altText": "Bannerbilde for temaet tall og algebra i faget 1T. Bilde.",
+ | "imageId": "1519",
+ | "language": "nn"
+ | }
+ | ],
+ | "published": "2018-02-13T17:56:35.000Z",
+ | "updatedBy": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "conceptIds": [],
+ | "disclaimer": {},
+ | "articleType": "topic-article",
+ | "responsible": null,
+ | "availability": "everyone",
+ | "editorLabels": [],
+ | "introduction": [
+ | {
+ | "language": "nb",
+ | "introduction": "I temaet tallregning skal vi vurdere, velge og bruke matematiske metoder og verktøy til å løse problemer fra ulike fag og samfunnsområder, og reflektere over, vurdere og presentere løsningene på en hensiktsmessig måte."
+ | },
+ | {
+ | "language": "nn",
+ | "introduction": "I temaet talrekning skal vi vurdere, velje og bruke matematiske metodar og verktøy til å løyse problem frå ulike fag og samfunnsområde, og reflektere over, vurdere og presentere løysingane på ein føremålstenleg måte."
+ | }
+ | ],
+ | "revisionMeta": [
+ | {
+ | "id": "0b82b045-f1e4-48af-b122-68f63f86e274",
+ | "note": "Automatisk revisjonsdato satt av systemet.",
+ | "status": "needs-revision",
+ | "revisionDate": "2030-01-01T00:00:00.000Z"
+ | }
+ | ],
+ | "visualElement": [
+ | {
+ | "language": "nb",
+ | "resource": ""
+ | },
+ | {
+ | "language": "nn",
+ | "resource": ""
+ | }
+ | ],
+ | "relatedContent": [],
+ | "metaDescription": [
+ | {
+ | "content": "Tallregning handler om å behandle tall i praktisk regning, bruke regningsartene, addisjon, subtraksjon, divisjon og multiplikasjon.",
+ | "language": "nb"
+ | },
+ | {
+ | "content": "Tallrekning handlar om å handtere tal i praktisk rekning, bruke rekningsartane, addisjon, subtraksjon, divisjon og multiplikasjon.",
+ | "language": "nn"
+ | }
+ | ],
+ | "qualityEvaluation": null,
+ | "requiredLibraries": [],
+ | "previousVersionsNotes": [
+ | {
+ | "note": "Status endret",
+ | "user": "nd3KiThoNIQ0KX5flWoFxUqr",
+ | "status": {
+ | "other": [
+ | "IMPORTED"
+ | ],
+ | "current": "PUBLISHED"
+ | },
+ | "timestamp": "2020-06-08T14:02:49.000Z"
+ | }
+ | ]
+ |}
+ |""".stripMargin
+
+ val expected = parser.parse(newDocument).toTry.get
+ migration.convertColumn(oldDocument) should be(expected.noSpaces)
+ }
+
+}
diff --git a/draft-api/src/test/scala/no/ndla/draftapi/service/WriteServiceTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/service/WriteServiceTest.scala
index 2f43b90198..90b1a5fdb7 100644
--- a/draft-api/src/test/scala/no/ndla/draftapi/service/WriteServiceTest.scala
+++ b/draft-api/src/test/scala/no/ndla/draftapi/service/WriteServiceTest.scala
@@ -204,7 +204,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
val updatedCopyright = model.api.DraftCopyrightDTO(
Some(commonApi.LicenseDTO("a", Some("b"), None)),
Some("c"),
- Seq(commonApi.AuthorDTO("Opphavsmann", "Jonas")),
+ Seq(commonApi.AuthorDTO(ContributorType.Originator, "Jonas")),
List(),
List(),
None,
@@ -236,7 +236,16 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
title = Seq(Title(updatedTitle, "en")),
content = Seq(ArticleContent(updatedContent, "en")),
copyright = Some(
- DraftCopyright(Some("a"), Some("c"), Seq(Author("Opphavsmann", "Jonas")), List(), List(), None, None, false)
+ DraftCopyright(
+ Some("a"),
+ Some("c"),
+ Seq(Author(ContributorType.Originator, "Jonas")),
+ List(),
+ List(),
+ None,
+ None,
+ false
+ )
),
tags = Seq(Tag(Seq("en", "to", "tre"), "en")),
requiredLibraries = Seq(RequiredLibrary("tjup", "tjap", "tjim")),
@@ -916,7 +925,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
}
test("cloneEmbedAndUpdateElement updates file embeds") {
- import scala.jdk.CollectionConverters._
+ import scala.jdk.CollectionConverters.*
val embed1 =
s"""<$EmbedTagName data-alt="Kul alt1" data-path="/files/resources/abc123.pdf" data-resource="file" data-title="Kul tittel1" data-type="pdf" />"""
val embed2 =
diff --git a/draft-api/src/test/scala/no/ndla/draftapi/service/search/ArticleSearchServiceTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/service/search/ArticleSearchServiceTest.scala
index 91369a4ecc..609229a6d9 100644
--- a/draft-api/src/test/scala/no/ndla/draftapi/service/search/ArticleSearchServiceTest.scala
+++ b/draft-api/src/test/scala/no/ndla/draftapi/service/search/ArticleSearchServiceTest.scala
@@ -35,7 +35,7 @@ class ArticleSearchServiceTest extends IntegrationSuite(EnableElasticsearchConta
val byNcSa: DraftCopyright = DraftCopyright(
Some(License.CC_BY_NC_SA.toString),
Some("Gotham City"),
- List(Author("Forfatter", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -46,7 +46,7 @@ class ArticleSearchServiceTest extends IntegrationSuite(EnableElasticsearchConta
val publicDomain: DraftCopyright = DraftCopyright(
Some(License.PublicDomain.toString),
Some("Metropolis"),
- List(Author("Forfatter", "Bruce Wayne")),
+ List(Author(ContributorType.Writer, "Bruce Wayne")),
List(),
List(),
None,
@@ -57,7 +57,7 @@ class ArticleSearchServiceTest extends IntegrationSuite(EnableElasticsearchConta
val copyrighted: DraftCopyright = DraftCopyright(
Some(License.Copyrighted.toString),
Some("New York"),
- List(Author("Forfatter", "Clark Kent")),
+ List(Author(ContributorType.Writer, "Clark Kent")),
List(),
List(),
None,
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 dc67aa83cf..ddb63f1f7e 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
@@ -216,7 +216,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
DraftCopyright(
Some(CC_BY_SA.toString),
None,
- Seq(Author("processor", "navn")),
+ Seq(Author(ContributorType.Originator, "navn")),
List(),
List(),
None,
@@ -251,9 +251,9 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
copyright = Some(
DraftCopyright(
Some(CC_BY_SA.toString),
- None,
+ Some("plain text"),
Seq(),
- List(Author("rightsholder", "test")),
+ List(Author(ContributorType.Processor, "test")),
List(),
None,
None,
@@ -270,7 +270,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
DraftCopyright(
Some(CC_BY_SA.toString),
None,
- Seq(Author("author", "John Doe")),
+ Seq(Author(ContributorType.Writer, "John Doe")),
List(),
List(),
None,
@@ -288,7 +288,7 @@ class ContentValidatorTest extends UnitSuite with TestEnvironment {
DraftCopyright(
Some(License.CC_BY_SA.toString),
None,
- Seq(Author("author", "john
")),
+ Seq(Author(ContributorType.Writer, "john
")),
List(),
List(),
None,
diff --git a/image-api/src/main/scala/no/ndla/imageapi/ImageApiProperties.scala b/image-api/src/main/scala/no/ndla/imageapi/ImageApiProperties.scala
index d3e09486eb..39e3fd9013 100644
--- a/image-api/src/main/scala/no/ndla/imageapi/ImageApiProperties.scala
+++ b/image-api/src/main/scala/no/ndla/imageapi/ImageApiProperties.scala
@@ -47,44 +47,6 @@ class ImageApiProperties extends BaseProps with DatabaseProps with StrictLogging
"image/svg+xml"
)
- val oldCreatorTypes: List[String] = List(
- "opphavsmann",
- "fotograf",
- "kunstner",
- "forfatter",
- "manusforfatter",
- "innleser",
- "oversetter",
- "regissør",
- "illustratør",
- "medforfatter",
- "komponist"
- )
-
- val creatorTypes: List[String] = List(
- "originator",
- "photographer",
- "artist",
- "writer",
- "scriptwriter",
- "reader",
- "translator",
- "director",
- "illustrator",
- "cowriter",
- "composer"
- )
-
- val oldProcessorTypes: List[String] =
- List("bearbeider", "tilrettelegger", "redaksjonelt", "språklig", "ide", "sammenstiller", "korrektur")
- val processorTypes: List[String] =
- List("processor", "facilitator", "editorial", "linguistic", "idea", "compiler", "correction")
-
- val oldRightsholderTypes: List[String] = List("rettighetshaver", "forlag", "distributør", "leverandør")
- val rightsholderTypes: List[String] = List("rightsholder", "publisher", "distributor", "supplier")
-
- val allowedAuthors: Seq[String] = creatorTypes ++ processorTypes ++ rightsholderTypes
-
val IsoMappingCacheAgeInMs: Int = 1000 * 60 * 60 // 1 hour caching
val LicenseMappingCacheAgeInMs: Int = 1000 * 60 * 60 // 1 hour caching
diff --git a/image-api/src/main/scala/no/ndla/imageapi/db/migration/V21__FixContributorTypes.scala b/image-api/src/main/scala/no/ndla/imageapi/db/migration/V21__FixContributorTypes.scala
new file mode 100644
index 0000000000..d2de289d7e
--- /dev/null
+++ b/image-api/src/main/scala/no/ndla/imageapi/db/migration/V21__FixContributorTypes.scala
@@ -0,0 +1,55 @@
+/*
+ * Part of NDLA image-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.imageapi.db.migration
+
+import io.circe.{ACursor, parser}
+import io.circe.generic.auto.*
+import io.circe.syntax.EncoderOps
+import no.ndla.common.model.domain.{Author, ContributorType}
+import no.ndla.database.DocumentMigration
+
+class V21__FixContributorTypes extends DocumentMigration {
+ override val tableName: String = "imagemetadata"
+ override val columnName: String = "metadata"
+
+ override def convertColumn(value: String): String = {
+ val oldDocument = parser.parse(value).toTry.get
+ val copyright = oldDocument.hcursor.downField("copyright")
+ val creators = convertList(copyright, "creators")
+ val processors = convertList(copyright, "processors")
+ val rightsholders = convertList(copyright, "rightsholders")
+
+ val newDocument = oldDocument.hcursor
+ .downField("copyright")
+ .withFocus(_.mapObject(_.remove("creators").add("creators", creators.asJson)))
+ .withFocus(_.mapObject(_.remove("processors").add("processors", processors.asJson)))
+ .withFocus(_.mapObject(_.remove("rightsholders").add("rightsholders", rightsholders.asJson)))
+ newDocument.top.get.noSpaces
+ }
+
+ private def convertList(cursor: ACursor, fieldName: String): List[Author] = {
+ val field = cursor.downField(fieldName)
+ if (field.succeeded) {
+ field.as[Option[List[OldAuthor]]].toTry.get match {
+ case None => List.empty
+ case Some(authors) => convertAuthors(authors)
+ }
+ } else List.empty
+ }
+
+ private def convertAuthors(authors: List[OldAuthor]): List[Author] = {
+ authors.map(author => {
+ ContributorType.valueOf(author.`type`.toLowerCase) match {
+ case None => Author(ContributorType.mapping(author.`type`.toLowerCase), author.name)
+ case Some(a) => Author(a, author.name)
+ }
+ })
+ }
+}
+case class OldAuthor(`type`: String, name: String)
diff --git a/image-api/src/main/scala/no/ndla/imageapi/service/ValidationService.scala b/image-api/src/main/scala/no/ndla/imageapi/service/ValidationService.scala
index f86ba61aae..80eb594b09 100644
--- a/image-api/src/main/scala/no/ndla/imageapi/service/ValidationService.scala
+++ b/image-api/src/main/scala/no/ndla/imageapi/service/ValidationService.scala
@@ -9,10 +9,10 @@
package no.ndla.imageapi.service
import no.ndla.common.errors.{ValidationException, ValidationMessage}
-import no.ndla.common.model.domain.{Author, Tag, UploadedFile}
-import no.ndla.common.model.{domain => commonDomain}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, UploadedFile}
+import no.ndla.common.model.domain as commonDomain
import no.ndla.imageapi.Props
-import no.ndla.imageapi.model.domain._
+import no.ndla.imageapi.model.domain.*
import no.ndla.mapping.ISO639.get6391CodeFor6392CodeMappings
import no.ndla.mapping.License.getLicense
import org.jsoup.Jsoup
@@ -44,7 +44,7 @@ trait ValidationService {
return Some(
ValidationMessage(
"file",
- s"The file ${fn} is not a valid image file. Only valid type is '${ValidMimeTypes.mkString(",")}', but was '$actualMimeType'"
+ s"The file $fn is not a valid image file. Only valid type is '${ValidMimeTypes.mkString(",")}', but was '$actualMimeType'"
)
)
@@ -108,13 +108,15 @@ trait ValidationService {
Some(copyright.license),
copyright.rightsholders ++ copyright.creators ++ copyright.processors
) ++
- copyright.creators.flatMap(a => validateAuthor("copyright.creators", a, props.creatorTypes)) ++
- copyright.processors.flatMap(a => validateAuthor("copyright.processors", a, props.processorTypes)) ++
- copyright.rightsholders.flatMap(a => validateAuthor("copyright.rightsholders", a, props.rightsholderTypes)) ++
+ copyright.creators.flatMap(a => validateAuthor("copyright.creators", a, ContributorType.creators)) ++
+ copyright.processors.flatMap(a => validateAuthor("copyright.processors", a, ContributorType.processors)) ++
+ copyright.rightsholders.flatMap(a =>
+ validateAuthor("copyright.rightsholders", a, ContributorType.rightsholders)
+ ) ++
copyright.origin.flatMap(origin => containsNoHtml("copyright.origin", origin))
}
- def validateLicense(license: String): Seq[ValidationMessage] = {
+ private def validateLicense(license: String): Seq[ValidationMessage] = {
getLicense(license) match {
case None => Seq(ValidationMessage("license.license", s"$license is not a valid license"))
case _ => Seq()
@@ -130,21 +132,40 @@ trait ValidationService {
}
}
- def validateAuthor(fieldPath: String, author: Author, allowedTypes: Seq[String]): Seq[ValidationMessage] = {
- containsNoHtml(s"$fieldPath.type", author.`type`).toList ++
- containsNoHtml(s"$fieldPath.name", author.name).toList ++
- validateAuthorType(s"$fieldPath.type", author.`type`, allowedTypes).toList
+ private def validateAuthor(
+ fieldPath: String,
+ author: Author,
+ allowedTypes: Seq[ContributorType]
+ ): Seq[ValidationMessage] = {
+ containsNoHtml(s"$fieldPath.name", author.name).toList ++
+ validateAuthorType(s"$fieldPath.type", author.`type`, allowedTypes) ++
+ validateMinimumLength(s"$fieldPath.name", author.name, 1)
}
- def validateAuthorType(fieldPath: String, `type`: String, allowedTypes: Seq[String]): Option[ValidationMessage] = {
- if (allowedTypes.contains(`type`.toLowerCase)) {
+ private def validateAuthorType(
+ fieldPath: String,
+ `type`: ContributorType,
+ allowedTypes: Seq[ContributorType]
+ ): Option[ValidationMessage] = {
+ if (allowedTypes.contains(`type`)) {
None
} else {
Some(ValidationMessage(fieldPath, s"Author is of illegal type. Must be one of ${allowedTypes.mkString(", ")}"))
}
}
- def validateTags(tags: Seq[Tag], oldLanguages: Seq[String]): Seq[ValidationMessage] = {
+ private def validateMinimumLength(fieldPath: String, content: String, minLength: Int): Option[ValidationMessage] =
+ if (content.trim.length < minLength)
+ Some(
+ ValidationMessage(
+ fieldPath,
+ s"This field does not meet the minimum length requirement of $minLength characters"
+ )
+ )
+ else
+ None
+
+ private def validateTags(tags: Seq[Tag], oldLanguages: Seq[String]): Seq[ValidationMessage] = {
tags.flatMap(tagList => {
tagList.tags.flatMap(containsNoHtml("tags.tags", _)).toList :::
validateLanguage("tags.language", tagList.language, oldLanguages).toList
diff --git a/image-api/src/test/scala/no/ndla/imageapi/TestData.scala b/image-api/src/test/scala/no/ndla/imageapi/TestData.scala
index aa123c6aea..6d16a8eb76 100644
--- a/image-api/src/test/scala/no/ndla/imageapi/TestData.scala
+++ b/image-api/src/test/scala/no/ndla/imageapi/TestData.scala
@@ -10,7 +10,7 @@ package no.ndla.imageapi
import no.ndla.common.model.NDLADate
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag}
import no.ndla.common.model.api as commonApi
import no.ndla.imageapi.model.api
import no.ndla.imageapi.model.api.ImageMetaInformationV2DTO
@@ -50,9 +50,9 @@ trait TestData {
copyright = Copyright(
ByNcSa,
Some("http://www.scanpix.no"),
- List(Author("Fotograf", "Test Testesen")),
- List(Author("Redaksjonelt", "Kåre Knegg")),
- List(Author("Leverandør", "Leverans Leveransensen")),
+ List(Author(ContributorType.Photographer, "Test Testesen")),
+ List(Author(ContributorType.Editorial, "Kåre Knegg")),
+ List(Author(ContributorType.Supplier, "Leverans Leveransensen")),
None,
None,
false
@@ -82,7 +82,7 @@ trait TestData {
Some("https://creativecommons.org/licenses/by-nc-sa/4.0/")
),
Some("http://www.scanpix.no"),
- List(commonApi.AuthorDTO("Fotograf", "Test Testesen")),
+ List(commonApi.AuthorDTO(ContributorType.Photographer, "Test Testesen")),
List(),
List(),
None,
@@ -114,7 +114,7 @@ trait TestData {
Some("https://creativecommons.org/licenses/by-nc-sa/2.0/")
),
Some("http://www.scanpix.no"),
- List(commonApi.AuthorDTO("Fotograf", "Test Testesen")),
+ List(commonApi.AuthorDTO(ContributorType.Photographer, "Test Testesen")),
List(),
List(),
None,
@@ -151,7 +151,7 @@ trait TestData {
copyright = Copyright(
ByNcSa,
Some("http://www.scanpix.no"),
- List(Author("Fotograf", "Test Testesen")),
+ List(Author(ContributorType.Photographer, "Test Testesen")),
List(),
List(),
None,
@@ -188,7 +188,7 @@ trait TestData {
copyright = Copyright(
ByNcSa,
Some("http://www.scanpix.no"),
- List(Author("Fotograf", "Test Testesen")),
+ List(Author(ContributorType.Photographer, "Test Testesen")),
List(),
List(),
None,
@@ -225,7 +225,7 @@ trait TestData {
copyright = Copyright(
ByNcSa,
Some("http://www.scanpix.no"),
- List(Author("Fotograf", "Test Testesen")),
+ List(Author(ContributorType.Photographer, "Test Testesen")),
List(),
List(),
None,
@@ -262,7 +262,7 @@ trait TestData {
copyright = Copyright(
ByNcSa,
Some("http://www.scanpix.no"),
- List(Author("Fotograf", "Test Testesen")),
+ List(Author(ContributorType.Photographer, "Test Testesen")),
List(),
List(),
None,
@@ -299,7 +299,7 @@ trait TestData {
copyright = Copyright(
ByNcSa,
Some("http://www.scanpix.no"),
- List(Author("Fotograf", "Test Testesen")),
+ List(Author(ContributorType.Photographer, "Test Testesen")),
List(),
List(),
None,
@@ -336,7 +336,7 @@ trait TestData {
copyright = Copyright(
ByNcSa,
Some("http://www.scanpix.no"),
- List(Author("Fotograf", "Test Testesen")),
+ List(Author(ContributorType.Photographer, "Test Testesen")),
List(),
List(),
None,
diff --git a/image-api/src/test/scala/no/ndla/imageapi/controller/HealthControllerTest.scala b/image-api/src/test/scala/no/ndla/imageapi/controller/HealthControllerTest.scala
index 8aaa525585..6a51e7da56 100644
--- a/image-api/src/test/scala/no/ndla/imageapi/controller/HealthControllerTest.scala
+++ b/image-api/src/test/scala/no/ndla/imageapi/controller/HealthControllerTest.scala
@@ -9,7 +9,7 @@
package no.ndla.imageapi.controller
import no.ndla.common.model.NDLADate
-import no.ndla.common.model.domain.Author
+import no.ndla.common.model.domain.{Author, ContributorType}
import no.ndla.common.model.domain.article.Copyright
import no.ndla.imageapi.model.domain.{
ImageAltText,
@@ -38,7 +38,7 @@ class HealthControllerTest extends UnitSuite with TestEnvironment with TapirCont
Copyright(
License.Copyrighted.toString,
Some("New York"),
- Seq(Author("Forfatter", "Clark Kent")),
+ Seq(Author(ContributorType.Writer, "Clark Kent")),
Seq(),
Seq(),
None,
diff --git a/image-api/src/test/scala/no/ndla/imageapi/controller/ImageControllerV2Test.scala b/image-api/src/test/scala/no/ndla/imageapi/controller/ImageControllerV2Test.scala
index 43496c318a..2d14434df2 100644
--- a/image-api/src/test/scala/no/ndla/imageapi/controller/ImageControllerV2Test.scala
+++ b/image-api/src/test/scala/no/ndla/imageapi/controller/ImageControllerV2Test.scala
@@ -70,7 +70,7 @@ class ImageControllerV2Test extends UnitSuite with TestEnvironment with TapirCon
| "rightsholders": [],
| "processors": [
| {
- | "type": "Forfatter",
+ | "type": "writer",
| "name": "Wenche Heir"
| }
| ]
@@ -158,7 +158,7 @@ class ImageControllerV2Test extends UnitSuite with TestEnvironment with TapirCon
test("That GET / returns body and 200 when image exists") {
val testUrl = "http://test.test/1"
val expectedBody =
- s"""{"id":"1","metaUrl":"$testUrl","title":{"title":"Elg i busk","language":"nb"},"created":"2017-04-01T12:15:32Z","createdBy":"ndla124","modelRelease":"yes","alttext":{"alttext":"Elg i busk","language":"nb"},"imageUrl":"$testUrl","size":2865539,"contentType":"image/jpeg","copyright":{"license":{"license":"CC-BY-NC-SA-4.0","description":"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"origin":"http://www.scanpix.no","creators":[{"type":"Fotograf","name":"Test Testesen"}],"processors":[{"type":"Redaksjonelt","name":"Kåre Knegg"}],"rightsholders":[{"type":"Leverandør","name":"Leverans Leveransensen"}],"processed":false},"tags":{"tags":["rovdyr","elg"],"language":"nb"},"caption":{"caption":"Elg i busk","language":"nb"},"supportedLanguages":["nb"]}"""
+ s"""{"id":"1","metaUrl":"$testUrl","title":{"title":"Elg i busk","language":"nb"},"created":"2017-04-01T12:15:32Z","createdBy":"ndla124","modelRelease":"yes","alttext":{"alttext":"Elg i busk","language":"nb"},"imageUrl":"$testUrl","size":2865539,"contentType":"image/jpeg","copyright":{"license":{"license":"CC-BY-NC-SA-4.0","description":"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"origin":"http://www.scanpix.no","creators":[{"type":"photographer","name":"Test Testesen"}],"processors":[{"type":"editorial","name":"Kåre Knegg"}],"rightsholders":[{"type":"supplier","name":"Leverans Leveransensen"}],"processed":false},"tags":{"tags":["rovdyr","elg"],"language":"nb"},"caption":{"caption":"Elg i busk","language":"nb"},"supportedLanguages":["nb"]}"""
val expectedObject = CirceUtil.unsafeParseAs[api.ImageMetaInformationV2DTO](expectedBody)
when(readService.withId(1, None, None)).thenReturn(Success(Option(expectedObject)))
@@ -173,7 +173,7 @@ class ImageControllerV2Test extends UnitSuite with TestEnvironment with TapirCon
test("That GET / returns body with agreement license and authors") {
val testUrl = "http://test.test/1"
val expectedBody =
- s"""{"id":"1","metaUrl":"$testUrl","title":{"title":"Elg i busk","language":"nb"},"created":"2017-04-01T12:15:32Z","createdBy":"ndla124","modelRelease":"yes","alttext":{"alttext":"Elg i busk","language":"nb"},"imageUrl":"$testUrl","size":2865539,"contentType":"image/jpeg","copyright":{"license":{"license":"gnu","description":"gnuggert","url":"https://gnuli/"},"agreementId": 1,"origin":"http://www.scanpix.no","creators":[{"type":"Forfatter","name":"Knutulf Knagsen"}],"processors":[{"type":"Redaksjonelt","name":"Kåre Knegg"}],"rightsholders":[],"processed":false},"tags":{"tags":["rovdyr","elg"],"language":"nb"},"caption":{"caption":"Elg i busk","language":"nb"},"supportedLanguages":["nb"]}"""
+ s"""{"id":"1","metaUrl":"$testUrl","title":{"title":"Elg i busk","language":"nb"},"created":"2017-04-01T12:15:32Z","createdBy":"ndla124","modelRelease":"yes","alttext":{"alttext":"Elg i busk","language":"nb"},"imageUrl":"$testUrl","size":2865539,"contentType":"image/jpeg","copyright":{"license":{"license":"gnu","description":"gnuggert","url":"https://gnuli/"},"agreementId": 1,"origin":"http://www.scanpix.no","creators":[{"type":"writer","name":"Knutulf Knagsen"}],"processors":[{"type":"editorial","name":"Kåre Knegg"}],"rightsholders":[],"processed":false},"tags":{"tags":["rovdyr","elg"],"language":"nb"},"caption":{"caption":"Elg i busk","language":"nb"},"supportedLanguages":["nb"]}"""
val expectedObject = CirceUtil.unsafeParseAs[api.ImageMetaInformationV2DTO](expectedBody)
when(readService.withId(1, None, None)).thenReturn(Success(Option(expectedObject)))
@@ -189,7 +189,7 @@ class ImageControllerV2Test extends UnitSuite with TestEnvironment with TapirCon
test("That GET / returns body with original copyright if agreement doesnt exist") {
val testUrl = "http://test.test/1"
val expectedBody =
- s"""{"id":"1","metaUrl":"$testUrl","title":{"title":"Elg i busk","language":"nb"},"created":"2017-04-01T12:15:32Z","createdBy":"ndla124","modelRelease":"yes","alttext":{"alttext":"Elg i busk","language":"nb"},"imageUrl":"$testUrl","size":2865539,"contentType":"image/jpeg","copyright":{"license":{"license":"CC-BY-NC-SA-4.0","description":"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"}, "agreementId":1, "origin":"http://www.scanpix.no","creators":[{"type":"Fotograf","name":"Test Testesen"}],"processors":[{"type":"Redaksjonelt","name":"Kåre Knegg"}],"rightsholders":[{"type":"Leverandør","name":"Leverans Leveransensen"}],"processed":false},"tags":{"tags":["rovdyr","elg"],"language":"nb"},"caption":{"caption":"Elg i busk","language":"nb"},"supportedLanguages":["nb"]}"""
+ s"""{"id":"1","metaUrl":"$testUrl","title":{"title":"Elg i busk","language":"nb"},"created":"2017-04-01T12:15:32Z","createdBy":"ndla124","modelRelease":"yes","alttext":{"alttext":"Elg i busk","language":"nb"},"imageUrl":"$testUrl","size":2865539,"contentType":"image/jpeg","copyright":{"license":{"license":"CC-BY-NC-SA-4.0","description":"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"}, "agreementId":1, "origin":"http://www.scanpix.no","creators":[{"type":"photographer","name":"Test Testesen"}],"processors":[{"type":"editorial","name":"Kåre Knegg"}],"rightsholders":[{"type":"supplier","name":"Leverans Leveransensen"}],"processed":false},"tags":{"tags":["rovdyr","elg"],"language":"nb"},"caption":{"caption":"Elg i busk","language":"nb"},"supportedLanguages":["nb"]}"""
val expectedObject = CirceUtil.unsafeParseAs[api.ImageMetaInformationV2DTO](expectedBody)
when(readService.withId(1, None, None)).thenReturn(Success(Option(expectedObject)))
diff --git a/image-api/src/test/scala/no/ndla/imageapi/db/migration/V21__FixContributorTypesTest.scala b/image-api/src/test/scala/no/ndla/imageapi/db/migration/V21__FixContributorTypesTest.scala
new file mode 100644
index 0000000000..3d5705c819
--- /dev/null
+++ b/image-api/src/test/scala/no/ndla/imageapi/db/migration/V21__FixContributorTypesTest.scala
@@ -0,0 +1,147 @@
+/*
+ * Part of NDLA image-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.imageapi.db.migration
+
+import io.circe.parser
+import no.ndla.imageapi.{TestEnvironment, UnitSuite}
+
+class V21__FixContributorTypesTest extends UnitSuite with TestEnvironment {
+ test("That copyright has correct contributor types") {
+ val migration = new V21__FixContributorTypes
+ val oldDocument =
+ """
+ |{
+ | "tags": [
+ | {
+ | "tags": [
+ | "st. olav",
+ | "saint",
+ | "konge"
+ | ],
+ | "language": "nn"
+ | }
+ | ],
+ | "titles": [
+ | {
+ | "title": "Olavs død - alterbilde",
+ | "language": "nn"
+ | }
+ | ],
+ | "created": "2019-02-17T15:07:30.000Z",
+ | "updated": "2023-10-31T17:11:47.960Z",
+ | "alttexts": [
+ | {
+ | "alttext": "Hærmenn samles rundt den døde kongen. Illustrasjon.",
+ | "language": "nn"
+ | }
+ | ],
+ | "captions": [
+ | {
+ | "caption": "Olav der Heilige",
+ | "language": "nn"
+ | }
+ | ],
+ | "copyright": {
+ | "origin": "https://commons.wikimedia.org/wiki/File:Olav_der_Heilige06.jpg",
+ | "license": "PD",
+ | "processed": false,
+ | "creators": [
+ | {
+ | "type": "Photographer",
+ | "name": "Fingalo"
+ | }
+ | ],
+ | "processors": [],
+ | "rightsholders": []
+ | },
+ | "createdBy": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "updatedBy": "YsTwRNHTx5X4hKCrLASNs_Pr",
+ | "editorNotes": [
+ | {
+ | "note": "Added new language 'nn'.",
+ | "timeStamp": "2023-10-31T17:11:47.960Z",
+ | "updatedBy": "YsTwRNHTx5X4hKCrLASNs_Pr"
+ | },
+ | {
+ | "note": "Deleted language 'und'.",
+ | "timeStamp": "2023-10-31T17:12:00.891Z",
+ | "updatedBy": "YsTwRNHTx5X4hKCrLASNs_Pr"
+ | }
+ | ],
+ | "modelReleased": "not-applicable"
+ |}
+ |""".stripMargin
+ val newDocument =
+ """
+ |{
+ | "tags": [
+ | {
+ | "tags": [
+ | "st. olav",
+ | "saint",
+ | "konge"
+ | ],
+ | "language": "nn"
+ | }
+ | ],
+ | "titles": [
+ | {
+ | "title": "Olavs død - alterbilde",
+ | "language": "nn"
+ | }
+ | ],
+ | "created": "2019-02-17T15:07:30.000Z",
+ | "updated": "2023-10-31T17:11:47.960Z",
+ | "alttexts": [
+ | {
+ | "alttext": "Hærmenn samles rundt den døde kongen. Illustrasjon.",
+ | "language": "nn"
+ | }
+ | ],
+ | "captions": [
+ | {
+ | "caption": "Olav der Heilige",
+ | "language": "nn"
+ | }
+ | ],
+ | "copyright": {
+ | "origin": "https://commons.wikimedia.org/wiki/File:Olav_der_Heilige06.jpg",
+ | "license": "PD",
+ | "processed": false,
+ | "creators": [
+ | {
+ | "type": "photographer",
+ | "name": "Fingalo"
+ | }
+ | ],
+ | "processors": [],
+ | "rightsholders": []
+ | },
+ | "createdBy": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "updatedBy": "YsTwRNHTx5X4hKCrLASNs_Pr",
+ | "editorNotes": [
+ | {
+ | "note": "Added new language 'nn'.",
+ | "timeStamp": "2023-10-31T17:11:47.960Z",
+ | "updatedBy": "YsTwRNHTx5X4hKCrLASNs_Pr"
+ | },
+ | {
+ | "note": "Deleted language 'und'.",
+ | "timeStamp": "2023-10-31T17:12:00.891Z",
+ | "updatedBy": "YsTwRNHTx5X4hKCrLASNs_Pr"
+ | }
+ | ],
+ | "modelReleased": "not-applicable"
+ |}
+ |""".stripMargin
+
+ val expected = parser.parse(newDocument).toTry.get
+ migration.convertColumn(oldDocument) should be(expected.noSpaces)
+ }
+}
diff --git a/image-api/src/test/scala/no/ndla/imageapi/service/ReadServiceTest.scala b/image-api/src/test/scala/no/ndla/imageapi/service/ReadServiceTest.scala
index 164c841970..386eee8272 100644
--- a/image-api/src/test/scala/no/ndla/imageapi/service/ReadServiceTest.scala
+++ b/image-api/src/test/scala/no/ndla/imageapi/service/ReadServiceTest.scala
@@ -11,6 +11,7 @@ package no.ndla.imageapi.service
import no.ndla.common.CirceUtil
import no.ndla.common.model.domain.article.Copyright
import no.ndla.common.model.domain as common
+import no.ndla.common.model.domain.ContributorType
import no.ndla.imageapi.model.api.ImageMetaInformationV2DTO
import no.ndla.imageapi.model.domain.{ImageFileData, ImageMetaInformation, ModelReleasedStatus}
import no.ndla.imageapi.model.{InvalidUrlException, api, domain}
@@ -50,7 +51,7 @@ class ReadServiceTest extends UnitSuite with TestEnvironment {
val testRawUrl = s"${props.Domain}/image-api/raw/Elg.jpg"
val dateString = TestData.updated().asString
val expectedBody =
- s"""{"id":"1","metaUrl":"$testUrl","title":{"title":"Elg i busk","language":"nb"},"created":"$dateString","createdBy":"ndla124","modelRelease":"yes","alttext":{"alttext":"Elg i busk","language":"nb"},"imageUrl":"$testRawUrl","size":2865539,"contentType":"image/jpeg","copyright":{"license":{"license":"CC-BY-NC-SA-4.0","description":"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"}, "agreementId":1, "origin":"http://www.scanpix.no","creators":[{"type":"Fotograf","name":"Test Testesen"}],"processors":[{"type":"Redaksjonelt","name":"Kåre Knegg"}],"rightsholders":[{"type":"Leverandør","name":"Leverans Leveransensen"}],"processed":false},"tags":{"tags":["rovdyr","elg"],"language":"nb"},"caption":{"caption":"Elg i busk","language":"nb"},"supportedLanguages":["nb"]}"""
+ s"""{"id":"1","metaUrl":"$testUrl","title":{"title":"Elg i busk","language":"nb"},"created":"$dateString","createdBy":"ndla124","modelRelease":"yes","alttext":{"alttext":"Elg i busk","language":"nb"},"imageUrl":"$testRawUrl","size":2865539,"contentType":"image/jpeg","copyright":{"license":{"license":"CC-BY-NC-SA-4.0","description":"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"}, "agreementId":1, "origin":"http://www.scanpix.no","creators":[{"type":"photographer","name":"Test Testesen"}],"processors":[{"type":"editorial","name":"Kåre Knegg"}],"rightsholders":[{"type":"supplier","name":"Leverans Leveransensen"}],"processed":false},"tags":{"tags":["rovdyr","elg"],"language":"nb"},"caption":{"caption":"Elg i busk","language":"nb"},"supportedLanguages":["nb"]}"""
val expectedObject: ImageMetaInformationV2DTO = CirceUtil.unsafeParseAs[api.ImageMetaInformationV2DTO](expectedBody)
val agreementElg = new ImageMetaInformation(
@@ -73,9 +74,9 @@ class ReadServiceTest extends UnitSuite with TestEnvironment {
copyright = Copyright(
TestData.ByNcSa,
Some("http://www.scanpix.no"),
- List(common.Author("Fotograf", "Test Testesen")),
- List(common.Author("Redaksjonelt", "Kåre Knegg")),
- List(common.Author("Leverandør", "Leverans Leveransensen")),
+ List(common.Author(ContributorType.Photographer, "Test Testesen")),
+ List(common.Author(ContributorType.Editorial, "Kåre Knegg")),
+ List(common.Author(ContributorType.Supplier, "Leverans Leveransensen")),
None,
None,
false
diff --git a/image-api/src/test/scala/no/ndla/imageapi/service/ValidationServiceTest.scala b/image-api/src/test/scala/no/ndla/imageapi/service/ValidationServiceTest.scala
index 33084accec..14fee1cf3d 100644
--- a/image-api/src/test/scala/no/ndla/imageapi/service/ValidationServiceTest.scala
+++ b/image-api/src/test/scala/no/ndla/imageapi/service/ValidationServiceTest.scala
@@ -11,7 +11,7 @@ package no.ndla.imageapi.service
import no.ndla.common.errors.{ValidationException, ValidationMessage}
import no.ndla.common.model.NDLADate
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag, UploadedFile}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, UploadedFile}
import no.ndla.imageapi.model.domain.*
import no.ndla.imageapi.{TestEnvironment, UnitSuite}
import no.ndla.mapping.License.CC_BY
@@ -40,8 +40,16 @@ class ValidationServiceTest extends UnitSuite with TestEnvironment {
)
)
),
- copyright =
- Copyright(CC_BY.toString, None, Seq(Author("originator", "test")), Seq.empty, Seq.empty, None, None, false),
+ copyright = Copyright(
+ CC_BY.toString,
+ None,
+ Seq(Author(ContributorType.Originator, "test")),
+ Seq.empty,
+ Seq.empty,
+ None,
+ None,
+ false
+ ),
tags = Seq.empty,
captions = Seq.empty,
updatedBy = "ndla124",
@@ -105,8 +113,16 @@ class ValidationServiceTest extends UnitSuite with TestEnvironment {
test("validate returns a validation error if copyright contains an invalid license") {
val imageMeta =
sampleImageMeta.copy(
- copyright =
- Copyright("invalid", None, Seq(Author("originator", "test")), Seq.empty, Seq.empty, None, None, false)
+ copyright = Copyright(
+ "invalid",
+ None,
+ Seq(Author(ContributorType.Originator, "test")),
+ Seq.empty,
+ Seq.empty,
+ None,
+ None,
+ false
+ )
)
val result = validationService.validate(imageMeta, None)
val exception = result.failed.get.asInstanceOf[ValidationException]
@@ -120,7 +136,7 @@ class ValidationServiceTest extends UnitSuite with TestEnvironment {
copyright = Copyright(
CC_BY.toString,
Some("origin
"),
- Seq(Author("originator", "test")),
+ Seq(Author(ContributorType.Originator, "test")),
Seq.empty,
Seq.empty,
None,
@@ -140,7 +156,7 @@ class ValidationServiceTest extends UnitSuite with TestEnvironment {
copyright = Copyright(
CC_BY.toString,
None,
- Seq(Author("originator", "Drumpf
")),
+ Seq(Author(ContributorType.Originator, "Drumpf
")),
Seq.empty,
Seq.empty,
None,
@@ -160,7 +176,7 @@ class ValidationServiceTest extends UnitSuite with TestEnvironment {
copyright = Copyright(
CC_BY.toString,
Some("ntb"),
- Seq(Author("originator", "Drumpf")),
+ Seq(Author(ContributorType.Originator, "Drumpf")),
Seq.empty,
Seq.empty,
None,
@@ -171,12 +187,12 @@ class ValidationServiceTest extends UnitSuite with TestEnvironment {
validationService.validate(imageMeta, None).isSuccess should be(true)
}
- test("validate returns error if authortype is invalid") {
+ test("validate returns error if author is empty") {
val imageMeta = sampleImageMeta.copy(
copyright = Copyright(
CC_BY.toString,
Some("ntb"),
- Seq(Author("invalidType", "Drumpf")),
+ Seq(Author(ContributorType.Writer, "")),
Seq.empty,
Seq.empty,
None,
@@ -189,7 +205,7 @@ class ValidationServiceTest extends UnitSuite with TestEnvironment {
exception.errors.length should be(1)
exception.errors.head.message.contains(
- "Author is of illegal type. Must be one of originator, photographer, artist, writer, scriptwriter, reader, translator, director, illustrator, cowriter, composer"
+ "This field does not meet the minimum length requirement of 1 characters"
) should be(true)
}
diff --git a/image-api/src/test/scala/no/ndla/imageapi/service/WriteServiceTest.scala b/image-api/src/test/scala/no/ndla/imageapi/service/WriteServiceTest.scala
index fc752e0f72..6442a79114 100644
--- a/image-api/src/test/scala/no/ndla/imageapi/service/WriteServiceTest.scala
+++ b/image-api/src/test/scala/no/ndla/imageapi/service/WriteServiceTest.scala
@@ -10,7 +10,7 @@ package no.ndla.imageapi.service
import no.ndla.common.errors.ValidationException
import no.ndla.common.model.api.{CopyrightDTO, LicenseDTO, Missing, UpdateWith}
-import no.ndla.common.model.domain.UploadedFile
+import no.ndla.common.model.domain.{ContributorType, UploadedFile}
import no.ndla.common.model.{NDLADate, api as commonApi, domain as common}
import no.ndla.common.model.domain.article.Copyright as DomainCopyright
import no.ndla.imageapi.model.api.*
@@ -350,7 +350,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
CopyrightDTO(
LicenseDTO("testLic", Some("License for testing"), None),
Some("test"),
- List(commonApi.AuthorDTO("Opphavsmann", "Testerud")),
+ List(commonApi.AuthorDTO(ContributorType.Originator, "Testerud")),
List(),
List(),
None,
@@ -369,7 +369,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment {
copyright = DomainCopyright(
"testLic",
Some("test"),
- List(common.Author("Opphavsmann", "Testerud")),
+ List(common.Author(ContributorType.Originator, "Testerud")),
List(),
List(),
None,
diff --git a/image-api/src/test/scala/no/ndla/imageapi/service/search/ImageSearchServiceTest.scala b/image-api/src/test/scala/no/ndla/imageapi/service/search/ImageSearchServiceTest.scala
index 05ebfc9b18..a466e652db 100644
--- a/image-api/src/test/scala/no/ndla/imageapi/service/search/ImageSearchServiceTest.scala
+++ b/image-api/src/test/scala/no/ndla/imageapi/service/search/ImageSearchServiceTest.scala
@@ -10,7 +10,7 @@ package no.ndla.imageapi.service.search
import no.ndla.common.model.NDLADate
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{Author, Tag}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag}
import no.ndla.common.model.api as commonApi
import no.ndla.imageapi.model.domain.*
import no.ndla.imageapi.{TestEnvironment, UnitSuite}
@@ -53,7 +53,7 @@ class ImageSearchServiceTest
val byNcSa: Copyright = Copyright(
CC_BY_NC_SA.toString,
Some("Gotham City"),
- List(Author("Forfatter", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -64,7 +64,7 @@ class ImageSearchServiceTest
val publicDomain: Copyright = Copyright(
PublicDomain.toString,
Some("Metropolis"),
- List(Author("Forfatter", "Bruce Wayne")),
+ List(Author(ContributorType.Writer, "Bruce Wayne")),
List(),
List(),
None,
@@ -538,7 +538,7 @@ class ImageSearchServiceTest
}
test("That filtering for modelReleased works as expected") {
- import ModelReleasedStatus._
+ import ModelReleasedStatus.*
val Success(searchResult1) = imageSearchService.matchingQuery(
searchSettings.copy(
language = "*",
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 d1c1a7dcd0..13b25f20db 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
@@ -9,7 +9,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.{ContributorType, 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}
@@ -82,7 +82,7 @@ class ArticleApiClientTest
val testCopyright: common.draft.DraftCopyright = common.draft.DraftCopyright(
Some("CC-BY-SA-4.0"),
Some("Origin"),
- Seq(common.Author("Writer", "John doe")),
+ Seq(common.Author(ContributorType.Writer, "John doe")),
Seq.empty,
Seq.empty,
None,
diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migration/V43__FixContributorTypes.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migration/V43__FixContributorTypes.scala
new file mode 100644
index 0000000000..9cb4d1a40c
--- /dev/null
+++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migration/V43__FixContributorTypes.scala
@@ -0,0 +1,51 @@
+/*
+ * Part of NDLA learningpath-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.learningpathapi.db.migration
+
+import io.circe.{ACursor, parser}
+import io.circe.generic.auto.*
+import io.circe.syntax.EncoderOps
+import no.ndla.common.model.domain.{Author, ContributorType}
+import no.ndla.database.DocumentMigration
+
+class V43__FixContributorTypes extends DocumentMigration {
+ override val tableName: String = "learningpaths"
+ override val columnName: String = "document"
+
+ override def convertColumn(value: String): String = {
+ val oldDocument = parser.parse(value).toTry.get
+ val copyright = oldDocument.hcursor.downField("copyright")
+ val contributors = convertList(copyright, "contributors")
+
+ val newDocument = oldDocument.hcursor
+ .downField("copyright")
+ .withFocus(_.mapObject(_.remove("contributors").add("contributors", contributors.asJson)))
+ newDocument.top.get.noSpaces
+ }
+
+ private def convertList(cursor: ACursor, fieldName: String): List[Author] = {
+ val field = cursor.downField(fieldName)
+ if (field.succeeded) {
+ field.as[Option[List[OldAuthor]]].toTry.get match {
+ case None => List.empty
+ case Some(authors) => convertAuthors(authors)
+ }
+ } else List.empty
+ }
+
+ private def convertAuthors(authors: List[OldAuthor]): List[Author] = {
+ authors.map(author => {
+ ContributorType.valueOf(author.`type`.toLowerCase) match {
+ case None => Author(ContributorType.mapping(author.`type`.toLowerCase), author.name)
+ case Some(a) => Author(a, author.name)
+ }
+ })
+ }
+}
+case class OldAuthor(`type`: String, name: String)
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 8762347d00..ae46aec6ef 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
@@ -12,12 +12,12 @@ import cats.implicits.*
import io.lemonlabs.uri.typesafe.dsl.*
import no.ndla.common.errors.{AccessDeniedException, NotFoundException}
import no.ndla.common.implicits.OptionImplicit
-import no.ndla.common.model.domain.learningpath
+import no.ndla.common.model.domain.{ContributorType, learningpath}
import no.ndla.common.model.domain.learningpath.{
Description,
- Introduction,
EmbedType,
EmbedUrl,
+ Introduction,
LearningPath,
LearningPathStatus,
LearningPathVerificationStatus,
@@ -86,7 +86,7 @@ trait ConverterService {
val names = Array(user.first_name, user.middle_name, user.last_name)
.filter(_.isDefined)
.map(_.get)
- commonApi.AuthorDTO("Forfatter", names.mkString(" "))
+ commonApi.AuthorDTO(ContributorType.Writer, names.mkString(" "))
}
def asCoverPhoto(imageId: String): Option[CoverPhotoDTO] = {
@@ -441,7 +441,10 @@ trait ConverterService {
private def newDefaultCopyright(user: CombinedUser): CopyrightDTO = {
val contributors =
- user.myndlaUser.map(_.displayName).map(name => Seq(commonApi.AuthorDTO("Forfatter", name))).getOrElse(Seq.empty)
+ user.myndlaUser
+ .map(_.displayName)
+ .map(name => Seq(commonApi.AuthorDTO(ContributorType.Writer, name)))
+ .getOrElse(Seq.empty)
CopyrightDTO(asApiLicense(License.CC_BY.toString), contributors)
}
diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/validation/LearningPathValidator.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/validation/LearningPathValidator.scala
index 464b4e8908..953bc38557 100644
--- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/validation/LearningPathValidator.scala
+++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/validation/LearningPathValidator.scala
@@ -124,8 +124,7 @@ trait LearningPathValidator {
}
private def validateAuthor(author: Author): Seq[ValidationMessage] = {
- noHtmlTextValidator.validate("author.type", author.`type`).toList ++
- noHtmlTextValidator.validate("author.name", author.name).toList
+ noHtmlTextValidator.validate("author.name", author.name).toList
}
}
diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/db/migration/V43__FixContributorTypesTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/db/migration/V43__FixContributorTypesTest.scala
new file mode 100644
index 0000000000..af55a53209
--- /dev/null
+++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/db/migration/V43__FixContributorTypesTest.scala
@@ -0,0 +1,161 @@
+/*
+ * Part of NDLA learningpath-api
+ * Copyright (C) 2025 NDLA
+ *
+ * See LICENSE
+ *
+ */
+
+package no.ndla.learningpathapi.db.migration
+
+import io.circe.parser
+import no.ndla.learningpathapi.{TestEnvironment, UnitSuite}
+
+class V43__FixContributorTypesTest extends UnitSuite with TestEnvironment {
+ test("That copyright has correct contributor types") {
+ val migration = new V43__FixContributorTypes
+ val oldDocument =
+ """
+ |{
+ | "id": null,
+ | "tags": [
+ | {
+ | "tags": [
+ | "scale",
+ | "geology"
+ | ],
+ | "language": "und"
+ | },
+ | {
+ | "tags": [
+ | "avsetning",
+ | "erosjon",
+ | "geologi"
+ | ],
+ | "language": "nb"
+ | },
+ | {
+ | "tags": [
+ | "scale",
+ | "geology"
+ | ],
+ | "language": "en"
+ | },
+ | {
+ | "tags": [
+ | "geologi"
+ | ],
+ | "language": "nn"
+ | }
+ | ],
+ | "owner": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "title": [
+ | {
+ | "title": "Geologiske prosesser",
+ | "language": "nb"
+ | }
+ | ],
+ | "status": "PUBLISHED",
+ | "created": "2020-06-05T07:58:34.000Z",
+ | "message": null,
+ | "duration": 180,
+ | "revision": null,
+ | "copyright": {
+ | "license": "CC-BY-SA-4.0",
+ | "contributors": [
+ | {
+ | "type": "Redaksjonelt",
+ | "name": "Sissel Paaske"
+ | }
+ | ]
+ | },
+ | "isBasedOn": null,
+ | "externalId": null,
+ | "description": [
+ | {
+ | "language": "nb",
+ | "description": "Læringssti om temaet geologiske prosesser."
+ | }
+ | ],
+ | "lastUpdated": "2020-06-05T07:58:34.000Z",
+ | "coverPhotoId": "40683",
+ | "isMyNDLAOwner": false,
+ | "madeAvailable": "2020-06-05T07:58:34.000Z",
+ | "verificationStatus": "CREATED_BY_NDLA"
+ |}
+ |""".stripMargin
+ val newDocument =
+ """
+ |{
+ | "id": null,
+ | "tags": [
+ | {
+ | "tags": [
+ | "scale",
+ | "geology"
+ | ],
+ | "language": "und"
+ | },
+ | {
+ | "tags": [
+ | "avsetning",
+ | "erosjon",
+ | "geologi"
+ | ],
+ | "language": "nb"
+ | },
+ | {
+ | "tags": [
+ | "scale",
+ | "geology"
+ | ],
+ | "language": "en"
+ | },
+ | {
+ | "tags": [
+ | "geologi"
+ | ],
+ | "language": "nn"
+ | }
+ | ],
+ | "owner": "r0gHb9Xg3li4yyXv0QSGQczV3bviakrT",
+ | "title": [
+ | {
+ | "title": "Geologiske prosesser",
+ | "language": "nb"
+ | }
+ | ],
+ | "status": "PUBLISHED",
+ | "created": "2020-06-05T07:58:34.000Z",
+ | "message": null,
+ | "duration": 180,
+ | "revision": null,
+ | "copyright": {
+ | "license": "CC-BY-SA-4.0",
+ | "contributors": [
+ | {
+ | "type": "editorial",
+ | "name": "Sissel Paaske"
+ | }
+ | ]
+ | },
+ | "isBasedOn": null,
+ | "externalId": null,
+ | "description": [
+ | {
+ | "language": "nb",
+ | "description": "Læringssti om temaet geologiske prosesser."
+ | }
+ | ],
+ | "lastUpdated": "2020-06-05T07:58:34.000Z",
+ | "coverPhotoId": "40683",
+ | "isMyNDLAOwner": false,
+ | "madeAvailable": "2020-06-05T07:58:34.000Z",
+ | "verificationStatus": "CREATED_BY_NDLA"
+ |}
+ |""".stripMargin
+
+ val expected = parser.parse(newDocument).toTry.get
+ migration.convertColumn(oldDocument) should be(expected.noSpaces)
+ }
+}
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 191005d463..d37377b198 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
@@ -24,7 +24,7 @@ import no.ndla.common.model.domain.learningpath.{
StepStatus,
StepType
}
-import no.ndla.common.model.domain.{Author, Tag, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title}
import no.ndla.learningpathapi.*
import no.ndla.learningpathapi.model.domain.*
import no.ndla.mapping.License
@@ -44,8 +44,8 @@ class LearningPathRepositoryComponentIntegrationTest
var repository: LearningPathRepository = _
- val clinton: Author = Author("author", "Hilla the Hun")
- val license = License.PublicDomain.toString
+ val clinton: Author = Author(ContributorType.Writer, "Hilla the Hun")
+ val license: String = License.PublicDomain.toString
val copyright: LearningpathCopyright = LearningpathCopyright(license, List(clinton))
val DefaultLearningPath: LearningPath = LearningPath(
@@ -265,9 +265,9 @@ class LearningPathRepositoryComponentIntegrationTest
copyright = LearningpathCopyright(
"by",
List(
- Author("forfatter", "James Bond"),
- Author("forfatter", "Christian Bond"),
- Author("forfatter", "Jens Petrius")
+ Author(ContributorType.Writer, "James Bond"),
+ Author(ContributorType.Writer, "Christian Bond"),
+ Author(ContributorType.Writer, "Jens Petrius")
)
)
)
@@ -275,15 +275,15 @@ class LearningPathRepositoryComponentIntegrationTest
val privatePath = repository.insert(
DefaultLearningPath.copy(
- copyright = LearningpathCopyright("by", List(Author("forfatter", "Test testesen")))
+ copyright = LearningpathCopyright("by", List(Author(ContributorType.Writer, "Test testesen")))
)
)
val publicContributors = repository.allPublishedContributors
- publicContributors should contain(Author("forfatter", "James Bond"))
- publicContributors should contain(Author("forfatter", "Christian Bond"))
- publicContributors should contain(Author("forfatter", "Jens Petrius"))
- publicContributors should not contain Author("forfatter", "Test testesen")
+ publicContributors should contain(Author(ContributorType.Writer, "James Bond"))
+ publicContributors should contain(Author(ContributorType.Writer, "Christian Bond"))
+ publicContributors should contain(Author(ContributorType.Writer, "Jens Petrius"))
+ publicContributors should not contain Author(ContributorType.Writer, "Test testesen")
repository.deletePath(publicPath.id.get)
repository.deletePath(privatePath.id.get)
@@ -296,9 +296,9 @@ class LearningPathRepositoryComponentIntegrationTest
copyright = LearningpathCopyright(
"by",
List(
- Author("forfatter", "James Bond"),
- Author("forfatter", "Christian Bond"),
- Author("forfatter", "Jens Petrius")
+ Author(ContributorType.Writer, "James Bond"),
+ Author(ContributorType.Writer, "Christian Bond"),
+ Author(ContributorType.Writer, "Jens Petrius")
)
)
)
@@ -306,16 +306,18 @@ class LearningPathRepositoryComponentIntegrationTest
val publicPath2 = repository.insert(
DefaultLearningPath.copy(
status = LearningPathStatus.PUBLISHED,
- copyright =
- LearningpathCopyright("by", List(Author("forfatter", "James Bond"), Author("forfatter", "Test testesen")))
+ copyright = LearningpathCopyright(
+ "by",
+ List(Author(ContributorType.Writer, "James Bond"), Author(ContributorType.Writer, "Test testesen"))
+ )
)
)
val publicContributors = repository.allPublishedContributors
- publicContributors should contain(Author("forfatter", "James Bond"))
- publicContributors should contain(Author("forfatter", "Christian Bond"))
- publicContributors should contain(Author("forfatter", "Jens Petrius"))
- publicContributors should contain(Author("forfatter", "Test testesen"))
+ publicContributors should contain(Author(ContributorType.Writer, "James Bond"))
+ publicContributors should contain(Author(ContributorType.Writer, "Christian Bond"))
+ publicContributors should contain(Author(ContributorType.Writer, "Jens Petrius"))
+ publicContributors should contain(Author(ContributorType.Writer, "Test testesen"))
publicContributors.count(_.name == "James Bond") should be(1)
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 85d4b2870a..09dff9b057 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
@@ -10,7 +10,7 @@ package no.ndla.learningpathapi.service
import no.ndla.common.errors.{NotFoundException, ValidationException}
import no.ndla.common.model.domain.learningpath.*
-import no.ndla.common.model.domain.{Tag, Title}
+import no.ndla.common.model.domain.{ContributorType, Tag, Title}
import no.ndla.common.model.{NDLADate, api as commonApi}
import no.ndla.learningpathapi.model.api
import no.ndla.learningpathapi.model.api.{
@@ -34,7 +34,7 @@ import scala.util.{Failure, Success}
class ConverterServiceTest extends UnitSuite with UnitTestEnvironment {
import props.DefaultLanguage
- val clinton: commonApi.AuthorDTO = commonApi.AuthorDTO("author", "Crooked Hillary")
+ val clinton: commonApi.AuthorDTO = commonApi.AuthorDTO(ContributorType.Writer, "Crooked Hillary")
val license: commonApi.LicenseDTO =
commonApi.LicenseDTO(
License.PublicDomain.toString,
@@ -477,7 +477,7 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment {
}
test("New learningPaths get correct verification") {
- val apiRubio = commonApi.AuthorDTO("author", "Little Marco")
+ val apiRubio = commonApi.AuthorDTO(ContributorType.Writer, "Little Marco")
val apiLicense =
commonApi.LicenseDTO(
License.PublicDomain.toString,
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 55dd260985..7abac1e832 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
@@ -19,7 +19,7 @@ import no.ndla.common.model.domain.learningpath.{
StepStatus,
StepType
}
-import no.ndla.common.model.domain.{Author, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Title}
import no.ndla.learningpathapi.{UnitSuite, UnitTestEnvironment}
import no.ndla.mapping.License
import no.ndla.network.tapir.auth.TokenUser
@@ -38,8 +38,8 @@ class ReadServiceTest extends UnitSuite with UnitTestEnvironment {
val PUBLISHED_OWNER: TokenUser = TokenUser("published_owner", Set.empty, None)
val PRIVATE_OWNER: TokenUser = TokenUser("private_owner", Set.empty, None)
- val cruz: Author = Author("author", "Lyin' Ted")
- val license = License.PublicDomain.toString
+ val cruz: Author = Author(ContributorType.Writer, "Lyin' Ted")
+ val license: String = License.PublicDomain.toString
val copyright: LearningpathCopyright = LearningpathCopyright(license, List(cruz))
val PUBLISHED_LEARNINGPATH: LearningPath = LearningPath(
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 2dd148d07d..4012a2ce0c 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
@@ -10,7 +10,7 @@ package no.ndla.learningpathapi.service
import no.ndla.common.errors.{AccessDeniedException, NotFoundException, ValidationException}
import no.ndla.common.model.domain.learningpath.*
-import no.ndla.common.model.domain.{Author, Title, learningpath}
+import no.ndla.common.model.domain.{Author, ContributorType, Title, learningpath}
import no.ndla.common.model.{NDLADate, api as commonApi, domain as common}
import no.ndla.learningpathapi.*
import no.ndla.learningpathapi.model.*
@@ -143,10 +143,10 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment {
val UPDATED_STEPV2: UpdatedLearningStepV2DTO =
UpdatedLearningStepV2DTO(1, Option("Tittel"), None, "nb", Some("Beskrivelse"), None, Some(false), None, None)
- val rubio: Author = Author("author", "Little Marco")
- val license = License.PublicDomain.toString
+ val rubio: Author = Author(ContributorType.Writer, "Little Marco")
+ val license: String = License.PublicDomain.toString
val copyright: LearningpathCopyright = LearningpathCopyright(license, List(rubio))
- val apiRubio: commonApi.AuthorDTO = commonApi.AuthorDTO("author", "Little Marco")
+ val apiRubio: commonApi.AuthorDTO = commonApi.AuthorDTO(ContributorType.Writer, "Little Marco")
val apiLicense: commonApi.LicenseDTO =
commonApi.LicenseDTO(
License.PublicDomain.toString,
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 c81d3ec420..bc4ed27f73 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
@@ -21,7 +21,7 @@ import no.ndla.common.model.domain.learningpath.{
StepStatus,
StepType
}
-import no.ndla.common.model.domain.{Author, Tag, Title, learningpath}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title, learningpath}
import no.ndla.language.Language
import no.ndla.learningpathapi.TestData.searchSettings
import no.ndla.learningpathapi.model.domain.*
@@ -45,8 +45,8 @@ class SearchServiceTest
}
override val searchService: SearchService = new SearchService
- val paul: Author = Author("author", "Truly Weird Rand Paul")
- val license = License.PublicDomain.toString
+ val paul: Author = Author(ContributorType.Writer, "Truly Weird Rand Paul")
+ val license: String = License.PublicDomain.toString
val copyright: LearningpathCopyright = LearningpathCopyright(license, List(paul))
val DefaultLearningPath: LearningPath = LearningPath(
@@ -95,7 +95,9 @@ class SearchServiceTest
if (elasticSearchContainer.isSuccess) {
searchIndexService.createIndexAndAlias().get
- doReturn(commonApi.AuthorDTO("Forfatter", "En eier"), Nil*).when(converterService).asAuthor(any[NdlaUserName])
+ doReturn(commonApi.AuthorDTO(ContributorType.Writer, "En eier"), Nil*)
+ .when(converterService)
+ .asAuthor(any[NdlaUserName])
val today = NDLADate.now()
val yesterday = NDLADate.now().minusDays(1)
diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/validation/LearningPathValidatorTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/validation/LearningPathValidatorTest.scala
index eb63653bb5..97c00f4ed6 100644
--- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/validation/LearningPathValidatorTest.scala
+++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/validation/LearningPathValidatorTest.scala
@@ -9,7 +9,7 @@
package no.ndla.learningpathapi.validation
import no.ndla.common.errors.ValidationMessage
-import no.ndla.common.model.domain.{Author, Tag, Title}
+import no.ndla.common.model.domain.{Author, ContributorType, Tag, Title}
import no.ndla.common.model.domain.learningpath.{
Description,
LearningPath,
@@ -33,7 +33,7 @@ class LearningPathValidatorTest extends UnitSuite with TestEnvironment {
}
- val trump: Author = Author("author", "Donald Drumpf")
+ val trump: Author = Author(ContributorType.Writer, "Donald Drumpf")
val license: String = PublicDomain.toString
val copyright: LearningpathCopyright = LearningpathCopyright(license, List(trump))
@@ -288,9 +288,9 @@ class LearningPathValidatorTest extends UnitSuite with TestEnvironment {
test("That validate returns error when copyright.contributors contains html") {
validMock()
val invalidCopyright =
- ValidLearningPath.copyright.copy(contributors = List(Author("wizardry
", "Gandalf
")))
+ ValidLearningPath.copyright.copy(contributors = List(Author(ContributorType.Writer, "Gandalf
")))
val validationErrors = validator.validateLearningPath(ValidLearningPath.copy(copyright = invalidCopyright), false)
- validationErrors.size should be(2)
+ validationErrors.size should be(1)
}
test("That validate returns no errors when copyright.contributors contains no html") {
diff --git a/project/constantslib.scala b/project/constantslib.scala
index 90dc6f3683..ee82a07cc6 100644
--- a/project/constantslib.scala
+++ b/project/constantslib.scala
@@ -30,10 +30,12 @@ object constantslib extends Module {
typescriptGenerationImports := Seq(
"no.ndla.common.model.domain.config._",
"no.ndla.network.tapir.auth._",
+ "no.ndla.common.model.domain._",
"no.ndla.common.model.domain.draft._",
"no.ndla.common.model.domain.concept._"
),
typescriptExports := Seq(
+ "ContributorType",
"ConfigKey",
"DraftStatus",
"Permission",
diff --git a/project/learningpathapi.scala b/project/learningpathapi.scala
index fb1d2a6c00..2dec4a6418 100644
--- a/project/learningpathapi.scala
+++ b/project/learningpathapi.scala
@@ -28,8 +28,7 @@ object learningpathapi extends Module {
lazy val tsSettings: Seq[Def.Setting[?]] = typescriptSettings(
imports = Seq(
"no.ndla.learningpathapi.model.api._",
- "no.ndla.common.model.api._",
- "no.ndla.common.model.api.config._"
+ "no.ndla.common.model.api._"
),
exports = Seq(
"AuthorDTO",
@@ -44,7 +43,7 @@ object learningpathapi extends Module {
"LearningStepV2DTO",
"LicenseDTO",
"SearchResultV2DTO",
- "ConfigMetaRestrictedDTO",
+ "config.ConfigMetaRestrictedDTO",
"config.ConfigMetaDTO"
)
)
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 c8674c6b54..60572e290b 100644
--- a/search-api/src/test/scala/no/ndla/searchapi/TestData.scala
+++ b/search-api/src/test/scala/no/ndla/searchapi/TestData.scala
@@ -11,6 +11,7 @@ package no.ndla.searchapi
import no.ndla.common.configuration.Constants.EmbedTagName
import no.ndla.common.model.api.MyNDLABundleDTO
import no.ndla.common.model.api.search.LearningResourceType
+import no.ndla.common.model.domain.ContributorType
import no.ndla.common.model.domain.{
ArticleContent,
ArticleMetaImage,
@@ -80,7 +81,7 @@ object TestData {
Copyright(
License.CC_BY_NC_SA.toString,
Some("Gotham City"),
- List(Author("Writer", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -91,7 +92,7 @@ object TestData {
Copyright(
License.Copyrighted.toString,
Some("New York"),
- List(Author("Writer", "Clark Kent")),
+ List(Author(ContributorType.Writer, "Clark Kent")),
List(),
List(),
None,
@@ -301,7 +302,7 @@ object TestData {
content = List(
ArticleContent("Bilde av en bil flaggermusmann som vifter med vingene bil.", "nb")
),
- copyright = byNcSaCopyright.copy(creators = List(Author("Forfatter", "Kjekspolitiet"))),
+ copyright = byNcSaCopyright.copy(creators = List(Author(ContributorType.Writer, "Kjekspolitiet"))),
tags = List(Tag(List("fugl"), "nb")),
visualElement = List.empty,
introduction = List(Introduction("Batmen", "nb")),
@@ -317,7 +318,10 @@ object TestData {
title = List(Title("Pingvinen er ute og går", "nb")),
content = List(ArticleContent("Bilde av en
en pingvin som vagger borover en gate
", "nb")),
copyright = publicDomainCopyright
- .copy(creators = List(Author("Forfatter", "Pjolter")), processors = List(Author("Editorial", "Svims"))),
+ .copy(
+ creators = List(Author(ContributorType.Writer, "Pjolter")),
+ processors = List(Author(ContributorType.Editorial, "Svims"))
+ ),
tags = List(Tag(List("fugl"), "nb")),
visualElement = List.empty,
introduction = List(Introduction("Pingvinen", "nb")),
@@ -616,7 +620,7 @@ object TestData {
val draftByNcSaCopyright: DraftCopyright = draft.DraftCopyright(
Some(License.CC_BY_NC_SA.toString),
Some("Gotham City"),
- List(Author("Forfatter", "DC Comics")),
+ List(Author(ContributorType.Writer, "DC Comics")),
List(),
List(),
None,
@@ -627,7 +631,7 @@ object TestData {
val draftCopyrighted: DraftCopyright = draft.DraftCopyright(
Some(License.Copyrighted.toString),
Some("New York"),
- List(Author("Forfatter", "Clark Kent")),
+ List(Author(ContributorType.Writer, "Clark Kent")),
List(),
List(),
None,
@@ -685,7 +689,7 @@ object TestData {
tags = List(Tag(List("fugl"), "nb")),
created = today.minusDays(4),
updated = today.minusDays(3),
- copyright = Some(draftByNcSaCopyright.copy(creators = List(Author("Forfatter", "Kjekspolitiet")))),
+ copyright = Some(draftByNcSaCopyright.copy(creators = List(Author(ContributorType.Writer, "Kjekspolitiet")))),
grepCodes = Seq("K123", "K456")
)
@@ -701,7 +705,10 @@ object TestData {
updated = today.minusDays(2),
copyright = Some(
draftPublicDomainCopyright
- .copy(creators = List(Author("Forfatter", "Pjolter")), processors = List(Author("Editorial", "Svims")))
+ .copy(
+ creators = List(Author(ContributorType.Writer, "Pjolter")),
+ processors = List(Author(ContributorType.Editorial, "Svims"))
+ )
),
grepCodes = Seq("K456", "K123")
)
@@ -954,8 +961,8 @@ object TestData {
draft16
)
- val paul: Author = Author("author", "Truly Weird Rand Paul")
- val license = License.PublicDomain.toString
+ val paul: Author = Author(ContributorType.Writer, "Truly Weird Rand Paul")
+ val license: String = License.PublicDomain.toString
val copyright: LearningpathCopyright = common.learningpath.LearningpathCopyright(license, List(paul))
val visibleMetadata: Option[Metadata] = Some(Metadata(Seq.empty, visible = true, Map.empty))
val invisibleMetadata: Option[Metadata] = Some(Metadata(Seq.empty, visible = false, Map.empty))
@@ -1032,7 +1039,7 @@ object TestData {
duration = Some(5),
lastUpdated = today.minusDays(6),
tags = List(),
- copyright = copyright.copy(contributors = List(Author("Writer", "Svims")))
+ copyright = copyright.copy(contributors = List(Author(ContributorType.Writer, "Svims")))
)
val learningPath6: LearningPath = DefaultLearningPath.copy(
diff --git a/search-api/src/test/scala/no/ndla/searchapi/model/search/SearchableLearningPathTest.scala b/search-api/src/test/scala/no/ndla/searchapi/model/search/SearchableLearningPathTest.scala
index d12f8051e9..0f0ac38b16 100644
--- a/search-api/src/test/scala/no/ndla/searchapi/model/search/SearchableLearningPathTest.scala
+++ b/search-api/src/test/scala/no/ndla/searchapi/model/search/SearchableLearningPathTest.scala
@@ -11,6 +11,7 @@ package no.ndla.searchapi.model.search
import no.ndla.common.CirceUtil
import no.ndla.common.model.api.search.LearningResourceType
import no.ndla.common.model.api.{AuthorDTO, LicenseDTO}
+import no.ndla.common.model.domain.ContributorType
import no.ndla.common.model.domain.learningpath.{LearningPathStatus, LearningPathVerificationStatus, StepType}
import no.ndla.mapping.License
import no.ndla.search.model.{LanguageValue, SearchableLanguageList, SearchableLanguageValues}
@@ -60,7 +61,7 @@ class SearchableLearningPathTest extends UnitSuite with TestEnvironment {
license = License.CC_BY_SA.toString,
copyright = CopyrightDTO(
LicenseDTO(License.CC_BY_SA.toString, Some("bysasaa"), None),
- Seq(AuthorDTO("Supplier", "Jonas"), AuthorDTO("Originator", "Kakemonsteret"))
+ Seq(AuthorDTO(ContributorType.Supplier, "Jonas"), AuthorDTO(ContributorType.Originator, "Kakemonsteret"))
),
isBasedOn = Some(1001),
supportedLanguages = List("nb", "en", "nn"),
diff --git a/search-api/src/test/scala/no/ndla/searchapi/service/search/ArticleIndexServiceTest.scala b/search-api/src/test/scala/no/ndla/searchapi/service/search/ArticleIndexServiceTest.scala
index 319d05604f..b2634959c0 100644
--- a/search-api/src/test/scala/no/ndla/searchapi/service/search/ArticleIndexServiceTest.scala
+++ b/search-api/src/test/scala/no/ndla/searchapi/service/search/ArticleIndexServiceTest.scala
@@ -12,7 +12,14 @@ import io.circe.syntax.*
import com.sksamuel.elastic4s.ElasticDsl.*
import no.ndla.common.CirceUtil
import no.ndla.common.model.domain.article.Copyright
-import no.ndla.common.model.domain.{ArticleContent, ArticleMetaImage, Author, Description, VisualElement}
+import no.ndla.common.model.domain.{
+ ArticleContent,
+ ArticleMetaImage,
+ Author,
+ ContributorType,
+ Description,
+ VisualElement
+}
import no.ndla.scalatestsuite.IntegrationSuite
import no.ndla.search.TestUtility.{getFields, getMappingFields}
import no.ndla.searchapi.TestData.*
@@ -137,9 +144,9 @@ class ArticleIndexServiceTest
copyright = Copyright(
license = "CC-BY-SA-4.0",
origin = None,
- creators = Seq(Author("writer", "hå")),
- processors = Seq(Author("writer", "hå")),
- rightsholders = Seq(Author("writer", "hå")),
+ creators = Seq(Author(ContributorType.Writer, "hå")),
+ processors = Seq(Author(ContributorType.Writer, "hå")),
+ rightsholders = Seq(Author(ContributorType.Writer, "hå")),
validFrom = None,
validTo = None,
processed = false
diff --git a/search-api/src/test/scala/no/ndla/searchapi/service/search/DraftIndexServiceTest.scala b/search-api/src/test/scala/no/ndla/searchapi/service/search/DraftIndexServiceTest.scala
index 25c1149996..77bf53c472 100644
--- a/search-api/src/test/scala/no/ndla/searchapi/service/search/DraftIndexServiceTest.scala
+++ b/search-api/src/test/scala/no/ndla/searchapi/service/search/DraftIndexServiceTest.scala
@@ -11,7 +11,7 @@ package no.ndla.searchapi.service.search
import io.circe.syntax.*
import no.ndla.common.model.NDLADate
import no.ndla.common.model.domain.draft.{DraftCopyright, DraftStatus, RevisionMeta, RevisionStatus}
-import no.ndla.common.model.domain.{ArticleContent, Author, EditorNote, Responsible, Status}
+import no.ndla.common.model.domain.{ArticleContent, Author, ContributorType, EditorNote, Responsible, Status}
import no.ndla.scalatestsuite.IntegrationSuite
import no.ndla.search.TestUtility.{getFields, getMappingFields}
import no.ndla.searchapi.model.domain.IndexingBundle
@@ -65,9 +65,9 @@ class DraftIndexServiceTest
DraftCopyright(
license = Some("hei"),
origin = Some("ho"),
- creators = Seq(Author("writer", "Jonas")),
- processors = Seq(Author("writer", "Jonas")),
- rightsholders = Seq(Author("writer", "Jonas")),
+ creators = Seq(Author(ContributorType.Writer, "Jonas")),
+ processors = Seq(Author(ContributorType.Writer, "Jonas")),
+ rightsholders = Seq(Author(ContributorType.Writer, "Jonas")),
validFrom = Some(now),
validTo = Some(now),
false
diff --git a/search/src/test/scala/no/ndla/search/SearchConverterTest.scala b/search/src/test/scala/no/ndla/search/SearchConverterTest.scala
index 5db421ad79..396e9c0462 100644
--- a/search/src/test/scala/no/ndla/search/SearchConverterTest.scala
+++ b/search/src/test/scala/no/ndla/search/SearchConverterTest.scala
@@ -5,6 +5,7 @@
* See LICENSE
*
*/
+
package no.ndla.search
import no.ndla.scalatestsuite.UnitTestSuite
diff --git a/typescript/constants-backend/index.ts b/typescript/constants-backend/index.ts
index 3eb47477ce..4fdc3f980d 100644
--- a/typescript/constants-backend/index.ts
+++ b/typescript/constants-backend/index.ts
@@ -7,6 +7,8 @@ export enum ConfigKeyEnum {
MY_NDLA_WRITE_RESTRICTED = "MY_NDLA_WRITE_RESTRICTED",
}
+export type ContributorType = ("artist" | "cowriter" | "compiler" | "composer" | "correction" | "director" | "distributor" | "editorial" | "facilitator" | "idea" | "illustrator" | "linguistic" | "originator" | "photographer" | "processor" | "publisher" | "reader" | "rightsholder" | "scriptwriter" | "supplier" | "translator" | "writer")
+
export type DraftStatus = DraftStatusEnum
export enum DraftStatusEnum {
diff --git a/typescript/types-backend/article-api.ts b/typescript/types-backend/article-api.ts
index 454fb3c079..e07308d01d 100644
--- a/typescript/types-backend/article-api.ts
+++ b/typescript/types-backend/article-api.ts
@@ -13,6 +13,8 @@ export enum ArticleSortEnum {
export type Availability = ("everyone" | "teacher")
+export type ContributorType = ("artist" | "cowriter" | "compiler" | "composer" | "correction" | "director" | "distributor" | "editorial" | "facilitator" | "idea" | "illustrator" | "linguistic" | "originator" | "photographer" | "processor" | "publisher" | "reader" | "rightsholder" | "scriptwriter" | "supplier" | "translator" | "writer")
+
export interface IArticleContentV2DTO {
content: string
language: string
@@ -118,7 +120,7 @@ export interface IArticleV2DTO {
}
export interface IAuthorDTO {
- type: string
+ type: ContributorType
name: string
}
diff --git a/typescript/types-backend/audio-api.ts b/typescript/types-backend/audio-api.ts
index a31ed95b79..1327514b35 100644
--- a/typescript/types-backend/audio-api.ts
+++ b/typescript/types-backend/audio-api.ts
@@ -11,6 +11,8 @@ export enum AudioSortEnum {
ByIdAsc = "id",
}
+export type ContributorType = ("artist" | "cowriter" | "compiler" | "composer" | "correction" | "director" | "distributor" | "editorial" | "facilitator" | "idea" | "illustrator" | "linguistic" | "originator" | "photographer" | "processor" | "publisher" | "reader" | "rightsholder" | "scriptwriter" | "supplier" | "translator" | "writer")
+
export interface IAudioDTO {
url: string
mimeType: string
@@ -56,7 +58,7 @@ export interface IAudioSummarySearchResultDTO {
}
export interface IAuthorDTO {
- type: string
+ type: ContributorType
name: string
}
diff --git a/typescript/types-backend/concept-api.ts b/typescript/types-backend/concept-api.ts
index cc6be87c63..b7804f0eda 100644
--- a/typescript/types-backend/concept-api.ts
+++ b/typescript/types-backend/concept-api.ts
@@ -19,8 +19,10 @@ export enum ConceptSortEnum {
ByConceptTypeDesc = "-conceptType",
}
+export type ContributorType = ("artist" | "cowriter" | "compiler" | "composer" | "correction" | "director" | "distributor" | "editorial" | "facilitator" | "idea" | "illustrator" | "linguistic" | "originator" | "photographer" | "processor" | "publisher" | "reader" | "rightsholder" | "scriptwriter" | "supplier" | "translator" | "writer")
+
export interface IAuthorDTO {
- type: string
+ type: ContributorType
name: string
}
diff --git a/typescript/types-backend/draft-api.ts b/typescript/types-backend/draft-api.ts
index 5a5fae540c..dd27eb4d12 100644
--- a/typescript/types-backend/draft-api.ts
+++ b/typescript/types-backend/draft-api.ts
@@ -2,6 +2,8 @@
export type Availability = ("everyone" | "teacher")
+export type ContributorType = ("artist" | "cowriter" | "compiler" | "composer" | "correction" | "director" | "distributor" | "editorial" | "facilitator" | "idea" | "illustrator" | "linguistic" | "originator" | "photographer" | "processor" | "publisher" | "reader" | "rightsholder" | "scriptwriter" | "supplier" | "translator" | "writer")
+
export enum DraftSortEnum {
ByRelevanceDesc = "-relevance",
ByRelevanceAsc = "relevance",
@@ -117,7 +119,7 @@ export interface IArticleTitleDTO {
}
export interface IAuthorDTO {
- type: string
+ type: ContributorType
name: string
}
diff --git a/typescript/types-backend/image-api.ts b/typescript/types-backend/image-api.ts
index fdb9641c5a..f2d283080a 100644
--- a/typescript/types-backend/image-api.ts
+++ b/typescript/types-backend/image-api.ts
@@ -1,7 +1,9 @@
// DO NOT EDIT: generated file by scala-tsi
+export type ContributorType = ("artist" | "cowriter" | "compiler" | "composer" | "correction" | "director" | "distributor" | "editorial" | "facilitator" | "idea" | "illustrator" | "linguistic" | "originator" | "photographer" | "processor" | "publisher" | "reader" | "rightsholder" | "scriptwriter" | "supplier" | "translator" | "writer")
+
export interface IAuthorDTO {
- type: string
+ type: ContributorType
name: string
}
diff --git a/typescript/types-backend/learningpath-api.ts b/typescript/types-backend/learningpath-api.ts
index 5f86b581ff..9b414bbf49 100644
--- a/typescript/types-backend/learningpath-api.ts
+++ b/typescript/types-backend/learningpath-api.ts
@@ -1,7 +1,9 @@
// DO NOT EDIT: generated file by scala-tsi
+export type ContributorType = ("artist" | "cowriter" | "compiler" | "composer" | "correction" | "director" | "distributor" | "editorial" | "facilitator" | "idea" | "illustrator" | "linguistic" | "originator" | "photographer" | "processor" | "publisher" | "reader" | "rightsholder" | "scriptwriter" | "supplier" | "translator" | "writer")
+
export interface IAuthorDTO {
- type: string
+ type: ContributorType
name: string
}