From 038d3a1151c4dd15174b78fe4ead969e5227f6c0 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Thu, 6 Jun 2024 10:58:38 +0200 Subject: [PATCH 1/5] Refactor model classes to share common namespace There is no need to have different types for `Description` or `CreationDate`. All model classes are in the `model` namespace now, which makes it easier to work with. --- .../io/renku/search/model/CreationDate.scala | 35 +++++++ .../model/{groups.scala => Description.scala} | 25 +++-- .../scala/io/renku/search/model/Email.scala | 29 ++++++ .../io/renku/search/model/Firstname.scala | 29 ++++++ .../io/renku/search/model/LastName.scala | 29 ++++++ .../io/renku/search/model/Repository.scala | 32 +++++++ .../scala/io/renku/search/model/Slug.scala | 32 +++++++ .../io/renku/search/model/Username.scala | 29 ++++++ .../io/renku/search/model/Visibility.scala | 41 +++++++++ .../io/renku/search/model/projects.scala | 81 ---------------- .../scala/io/renku/search/model/users.scala | 52 ----------- .../renku/search/model/ModelGenerators.scala | 32 +++---- .../io/renku/search/events/GroupAdded.scala | 3 +- .../io/renku/search/events/GroupUpdated.scala | 2 +- .../renku/search/events/ProjectCreated.scala | 9 +- .../renku/search/events/ProjectUpdated.scala | 9 +- .../io/renku/search/events/UserAdded.scala | 8 +- .../io/renku/search/events/UserUpdated.scala | 8 +- .../scala/io/renku/search/events/syntax.scala | 4 - .../io/renku/events/EventsGenerators.scala | 5 +- .../io/renku/search/http/ClientBuilder.scala | 3 + .../io/renku/search/http/HttpServer.scala | 1 + .../renku/search/http/HttpServerConfig.scala | 3 +- .../io/renku/search/api/Microservice.scala | 8 +- .../renku/search/api/data/SearchEntity.scala | 52 +++++------ .../io/renku/search/api/tapir/ApiSchema.scala | 92 +++++++++++++++++++ .../io/renku/search/api/SearchApiSpec.scala | 4 +- .../io/renku/search/cli/CommonOpts.scala | 33 ++++--- .../cli/perftests/GitLabDocsCreator.scala | 11 +-- .../search/cli/perftests/GitLabEntities.scala | 4 +- .../cli/perftests/ModelTypesGenerators.scala | 11 +-- .../perftests/RandommerIoDocsCreator.scala | 27 +++--- .../renku/search/cli/projects/CreateCmd.scala | 8 +- .../renku/search/cli/projects/UpdateCmd.scala | 8 +- .../io/renku/search/cli/users/AddCmd.scala | 1 - .../io/renku/search/cli/users/UpdateCmd.scala | 1 - .../search/provision/events/Groups.scala | 16 ++-- .../group/GroupRemovedProcessSpec.scala | 3 +- .../AuthorizationAddedProvisioningSpec.scala | 1 - .../user/UserAddedProvisioningSpec.scala | 4 +- modules/search-query-docs/docs/manual.md | 2 +- .../io/renku/search/query/FieldTerm.scala | 4 +- .../scala/io/renku/search/query/Query.scala | 3 +- .../search/query/parse/QueryParser.scala | 1 - .../renku/search/query/QueryGenerators.scala | 5 +- .../solr/documents/EntityDocument.scala | 23 +++-- .../documents/PartialEntityDocument.scala | 3 +- .../renku/search/solr/query/SolrToken.scala | 1 - .../solr/client/SearchSolrClientSpec.scala | 9 +- .../solr/client/SolrDocumentGenerators.scala | 12 +-- .../search/solr/query/AuthTestData.scala | 3 +- 51 files changed, 520 insertions(+), 331 deletions(-) create mode 100644 modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala rename modules/commons/src/main/scala/io/renku/search/model/{groups.scala => Description.scala} (65%) create mode 100644 modules/commons/src/main/scala/io/renku/search/model/Email.scala create mode 100644 modules/commons/src/main/scala/io/renku/search/model/Firstname.scala create mode 100644 modules/commons/src/main/scala/io/renku/search/model/LastName.scala create mode 100644 modules/commons/src/main/scala/io/renku/search/model/Repository.scala create mode 100644 modules/commons/src/main/scala/io/renku/search/model/Slug.scala create mode 100644 modules/commons/src/main/scala/io/renku/search/model/Username.scala create mode 100644 modules/commons/src/main/scala/io/renku/search/model/Visibility.scala delete mode 100644 modules/commons/src/main/scala/io/renku/search/model/projects.scala delete mode 100644 modules/commons/src/main/scala/io/renku/search/model/users.scala create mode 100644 modules/search-api/src/main/scala/io/renku/search/api/tapir/ApiSchema.scala diff --git a/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala b/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala new file mode 100644 index 00000000..4c306505 --- /dev/null +++ b/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.model + +import java.time.Instant + +import cats.kernel.Order + +import io.bullet.borer.Codec +import io.github.arainko.ducktape.* +import io.renku.search.borer.codecs.all.given + +opaque type CreationDate = Instant +object CreationDate: + def apply(v: Instant): CreationDate = v + extension (self: CreationDate) def value: Instant = self + given Transformer[Instant, CreationDate] = apply + given Codec[CreationDate] = Codec.of[Instant] + given Order[CreationDate] = Order.fromComparable[Instant] diff --git a/modules/commons/src/main/scala/io/renku/search/model/groups.scala b/modules/commons/src/main/scala/io/renku/search/model/Description.scala similarity index 65% rename from modules/commons/src/main/scala/io/renku/search/model/groups.scala rename to modules/commons/src/main/scala/io/renku/search/model/Description.scala index c84bb9a1..66a186a0 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/groups.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Description.scala @@ -21,17 +21,16 @@ package io.renku.search.model import io.bullet.borer.Codec import io.github.arainko.ducktape.Transformer -object groups: - opaque type Description = String - object Description: - def apply(v: String): Description = v - def from(v: Option[String]): Option[Description] = - v.flatMap { - _.trim match { - case "" => Option.empty[Description] - case o => Option(o) - } +opaque type Description = String +object Description: + def apply(v: String): Description = v + def from(v: Option[String]): Option[Description] = + v.flatMap { + _.trim match { + case "" => Option.empty[Description] + case o => Option(o) } - extension (self: Description) def value: String = self - given Transformer[String, Description] = apply - given Codec[Description] = Codec.of[String] + } + extension (self: Description) def value: String = self + given Transformer[String, Description] = apply + given Codec[Description] = Codec.of[String] diff --git a/modules/commons/src/main/scala/io/renku/search/model/Email.scala b/modules/commons/src/main/scala/io/renku/search/model/Email.scala new file mode 100644 index 00000000..6a8ca0a5 --- /dev/null +++ b/modules/commons/src/main/scala/io/renku/search/model/Email.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.model + +import io.bullet.borer.Codec +import io.github.arainko.ducktape.Transformer + +opaque type Email = String +object Email: + def apply(v: String): Email = v + extension (self: Email) def value: String = self + given Transformer[String, Email] = apply + given Codec[Email] = Codec.bimap[String, Email](_.value, Email.apply) diff --git a/modules/commons/src/main/scala/io/renku/search/model/Firstname.scala b/modules/commons/src/main/scala/io/renku/search/model/Firstname.scala new file mode 100644 index 00000000..b1b86e59 --- /dev/null +++ b/modules/commons/src/main/scala/io/renku/search/model/Firstname.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.model + +import io.bullet.borer.Codec +import io.github.arainko.ducktape.Transformer + +opaque type FirstName = String +object FirstName: + def apply(v: String): FirstName = v + extension (self: FirstName) def value: String = self + given Transformer[String, FirstName] = apply + given Codec[FirstName] = Codec.bimap[String, FirstName](_.value, FirstName.apply) diff --git a/modules/commons/src/main/scala/io/renku/search/model/LastName.scala b/modules/commons/src/main/scala/io/renku/search/model/LastName.scala new file mode 100644 index 00000000..18ba0dd0 --- /dev/null +++ b/modules/commons/src/main/scala/io/renku/search/model/LastName.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.model + +import io.bullet.borer.Codec +import io.github.arainko.ducktape.Transformer + +opaque type LastName = String +object LastName: + def apply(v: String): LastName = v + extension (self: LastName) def value: String = self + given Transformer[String, LastName] = apply + given Codec[LastName] = Codec.bimap[String, LastName](_.value, LastName.apply) diff --git a/modules/commons/src/main/scala/io/renku/search/model/Repository.scala b/modules/commons/src/main/scala/io/renku/search/model/Repository.scala new file mode 100644 index 00000000..eccd622e --- /dev/null +++ b/modules/commons/src/main/scala/io/renku/search/model/Repository.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.model + +import cats.kernel.Order + +import io.bullet.borer.Codec +import io.github.arainko.ducktape.* + +opaque type Repository = String +object Repository: + def apply(v: String): Repository = v + extension (self: Repository) def value: String = self + given Transformer[String, Repository] = apply + given Codec[Repository] = Codec.of[String] + given Order[Repository] = Order.fromComparable[String] diff --git a/modules/commons/src/main/scala/io/renku/search/model/Slug.scala b/modules/commons/src/main/scala/io/renku/search/model/Slug.scala new file mode 100644 index 00000000..cce497fd --- /dev/null +++ b/modules/commons/src/main/scala/io/renku/search/model/Slug.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.model + +import cats.kernel.Order + +import io.bullet.borer.Codec +import io.github.arainko.ducktape.* + +opaque type Slug = String +object Slug: + def apply(v: String): Slug = v + extension (self: Slug) def value: String = self + given Transformer[String, Slug] = apply + given Codec[Slug] = Codec.of[String] + given Order[Slug] = Order.fromComparable[String] diff --git a/modules/commons/src/main/scala/io/renku/search/model/Username.scala b/modules/commons/src/main/scala/io/renku/search/model/Username.scala new file mode 100644 index 00000000..8a21ae45 --- /dev/null +++ b/modules/commons/src/main/scala/io/renku/search/model/Username.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.model + +import io.bullet.borer.Codec +import io.github.arainko.ducktape.Transformer + +opaque type Username = String +object Username: + def apply(v: String): Username = v + extension (self: Username) def value: String = self + given Transformer[String, Username] = apply + given Codec[Username] = Codec.bimap[String, Username](_.value, Username.apply) diff --git a/modules/commons/src/main/scala/io/renku/search/model/Visibility.scala b/modules/commons/src/main/scala/io/renku/search/model/Visibility.scala new file mode 100644 index 00000000..c1d6651e --- /dev/null +++ b/modules/commons/src/main/scala/io/renku/search/model/Visibility.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.model + +import cats.kernel.Order + +import io.bullet.borer.derivation.MapBasedCodecs.* +import io.bullet.borer.{Decoder, Encoder} + +enum Visibility: + lazy val name: String = productPrefix.toLowerCase + case Public, Private + +object Visibility: + given Order[Visibility] = Order.by(_.ordinal) + given Encoder[Visibility] = Encoder.forString.contramap(_.name) + given Decoder[Visibility] = Decoder.forString.mapEither(Visibility.fromString) + + def fromString(v: String): Either[String, Visibility] = + Visibility.values + .find(_.name.equalsIgnoreCase(v)) + .toRight(s"Invalid visibility: $v") + + def unsafeFromString(v: String): Visibility = + fromString(v).fold(sys.error, identity) diff --git a/modules/commons/src/main/scala/io/renku/search/model/projects.scala b/modules/commons/src/main/scala/io/renku/search/model/projects.scala deleted file mode 100644 index b7a4370d..00000000 --- a/modules/commons/src/main/scala/io/renku/search/model/projects.scala +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2024 Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 io.renku.search.model - -import java.time.Instant - -import cats.kernel.Order - -import io.bullet.borer.derivation.MapBasedCodecs.* -import io.bullet.borer.{Codec, Decoder, Encoder} -import io.github.arainko.ducktape.* -import io.renku.search.borer.codecs.all.given - -object projects: - opaque type Slug = String - object Slug: - def apply(v: String): Slug = v - extension (self: Slug) def value: String = self - given Transformer[String, Slug] = apply - given Codec[Slug] = Codec.of[String] - - opaque type Repository = String - object Repository: - def apply(v: String): Repository = v - extension (self: Repository) def value: String = self - given Transformer[String, Repository] = apply - given Codec[Repository] = Codec.of[String] - - opaque type Description = String - object Description: - def apply(v: String): Description = v - def from(v: Option[String]): Option[Description] = - v.flatMap { - _.trim match { - case "" => Option.empty[Description] - case o => Option(o) - } - } - extension (self: Description) def value: String = self - given Transformer[String, Description] = apply - given Codec[Description] = Codec.of[String] - - opaque type CreationDate = Instant - object CreationDate: - def apply(v: Instant): CreationDate = v - extension (self: CreationDate) def value: Instant = self - given Transformer[Instant, CreationDate] = apply - given Codec[CreationDate] = Codec.of[Instant] - - enum Visibility: - lazy val name: String = productPrefix.toLowerCase - case Public, Private - - object Visibility: - given Order[Visibility] = Order.by(_.ordinal) - given Encoder[Visibility] = Encoder.forString.contramap(_.name) - given Decoder[Visibility] = Decoder.forString.mapEither(Visibility.fromString) - - def fromString(v: String): Either[String, Visibility] = - Visibility.values - .find(_.name.equalsIgnoreCase(v)) - .toRight(s"Invalid visibility: $v") - - def unsafeFromString(v: String): Visibility = - fromString(v).fold(sys.error, identity) diff --git a/modules/commons/src/main/scala/io/renku/search/model/users.scala b/modules/commons/src/main/scala/io/renku/search/model/users.scala deleted file mode 100644 index 788fb685..00000000 --- a/modules/commons/src/main/scala/io/renku/search/model/users.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2024 Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 io.renku.search.model - -import io.bullet.borer.Codec -import io.github.arainko.ducktape.Transformer - -object users: - - opaque type FirstName = String - object FirstName: - def apply(v: String): FirstName = v - extension (self: FirstName) def value: String = self - given Transformer[String, FirstName] = apply - given Codec[FirstName] = Codec.bimap[String, FirstName](_.value, FirstName.apply) - - opaque type LastName = String - object LastName: - def apply(v: String): LastName = v - extension (self: LastName) def value: String = self - given Transformer[String, LastName] = apply - given Codec[LastName] = Codec.bimap[String, LastName](_.value, LastName.apply) - - opaque type Email = String - object Email: - def apply(v: String): Email = v - extension (self: Email) def value: String = self - given Transformer[String, Email] = apply - given Codec[Email] = Codec.bimap[String, Email](_.value, Email.apply) - - opaque type Username = String - object Username: - def apply(v: String): Username = v - extension (self: Username) def value: String = self - given Transformer[String, Username] = apply - given Codec[Username] = Codec.bimap[String, Username](_.value, Username.apply) diff --git a/modules/commons/src/test/scala/io/renku/search/model/ModelGenerators.scala b/modules/commons/src/test/scala/io/renku/search/model/ModelGenerators.scala index 87ebf4e3..8f2347a4 100644 --- a/modules/commons/src/test/scala/io/renku/search/model/ModelGenerators.scala +++ b/modules/commons/src/test/scala/io/renku/search/model/ModelGenerators.scala @@ -30,8 +30,8 @@ object ModelGenerators: val idGen: Gen[Id] = Gen.uuid.map(uuid => Id(uuid.toString)) val nameGen: Gen[Name] = alphaStringGen(max = 10).map(Name.apply) - val projectDescGen: Gen[projects.Description] = - alphaStringGen(max = 30).map(projects.Description.apply) + val projectDescGen: Gen[Description] = + alphaStringGen(max = 30).map(Description.apply) val timestampGen: Gen[Timestamp] = Gen @@ -47,10 +47,10 @@ object ModelGenerators: val memberRoleGen: Gen[MemberRole] = Gen.oneOf(MemberRole.valuesV2) - val projectVisibilityGen: Gen[projects.Visibility] = - Gen.oneOf(projects.Visibility.values.toList) - val projectCreationDateGen: Gen[projects.CreationDate] = - instantGen().map(projects.CreationDate.apply) + val visibilityGen: Gen[Visibility] = + Gen.oneOf(Visibility.values.toList) + val creationDateGen: Gen[CreationDate] = + instantGen().map(CreationDate.apply) val keywordGen: Gen[Keyword] = Gen @@ -73,25 +73,25 @@ object ModelGenerators: .chooseNum(min.toEpochMilli, max.toEpochMilli) .map(Instant.ofEpochMilli(_).truncatedTo(ChronoUnit.MILLIS)) - val userFirstNameGen: Gen[users.FirstName] = Gen + val userFirstNameGen: Gen[FirstName] = Gen .oneOf("Eike", "Kuba", "Ralf", "Lorenzo", "Jean-Pierre", "Alfonso") - .map(users.FirstName.apply) - val userLastNameGen: Gen[users.LastName] = Gen + .map(FirstName.apply) + val userLastNameGen: Gen[LastName] = Gen .oneOf("Kowalski", "Doe", "Tourist", "Milkman", "Da Silva", "Bar") - .map(users.LastName.apply) - def userEmailGen(first: users.FirstName, last: users.LastName): Gen[users.Email] = Gen + .map(LastName.apply) + def userEmailGen(first: FirstName, last: LastName): Gen[Email] = Gen .oneOf("mail.com", "hotmail.com", "epfl.ch", "ethz.ch") - .map(v => users.Email(s"$first.$last@$v")) - val userEmailGen: Gen[users.Email] = + .map(v => Email(s"$first.$last@$v")) + val userEmailGen: Gen[Email] = ( Gen.oneOf("mail.com", "hotmail.com", "epfl.ch", "ethz.ch"), userFirstNameGen, userLastNameGen - ).mapN((f, l, p) => users.Email(s"$f.$l@$p")) + ).mapN((f, l, p) => Email(s"$f.$l@$p")) val groupNameGen: Gen[Name] = Gen.oneOf( List("sdsc", "renku", "datascience", "rocket-science").map(Name.apply) ) - val groupDescGen: Gen[groups.Description] = - alphaStringGen(max = 5).map(groups.Description.apply) + val groupDescGen: Gen[Description] = + alphaStringGen(max = 5).map(Description.apply) diff --git a/modules/events/src/main/scala/io/renku/search/events/GroupAdded.scala b/modules/events/src/main/scala/io/renku/search/events/GroupAdded.scala index 10ed9b4f..2b4b788d 100644 --- a/modules/events/src/main/scala/io/renku/search/events/GroupAdded.scala +++ b/modules/events/src/main/scala/io/renku/search/events/GroupAdded.scala @@ -25,7 +25,6 @@ import io.renku.avro.codec.AvroEncoder import io.renku.avro.codec.all.given import io.renku.events.v2 import io.renku.search.model.* -import io.renku.search.model.Id import org.apache.avro.Schema sealed trait GroupAdded extends RenkuEventPayload: @@ -41,7 +40,7 @@ object GroupAdded: id: Id, name: Name, namespace: Namespace, - description: Option[groups.Description] + description: Option[Description] ): GroupAdded = GroupAdded.V2( v2.GroupAdded(id.value, name.value, description.map(_.value), namespace.value) diff --git a/modules/events/src/main/scala/io/renku/search/events/GroupUpdated.scala b/modules/events/src/main/scala/io/renku/search/events/GroupUpdated.scala index 4c9e2463..c6680d21 100644 --- a/modules/events/src/main/scala/io/renku/search/events/GroupUpdated.scala +++ b/modules/events/src/main/scala/io/renku/search/events/GroupUpdated.scala @@ -41,7 +41,7 @@ object GroupUpdated: id: Id, name: Name, namespace: Namespace, - description: Option[groups.Description] + description: Option[Description] ): GroupUpdated = GroupUpdated.V2( v2.GroupUpdated(id.value, name.value, description.map(_.value), namespace.value) diff --git a/modules/events/src/main/scala/io/renku/search/events/ProjectCreated.scala b/modules/events/src/main/scala/io/renku/search/events/ProjectCreated.scala index 7166200d..5bcb02c6 100644 --- a/modules/events/src/main/scala/io/renku/search/events/ProjectCreated.scala +++ b/modules/events/src/main/scala/io/renku/search/events/ProjectCreated.scala @@ -26,7 +26,6 @@ import io.renku.avro.codec.AvroEncoder import io.renku.avro.codec.all.given import io.renku.events.{v1, v2} import io.renku.search.model.* -import io.renku.search.model.projects.Visibility import org.apache.avro.Schema sealed trait ProjectCreated extends RenkuEventPayload: @@ -44,12 +43,12 @@ object ProjectCreated: id: Id, name: Name, namespace: Namespace, - slug: projects.Slug, - visibility: projects.Visibility, + slug: Slug, + visibility: Visibility, createdBy: Id, creationDate: Timestamp, - repositories: Seq[projects.Repository] = Seq(), - description: Option[projects.Description] = None, + repositories: Seq[Repository] = Seq(), + description: Option[Description] = None, keywords: Seq[Keyword] = Seq() ): ProjectCreated = V2( v2.ProjectCreated( diff --git a/modules/events/src/main/scala/io/renku/search/events/ProjectUpdated.scala b/modules/events/src/main/scala/io/renku/search/events/ProjectUpdated.scala index baaa8e58..029cc773 100644 --- a/modules/events/src/main/scala/io/renku/search/events/ProjectUpdated.scala +++ b/modules/events/src/main/scala/io/renku/search/events/ProjectUpdated.scala @@ -25,7 +25,6 @@ import io.renku.avro.codec.AvroEncoder import io.renku.avro.codec.all.given import io.renku.events.{v1, v2} import io.renku.search.model.* -import io.renku.search.model.projects.Visibility import org.apache.avro.Schema sealed trait ProjectUpdated extends RenkuEventPayload: @@ -43,10 +42,10 @@ object ProjectUpdated: id: Id, name: Name, namespace: Namespace, - slug: projects.Slug, - visibility: projects.Visibility, - repositories: Seq[projects.Repository] = Seq(), - description: Option[projects.Description] = None, + slug: Slug, + visibility: Visibility, + repositories: Seq[Repository] = Seq(), + description: Option[Description] = None, keywords: Seq[Keyword] = Seq() ): ProjectUpdated = V2( v2.ProjectUpdated( diff --git a/modules/events/src/main/scala/io/renku/search/events/UserAdded.scala b/modules/events/src/main/scala/io/renku/search/events/UserAdded.scala index 9355ebb8..dc5d18b1 100644 --- a/modules/events/src/main/scala/io/renku/search/events/UserAdded.scala +++ b/modules/events/src/main/scala/io/renku/search/events/UserAdded.scala @@ -24,7 +24,7 @@ import cats.data.NonEmptyList import io.renku.avro.codec.AvroEncoder import io.renku.avro.codec.all.given import io.renku.events.{v1, v2} -import io.renku.search.model.{Id, Namespace, users} +import io.renku.search.model.* import org.apache.avro.Schema sealed trait UserAdded extends RenkuEventPayload: @@ -39,9 +39,9 @@ object UserAdded: def apply( id: Id, namespace: Namespace, - firstName: Option[users.FirstName], - lastName: Option[users.LastName], - email: Option[users.Email] + firstName: Option[FirstName], + lastName: Option[LastName], + email: Option[Email] ): UserAdded = V2( v2.UserAdded( diff --git a/modules/events/src/main/scala/io/renku/search/events/UserUpdated.scala b/modules/events/src/main/scala/io/renku/search/events/UserUpdated.scala index 8a0c746f..e0da9c0e 100644 --- a/modules/events/src/main/scala/io/renku/search/events/UserUpdated.scala +++ b/modules/events/src/main/scala/io/renku/search/events/UserUpdated.scala @@ -24,7 +24,7 @@ import cats.data.NonEmptyList import io.renku.avro.codec.AvroEncoder import io.renku.avro.codec.all.given import io.renku.events.{v1, v2} -import io.renku.search.model.{Id, Namespace, users} +import io.renku.search.model.* import org.apache.avro.Schema sealed trait UserUpdated extends RenkuEventPayload: @@ -39,9 +39,9 @@ object UserUpdated: def apply( id: Id, namespace: Namespace, - firstName: Option[users.FirstName], - lastName: Option[users.LastName], - email: Option[users.Email] + firstName: Option[FirstName], + lastName: Option[LastName], + email: Option[Email] ): UserUpdated = V2( v2.UserUpdated( diff --git a/modules/events/src/main/scala/io/renku/search/events/syntax.scala b/modules/events/src/main/scala/io/renku/search/events/syntax.scala index 1d9ea80a..ae045635 100644 --- a/modules/events/src/main/scala/io/renku/search/events/syntax.scala +++ b/modules/events/src/main/scala/io/renku/search/events/syntax.scala @@ -22,8 +22,6 @@ import java.time.Instant import io.renku.events.{v1, v2} import io.renku.search.model.* -import io.renku.search.model.projects.* -import io.renku.search.model.users.{FirstName, LastName} trait syntax: extension (self: v1.Visibility) @@ -52,8 +50,6 @@ trait syntax: def toFirstName: FirstName = FirstName(self) def toLastName: LastName = LastName(self) def toKeyword: Keyword = Keyword(self) - def toGroupName: Name = toName - def toGroupDescription: groups.Description = groups.Description(self) extension (self: Instant) def toCreationDate: CreationDate = CreationDate(self) diff --git a/modules/events/src/test/scala/io/renku/events/EventsGenerators.scala b/modules/events/src/test/scala/io/renku/events/EventsGenerators.scala index 74b7c0e3..6d8f974e 100644 --- a/modules/events/src/test/scala/io/renku/events/EventsGenerators.scala +++ b/modules/events/src/test/scala/io/renku/events/EventsGenerators.scala @@ -24,10 +24,7 @@ import java.time.temporal.ChronoUnit import io.renku.events.v1.ProjectAuthorizationAdded import io.renku.search.GeneratorSyntax.* import io.renku.search.events.* -import io.renku.search.model.Id -import io.renku.search.model.MemberRole -import io.renku.search.model.ModelGenerators -import io.renku.search.model.users.FirstName +import io.renku.search.model.* import org.apache.avro.Schema import org.scalacheck.Gen import org.scalacheck.Gen.{alphaChar, alphaNumChar} diff --git a/modules/http-client/src/main/scala/io/renku/search/http/ClientBuilder.scala b/modules/http-client/src/main/scala/io/renku/search/http/ClientBuilder.scala index 355108c6..3f9200bd 100644 --- a/modules/http-client/src/main/scala/io/renku/search/http/ClientBuilder.scala +++ b/modules/http-client/src/main/scala/io/renku/search/http/ClientBuilder.scala @@ -61,6 +61,9 @@ final class ClientBuilder[F[_]: Async]( new ClientBuilder[F](delegate.withLogger(LoggerProxy(logger)), mw :: middlewares) } + def withIdleConnectionTime(t: Duration) = + forward(_.withIdleConnectionTime(t)) + private def forward( f: EmberClientBuilder[F] => EmberClientBuilder[F] ): ClientBuilder[F] = diff --git a/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServer.scala b/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServer.scala index 19c26ed1..5effb7e3 100644 --- a/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServer.scala +++ b/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServer.scala @@ -34,5 +34,6 @@ object HttpServer: .default[F] .withHost(config.bindAddress) .withPort(config.port) + .withIdleTimeout(scala.concurrent.duration.Duration.Zero) .withHttpApp(routes.orNotFound) .build diff --git a/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServerConfig.scala b/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServerConfig.scala index e7637624..773ea966 100644 --- a/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServerConfig.scala +++ b/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServerConfig.scala @@ -20,4 +20,5 @@ package io.renku.search.http import com.comcast.ip4s.{Ipv4Address, Port} -final case class HttpServerConfig(bindAddress: Ipv4Address, port: Port) +final case class HttpServerConfig(bindAddress: Ipv4Address, port: Port): + override def toString = s"Http server @ ${bindAddress}:$port" diff --git a/modules/search-api/src/main/scala/io/renku/search/api/Microservice.scala b/modules/search-api/src/main/scala/io/renku/search/api/Microservice.scala index 4c9a099e..5612b745 100644 --- a/modules/search-api/src/main/scala/io/renku/search/api/Microservice.scala +++ b/modules/search-api/src/main/scala/io/renku/search/api/Microservice.scala @@ -24,7 +24,7 @@ import io.renku.logging.LoggingSetup import io.renku.search.http.HttpServer object Microservice extends IOApp: - + private val logger = scribe.cats.io private val loadConfig = SearchApiConfig.config.load[IO] override def run(args: List[String]): IO[ExitCode] = @@ -33,5 +33,9 @@ object Microservice extends IOApp: _ <- IO(LoggingSetup.doConfigure(config.verbosity)) _ <- Routes[IO](config.solrConfig, config.jwtVerifyConfig).makeRoutes .flatMap(HttpServer.build(_, config.httpServerConfig)) - .use(_ => IO.never) + .use { _ => + logger.info( + s"Search microservice running: ${config.httpServerConfig}" + ) >> IO.never + } } yield ExitCode.Success diff --git a/modules/search-api/src/main/scala/io/renku/search/api/data/SearchEntity.scala b/modules/search-api/src/main/scala/io/renku/search/api/data/SearchEntity.scala index 70e0c0f8..91e18960 100644 --- a/modules/search-api/src/main/scala/io/renku/search/api/data/SearchEntity.scala +++ b/modules/search-api/src/main/scala/io/renku/search/api/data/SearchEntity.scala @@ -35,13 +35,13 @@ sealed trait SearchEntity final case class Project( id: Id, name: Name, - slug: projects.Slug, + slug: Slug, namespace: Option[Namespace], - repositories: Seq[projects.Repository], - visibility: projects.Visibility, - description: Option[projects.Description] = None, + repositories: Seq[Repository], + visibility: Visibility, + description: Option[Description] = None, createdBy: UserId, - creationDate: projects.CreationDate, + creationDate: CreationDate, keywords: List[Keyword] = Nil, score: Option[Double] = None ) extends SearchEntity @@ -50,12 +50,12 @@ object Project: private given Schema[Id] = Schema.string[Id] private given Schema[Name] = Schema.string[Name] private given Schema[Namespace] = Schema.string[Namespace] - private given Schema[projects.Slug] = Schema.string[projects.Slug] - private given Schema[projects.Repository] = Schema.string[projects.Repository] - private given Schema[projects.Visibility] = - Schema.derivedEnumeration[projects.Visibility].defaultStringBased - private given Schema[projects.Description] = Schema.string[projects.Description] - private given Schema[projects.CreationDate] = Schema(SDateTime()) + private given Schema[Slug] = Schema.string[Slug] + private given Schema[Repository] = Schema.string[Repository] + private given Schema[Visibility] = + Schema.derivedEnumeration[Visibility].defaultStringBased + private given Schema[Description] = Schema.string[Description] + private given Schema[CreationDate] = Schema(SDateTime()) private given Schema[Keyword] = Schema.string[Keyword] given Schema[Project] = Schema .derived[Project] @@ -63,13 +63,13 @@ object Project: Project( Id("01HRA7AZ2Q234CDQWGA052F8MK"), Name("renku"), - projects.Slug("renku"), + Slug("renku"), Some(Namespace("renku/renku")), - Seq(projects.Repository("https://github.com/renku")), - projects.Visibility.Public, - Some(projects.Description("Renku project")), + Seq(Repository("https://github.com/renku")), + Visibility.Public, + Some(Description("Renku project")), UserId(Id("1CAF4C73F50D4514A041C9EDDB025A36")), - projects.CreationDate(Instant.now), + CreationDate(Instant.now), List(Keyword("data"), Keyword("science")), Some(1.0) ): SearchEntity @@ -87,16 +87,16 @@ object UserId: final case class User( id: Id, namespace: Option[Namespace] = None, - firstName: Option[users.FirstName] = None, - lastName: Option[users.LastName] = None, + firstName: Option[FirstName] = None, + lastName: Option[LastName] = None, score: Option[Double] = None ) extends SearchEntity object User: private given Schema[Id] = Schema.string[Id] - private given Schema[users.FirstName] = Schema.string[users.FirstName] - private given Schema[users.LastName] = Schema.string[users.LastName] - private given Schema[users.Email] = Schema.string[users.Email] + private given Schema[FirstName] = Schema.string[FirstName] + private given Schema[LastName] = Schema.string[LastName] + private given Schema[Email] = Schema.string[Email] private given Schema[Namespace] = Schema.string[Namespace] given Schema[User] = Schema .derived[User] @@ -104,8 +104,8 @@ object User: User( Id("1CAF4C73F50D4514A041C9EDDB025A36"), Some(Namespace("renku/renku")), - Some(users.FirstName("Albert")), - Some(users.LastName("Einstein")), + Some(FirstName("Albert")), + Some(LastName("Einstein")), Some(2.1) ): SearchEntity ) @@ -114,7 +114,7 @@ final case class Group( id: Id, name: Name, namespace: Namespace, - description: Option[groups.Description] = None, + description: Option[Description] = None, score: Option[Double] = None ) extends SearchEntity @@ -122,7 +122,7 @@ object Group: private given Schema[Id] = Schema.string[Id] private given Schema[Name] = Schema.string[Name] private given Schema[Namespace] = Schema.string[Namespace] - private given Schema[groups.Description] = Schema.string[groups.Description] + private given Schema[Description] = Schema.string[Description] given Schema[Group] = Schema .derived[Group] .jsonExample( @@ -130,7 +130,7 @@ object Group: Id("2CAF4C73F50D4514A041C9EDDB025A36"), Name("SDSC"), Namespace("SDSC"), - Some(groups.Description("SDSC group")), + Some(Description("SDSC group")), Some(1.1) ): SearchEntity ) diff --git a/modules/search-api/src/main/scala/io/renku/search/api/tapir/ApiSchema.scala b/modules/search-api/src/main/scala/io/renku/search/api/tapir/ApiSchema.scala new file mode 100644 index 00000000..a41bf292 --- /dev/null +++ b/modules/search-api/src/main/scala/io/renku/search/api/tapir/ApiSchema.scala @@ -0,0 +1,92 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.api.tapir + +import java.time.Instant + +import io.renku.search.api.data.* +import io.renku.search.api.tapir.SchemaSyntax.* +import io.renku.search.model.* +import sttp.tapir.Schema.SName +import sttp.tapir.SchemaType.* +import sttp.tapir.generic.Configuration +import sttp.tapir.{FieldName, Schema, SchemaType} + +/** tapir schema definitions for the search api data structures */ +trait ApiSchema extends ApiSchema.Primitives: + given Schema[User] = Schema + .derived[User] + .jsonExample( + User( + Id("1CAF4C73F50D4514A041C9EDDB025A36"), + Some(Namespace("renku/renku")), + Some(FirstName("Albert")), + Some(LastName("Einstein")), + Some(2.1) + ): SearchEntity + ) + + given Schema[Group] = Schema + .derived[Group] + .jsonExample( + Group( + Id("2CAF4C73F50D4514A041C9EDDB025A36"), + Name("SDSC"), + Namespace("SDSC"), + Some(Description("SDSC group")), + Some(1.1) + ): SearchEntity + ) + + given Schema[Project] = Schema + .derived[Project] + .jsonExample( + Project( + Id("01HRA7AZ2Q234CDQWGA052F8MK"), + Name("renku"), + Slug("renku"), + Some(Namespace("renku/renku")), + Seq(Repository("https://github.com/renku")), + Visibility.Public, + Some(Description("Renku project")), + UserId(Id("bla")), + CreationDate(Instant.now), + List(Keyword("data"), Keyword("science")), + Some(1.0) + ): SearchEntity + ) +end ApiSchema + +object ApiSchema: + trait Primitives: + given Schema[Id] = Schema.string[Id] + given Schema[Name] = Schema.string[Name] + given Schema[Namespace] = Schema.string[Namespace] + given Schema[Slug] = Schema.string[Slug] + given Schema[Repository] = Schema.string[Repository] + given Schema[Visibility] = + Schema.derivedEnumeration[Visibility].defaultStringBased + given Schema[Description] = Schema.string[Description] + given Schema[CreationDate] = Schema(SDateTime()) + given Schema[Keyword] = Schema.string[Keyword] + given Schema[FirstName] = Schema.string[FirstName] + given Schema[LastName] = Schema.string[LastName] + given Schema[Email] = Schema.string[Email] + + end Primitives diff --git a/modules/search-api/src/test/scala/io/renku/search/api/SearchApiSpec.scala b/modules/search-api/src/test/scala/io/renku/search/api/SearchApiSpec.scala index 66ddb12b..49863c47 100644 --- a/modules/search-api/src/test/scala/io/renku/search/api/SearchApiSpec.scala +++ b/modules/search-api/src/test/scala/io/renku/search/api/SearchApiSpec.scala @@ -24,9 +24,7 @@ import cats.syntax.all.* import io.github.arainko.ducktape.* import io.renku.search.GeneratorSyntax.* import io.renku.search.api.data.* -import io.renku.search.model.Id -import io.renku.search.model.projects.Visibility -import io.renku.search.model.users.FirstName +import io.renku.search.model.* import io.renku.search.query.Query import io.renku.search.solr.client.SearchSolrSuite import io.renku.search.solr.client.SolrDocumentGenerators.* diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/CommonOpts.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/CommonOpts.scala index f0c2801d..830be96e 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/CommonOpts.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/CommonOpts.scala @@ -23,7 +23,6 @@ import cats.syntax.all.* import com.monovore.decline.Argument import com.monovore.decline.Opts import io.renku.search.model.* -import io.renku.search.model.projects.{Description as ProjectDescription, *} trait CommonOpts: @@ -52,20 +51,20 @@ trait CommonOpts: given Argument[Repository] = Argument.readString.map(Repository(_)) - given Argument[ProjectDescription] = - Argument.readString.map(ProjectDescription(_)) + given Argument[Description] = + Argument.readString.map(Description(_)) given Argument[Keyword] = Argument.readString.map(Keyword(_)) - given Argument[users.FirstName] = - Argument.readString.map(users.FirstName(_)) + given Argument[FirstName] = + Argument.readString.map(FirstName(_)) - given Argument[users.LastName] = - Argument.readString.map(users.LastName(_)) + given Argument[LastName] = + Argument.readString.map(LastName(_)) - given Argument[users.Email] = - Argument.readString.map(users.Email(_)) + given Argument[Email] = + Argument.readString.map(Email(_)) val nameOpt: Opts[Name] = Opts.option[Name]("name", "The name of the entity") @@ -111,16 +110,16 @@ trait CommonOpts: .map(_.toList) .withDefault(Nil) - val projectDescription: Opts[Option[ProjectDescription]] = - Opts.option[ProjectDescription]("description", "The project description").orNone + val projectDescription: Opts[Option[Description]] = + Opts.option[Description]("description", "The project description").orNone - val firstName: Opts[Option[users.FirstName]] = - Opts.option[users.FirstName]("first-name", "The first name").orNone + val firstName: Opts[Option[FirstName]] = + Opts.option[FirstName]("first-name", "The first name").orNone - val lastName: Opts[Option[users.LastName]] = - Opts.option[users.LastName]("last-name", "The last name").orNone + val lastName: Opts[Option[LastName]] = + Opts.option[LastName]("last-name", "The last name").orNone - val email: Opts[Option[users.Email]] = - Opts.option[users.Email]("email", "The email address").orNone + val email: Opts[Option[Email]] = + Opts.option[Email]("email", "The email address").orNone object CommonOpts extends CommonOpts diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabDocsCreator.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabDocsCreator.scala index c96ad90f..ad99dd07 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabDocsCreator.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabDocsCreator.scala @@ -28,7 +28,6 @@ import io.github.arainko.ducktape.* import io.renku.search.http.HttpClientDsl import io.renku.search.http.borer.BorerEntityJsonCodec.given import io.renku.search.model.* -import io.renku.search.model.Namespace import io.renku.search.solr.documents.{Project, User} import io.renku.solr.client.DocVersion import org.http4s.* @@ -85,12 +84,12 @@ private class GitLabDocsCreator[F[_]: Async: ModelTypesGenerators]( Field.computed(_.keywords, _.tagsAndTopics.map(Keyword.apply)), Field.default(_.version), Field.computed(_.id, s => Id(s"gl_proj_${s.id}")), - Field.computed(_.slug, s => projects.Slug(s.path_with_namespace)), + Field.computed(_.slug, s => Slug(s.path_with_namespace)), Field - .computed(_.repositories, s => Seq(projects.Repository(s.http_url_to_repo))), + .computed(_.repositories, s => Seq(Repository(s.http_url_to_repo))), Field.computed(_.visibility, s => s.visibility), Field.computed(_.createdBy, s => user.id), - Field.computed(_.creationDate, s => projects.CreationDate(s.created_at)), + Field.computed(_.creationDate, s => CreationDate(s.created_at)), Field.default(_.owners), Field.default(_.editors), Field.default(_.viewers), @@ -154,8 +153,8 @@ private class GitLabDocsCreator[F[_]: Async: ModelTypesGenerators]( GET(uri) .putHeaders(Accept(application.json)) - private def toFirstAndLast(v: String): Option[(users.FirstName, users.LastName)] = + private def toFirstAndLast(v: String): Option[(FirstName, LastName)] = v.trim.split(' ').toList match { - case f :: r => Some(users.FirstName(f) -> users.LastName(r.mkString(" "))) + case f :: r => Some(FirstName(f) -> LastName(r.mkString(" "))) case _ => None } diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala index 94c24eac..3a4735a6 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala @@ -27,7 +27,7 @@ import io.bullet.borer.NullOptions.given import io.bullet.borer.derivation.MapBasedCodecs.deriveDecoder import io.bullet.borer.derivation.key import io.renku.search.borer.codecs.DateTimeDecoders -import io.renku.search.model.projects +import io.renku.search.model.Visibility final private case class GitLabProject( id: Int, @@ -39,7 +39,7 @@ final private case class GitLabProject( @key("tag_list") tagList: List[String], topics: List[String] ): - val visibility: projects.Visibility = projects.Visibility.Public + val visibility: Visibility = Visibility.Public lazy val tagsAndTopics: List[String] = (tagList ::: topics).distinct diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/ModelTypesGenerators.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/ModelTypesGenerators.scala index d318ebfa..9c6bff49 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/ModelTypesGenerators.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/ModelTypesGenerators.scala @@ -28,8 +28,7 @@ import cats.effect.std.{Random, UUIDGen} import cats.syntax.all.* import io.renku.search.events.* -import io.renku.search.model.MemberRole -import io.renku.search.model.{Id, projects} +import io.renku.search.model.* private object ModelTypesGenerators: @@ -49,16 +48,16 @@ private trait ModelTypesGenerators[F[_]: Monad: Random: UUIDGen]: def generateId: F[Id] = UUIDGen.randomString[F].map(_.replace("-", "").toUpperCase).map(Id(_)) - def generateCreationDate: F[projects.CreationDate] = + def generateCreationDate: F[CreationDate] = Random[F] .betweenLong( Instant.now().minus(5 * 365, DAYS).toEpochMilli, Instant.now().toEpochMilli ) .map(Instant.ofEpochMilli) - .map(projects.CreationDate.apply) - def generateVisibility: F[projects.Visibility] = - Random[F].shuffleList(projects.Visibility.values.toList).map(_.head) + .map(CreationDate.apply) + def generateVisibility: F[Visibility] = + Random[F].shuffleList(Visibility.values.toList).map(_.head) def generateRole: F[MemberRole] = Random[F].shuffleList(MemberRole.values.toList).map(_.head) diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/RandommerIoDocsCreator.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/RandommerIoDocsCreator.scala index 79e6ad9a..78ba9b35 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/RandommerIoDocsCreator.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/RandommerIoDocsCreator.scala @@ -68,11 +68,10 @@ private class RandommerIoDocsCreator[F[_]: Async: ModelTypesGenerators]( ) client.expect[List[String]](req).map(_.flatMap(toFirstAndLast)) - private lazy val toUser: ((users.FirstName, users.LastName)) => F[User] = { - case (first, last) => - gens.generateId.map(id => - User(id, DocVersion.NotExists, first.some, last.some, Name(s"$first $last").some) - ) + private lazy val toUser: ((FirstName, LastName)) => F[User] = { case (first, last) => + gens.generateId.map(id => + User(id, DocVersion.NotExists, first.some, last.some, Name(s"$first $last").some) + ) } override def findProject: Stream[F, (Project, List[User])] = @@ -86,7 +85,7 @@ private class RandommerIoDocsCreator[F[_]: Async: ModelTypesGenerators]( private def toProject( name: Name, - desc: projects.Description, + desc: Description, keywords: List[Keyword], user: User ): F[(Project, List[User])] = @@ -98,7 +97,7 @@ private class RandommerIoDocsCreator[F[_]: Async: ModelTypesGenerators]( name = name, slug = slug, repositories = Seq(createRepo(slug)), - visibility = projects.Visibility.Public, + visibility = Visibility.Public, description = Some(desc), keywords = keywords, createdBy = user.id, @@ -107,14 +106,14 @@ private class RandommerIoDocsCreator[F[_]: Async: ModelTypesGenerators]( } private def createSlug(name: Name, user: User) = - projects.Slug { + Slug { val nameConditioned = name.value.replace(" ", "_") val namespace = user.name.map(_.value.replace(" ", "_")).getOrElse(nameConditioned) s"$namespace/$nameConditioned".toLowerCase } - private def createRepo(slug: projects.Slug) = - projects.Repository(s"https://github.com/$slug") + private def createRepo(slug: Slug) = + Repository(s"https://github.com/$slug") private lazy val findName: Stream[F, Name] = Stream.evals(getNameSuggestions).map(Name.apply) ++ findName @@ -126,12 +125,12 @@ private class RandommerIoDocsCreator[F[_]: Async: ModelTypesGenerators]( ) client.expect[List[String]](req) - private lazy val findDescription: Stream[F, projects.Description] = + private lazy val findDescription: Stream[F, Description] = Stream .evals(getReviews) .zip(Stream.evals(getBusinessNames)) .flatMap { case (l, r) => Stream(l, r) } - .map(projects.Description.apply) ++ findDescription + .map(Description.apply) ++ findDescription private lazy val getReviews: F[List[String]] = val req = post( @@ -168,8 +167,8 @@ private class RandommerIoDocsCreator[F[_]: Async: ModelTypesGenerators]( .putHeaders(Accept(application.json)) .putHeaders(Header.Raw(ci"X-Api-Key", apiKey)) - private def toFirstAndLast(v: String): Option[(users.FirstName, users.LastName)] = + private def toFirstAndLast(v: String): Option[(FirstName, LastName)] = v.split(' ').toList match { - case f :: r => Some(users.FirstName(f) -> users.LastName(r.mkString(" "))) + case f :: r => Some(FirstName(f) -> LastName(r.mkString(" "))) case _ => None } diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/projects/CreateCmd.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/projects/CreateCmd.scala index b74ac6df..aa9dcbfa 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/projects/CreateCmd.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/projects/CreateCmd.scala @@ -33,12 +33,12 @@ object CreateCmd extends CommonOpts: id: Id, name: Name, namespace: Namespace, - slug: projects.Slug, - visibility: projects.Visibility, + slug: Slug, + visibility: Visibility, createdBy: Id, creationDate: Timestamp, - repositories: Seq[projects.Repository], - description: Option[projects.Description], + repositories: Seq[Repository], + description: Option[Description], keywords: Seq[Keyword] ): def asPayload: ProjectCreated = ProjectCreated( diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/projects/UpdateCmd.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/projects/UpdateCmd.scala index 49ec6582..39b8ce21 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/projects/UpdateCmd.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/projects/UpdateCmd.scala @@ -33,10 +33,10 @@ object UpdateCmd extends CommonOpts: id: Id, name: Name, namespace: Namespace, - slug: projects.Slug, - visibility: projects.Visibility, - repositories: Seq[projects.Repository], - description: Option[projects.Description], + slug: Slug, + visibility: Visibility, + repositories: Seq[Repository], + description: Option[Description], keywords: Seq[Keyword] ): def asPayload: ProjectUpdated = ProjectUpdated( diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/users/AddCmd.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/users/AddCmd.scala index 34597645..f0bf4519 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/users/AddCmd.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/users/AddCmd.scala @@ -26,7 +26,6 @@ import io.renku.search.cli.{CommonOpts, Services} import io.renku.search.config.QueuesConfig import io.renku.search.events.UserAdded import io.renku.search.model.* -import io.renku.search.model.users.* object AddCmd: diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/users/UpdateCmd.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/users/UpdateCmd.scala index 03c9c084..a69e79a2 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/users/UpdateCmd.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/users/UpdateCmd.scala @@ -26,7 +26,6 @@ import io.renku.search.cli.{CommonOpts, Services} import io.renku.search.config.QueuesConfig import io.renku.search.events.UserUpdated import io.renku.search.model.* -import io.renku.search.model.users.* object UpdateCmd: diff --git a/modules/search-provision/src/main/scala/io/renku/search/provision/events/Groups.scala b/modules/search-provision/src/main/scala/io/renku/search/provision/events/Groups.scala index 3106c486..fd92dec2 100644 --- a/modules/search-provision/src/main/scala/io/renku/search/provision/events/Groups.scala +++ b/modules/search-provision/src/main/scala/io/renku/search/provision/events/Groups.scala @@ -31,9 +31,9 @@ trait Groups: GroupDocument( id = ga.id.toId, version = version, - name = ga.name.toGroupName, + name = ga.name.toName, namespace = ga.namespace.toNamespace, - description = ga.description.map(_.toGroupDescription) + description = ga.description.map(_.toDescription) ) def fromGroupUpdated( @@ -43,17 +43,17 @@ trait Groups: PartialEntityDocument.Group( id = ga.id.toId, version = version, - name = Some(ga.name.toGroupName), + name = Some(ga.name.toName), namespace = Some(ga.namespace.toNamespace), - description = ga.description.map(_.toGroupDescription) + description = ga.description.map(_.toDescription) ) def fromGroupUpdated(gu: v2.GroupUpdated, orig: GroupDocument): GroupDocument = orig.copy( id = gu.id.toId, - name = gu.name.toGroupName, + name = gu.name.toName, namespace = gu.namespace.toNamespace, - description = gu.description.map(_.toGroupDescription) + description = gu.description.map(_.toDescription) ) def fromGroupUpdated( @@ -62,9 +62,9 @@ trait Groups: ): PartialEntityDocument.Group = orig.copy( id = gu.id.toId, - name = Some(gu.name.toGroupName), + name = Some(gu.name.toName), namespace = Some(gu.namespace.toNamespace), - description = gu.description.map(_.toGroupDescription) + description = gu.description.map(_.toDescription) ) def fromGroupMemberAdded( diff --git a/modules/search-provision/src/test/scala/io/renku/search/provision/group/GroupRemovedProcessSpec.scala b/modules/search-provision/src/test/scala/io/renku/search/provision/group/GroupRemovedProcessSpec.scala index a65922a3..a575a975 100644 --- a/modules/search-provision/src/test/scala/io/renku/search/provision/group/GroupRemovedProcessSpec.scala +++ b/modules/search-provision/src/test/scala/io/renku/search/provision/group/GroupRemovedProcessSpec.scala @@ -24,8 +24,7 @@ import cats.syntax.all.* import io.renku.events.EventsGenerators import io.renku.search.GeneratorSyntax.* import io.renku.search.events.GroupRemoved -import io.renku.search.model.EntityType -import io.renku.search.model.Id +import io.renku.search.model.{EntityType, Id} import io.renku.search.provision.ProvisioningSuite import io.renku.search.solr.client.SearchSolrClient import io.renku.search.solr.client.SolrDocumentGenerators diff --git a/modules/search-provision/src/test/scala/io/renku/search/provision/project/AuthorizationAddedProvisioningSpec.scala b/modules/search-provision/src/test/scala/io/renku/search/provision/project/AuthorizationAddedProvisioningSpec.scala index f192460d..7302efac 100644 --- a/modules/search-provision/src/test/scala/io/renku/search/provision/project/AuthorizationAddedProvisioningSpec.scala +++ b/modules/search-provision/src/test/scala/io/renku/search/provision/project/AuthorizationAddedProvisioningSpec.scala @@ -24,7 +24,6 @@ import io.renku.events.EventsGenerators import io.renku.search.GeneratorSyntax.* import io.renku.search.events.ProjectMemberAdded import io.renku.search.model.* -import io.renku.search.model.MemberRole import io.renku.search.provision.project.AuthorizationAddedProvisioningSpec.testCases import io.renku.search.provision.{BackgroundCollector, ProvisioningSuite} import io.renku.search.solr.client.{SearchSolrClient, SolrDocumentGenerators} diff --git a/modules/search-provision/src/test/scala/io/renku/search/provision/user/UserAddedProvisioningSpec.scala b/modules/search-provision/src/test/scala/io/renku/search/provision/user/UserAddedProvisioningSpec.scala index bbb20b01..9feeac24 100644 --- a/modules/search-provision/src/test/scala/io/renku/search/provision/user/UserAddedProvisioningSpec.scala +++ b/modules/search-provision/src/test/scala/io/renku/search/provision/user/UserAddedProvisioningSpec.scala @@ -25,9 +25,7 @@ import cats.syntax.all.* import io.renku.events.EventsGenerators import io.renku.search.GeneratorSyntax.* import io.renku.search.events.UserAdded -import io.renku.search.model.Id -import io.renku.search.model.ModelGenerators -import io.renku.search.model.users.FirstName +import io.renku.search.model.* import io.renku.search.provision.ProvisioningSuite import io.renku.search.provision.events.syntax.* import io.renku.search.solr.documents.{CompoundId, EntityDocument, User as UserDocument} diff --git a/modules/search-query-docs/docs/manual.md b/modules/search-query-docs/docs/manual.md index 6f0e9c70..aaeebb4f 100644 --- a/modules/search-query-docs/docs/manual.md +++ b/modules/search-query-docs/docs/manual.md @@ -85,7 +85,7 @@ Possbile values are: ```scala mdoc:passthrough println( - projects.Visibility.values.map(e => s"`${e.name}`").mkString("- ", "\n- ", "") + Visibility.values.map(e => s"`${e.name}`").mkString("- ", "\n- ", "") ) ``` diff --git a/modules/search-query/src/main/scala/io/renku/search/query/FieldTerm.scala b/modules/search-query/src/main/scala/io/renku/search/query/FieldTerm.scala index 2820821d..f77fead7 100644 --- a/modules/search-query/src/main/scala/io/renku/search/query/FieldTerm.scala +++ b/modules/search-query/src/main/scala/io/renku/search/query/FieldTerm.scala @@ -20,9 +20,7 @@ package io.renku.search.query import cats.data.NonEmptyList -import io.renku.search.model.Namespace -import io.renku.search.model.projects.Visibility -import io.renku.search.model.{EntityType, Keyword, MemberRole} +import io.renku.search.model.* enum FieldTerm(val field: Field, val cmp: Comparison): case TypeIs(values: NonEmptyList[EntityType]) diff --git a/modules/search-query/src/main/scala/io/renku/search/query/Query.scala b/modules/search-query/src/main/scala/io/renku/search/query/Query.scala index d095b45a..c52ef03d 100644 --- a/modules/search-query/src/main/scala/io/renku/search/query/Query.scala +++ b/modules/search-query/src/main/scala/io/renku/search/query/Query.scala @@ -22,8 +22,7 @@ import cats.data.NonEmptyList import cats.kernel.Monoid import cats.syntax.all.* -import io.renku.search.model.EntityType -import io.renku.search.model.projects.Visibility +import io.renku.search.model.* import io.renku.search.query.FieldTerm.Created import io.renku.search.query.Query.Segment import io.renku.search.query.parse.{QueryParser, QueryUtil} diff --git a/modules/search-query/src/main/scala/io/renku/search/query/parse/QueryParser.scala b/modules/search-query/src/main/scala/io/renku/search/query/parse/QueryParser.scala index 6f4b2089..d44d850d 100644 --- a/modules/search-query/src/main/scala/io/renku/search/query/parse/QueryParser.scala +++ b/modules/search-query/src/main/scala/io/renku/search/query/parse/QueryParser.scala @@ -22,7 +22,6 @@ import cats.data.NonEmptyList import cats.parse.{Parser as P, Parser0 as P0} import io.renku.search.model.* -import io.renku.search.model.projects.Visibility import io.renku.search.query.* private[query] object QueryParser { diff --git a/modules/search-query/src/test/scala/io/renku/search/query/QueryGenerators.scala b/modules/search-query/src/test/scala/io/renku/search/query/QueryGenerators.scala index 01ddbc63..5fea9536 100644 --- a/modules/search-query/src/test/scala/io/renku/search/query/QueryGenerators.scala +++ b/modules/search-query/src/test/scala/io/renku/search/query/QueryGenerators.scala @@ -26,7 +26,6 @@ import cats.syntax.all.* import io.renku.search.common.CommonGenerators import io.renku.search.model.* -import io.renku.search.model.projects.Visibility import io.renku.search.query.parse.QueryUtil import org.scalacheck.Gen import org.scalacheck.cats.implicits.* @@ -134,8 +133,8 @@ object QueryGenerators: val visibilityTerm: Gen[FieldTerm] = Gen .frequency( - 10 -> ModelGenerators.projectVisibilityGen.map(NonEmptyList.one), - 1 -> CommonGenerators.nelOfN(2, ModelGenerators.projectVisibilityGen) + 10 -> ModelGenerators.visibilityGen.map(NonEmptyList.one), + 1 -> CommonGenerators.nelOfN(2, ModelGenerators.visibilityGen) ) .map(vs => FieldTerm.VisibilityIs(vs.distinct)) diff --git a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/EntityDocument.scala b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/EntityDocument.scala index 858292a0..41c4cc7f 100644 --- a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/EntityDocument.scala +++ b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/EntityDocument.scala @@ -23,7 +23,6 @@ import io.bullet.borer.NullOptions.given import io.bullet.borer.derivation.{MapBasedCodecs, key} import io.renku.search.model.* import io.renku.search.model.MemberRole.* -import io.renku.search.model.projects.Visibility import io.renku.search.solr.schema.EntityDocumentSchema.Fields import io.renku.solr.client.EncoderSupport.* import io.renku.solr.client.{DocVersion, EncoderSupport} @@ -45,12 +44,12 @@ final case class Project( id: Id, @key("_version_") version: DocVersion = DocVersion.Off, name: Name, - slug: projects.Slug, - repositories: Seq[projects.Repository] = Seq.empty, - visibility: projects.Visibility, - description: Option[projects.Description] = None, + slug: Slug, + repositories: Seq[Repository] = Seq.empty, + visibility: Visibility, + description: Option[Description] = None, createdBy: Id, - creationDate: projects.CreationDate, + creationDate: CreationDate, owners: Set[Id] = Set.empty, editors: Set[Id] = Set.empty, viewers: Set[Id] = Set.empty, @@ -97,8 +96,8 @@ object Project: final case class User( id: Id, @key("_version_") version: DocVersion = DocVersion.Off, - firstName: Option[users.FirstName] = None, - lastName: Option[users.LastName] = None, + firstName: Option[FirstName] = None, + lastName: Option[LastName] = None, name: Option[Name] = None, namespace: Option[Namespace] = None, score: Option[Double] = None @@ -123,8 +122,8 @@ object User: def of( id: Id, namespace: Option[Namespace] = None, - firstName: Option[users.FirstName] = None, - lastName: Option[users.LastName] = None, + firstName: Option[FirstName] = None, + lastName: Option[LastName] = None, score: Option[Double] = None ): User = User( @@ -142,7 +141,7 @@ final case class Group( @key("_version_") version: DocVersion = DocVersion.Off, name: Name, namespace: Namespace, - description: Option[groups.Description] = None, + description: Option[Description] = None, owners: Set[Id] = Set.empty, editors: Set[Id] = Set.empty, viewers: Set[Id] = Set.empty, @@ -180,7 +179,7 @@ object Group: owners: Set[Id] = Set.empty, editors: Set[Id] = Set.empty, viewers: Set[Id] = Set.empty, - description: Option[groups.Description] = None, + description: Option[Description] = None, score: Option[Double] = None ): Group = Group( diff --git a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/PartialEntityDocument.scala b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/PartialEntityDocument.scala index f4ba84ce..ea2b5910 100644 --- a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/PartialEntityDocument.scala +++ b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/PartialEntityDocument.scala @@ -22,7 +22,6 @@ import io.bullet.borer.* import io.bullet.borer.NullOptions.given import io.bullet.borer.derivation.{MapBasedCodecs, key} import io.renku.search.model.* -import io.renku.search.model.projects.* import io.renku.search.solr.documents.{Group as GroupDocument, Project as ProjectDocument} import io.renku.search.solr.schema.EntityDocumentSchema.Fields as SolrField import io.renku.solr.client.{DocVersion, EncoderSupport} @@ -113,7 +112,7 @@ object PartialEntityDocument: @key("_version_") version: DocVersion = DocVersion.Off, name: Option[Name] = None, namespace: Option[Namespace] = None, - description: Option[groups.Description] = None, + description: Option[Description] = None, owners: Set[Id] = Set.empty, editors: Set[Id] = Set.empty, viewers: Set[Id] = Set.empty diff --git a/modules/search-solr-client/src/main/scala/io/renku/search/solr/query/SolrToken.scala b/modules/search-solr-client/src/main/scala/io/renku/search/solr/query/SolrToken.scala index 5cde85e3..db4d5357 100644 --- a/modules/search-solr-client/src/main/scala/io/renku/search/solr/query/SolrToken.scala +++ b/modules/search-solr-client/src/main/scala/io/renku/search/solr/query/SolrToken.scala @@ -25,7 +25,6 @@ import cats.data.NonEmptyList import cats.syntax.all.* import io.renku.search.model.* -import io.renku.search.model.projects.Visibility import io.renku.search.query.Comparison import io.renku.search.solr.documents.DocumentKind import io.renku.search.solr.schema.EntityDocumentSchema.Fields as SolrField diff --git a/modules/search-solr-client/src/test/scala/io/renku/search/solr/client/SearchSolrClientSpec.scala b/modules/search-solr-client/src/test/scala/io/renku/search/solr/client/SearchSolrClientSpec.scala index dd6c143b..583d5c48 100644 --- a/modules/search-solr-client/src/test/scala/io/renku/search/solr/client/SearchSolrClientSpec.scala +++ b/modules/search-solr-client/src/test/scala/io/renku/search/solr/client/SearchSolrClientSpec.scala @@ -24,10 +24,7 @@ import cats.syntax.all.* import io.bullet.borer.Decoder import io.bullet.borer.derivation.MapBasedCodecs.deriveDecoder import io.renku.search.GeneratorSyntax.* -import io.renku.search.model.Id -import io.renku.search.model.ModelGenerators -import io.renku.search.model.projects.Visibility -import io.renku.search.model.users +import io.renku.search.model.* import io.renku.search.query.Query import io.renku.search.solr.SearchRole import io.renku.search.solr.client.SolrDocumentGenerators.* @@ -70,7 +67,7 @@ class SearchSolrClientSpec extends CatsEffectSuite with SearchSolrSuite: } yield () test("be able to insert and fetch a User document"): - val firstName = users.FirstName("Johnny") + val firstName = FirstName("Johnny") val user = userDocumentGen.generateOne.copy(firstName = firstName.some) for { client <- IO(searchSolrClient()) @@ -97,7 +94,7 @@ class SearchSolrClientSpec extends CatsEffectSuite with SearchSolrSuite: } yield () test("be able to find by the given query"): - val firstName = users.FirstName("Ian") + val firstName = FirstName("Ian") val user = userDocumentGen.generateOne.copy(firstName = firstName.some) case class UserId(id: String) given Decoder[UserId] = deriveDecoder[UserId] diff --git a/modules/search-solr-client/src/test/scala/io/renku/search/solr/client/SolrDocumentGenerators.scala b/modules/search-solr-client/src/test/scala/io/renku/search/solr/client/SolrDocumentGenerators.scala index eda7bfae..839a7c9b 100644 --- a/modules/search-solr-client/src/test/scala/io/renku/search/solr/client/SolrDocumentGenerators.scala +++ b/modules/search-solr-client/src/test/scala/io/renku/search/solr/client/SolrDocumentGenerators.scala @@ -23,7 +23,7 @@ import cats.syntax.all.* import io.renku.search.GeneratorSyntax.* import io.renku.search.model.* import io.renku.search.model.ModelGenerators.* -import io.renku.search.model.projects.Visibility +import io.renku.search.model.Visibility import io.renku.search.solr.documents.* import io.renku.solr.client.DocVersion import org.scalacheck.Gen @@ -53,18 +53,18 @@ trait SolrDocumentGenerators: def projectDocumentGen( name: String, desc: String, - visibilityGen: Gen[Visibility] = projectVisibilityGen + visibilityGen: Gen[Visibility] = visibilityGen ): Gen[Project] = - (idGen, idGen, visibilityGen, projectCreationDateGen) + (idGen, idGen, visibilityGen, creationDateGen) .mapN((projectId, creatorId, visibility, creationDate) => Project( projectId, DocVersion.NotExists, Name(name), - projects.Slug(name), - Seq(projects.Repository(s"http://github.com/$name")), + Slug(name), + Seq(Repository(s"http://github.com/$name")), visibility, - Option(projects.Description(desc)), + Option(Description(desc)), creatorId, creationDate ) diff --git a/modules/search-solr-client/src/test/scala/io/renku/search/solr/query/AuthTestData.scala b/modules/search-solr-client/src/test/scala/io/renku/search/solr/query/AuthTestData.scala index 94a73e15..f791cc53 100644 --- a/modules/search-solr-client/src/test/scala/io/renku/search/solr/query/AuthTestData.scala +++ b/modules/search-solr-client/src/test/scala/io/renku/search/solr/query/AuthTestData.scala @@ -22,8 +22,7 @@ import cats.effect.IO import cats.syntax.all.* import io.renku.search.GeneratorSyntax.* -import io.renku.search.model.projects.Visibility -import io.renku.search.model.{Id, MemberRole} +import io.renku.search.model.{Id, MemberRole, Visibility} import io.renku.search.query.Query import io.renku.search.solr.client.SolrDocumentGenerators import io.renku.search.solr.documents.* From 40674874a0a2884aebecd28494ccfa5436e23413 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Thu, 6 Jun 2024 13:08:18 +0200 Subject: [PATCH 2/5] Configure http server shutdown timeout It is useful for local development to be able to set it to 0, otherwise the default of 30s seems appropriate. --- flake.nix | 4 ++++ .../main/scala/io/renku/search/config/ConfigValues.scala | 3 ++- .../src/main/scala/io/renku/search/http/HttpServer.scala | 2 +- .../main/scala/io/renku/search/http/HttpServerConfig.scala | 7 ++++++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index b040580d..db9ed5ba 100644 --- a/flake.nix +++ b/flake.nix @@ -84,7 +84,9 @@ RS_CONTAINER = "rsdev"; RS_LOG_LEVEL = "3"; RS_SEARCH_HTTP_SERVER_PORT = "8080"; + RS_SEARCH_HTTP_SHUTDOWN_TIMEOUT = "0ms"; RS_PROVISION_HTTP_SERVER_PORT = "8082"; + RS_PROVISION_HTTP_SHUTDOWN_TIMEOUT = "0ms"; RS_METRICS_UPDATE_INTERVAL = "0s"; RS_SOLR_CREATE_CORE_CMD = "cnt-solr-create-core %s"; RS_SOLR_DELETE_CORE_CMD = "cnt-solr-delete-core %s"; @@ -109,7 +111,9 @@ VM_SSH_PORT = "10022"; RS_LOG_LEVEL = "3"; RS_SEARCH_HTTP_SERVER_PORT = "8080"; + RS_SEARCH_HTTP_SHUTDOWN_TIMEOUT = "0ms"; RS_PROVISION_HTTP_SERVER_PORT = "8082"; + RS_PROVISION_HTTP_SHUTDOWN_TIMEOUT = "0ms"; RS_METRICS_UPDATE_INTERVAL = "0s"; RS_SOLR_CREATE_CORE_CMD = "vm-solr-create-core %s"; RS_SOLR_DELETE_CORE_CMD = "vm-solr-delete-core %s"; diff --git a/modules/config-values/src/main/scala/io/renku/search/config/ConfigValues.scala b/modules/config-values/src/main/scala/io/renku/search/config/ConfigValues.scala index 1bf76259..33f2ebef 100644 --- a/modules/config-values/src/main/scala/io/renku/search/config/ConfigValues.scala +++ b/modules/config-values/src/main/scala/io/renku/search/config/ConfigValues.scala @@ -86,7 +86,8 @@ object ConfigValues extends ConfigDecoders: renv(s"${prefix}_HTTP_SERVER_BIND_ADDRESS").default("0.0.0.0").as[Ipv4Address] val port = renv(s"${prefix}_HTTP_SERVER_PORT").default(defaultPort.value.toString).as[Port] - (bindAddress, port).mapN(HttpServerConfig.apply) + val shutdownTimeout = renv(s"${prefix}_HTTP_SHUTDOWN_TIMEOUT").default("30s").as[Duration] + (bindAddress, port, shutdownTimeout).mapN(HttpServerConfig.apply) val jwtVerifyConfig: ConfigValue[Effect, JwtVerifyConfig] = { val defaults = JwtVerifyConfig.default diff --git a/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServer.scala b/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServer.scala index 5effb7e3..99218733 100644 --- a/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServer.scala +++ b/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServer.scala @@ -34,6 +34,6 @@ object HttpServer: .default[F] .withHost(config.bindAddress) .withPort(config.port) - .withIdleTimeout(scala.concurrent.duration.Duration.Zero) + .withShutdownTimeout(config.shutdownTimeout) .withHttpApp(routes.orNotFound) .build diff --git a/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServerConfig.scala b/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServerConfig.scala index 773ea966..60f4ef3c 100644 --- a/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServerConfig.scala +++ b/modules/http4s-commons/src/main/scala/io/renku/search/http/HttpServerConfig.scala @@ -19,6 +19,11 @@ package io.renku.search.http import com.comcast.ip4s.{Ipv4Address, Port} +import scala.concurrent.duration.Duration -final case class HttpServerConfig(bindAddress: Ipv4Address, port: Port): +final case class HttpServerConfig( + bindAddress: Ipv4Address, + port: Port, + shutdownTimeout: Duration +): override def toString = s"Http server @ ${bindAddress}:$port" From 14fa287ad8419f441bfeb3ef5b7dd015f196c627 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Fri, 7 Jun 2024 08:29:57 +0200 Subject: [PATCH 3/5] Move tapir api schema definitions and search entity model - Separates tapir definitions into its own file - Put search entities into the parent namespace to help with same named classes like `Project`, `User` etc. Instead of renaming in the import, they can be referred to by `SearchEntity.Project` for example or imported all if desired - Create manual conversion form solr documents to search results entities, because later more customization is needed, which will be easier to do without ducktape --- .../io/renku/search/api/SearchApiImpl.scala | 8 +- .../search/api/data/EntityConverter.scala | 69 ++++++++ .../renku/search/api/data/SearchEntity.scala | 167 ++++-------------- .../renku/search/api/data/SearchResult.scala | 2 - .../io/renku/search/api/tapir/ApiSchema.scala | 98 ++++++---- .../io/renku/search/api/tapir/Params.scala | 2 +- .../io/renku/search/api/SearchApiSpec.scala | 13 +- 7 files changed, 176 insertions(+), 183 deletions(-) create mode 100644 modules/search-api/src/main/scala/io/renku/search/api/data/EntityConverter.scala diff --git a/modules/search-api/src/main/scala/io/renku/search/api/SearchApiImpl.scala b/modules/search-api/src/main/scala/io/renku/search/api/SearchApiImpl.scala index 40b2da60..56d4e796 100644 --- a/modules/search-api/src/main/scala/io/renku/search/api/SearchApiImpl.scala +++ b/modules/search-api/src/main/scala/io/renku/search/api/SearchApiImpl.scala @@ -21,10 +21,8 @@ package io.renku.search.api import cats.effect.Async import cats.syntax.all.* -import io.github.arainko.ducktape.* import io.renku.search.api.data.* import io.renku.search.model.EntityType -import io.renku.search.model.Id import io.renku.search.solr.client.SearchSolrClient import io.renku.search.solr.documents.EntityDocument import io.renku.search.solr.schema.EntityDocumentSchema.Fields @@ -80,10 +78,6 @@ private class SearchApiImpl[F[_]: Async](solrClient: SearchSolrClient[F]) FacetData(all) } .getOrElse(FacetData.empty) - val items = solrResult.responseBody.docs.map(toApiEntity) + val items = solrResult.responseBody.docs.map(EntityConverter.apply) if (hasMore) SearchResult(items.init, facets, pageInfo) else SearchResult(items, facets, pageInfo) - - private lazy val toApiEntity: EntityDocument => SearchEntity = - given Transformer[Id, UserId] = (id: Id) => UserId(id) - _.to[SearchEntity] diff --git a/modules/search-api/src/main/scala/io/renku/search/api/data/EntityConverter.scala b/modules/search-api/src/main/scala/io/renku/search/api/data/EntityConverter.scala new file mode 100644 index 00000000..8313eb5c --- /dev/null +++ b/modules/search-api/src/main/scala/io/renku/search/api/data/EntityConverter.scala @@ -0,0 +1,69 @@ +/* + * Copyright 2024 Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 io.renku.search.api.data + +import io.renku.search.solr.documents.{ + Group as GroupDocument, + Project as ProjectDocument, + User as UserDocument, + * +} + +trait EntityConverter: + def project(p: ProjectDocument): SearchEntity.Project = + SearchEntity.Project( + id = p.id, + name = p.name, + slug = p.slug, + namespace = p.namespace, + repositories = p.repositories, + visibility = p.visibility, + description = p.description, + createdBy = SearchEntity.UserId(p.createdBy), + creationDate = p.creationDate, + keywords = p.keywords, + score = p.score + ) + + def user(u: UserDocument): SearchEntity.User = + SearchEntity.User( + id = u.id, + namespace = u.namespace, + firstName = u.firstName, + lastName = u.lastName, + score = u.score + ) + + def group(g: GroupDocument): SearchEntity.Group = + SearchEntity.Group( + id = g.id, + name = g.name, + namespace = g.namespace, + description = g.description, + score = g.score + ) + + def entity(e: EntityDocument): SearchEntity = e match + case p: ProjectDocument => project(p) + case u: UserDocument => user(u) + case g: GroupDocument => group(g) +end EntityConverter + +object EntityConverter extends EntityConverter: + def apply(e: EntityDocument): SearchEntity = entity(e) diff --git a/modules/search-api/src/main/scala/io/renku/search/api/data/SearchEntity.scala b/modules/search-api/src/main/scala/io/renku/search/api/data/SearchEntity.scala index 91e18960..781f665c 100644 --- a/modules/search-api/src/main/scala/io/renku/search/api/data/SearchEntity.scala +++ b/modules/search-api/src/main/scala/io/renku/search/api/data/SearchEntity.scala @@ -18,143 +18,50 @@ package io.renku.search.api.data -import java.time.Instant - import io.bullet.borer.* import io.bullet.borer.NullOptions.given import io.bullet.borer.derivation.MapBasedCodecs.{deriveAllCodecs, deriveCodec} -import io.renku.search.api.tapir.SchemaSyntax.* import io.renku.search.model.* -import sttp.tapir.Schema.SName -import sttp.tapir.SchemaType.* -import sttp.tapir.generic.Configuration -import sttp.tapir.{FieldName, Schema, SchemaType} - -sealed trait SearchEntity - -final case class Project( - id: Id, - name: Name, - slug: Slug, - namespace: Option[Namespace], - repositories: Seq[Repository], - visibility: Visibility, - description: Option[Description] = None, - createdBy: UserId, - creationDate: CreationDate, - keywords: List[Keyword] = Nil, - score: Option[Double] = None -) extends SearchEntity - -object Project: - private given Schema[Id] = Schema.string[Id] - private given Schema[Name] = Schema.string[Name] - private given Schema[Namespace] = Schema.string[Namespace] - private given Schema[Slug] = Schema.string[Slug] - private given Schema[Repository] = Schema.string[Repository] - private given Schema[Visibility] = - Schema.derivedEnumeration[Visibility].defaultStringBased - private given Schema[Description] = Schema.string[Description] - private given Schema[CreationDate] = Schema(SDateTime()) - private given Schema[Keyword] = Schema.string[Keyword] - given Schema[Project] = Schema - .derived[Project] - .jsonExample( - Project( - Id("01HRA7AZ2Q234CDQWGA052F8MK"), - Name("renku"), - Slug("renku"), - Some(Namespace("renku/renku")), - Seq(Repository("https://github.com/renku")), - Visibility.Public, - Some(Description("Renku project")), - UserId(Id("1CAF4C73F50D4514A041C9EDDB025A36")), - CreationDate(Instant.now), - List(Keyword("data"), Keyword("science")), - Some(1.0) - ): SearchEntity - ) - -final case class UserId(id: Id) -object UserId: - given Codec[UserId] = deriveCodec[UserId] - - private given Schema[Id] = Schema.string[Id] - given Schema[UserId] = Schema - .derived[UserId] - .jsonExample(UserId(Id("01HRA7AZ2Q234CDQWGA052F8MK"))) - -final case class User( - id: Id, - namespace: Option[Namespace] = None, - firstName: Option[FirstName] = None, - lastName: Option[LastName] = None, - score: Option[Double] = None -) extends SearchEntity -object User: - private given Schema[Id] = Schema.string[Id] - private given Schema[FirstName] = Schema.string[FirstName] - private given Schema[LastName] = Schema.string[LastName] - private given Schema[Email] = Schema.string[Email] - private given Schema[Namespace] = Schema.string[Namespace] - given Schema[User] = Schema - .derived[User] - .jsonExample( - User( - Id("1CAF4C73F50D4514A041C9EDDB025A36"), - Some(Namespace("renku/renku")), - Some(FirstName("Albert")), - Some(LastName("Einstein")), - Some(2.1) - ): SearchEntity - ) - -final case class Group( - id: Id, - name: Name, - namespace: Namespace, - description: Option[Description] = None, - score: Option[Double] = None -) extends SearchEntity - -object Group: - private given Schema[Id] = Schema.string[Id] - private given Schema[Name] = Schema.string[Name] - private given Schema[Namespace] = Schema.string[Namespace] - private given Schema[Description] = Schema.string[Description] - given Schema[Group] = Schema - .derived[Group] - .jsonExample( - Group( - Id("2CAF4C73F50D4514A041C9EDDB025A36"), - Name("SDSC"), - Namespace("SDSC"), - Some(Description("SDSC group")), - Some(1.1) - ): SearchEntity - ) +sealed trait SearchEntity: + def id: Id + def score: Option[Double] object SearchEntity: - - private val discriminatorField = "type" + private[api] val discriminatorField = "type" given AdtEncodingStrategy = AdtEncodingStrategy.flat(discriminatorField) given Codec[SearchEntity] = deriveAllCodecs[SearchEntity] - given Schema[SearchEntity] = { - val derived = Schema.derived[SearchEntity] - derived.schemaType match { - case s: SCoproduct[_] => - derived.copy(schemaType = - s.addDiscriminatorField( - FieldName(discriminatorField), - Schema.string, - List( - summon[Schema[Project]].name.map(SRef(_)).map("Project" -> _), - summon[Schema[User]].name.map(SRef(_)).map("User" -> _) - ).flatten.toMap - ) - ) - case s => derived - } - } + final case class Project( + id: Id, + name: Name, + slug: Slug, + namespace: Option[Namespace], + repositories: Seq[Repository], + visibility: Visibility, + description: Option[Description] = None, + createdBy: UserId, + creationDate: CreationDate, + keywords: List[Keyword] = Nil, + score: Option[Double] = None + ) extends SearchEntity + + final case class UserId(id: Id) + object UserId: + given Codec[UserId] = deriveCodec[UserId] + + final case class User( + id: Id, + namespace: Option[Namespace] = None, + firstName: Option[FirstName] = None, + lastName: Option[LastName] = None, + score: Option[Double] = None + ) extends SearchEntity + + final case class Group( + id: Id, + name: Name, + namespace: Namespace, + description: Option[Description] = None, + score: Option[Double] = None + ) extends SearchEntity diff --git a/modules/search-api/src/main/scala/io/renku/search/api/data/SearchResult.scala b/modules/search-api/src/main/scala/io/renku/search/api/data/SearchResult.scala index c3733270..2b92cc40 100644 --- a/modules/search-api/src/main/scala/io/renku/search/api/data/SearchResult.scala +++ b/modules/search-api/src/main/scala/io/renku/search/api/data/SearchResult.scala @@ -21,7 +21,6 @@ package io.renku.search.api.data import io.bullet.borer.Decoder import io.bullet.borer.Encoder import io.bullet.borer.derivation.MapBasedCodecs -import sttp.tapir.Schema final case class SearchResult( items: Seq[SearchEntity], @@ -32,4 +31,3 @@ final case class SearchResult( object SearchResult: given Encoder[SearchResult] = MapBasedCodecs.deriveEncoder given Decoder[SearchResult] = MapBasedCodecs.deriveDecoder - given Schema[SearchResult] = Schema.derived diff --git a/modules/search-api/src/main/scala/io/renku/search/api/tapir/ApiSchema.scala b/modules/search-api/src/main/scala/io/renku/search/api/tapir/ApiSchema.scala index a41bf292..f1086966 100644 --- a/modules/search-api/src/main/scala/io/renku/search/api/tapir/ApiSchema.scala +++ b/modules/search-api/src/main/scala/io/renku/search/api/tapir/ApiSchema.scala @@ -21,6 +21,7 @@ package io.renku.search.api.tapir import java.time.Instant import io.renku.search.api.data.* +import io.renku.search.api.data.SearchEntity.* import io.renku.search.api.tapir.SchemaSyntax.* import io.renku.search.model.* import sttp.tapir.Schema.SName @@ -32,45 +33,44 @@ import sttp.tapir.{FieldName, Schema, SchemaType} trait ApiSchema extends ApiSchema.Primitives: given Schema[User] = Schema .derived[User] - .jsonExample( - User( - Id("1CAF4C73F50D4514A041C9EDDB025A36"), - Some(Namespace("renku/renku")), - Some(FirstName("Albert")), - Some(LastName("Einstein")), - Some(2.1) - ): SearchEntity - ) + .jsonExample(ApiSchema.exampleUser) given Schema[Group] = Schema .derived[Group] - .jsonExample( - Group( - Id("2CAF4C73F50D4514A041C9EDDB025A36"), - Name("SDSC"), - Namespace("SDSC"), - Some(Description("SDSC group")), - Some(1.1) - ): SearchEntity - ) + .jsonExample(ApiSchema.exampleGroup) given Schema[Project] = Schema .derived[Project] - .jsonExample( - Project( - Id("01HRA7AZ2Q234CDQWGA052F8MK"), - Name("renku"), - Slug("renku"), - Some(Namespace("renku/renku")), - Seq(Repository("https://github.com/renku")), - Visibility.Public, - Some(Description("Renku project")), - UserId(Id("bla")), - CreationDate(Instant.now), - List(Keyword("data"), Keyword("science")), - Some(1.0) - ): SearchEntity - ) + .jsonExample(ApiSchema.exampleProject) + + given Schema[UserId] = Schema + .derived[UserId] + .jsonExample(UserId(Id("01HRA7AZ2Q234CDQWGA052F8MK"))) + + given (using + projectSchema: Schema[Project], + userSchema: Schema[User], + groupSchema: Schema[Group] + ): Schema[SearchEntity] = { + val derived = Schema.derived[SearchEntity] + derived.schemaType match { + case s: SCoproduct[_] => + derived.copy(schemaType = + s.addDiscriminatorField( + FieldName(SearchEntity.discriminatorField), + Schema.string, + List( + projectSchema.name.map(SRef(_)).map("Project" -> _), + userSchema.name.map(SRef(_)).map("User" -> _), + groupSchema.name.map(SRef(_)).map("Group" -> _) + ).flatten.toMap + ) + ) + case s => derived + } + } + + given Schema[SearchResult] = Schema.derived end ApiSchema object ApiSchema: @@ -88,5 +88,35 @@ object ApiSchema: given Schema[FirstName] = Schema.string[FirstName] given Schema[LastName] = Schema.string[LastName] given Schema[Email] = Schema.string[Email] - end Primitives + + val exampleUser: SearchEntity = User( + Id("1CAF4C73F50D4514A041C9EDDB025A36"), + Some(Namespace("renku/renku")), + Some(FirstName("Albert")), + Some(LastName("Einstein")), + Some(2.1) + ) + + val exampleGroup: SearchEntity = Group( + Id("2CAF4C73F50D4514A041C9EDDB025A36"), + Name("SDSC"), + Namespace("SDSC"), + Some(Description("SDSC group")), + Some(1.1) + ) + + val exampleProject: SearchEntity = Project( + Id("01HRA7AZ2Q234CDQWGA052F8MK"), + Name("renku"), + Slug("renku"), + Some(Namespace("renku/renku")), + Seq(Repository("https://github.com/renku")), + Visibility.Public, + Some(Description("Renku project")), + UserId(Id("bla")), + CreationDate(Instant.now), + List(Keyword("data"), Keyword("science")), + Some(1.0) + ) +end ApiSchema diff --git a/modules/search-api/src/main/scala/io/renku/search/api/tapir/Params.scala b/modules/search-api/src/main/scala/io/renku/search/api/tapir/Params.scala index 0dda5ef6..ea8a8d5f 100644 --- a/modules/search-api/src/main/scala/io/renku/search/api/tapir/Params.scala +++ b/modules/search-api/src/main/scala/io/renku/search/api/tapir/Params.scala @@ -25,7 +25,7 @@ import io.renku.search.http.borer.TapirBorerJson import io.renku.search.query.Query import sttp.tapir.{query as queryParam, *} -object Params extends TapirCodecs with TapirBorerJson { +object Params extends TapirCodecs with TapirBorerJson with ApiSchema { val query: EndpointInput[Query] = queryParam[Query]("q").description("User defined search query").default(Query.empty) diff --git a/modules/search-api/src/test/scala/io/renku/search/api/SearchApiSpec.scala b/modules/search-api/src/test/scala/io/renku/search/api/SearchApiSpec.scala index 49863c47..ecd85269 100644 --- a/modules/search-api/src/test/scala/io/renku/search/api/SearchApiSpec.scala +++ b/modules/search-api/src/test/scala/io/renku/search/api/SearchApiSpec.scala @@ -21,7 +21,6 @@ package io.renku.search.api import cats.effect.IO import cats.syntax.all.* -import io.github.arainko.ducktape.* import io.renku.search.GeneratorSyntax.* import io.renku.search.api.data.* import io.renku.search.model.* @@ -90,15 +89,11 @@ class SearchApiSpec extends CatsEffectSuite with SearchSolrSuite: ) private def scoreToNone(e: SearchEntity): SearchEntity = e match - case e: Project => e.copy(score = None) - case e: User => e.copy(score = None) - case e: Group => e.copy(score = None) + case e: SearchEntity.Project => e.copy(score = None) + case e: SearchEntity.User => e.copy(score = None) + case e: SearchEntity.Group => e.copy(score = None) private def mkQuery(phrase: String): QueryInput = QueryInput.pageOne(Query.parse(s"Fields $phrase").fold(sys.error, identity)) - private def toApiEntities(e: EntityDocument*) = e.map(toApiEntity) - - private def toApiEntity(e: EntityDocument) = - given Transformer[Id, UserId] = (id: Id) => UserId(id) - e.to[SearchEntity] + private def toApiEntities(e: EntityDocument*) = e.map(EntityConverter.apply) From c59fbc8b68290239fb35d3731da73a354991fc5d Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Fri, 7 Jun 2024 09:04:42 +0200 Subject: [PATCH 4/5] Create separate module for borer utilities Moving common code working with borer library from solr-client and commons to its own module to be used in other library modules. --- build.sbt | 15 +++++++++++++++ .../io/renku/search/model/CreationDate.scala | 2 +- .../scala/io/renku/json}/EncoderSupport.scala | 2 +- .../main/scala/io/renku/json}/LabelsMacro.scala | 2 +- .../io/renku/json}/codecs/DateTimeDecoders.scala | 2 +- .../io/renku/json}/codecs/DateTimeEncoders.scala | 2 +- .../main/scala/io/renku/json}/codecs/all.scala | 2 +- .../scala/io/renku/json}/JsonEncodingSpec.scala | 4 ++-- .../search/cli/perftests/GitLabEntities.scala | 2 +- .../search/solr/documents/DocumentKind.scala | 2 +- .../search/solr/documents/EntityDocument.scala | 4 ++-- .../solr/documents/PartialEntityDocument.scala | 3 ++- .../io/renku/solr/client/SolrClientSpec.scala | 1 + 13 files changed, 30 insertions(+), 13 deletions(-) rename modules/{solr-client/src/main/scala/io/renku/solr/client => json/src/main/scala/io/renku/json}/EncoderSupport.scala (99%) rename modules/{solr-client/src/main/scala/io/renku/solr/client => json/src/main/scala/io/renku/json}/LabelsMacro.scala (98%) rename modules/{commons/src/main/scala/io/renku/search/borer => json/src/main/scala/io/renku/json}/codecs/DateTimeDecoders.scala (97%) rename modules/{commons/src/main/scala/io/renku/search/borer => json/src/main/scala/io/renku/json}/codecs/DateTimeEncoders.scala (96%) rename modules/{commons/src/main/scala/io/renku/search/borer => json/src/main/scala/io/renku/json}/codecs/all.scala (95%) rename modules/{solr-client/src/test/scala/io/renku/solr/client => json/src/test/scala/io/renku/json}/JsonEncodingSpec.scala (96%) diff --git a/build.sbt b/build.sbt index 4584918b..ffab3e42 100644 --- a/build.sbt +++ b/build.sbt @@ -57,6 +57,7 @@ lazy val root = project } ) .aggregate( + json, commons, jwt, openidKeycloak, @@ -72,6 +73,18 @@ lazy val root = project searchCli ) +lazy val json = project + .in(file("modules/json")) + .settings(commonSettings) + .settings( + name := "json", + // please don't add more dependencies here + libraryDependencies ++= Dependencies.borer, + description := "Utilities around working with borer" + ) + .enablePlugins(AutomateHeaderPlugin) + .disablePlugins(DbTestPlugin, RevolverPlugin) + lazy val commons = project .in(file("modules/commons")) .settings(commonSettings) @@ -102,6 +115,7 @@ lazy val commons = project buildInfoKeys := Seq(name, version, gitHeadCommit, gitDescribedVersion), buildInfoPackage := "io.renku.search" ) + .dependsOn(json % "compile->compile;test->test") .enablePlugins(AutomateHeaderPlugin, BuildInfoPlugin) .disablePlugins(DbTestPlugin, RevolverPlugin) @@ -255,6 +269,7 @@ lazy val solrClient = project ) .dependsOn( httpClient % "compile->compile;test->test", + json % "compile->compile;test->test", commons % "test->test" ) diff --git a/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala b/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala index 4c306505..438d8970 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala @@ -24,7 +24,7 @@ import cats.kernel.Order import io.bullet.borer.Codec import io.github.arainko.ducktape.* -import io.renku.search.borer.codecs.all.given +import io.renku.json.codecs.all.given opaque type CreationDate = Instant object CreationDate: diff --git a/modules/solr-client/src/main/scala/io/renku/solr/client/EncoderSupport.scala b/modules/json/src/main/scala/io/renku/json/EncoderSupport.scala similarity index 99% rename from modules/solr-client/src/main/scala/io/renku/solr/client/EncoderSupport.scala rename to modules/json/src/main/scala/io/renku/json/EncoderSupport.scala index 249a22d4..76687ffd 100644 --- a/modules/solr-client/src/main/scala/io/renku/solr/client/EncoderSupport.scala +++ b/modules/json/src/main/scala/io/renku/json/EncoderSupport.scala @@ -16,7 +16,7 @@ * limitations under the License. */ -package io.renku.solr.client +package io.renku.json import scala.compiletime.* import scala.deriving.* diff --git a/modules/solr-client/src/main/scala/io/renku/solr/client/LabelsMacro.scala b/modules/json/src/main/scala/io/renku/json/LabelsMacro.scala similarity index 98% rename from modules/solr-client/src/main/scala/io/renku/solr/client/LabelsMacro.scala rename to modules/json/src/main/scala/io/renku/json/LabelsMacro.scala index 0b18af65..42c41f4a 100644 --- a/modules/solr-client/src/main/scala/io/renku/solr/client/LabelsMacro.scala +++ b/modules/json/src/main/scala/io/renku/json/LabelsMacro.scala @@ -16,7 +16,7 @@ * limitations under the License. */ -package io.renku.solr.client +package io.renku.json import scala.quoted.* diff --git a/modules/commons/src/main/scala/io/renku/search/borer/codecs/DateTimeDecoders.scala b/modules/json/src/main/scala/io/renku/json/codecs/DateTimeDecoders.scala similarity index 97% rename from modules/commons/src/main/scala/io/renku/search/borer/codecs/DateTimeDecoders.scala rename to modules/json/src/main/scala/io/renku/json/codecs/DateTimeDecoders.scala index f0d11915..ef63e38a 100644 --- a/modules/commons/src/main/scala/io/renku/search/borer/codecs/DateTimeDecoders.scala +++ b/modules/json/src/main/scala/io/renku/json/codecs/DateTimeDecoders.scala @@ -16,7 +16,7 @@ * limitations under the License. */ -package io.renku.search.borer.codecs +package io.renku.json.codecs import java.time.Instant import java.time.format.DateTimeParseException diff --git a/modules/commons/src/main/scala/io/renku/search/borer/codecs/DateTimeEncoders.scala b/modules/json/src/main/scala/io/renku/json/codecs/DateTimeEncoders.scala similarity index 96% rename from modules/commons/src/main/scala/io/renku/search/borer/codecs/DateTimeEncoders.scala rename to modules/json/src/main/scala/io/renku/json/codecs/DateTimeEncoders.scala index fddda8d3..b61906d3 100644 --- a/modules/commons/src/main/scala/io/renku/search/borer/codecs/DateTimeEncoders.scala +++ b/modules/json/src/main/scala/io/renku/json/codecs/DateTimeEncoders.scala @@ -16,7 +16,7 @@ * limitations under the License. */ -package io.renku.search.borer.codecs +package io.renku.json.codecs import java.time.Instant diff --git a/modules/commons/src/main/scala/io/renku/search/borer/codecs/all.scala b/modules/json/src/main/scala/io/renku/json/codecs/all.scala similarity index 95% rename from modules/commons/src/main/scala/io/renku/search/borer/codecs/all.scala rename to modules/json/src/main/scala/io/renku/json/codecs/all.scala index ae13cf7c..d64a531e 100644 --- a/modules/commons/src/main/scala/io/renku/search/borer/codecs/all.scala +++ b/modules/json/src/main/scala/io/renku/json/codecs/all.scala @@ -16,7 +16,7 @@ * limitations under the License. */ -package io.renku.search.borer.codecs +package io.renku.json.codecs trait all extends DateTimeEncoders, DateTimeDecoders diff --git a/modules/solr-client/src/test/scala/io/renku/solr/client/JsonEncodingSpec.scala b/modules/json/src/test/scala/io/renku/json/JsonEncodingSpec.scala similarity index 96% rename from modules/solr-client/src/test/scala/io/renku/solr/client/JsonEncodingSpec.scala rename to modules/json/src/test/scala/io/renku/json/JsonEncodingSpec.scala index d1026969..21be4fc5 100644 --- a/modules/solr-client/src/test/scala/io/renku/solr/client/JsonEncodingSpec.scala +++ b/modules/json/src/test/scala/io/renku/json/JsonEncodingSpec.scala @@ -16,13 +16,13 @@ * limitations under the License. */ -package io.renku.solr.client +package io.renku.json import io.bullet.borer.* import io.bullet.borer.derivation.MapBasedCodecs import io.bullet.borer.derivation.MapBasedCodecs.deriveDecoder import io.bullet.borer.derivation.key -import io.renku.solr.client.JsonEncodingSpec.{Animal, Room} +import io.renku.json.JsonEncodingSpec.{Animal, Room} import munit.FunSuite class JsonEncodingSpec extends FunSuite { diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala index 3a4735a6..e8821f7f 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala @@ -26,7 +26,7 @@ import io.bullet.borer.Decoder import io.bullet.borer.NullOptions.given import io.bullet.borer.derivation.MapBasedCodecs.deriveDecoder import io.bullet.borer.derivation.key -import io.renku.search.borer.codecs.DateTimeDecoders +import io.renku.json.codecs.DateTimeDecoders import io.renku.search.model.Visibility final private case class GitLabProject( diff --git a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/DocumentKind.scala b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/DocumentKind.scala index 196fe907..cf666711 100644 --- a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/DocumentKind.scala +++ b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/DocumentKind.scala @@ -20,8 +20,8 @@ package io.renku.search.solr.documents import io.bullet.borer.Decoder import io.bullet.borer.Encoder +import io.renku.json.EncoderSupport import io.renku.search.solr.schema.EntityDocumentSchema.Fields -import io.renku.solr.client.EncoderSupport enum DocumentKind: case FullEntity diff --git a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/EntityDocument.scala b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/EntityDocument.scala index 41c4cc7f..5e1872ba 100644 --- a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/EntityDocument.scala +++ b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/EntityDocument.scala @@ -21,11 +21,11 @@ package io.renku.search.solr.documents import io.bullet.borer.* import io.bullet.borer.NullOptions.given import io.bullet.borer.derivation.{MapBasedCodecs, key} +import io.renku.json.EncoderSupport import io.renku.search.model.* import io.renku.search.model.MemberRole.* import io.renku.search.solr.schema.EntityDocumentSchema.Fields -import io.renku.solr.client.EncoderSupport.* -import io.renku.solr.client.{DocVersion, EncoderSupport} +import io.renku.solr.client.DocVersion sealed trait EntityDocument extends SolrDocument: val score: Option[Double] diff --git a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/PartialEntityDocument.scala b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/PartialEntityDocument.scala index ea2b5910..02a48557 100644 --- a/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/PartialEntityDocument.scala +++ b/modules/search-solr-client/src/main/scala/io/renku/search/solr/documents/PartialEntityDocument.scala @@ -21,10 +21,11 @@ package io.renku.search.solr.documents import io.bullet.borer.* import io.bullet.borer.NullOptions.given import io.bullet.borer.derivation.{MapBasedCodecs, key} +import io.renku.json.EncoderSupport import io.renku.search.model.* import io.renku.search.solr.documents.{Group as GroupDocument, Project as ProjectDocument} import io.renku.search.solr.schema.EntityDocumentSchema.Fields as SolrField -import io.renku.solr.client.{DocVersion, EncoderSupport} +import io.renku.solr.client.DocVersion sealed trait PartialEntityDocument extends SolrDocument: def applyTo(e: EntityDocument): EntityDocument diff --git a/modules/solr-client/src/test/scala/io/renku/solr/client/SolrClientSpec.scala b/modules/solr-client/src/test/scala/io/renku/solr/client/SolrClientSpec.scala index 35dfc703..8f5fbd26 100644 --- a/modules/solr-client/src/test/scala/io/renku/solr/client/SolrClientSpec.scala +++ b/modules/solr-client/src/test/scala/io/renku/solr/client/SolrClientSpec.scala @@ -28,6 +28,7 @@ import cats.effect.IO import io.bullet.borer.derivation.MapBasedCodecs import io.bullet.borer.derivation.key import io.bullet.borer.{Decoder, Encoder, Reader} +import io.renku.json.EncoderSupport import io.renku.search.GeneratorSyntax.* import io.renku.solr.client.SolrClientSpec.CourseMember import io.renku.solr.client.SolrClientSpec.{Course, Room} From 4731c91e31c116e34ac93a936a0d1b050a1f4691 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Fri, 7 Jun 2024 10:57:46 +0200 Subject: [PATCH 5/5] Remove ducktape library While this library is very nice, our use case for it degraded too much. The library intends to help with conversions between case classes by creating code (via macros) that copies values based on their name. Exceptions to that can be written via custom transformations. In our code we had to write much more boilerplate, because only a small subset of all properties could be automatically derived. Many other attributes had to be explicitly set to their default value or computed. When using "just scala", default values don't need to be written and thus it saves more lines now. --- build.sbt | 1 - flake.nix | 3 +- .../io/renku/search/model/CreationDate.scala | 2 - .../io/renku/search/model/Description.scala | 2 - .../scala/io/renku/search/model/Email.scala | 2 - .../io/renku/search/model/Firstname.scala | 2 - .../main/scala/io/renku/search/model/Id.scala | 2 - .../io/renku/search/model/LastName.scala | 2 - .../scala/io/renku/search/model/Name.scala | 2 - .../io/renku/search/model/Repository.scala | 2 - .../scala/io/renku/search/model/Slug.scala | 2 - .../io/renku/search/model/Username.scala | 2 - .../cli/perftests/GitLabDocsCreator.scala | 92 ++++++++----------- .../search/cli/perftests/GitLabEntities.scala | 5 + project/Dependencies.scala | 5 - 15 files changed, 43 insertions(+), 83 deletions(-) diff --git a/build.sbt b/build.sbt index ffab3e42..a7dbb8ca 100644 --- a/build.sbt +++ b/build.sbt @@ -94,7 +94,6 @@ lazy val commons = project Dependencies.borer ++ Dependencies.catsCore ++ Dependencies.catsEffect ++ - Dependencies.ducktape ++ Dependencies.fs2Core ++ Dependencies.scodecBits ++ Dependencies.scribe, diff --git a/flake.nix b/flake.nix index db9ed5ba..9a9a64d5 100644 --- a/flake.nix +++ b/flake.nix @@ -95,6 +95,7 @@ NO_SOLR = "true"; NO_REDIS = "true"; DEV_CONTAINER = "rsdev-cnt"; + SBT_OPTS = "-Xmx2G"; buildInputs = commonPackages @@ -121,8 +122,8 @@ #don't start docker container for dbTests NO_SOLR = "true"; NO_REDIS = "true"; - DEV_VM = "rsdev-vm"; + SBT_OPTS = "-Xmx2G"; buildInputs = commonPackages diff --git a/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala b/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala index 438d8970..36097f2e 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/CreationDate.scala @@ -23,13 +23,11 @@ import java.time.Instant import cats.kernel.Order import io.bullet.borer.Codec -import io.github.arainko.ducktape.* import io.renku.json.codecs.all.given opaque type CreationDate = Instant object CreationDate: def apply(v: Instant): CreationDate = v extension (self: CreationDate) def value: Instant = self - given Transformer[Instant, CreationDate] = apply given Codec[CreationDate] = Codec.of[Instant] given Order[CreationDate] = Order.fromComparable[Instant] diff --git a/modules/commons/src/main/scala/io/renku/search/model/Description.scala b/modules/commons/src/main/scala/io/renku/search/model/Description.scala index 66a186a0..126ae917 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/Description.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Description.scala @@ -19,7 +19,6 @@ package io.renku.search.model import io.bullet.borer.Codec -import io.github.arainko.ducktape.Transformer opaque type Description = String object Description: @@ -32,5 +31,4 @@ object Description: } } extension (self: Description) def value: String = self - given Transformer[String, Description] = apply given Codec[Description] = Codec.of[String] diff --git a/modules/commons/src/main/scala/io/renku/search/model/Email.scala b/modules/commons/src/main/scala/io/renku/search/model/Email.scala index 6a8ca0a5..a7ecd8be 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/Email.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Email.scala @@ -19,11 +19,9 @@ package io.renku.search.model import io.bullet.borer.Codec -import io.github.arainko.ducktape.Transformer opaque type Email = String object Email: def apply(v: String): Email = v extension (self: Email) def value: String = self - given Transformer[String, Email] = apply given Codec[Email] = Codec.bimap[String, Email](_.value, Email.apply) diff --git a/modules/commons/src/main/scala/io/renku/search/model/Firstname.scala b/modules/commons/src/main/scala/io/renku/search/model/Firstname.scala index b1b86e59..1221c750 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/Firstname.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Firstname.scala @@ -19,11 +19,9 @@ package io.renku.search.model import io.bullet.borer.Codec -import io.github.arainko.ducktape.Transformer opaque type FirstName = String object FirstName: def apply(v: String): FirstName = v extension (self: FirstName) def value: String = self - given Transformer[String, FirstName] = apply given Codec[FirstName] = Codec.bimap[String, FirstName](_.value, FirstName.apply) diff --git a/modules/commons/src/main/scala/io/renku/search/model/Id.scala b/modules/commons/src/main/scala/io/renku/search/model/Id.scala index 12b10eb3..5dc5dec3 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/Id.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Id.scala @@ -21,12 +21,10 @@ package io.renku.search.model import cats.Show import io.bullet.borer.Codec -import io.github.arainko.ducktape.Transformer opaque type Id = String object Id: def apply(v: String): Id = v extension (self: Id) def value: String = self - given Transformer[String, Id] = apply given Codec[Id] = Codec.of[String] given Show[Id] = Show.show(_.value) diff --git a/modules/commons/src/main/scala/io/renku/search/model/LastName.scala b/modules/commons/src/main/scala/io/renku/search/model/LastName.scala index 18ba0dd0..e3eabb79 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/LastName.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/LastName.scala @@ -19,11 +19,9 @@ package io.renku.search.model import io.bullet.borer.Codec -import io.github.arainko.ducktape.Transformer opaque type LastName = String object LastName: def apply(v: String): LastName = v extension (self: LastName) def value: String = self - given Transformer[String, LastName] = apply given Codec[LastName] = Codec.bimap[String, LastName](_.value, LastName.apply) diff --git a/modules/commons/src/main/scala/io/renku/search/model/Name.scala b/modules/commons/src/main/scala/io/renku/search/model/Name.scala index 894e3198..c94af2d0 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/Name.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Name.scala @@ -19,11 +19,9 @@ package io.renku.search.model import io.bullet.borer.Codec -import io.github.arainko.ducktape.Transformer opaque type Name = String object Name: def apply(v: String): Name = v extension (self: Name) def value: String = self - given Transformer[String, Name] = apply given Codec[Name] = Codec.of[String] diff --git a/modules/commons/src/main/scala/io/renku/search/model/Repository.scala b/modules/commons/src/main/scala/io/renku/search/model/Repository.scala index eccd622e..8b161308 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/Repository.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Repository.scala @@ -21,12 +21,10 @@ package io.renku.search.model import cats.kernel.Order import io.bullet.borer.Codec -import io.github.arainko.ducktape.* opaque type Repository = String object Repository: def apply(v: String): Repository = v extension (self: Repository) def value: String = self - given Transformer[String, Repository] = apply given Codec[Repository] = Codec.of[String] given Order[Repository] = Order.fromComparable[String] diff --git a/modules/commons/src/main/scala/io/renku/search/model/Slug.scala b/modules/commons/src/main/scala/io/renku/search/model/Slug.scala index cce497fd..5ec1df3a 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/Slug.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Slug.scala @@ -21,12 +21,10 @@ package io.renku.search.model import cats.kernel.Order import io.bullet.borer.Codec -import io.github.arainko.ducktape.* opaque type Slug = String object Slug: def apply(v: String): Slug = v extension (self: Slug) def value: String = self - given Transformer[String, Slug] = apply given Codec[Slug] = Codec.of[String] given Order[Slug] = Order.fromComparable[String] diff --git a/modules/commons/src/main/scala/io/renku/search/model/Username.scala b/modules/commons/src/main/scala/io/renku/search/model/Username.scala index 8a21ae45..90399cd6 100644 --- a/modules/commons/src/main/scala/io/renku/search/model/Username.scala +++ b/modules/commons/src/main/scala/io/renku/search/model/Username.scala @@ -19,11 +19,9 @@ package io.renku.search.model import io.bullet.borer.Codec -import io.github.arainko.ducktape.Transformer opaque type Username = String object Username: def apply(v: String): Username = v extension (self: Username) def value: String = self - given Transformer[String, Username] = apply given Codec[Username] = Codec.bimap[String, Username](_.value, Username.apply) diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabDocsCreator.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabDocsCreator.scala index ad99dd07..984ed5c4 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabDocsCreator.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabDocsCreator.scala @@ -24,7 +24,7 @@ import fs2.Stream import fs2.io.net.Network import io.bullet.borer.Decoder -import io.github.arainko.ducktape.* +import io.renku.search.events.syntax.* import io.renku.search.http.HttpClientDsl import io.renku.search.http.borer.BorerEntityJsonCodec.given import io.renku.search.model.* @@ -62,7 +62,7 @@ private class GitLabDocsCreator[F[_]: Async: ModelTypesGenerators]( .takeWhile(_.nonEmpty) .flatMap(Stream.emits) .evalMap(gp => findProjectUsers(gp.id).compile.toList.map(_.distinct).tupleLeft(gp)) - .evalMap(toProject) + .map(toProject) .unNone private def getProjects(page: Int) = @@ -74,34 +74,26 @@ private class GitLabDocsCreator[F[_]: Async: ModelTypesGenerators]( ) client.expect[List[GitLabProject]](req) - private lazy val toProject - : ((GitLabProject, List[User])) => F[Option[(Project, List[User])]] = { - case (glProj, all @ user :: users) => - (glProj - .into[Project] - .transform( - Field.default(_.namespace), - Field.computed(_.keywords, _.tagsAndTopics.map(Keyword.apply)), - Field.default(_.version), - Field.computed(_.id, s => Id(s"gl_proj_${s.id}")), - Field.computed(_.slug, s => Slug(s.path_with_namespace)), - Field - .computed(_.repositories, s => Seq(Repository(s.http_url_to_repo))), - Field.computed(_.visibility, s => s.visibility), - Field.computed(_.createdBy, s => user.id), - Field.computed(_.creationDate, s => CreationDate(s.created_at)), - Field.default(_.owners), - Field.default(_.editors), - Field.default(_.viewers), - Field.default(_.groupOwners), - Field.default(_.groupEditors), - Field.default(_.groupViewers), - Field.default(_.members), - Field.default(_.score) - ) -> all).some.pure[F] - case (name, Nil) => - Option.empty.pure[F] - } + private def toProject( + glProj: GitLabProject, + users: List[User] + ): Option[(Project, List[User])] = + users match + case Nil => None + case creator :: all => + val p = Project( + id = Id(s"gl_proj_${glProj.id}"), + name = glProj.name.toName, + slug = glProj.path_with_namespace.toSlug, + repositories = Seq(glProj.http_url_to_repo.toRepository), + visibility = glProj.visibility, + description = glProj.description.map(_.toDescription), + createdBy = creator.id, + creationDate = glProj.created_at.toCreationDate, + keywords = glProj.tagsAndTopics.map(_.toKeyword), + namespace = glProj.namespace.toNamespace.some + ) + Some(p -> users) private def findProjectUsers(projectId: Int) = Stream @@ -120,32 +112,20 @@ private class GitLabDocsCreator[F[_]: Async: ModelTypesGenerators]( ) client.expect[List[GitLabProjectUser]](req) - private def toUser(glUser: GitLabProjectUser): User = - val firstAndLast = toFirstAndLast(glUser.name.trim) - glUser - .into[User] - .transform( - Field.computed(_.namespace, u => Namespace(u.username).some), - Field.default(_.version), - Field.computed(_.id, s => Id(s"gl_user_${s.id}")), - Field.computed( - _.firstName, - s => - firstAndLast.map(_._1).flatMap { - case v if v.value.trim.isBlank => None - case v => v.some - } - ), - Field.computed( - _.lastName, - s => - firstAndLast.map(_._2).flatMap { - case v if v.value.trim.isBlank => None - case v => v.some - } - ), - Field.default(_.score) - ) + private def toUser(u: GitLabProjectUser): User = + val firstAndLast = toFirstAndLast(u.name.trim) + User( + id = Id(s"gl_user_${u.id}"), + namespace = u.username.toNamespace.some, + firstName = firstAndLast.map(_._1).flatMap { + case v if v.value.trim.isBlank => None + case v => v.some + }, + lastName = firstAndLast.map(_._2).flatMap { + case v if v.value.trim.isBlank => None + case v => v.some + } + ) private lazy val apiV4 = gitLabUri / "api" / "v4" diff --git a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala index e8821f7f..a94f2bb5 100644 --- a/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala +++ b/modules/search-cli/src/main/scala/io/renku/search/cli/perftests/GitLabEntities.scala @@ -44,6 +44,11 @@ final private case class GitLabProject( lazy val tagsAndTopics: List[String] = (tagList ::: topics).distinct + lazy val namespace: String = + path_with_namespace.lastIndexOf('/') match + case n if n > 0 => path_with_namespace.drop(n) + case _ => path_with_namespace + private object GitLabProject extends DateTimeDecoders: given Decoder[GitLabProject] = deriveDecoder diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f8b8760a..f4436bbd 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -14,7 +14,6 @@ object Dependencies { val catsScalaCheck = "0.3.2" val ciris = "3.6.0" val decline = "2.4.1" - val ducktape = "0.2.2" val fs2 = "3.10.2" val http4s = "0.23.27" val http4sPrometheusMetrics = "0.24.6" @@ -91,10 +90,6 @@ object Dependencies { "com.monovore" %% "decline-effect" % V.decline ) - val ducktape = Seq( - "io.github.arainko" %% "ducktape" % V.ducktape - ) - val fs2Core = Seq( "co.fs2" %% "fs2-core" % V.fs2 )