Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add endpoints for deleting language of subject page and film front page #629

Merged
merged 6 commits into from
Mar 26, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -8,5 +8,10 @@

package no.ndla.common.model.domain.frontpage

import no.ndla.language.model.LanguageField

case class MovieTheme(name: Seq[MovieThemeName], movies: Seq[String])
case class MovieThemeName(name: String, language: String)
case class MovieThemeName(name: String, language: String) extends LanguageField[String] {
override def value: String = name
override def isEmpty: Boolean = name.isEmpty
}
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ trait FilmPageController {
class FilmPageController extends TapirController {
override val serviceName: String = "filmfrontpage"
override val prefix: EndpointInput[Unit] = "frontpage-api" / "v1" / serviceName
private val pathLanguage = path[String]("language").description("The ISO 639-1 language code describing language.")
override val endpoints: List[ServerEndpoint[Any, Eff]] = List(
endpoint.get
.summary("Get data to display on the film front page")
@@ -49,6 +50,16 @@ trait FilmPageController {
writeService.updateFilmFrontPage(filmFrontPage).partialOverride { case ex: ValidationException =>
ErrorHelpers.unprocessableEntity(ex.getMessage)
}
},
endpoint.delete
.in("language" / pathLanguage)
.summary("Delete language from film front page")
.description("Delete language from film front page")
.out(jsonBody[FilmFrontPageDTO])
.errorOut(errorOutputsFor(400, 401, 403, 404))
.requirePermission(FRONTPAGE_API_WRITE)
.serverLogicPure { _ => language =>
writeService.deleteFilmFrontPageLanguage(language)
}
)
}
Original file line number Diff line number Diff line change
@@ -30,6 +30,8 @@ trait SubjectPageController {
class SubjectPageController extends TapirController {
override val serviceName: String = "subjectpage"
override val prefix: EndpointInput[Unit] = "frontpage-api" / "v1" / serviceName
private val pathSubjectPageId = path[Long]("subjectpage-id").description("The subjectpage id")
private val pathLanguage = path[String]("language").description("The ISO 639-1 language code describing language.")

def getAllSubjectPages: ServerEndpoint[Any, Eff] = endpoint.get
.summary("Fetch all subjectpages")
@@ -45,7 +47,7 @@ trait SubjectPageController {

def getSingleSubjectPage: ServerEndpoint[Any, Eff] = endpoint.get
.summary("Get data to display on a subject page")
.in(path[Long]("subjectpage-id").description("The subjectpage id"))
.in(pathSubjectPageId)
.in(query[String]("language").default(props.DefaultLanguage))
.in(query[Boolean]("fallback").default(false))
.out(jsonBody[SubjectPageDTO])
@@ -89,7 +91,7 @@ trait SubjectPageController {
def updateSubjectPage: ServerEndpoint[Any, Eff] = endpoint.patch
.summary("Update subject page")
.in(jsonBody[UpdatedSubjectPageDTO])
.in(path[Long]("subjectpage-id").description("The subjectpage id"))
.in(pathSubjectPageId)
.in(query[String]("language").default(props.DefaultLanguage))
.in(query[Boolean]("fallback").default(false))
.out(jsonBody[SubjectPageDTO])
@@ -105,12 +107,26 @@ trait SubjectPageController {
}
}

def deleteLanguage: ServerEndpoint[Any, Eff] = endpoint.delete
.in(pathSubjectPageId / "language" / pathLanguage)
.summary("Delete language from subject page")
.description("Delete language from subject page")
.out(jsonBody[SubjectPageDTO])
.errorOut(errorOutputsFor(400, 401, 403, 404))
.requirePermission(FRONTPAGE_API_WRITE)
.serverLogicPure { _ =>
{ case (articleId, language) =>
writeService.deleteSubjectPageLanguage(articleId, language)
}
}

override val endpoints: List[ServerEndpoint[Any, Eff]] = List(
getAllSubjectPages,
getSubjectPagesByIds,
getSingleSubjectPage,
createNewSubjectPage,
updateSubjectPage
updateSubjectPage,
deleteLanguage
)
}
}
Original file line number Diff line number Diff line change
@@ -13,5 +13,6 @@ case class FilmFrontPageDTO(
about: Seq[AboutFilmSubjectDTO],
movieThemes: Seq[MovieThemeDTO],
slideShow: Seq[String],
article: Option[String]
article: Option[String],
supportedLanguages: Seq[String]
)
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ import io.circe.parser.*
import io.circe.{Decoder, Encoder}
import no.ndla.common.model.domain.frontpage.{AboutSubject, MovieTheme}
import no.ndla.frontpageapi.Props
import no.ndla.language.Language.getSupportedLanguages
import scalikejdbc.{WrappedResultSet, *}

import scala.util.Try
@@ -25,7 +26,10 @@ case class FilmFrontPage(
movieThemes: Seq[MovieTheme],
slideShow: Seq[String],
article: Option[String]
)
) {

def supportedLanguages: Seq[String] = getSupportedLanguages(about, movieThemes.flatMap(_.name))
}

object FilmFrontPage {
implicit val decoder: Decoder[FilmFrontPage] = deriveDecoder
Original file line number Diff line number Diff line change
@@ -63,6 +63,16 @@ trait FilmFrontPageRepository {

}

def update(page: FilmFrontPage)(implicit session: DBSession = AutoSession): Try[FilmFrontPage] = {
val dataObject = new PGobject()
dataObject.setType("jsonb")
dataObject.setValue(page.asJson.noSpacesDropNull)

Try(
sql"update ${DBFilmFrontPageData.table} set document=$dataObject".update()
).map(_ => page)
}

}

}
Original file line number Diff line number Diff line change
@@ -90,7 +90,8 @@ trait ConverterService {
toApiAboutFilmSubject(page.about, language),
toApiMovieThemes(page.movieThemes, language),
page.slideShow,
page.article
page.article,
page.supportedLanguages
)
}

Original file line number Diff line number Diff line change
@@ -8,13 +8,14 @@

package no.ndla.frontpageapi.service

import no.ndla.common.errors.ValidationException
import no.ndla.common.errors.{NotFoundException, ValidationException}
import no.ndla.common.model.api.FrontPageDTO
import no.ndla.common.model.api.frontpage.SubjectPageDTO
import no.ndla.frontpageapi.Props
import no.ndla.frontpageapi.model.api
import no.ndla.frontpageapi.model.domain.Errors.SubjectPageNotFoundException
import no.ndla.frontpageapi.model.domain.Errors.{OperationNotAllowedException, SubjectPageNotFoundException}
import no.ndla.frontpageapi.repository.{FilmFrontPageRepository, FrontPageRepository, SubjectPageRepository}
import no.ndla.language.Language

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

@@ -109,6 +110,41 @@ trait WriteService {
filmFrontPage <- filmFrontPageRepository.newFilmFrontPage(domainFilmFrontPage)
} yield ConverterService.toApiFilmFrontPage(filmFrontPage, None)
}

def deleteSubjectPageLanguage(id: Long, language: String): Try[SubjectPageDTO] = {
subjectPageRepository.withId(id) match {
case Success(Some(subjectPage)) =>
subjectPage.supportedLanguages.size match {
case 1 => Failure(OperationNotAllowedException("Only one language left"))
case _ =>
val about = subjectPage.about.filter(_.language != language)
val metaDescription = subjectPage.metaDescription.filter(_.language != language)
subjectPageRepository
.updateSubjectPage(subjectPage.copy(about = about, metaDescription = metaDescription))
.flatMap(ConverterService.toApiSubjectPage(_, Language.NoLanguage, fallback = true))
}
case Success(None) => Failure(SubjectPageNotFoundException(id))
case Failure(ex) => Failure(ex)
}
}

def deleteFilmFrontPageLanguage(language: String): Try[api.FilmFrontPageDTO] = {
filmFrontPageRepository.get match {
case Some(page) =>
page.supportedLanguages.size match {
case 1 => Failure(OperationNotAllowedException("Only one language left"))
case _ =>
val about = page.about.filter(_.language != language)
val movieThemes = page.movieThemes.map(movieTheme =>
movieTheme.copy(name = movieTheme.name.filter(_.language != language))
)
filmFrontPageRepository
.update(page.copy(about = about, movieThemes = movieThemes))
.map(ConverterService.toApiFilmFrontPage(_, None))
}
case None => Failure(NotFoundException("The film front page was not found"))
}
}
}

}
Original file line number Diff line number Diff line change
@@ -163,5 +163,5 @@ object TestData {
None
)

val apiFilmFrontPage: api.FilmFrontPageDTO = api.FilmFrontPageDTO("", Seq(), Seq(), Seq(), None)
val apiFilmFrontPage: api.FilmFrontPageDTO = api.FilmFrontPageDTO("", Seq(), Seq(), Seq(), None, Seq())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Part of NDLA frontpage-api
* Copyright (C) 2025 NDLA
*
* See LICENSE
*
*/

package no.ndla.frontpageapi.service

import no.ndla.common.model.domain.frontpage.VisualElementType.Image
import no.ndla.common.model.domain.frontpage.{AboutSubject, MetaDescription, MovieThemeName, VisualElement}
import no.ndla.frontpageapi.{TestData, TestEnvironment, UnitSuite}
import no.ndla.language.Language
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.when

import scala.util.Success

class WriteServiceTest extends UnitSuite with TestEnvironment {
override val writeService: WriteService = new WriteService

test("That language is deleted for subject page") {
val subjectPage = TestData.domainSubjectPage.copy(
about =
TestData.domainSubjectPage.about ++ Seq(AboutSubject("Foo", "Bar", "nn", VisualElement(Image, "123", None))),
metaDescription = TestData.domainSubjectPage.metaDescription ++ Seq(MetaDescription("Description", "nn"))
)
when(subjectPageRepository.withId(any)).thenReturn(Success(Some(subjectPage)))
when(subjectPageRepository.updateSubjectPage(any)(any)).thenAnswer(i => Success(i.getArgument(0)))

val result = writeService.deleteSubjectPageLanguage(subjectPage.id.get, "nn")
result should be(
Success(
ConverterService
.toApiSubjectPage(TestData.domainSubjectPage, Language.NoLanguage, fallback = true)
.failIfFailure
)
)
}

test("That deleting last language for subject page throws exception") {
when(subjectPageRepository.withId(any)).thenReturn(Success(Some(TestData.domainSubjectPage)))
when(subjectPageRepository.updateSubjectPage(any)(any)).thenAnswer(i => Success(i.getArgument(0)))

val result = writeService.deleteSubjectPageLanguage(TestData.domainSubjectPage.id.get, "nb")
result.isFailure should be(true)
}

test("That language is deleted for film front page") {
val filmFrontPage = TestData.domainFilmFrontPage.copy(
about =
TestData.domainFilmFrontPage.about ++ Seq(AboutSubject("Foo", "Bar", "nn", VisualElement(Image, "123", None))),
movieThemes = TestData.domainFilmFrontPage.movieThemes.map(movieTheme =>
movieTheme.copy(name = movieTheme.name ++ Seq(MovieThemeName("FooBar", "nn")))
)
)
when(filmFrontPageRepository.get(any)).thenReturn(Some(filmFrontPage))
when(filmFrontPageRepository.update(any)(any)).thenAnswer(i => Success(i.getArgument(0)))

val result = writeService.deleteFilmFrontPageLanguage("nn")
result should be(Success(ConverterService.toApiFilmFrontPage(TestData.domainFilmFrontPage, None)))
}

test("That deleting last language for film front page throws exception") {
val filmFrontPage = TestData.domainFilmFrontPage.copy(
about = TestData.domainFilmFrontPage.about.filter(_.language == "nb"),
movieThemes = TestData.domainFilmFrontPage.movieThemes.map(movieTheme =>
movieTheme.copy(name = movieTheme.name.filter(_.language == "nb"))
)
)
when(filmFrontPageRepository.get(any)).thenReturn(Some(filmFrontPage))
when(filmFrontPageRepository.update(any)(any)).thenAnswer(i => Success(i.getArgument(0)))

val result = writeService.deleteFilmFrontPageLanguage("nb")
result.isFailure should be(true)
}
}