diff --git a/draft-api/src/main/resources/no/ndla/draftapi/db/migration/V83__ResponsibleIdsMaterializedView.sql b/draft-api/src/main/resources/no/ndla/draftapi/db/migration/V83__ResponsibleIdsMaterializedView.sql new file mode 100644 index 0000000000..5096aaee79 --- /dev/null +++ b/draft-api/src/main/resources/no/ndla/draftapi/db/migration/V83__ResponsibleIdsMaterializedView.sql @@ -0,0 +1,2 @@ +CREATE MATERIALIZED VIEW responsible_view AS SELECT distinct ("document" -> 'responsible' ->> 'responsibleId') as responsibleId FROM articledata where ("document" -> 'responsible' ->> 'responsibleId') IS NOT NULL; +CREATE UNIQUE INDEX responsibleId_idx ON responsible_view(responsibleId); \ No newline at end of file diff --git a/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala b/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala index 95a78ee78b..c28fef162c 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala @@ -123,6 +123,7 @@ class DraftController(using migrateOutdatedGreps, addNotes, deleteCurrentRevision, + getResponsibles, ) /** Does a scroll with [[ArticleSearchService]] If no scrollId is specified execute the function @orFunction in the @@ -672,4 +673,19 @@ class DraftController(using .serverLogicPure { _ => articleId => writeService.deleteCurrentRevision(articleId).handleErrorsOrOk } + + def getResponsibles: ServerEndpoint[Any, Eff] = endpoint + .get + .in("responsibles" / "list") + .summary("Get list of responsibles for drafts") + .description("Get list of responsibles for drafts") + .out(jsonBody[Seq[String]]) + .errorOut(errorOutputsFor(401, 403)) + .requirePermission(DRAFT_API_WRITE) + .serverLogicPure { _ => _ => + readService.getAllResponsibles match { + case Success(resp) => Right(resp) + case Failure(ex) => errorHandling.returnLeftError(ex) + } + } } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala b/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala index 3df558ab10..07e3eecf2d 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala @@ -438,4 +438,12 @@ class DraftRepository(using draftErrorHelpers: DraftErrorHelpers, clock: Clock) sq.map(rs => rs.long("count")).runSingle().map(_.exists(_ > 0)) } + def refreshResponsibleView(using session: DBSession): Try[Unit] = { + tsql"refresh materialized view responsible_view".update().map(_ => ()) + } + + def getAllResponsibles(using session: DBSession): Try[Seq[String]] = { + tsql"""select responsibleId from responsible_view""".map(rs => rs.string("responsibleId")).runList() + } + } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala index 1ae1134c48..c445f330b2 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala @@ -226,4 +226,12 @@ class ReadService(using Success(ArticleRevisionHistoryDTO(articles, canDeleteCurrentRevision)) } + + def getAllResponsibles: Try[Seq[String]] = dbUtility.readOnly { implicit session => + draftRepository.getAllResponsibles match { + case Failure(exception) => Failure(exception) + case Success(value) => Success(value) + } + } + } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala index 62be524893..c866f8fdb9 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala @@ -336,7 +336,7 @@ class WriteService(using statusWasUpdated: Boolean, updatedApiArticle: api.UpdatedArticleDTO, shouldNotAutoUpdateStatus: Boolean, - ): Draft = { + )(using DBSession): Draft = { val isAutomaticResponsibleChange = updatedApiArticle.responsibleId match { case UpdateWith(_) => false case _ => true @@ -353,7 +353,16 @@ class WriteService(using draft.copy(started = true) } else { val responsibleIdWasUpdated = hasResponsibleBeenUpdated(draft, oldDraft) - + if (responsibleIdWasUpdated) { + draftRepository + .refreshResponsibleView + .recover { case ex => + logger.error( + s"Failed to refresh responsible view after responsible change for article ${draft.id.getOrElse("unknown")}", + ex, + ) + }: Unit + } val shouldReset = statusWasUpdated && !isAutomaticStatusChange || responsibleIdWasUpdated draft.copy(started = !shouldReset) } 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 7cb738d3c5..b9d095c91c 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 @@ -1278,6 +1278,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment { .copy(revision = 1, title = Some("updated title"), language = Some("nb"), responsibleId = UpdateWith("heiho")) when(draftRepository.slugExists(any, any)(using any)).thenReturn(Success(false)) when(draftRepository.withId(eqTo(existing.id.get))(using any)).thenReturn(Success(Some(existing))) + when(draftRepository.refreshResponsibleView(using any)).thenReturn(Success(())) val result = service.updateArticle(existing.id.get, updatedArticle, TestData.userWithWriteAccess).get result.started should be(false)