diff --git a/it/src/main/scala/org/mbari/oni/endpoints/AuthorizationEndpointsSuite.scala b/it/src/main/scala/org/mbari/oni/endpoints/AuthorizationEndpointsSuite.scala index 40f4ef5..b4ea7e1 100644 --- a/it/src/main/scala/org/mbari/oni/endpoints/AuthorizationEndpointsSuite.scala +++ b/it/src/main/scala/org/mbari/oni/endpoints/AuthorizationEndpointsSuite.scala @@ -44,7 +44,6 @@ trait AuthorizationEndpointsSuite extends DatabaseFunSuite with EndpointsSuite: .send(backendStub) .join - response.body match case Left(e) => fail(e) case Right(body) => @@ -112,6 +111,6 @@ trait AuthorizationEndpointsSuite extends DatabaseFunSuite with EndpointsSuite: response.body match case Left(e) => - // this is expected. READONLY users cannot login + // this is expected. READONLY users cannot login case Right(body) => fail("READONLY user should not be able to login") diff --git a/it/src/main/scala/org/mbari/oni/endpoints/ConceptEndpointsSuite.scala b/it/src/main/scala/org/mbari/oni/endpoints/ConceptEndpointsSuite.scala index b59e9a8..69502ad 100644 --- a/it/src/main/scala/org/mbari/oni/endpoints/ConceptEndpointsSuite.scala +++ b/it/src/main/scala/org/mbari/oni/endpoints/ConceptEndpointsSuite.scala @@ -81,8 +81,7 @@ trait ConceptEndpointsSuite extends EndpointsSuite with DataInitializer with Use runGet( endpoints.findParentEndpointImpl, s"http://test.com/v1/concept/parent/${name}", - response => - assertEquals(response.code, StatusCode.NotFound) + response => assertEquals(response.code, StatusCode.NotFound) // val obtained = checkResponse[ConceptMetadata](response.body) // assertEquals(obtained.name, root.getPrimaryConceptName.getName) ) @@ -126,12 +125,10 @@ trait ConceptEndpointsSuite extends EndpointsSuite with DataInitializer with Use runGet( endpoints.findByNameImpl, s"http://test.com/v1/concept/${name}", - response => - assertEquals(response.code, StatusCode.NotFound) + response => assertEquals(response.code, StatusCode.NotFound) ) } - test("findByNameContaining") { val root = init(2, 0) val child = root.getChildConcepts.iterator().next() @@ -168,10 +165,10 @@ trait ConceptEndpointsSuite extends EndpointsSuite with DataInitializer with Use val attempt = testWithUserAuth( user => - val root = init(2, 0) - val name = root.getPrimaryConceptName.getName + val root = init(2, 0) + val name = root.getPrimaryConceptName.getName val (rankLevel, rankName) = TestEntityFactory.randomRankLevelAndName() - val expectedRank = Some(s"${{rankLevel.getOrElse("")}}${{rankName.getOrElse("")}}") + val expectedRank = Some(s"${{ rankLevel.getOrElse("") }}${{ rankName.getOrElse("") }}") val conceptCreate = ConceptCreate( "SomeChildConcept", @@ -207,11 +204,11 @@ trait ConceptEndpointsSuite extends EndpointsSuite with DataInitializer with Use val attempt = testWithUserAuth( user => - val root = init(3, 0) - val grandChild = root.getChildConcepts.iterator().next().getChildConcepts.iterator().next() + val root = init(3, 0) + val grandChild = root.getChildConcepts.iterator().next().getChildConcepts.iterator().next() val (rankLevel, rankName) = TestEntityFactory.randomRankLevelAndName() - val expectedRank = Some(s"${{rankLevel.getOrElse("")}}${{rankName.getOrElse("")}}") - val conceptUpdate = ConceptUpdate( + val expectedRank = Some(s"${{ rankLevel.getOrElse("") }}${{ rankName.getOrElse("") }}") + val conceptUpdate = ConceptUpdate( Some(root.getPrimaryConceptName.getName), rankLevel = rankLevel, rankName = rankName, diff --git a/it/src/main/scala/org/mbari/oni/endpoints/EndpointsSuite.scala b/it/src/main/scala/org/mbari/oni/endpoints/EndpointsSuite.scala index f94b3f5..6fc69e6 100644 --- a/it/src/main/scala/org/mbari/oni/endpoints/EndpointsSuite.scala +++ b/it/src/main/scala/org/mbari/oni/endpoints/EndpointsSuite.scala @@ -36,7 +36,7 @@ import scala.concurrent.{ExecutionContext, Future} trait EndpointsSuite extends munit.FunSuite: - given ExecutionContext = ExecutionContext.global + given ExecutionContext = ExecutionContext.global private val log: System.Logger = System.getLogger(getClass.getName) def runDelete( @@ -97,7 +97,6 @@ trait EndpointsSuite extends munit.FunSuite: .auth .bearer(bearer) - val response = request.send(backendStub).join assertions(response) diff --git a/it/src/main/scala/org/mbari/oni/endpoints/LinkTemplateEndpointsSuite.scala b/it/src/main/scala/org/mbari/oni/endpoints/LinkTemplateEndpointsSuite.scala index 6422715..f9cffb8 100644 --- a/it/src/main/scala/org/mbari/oni/endpoints/LinkTemplateEndpointsSuite.scala +++ b/it/src/main/scala/org/mbari/oni/endpoints/LinkTemplateEndpointsSuite.scala @@ -16,7 +16,14 @@ package org.mbari.oni.endpoints -import org.mbari.oni.domain.{ExtendedLink, ILink, LinkCreate, LinkRenameToConceptRequest, LinkRenameToConceptResponse, LinkUpdate} +import org.mbari.oni.domain.{ + ExtendedLink, + ILink, + LinkCreate, + LinkRenameToConceptRequest, + LinkRenameToConceptResponse, + LinkUpdate +} import org.mbari.oni.etc.jwt.JwtService import org.mbari.oni.jpa.DataInitializer import org.mbari.oni.services.UserAuthMixin @@ -104,13 +111,13 @@ trait LinkTemplateEndpointsSuite extends EndpointsSuite with DataInitializer wit } test("renameToConcept") { - val root = init(3, 10) - val descendants = root.getDescendants.asScala + val root = init(3, 10) + val descendants = root.getDescendants.asScala val allLinkTemplates = descendants.flatMap(_.getConceptMetadata.getLinkTemplates.asScala).toSeq - val link = allLinkTemplates.head - val request = LinkRenameToConceptRequest(link.getToConcept, Strings.random(10)) + val link = allLinkTemplates.head + val request = LinkRenameToConceptRequest(link.getToConcept, Strings.random(10)) // log.atError.log(request.stringify) - val attempt = testWithUserAuth( + val attempt = testWithUserAuth( user => runPut( endpoints.renameToConceptImpl, diff --git a/it/src/main/scala/org/mbari/oni/endpoints/MediaEndpointsSuite.scala b/it/src/main/scala/org/mbari/oni/endpoints/MediaEndpointsSuite.scala index baa7b7b..98471ae 100644 --- a/it/src/main/scala/org/mbari/oni/endpoints/MediaEndpointsSuite.scala +++ b/it/src/main/scala/org/mbari/oni/endpoints/MediaEndpointsSuite.scala @@ -28,14 +28,14 @@ import org.mbari.oni.etc.circe.CirceCodecs.{*, given} import java.net.URI import scala.jdk.CollectionConverters.* -trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserAuthMixin { +trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserAuthMixin: - given jwtService: JwtService = JwtService("mbari", "foo", "bar") - lazy val fastPhylogenyService = FastPhylogenyService(entityManagerFactory) + given jwtService: JwtService = JwtService("mbari", "foo", "bar") + lazy val fastPhylogenyService = FastPhylogenyService(entityManagerFactory) lazy val endpoints: MediaEndpoints = MediaEndpoints(entityManagerFactory, fastPhylogenyService) - private val password = Strings.random(10) + private val password = Strings.random(10) - def createMedia(): Seq[Media] = { + def createMedia(): Seq[Media] = val root = init(1, 6) root.getDescendants .asScala @@ -43,13 +43,12 @@ trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserA .toSeq .map(Media.from) .sortBy(_.url.toExternalForm) - } test("mediaForConcept") { val expected = createMedia() - val opt = expected.head.conceptName + val opt = expected.head.conceptName assert(opt.isDefined) - val name = opt.get + val name = opt.get runGet( endpoints.mediaForConceptEndpointImpl, s"http://test.com/v1/media/${name}", @@ -62,7 +61,7 @@ trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserA } test("createMedia") { - val root = init(2, 0) + val root = init(2, 0) assert(root != null) val mediaCreate = MediaCreate( conceptName = root.getName, @@ -72,7 +71,7 @@ trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserA mediaType = Some(MediaTypes.IMAGE.name), isPrimary = Some(true) ) - val attempt = testWithUserAuth( + val attempt = testWithUserAuth( user => runPost( endpoints.createMediaEndpointImpl, @@ -85,21 +84,21 @@ trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserA assertEquals(mediaCreate.url, obtained.url) assertEquals(mediaCreate.caption, obtained.caption) assertEquals(mediaCreate.credit, obtained.credit) - val t = Media.resolveMimeType(mediaCreate.mediaType.getOrElse(""), obtained.url.toExternalForm) + val t = Media.resolveMimeType(mediaCreate.mediaType.getOrElse(""), obtained.url.toExternalForm) assertEquals(t, obtained.mimeType) assertEquals(mediaCreate.isPrimary.getOrElse(false), obtained.isPrimary) - , - jwt = jwtService.login(user.username, password, user.toEntity) + , + jwt = jwtService.login(user.username, password, user.toEntity) ), password ) attempt match - case Left(value) => fail(value.toString) + case Left(value) => fail(value.toString) case Right(value) => assert(true) } test("updateMedia") { - val media = createMedia().head + val media = createMedia().head val mediaUpdate = MediaUpdate( url = Some(URI.create(s"http://www.mbari.org/${Strings.random(10)}.png").toURL), caption = Some(Strings.random(1000)), @@ -107,7 +106,7 @@ trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserA mediaType = Some(MediaTypes.IMAGE.name), isPrimary = Some(true) ) - val attempt = testWithUserAuth( + val attempt = testWithUserAuth( user => runPut( endpoints.updateMediaEndpointImpl, @@ -119,21 +118,21 @@ trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserA assertEquals(mediaUpdate.url.orNull, obtained.url) assertEquals(mediaUpdate.caption, obtained.caption) assertEquals(mediaUpdate.credit, obtained.credit) - val t = Media.resolveMimeType(mediaUpdate.mediaType.getOrElse(""), obtained.url.toExternalForm) + val t = Media.resolveMimeType(mediaUpdate.mediaType.getOrElse(""), obtained.url.toExternalForm) assertEquals(t, obtained.mimeType) assertEquals(mediaUpdate.isPrimary.getOrElse(false), obtained.isPrimary) - , - jwt = jwtService.login(user.username, password, user.toEntity) + , + jwt = jwtService.login(user.username, password, user.toEntity) ), password ) attempt match - case Left(value) => fail(value.toString) + case Left(value) => fail(value.toString) case Right(value) => assert(true) } test("deleteMedia") { - val media = createMedia().head + val media = createMedia().head val attempt = testWithUserAuth( user => runDelete( @@ -145,14 +144,6 @@ trait MediaEndpointsSuite extends EndpointsSuite with DataInitializer with UserA password ) attempt match - case Left(value) => fail(value.toString) + case Left(value) => fail(value.toString) case Right(value) => assert(true) } - - - - - - - -} diff --git a/it/src/main/scala/org/mbari/oni/endpoints/PrefNodeEndpointsSuite.scala b/it/src/main/scala/org/mbari/oni/endpoints/PrefNodeEndpointsSuite.scala index 330f6f7..d777160 100644 --- a/it/src/main/scala/org/mbari/oni/endpoints/PrefNodeEndpointsSuite.scala +++ b/it/src/main/scala/org/mbari/oni/endpoints/PrefNodeEndpointsSuite.scala @@ -200,9 +200,9 @@ trait PrefNodeEndpointsSuite extends EndpointsSuite with DataInitializer with Us } test("updateEndpoint (query params with form body)") { - val nodes = createNodes(1) - val node = nodes.head - val newValue = "new_value_" + Strings.random(5) + val nodes = createNodes(1) + val node = nodes.head + val newValue = "new_value_" + Strings.random(5) val updatedNode = PrefNodeUpdate(None, None, newValue) testWithUserAuth( user => @@ -224,9 +224,9 @@ trait PrefNodeEndpointsSuite extends EndpointsSuite with DataInitializer with Us } test("updateEndpoint (query params with json body)") { - val nodes = createNodes(1) - val node = nodes.head - val newValue = "new_value_" + Strings.random(5) + val nodes = createNodes(1) + val node = nodes.head + val newValue = "new_value_" + Strings.random(5) val updatedNode = PrefNodeUpdate(None, None, newValue) testWithUserAuth( user => diff --git a/it/src/main/scala/org/mbari/oni/endpoints/UserAccountsEndpointsSuite.scala b/it/src/main/scala/org/mbari/oni/endpoints/UserAccountsEndpointsSuite.scala index 106a0c6..7e53b59 100644 --- a/it/src/main/scala/org/mbari/oni/endpoints/UserAccountsEndpointsSuite.scala +++ b/it/src/main/scala/org/mbari/oni/endpoints/UserAccountsEndpointsSuite.scala @@ -110,7 +110,6 @@ trait UserAccountsEndpointsSuite extends EndpointsSuite with DataInitializer: val entity = TestEntityFactory.createUserAccount(UserAccountRoles.ADMINISTRATOR.getRoleName) val userAccount = UserAccount.from(entity).copy(password = Strings.random(10)) - runPost( endpoints.createEndpointImpl, "http://test.com/v1/users", @@ -128,7 +127,6 @@ trait UserAccountsEndpointsSuite extends EndpointsSuite with DataInitializer: val entity = TestEntityFactory.createUserAccount(UserAccountRoles.READONLY.getRoleName) val userAccount = UserAccount.from(entity).copy(password = Strings.random(10)) - runPost( endpoints.createEndpointImpl, "http://test.com/v1/users", @@ -144,9 +142,9 @@ trait UserAccountsEndpointsSuite extends EndpointsSuite with DataInitializer: } test("createEndpoint (form camelCase)") { - val entity = TestEntityFactory.createUserAccount(UserAccountRoles.ADMINISTRATOR.getRoleName) + val entity = TestEntityFactory.createUserAccount(UserAccountRoles.ADMINISTRATOR.getRoleName) val userAccount = UserAccount.from(entity).copy(password = Strings.random(10)) - val formBody = userAccount.toFormBody + val formBody = userAccount.toFormBody runPost( endpoints.createEndpointImpl, @@ -163,9 +161,9 @@ trait UserAccountsEndpointsSuite extends EndpointsSuite with DataInitializer: } test("createEndpoint (form snake_case)") { - val entity = TestEntityFactory.createUserAccount(UserAccountRoles.ADMINISTRATOR.getRoleName) + val entity = TestEntityFactory.createUserAccount(UserAccountRoles.ADMINISTRATOR.getRoleName) val userAccount = UserAccount.from(entity).copy(password = Strings.random(10)) - val formBody = userAccount.toFormBody.replace("firstName", "first_name").replace("lastName", "last_name") + val formBody = userAccount.toFormBody.replace("firstName", "first_name").replace("lastName", "last_name") runPost( endpoints.createEndpointImpl, diff --git a/it/src/main/scala/org/mbari/oni/issues/Oni7.scala b/it/src/main/scala/org/mbari/oni/issues/Oni7.scala new file mode 100644 index 0000000..1b13ad1 --- /dev/null +++ b/it/src/main/scala/org/mbari/oni/issues/Oni7.scala @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Monterey Bay Aquarium Research Institute + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mbari.oni.issues + +import org.mbari.oni.endpoints.{EndpointsSuite, HistoryEndpoints} +import org.mbari.oni.etc.jwt.JwtService +import org.mbari.oni.jdbc.FastPhylogenyService +import org.mbari.oni.jpa.DataInitializer +import org.mbari.oni.services.{HistoryService, LinkTemplateService, UserAuthMixin} + +trait Oni7 extends EndpointsSuite with DataInitializer with UserAuthMixin: + + given jwtService: JwtService = JwtService("mbari", "foo", "bar") + + lazy val fastPhylogenyService = new FastPhylogenyService(entityManagerFactory) + lazy val historyService = new HistoryService(entityManagerFactory) + lazy val historyEndpoints: HistoryEndpoints = HistoryEndpoints(entityManagerFactory, fastPhylogenyService) + private val password = "foofoofoo" + + test("mbari-org/oni#7 - accept rank level replace") { + val root = init(2, 0) + + } + + test("mbari-org/oni#7 - reject rank level replace") { + val root = init(2, 0) + + } + + test("mbari-org/oni#7 - accept rank name replace") { + val root = init(2, 0) + + } + + test("mbari-org/oni#7 - reject rank name replace") { + val root = init(2, 0) + + } diff --git a/it/src/main/scala/org/mbari/oni/jpa/entities/TestEntityFactory.scala b/it/src/main/scala/org/mbari/oni/jpa/entities/TestEntityFactory.scala index 5575077..d837f15 100644 --- a/it/src/main/scala/org/mbari/oni/jpa/entities/TestEntityFactory.scala +++ b/it/src/main/scala/org/mbari/oni/jpa/entities/TestEntityFactory.scala @@ -196,7 +196,6 @@ object TestEntityFactory: entity.setCitation(s) entity - def randomRankLevelAndName(): (Option[String], Option[String]) = val idx = random.nextInt(RankValidator.ValidRanks.size) RankValidator.ValidRankLevelsAndNames(idx) diff --git a/it/src/main/scala/org/mbari/oni/services/ConceptNameServiceSuite.scala b/it/src/main/scala/org/mbari/oni/services/ConceptNameServiceSuite.scala index fc282b9..274bb90 100644 --- a/it/src/main/scala/org/mbari/oni/services/ConceptNameServiceSuite.scala +++ b/it/src/main/scala/org/mbari/oni/services/ConceptNameServiceSuite.scala @@ -16,7 +16,15 @@ package org.mbari.oni.services -import org.mbari.oni.domain.{ConceptNameCreate, ConceptNameTypes, ConceptNameUpdate, RawConcept, RawConceptName, UserAccount, UserAccountRoles} +import org.mbari.oni.domain.{ + ConceptNameCreate, + ConceptNameTypes, + ConceptNameUpdate, + RawConcept, + RawConceptName, + UserAccount, + UserAccountRoles +} import org.mbari.oni.jpa.DataInitializer import org.mbari.oni.etc.circe.CirceCodecs.{*, given} import org.mbari.oni.etc.jdk.Strings @@ -88,19 +96,23 @@ trait ConceptNameServiceSuite extends DataInitializer with UserAuthMixin: val rawRoot = RawConcept.from(root) val name = rawRoot.primaryName val dto = - ConceptNameUpdate(newName = Some("newName"), nameType = Some(ConceptNameTypes.PRIMARY.getType), author = Some(Strings.random(5))) + ConceptNameUpdate( + newName = Some("newName"), + nameType = Some(ConceptNameTypes.PRIMARY.getType), + author = Some(Strings.random(5)) + ) val attempt = runWithUserAuth(user => conceptNameService.updateName(name, dto, user.username)) attempt match case Right(rawConcept) => println(rawConcept.stringify) - val obtained = rawConcept.names.map(_.name).toSeq + val obtained = rawConcept.names.map(_.name).toSeq assert(!obtained.contains(name)) assert(obtained.contains(dto.newName.getOrElse(""))) val updatedNameOpt = rawConcept.names.find(_.name == dto.newName.getOrElse("")) assert(updatedNameOpt.isDefined) - val updatedName = updatedNameOpt.get + val updatedName = updatedNameOpt.get assertEquals(updatedName.nameType, ConceptNameTypes.PRIMARY.getType) assertEquals(updatedName.author, dto.author) case Left(error) => @@ -109,23 +121,23 @@ trait ConceptNameServiceSuite extends DataInitializer with UserAuthMixin: test("updateName with blank author - changes author to null in the database") { - val root = init(3, 3) - assert(root != null) - val rawRoot = RawConcept.from(root) - val name = rawRoot.primaryName - val dto = - ConceptNameUpdate(author = Some("")) - - val attempt = runWithUserAuth(user => conceptNameService.updateName(name, dto, user.username)) - - attempt match - case Right(rawConcept) => - val updatedNameOpt = rawConcept.names.find(_.name == name) - assert(updatedNameOpt.isDefined) - val updatedName = updatedNameOpt.get - assertEquals(updatedName.author, dto.author) - case Left(error) => - fail(error.toString) + val root = init(3, 3) + assert(root != null) + val rawRoot = RawConcept.from(root) + val name = rawRoot.primaryName + val dto = + ConceptNameUpdate(author = Some("")) + + val attempt = runWithUserAuth(user => conceptNameService.updateName(name, dto, user.username)) + + attempt match + case Right(rawConcept) => + val updatedNameOpt = rawConcept.names.find(_.name == name) + assert(updatedNameOpt.isDefined) + val updatedName = updatedNameOpt.get + assertEquals(updatedName.author, dto.author) + case Left(error) => + fail(error.toString) } test("updateName (attempt to change primary to non-primary)") { diff --git a/it/src/main/scala/org/mbari/oni/services/ConceptServiceSuite.scala b/it/src/main/scala/org/mbari/oni/services/ConceptServiceSuite.scala index 18bc03d..8ca6577 100644 --- a/it/src/main/scala/org/mbari/oni/services/ConceptServiceSuite.scala +++ b/it/src/main/scala/org/mbari/oni/services/ConceptServiceSuite.scala @@ -372,16 +372,16 @@ trait ConceptServiceSuite extends DatabaseFunSuite with UserAuthMixin: } test("update to remove rank name and rank level") { - val root = TestEntityFactory.buildRoot(2) + val root = TestEntityFactory.buildRoot(2) root.setRankName("genus") root.setRankLevel("sub") val attempt = runWithUserAuth(user => for rootEntity <- conceptService.init(root) updated <- conceptService.update( - root.getPrimaryConceptName.getName, - ConceptUpdate(rankName = Some(""), rankLevel = Some("")), - user.username + root.getPrimaryConceptName.getName, + ConceptUpdate(rankName = Some(""), rankLevel = Some("")), + user.username ) yield updated ) @@ -391,7 +391,7 @@ trait ConceptServiceSuite extends DatabaseFunSuite with UserAuthMixin: fail("Failed to update") case Right(_) => conceptService.findByName(root.getPrimaryConceptName.getName) match - case Left(_) => + case Left(_) => case Right(conceptMetadata) => assertEquals(conceptMetadata.rankName, None) assertEquals(conceptMetadata.rankLevel, None) diff --git a/it/src/main/scala/org/mbari/oni/services/HistoryActionServiceSuite.scala b/it/src/main/scala/org/mbari/oni/services/HistoryActionServiceSuite.scala index 3469b98..e76c9d7 100644 --- a/it/src/main/scala/org/mbari/oni/services/HistoryActionServiceSuite.scala +++ b/it/src/main/scala/org/mbari/oni/services/HistoryActionServiceSuite.scala @@ -367,7 +367,7 @@ trait HistoryActionServiceSuite extends DataInitializer with UserAuthMixin: // service.findRawByName(root.getName, true).map(xs => println(xs.stringify)) service.findChildrenByParentName(a.getName) match case Right(concepts) => - // println(concepts.stringify) + // println(concepts.stringify) val xs = concepts.filter(_.name == b.getName) assert(xs.isEmpty) case Left(_) => fail("Concept should exist after approval") diff --git a/it/src/main/scala/org/mbari/oni/services/Issue6.scala b/it/src/main/scala/org/mbari/oni/services/Issue6.scala index 8994a94..d68169b 100644 --- a/it/src/main/scala/org/mbari/oni/services/Issue6.scala +++ b/it/src/main/scala/org/mbari/oni/services/Issue6.scala @@ -22,23 +22,20 @@ import org.mbari.oni.jpa.DataInitializer import java.net.URI -trait Issue6 extends DataInitializer with UserAuthMixin { +trait Issue6 extends DataInitializer with UserAuthMixin: lazy val fastPhylogenyService = new FastPhylogenyService(entityManagerFactory) - lazy val historyService = new HistoryService(entityManagerFactory) + lazy val historyService = new HistoryService(entityManagerFactory) lazy val historyActionService = new HistoryActionService(entityManagerFactory, fastPhylogenyService) - lazy val mediaService = new MediaService(entityManagerFactory, fastPhylogenyService) - + lazy val mediaService = new MediaService(entityManagerFactory, fastPhylogenyService) test("6 - Duplicate ConceptMetadata") { - val root = init(2, 0) + val root = init(2, 0) assert(root != null) - val a = conceptService.findByName(root.getName) + val a = conceptService.findByName(root.getName) val mediaCreate = MediaCreate( conceptName = root.getName, url = URI.create("http://www.mbari.org").toURL ) - - } -} + } diff --git a/it/src/main/scala/org/mbari/oni/services/LinkTemplateServiceSuite.scala b/it/src/main/scala/org/mbari/oni/services/LinkTemplateServiceSuite.scala index 17dc6a2..0f4ce43 100644 --- a/it/src/main/scala/org/mbari/oni/services/LinkTemplateServiceSuite.scala +++ b/it/src/main/scala/org/mbari/oni/services/LinkTemplateServiceSuite.scala @@ -115,8 +115,9 @@ trait LinkTemplateServiceSuite extends DataInitializer with UserAuthMixin: assert(root != null) val descendants = root.getDescendants.asScala val allLinkTemplates = descendants.flatMap(_.getConceptMetadata.getLinkTemplates.asScala).toSeq - val request = LinkRenameToConceptRequest(allLinkTemplates.head.getToConcept, Strings.random(10)) - val attempt = runWithUserAuth(user => linkTemplateService.renameToConcept(request.old, request.`new`, user.username)) + val request = LinkRenameToConceptRequest(allLinkTemplates.head.getToConcept, Strings.random(10)) + val attempt = + runWithUserAuth(user => linkTemplateService.renameToConcept(request.old, request.`new`, user.username)) attempt match case Right(obtained) => val expected = allLinkTemplates.count(t => t.getToConcept == request.old) diff --git a/it/src/main/scala/org/mbari/oni/services/MediaServiceSuite.scala b/it/src/main/scala/org/mbari/oni/services/MediaServiceSuite.scala index 397e7bc..7fb6b06 100644 --- a/it/src/main/scala/org/mbari/oni/services/MediaServiceSuite.scala +++ b/it/src/main/scala/org/mbari/oni/services/MediaServiceSuite.scala @@ -27,10 +27,10 @@ import java.net.URI trait MediaServiceSuite extends DataInitializer with UserAuthMixin: lazy val fastPhylogenyService = FastPhylogenyService(entityManagerFactory) - lazy val mediaService = MediaService(entityManagerFactory, fastPhylogenyService) + lazy val mediaService = MediaService(entityManagerFactory, fastPhylogenyService) test("create") { - val root = init(2, 0) + val root = init(2, 0) assert(root != null) val mediaCreate = MediaCreate( conceptName = root.getName, @@ -40,10 +40,10 @@ trait MediaServiceSuite extends DataInitializer with UserAuthMixin: mediaType = Some(MediaTypes.IMAGE.name), isPrimary = Some(true) ) - val attempt = runWithUserAuth(user => mediaService.create(mediaCreate, user.username)) + val attempt = runWithUserAuth(user => mediaService.create(mediaCreate, user.username)) attempt match - case Left(e) => fail(e.getMessage) + case Left(e) => fail(e.getMessage) case Right(media) => assert(media.id.isDefined) assertEquals(mediaCreate.conceptName, media.conceptName.getOrElse("")) @@ -57,7 +57,7 @@ trait MediaServiceSuite extends DataInitializer with UserAuthMixin: } test("update") { - val root = init(2, 0) + val root = init(2, 0) assert(root != null) val mediaCreate = MediaCreate( conceptName = root.getName, @@ -83,7 +83,7 @@ trait MediaServiceSuite extends DataInitializer with UserAuthMixin: ) attempt match - case Left(e) => fail(e.getMessage) + case Left(e) => fail(e.getMessage) case Right(media) => assertEquals(mediaUpdate.url.orNull, media.url) assertEquals(mediaUpdate.caption, media.caption) @@ -94,7 +94,7 @@ trait MediaServiceSuite extends DataInitializer with UserAuthMixin: } test("delete") { - val root = init(2, 0) + val root = init(2, 0) assert(root != null) val mediaCreate = MediaCreate( conceptName = root.getName, @@ -104,22 +104,22 @@ trait MediaServiceSuite extends DataInitializer with UserAuthMixin: mediaType = Some(MediaTypes.IMAGE.name), isPrimary = Some(true) ) - val attempt = runWithUserAuth(user => mediaService.create(mediaCreate, user.username)) + val attempt = runWithUserAuth(user => mediaService.create(mediaCreate, user.username)) attempt match - case Left(e) => fail(e.getMessage) + case Left(e) => fail(e.getMessage) case Right(media) => val attempt = runWithUserAuth(user => mediaService.deleteById(media.id.getOrElse(0L), user.username)) attempt match - case Left(e) => fail(e.getMessage) + case Left(e) => fail(e.getMessage) case Right(_) => mediaService.findById(media.id.getOrElse(0L)) match - case Left(e) => fail(e.getMessage) + case Left(e) => fail(e.getMessage) case Right(opt) => assert(opt.isEmpty) } test("findById") { - val root = init(2, 0) + val root = init(2, 0) assert(root != null) val mediaCreate = MediaCreate( conceptName = root.getName, @@ -129,14 +129,14 @@ trait MediaServiceSuite extends DataInitializer with UserAuthMixin: mediaType = Some(MediaTypes.IMAGE.name), isPrimary = Some(true) ) - val attempt0 = runWithUserAuth(user => mediaService.create(mediaCreate, user.username)) + val attempt0 = runWithUserAuth(user => mediaService.create(mediaCreate, user.username)) attempt0 match - case Left(e) => fail(e.getMessage) + case Left(e) => fail(e.getMessage) case Right(media) => val attempt1 = runWithUserAuth(user => mediaService.findById(media.id.getOrElse(0L))) attempt1 match - case Left(e) => fail(e.getMessage) + case Left(e) => fail(e.getMessage) case Right(opt) => assert(opt.isDefined) val m = opt.get diff --git a/oni/src/main/scala/org/mbari/oni/AppConfig.scala b/oni/src/main/scala/org/mbari/oni/AppConfig.scala index b544c64..018020a 100644 --- a/oni/src/main/scala/org/mbari/oni/AppConfig.scala +++ b/oni/src/main/scala/org/mbari/oni/AppConfig.scala @@ -13,8 +13,8 @@ import org.mbari.oni.jpa.EntityManagerFactories import jakarta.persistence.EntityManagerFactory /** - * Parse configuration info from reference.conf and application.conf - */ + * Parse configuration info from reference.conf and application.conf + */ object AppConfig: val Config = ConfigFactory.load() @@ -28,7 +28,7 @@ object AppConfig: val Description: String = "Organism Naming Infrastructure: Knowledge-base and User Accounts" - val NumberOfThreads: Int = Config.getInt("database.threads") + val NumberOfThreads: Int = Config.getInt("database.threads") lazy val DefaultJwtConfig: JwtConfig = JwtConfig( issuer = Config.getString("basicjwt.issuer"), diff --git a/oni/src/main/scala/org/mbari/oni/Endpoints.scala b/oni/src/main/scala/org/mbari/oni/Endpoints.scala index d82d831..eafbac8 100644 --- a/oni/src/main/scala/org/mbari/oni/Endpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/Endpoints.scala @@ -8,7 +8,21 @@ package org.mbari.oni import jakarta.persistence.EntityManagerFactory -import org.mbari.oni.endpoints.{AuthorizationEndpoints, ConceptEndpoints, ConceptNameEndpoints, HealthEndpoints, HistoryEndpoints, LinkEndpoints, LinkRealizationEndpoints, LinkTemplateEndpoints, MediaEndpoints, PhylogenyEndpoints, PrefNodeEndpoints, ReferenceEndpoints, UserAccountEndpoints} +import org.mbari.oni.endpoints.{ + AuthorizationEndpoints, + ConceptEndpoints, + ConceptNameEndpoints, + HealthEndpoints, + HistoryEndpoints, + LinkEndpoints, + LinkRealizationEndpoints, + LinkTemplateEndpoints, + MediaEndpoints, + PhylogenyEndpoints, + PrefNodeEndpoints, + ReferenceEndpoints, + UserAccountEndpoints +} import org.mbari.oni.etc.jwt.JwtService import org.mbari.oni.jdbc.FastPhylogenyService import sttp.tapir.server.ServerEndpoint @@ -20,8 +34,8 @@ import java.util.concurrent.Executors import scala.concurrent.{ExecutionContext, Future} /** - * Configures all endpoint/api definitions - */ + * Configures all endpoint/api definitions + */ object Endpoints: given JwtService = diff --git a/oni/src/main/scala/org/mbari/oni/Main.scala b/oni/src/main/scala/org/mbari/oni/Main.scala index 4a45e85..b699922 100644 --- a/oni/src/main/scala/org/mbari/oni/Main.scala +++ b/oni/src/main/scala/org/mbari/oni/Main.scala @@ -21,8 +21,8 @@ import scala.concurrent.duration.Duration import io.vertx.core.http.HttpServerOptions /** - * Launches Oni - */ + * Launches Oni + */ object Main: def main(args: Array[String]): Unit = @@ -51,13 +51,14 @@ object Main: .metricsInterceptor(Endpoints.prometheusMetrics.metricsInterceptor()) .options - val vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(AppConfig.NumberOfThreads)) + val vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(AppConfig.NumberOfThreads)) val httpServerOptions = new HttpServerOptions().setCompressionSupported(true) - val server = vertx.createHttpServer(httpServerOptions) - val router = Router.router(vertx) - val interpreter = VertxFutureServerInterpreter(serverOptions) + val server = vertx.createHttpServer(httpServerOptions) + val router = Router.router(vertx) + val interpreter = VertxFutureServerInterpreter(serverOptions) - Endpoints.endpoints + Endpoints + .endpoints .foreach(endpoint => interpreter .blockingRoute(endpoint) @@ -65,7 +66,8 @@ object Main: ) // Add our documentation endpoints - Endpoints.docEndpoints + Endpoints + .docEndpoints .foreach(endpoint => interpreter .route(endpoint) @@ -82,7 +84,6 @@ object Main: Await.result(program, Duration.Inf) - // --- Helidon WeServer // val serverOptions = NimaServerOptions // .customiseInterceptors diff --git a/oni/src/main/scala/org/mbari/oni/OniException.scala b/oni/src/main/scala/org/mbari/oni/OniException.scala index 9054e52..e71825f 100644 --- a/oni/src/main/scala/org/mbari/oni/OniException.scala +++ b/oni/src/main/scala/org/mbari/oni/OniException.scala @@ -8,20 +8,18 @@ package org.mbari.oni /** - * Defines custom exceptions used by Oni - */ + * Defines custom exceptions used by Oni + */ sealed trait OniException extends Throwable -trait NotFoundException extends OniException -trait AccessException extends OniException +trait NotFoundException extends OniException +trait AccessException extends OniException trait ConceptNotFoundException extends NotFoundException case class AccessDenied(user: String) extends Exception(s"I'm sorry `$user`, I can not let you do that.") with AccessException -case class AphiaIdNotFound(aphiaId: Long) extends Exception(s"AphiaId `$aphiaId` was not found") with NotFoundException - - +case class AphiaIdNotFound(aphiaId: Long) extends Exception(s"AphiaId `$aphiaId` was not found") with NotFoundException case class ChildConceptNotFound(parentName: String, childName: String) extends Exception(s"Child concept `$childName` not found under `$parentName`") @@ -35,6 +33,7 @@ case class ConceptNameNotFound(name: String) case class HistoryHasBeenPreviouslyProcessed(id: Long) extends Exception(s"History with id `$id` has already been processed") with OniException +case class HistoryIsInvalid(msg: String) extends Exception(msg) with OniException case class ItemNotFound(msg: String) extends Exception(msg) with NotFoundException case class LinkRealizationIdNotFound(id: Long) extends Exception(s"LinkRealization with `$id` was not found") diff --git a/oni/src/main/scala/org/mbari/oni/domain/AuthorizationSC.scala b/oni/src/main/scala/org/mbari/oni/domain/AuthorizationSC.scala index 62a56d6..c62087e 100644 --- a/oni/src/main/scala/org/mbari/oni/domain/AuthorizationSC.scala +++ b/oni/src/main/scala/org/mbari/oni/domain/AuthorizationSC.scala @@ -8,11 +8,13 @@ package org.mbari.oni.domain /** - * Authorization information as snake_case - * - * @param token_type Typically it will be `Beaerer` - * @param access_token For Oni this will be a JWT - */ + * Authorization information as snake_case + * + * @param token_type + * Typically it will be `Beaerer` + * @param access_token + * For Oni this will be a JWT + */ final case class AuthorizationSC(token_type: String, access_token: String) object AuthorizationSC: diff --git a/oni/src/main/scala/org/mbari/oni/domain/Concept.scala b/oni/src/main/scala/org/mbari/oni/domain/Concept.scala index a3dca41..c3ff557 100644 --- a/oni/src/main/scala/org/mbari/oni/domain/Concept.scala +++ b/oni/src/main/scala/org/mbari/oni/domain/Concept.scala @@ -12,15 +12,21 @@ import org.mbari.oni.etc.jdk.Numbers.* import scala.jdk.CollectionConverters.* /** - * A node in the knoweldgebase tree - * - * @param name The primary, or accepted, name of the concept - * @param rank The pyhologenetic rank - * @param alternativeNames Synonyms, common names, or former names - * @param children Child concepts of this concept - * @param aphiaId The WoRMS AphiaID - * @param id The database id - */ + * A node in the knoweldgebase tree + * + * @param name + * The primary, or accepted, name of the concept + * @param rank + * The pyhologenetic rank + * @param alternativeNames + * Synonyms, common names, or former names + * @param children + * Child concepts of this concept + * @param aphiaId + * The WoRMS AphiaID + * @param id + * The database id + */ case class Concept( name: String, rank: Option[String] = None, @@ -31,36 +37,34 @@ case class Concept( ): /** - * Checks is this concept uses the provided name - * - * @param n The name to check - * @return true if this concept's name or alternative names contain this - * name. false if it does not. - */ + * Checks is this concept uses the provided name + * + * @param n + * The name to check + * @return + * true if this concept's name or alternative names contain this name. false if it does not. + */ def containsName(n: String): Boolean = name.equals(n) || alternativeNames.contains(n) /** - * All names applicable to this concept. THe first name will - * be the primary name. - */ + * All names applicable to this concept. THe first name will be the primary name. + */ lazy val names: Seq[String] = name +: alternativeNames /** - * A sorted list all names applicable to this concept and - * all of it's descendants. - */ + * A sorted list all names applicable to this concept and all of it's descendants. + */ lazy val descendantNames: Seq[String] = descendants .flatMap(_.names) .toSeq .sorted /** - * A list of all of this concepts descendants - */ + * A list of all of this concepts descendants + */ lazy val descendants: Set[Concept] = children.toSet.flatMap(_.descendants) + this - lazy val flatten: Seq[Concept] = Seq(this) ++ children.flatMap(_.flatten) object Concept: diff --git a/oni/src/main/scala/org/mbari/oni/domain/Media.scala b/oni/src/main/scala/org/mbari/oni/domain/Media.scala index e7d2586..9205069 100644 --- a/oni/src/main/scala/org/mbari/oni/domain/Media.scala +++ b/oni/src/main/scala/org/mbari/oni/domain/Media.scala @@ -62,7 +62,7 @@ object Media: ) def resolveMimeType(t: String, url: String): String = - val ext = url.split(Pattern.quote(".")).last.toLowerCase + val ext = url.split(Pattern.quote(".")).last.toLowerCase val mediaType = Strings.initCap(t) Try(MediaType.valueOf(mediaType)) match case Success(MediaType.Image) => s"image/$ext" diff --git a/oni/src/main/scala/org/mbari/oni/domain/MediaUpdate.scala b/oni/src/main/scala/org/mbari/oni/domain/MediaUpdate.scala index 823bd71..da9dc3a 100644 --- a/oni/src/main/scala/org/mbari/oni/domain/MediaUpdate.scala +++ b/oni/src/main/scala/org/mbari/oni/domain/MediaUpdate.scala @@ -9,9 +9,10 @@ package org.mbari.oni.domain import java.net.URL -case class MediaUpdate( url: Option[URL] = None, +case class MediaUpdate( + url: Option[URL] = None, caption: Option[String] = None, credit: Option[String] = None, mediaType: Option[String] = None, - isPrimary: Option[Boolean] = None) { -} + isPrimary: Option[Boolean] = None +) {} diff --git a/oni/src/main/scala/org/mbari/oni/domain/PrefNode.scala b/oni/src/main/scala/org/mbari/oni/domain/PrefNode.scala index d0cc006..b8f1387 100644 --- a/oni/src/main/scala/org/mbari/oni/domain/PrefNode.scala +++ b/oni/src/main/scala/org/mbari/oni/domain/PrefNode.scala @@ -25,4 +25,3 @@ object PrefNode: ) case class PrefNodeUpdate(name: Option[String], key: Option[String], value: String) - diff --git a/oni/src/main/scala/org/mbari/oni/domain/ToCamelCase.scala b/oni/src/main/scala/org/mbari/oni/domain/ToCamelCase.scala index 23b9a80..e154e27 100644 --- a/oni/src/main/scala/org/mbari/oni/domain/ToCamelCase.scala +++ b/oni/src/main/scala/org/mbari/oni/domain/ToCamelCase.scala @@ -7,6 +7,5 @@ package org.mbari.oni.domain -trait ToCamelCase[A] { +trait ToCamelCase[A]: def toCamelCase: A -} \ No newline at end of file diff --git a/oni/src/main/scala/org/mbari/oni/domain/ToSnakeCase.scala b/oni/src/main/scala/org/mbari/oni/domain/ToSnakeCase.scala index a37fce6..c0a2bcb 100644 --- a/oni/src/main/scala/org/mbari/oni/domain/ToSnakeCase.scala +++ b/oni/src/main/scala/org/mbari/oni/domain/ToSnakeCase.scala @@ -7,7 +7,5 @@ package org.mbari.oni.domain -trait ToSnakeCase[A] { +trait ToSnakeCase[A]: def toSnakeCase: A -} - diff --git a/oni/src/main/scala/org/mbari/oni/domain/UserAccountCreate.scala b/oni/src/main/scala/org/mbari/oni/domain/UserAccountCreate.scala index 0cf33ae..3776d7a 100644 --- a/oni/src/main/scala/org/mbari/oni/domain/UserAccountCreate.scala +++ b/oni/src/main/scala/org/mbari/oni/domain/UserAccountCreate.scala @@ -8,16 +8,16 @@ package org.mbari.oni.domain case class UserAccountCreate( - username: String, - password: String, - role: Option[String] = Some(UserAccountRoles.READONLY.getRoleName), - affiliation: Option[String] = None, - firstName: Option[String] = None, - lastName: Option[String] = None, - email: Option[String] = None, - first_name: Option[String] = None, - last_name: Option[String] = None -) { + username: String, + password: String, + role: Option[String] = Some(UserAccountRoles.READONLY.getRoleName), + affiliation: Option[String] = None, + firstName: Option[String] = None, + lastName: Option[String] = None, + email: Option[String] = None, + first_name: Option[String] = None, + last_name: Option[String] = None +): def toUserAccount: UserAccount = UserAccount( username, password, @@ -27,9 +27,8 @@ case class UserAccountCreate( lastName.orElse(last_name), email ) -} -object UserAccountCreate { +object UserAccountCreate: def fromUserAccount(userAccount: UserAccount): UserAccountCreate = UserAccountCreate( userAccount.username, userAccount.password, @@ -39,4 +38,3 @@ object UserAccountCreate { userAccount.lastName, userAccount.email ) -} diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/AuthorizationEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/AuthorizationEndpoints.scala index 02b5b04..4563acc 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/AuthorizationEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/AuthorizationEndpoints.scala @@ -24,8 +24,10 @@ import sttp.model.headers.{AuthenticationScheme, WWWAuthenticateChallenge} import scala.concurrent.{ExecutionContext, Future} -class AuthorizationEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtService: JwtService, executionContext: ExecutionContext) - extends Endpoints: +class AuthorizationEndpoints(entityManagerFactory: EntityManagerFactory)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: private val service = UserAccountService(entityManagerFactory) private val base = "auth" @@ -102,13 +104,18 @@ class AuthorizationEndpoints(entityManagerFactory: EntityManagerFactory)(using j _.toRight(NotFound("User account not found")) ) entity <- Right(userAccount.toEntity) - jwt <- jwtService - .login(usernamePassword.username, usernamePassword.password.getOrElse(""), entity) - .toRight(Unauthorized("Unable to login. Check your username and password and verify that you are an administrator or maintainer")) + jwt <- + jwtService + .login(usernamePassword.username, usernamePassword.password.getOrElse(""), entity) + .toRight( + Unauthorized( + "Unable to login. Check your username and password and verify that you are an administrator or maintainer" + ) + ) yield AuthorizationSC.bearer(jwt) } .serverLogic(bearerAuth => Unit => Future(Right(bearerAuth))) - override val all: List[Endpoint[?, ?, ?, ?, ?]] = List(loginEndpoint, authEndpoint) + override val all: List[Endpoint[?, ?, ?, ?, ?]] = List(loginEndpoint, authEndpoint) override val allImpl: List[ServerEndpoint[Any, Future]] = List(loginEndpointImpl, authEndpointImpl) diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/ConceptEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/ConceptEndpoints.scala index fb4920f..17516c6 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/ConceptEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/ConceptEndpoints.scala @@ -13,7 +13,15 @@ import sttp.tapir.* import sttp.tapir.Endpoint import sttp.tapir.json.circe.* import sttp.tapir.server.ServerEndpoint -import org.mbari.oni.domain.{ConceptCreate, ConceptDelete, ConceptMetadata, ConceptUpdate, ErrorMsg, NotFound, ServerError} +import org.mbari.oni.domain.{ + ConceptCreate, + ConceptDelete, + ConceptMetadata, + ConceptUpdate, + ErrorMsg, + NotFound, + ServerError +} import org.mbari.oni.etc.circe.CirceCodecs.given import org.mbari.oni.etc.jwt.JwtService import org.mbari.oni.services.{ConceptCache, ConceptNameService, ConceptService} @@ -23,7 +31,10 @@ import scala.concurrent.{ExecutionContext, Future} import org.mbari.oni.domain.Rank import org.mbari.oni.services.RankValidator -class ConceptEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtService: JwtService, executionContext: ExecutionContext) extends Endpoints: +class ConceptEndpoints(entityManagerFactory: EntityManagerFactory)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: private val service = ConceptService(entityManagerFactory) private val conceptNameService = ConceptNameService(entityManagerFactory) @@ -75,12 +86,13 @@ class ConceptEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtServ val deleteEndpointImpl: ServerEndpoint[Any, Future] = deleteEndpoint .serverSecurityLogic(jwtOpt => verifyLoginAsync(jwtOpt)) .serverLogic { userAccount => name => - Future(service - .delete(name, userAccount.username) - .fold( - error => Left(ServerError(error.getMessage)), - _ => Right(()) - ) + Future( + service + .delete(name, userAccount.username) + .fold( + error => Left(ServerError(error.getMessage)), + _ => Right(()) + ) ).andThen(v => conceptCache.clear() v @@ -156,8 +168,8 @@ class ConceptEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtServ .tag(tag) val listValidRanksImpl: ServerEndpoint[Any, Future] = listValidRanks.serverLogic { _ => - Future(Right(RankValidator.ValidRankLevelsAndNames.map { - (rankLevel, rankName) => Rank(rankLevel, rankName) + Future(Right(RankValidator.ValidRankLevelsAndNames.map { (rankLevel, rankName) => + Rank(rankLevel, rankName) })) } @@ -168,23 +180,26 @@ class ConceptEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtServ .in(jsonBody[ConceptUpdate]) .out(jsonBody[ConceptMetadata]) .name("updateConcept") - .description("Update a concept. To remove a rank name or level, set it to an empty string. Only administrators can remove rank names and levels.") + .description( + "Update a concept. To remove a rank name or level, set it to an empty string. Only administrators can remove rank names and levels." + ) .tag(tag) val updateEndpointImpl: ServerEndpoint[Any, Future] = updateEndpoint .serverSecurityLogic(jwtOpt => verifyLoginAsync(jwtOpt)) .serverLogic { userAccount => (name, conceptUpdate) => - Future(service - .update(name, conceptUpdate, userAccount.username) - .fold( - error => Left(ServerError(error.getMessage)), - concept => Right(concept) - ) - ) - .andThen(v => - conceptCache.clear() - v + Future( + service + .update(name, conceptUpdate, userAccount.username) + .fold( + error => Left(ServerError(error.getMessage)), + concept => Right(concept) + ) ) + .andThen(v => + conceptCache.clear() + v + ) } override val all: List[Endpoint[?, ?, ?, ?, ?]] = List( diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/ConceptNameEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/ConceptNameEndpoints.scala index 9b36c55..b0cc11d 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/ConceptNameEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/ConceptNameEndpoints.scala @@ -20,7 +20,10 @@ import org.mbari.oni.etc.circe.CirceCodecs.given import scala.concurrent.{ExecutionContext, Future} -class ConceptNameEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtService: JwtService, executionContext: ExecutionContext) extends Endpoints: +class ConceptNameEndpoints(entityManagerFactory: EntityManagerFactory)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: private val service = ConceptNameService(entityManagerFactory) private val base = "names" diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/Endpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/Endpoints.scala index 56be8c5..6a90c2f 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/Endpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/Endpoints.scala @@ -42,40 +42,42 @@ trait Endpoints: val log: System.Logger = System.getLogger(getClass.getName) // --- Schemas - implicit lazy val sCount: Schema[Count] = Schema.derived[Count] - implicit lazy val sExtendedHistory: Schema[ExtendedHistory] = Schema.derived[ExtendedHistory] - implicit lazy val sExtendedLink: Schema[ExtendedLink] = Schema.derived[ExtendedLink] - implicit lazy val sLink: Schema[Link] = Schema.derived[Link] - implicit lazy val sURI: Schema[URI] = Schema.string - implicit lazy val sURL: Schema[URL] = Schema.string - implicit lazy val sInstant: Schema[Instant] = Schema.string - implicit lazy val sDoi: Schema[ReferenceQuery] = Schema.derived[ReferenceQuery] - implicit lazy val sLinkCreate: Schema[LinkCreate] = Schema.derived[LinkCreate] - implicit lazy val sRenameToConceptRequest: Schema[LinkRenameToConceptRequest] = Schema.derived[LinkRenameToConceptRequest] - implicit lazy val sRenameToConceptResponse: Schema[LinkRenameToConceptResponse] = Schema.derived[LinkRenameToConceptResponse] - implicit lazy val sLinkUpdate: Schema[LinkUpdate] = Schema.derived[LinkUpdate] - implicit lazy val sMedia: Schema[Media] = Schema.derived[Media] - implicit lazy val sMediaCreate: Schema[MediaCreate] = Schema.derived[MediaCreate] - implicit lazy val sMediaUpdate: Schema[MediaUpdate] = Schema.derived[MediaUpdate] - implicit lazy val sPaging: Schema[Paging] = Schema.derived[Paging] - implicit lazy val sPrefNode: Schema[PrefNode] = Schema.derived[PrefNode] - implicit lazy val sPrefNodeUpdate: Schema[PrefNodeUpdate] = Schema.derived[PrefNodeUpdate] - implicit lazy val sReference: Schema[Reference] = Schema.derived[Reference] - implicit lazy val sReferenceUpdate: Schema[ReferenceUpdate] = Schema.derived[ReferenceUpdate] - implicit lazy val sConceptCreate: Schema[ConceptCreate] = Schema.derived[ConceptCreate] - implicit lazy val sConceptDelete: Schema[ConceptDelete] = Schema.derived[ConceptDelete] - implicit lazy val sConceptNameCreate: Schema[ConceptNameCreate] = Schema.derived[ConceptNameCreate] - implicit lazy val sConceptNameUpdate: Schema[ConceptNameUpdate] = Schema.derived[ConceptNameUpdate] - implicit lazy val sConceptUpdate: Schema[ConceptUpdate] = Schema.derived[ConceptUpdate] - implicit lazy val sConceptMetadata: Schema[ConceptMetadata] = Schema.derived[ConceptMetadata] - implicit lazy val sConceptName: Schema[RawConceptName] = Schema.derived[RawConceptName] - implicit lazy val sPageSeqExtendedHistory: Schema[Page[Seq[ExtendedHistory]]] = + implicit lazy val sCount: Schema[Count] = Schema.derived[Count] + implicit lazy val sExtendedHistory: Schema[ExtendedHistory] = Schema.derived[ExtendedHistory] + implicit lazy val sExtendedLink: Schema[ExtendedLink] = Schema.derived[ExtendedLink] + implicit lazy val sLink: Schema[Link] = Schema.derived[Link] + implicit lazy val sURI: Schema[URI] = Schema.string + implicit lazy val sURL: Schema[URL] = Schema.string + implicit lazy val sInstant: Schema[Instant] = Schema.string + implicit lazy val sDoi: Schema[ReferenceQuery] = Schema.derived[ReferenceQuery] + implicit lazy val sLinkCreate: Schema[LinkCreate] = Schema.derived[LinkCreate] + implicit lazy val sRenameToConceptRequest: Schema[LinkRenameToConceptRequest] = + Schema.derived[LinkRenameToConceptRequest] + implicit lazy val sRenameToConceptResponse: Schema[LinkRenameToConceptResponse] = + Schema.derived[LinkRenameToConceptResponse] + implicit lazy val sLinkUpdate: Schema[LinkUpdate] = Schema.derived[LinkUpdate] + implicit lazy val sMedia: Schema[Media] = Schema.derived[Media] + implicit lazy val sMediaCreate: Schema[MediaCreate] = Schema.derived[MediaCreate] + implicit lazy val sMediaUpdate: Schema[MediaUpdate] = Schema.derived[MediaUpdate] + implicit lazy val sPaging: Schema[Paging] = Schema.derived[Paging] + implicit lazy val sPrefNode: Schema[PrefNode] = Schema.derived[PrefNode] + implicit lazy val sPrefNodeUpdate: Schema[PrefNodeUpdate] = Schema.derived[PrefNodeUpdate] + implicit lazy val sReference: Schema[Reference] = Schema.derived[Reference] + implicit lazy val sReferenceUpdate: Schema[ReferenceUpdate] = Schema.derived[ReferenceUpdate] + implicit lazy val sConceptCreate: Schema[ConceptCreate] = Schema.derived[ConceptCreate] + implicit lazy val sConceptDelete: Schema[ConceptDelete] = Schema.derived[ConceptDelete] + implicit lazy val sConceptNameCreate: Schema[ConceptNameCreate] = Schema.derived[ConceptNameCreate] + implicit lazy val sConceptNameUpdate: Schema[ConceptNameUpdate] = Schema.derived[ConceptNameUpdate] + implicit lazy val sConceptUpdate: Schema[ConceptUpdate] = Schema.derived[ConceptUpdate] + implicit lazy val sConceptMetadata: Schema[ConceptMetadata] = Schema.derived[ConceptMetadata] + implicit lazy val sConceptName: Schema[RawConceptName] = Schema.derived[RawConceptName] + implicit lazy val sPageSeqExtendedHistory: Schema[Page[Seq[ExtendedHistory]]] = Schema.derived[Page[Seq[ExtendedHistory]]] - implicit lazy val sPageSeqString: Schema[Page[Seq[String]]] = Schema.derived[Page[Seq[String]]] - implicit lazy val sRank: Schema[Rank] = Schema.derived[Rank] - implicit lazy val sUserAccount: Schema[UserAccount] = Schema.derived[UserAccount] - implicit lazy val sUserAccountCreate: Schema[UserAccountCreate] = Schema.derived[UserAccountCreate] - implicit lazy val sUserAccountUpdate: Schema[UserAccountUpdate] = Schema.derived[UserAccountUpdate] + implicit lazy val sPageSeqString: Schema[Page[Seq[String]]] = Schema.derived[Page[Seq[String]]] + implicit lazy val sRank: Schema[Rank] = Schema.derived[Rank] + implicit lazy val sUserAccount: Schema[UserAccount] = Schema.derived[UserAccount] + implicit lazy val sUserAccountCreate: Schema[UserAccountCreate] = Schema.derived[UserAccountCreate] + implicit lazy val sUserAccountUpdate: Schema[UserAccountUpdate] = Schema.derived[UserAccountUpdate] // Make Tapir recursive types happy by using `implicit def`, not lazy val // https://tapir.softwaremill.com/en/latest/endpoint/schemas.html#derivation-for-recursive-types-in-scala3 @@ -117,9 +119,9 @@ trait Endpoints: def handleErrors[T](f: => Either[Throwable, T]): Either[ErrorMsg, T] = f match - case Right(concept) => Right(concept) + case Right(concept) => Right(concept) case Left(c: ConceptNotFoundException) => Left(NotFound(c.getMessage)) - case Left(e) => + case Left(e) => log.atError.withCause(e).log("Error") Left(ServerError(e.getMessage)) @@ -130,7 +132,7 @@ trait Endpoints: // , // Right(_) // ) - + def handleErrorsAsync[T](f: => Either[Throwable, T])(using ec: ExecutionContext): Future[Either[ErrorMsg, T]] = Future(handleErrors(f)) @@ -165,7 +167,7 @@ trait Endpoints: jwtService.decode(jwt) match case None => Left(Unauthorized("Invalid token")) case Some(userAccount) => Right(userAccount) - + def verifyLoginAsync( jwtOpt: Option[String] )(using jwtService: JwtService, executionContext: ExecutionContext): Future[Either[Unauthorized, UserAccount]] = diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/HistoryEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/HistoryEndpoints.scala index b7978e8..faf4a10 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/HistoryEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/HistoryEndpoints.scala @@ -23,7 +23,8 @@ import sttp.shared.Identity import scala.concurrent.{ExecutionContext, Future} class HistoryEndpoints(entityManagerFactory: EntityManagerFactory, fastPhylogenyService: FastPhylogenyService)(using - jwtService: JwtService, executionContext: ExecutionContext + jwtService: JwtService, + executionContext: ExecutionContext ) extends Endpoints: private val service = HistoryService(entityManagerFactory) @@ -59,11 +60,11 @@ class HistoryEndpoints(entityManagerFactory: EntityManagerFactory, fastPhylogeny val pendingEndpointImpl: ServerEndpoint[Any, Future] = pendingEndpoint.serverLogic { paging => Future { - val limit = paging.limit.getOrElse(defaultLimit) - val offset = paging.offset.getOrElse(0) + val limit = paging.limit.getOrElse(defaultLimit) + val offset = paging.offset.getOrElse(0) val attempt = for pending <- service.findAllPending(limit, offset) - yield Page(pending, limit, offset) + yield Page(pending, limit, offset) handleErrors(attempt) } } @@ -95,11 +96,11 @@ class HistoryEndpoints(entityManagerFactory: EntityManagerFactory, fastPhylogeny val approvedEndpointsImpl: ServerEndpoint[Any, Future] = approvedEndpoints.serverLogic { paging => Future { - val limit = paging.limit.getOrElse(defaultLimit) - val offset = paging.offset.getOrElse(0) + val limit = paging.limit.getOrElse(defaultLimit) + val offset = paging.offset.getOrElse(0) val attempt = for approved <- service.findAllApproved() - yield Page(approved, limit, offset) + yield Page(approved, limit, offset) handleErrors(attempt) } } diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/LinkEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/LinkEndpoints.scala index ae0b79e..b563bf2 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/LinkEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/LinkEndpoints.scala @@ -19,7 +19,8 @@ import sttp.tapir.server.ServerEndpoint import scala.concurrent.{ExecutionContext, Future} -class LinkEndpoints(entityManagerFactory: EntityManagerFactory)(using executionContext: ExecutionContext) extends Endpoints: +class LinkEndpoints(entityManagerFactory: EntityManagerFactory)(using executionContext: ExecutionContext) + extends Endpoints: private val service = LinkService(entityManagerFactory) private val base = "links" diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/LinkRealizationEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/LinkRealizationEndpoints.scala index 11ba1c5..3ff78f0 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/LinkRealizationEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/LinkRealizationEndpoints.scala @@ -20,8 +20,10 @@ import sttp.tapir.server.ServerEndpoint import scala.concurrent.{ExecutionContext, Future} -class LinkRealizationEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtService: JwtService, executionContext: ExecutionContext) - extends Endpoints: +class LinkRealizationEndpoints(entityManagerFactory: EntityManagerFactory)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: private val service = LinkRealizationService(entityManagerFactory) private val base = "linkrealizations" diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/LinkTemplateEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/LinkTemplateEndpoints.scala index 8518463..451d385 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/LinkTemplateEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/LinkTemplateEndpoints.scala @@ -8,7 +8,16 @@ package org.mbari.oni.endpoints import jakarta.persistence.EntityManagerFactory -import org.mbari.oni.domain.{ErrorMsg, ExtendedLink, Link, LinkCreate, LinkRenameToConceptRequest, LinkRenameToConceptResponse, LinkUpdate, ServerError} +import org.mbari.oni.domain.{ + ErrorMsg, + ExtendedLink, + Link, + LinkCreate, + LinkRenameToConceptRequest, + LinkRenameToConceptResponse, + LinkUpdate, + ServerError +} import org.mbari.oni.etc.circe.CirceCodecs.given import org.mbari.oni.etc.jwt.JwtService import org.mbari.oni.services.LinkTemplateService @@ -21,7 +30,10 @@ import org.mbari.oni.etc.jdk.Loggers.given import scala.concurrent.{ExecutionContext, Future} -class LinkTemplateEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtService: JwtService, executionContext: ExecutionContext) extends Endpoints: +class LinkTemplateEndpoints(entityManagerFactory: EntityManagerFactory)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: private val service = LinkTemplateService(entityManagerFactory) private val base = "linktemplates" @@ -93,7 +105,8 @@ class LinkTemplateEndpoints(entityManagerFactory: EntityManagerFactory)(using jw handleErrorsAsync(service.findByToConcept(toConcept)) } - val renameToConcept: Endpoint[Option[String], LinkRenameToConceptRequest, ErrorMsg, LinkRenameToConceptResponse, Any] = + val renameToConcept + : Endpoint[Option[String], LinkRenameToConceptRequest, ErrorMsg, LinkRenameToConceptResponse, Any] = secureEndpoint .put .in(base / "toconcept" / "rename") @@ -105,10 +118,9 @@ class LinkTemplateEndpoints(entityManagerFactory: EntityManagerFactory)(using jw val renameToConceptImpl: ServerEndpoint[Any, Future] = renameToConcept .serverSecurityLogic(jwtOpt => verifyLoginAsync(jwtOpt)) - .serverLogic { userAccount => request => + .serverLogic { userAccount => request => handleErrorsAsync(service.renameToConcept(request.old, request.`new`, userAccount.username)) } - val createLinkTemplate: Endpoint[Option[String], LinkCreate, ErrorMsg, ExtendedLink, Any] = secureEndpoint .post diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/MediaEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/MediaEndpoints.scala index ae326e4..a74448a 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/MediaEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/MediaEndpoints.scala @@ -25,13 +25,15 @@ import org.mbari.oni.etc.circe.CirceCodecs.given import scala.concurrent.Future import org.mbari.oni.etc.jwt.JwtService -class MediaEndpoints(entityManagerFactory: EntityManagerFactory, - fastPhylogenyService: FastPhylogenyService)(using jwtService: JwtService, executionContext: ExecutionContext) extends Endpoints: +class MediaEndpoints(entityManagerFactory: EntityManagerFactory, fastPhylogenyService: FastPhylogenyService)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: - private val service = MediaService(entityManagerFactory, fastPhylogenyService) + private val service = MediaService(entityManagerFactory, fastPhylogenyService) private val conceptService = ConceptService(entityManagerFactory) - private val base = "media" - private val tag = "Media" + private val base = "media" + private val tag = "Media" val mediaForConceptEndpoint: Endpoint[Unit, String, ErrorMsg, Seq[Media], Any] = openEndpoint .get @@ -48,7 +50,11 @@ class MediaEndpoints(entityManagerFactory: EntityManagerFactory, val createMediaEndpoint: Endpoint[Option[String], MediaCreate, ErrorMsg, Media, Any] = secureEndpoint .post .in(base) - .in(jsonBody[MediaCreate].description("The media record to create. mediaType defaults to 'IMAGE', but can also be set to 'VIDEO'")) + .in( + jsonBody[MediaCreate].description( + "The media record to create. mediaType defaults to 'IMAGE', but can also be set to 'VIDEO'" + ) + ) .out(jsonBody[Media]) .name("createMedia") .description("Create a new media record") @@ -102,5 +108,3 @@ class MediaEndpoints(entityManagerFactory: EntityManagerFactory, updateMediaEndpointImpl, deleteMediaEndpointImpl ) - - diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/PhylogenyEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/PhylogenyEndpoints.scala index 3b14102..74c52be 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/PhylogenyEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/PhylogenyEndpoints.scala @@ -20,7 +20,8 @@ import jakarta.persistence.EntityManagerFactory import scala.concurrent.{ExecutionContext, Future} -class PhylogenyEndpoints(entityManagerFactory: EntityManagerFactory)(using executionContext: ExecutionContext) extends Endpoints: +class PhylogenyEndpoints(entityManagerFactory: EntityManagerFactory)(using executionContext: ExecutionContext) + extends Endpoints: /** This services does caching so we should share it */ val service: FastPhylogenyService = FastPhylogenyService(entityManagerFactory) diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/PrefNodeEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/PrefNodeEndpoints.scala index 9aafa33..0e1d9f4 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/PrefNodeEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/PrefNodeEndpoints.scala @@ -21,7 +21,10 @@ import org.mbari.oni.services.PrefNodeService import scala.concurrent.{ExecutionContext, Future} -class PrefNodeEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtService: JwtService, executionContext: ExecutionContext) extends Endpoints: +class PrefNodeEndpoints(entityManagerFactory: EntityManagerFactory)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: private val service = PrefNodeService(entityManagerFactory) @@ -109,7 +112,8 @@ class PrefNodeEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtSer .serverSecurityLogic(jwtOpt => verifyAsync(jwtOpt)) .serverLogic { _ => prefNode => handleErrorsAsync(service.create(prefNode)) } - val updateEndpoint: Endpoint[Option[String], (Option[String], Option[String], PrefNodeUpdate), ErrorMsg, PrefNode, Any] = + val updateEndpoint + : Endpoint[Option[String], (Option[String], Option[String], PrefNodeUpdate), ErrorMsg, PrefNode, Any] = secureEndpoint .put .in(base) @@ -118,22 +122,24 @@ class PrefNodeEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtSer .in(oneOfBody(jsonBody[PrefNodeUpdate], formBody[PrefNodeUpdate])) .out(jsonBody[PrefNode]) .name("updatePrefNode") - .description("Update a prefNode. Can be called using form body or json body. If name and key are not in the request body, they must be in the query parameters.") + .description( + "Update a prefNode. Can be called using form body or json body. If name and key are not in the request body, they must be in the query parameters." + ) .tag(tag) val updateEndpointImpl: ServerEndpoint[Any, Future] = updateEndpoint .serverSecurityLogic(jwtOpt => verifyAsync(jwtOpt)) - .serverLogic { _ => (nameOpt, keyOpt, prefNodeUpdate) => { - val name = prefNodeUpdate.name.orElse(nameOpt) - val key = prefNodeUpdate.key.orElse(keyOpt) + .serverLogic { _ => (nameOpt, keyOpt, prefNodeUpdate) => + val name = prefNodeUpdate.name.orElse(nameOpt) + val key = prefNodeUpdate.key.orElse(keyOpt) val value = prefNodeUpdate.value - if (name.isEmpty || key.isEmpty) then + if name.isEmpty || key.isEmpty then Future(Left(BadRequest("A name and key must be provided in order to update a preference."))) else val prefNode = PrefNode(name.get, key.get, value) handleErrorsAsync(service.update(prefNode)) - } } + } val deleteEndpoint: Endpoint[Option[String], (String, String), ErrorMsg, Unit, Any] = secureEndpoint diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/ReferenceEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/ReferenceEndpoints.scala index 1487669..3ec9fd4 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/ReferenceEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/ReferenceEndpoints.scala @@ -24,7 +24,10 @@ import org.mbari.oni.etc.jdk.Loggers.given import java.net.{URLDecoder, URLEncoder} import scala.concurrent.{ExecutionContext, Future} -class ReferenceEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtService: JwtService, executionContext: ExecutionContext) extends Endpoints: +class ReferenceEndpoints(entityManagerFactory: EntityManagerFactory)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: private val service = ReferenceService(entityManagerFactory) @@ -44,8 +47,8 @@ class ReferenceEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtSe Future { service.findById(id) match case Right(Some(reference)) => Right(reference) - case Right(None) => Left(NotFound(s"Reference with ID $id not found")) - case Left(e) => Left(ServerError(e.getMessage)) + case Right(None) => Left(NotFound(s"Reference with ID $id not found")) + case Left(e) => Left(ServerError(e.getMessage)) } } @@ -101,12 +104,12 @@ class ReferenceEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtSe val findReferenceByDoiEndpointImpl: ServerEndpoint[Any, Future] = findReferenceByDoiEndpoint.serverLogic { doi => Future { doi.doi match - case None => Left(BadRequest("DOI is required")) + case None => Left(BadRequest("DOI is required")) case Some(doi) => service.findByDoi(doi) match case Right(Some(reference)) => Right(reference) - case Right(None) => Left(NotFound(s"Reference with DOI '${doi}' not found")) - case Left(e) => Left(ServerError(e.getMessage)) + case Right(None) => Left(NotFound(s"Reference with DOI '${doi}' not found")) + case Left(e) => Left(ServerError(e.getMessage)) } } diff --git a/oni/src/main/scala/org/mbari/oni/endpoints/UserAccountEndpoints.scala b/oni/src/main/scala/org/mbari/oni/endpoints/UserAccountEndpoints.scala index 3b44e0f..295b29a 100644 --- a/oni/src/main/scala/org/mbari/oni/endpoints/UserAccountEndpoints.scala +++ b/oni/src/main/scala/org/mbari/oni/endpoints/UserAccountEndpoints.scala @@ -20,7 +20,10 @@ import org.mbari.oni.etc.circe.CirceCodecs.{*, given} import scala.concurrent.{ExecutionContext, Future} -class UserAccountEndpoints(entityManagerFactory: EntityManagerFactory)(using jwtService: JwtService, executionContext: ExecutionContext) extends Endpoints: +class UserAccountEndpoints(entityManagerFactory: EntityManagerFactory)(using + jwtService: JwtService, + executionContext: ExecutionContext +) extends Endpoints: private val service = UserAccountService(entityManagerFactory) private val base = "users" @@ -53,7 +56,7 @@ class UserAccountEndpoints(entityManagerFactory: EntityManagerFactory)(using jwt val findByUserNameEndpointImpl: ServerEndpoint[Any, Future] = findByUserNameEndpoint.serverLogic { name => Future { handleErrors(service.findByUserName(name)).flatMap { - case None => Left(NotFound(s"User account not found: $name")) + case None => Left(NotFound(s"User account not found: $name")) case Some(value) => Right(value) } } @@ -94,7 +97,16 @@ class UserAccountEndpoints(entityManagerFactory: EntityManagerFactory)(using jwt secureEndpoint .post .in(base) - .in(oneOfBody(jsonBody[UserAccountCreate].description("The user account to create. Accepts camelCase or snake_case."), formBody[UserAccountCreate].description("The user account to create. Accepts camelCase or snake_case."))) + .in( + oneOfBody( + jsonBody[UserAccountCreate].description( + "The user account to create. Accepts camelCase or snake_case." + ), + formBody[UserAccountCreate].description( + "The user account to create. Accepts camelCase or snake_case." + ) + ) + ) .out(jsonBody[UserAccount]) .name("createUserAccount") .description("Create a new user account") diff --git a/oni/src/main/scala/org/mbari/oni/etc/jdk/CloseableLock.scala b/oni/src/main/scala/org/mbari/oni/etc/jdk/CloseableLock.scala index 2f0466b..3b73622 100644 --- a/oni/src/main/scala/org/mbari/oni/etc/jdk/CloseableLock.scala +++ b/oni/src/main/scala/org/mbari/oni/etc/jdk/CloseableLock.scala @@ -10,15 +10,12 @@ package org.mbari.oni.etc.jdk import java.util.concurrent.locks.ReentrantLock /** - * An auto-closeable lock. Use in a try-with-resources block to ensure the lock is released. - */ -class CloseableLock extends ReentrantLock with AutoCloseable { + * An auto-closeable lock. Use in a try-with-resources block to ensure the lock is released. + */ +class CloseableLock extends ReentrantLock with AutoCloseable: - def lockAndGet: CloseableLock = { + def lockAndGet: CloseableLock = lock(); this - } override def close(): Unit = unlock() - -} diff --git a/oni/src/main/scala/org/mbari/oni/etc/jdk/Loggers.scala b/oni/src/main/scala/org/mbari/oni/etc/jdk/Loggers.scala index 6cd6618..bc619d1 100644 --- a/oni/src/main/scala/org/mbari/oni/etc/jdk/Loggers.scala +++ b/oni/src/main/scala/org/mbari/oni/etc/jdk/Loggers.scala @@ -13,9 +13,9 @@ import java.util.function.Supplier /** * Add fluent logging to System.Logger. Usage: - * + * * ```scala - * import org.fathomnet.support.etc.jdk.Loggers.{given, *} + * import org.fathomnet.support.etc.jdk.Loggers.{*, given} * given log: Logger = System.getLogger("my.logger") * * log.atInfo.log("Hello World") diff --git a/oni/src/main/scala/org/mbari/oni/etc/jdk/Strings.scala b/oni/src/main/scala/org/mbari/oni/etc/jdk/Strings.scala index 122073f..35eb8f4 100644 --- a/oni/src/main/scala/org/mbari/oni/etc/jdk/Strings.scala +++ b/oni/src/main/scala/org/mbari/oni/etc/jdk/Strings.scala @@ -20,9 +20,11 @@ object Strings: /** * Change case of a string to init Cap. That is the first letter is capitalized and the rest are lower case. - * @param s the string to convert - * @return the init cap version of the string + * @param s + * the string to convert + * @return + * the init cap version of the string */ def initCap(s: String): String = val a = s.toLowerCase() - a.substring(0, 1).toUpperCase() + a.substring(1) \ No newline at end of file + a.substring(0, 1).toUpperCase() + a.substring(1) diff --git a/oni/src/main/scala/org/mbari/oni/etc/sdk/IO.scala b/oni/src/main/scala/org/mbari/oni/etc/sdk/IO.scala index f891a2e..73575c8 100644 --- a/oni/src/main/scala/org/mbari/oni/etc/sdk/IO.scala +++ b/oni/src/main/scala/org/mbari/oni/etc/sdk/IO.scala @@ -16,7 +16,7 @@ import scala.concurrent.{ExecutionContext, Future} * * This doesn't model asyncrhonous operations, we're going to use sync operations with virual threads instead. */ -type IO[A, B] = A => Either[Throwable, B] +type IO[A, B] = A => Either[Throwable, B] type AsyncIO[A, B] = A => Future[B] object IO: diff --git a/oni/src/main/scala/org/mbari/oni/jdbc/ConceptRow.scala b/oni/src/main/scala/org/mbari/oni/jdbc/ConceptRow.scala index 682f672..69bf99d 100644 --- a/oni/src/main/scala/org/mbari/oni/jdbc/ConceptRow.scala +++ b/oni/src/main/scala/org/mbari/oni/jdbc/ConceptRow.scala @@ -49,6 +49,5 @@ case class ConceptRow( lazy val lastUpdate: Instant = Seq(conceptTimestamp, conceptNameTimestamp) .maxBy(i => i.toEpochMilli) - case class CName(name: String, nameType: String): val isPrimary: Boolean = nameType.equalsIgnoreCase(ConceptNameTypes.PRIMARY.getType) diff --git a/oni/src/main/scala/org/mbari/oni/jdbc/FastPhylogenyService.scala b/oni/src/main/scala/org/mbari/oni/jdbc/FastPhylogenyService.scala index 0f7d7cf..e4ce282 100644 --- a/oni/src/main/scala/org/mbari/oni/jdbc/FastPhylogenyService.scala +++ b/oni/src/main/scala/org/mbari/oni/jdbc/FastPhylogenyService.scala @@ -86,12 +86,11 @@ class FastPhylogenyService(entityManagerFactory: EntityManagerFactory): val r = MutableConcept.toTree(cache) rootNode = r._1 allNodes = r._2 - finally - lock.unlock() + finally lock.unlock() def findLastUpdate(): Instant = val attempt = entityManagerFactory.transaction(entityManager => - val query = entityManager.createNativeQuery(FastPhylogenyDAO.LAST_UPDATE_SQL) + val query = entityManager.createNativeQuery(FastPhylogenyDAO.LAST_UPDATE_SQL) val lastUpdate = query.getSingleResult.asInstanceOf[Timestamp] if lastUpdate == null then log.atWarn @@ -102,7 +101,7 @@ class FastPhylogenyService(entityManagerFactory: EntityManagerFactory): else lastUpdate.toInstant ) attempt match - case Right(result) => result + case Right(result) => result case Left(exception) => log.atError.withCause(exception).log("Failed to execute last update query") Instant.now() @@ -114,8 +113,7 @@ class FastPhylogenyService(entityManagerFactory: EntityManagerFactory): ) attempt match case Right(results) => - for - result <- ArraySeq.unsafeWrapArray(results.toArray) + for result <- ArraySeq.unsafeWrapArray(results.toArray) yield val row = result.asInstanceOf[Array[Object]] val id = row(0).asLong.getOrElse(-1L) @@ -126,7 +124,16 @@ class FastPhylogenyService(entityManagerFactory: EntityManagerFactory): val nameType = row(5).asString.orNull val conceptTimestamp = row(6).asInstant.getOrElse(Instant.now()) val conceptNameTimestamp = row(7).asInstant.getOrElse(Instant.now()) - ConceptRow(id, parentId, name, rankLevel, rankName, nameType, conceptTimestamp, conceptNameTimestamp) + ConceptRow( + id, + parentId, + name, + rankLevel, + rankName, + nameType, + conceptTimestamp, + conceptNameTimestamp + ) case Left(exception) => log.atError.withCause(exception).log("Failed to execute query") diff --git a/oni/src/main/scala/org/mbari/oni/jdbc/sql.scala b/oni/src/main/scala/org/mbari/oni/jdbc/sql.scala index 98ebce5..bdcc5f5 100644 --- a/oni/src/main/scala/org/mbari/oni/jdbc/sql.scala +++ b/oni/src/main/scala/org/mbari/oni/jdbc/sql.scala @@ -28,4 +28,3 @@ extension (obj: Object) def asString: Option[String] = JdbcTypes.stringConverter(obj) def asUrl: Option[URL] = JdbcTypes.urlConverter(obj) def asUUID: Option[UUID] = JdbcTypes.uuidConverter(obj) - diff --git a/oni/src/main/scala/org/mbari/oni/jpa/EntityManagerFactories.scala b/oni/src/main/scala/org/mbari/oni/jpa/EntityManagerFactories.scala index 346da74..a172444 100644 --- a/oni/src/main/scala/org/mbari/oni/jpa/EntityManagerFactories.scala +++ b/oni/src/main/scala/org/mbari/oni/jpa/EntityManagerFactories.scala @@ -37,14 +37,14 @@ object EntityManagerFactories: val PRODUCTION_PROPS = Map( // "hibernate.cache.region.factory_class" -> "jcache", // "hibernate.cache.use_second_level_cache" -> "true", - "hibernate.connection.provider_class" -> "org.hibernate.hikaricp.internal.HikariCPConnectionProvider", + "hibernate.connection.provider_class" -> "org.hibernate.hikaricp.internal.HikariCPConnectionProvider", // "hibernate.javax.cache.missing_cache_strategy" -> "create", // "hibernate.javax.cache.provider" -> "com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider", - "hibernate.hbm2ddl.auto" -> "validate", - "hibernate.hikari.idleTimeout" -> "30000", - "hibernate.hikari.maximumPoolSize" -> s"${AppConfig.NumberOfThreads}", // Same as vertx worker pool threads - "hibernate.hikari.minimumIdle" -> "2", - "hibernate.jdbc.batch_size" -> "100" + "hibernate.hbm2ddl.auto" -> "validate", + "hibernate.hikari.idleTimeout" -> "30000", + "hibernate.hikari.maximumPoolSize" -> s"${AppConfig.NumberOfThreads}", // Same as vertx worker pool threads + "hibernate.hikari.minimumIdle" -> "2", + "hibernate.jdbc.batch_size" -> "100" ) def apply(properties: Map[String, String]): EntityManagerFactory = diff --git a/oni/src/main/scala/org/mbari/oni/services/ConceptCache.scala b/oni/src/main/scala/org/mbari/oni/services/ConceptCache.scala index 5722e40..9a78cc8 100644 --- a/oni/src/main/scala/org/mbari/oni/services/ConceptCache.scala +++ b/oni/src/main/scala/org/mbari/oni/services/ConceptCache.scala @@ -20,7 +20,7 @@ import org.mbari.oni.etc.jdk.Loggers.{*, given} import java.util.concurrent.TimeUnit -class ConceptCache(conceptService: ConceptService, conceptNameService: ConceptNameService) { +class ConceptCache(conceptService: ConceptService, conceptNameService: ConceptNameService): private val log = System.getLogger(getClass.getName) @@ -34,43 +34,33 @@ class ConceptCache(conceptService: ConceptService, conceptNameService: ConceptNa .expireAfterWrite(15, TimeUnit.MINUTES) .build[String, Seq[String]]() - def findByName(name: String): Either[Throwable, ConceptMetadata] = { - Option(nameCache.getIfPresent(name)) match { + def findByName(name: String): Either[Throwable, ConceptMetadata] = + Option(nameCache.getIfPresent(name)) match case Some(node) => Right(node) - case None => + case None => conceptService.findByName(name) match - case Left(e) => + case Left(e) => log.atInfo.withCause(e).log(s"Failed to find concept by name: $name") Left(e) case Right(conceptNode) => nameCache.put(name, conceptNode) Right(conceptNode) - } - } - def findAllNames(limit: Int, offset: Int): Either[Throwable, Seq[String]] = { + def findAllNames(limit: Int, offset: Int): Either[Throwable, Seq[String]] = val allNames = Option(allNamesCache.getIfPresent(ConceptCache.AllNamesCacheKey)) - if (allNames.isDefined && allNames.get.nonEmpty) { - Right(allNames.get.slice(offset, offset + limit)) - } - else { + if allNames.isDefined && allNames.get.nonEmpty then Right(allNames.get.slice(offset, offset + limit)) + else conceptNameService.findAllNames(1000000, 0) match - case Left(e) => + case Left(e) => log.atError.withCause(e).log("Failed to find all concept names") Left(e) case Right(names) => allNamesCache.put(ConceptCache.AllNamesCacheKey, names) Right(names.slice(offset, offset + limit)) - } - } - def clear(): Unit = { + def clear(): Unit = nameCache.invalidateAll() allNamesCache.invalidateAll() - } -} - -object ConceptCache { +object ConceptCache: val AllNamesCacheKey = "all-names" -} diff --git a/oni/src/main/scala/org/mbari/oni/services/ConceptNameService.scala b/oni/src/main/scala/org/mbari/oni/services/ConceptNameService.scala index 224b708..09db006 100644 --- a/oni/src/main/scala/org/mbari/oni/services/ConceptNameService.scala +++ b/oni/src/main/scala/org/mbari/oni/services/ConceptNameService.scala @@ -123,9 +123,9 @@ class ConceptNameService(entityManagerFactory: EntityManagerFactory) extends Con dto.author .foreach(author => - if (author.isBlank) existingConceptName.setAuthor(null) + if author.isBlank then existingConceptName.setAuthor(null) else existingConceptName.setAuthor(author) - ) + ) dto.updateEntity(existingConceptName) diff --git a/oni/src/main/scala/org/mbari/oni/services/ConceptService.scala b/oni/src/main/scala/org/mbari/oni/services/ConceptService.scala index f611941..9b5faec 100644 --- a/oni/src/main/scala/org/mbari/oni/services/ConceptService.scala +++ b/oni/src/main/scala/org/mbari/oni/services/ConceptService.scala @@ -8,9 +8,26 @@ package org.mbari.oni.services import jakarta.persistence.{EntityManager, EntityManagerFactory} -import org.mbari.oni.{AccessDenied, AccessDeniedMissingCredentials, ChildConceptNotFound, ConceptNameAlreadyExists, ConceptNameNotFound, MissingRootConcept, OniException, ParentConceptNotFound, RootAlreadyExists} +import org.mbari.oni.{ + AccessDenied, + AccessDeniedMissingCredentials, + ChildConceptNotFound, + ConceptNameAlreadyExists, + ConceptNameNotFound, + HistoryIsInvalid, + MissingRootConcept, + OniException, + ParentConceptNotFound, + RootAlreadyExists +} import org.mbari.oni.domain.{ConceptCreate, ConceptDelete, ConceptMetadata, ConceptUpdate, RawConcept, SimpleConcept} -import org.mbari.oni.jpa.entities.{ConceptEntity, ConceptNameEntity, HistoryEntity, HistoryEntityFactory, UserAccountEntity} +import org.mbari.oni.jpa.entities.{ + ConceptEntity, + ConceptNameEntity, + HistoryEntity, + HistoryEntityFactory, + UserAccountEntity +} import org.mbari.oni.jpa.EntityManagerFactories.* import org.mbari.oni.jpa.repositories.ConceptRepository import org.mbari.oni.etc.jdk.Loggers.given @@ -86,8 +103,12 @@ class ConceptService(entityManagerFactory: EntityManagerFactory): handleByConceptNameQuery(name, ConceptMetadata.from) def findParentByChildName(name: String): Either[Throwable, ConceptMetadata] = - handleByConceptNameQuery(name, c => if (c.getParentConcept == null) throw ParentConceptNotFound(name) else - ConceptMetadata.from(c.getParentConcept)) + handleByConceptNameQuery( + name, + c => + if c.getParentConcept == null then throw ParentConceptNotFound(name) + else ConceptMetadata.from(c.getParentConcept) + ) def findChildrenByParentName(name: String): Either[Throwable, Set[ConceptMetadata]] = handleByConceptNameQuery(name, c => c.getChildConcepts.asScala.map(ConceptMetadata.from).toSet) @@ -315,10 +336,8 @@ class ConceptService(entityManagerFactory: EntityManagerFactory): rankLevel.foreach(v => if v != conceptEntity.getRankLevel then if v.isBlank then - if userEntity.isAdministrator then - conceptEntity.setRankLevel(null) - else - throw new IllegalArgumentException("Rank level can only be removed by an administrator") + if userEntity.isAdministrator then conceptEntity.setRankLevel(null) + else throw new IllegalArgumentException("Rank level can only be removed by an administrator") else val history = HistoryEntityFactory.replaceRankLevel(userEntity, conceptEntity.getRankLevel, v) conceptEntity.getConceptMetadata.addHistory(history) @@ -335,10 +354,8 @@ class ConceptService(entityManagerFactory: EntityManagerFactory): rankLevel.foreach(v => if v != conceptEntity.getRankName then if v.isBlank then - if userEntity.isAdministrator then - conceptEntity.setRankName(null) - else - throw new IllegalArgumentException("Rank name can only be removed by an administrator") + if userEntity.isAdministrator then conceptEntity.setRankName(null) + else throw new IllegalArgumentException("Rank name can only be removed by an administrator") else val history = HistoryEntityFactory.replaceRankName(userEntity, conceptEntity.getRankName, v) conceptEntity.getConceptMetadata.addHistory(history) @@ -466,15 +483,14 @@ class ConceptService(entityManagerFactory: EntityManagerFactory): userEntity: UserAccountEntity, entityManager: EntityManager ): Either[Throwable, Boolean] = - try { + try val repo = ConceptRepository(entityManager) val concept = historyEntity.getConceptMetadata.getConcept // val parent = concept.getParentConcept - val parent = repo.findByName(historyEntity.getNewValue).toScala match - case None => throw ConceptNameNotFound(historyEntity.getOldValue) - case Some(p) => p + case None => throw ConceptNameNotFound(historyEntity.getOldValue) + case Some(p) => p val oldParentConcept = repo.findByName(historyEntity.getOldValue).toScala match case None => throw ConceptNameNotFound(historyEntity.getOldValue) @@ -496,7 +512,36 @@ class ConceptService(entityManagerFactory: EntityManagerFactory): s"Rejected replace parent. Moving ${concept.getName} from ${parent.getName} back to ${oldParentConcept.getName} " ) Right(true) - } - catch { - case e: Throwable => Left(e) - } + catch case e: Throwable => Left(e) + + def inTxnRejectReplaceRankLevel( + history: HistoryEntity, + user: UserAccountEntity, + entityManger: EntityManager + ): Either[Throwable, Boolean] = + if history.getAction.equals(HistoryEntity.ACTION_REPLACE) && + history.getField.equals(HistoryEntity.FIELD_CONCEPT_RANKLEVEL) + then + val conceptMetadata = history.getConceptMetadata + val concept = conceptMetadata.getConcept + val oldRankLevel = history.getOldValue + if oldRankLevel.isEmpty || oldRankLevel.isBlank then concept.setRankLevel(null) + else concept.setRankLevel(oldRankLevel) + Right(true) + else Left(HistoryIsInvalid("History is not a replace rank level history")) + + def inTxnRejectReplaceRankName( + history: HistoryEntity, + user: UserAccountEntity, + entityManger: EntityManager + ): Either[Throwable, Boolean] = + if history.getAction.equals(HistoryEntity.ACTION_REPLACE) && + history.getField.equals(HistoryEntity.FIELD_CONCEPT_RANKNAME) + then + val conceptMetadata = history.getConceptMetadata + val concept = conceptMetadata.getConcept + val oldRankName = history.getOldValue + if oldRankName.isEmpty || oldRankName.isBlank then concept.setRankName(null) + else concept.setRankName(oldRankName) + Right(true) + else Left(HistoryIsInvalid("History is not a replace rank name history")) diff --git a/oni/src/main/scala/org/mbari/oni/services/HistoryActionService.scala b/oni/src/main/scala/org/mbari/oni/services/HistoryActionService.scala index 36a205a..afca257 100644 --- a/oni/src/main/scala/org/mbari/oni/services/HistoryActionService.scala +++ b/oni/src/main/scala/org/mbari/oni/services/HistoryActionService.scala @@ -103,8 +103,10 @@ class HistoryActionService(entityManagerFactory: EntityManagerFactory, fastPhylo case _ => notOkHistoryAction case HistoryEntity.ACTION_REPLACE => historyEntity.getField match - case HistoryEntity.FIELD_CONCEPT_PARENT => okHistoryAction - case _ => notOkHistoryAction + case HistoryEntity.FIELD_CONCEPT_PARENT => okHistoryAction + case HistoryEntity.FIELD_CONCEPT_RANKLEVEL => okHistoryAction + case HistoryEntity.FIELD_CONCEPT_RANKNAME => okHistoryAction + case _ => notOkHistoryAction private def lookupRejectHistoryAction(historyEntity: HistoryEntity): HistoryAction = historyEntity.getAction match @@ -119,5 +121,9 @@ class HistoryActionService(entityManagerFactory: EntityManagerFactory, fastPhylo case HistoryEntity.ACTION_DELETE => okHistoryAction case HistoryEntity.ACTION_REPLACE => historyEntity.getField match - case HistoryEntity.FIELD_CONCEPT_PARENT => conceptService.inTxnRejectReplaceParent - case _ => notOkHistoryAction + case HistoryEntity.FIELD_CONCEPT_PARENT => conceptService.inTxnRejectReplaceParent + case HistoryEntity.FIELD_CONCEPT_RANKLEVEL => + conceptService.inTxnRejectReplaceRankLevel // TODO revert + case HistoryEntity.FIELD_CONCEPT_RANKNAME => + conceptService.inTxnRejectReplaceRankName // TODO revert + case _ => notOkHistoryAction diff --git a/oni/src/main/scala/org/mbari/oni/services/HistoryService.scala b/oni/src/main/scala/org/mbari/oni/services/HistoryService.scala index ddc3167..76d86db 100644 --- a/oni/src/main/scala/org/mbari/oni/services/HistoryService.scala +++ b/oni/src/main/scala/org/mbari/oni/services/HistoryService.scala @@ -8,10 +8,10 @@ package org.mbari.oni.services import org.mbari.oni.domain.ExtendedHistory -import jakarta.persistence.EntityManagerFactory +import jakarta.persistence.{EntityManager, EntityManagerFactory} import org.mbari.oni.ItemNotFound import org.mbari.oni.jpa.EntityManagerFactories.* -import org.mbari.oni.jpa.entities.HistoryEntity +import org.mbari.oni.jpa.entities.{HistoryEntity, UserAccountEntity} import org.mbari.oni.jpa.repositories.HistoryRepository import java.util.Objects @@ -39,7 +39,9 @@ class HistoryService(entityManagerFactory: EntityManagerFactory): repo.findPendingHistories(limit, offset) .asScala .toSeq - .map(h => ExtendedHistory.from(h.getConceptMetadata.getConcept.getPrimaryConceptName.getName, h)) // TRY because of the potential for nulls + .map(h => + ExtendedHistory.from(h.getConceptMetadata.getConcept.getPrimaryConceptName.getName, h) + ) // TRY because of the potential for nulls .sortBy(_.creationTimestamp) ) @@ -49,7 +51,9 @@ class HistoryService(entityManagerFactory: EntityManagerFactory): repo.findApprovedHistories() .asScala .toSeq - .map(h => ExtendedHistory.from(h.getConceptMetadata.getConcept.getPrimaryConceptName.getName, h)) // TRY because of the potential for nulls + .map(h => + ExtendedHistory.from(h.getConceptMetadata.getConcept.getPrimaryConceptName.getName, h) + ) // TRY because of the potential for nulls .sortBy(_.creationTimestamp) ) diff --git a/oni/src/main/scala/org/mbari/oni/services/LinkTemplateService.scala b/oni/src/main/scala/org/mbari/oni/services/LinkTemplateService.scala index 0095b04..e1f6eed 100644 --- a/oni/src/main/scala/org/mbari/oni/services/LinkTemplateService.scala +++ b/oni/src/main/scala/org/mbari/oni/services/LinkTemplateService.scala @@ -9,10 +9,24 @@ package org.mbari.oni.services import jakarta.persistence.{EntityManager, EntityManagerFactory} import org.mbari.oni.{ConceptNameNotFound, ItemNotFound, LinkRealizationIdNotFound, LinkTemplateIdNotFound} -import org.mbari.oni.domain.{ExtendedLink, ILink, Link, LinkCreate, LinkRenameToConceptRequest, LinkRenameToConceptResponse, LinkUpdate} +import org.mbari.oni.domain.{ + ExtendedLink, + ILink, + Link, + LinkCreate, + LinkRenameToConceptRequest, + LinkRenameToConceptResponse, + LinkUpdate +} import org.mbari.oni.jpa.EntityManagerFactories.* import org.mbari.oni.etc.jdk.Loggers.given -import org.mbari.oni.jpa.entities.{HistoryEntity, HistoryEntityFactory, LinkRealizationEntity, LinkTemplateEntity, UserAccountEntity} +import org.mbari.oni.jpa.entities.{ + HistoryEntity, + HistoryEntityFactory, + LinkRealizationEntity, + LinkTemplateEntity, + UserAccountEntity +} import org.mbari.oni.jpa.repositories.{ConceptRepository, LinkTemplateRepository} import scala.jdk.CollectionConverters.* @@ -144,18 +158,22 @@ class LinkTemplateService(entityManagerFactory: EntityManagerFactory): _ <- txn(user.toEntity) yield () - def renameToConcept(oldConcept: String, newConcept: String, userName: String): Either[Throwable, LinkRenameToConceptResponse] = + def renameToConcept( + oldConcept: String, + newConcept: String, + userName: String + ): Either[Throwable, LinkRenameToConceptResponse] = def txn(userEntity: UserAccountEntity): Either[Throwable, LinkRenameToConceptResponse] = entityManagerFactory.transaction(entityManager => val query = entityManager.createNamedQuery("LinkTemplate.updateToConcept") query.setParameter(1, newConcept) query.setParameter(2, oldConcept) - val n = query.executeUpdate() + val n = query.executeUpdate() LinkRenameToConceptResponse(oldConcept, newConcept, n) ) for - user <- userAccountService.verifyWriteAccess(Option(userName)) + user <- userAccountService.verifyWriteAccess(Option(userName)) response <- txn(user.toEntity) yield response diff --git a/oni/src/main/scala/org/mbari/oni/services/MediaService.scala b/oni/src/main/scala/org/mbari/oni/services/MediaService.scala index 6f2518b..16bb459 100644 --- a/oni/src/main/scala/org/mbari/oni/services/MediaService.scala +++ b/oni/src/main/scala/org/mbari/oni/services/MediaService.scala @@ -95,7 +95,6 @@ class MediaService(entityManagerFactory: EntityManagerFactory, fastPhylogenyServ val conceptMetadata = media.getConceptMetadata conceptMetadata.removeMedia(media) repo.delete(media) - ) for @@ -103,7 +102,7 @@ class MediaService(entityManagerFactory: EntityManagerFactory, fastPhylogenyServ media <- txn(user.toEntity) yield () - def update(id: Long, mediaUpdate: MediaUpdate, userName: String) = + def update(id: Long, mediaUpdate: MediaUpdate, userName: String) = def txn(userEntity: UserAccountEntity): Either[Throwable, Media] = entityManagerFactory.transaction(entityManager => val repo = MediaRepository(entityManager, fastPhylogenyService) @@ -118,7 +117,7 @@ class MediaService(entityManagerFactory: EntityManagerFactory, fastPhylogenyServ Media.from(media) ) - for + for user <- userAccountService.verifyWriteAccess(Option(userName)) media <- txn(user.toEntity) yield media diff --git a/oni/src/main/scala/org/mbari/oni/services/PrefNodeService.scala b/oni/src/main/scala/org/mbari/oni/services/PrefNodeService.scala index efeca10..5d4e77f 100644 --- a/oni/src/main/scala/org/mbari/oni/services/PrefNodeService.scala +++ b/oni/src/main/scala/org/mbari/oni/services/PrefNodeService.scala @@ -78,6 +78,8 @@ class PrefNodeService(entityManagerFactory: EntityManagerFactory): val repo = new PrefNodeRepository(entityManager) repo.findAll(limit, offset) ) - attempt.map(_.asScala + attempt.map( + _.asScala .map(PrefNode.from) - .toSeq) + .toSeq + ) diff --git a/oni/src/main/scala/org/mbari/oni/services/RankValidator.scala b/oni/src/main/scala/org/mbari/oni/services/RankValidator.scala index a55cb4b..cf293c4 100644 --- a/oni/src/main/scala/org/mbari/oni/services/RankValidator.scala +++ b/oni/src/main/scala/org/mbari/oni/services/RankValidator.scala @@ -51,42 +51,33 @@ object RankValidator: /** * A list of commonlhy accepted, valid ranks scraped from Wikipedia */ - val ValidRanks: Seq[String] = ValidRankLevelsAndNames.map { - (rankLevel, rankName) => s"${rankLevel.getOrElse("")}${rankName.getOrElse("")}".toLowerCase + val ValidRanks: Seq[String] = ValidRankLevelsAndNames.map { (rankLevel, rankName) => + s"${rankLevel.getOrElse("")}${rankName.getOrElse("")}".toLowerCase } - def validate(rank: String): Boolean = { + def validate(rank: String): Boolean = ValidRanks.contains(rank) - } - def validate(rankLevel: Option[String] = None, rankName: Option[String] = None): Boolean = { + def validate(rankLevel: Option[String] = None, rankName: Option[String] = None): Boolean = val rank = s"${rankLevel.getOrElse("")}${rankName.getOrElse("")}".toLowerCase validate(rank) - } - def validate(conceptCreate: ConceptCreate): Boolean = { + def validate(conceptCreate: ConceptCreate): Boolean = validate(conceptCreate.rankLevel, conceptCreate.rankName) - } - def validate(conceptUpdate: ConceptUpdate): Boolean = { + def validate(conceptUpdate: ConceptUpdate): Boolean = validate(conceptUpdate.rankLevel, conceptUpdate.rankName) - } - def throwExceptionIfInvalid(conceptCreate: ConceptCreate): Unit = { - if (!validate(conceptCreate)) { + def throwExceptionIfInvalid(conceptCreate: ConceptCreate): Unit = + if !validate(conceptCreate) then val rank = s"${conceptCreate.rankLevel.getOrElse("")}${conceptCreate.rankName.getOrElse("")}" throw new IllegalArgumentException( s"Invalid rank level + rank name ($rank). Should be one of ${RankValidator.ValidRanks.mkString(", ")}" ) - } - } - def throwExceptionIfInvalid(conceptUpdate: ConceptUpdate): Unit = { - if (!validate(conceptUpdate.rankLevel, conceptUpdate.rankName)) { + def throwExceptionIfInvalid(conceptUpdate: ConceptUpdate): Unit = + if !validate(conceptUpdate.rankLevel, conceptUpdate.rankName) then val rank = s"${conceptUpdate.rankLevel.getOrElse("")}${conceptUpdate.rankName.getOrElse("")}" throw new IllegalArgumentException( s"Invalid rank level + rank name ($rank). Should be one of ${RankValidator.ValidRanks.mkString(", ")}" ) - } - - } diff --git a/oni/src/main/scala/org/mbari/oni/services/UserAccountService.scala b/oni/src/main/scala/org/mbari/oni/services/UserAccountService.scala index 6ab5299..53bad78 100644 --- a/oni/src/main/scala/org/mbari/oni/services/UserAccountService.scala +++ b/oni/src/main/scala/org/mbari/oni/services/UserAccountService.scala @@ -27,9 +27,11 @@ class UserAccountService(entityManagerFactory: EntityManagerFactory): val repo = UserAccountRepository(entityManager) repo.findAll() ) - attempt.map(_.asScala - .toSeq - .map(UserAccount.from)) + attempt.map( + _.asScala + .toSeq + .map(UserAccount.from) + ) def findByUserName(name: String): Either[Throwable, Option[UserAccount]] = val attempt = entityManagerFactory.transaction(entityManager => @@ -43,9 +45,11 @@ class UserAccountService(entityManagerFactory: EntityManagerFactory): val repo = UserAccountRepository(entityManager) repo.findAllByRole(role) ) - attempt.map(_.asScala - .toSeq - .map(UserAccount.from)) + attempt.map( + _.asScala + .toSeq + .map(UserAccount.from) + ) def deleteByUserName(name: String): Either[Throwable, Unit] = entityManagerFactory.transaction(entityManager => diff --git a/project/Dependencies.scala b/project/Dependencies.scala index d3be3a3..94fc8a5 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,7 +1,7 @@ import sbt.* object Dependencies { - lazy val auth0 = "com.auth0" % "java-jwt" % "4.4.0" + lazy val auth0 = "com.auth0" % "java-jwt" % "4.5.0" val caffeineVersion = "3.2.0" lazy val caffeine = "com.github.ben-manes.caffeine" % "caffeine" % caffeineVersion @@ -13,7 +13,7 @@ object Dependencies { lazy val circeParser = "io.circe" %% "circe-parser" % circeVersion lazy val commonsCodec = "commons-codec" % "commons-codec" % "1.18.0" - lazy val gson = "com.google.code.gson" % "gson" % "2.11.0" + lazy val gson = "com.google.code.gson" % "gson" % "2.12.1" // THis needs to match the version used by tapirHelidon. // Just including these in the build allows Helidon to use them for content encoding. @@ -21,7 +21,7 @@ object Dependencies { lazy val helidonEncodingDeflate = "io.helidon.http.encoding" % "helidon-http-encoding-deflate" % helidonVersion lazy val helidonEncodingGzip = "io.helidon.http.encoding" % "helidon-http-encoding-gzip" % helidonVersion - val hibernateVersion = "6.6.5.Final" + val hibernateVersion = "6.6.6.Final" lazy val hibernateCore = "org.hibernate.orm" % "hibernate-core" % hibernateVersion lazy val hibernateJCache = "org.hibernate" % "hibernate-jcache" % hibernateVersion lazy val hibernateEnvers = "org.hibernate.orm" % "hibernate-envers" % hibernateVersion @@ -42,7 +42,7 @@ object Dependencies { lazy val slf4jJulBridge = "org.slf4j" % "jul-to-slf4j" % slf4jVersion lazy val slf4jSystem = "org.slf4j" % "slf4j-jdk-platform-logging" % slf4jVersion - private val tapirVersion = "1.11.13" + private val tapirVersion = "1.11.14" lazy val tapirCirce = "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion lazy val tapirHelidon = "com.softwaremill.sttp.tapir" %% "tapir-nima-server" % tapirVersion lazy val tapirPrometheus = "com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % tapirVersion @@ -50,7 +50,7 @@ object Dependencies { lazy val tapirSwagger = "com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % tapirVersion lazy val tapirVertex = "com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % tapirVersion - lazy val tapirSttpCirce = "com.softwaremill.sttp.client3" %% "circe" % "3.10.2" + lazy val tapirSttpCirce = "com.softwaremill.sttp.client3" %% "circe" % "3.10.3" val testcontainersVersion = "1.20.4" lazy val testcontainersCore = "org.testcontainers" % "testcontainers" % testcontainersVersion diff --git a/project/plugins.sbt b/project/plugins.sbt index 42a75e7..f7296e2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.3") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") addSbtPlugin("com.github.sbt" % "sbt-git" % "2.1.0") -addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.0") +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.1") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.4") \ No newline at end of file