Skip to content

Commit

Permalink
Merge pull request #451 from NDLANO/concept-index
Browse files Browse the repository at this point in the history
search-api: Index concepts from concept-api
  • Loading branch information
jnatten authored May 15, 2024
2 parents fc4866c + 390ceaf commit 0317e95
Show file tree
Hide file tree
Showing 92 changed files with 1,748 additions and 687 deletions.
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ lazy val constants = Module.setup(
network,
language,
mapping,
`concept-api`,
testWith(scalatestsuite)
)
)
Expand Down
19 changes: 18 additions & 1 deletion common/src/main/scala/no/ndla/common/CirceUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

package no.ndla.common

import io.circe.{Decoder, Encoder, HCursor, parser}
import enumeratum.*
import io.circe.syntax.*
import io.circe.*

import scala.util.{Failure, Try}

Expand Down Expand Up @@ -38,4 +39,20 @@ object CirceUtil {
cur.downField(key).as[Option[T]].map(_.getOrElse(default))
}

private val stringDecoder = implicitly[Decoder[String]]

/** Trait that does the same as `CirceEnum`, but with slightly better error message */
trait CirceEnumWithErrors[A <: EnumEntry] extends CirceEnum[A] {
this: Enum[A] =>
override implicit val circeDecoder: Decoder[A] = (c: HCursor) =>
stringDecoder(c).flatMap { s =>
withNameEither(s).left.map { notFound =>
val enumName = this.getClass.getSimpleName.stripSuffix("$")
val enumList = s"[${notFound.enumValues.mkString("'", "','", "'")}]"
val message = s"'${notFound.notFoundName}' is not a member of enum '$enumName'. Must be one of $enumList"
DecodingFailure(message, c.history)
}
}
}

}
7 changes: 4 additions & 3 deletions common/src/main/scala/no/ndla/common/errors/NDLAErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ object AccessDeniedException {
def forbidden: AccessDeniedException =
AccessDeniedException("User is missing required permission(s) to perform this operation")
}
case class NotFoundException(message: String) extends RuntimeException(message)
case class RollbackException(ex: Throwable) extends RuntimeException
case class FileTooBigException() extends RuntimeException
case class NotFoundException(message: String) extends RuntimeException(message)
case class RollbackException(ex: Throwable) extends RuntimeException
case class FileTooBigException() extends RuntimeException
case class InvalidStatusException(message: String) extends RuntimeException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.common.model.domain.concept

import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
import no.ndla.common.model.NDLADate
import no.ndla.common.model.domain.draft.DraftCopyright
import no.ndla.common.model.domain.{Content, Responsible, Tag, Title}
import no.ndla.language.Language.getSupportedLanguages

case class Concept(
id: Option[Long],
revision: Option[Int],
title: Seq[Title],
content: Seq[ConceptContent],
copyright: Option[DraftCopyright],
created: NDLADate,
updated: NDLADate,
updatedBy: Seq[String],
metaImage: Seq[ConceptMetaImage],
tags: Seq[Tag],
subjectIds: Set[String],
articleIds: Seq[Long],
status: Status,
visualElement: Seq[VisualElement],
responsible: Option[Responsible],
conceptType: ConceptType,
glossData: Option[GlossData],
editorNotes: Seq[ConceptEditorNote]
) extends Content {
def supportedLanguages: Set[String] =
getSupportedLanguages(title, content, tags, visualElement, metaImage).toSet
}

object Concept {
implicit val encoder: Encoder[Concept] = deriveEncoder
implicit val decoder: Decoder[Concept] = deriveDecoder
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/*
* Part of NDLA concept-api
* Copyright (C) 2019 NDLA
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.conceptapi.model.domain
package no.ndla.common.model.domain.concept

import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
import no.ndla.language.model.LanguageField

case class ConceptContent(content: String, language: String) extends LanguageField[String] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Part of NDLA common.
* Copyright (C) 2024 NDLA
*
* See LICENSE
*
*/

package no.ndla.common.model.domain.concept

import no.ndla.common.model.NDLADate
import io.circe.{Encoder, Decoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}

case class ConceptEditorNote(
note: String,
user: String,
status: Status,
timestamp: NDLADate
)

object ConceptEditorNote {
implicit val encoder: Encoder[ConceptEditorNote] = deriveEncoder
implicit val decoder: Decoder[ConceptEditorNote] = deriveDecoder
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Part of NDLA concept-api
* Copyright (C) 2019 NDLA
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.conceptapi.model.domain
package no.ndla.common.model.domain.concept

import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.common.model.domain.concept

import enumeratum.*
import no.ndla.common.errors.ValidationException

import scala.util.{Failure, Success, Try}

sealed trait ConceptStatus extends EnumEntry {}
object ConceptStatus extends Enum[ConceptStatus] with CirceEnum[ConceptStatus] {
case object IN_PROGRESS extends ConceptStatus
case object EXTERNAL_REVIEW extends ConceptStatus
case object INTERNAL_REVIEW extends ConceptStatus
case object QUALITY_ASSURANCE extends ConceptStatus
case object LANGUAGE extends ConceptStatus
case object FOR_APPROVAL extends ConceptStatus
case object END_CONTROL extends ConceptStatus
case object PUBLISHED extends ConceptStatus
case object UNPUBLISHED extends ConceptStatus
case object ARCHIVED extends ConceptStatus

val values: IndexedSeq[ConceptStatus] = findValues

def valueOfOrError(s: String): Try[ConceptStatus] =
valueOf(s) match {
case Some(st) => Success(st)
case None =>
val validStatuses = values.map(_.toString).mkString(", ")
Failure(
ValidationException(
"status",
s"'$s' is not a valid concept status. Must be one of $validStatuses"
)
)
}

def valueOf(s: String): Option[ConceptStatus] = values.find(_.toString == s.toUpperCase)

val thatDoesNotRequireResponsible: Seq[ConceptStatus] = Seq(PUBLISHED, UNPUBLISHED, ARCHIVED)
val thatRequiresResponsible: Set[ConceptStatus] = this.values.filterNot(thatDoesNotRequireResponsible.contains).toSet

implicit def ordering[A <: ConceptStatus]: Ordering[ConceptStatus] =
(x: ConceptStatus, y: ConceptStatus) => indexOf(x) - indexOf(y)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.common.model.domain.concept

import enumeratum.*
import no.ndla.common.CirceUtil.CirceEnumWithErrors
import no.ndla.common.errors.InvalidStatusException

import scala.util.{Failure, Success, Try}

sealed abstract class ConceptType(override val entryName: String) extends EnumEntry {
override def toString: String = entryName
}

object ConceptType extends Enum[ConceptType] with CirceEnumWithErrors[ConceptType] {
case object CONCEPT extends ConceptType("concept")
case object GLOSS extends ConceptType("gloss")

def all: Seq[String] = ConceptType.values.map(_.toString)
def valueOf(s: String): Option[ConceptType] = ConceptType.values.find(_.toString == s)
def valueOf(s: Option[String]): Option[ConceptType] = s.flatMap(valueOf)

def valueOfOrError(s: String): Try[ConceptType] = {
valueOf(s) match {
case None =>
Failure(InvalidStatusException(s"'$s' is not a valid concept type. Valid options are ${all.mkString(", ")}."))
case Some(conceptType) => Success(conceptType)
}
}

override def values: IndexedSeq[ConceptType] = findValues
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Part of NDLA concept-api
* Copyright (C) 2023 NDLA
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.conceptapi.model.domain
package no.ndla.common.model.domain.concept

import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.common.model.domain.concept

import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}

case class GlossExample(example: String, language: String, transcriptions: Map[String, String])

object GlossExample {
implicit val encoder: Encoder[GlossExample] = deriveEncoder
implicit val decoder: Decoder[GlossExample] = deriveDecoder
}

case class GlossData(
gloss: String,
wordClass: WordClass,
originalLanguage: String,
transcriptions: Map[String, String],
examples: List[List[GlossExample]]
)

object GlossData {
implicit val encoder: Encoder[GlossData] = deriveEncoder
implicit val decoder: Decoder[GlossData] = deriveDecoder
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Part of NDLA concept-api
* Copyright (C) 2020 NDLA
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.conceptapi.model.domain
package no.ndla.common.model.domain.concept

import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Part of NDLA concept-api
* Copyright (C) 2019 NDLA
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.conceptapi.model.domain
package no.ndla.common.model.domain.concept

import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
/*
* Part of NDLA concept-api
* Copyright (C) 2023 NDLA
* Part of NDLA common
* Copyright (C) 2024 NDLA
*
* See LICENSE
*/

package no.ndla.conceptapi.model.domain
package no.ndla.common.model.domain.concept

import com.scalatsi.TypescriptType.TSEnum
import com.scalatsi.{TSNamedType, TSType}
import enumeratum._
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
import no.ndla.conceptapi.model.api.InvalidStatusException
import enumeratum.*
import no.ndla.common.errors.InvalidStatusException

import scala.util.{Failure, Success, Try}

Expand Down Expand Up @@ -77,23 +75,3 @@ object WordClass extends Enum[WordClass] with CirceEnum[WordClass] {
TSEnum.string("WordClassEnum", tsEnumValues: _*)
)
}

case class GlossExample(example: String, language: String, transcriptions: Map[String, String])

object GlossExample {
implicit val encoder: Encoder[GlossExample] = deriveEncoder
implicit val decoder: Decoder[GlossExample] = deriveDecoder
}

case class GlossData(
gloss: String,
wordClass: WordClass,
originalLanguage: String,
transcriptions: Map[String, String],
examples: List[List[GlossExample]]
)

object GlossData {
implicit val encoder: Encoder[GlossData] = deriveEncoder
implicit val decoder: Decoder[GlossData] = deriveDecoder
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
package no.ndla.conceptapi.controller

import no.ndla.common.model.api.CommaSeparatedList._
import no.ndla.common.model.domain.concept.ConceptType
import no.ndla.conceptapi.Props
import no.ndla.conceptapi.model.domain.{ConceptType, Sort}
import no.ndla.conceptapi.model.domain.Sort
import no.ndla.language.Language
import sttp.tapir._
import sttp.tapir.model.Delimited
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ package no.ndla.conceptapi.controller
import cats.implicits._
import no.ndla.common.implicits._
import no.ndla.common.model.api.CommaSeparatedList._
import no.ndla.common.model.domain.concept.ConceptStatus
import no.ndla.conceptapi.model.api._
import no.ndla.conceptapi.model.domain.{ConceptStatus, Sort}
import no.ndla.conceptapi.model.domain.Sort
import no.ndla.conceptapi.model.search.DraftSearchSettings
import no.ndla.conceptapi.service.search.{DraftConceptSearchService, SearchConverterService}
import no.ndla.conceptapi.service.{ConverterService, ReadService, WriteService}
Expand Down
Loading

0 comments on commit 0317e95

Please sign in to comment.