diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/ArtifactResponse.scala b/modules/core/shared/src/main/scala/scaladex/core/api/ArtifactResponse.scala index c996558f1..f20646864 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/api/ArtifactResponse.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/api/ArtifactResponse.scala @@ -2,35 +2,17 @@ package scaladex.core.api import java.time.Instant -import scaladex.core.model.Artifact -import scaladex.core.model.Language -import scaladex.core.model.License -import scaladex.core.model.Platform -import scaladex.core.model.Project -import scaladex.core.model.SemanticVersion +import scaladex.core.model._ final case class ArtifactResponse( groupId: Artifact.GroupId, - artifactId: String, - version: SemanticVersion, + artifactId: Artifact.ArtifactId, + version: Version, artifactName: Artifact.Name, + binaryVersion: BinaryVersion, + language: Language, + platform: Platform, project: Project.Reference, releaseDate: Instant, - licenses: Seq[License], - language: Language, - platform: Platform + licenses: Seq[License] ) - -object ArtifactResponse { - def apply(artifact: Artifact): ArtifactResponse = ArtifactResponse( - artifact.groupId, - artifact.artifactId, - artifact.version, - artifact.artifactName, - artifact.projectRef, - artifact.releaseDate, - artifact.licenses.toSeq, - artifact.language, - artifact.platform - ) -} diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/AutocompletionParams.scala b/modules/core/shared/src/main/scala/scaladex/core/api/AutocompletionParams.scala index ee1410d5b..994bd5bdf 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/api/AutocompletionParams.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/api/AutocompletionParams.scala @@ -1,15 +1,14 @@ package scaladex.core.api -import scaladex.core.model.Project -import scaladex.core.model.UserState +import scaladex.core.model._ import scaladex.core.model.search.SearchParams import scaladex.core.model.search.Sorting case class AutocompletionParams( query: String, topics: Seq[String], - languages: Seq[String], - platforms: Seq[String], + languages: Seq[Language], + platforms: Seq[Platform], contributingSearch: Boolean, you: Boolean ) { diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/Endpoints.scala b/modules/core/shared/src/main/scala/scaladex/core/api/Endpoints.scala index 998efe142..3dd7cd3da 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/api/Endpoints.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/api/Endpoints.scala @@ -1,61 +1,155 @@ package scaladex.core.api -import scaladex.core.model.Artifact -import scaladex.core.model.Project +import endpoints4s.Validated +import scaladex.core.model._ trait Endpoints extends JsonSchemas with endpoints4s.algebra.Endpoints with endpoints4s.algebra.JsonEntitiesFromSchemas { + val v0 = None + val v1: Some[String] = Some("v1") + + private val root: Path[Unit] = path / "api" + private def api(version: Option[String]): Path[Unit] = version.fold(root)(version => root / version) + private val projectsPath: Path[Unit] = staticPathSegment("projects") + private val artifactsPath: Path[Unit] = staticPathSegment("artifacts") + + private val organizationSegment: Path[Project.Organization] = + segment[Project.Organization]("organization")(stringSegment.xmap(Project.Organization(_))(_.value)) + private val repositorySegment: Path[Project.Repository] = + segment[Project.Repository]("repository")(stringSegment.xmap(Project.Repository(_))(_.value)) + private val groupIdSegment: Path[Artifact.GroupId] = + segment[Artifact.GroupId]("groupId")(stringSegment.xmap(Artifact.GroupId(_))(_.value)) + private val artifactIdSegment: Path[Artifact.ArtifactId] = + segment[Artifact.ArtifactId]("artifactId")(stringSegment.xmap(Artifact.ArtifactId(_))(_.value)) + private val versionSegment: Path[Version] = segment[Version]("version")(stringSegment.xmap(Version(_))(_.value)) + private val projectPath: Path[Project.Reference] = - (segment[String]("organization") / segment[String]("repository")) - .xmap { case (org, repo) => Project.Reference.from(org, repo) }(ref => - (ref.organization.value, ref.repository.value) - ) - - private val artifactPath: Path[Artifact.MavenReference] = - (segment[String]("groupId") / segment[String]("artifactId") / segment[String]("version")) - .xmap { case (groupId, artifactId, version) => Artifact.MavenReference(groupId, artifactId, version) }(ref => - (ref.groupId, ref.artifactId, ref.version) - ) - - private val autocompletionQueryString: QueryString[AutocompletionParams] = ( + (projectsPath / organizationSegment / repositorySegment) + .xmap((Project.Reference.apply _).tupled)(Function.unlift(Project.Reference.unapply)) + + private val projectVersionsPath: Path[Project.Reference] = projectPath / "versions" + private val projectArtifactsPath: Path[Project.Reference] = projectPath / "artifacts" + + private val artifactPath: Path[Artifact.Reference] = + (artifactsPath / groupIdSegment / artifactIdSegment / versionSegment) + .xmap((Artifact.Reference.apply _).tupled)(Function.unlift(Artifact.Reference.unapply)) + + private implicit val platformQueryString: QueryStringParam[Platform] = stringQueryString + .xmapPartial(v => Validated.fromOption(Platform.parse(v))(s"Cannot parse $v"))(_.value) + private implicit val languageQueryString: QueryStringParam[Language] = stringQueryString + .xmapPartial(v => Validated.fromOption(Language.parse(v))(s"Cannot parse $v"))(_.value) + private implicit val binaryVersionQueryString: QueryStringParam[BinaryVersion] = stringQueryString + .xmapPartial(v => Validated.fromOption(BinaryVersion.parse(v))(s"Cannot parse $v"))(_.value) + + private val languageFilters = qs[Seq[Language]]( + "language", + qsDoc("Filter on language versions", Seq("3", "2.13", "2.12", "2.11", "java")) + ) + + private val platformFilters = qs[Seq[Platform]]( + "platform", + qsDoc("Filter on platform versions", Seq("jvm", "sjs1", "native0.5", "sbt1.0", "mill0.11")) + ) + + private val binaryVersionFilters: QueryString[Seq[BinaryVersion]] = + qs( + "binary-version", + qsDoc("Filter on binary versions", Seq("_2.13", "_3", "_sjs1_3", "_2.12_1.0", "_mill0.11_2.13")) + ) + + private val binaryVersionFilter: QueryString[Option[BinaryVersion]] = + qs[Option[BinaryVersion]]( + "binary-version", + qsDoc("Filter on binary version", Seq("_2.13", "_3", "_sjs1_3", "_2.12_1.0", "_mill0.11_2.13")) + ) + + private val artifactNameFilters = qs[Seq[String]]( + "artifact-name", + qsDoc("Filter on artifact names", Seq("cats-core", "cats-free")) + ).xmap(_.map(Artifact.Name.apply))(_.map(_.value)) + + private val artifactNameFilter = qs[Option[String]]( + "artifact-name", + qsDoc("Filter on artifact name", Seq("cats-core", "cats-free")) + ).xmap(_.map(Artifact.Name.apply))(_.map(_.value)) + + private val stableOnlyFilter = + qs[Option[Boolean]]("stable-only", Some("Keep only stable versions. (Default is true)")) + .xmap(o => o.getOrElse(true))(b => Option.when(!b)(b)) + + private val projectsParams: QueryString[ProjectsParams] = + (languageFilters & platformFilters) + .xmap((ProjectsParams.apply _).tupled)(Function.unlift(ProjectsParams.unapply)) + + private val projectVersionsParams: QueryString[ProjectVersionsParams] = + (binaryVersionFilters & artifactNameFilters & stableOnlyFilter) + .xmap((ProjectVersionsParams.apply _).tupled)(Function.unlift(ProjectVersionsParams.unapply)) + + private val projectArtifactsParams: QueryString[ProjectArtifactsParams] = + (binaryVersionFilter & artifactNameFilter & stableOnlyFilter) + .xmap((ProjectArtifactsParams.apply _).tupled)(Function.unlift(ProjectArtifactsParams.unapply)) + + private val autocompletionParams: QueryString[AutocompletionParams] = ( qs[String]("q", docs = Some("Main query (e.g., 'json', 'testing', etc.)")) & - qs[Seq[String]]("topics", docs = Some("Filter on Github topics")) & - qs[Seq[String]]( - "languages", - docs = Some("Filter on language versions (e.g., '3', '2.13', '2.12', '2.11', 'java')") - ) & - qs[Seq[String]]( - "platforms", - docs = Some("Filter on runtime platforms (e.g., 'jvm', 'sjs1', 'native0.4', 'sbt1.0')") - ) & + qs[Seq[String]]("topic", docs = Some("Filter on Github topics")) & + languageFilters & + platformFilters & qs[Option[Boolean]]("contributingSearch").xmap(_.getOrElse(false))(Option.when(_)(true)) & qs[Option[String]]("you", docs = Some("internal usage")).xmap[Boolean](_.contains("✓"))(Option.when(_)("✓")) ).xmap((AutocompletionParams.apply _).tupled)(Function.unlift(AutocompletionParams.unapply)) - val listProjects: Endpoint[Unit, Seq[Project.Reference]] = + def getProjects(v: Option[String]): Endpoint[ProjectsParams, Seq[Project.Reference]] = endpoint( - get(path / "api" / "projects"), + get(api(v) / projectsPath /? projectsParams), ok(jsonResponse[Seq[Project.Reference]]) ) - val listProjectArtifacts: Endpoint[Project.Reference, Seq[Artifact.MavenReference]] = + val getProjectV1: Endpoint[Project.Reference, Option[ProjectResponse]] = + endpoint(get(api(v1) / projectPath), ok(jsonResponse[ProjectResponse]).orNotFound()) + + val getProjectVersionsV1: Endpoint[(Project.Reference, ProjectVersionsParams), Seq[Version]] = + endpoint(get(api(v1) / projectVersionsPath /? projectVersionsParams), ok(jsonResponse[Seq[Version]])) + + val getLatestProjectVersionV1: Endpoint[Project.Reference, Seq[Artifact.Reference]] = + endpoint(get(api(v1) / projectVersionsPath / "latest"), ok(jsonResponse[Seq[Artifact.Reference]])) + + val getProjectVersionV1: Endpoint[(Project.Reference, Version), Seq[Artifact.Reference]] = + endpoint(get(api(v1) / projectVersionsPath / versionSegment), ok(jsonResponse[Seq[Artifact.Reference]])) + + def getProjectArtifacts( + v: Option[String] + ): Endpoint[(Project.Reference, ProjectArtifactsParams), Seq[Artifact.Reference]] = + endpoint( + get(api(v) / projectArtifactsPath /? projectArtifactsParams), + ok(jsonResponse[Seq[Artifact.Reference]]) + ) + + def getArtifactVersions( + v: Option[String] + ): Endpoint[(Artifact.GroupId, Artifact.ArtifactId, Boolean), Seq[Version]] = endpoint( - get(path / "api" / "projects" / projectPath / "artifacts"), - ok(jsonResponse[Seq[Artifact.MavenReference]]) + get(api(v) / artifactsPath / groupIdSegment / artifactIdSegment /? stableOnlyFilter), + ok(jsonResponse[Seq[Version]]) ) - val getArtifact: Endpoint[Artifact.MavenReference, Option[ArtifactResponse]] = + def getLatestArtifactV1: Endpoint[(Artifact.GroupId, Artifact.ArtifactId), Option[ArtifactResponse]] = endpoint( - get(path / "api" / "artifacts" / artifactPath), + get(api(v1) / artifactsPath / groupIdSegment / artifactIdSegment / "latest"), ok(jsonResponse[ArtifactResponse]).orNotFound() ) + def getArtifact(v: Option[String]): Endpoint[Artifact.Reference, Option[ArtifactResponse]] = + endpoint(get(api(v) / artifactPath), ok(jsonResponse[ArtifactResponse]).orNotFound()) + val autocomplete: Endpoint[AutocompletionParams, Seq[AutocompletionResponse]] = - endpoint( - get(path / "api" / "autocomplete" /? autocompletionQueryString), - ok(jsonResponse[Seq[AutocompletionResponse]]) + endpoint(get(root / "autocomplete" /? autocompletionParams), ok(jsonResponse[Seq[AutocompletionResponse]])) + + private def qsDoc(desc: String, examples: Seq[String]): Some[String] = + Some( + s"""|$desc. + |Examples: ${examples.mkString(", ")}""".stripMargin ) } diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/JsonSchemas.scala b/modules/core/shared/src/main/scala/scaladex/core/api/JsonSchemas.scala index c6a40e8c4..8f80c7028 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/api/JsonSchemas.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/api/JsonSchemas.scala @@ -2,53 +2,83 @@ package scaladex.core.api import java.time.Instant -import scaladex.core.model.Artifact -import scaladex.core.model.Language -import scaladex.core.model.License -import scaladex.core.model.Platform -import scaladex.core.model.Project -import scaladex.core.model.SemanticVersion +import endpoints4s.Validated +import scaladex.core.model._ /** * The Json schema of the Scaladex API */ trait JsonSchemas extends endpoints4s.algebra.JsonSchemas { + private val stringJson = stringJsonSchema(None) + + implicit val organizationSchema: JsonSchema[Project.Organization] = stringJson.xmap(Project.Organization(_))(_.value) + implicit val repositorySchema: JsonSchema[Project.Repository] = stringJson.xmap(Project.Repository(_))(_.value) + implicit val groupIdSchema: JsonSchema[Artifact.GroupId] = stringJson.xmap(Artifact.GroupId(_))(_.value) + implicit val artifactIdSchema: JsonSchema[Artifact.ArtifactId] = stringJson.xmap(Artifact.ArtifactId(_))(_.value) + implicit val versionSchema: JsonSchema[Version] = stringJson.xmap(Version(_))(_.value) + implicit val urlSchema: JsonSchema[Url] = stringJson.xmap(Url(_))(_.target) + implicit val licenseSchema: JsonSchema[License] = stringJson + .xmapPartial(l => Validated.fromOption(License.get(l))(s"Unknown license $l"))(_.shortName) + implicit val artifactNameSchema: JsonSchema[Artifact.Name] = stringJson.xmap(Artifact.Name(_))(_.value) + implicit val categorySchema: JsonSchema[Category] = stringJson + .xmapPartial(c => Validated.fromOption(Category.byLabel.get(c))(s"Unknown category $c"))(_.label) + implicit val binaryVersionSchema: JsonSchema[BinaryVersion] = stringJson + .xmapPartial(l => Validated.fromOption(BinaryVersion.parse(l))(s"Unknown binary version $l"))(_.value) + implicit val language: JsonSchema[Language] = stringJson + .xmapPartial(l => Validated.fromOption(Language.parse(l))(s"Unknown language $l"))(_.value) + implicit val platform: JsonSchema[Platform] = stringJson + .xmapPartial(l => Validated.fromOption(Platform.parse(l))(s"Unknown platform $l"))(_.value) + implicit val projectReferenceSchema: JsonSchema[Project.Reference] = - field[String]("organization") - .zip(field[String]("repository")) - .xmap(Function.tupled(Project.Reference.from(_, _)))(ref => (ref.organization.value, ref.repository.value)) - - implicit val mavenReferenceSchema: JsonSchema[Artifact.MavenReference] = - field[String]("groupId") - .zip(field[String]("artifactId")) - .zip(field[String]("version")) - .xmap(Function.tupled(Artifact.MavenReference.apply _))(Function.unlift(Artifact.MavenReference.unapply)) - - implicit val getArtifactResponseSchema: JsonSchema[ArtifactResponse] = - field[String]("groupId") - .xmap(Artifact.GroupId.apply)(_.value) - .zip(field[String]("artifactId")) - .zip(field[String]("version").xmap(SemanticVersion.from)(_.encode)) - .zip(field[String]("artifactName").xmap(Artifact.Name.apply)(_.value)) - .zip(field[String]("project").xmap(Project.Reference.from)(_.toString)) - .zip(field[Long]("releaseDate").xmap(Instant.ofEpochMilli)(_.toEpochMilli)) - .zip(field[Seq[String]]("licenses").xmap(_.flatMap(License.get))(_.map(_.shortName))) - .zip(field[String]("language").xmap(Language.fromLabel(_).get)(_.label)) - .zip(field[String]("platform").xmap(Platform.fromLabel(_).get)(_.label)) - .xmap { - case (groupId, artifactId, version, artifactName, project, releaseDate, licenses, language, platform) => - ArtifactResponse( - groupId, - artifactId, - version, - artifactName, - project, - releaseDate, - licenses, - language, - platform - ) - }(Function.unlift(ArtifactResponse.unapply)) + field[Project.Organization]("organization") + .zip(field[Project.Repository]("repository")) + .xmap { case (org, repo) => Project.Reference(org, repo) }(ref => (ref.organization, ref.repository)) + + implicit val artifactReferenceSchema: JsonSchema[Artifact.Reference] = + field[Artifact.GroupId]("groupId") + .zip(field[Artifact.ArtifactId]("artifactId")) + .zip(field[Version]("version")) + .xmap((Artifact.Reference.apply _).tupled)(Function.unlift(Artifact.Reference.unapply)) + + implicit val documentationPatternSchema: JsonSchema[DocumentationPattern] = + field[String]("label") + .zip(field[String]("pattern")) + .xmap { case (label, pattern) => DocumentationPattern(label, pattern) }(p => (p.label, p.pattern)) + + implicit val projectResponseSchema: JsonSchema[ProjectResponse] = + field[Project.Organization]("organization") + .zip(field[Project.Repository]("repository")) + .zip(optField[Url]("homepage")) + .zip(optField[String]("description")) + .zip(optField[Url]("logo")) + .zip(optField[Int]("stars")) + .zip(optField[Int]("forks")) + .zip(optField[Int]("issues")) + .zip(field[Set[String]]("topics")) + .zip(optField[Url]("contributingGuide")) + .zip(optField[Url]("codeOfConduct")) + .zip(optField[License]("license")) + .zip(optField[Artifact.Name]("defaultArtifact")) + .zip(optField[String]("customScalaDoc")) + .zip(field[Seq[DocumentationPattern]]("documentationLinks")) + .zip(field[Boolean]("contributorsWanted")) + .zip(field[Set[Artifact.Name]]("cliArtifacts")) + .zip(optField[Category]("category")) + .zip(optField[String]("chatroom")) + .xmap((ProjectResponse.apply _).tupled)(Function.unlift(ProjectResponse.unapply)) + + implicit val artifactResponseSchema: JsonSchema[ArtifactResponse] = + field[Artifact.GroupId]("groupId") + .zip(field[Artifact.ArtifactId]("artifactId")) + .zip(field[Version]("version")) + .zip(field[Artifact.Name]("name")) + .zip(field[BinaryVersion]("binaryVersion")) + .zip(field[Language]("language")) + .zip(field[Platform]("platform")) + .zip(field[Project.Reference]("project")) + .zip(field[Instant]("releaseDate")) + .zip(field[Seq[License]]("licenses")) + .xmap((ArtifactResponse.apply _).tupled)(Function.unlift(ArtifactResponse.unapply)) implicit val autocompletionResponseSchema: JsonSchema[AutocompletionResponse] = field[String]("organization") diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/ProjectArtifactsParams.scala b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectArtifactsParams.scala new file mode 100644 index 000000000..51221cd0c --- /dev/null +++ b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectArtifactsParams.scala @@ -0,0 +1,10 @@ +package scaladex.core.api + +import scaladex.core.model.Artifact +import scaladex.core.model.BinaryVersion + +final case class ProjectArtifactsParams( + binaryVersion: Option[BinaryVersion], + artifactName: Option[Artifact.Name], + stableOnly: Boolean +) diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/ProjectParams.scala b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectParams.scala deleted file mode 100644 index 5c59f7144..000000000 --- a/modules/core/shared/src/main/scala/scaladex/core/api/ProjectParams.scala +++ /dev/null @@ -1,3 +0,0 @@ -package scaladex.core.api - -final case class ProjectParams(language: Option[String], platform: Option[String]) diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/ProjectResponse.scala b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectResponse.scala new file mode 100644 index 000000000..5002e781b --- /dev/null +++ b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectResponse.scala @@ -0,0 +1,27 @@ +package scaladex.core.api + +import scaladex.core.model.Project._ +import scaladex.core.model.Url +import scaladex.core.model._ + +case class ProjectResponse( + organization: Organization, + repository: Repository, + homepage: Option[Url], + description: Option[String], + logo: Option[Url], + stars: Option[Int], + forks: Option[Int], + issues: Option[Int], + topics: Set[String], + contributingGuide: Option[Url], + codeOfConduct: Option[Url], + license: Option[License], + defaultArtifact: Option[Artifact.Name], + customScalaDoc: Option[String], + documentationLinks: Seq[DocumentationPattern], + contributorsWanted: Boolean, + cliArtifacts: Set[Artifact.Name], + category: Option[Category], + chatroom: Option[String] +) diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/ProjectVersionsParams.scala b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectVersionsParams.scala new file mode 100644 index 000000000..74b869b55 --- /dev/null +++ b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectVersionsParams.scala @@ -0,0 +1,10 @@ +package scaladex.core.api + +import scaladex.core.model.Artifact +import scaladex.core.model.BinaryVersion + +case class ProjectVersionsParams( + binaryVersions: Seq[BinaryVersion], + artifactNames: Seq[Artifact.Name], + stableOnly: Boolean +) diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/ProjectsParams.scala b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectsParams.scala new file mode 100644 index 000000000..5af8d9702 --- /dev/null +++ b/modules/core/shared/src/main/scala/scaladex/core/api/ProjectsParams.scala @@ -0,0 +1,5 @@ +package scaladex.core.api + +import scaladex.core.model._ + +case class ProjectsParams(languages: Seq[Language], platforms: Seq[Platform]) diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/Artifact.scala b/modules/core/shared/src/main/scala/scaladex/core/model/Artifact.scala index 8ca9f21f3..bf28efcfd 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/Artifact.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/Artifact.scala @@ -4,38 +4,34 @@ import java.time.Instant import java.time.ZoneOffset import java.time.format.DateTimeFormatter -import fastparse.P -import fastparse.Start import fastparse._ +import scaladex.core.api.ArtifactResponse +import scaladex.core.model.Artifact._ import scaladex.core.util.Parsers._ /** - * @param isNonStandardLib if not using artifactName_scalaVersion convention + * @param isNonStandardLib if not using name_scalaVersion convention */ case class Artifact( - groupId: Artifact.GroupId, - // artifactId is redundant with ArtifactName + platform - // it's kept because we need to do joins with ArtifactDependency on MavenReference - // It's also possible to create a new key for Artifact: ArtifactReference(GroupId, ArtifactId, SemanticVersion) - // and keep untyped MavenReference ArtifactDependency. - artifactId: String, - version: SemanticVersion, - artifactName: Artifact.Name, + groupId: GroupId, + artifactId: ArtifactId, + version: Version, projectRef: Project.Reference, description: Option[String], releaseDate: Instant, resolver: Option[Resolver], licenses: Set[License], isNonStandardLib: Boolean, - platform: Platform, - language: Language, - fullScalaVersion: Option[SemanticVersion], + fullScalaVersion: Option[Version], scaladocUrl: Option[Url], versionScheme: Option[String], developers: Seq[Contributor] = Seq.empty ) { - val binaryVersion: BinaryVersion = BinaryVersion(platform, language) - val mavenReference: Artifact.MavenReference = Artifact.MavenReference(groupId.value, artifactId, version.encode) + val reference: Reference = Reference(groupId, artifactId, version) + def binaryVersion: BinaryVersion = artifactId.binaryVersion + def language: Language = binaryVersion.language + def platform: Platform = binaryVersion.platform + def name: Artifact.Name = artifactId.name def isValid: Boolean = binaryVersion.isValid @@ -45,19 +41,17 @@ case class Artifact( case BinaryVersion(ScalaJs(_) | ScalaNative(_), _) => ":::" case _ => "::" } - s"$groupId$sep$artifactName" + s"$groupId$sep$name" } def releaseDateFormat: String = Artifact.dateFormatter.format(releaseDate) - def httpUrl: String = { - val binaryVersionQuery = s"?binaryVersion=${binaryVersion.encode}" - s"$artifactHttpPath/$version$binaryVersionQuery" - } + def httpUrl: String = s"$artifactHttpPath/$version?binary-version=${binaryVersion.value}" def badgeUrl(env: Env, platform: Option[Platform] = None): String = - s"${fullHttpUrl(env)}/latest-by-scala-version.svg?platform=${platform.map(_.label).getOrElse(binaryVersion.platform.label)}" + s"${fullHttpUrl(env)}/latest-by-scala-version.svg?platform=${platform.map(_.value).getOrElse(this.platform.value)}" + // TODO move this out def fullHttpUrl(env: Env): String = env match { case Env.Prod => s"https://index.scala-lang.org$artifactHttpPath" @@ -67,24 +61,24 @@ case class Artifact( s"http://localhost:8080$artifactHttpPath" // todo: fix locally } - private def artifactHttpPath: String = s"/${projectRef.organization}/${projectRef.repository}/$artifactName" + private def artifactHttpPath: String = s"/${projectRef.organization}/${projectRef.repository}/$name" def latestBadgeUrl(env: Env): String = s"${fullHttpUrl(env)}/latest.svg" def sbtInstall: Option[String] = { - val install = binaryVersion.platform match { - case SbtPlugin(_) => Some(s"""addSbtPlugin("$groupId" % "$artifactName" % "$version")""") + val install = platform match { + case SbtPlugin(_) => Some(s"""addSbtPlugin("$groupId" % "$name" % "$version")""") case MillPlugin(_) => None case _ if isNonStandardLib => Some(s"""libraryDependencies += "$groupId" % "$artifactId" % "$version"""") case ScalaJs(_) | ScalaNative(_) => - Some(s"""libraryDependencies += "$groupId" %%% "$artifactName" % "$version"""") + Some(s"""libraryDependencies += "$groupId" %%% "$name" % "$version"""") case Jvm => - binaryVersion.language match { - case Java => Some(s"""libraryDependencies += "$groupId" % "$artifactName" % "$version"""") - case Scala(PatchVersion(_, _, _)) => - Some(s"""libraryDependencies += "$groupId" % "$artifactName" % "$version" cross CrossVersion.full""") - case _ => Some(s"""libraryDependencies += "$groupId" %% "$artifactName" % "$version"""") + language match { + case Java => Some(s"""libraryDependencies += "$groupId" % "$name" % "$version"""") + case Scala(Version.Patch(_, _, _)) => + Some(s"""libraryDependencies += "$groupId" % "$name" % "$version" cross CrossVersion.full""") + case _ => Some(s"""libraryDependencies += "$groupId" %% "$name" % "$version"""") } } @@ -113,14 +107,14 @@ case class Artifact( | false) |interp.resolvers() = interp.resolvers() :+ res""".stripMargin - val install = binaryVersion.platform match { + val install = platform match { case MillPlugin(_) | SbtPlugin(_) | ScalaNative(_) | ScalaJs(_) => None case Jvm => - binaryVersion.language match { - case _ if isNonStandardLib => Some(s"import $$ivy.`$groupId:$artifactId:$version`") - case Java => Some(s"import $$ivy.`$groupId:$artifactId:$version`") - case Scala(PatchVersion(_, _, _)) => Some(s"import $$ivy.`$groupId:::$artifactName:$version`") - case _ => Some(s"import $$ivy.`$groupId::$artifactName:$version`") + language match { + case _ if isNonStandardLib => Some(s"import $$ivy.`$groupId:$artifactId:$version`") + case Java => Some(s"import $$ivy.`$groupId:$artifactId:$version`") + case Scala(Version.Patch(_, _, _)) => Some(s"import $$ivy.`$groupId:::$name:$version`") + case _ => Some(s"import $$ivy.`$groupId::$name:$version`") } } @@ -136,7 +130,7 @@ case class Artifact( * @return */ def mavenInstall: Option[String] = - binaryVersion.platform match { + platform match { case MillPlugin(_) | SbtPlugin(_) | ScalaNative(_) | ScalaJs(_) => None case Jvm => Some( @@ -153,7 +147,7 @@ case class Artifact( * @return */ def gradleInstall: Option[String] = - binaryVersion.platform match { + platform match { case MillPlugin(_) | SbtPlugin(_) | ScalaNative(_) | ScalaJs(_) => None case Jvm => Some(s"compile group: '$groupId', name: '$artifactId', version: '$version'") } @@ -163,16 +157,16 @@ case class Artifact( * @return */ def millInstall: Option[String] = { - val install = binaryVersion.platform match { - case MillPlugin(_) => Some(s"import $$ivy.`$groupId::$artifactName::$version`") + val install = platform match { + case MillPlugin(_) => Some(s"import $$ivy.`$groupId::$name::$version`") case SbtPlugin(_) => None - case ScalaNative(_) | ScalaJs(_) => Some(s"""ivy"$groupId::$artifactName::$version"""") + case ScalaNative(_) | ScalaJs(_) => Some(s"""ivy"$groupId::$name::$version"""") case Jvm => - binaryVersion.language match { - case _ if isNonStandardLib => Some(s"""ivy"$groupId:$artifactId:$version"""") - case Java => Some(s"""ivy"$groupId:$artifactId:$version"""") - case Scala(PatchVersion(_, _, _)) => Some(s"""ivy"$groupId:::$artifactName:$version"""") - case _ => Some(s"""ivy"$groupId::$artifactName:$version"""") + language match { + case _ if isNonStandardLib => Some(s"""ivy"$groupId:$artifactId:$version"""") + case Java => Some(s"""ivy"$groupId:$artifactId:$version"""") + case Scala(Version.Patch(_, _, _)) => Some(s"""ivy"$groupId:::$name:$version"""") + case _ => Some(s"""ivy"$groupId::$name:$version"""") } } (install, resolver.flatMap(_.url)) match { @@ -189,26 +183,26 @@ case class Artifact( def scalaCliInstall: Option[String] = binaryVersion.platform match { case MillPlugin(_) | SbtPlugin(_) => None - case ScalaNative(_) | ScalaJs(_) => Some(s"""//> using dep "$groupId::$artifactName::$version"""") + case ScalaNative(_) | ScalaJs(_) => Some(s"""//> using dep "$groupId::$name::$version"""") case Jvm => - binaryVersion.language match { - case _ if isNonStandardLib => Some(s"""//> using dep "$groupId:$artifactId:$version"""") - case Java => Some(s"""//> using dep "$groupId:$artifactId:$version"""") - case Scala(PatchVersion(_, _, _)) => Some(s"""//> using dep "$groupId:::$artifactName:$version"""") - case _ => Some(s"""//> using dep "$groupId::$artifactName:$version"""") + language match { + case _ if isNonStandardLib => Some(s"""//> using dep "$groupId:$artifactId:$version"""") + case Java => Some(s"""//> using dep "$groupId:$artifactId:$version"""") + case Scala(Version.Patch(_, _, _)) => Some(s"""//> using dep "$groupId:::$name:$version"""") + case _ => Some(s"""//> using dep "$groupId::$name:$version"""") } } def csLaunch: Option[String] = - binaryVersion.platform match { + platform match { case MillPlugin(_) | SbtPlugin(_) => None - case ScalaNative(_) | ScalaJs(_) => Some(s"cs launch $groupId::$artifactName::$version") + case ScalaNative(_) | ScalaJs(_) => Some(s"cs launch $groupId::$name::$version") case Jvm => - binaryVersion.language match { - case _ if isNonStandardLib => Some(s"cs launch $groupId:$artifactId:$version") - case Java => Some(s"cs launch $groupId:$artifactId:$version") - case Scala(PatchVersion(_, _, _)) => Some(s"cs launch $groupId:::$artifactName:$version") - case _ => Some(s"cs launch $groupId::$artifactName:$version") + language match { + case _ if isNonStandardLib => Some(s"cs launch $groupId:$artifactId:$version") + case Java => Some(s"cs launch $groupId:$artifactId:$version") + case Scala(Version.Patch(_, _, _)) => Some(s"cs launch $groupId:::$name:$version") + case _ => Some(s"cs launch $groupId::$name:$version") } } @@ -221,13 +215,13 @@ case class Artifact( def scastieURL: Option[String] = { val tryBaseUrl = "https://scastie.scala-lang.org/try" - val targetParam = binaryVersion.platform match { + val targetParam = platform match { case ScalaJs(_) => Some("t" -> "JS") case Jvm => Some("t" -> "JVM") case _ => None } - val scalaVersionParam = binaryVersion.language match { + val scalaVersionParam = language match { case Scala(v) => Some("sv" -> v.toString) case _ => None } @@ -238,8 +232,8 @@ case class Artifact( } yield { val params: List[(String, String)] = List( "g" -> groupId.value, - "a" -> artifactName.value, - "v" -> version.toString, + "a" -> name.value, + "v" -> version.value, "o" -> projectRef.organization.toString, "r" -> projectRef.repository.toString, target, @@ -249,6 +243,20 @@ case class Artifact( } } + + def toResponse: ArtifactResponse = + ArtifactResponse( + groupId, + artifactId, + version, + name, + binaryVersion, + language, + platform, + projectRef, + releaseDate, + licenses.toSeq + ) } object Artifact { @@ -265,8 +273,13 @@ object Artifact { override def toString: String = value def mavenUrl: String = value.replace('.', '/') } + object GroupId { + implicit val groupIdOrdering: Ordering[GroupId] = Ordering.by(_.value) + } + case class ArtifactId(name: Name, binaryVersion: BinaryVersion) { - def value: String = s"$name${binaryVersion.encode}" + override def toString = value + def value: String = s"$name${binaryVersion.asSuffix}" def isScala: Boolean = binaryVersion.language.isScala } @@ -283,25 +296,25 @@ object Artifact { ArtifactId(Name(name), binaryVersion) } - def parse(artifactId: String): Option[ArtifactId] = - tryParse(artifactId, x => FullParser(x)) + def apply(artifactId: String): ArtifactId = + tryParse(artifactId, x => FullParser(x)).getOrElse(ArtifactId(Name(artifactId), BinaryVersion(Jvm, Java))) } - case class MavenReference(groupId: String, artifactId: String, version: String) { + case class Reference(groupId: GroupId, artifactId: ArtifactId, version: Version) { override def toString(): String = s"$groupId:$artifactId:$version" - /** - * url to maven page with related information to this reference - */ + def name: Name = artifactId.name + def binaryVersion: BinaryVersion = artifactId.binaryVersion + def searchUrl: String = s"https://search.maven.org/#artifactdetails|$groupId|$artifactId|$version|jar" def repoUrl: String = - s"https://repo1.maven.org/maven2/${groupId.replace('.', '/')}/$artifactId/$version/" + s"https://repo1.maven.org/maven2/${groupId.value.replace('.', '/')}/$artifactId/$version/" } - object MavenReference { - def apply(groupId: GroupId, artifactId: String, version: SemanticVersion): MavenReference = - MavenReference(groupId.value, artifactId, version.encode) + object Reference { + def from(groupId: String, artifactId: String, version: String): Reference = + Reference(GroupId(groupId), ArtifactId(artifactId), Version(version)) } } diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/ArtifactDependency.scala b/modules/core/shared/src/main/scala/scaladex/core/model/ArtifactDependency.scala index d4faa1e5e..cde101be5 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/ArtifactDependency.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/ArtifactDependency.scala @@ -7,8 +7,8 @@ package scaladex.core.model * @param target the maven reference of one of the dependant libraries of the source, ex: doobie */ case class ArtifactDependency( - source: Artifact.MavenReference, - target: Artifact.MavenReference, + source: Artifact.Reference, + target: Artifact.Reference, scope: ArtifactDependency.Scope ) @@ -24,7 +24,7 @@ object ArtifactDependency { case None => s"${artifactDep.target.groupId}:${artifactDep.target.artifactId}" } - val version: String = artifactDep.target.version + val version: Version = artifactDep.target.version def isInternal(ref: Project.Reference): Boolean = target.exists(_.projectRef == ref) @@ -40,7 +40,7 @@ object ArtifactDependency { ) { def url: String = source.httpUrl def groupIdAndName: String = source.groupIdAndName - def version: SemanticVersion = source.version + def version: Version = source.version } object Reverse { diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/ArtifactSelection.scala b/modules/core/shared/src/main/scala/scaladex/core/model/ArtifactSelection.scala index f3d77cfa5..ffaa77aa7 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/ArtifactSelection.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/ArtifactSelection.scala @@ -4,24 +4,24 @@ case class ArtifactSelection( binaryVersion: Option[BinaryVersion], artifactNames: Option[Artifact.Name] ) { - private def filterAll(artifact: Artifact): Boolean = - binaryVersion.forall(_ == artifact.binaryVersion) && artifactNames.forall(_ == artifact.artifactName) + private def filterAll(artifact: Artifact.Reference): Boolean = + binaryVersion.forall(_ == artifact.binaryVersion) && artifactNames.forall(_ == artifact.name) - def defaultArtifact(artifacts: Seq[Artifact], project: Project): Option[Artifact] = { + def defaultArtifact(artifacts: Seq[Artifact.Reference], project: Project): Option[Artifact.Reference] = { val filteredArtifacts = artifacts.view.filter(filterAll) filteredArtifacts.maxByOption { artifact => ( // default artifact (ex: akka-actors is the default for akka/akka) - project.settings.defaultArtifact.contains(artifact.artifactName), + project.settings.defaultArtifact.contains(artifact.name), // not deprecated - !project.settings.deprecatedArtifacts.contains(artifact.artifactName), + !project.settings.deprecatedArtifacts.contains(artifact.name), // project repository (ex: shapeless) - project.repository.value == artifact.artifactName.value, + project.repository.value == artifact.name.value, // alphabetically - artifact.artifactName, + artifact.name, // stable version first - project.settings.preferStableVersion && artifact.version.preRelease.isDefined, + project.settings.preferStableVersion && !artifact.version.isStable, artifact.version, artifact.binaryVersion ) @@ -32,27 +32,27 @@ case class ArtifactSelection( Ordering[Boolean], Ordering[Artifact.Name].reverse, Ordering[Boolean].reverse, - Ordering[SemanticVersion], + Ordering[Version], Ordering[BinaryVersion] ) ) } - def filterArtifacts(artifacts: Seq[Artifact], project: Project): Seq[Artifact] = + def filterArtifacts(artifacts: Seq[Artifact.Reference], project: Project): Seq[Artifact.Reference] = artifacts .filter(filterAll) .sortBy { artifact => ( // default artifact (ex: akka-actors is the default for akka/akka) - project.settings.defaultArtifact.contains(artifact.artifactName), + project.settings.defaultArtifact.contains(artifact.name), // not deprecated - !project.settings.deprecatedArtifacts.contains(artifact.artifactName), + !project.settings.deprecatedArtifacts.contains(artifact.name), // project repository (ex: shapeless) - project.repository.value == artifact.artifactName.value, + project.repository.value == artifact.name.value, // alphabetically - artifact.artifactName, + artifact.name, // stable version first - project.settings.preferStableVersion && artifact.version.preRelease.isDefined, + project.settings.preferStableVersion && !artifact.version.isStable, artifact.version, artifact.binaryVersion ) @@ -64,7 +64,7 @@ case class ArtifactSelection( Ordering[Boolean], Ordering[Artifact.Name].reverse, Ordering[Boolean].reverse, - Ordering[SemanticVersion], + Ordering[Version], Ordering[BinaryVersion] ) .reverse diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/BinaryVersion.scala b/modules/core/shared/src/main/scala/scaladex/core/model/BinaryVersion.scala index 261001cc8..a43c6bb8d 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/BinaryVersion.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/BinaryVersion.scala @@ -6,17 +6,21 @@ import scaladex.core.util.Parsers final case class BinaryVersion(platform: Platform, language: Language) { def isValid: Boolean = platform.isValid && language.isValid - def encode: String = (platform, language) match { - case (Jvm, Java) => "" - case (Jvm, Scala(sv)) => s"_${sv.encode}" - case (SbtPlugin(sbtV), Scala(sv)) => s"_${sv.encode}_${sbtV.encode}" - case (platform, Scala(sv)) => s"_${platform.label}_${sv.encode}" - case (platform, Java) => s"_${platform.label}" + + // possibly empty + def asSuffix: String = (platform, language) match { + case (Jvm, Java) => "" + case _ => value } - def label: String = (platform, language) match { - case (Jvm, Java) => "Java" - case _ => encode + + // non-empty + def value: String = (platform, language) match { + case (Jvm, Java) => language.value + case (Jvm, Scala(sv)) => s"_${sv.value}" + case (SbtPlugin(sbtV), Scala(sv)) => s"_${sv.value}_${sbtV.value}" + case (platform, language) => s"_${platform.value}_${language.value}" } + override def toString: String = (platform, language) match { case (Jvm, Java) => "Java" case (Jvm, Scala(version)) => s"Scala $version" @@ -27,10 +31,10 @@ final case class BinaryVersion(platform: Platform, language: Language) { object BinaryVersion { implicit val ordering: Ordering[BinaryVersion] = Ordering.by(v => (v.platform, v.language)) - def IntermediateParser[A: P]: P[(String, Option[SemanticVersion], Option[SemanticVersion])] = - ("_sjs" | "_native" | "_mill" | "_" | "").! ~ (SemanticVersion.Parser.?) ~ ("_" ~ SemanticVersion.Parser).? + def IntermediateParser[A: P]: P[(String, Option[Version], Option[Version])] = + ("_sjs" | "_native" | "_mill" | "_" | "").! ~ (Version.SemanticParser.?) ~ ("_" ~ Version.SemanticParser).? - def IntermediateParserButNotInvalidSbt[A: P]: P[(String, Option[SemanticVersion], Option[SemanticVersion])] = + def IntermediateParserButNotInvalidSbt[A: P]: P[(String, Option[Version], Option[Version])] = IntermediateParser.filter { case ("_", Some(scalaV), Some(sbtV)) => BinaryVersion(SbtPlugin(sbtV), Scala(scalaV)).isValid case _ => true @@ -54,10 +58,9 @@ object BinaryVersion { def FullParser[A: P]: P[BinaryVersion] = Parser ~ End - def parse(input: String): Option[BinaryVersion] = Parsers.tryParse(input, x => FullParser(x)) - - def fromLabel(label: String): Option[BinaryVersion] = label match { - case "Java" => Some(BinaryVersion(Jvm, Java)) - case _ => parse(label) - } + def parse(input: String): Option[BinaryVersion] = + input match { + case "java" => Some(BinaryVersion(Jvm, Java)) + case _ => Parsers.tryParse(input, x => FullParser(x)) + } } diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/DocumentationPattern.scala b/modules/core/shared/src/main/scala/scaladex/core/model/DocumentationPattern.scala index dca67ddc7..51fd37f10 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/DocumentationPattern.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/DocumentationPattern.scala @@ -12,13 +12,21 @@ final case class DocumentationPattern(label: String, pattern: String) { * https://playframework.com/documentation/[major].[minor].x/Home */ def eval(artifact: Artifact): LabeledLink = { + def major: String = artifact.version match { + case v: Version.SemanticLike => v.major.toString + case v: Version.Custom => v.value.toString + } + def minor: String = artifact.version match { + case Version.SemanticLike(_, Some(v), _, _, _, _) => v.toString + case _ => "" + } val link = pattern .replace("[groupId]", artifact.groupId.value) - .replace("[artifactId]", artifact.artifactId) - .replace("[version]", artifact.version.toString) - .replace("[major]", artifact.version.major.toString) - .replace("[minor]", artifact.version.minor.toString) - .replace("[name]", artifact.artifactName.value) + .replace("[artifactId]", artifact.artifactId.value) + .replace("[version]", artifact.version.value) + .replace("[major]", major) + .replace("[minor]", minor) + .replace("[name]", artifact.name.value) LabeledLink(label, link) } } diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/Language.scala b/modules/core/shared/src/main/scala/scaladex/core/model/Language.scala index 6c9ae6573..354676603 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/Language.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/Language.scala @@ -1,6 +1,9 @@ package scaladex.core.model sealed trait Language { + // a string value for serialization/deserialization + def value: String + // a short version of toString def label: String def isValid: Boolean def isJava: Boolean = this match { @@ -15,41 +18,42 @@ sealed trait Language { object Language { implicit val ordering: Ordering[Language] = Ordering.by { - case Java => MajorVersion(Int.MinValue) + case Java => Version(Int.MinValue) case Scala(v) => v } - def fromLabel(label: String): Option[Language] = label match { - case "Java" => Some(Java) - case s"$sv.x" => SemanticVersion.parse(sv).map(Scala.apply) - case sv => SemanticVersion.parse(sv).map(Scala.apply) + def parse(value: String): Option[Language] = value match { + case "java" => Some(Java) + case sv => Version.parseSemantically(sv).map(Scala.apply) } } case object Java extends Language { + override def value: String = "java" override def label: String = toString override def isValid: Boolean = true } -final case class Scala(version: SemanticVersion) extends Language { +final case class Scala(version: Version) extends Language { + override def value: String = version.value override def label: String = version.toString override def isValid: Boolean = Scala.stableVersions.contains(this) override def toString: String = s"Scala $version" } object Scala { - val `2.10`: Scala = Scala(MinorVersion(2, 10)) - val `2.11`: Scala = Scala(MinorVersion(2, 11)) - val `2.12`: Scala = Scala(MinorVersion(2, 12)) - val `2.13`: Scala = Scala(MinorVersion(2, 13)) - val `3`: Scala = Scala(MajorVersion(3)) + val `2.10`: Scala = Scala(Version(2, 10)) + val `2.11`: Scala = Scala(Version(2, 11)) + val `2.12`: Scala = Scala(Version(2, 12)) + val `2.13`: Scala = Scala(Version(2, 13)) + val `3`: Scala = Scala(Version(3)) val stableVersions: Set[Scala] = Set(`2.10`, `2.11`, `2.12`, `2.13`, `3`) - def fromFullVersion(fullVersion: SemanticVersion): Scala = { + def fromFullVersion(fullVersion: Version): Scala = { val binaryVersion = fullVersion match { - case SemanticVersion(major, Some(minor), _, _, _, _) => - if (major > 3) MajorVersion(major) else MinorVersion(major, minor) + case Version.SemanticLike(major, Some(minor), _, _, _, _) => + if (major > 3) Version(major) else Version(major, minor) case _ => fullVersion } Scala(binaryVersion) diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/Platform.scala b/modules/core/shared/src/main/scala/scaladex/core/model/Platform.scala index e64075c11..d968b4bb6 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/Platform.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/Platform.scala @@ -1,78 +1,78 @@ package scaladex.core.model sealed trait Platform { - def label: String + def value: String def isValid: Boolean } case object Jvm extends Platform { override def toString: String = "JVM" - override def label: String = "jvm" + override def value: String = "jvm" override def isValid: Boolean = true } -case class ScalaJs(version: SemanticVersion) extends Platform { +case class ScalaJs(version: Version) extends Platform { override def toString: String = s"Scala.js $version" - override def label: String = s"sjs${version.encode}" + override def value: String = s"sjs${version.value}" override def isValid: Boolean = ScalaJs.stableVersions.contains(this) } object ScalaJs { - val `0.6`: ScalaJs = ScalaJs(MinorVersion(0, 6)) - val `1.x`: ScalaJs = ScalaJs(MajorVersion(1)) + val `0.6`: ScalaJs = ScalaJs(Version(0, 6)) + val `1.x`: ScalaJs = ScalaJs(Version(1)) val stableVersions: Set[ScalaJs] = Set(`0.6`, `1.x`) implicit val ordering: Ordering[ScalaJs] = Ordering.by(p => p.asInstanceOf[Platform]) } -case class SbtPlugin(version: SemanticVersion) extends Platform { +case class SbtPlugin(version: Version) extends Platform { override def toString: String = version match { - case MinorVersion(1, 0) => s"sbt 1.x" - case _ => s"sbt $version" + case Version.Minor(1, 0) => s"sbt 1.x" + case _ => s"sbt $version" } - override def label: String = s"sbt${version.encode}" + override def value: String = s"sbt${version.value}" override def isValid: Boolean = SbtPlugin.stableVersions.contains(this) } object SbtPlugin { - val `0.13`: SbtPlugin = SbtPlugin(MinorVersion(0, 13)) - val `1.0`: SbtPlugin = SbtPlugin(MinorVersion(1, 0)) + val `0.13`: SbtPlugin = SbtPlugin(Version(0, 13)) + val `1.0`: SbtPlugin = SbtPlugin(Version(1, 0)) val stableVersions: Set[SbtPlugin] = Set(`0.13`, `1.0`) implicit val ordering: Ordering[SbtPlugin] = Ordering.by(p => p.asInstanceOf[Platform]) } -case class ScalaNative(version: SemanticVersion) extends Platform { +case class ScalaNative(version: Version) extends Platform { override def toString: String = s"Scala Native $version" - override def label: String = s"native${version.encode}" + override def value: String = s"native${version.value}" override def isValid: Boolean = ScalaNative.stableVersions.contains(this) } object ScalaNative { - val `0.3`: ScalaNative = ScalaNative(MinorVersion(0, 3)) - val `0.4`: ScalaNative = ScalaNative(MinorVersion(0, 4)) - val `0.5`: ScalaNative = ScalaNative(MinorVersion(0, 5)) + val `0.3`: ScalaNative = ScalaNative(Version(0, 3)) + val `0.4`: ScalaNative = ScalaNative(Version(0, 4)) + val `0.5`: ScalaNative = ScalaNative(Version(0, 5)) val stableVersions: Set[ScalaNative] = Set(`0.3`, `0.4`, `0.5`) implicit val ordering: Ordering[ScalaNative] = Ordering.by(p => p.asInstanceOf[Platform]) } -case class MillPlugin(version: SemanticVersion) extends Platform { +case class MillPlugin(version: Version) extends Platform { override def toString: String = s"Mill $version" - override def label: String = s"mill${version.encode}" + override def value: String = s"mill${version.value}" override def isValid: Boolean = version match { - case MinorVersion(_, _) => true - case _ => false + case Version.Minor(_, _) => true + case _ => false } } object MillPlugin { - val `0.10` = MillPlugin(MinorVersion(0, 10)) + val `0.10` = MillPlugin(Version(0, 10)) implicit val ordering: Ordering[MillPlugin] = Ordering.by(p => p.asInstanceOf[Platform]) } @@ -86,13 +86,13 @@ object Platform { case MillPlugin(version) => (1, Some(version)) } - def fromLabel(input: String): Option[Platform] = + def parse(input: String): Option[Platform] = input match { case "jvm" => Some(Jvm) - case s"sjs$version" => SemanticVersion.parse(version).map(ScalaJs.apply) - case s"native$version" => SemanticVersion.parse(version).map(ScalaNative.apply) - case s"sbt$version" => SemanticVersion.parse(version).map(SbtPlugin.apply) - case s"mill$version" => SemanticVersion.parse(version).map(MillPlugin.apply) + case s"sjs$version" => Version.parseSemantically(version).map(ScalaJs.apply) + case s"native$version" => Version.parseSemantically(version).map(ScalaNative.apply) + case s"sbt$version" => Version.parseSemantically(version).map(SbtPlugin.apply) + case s"mill$version" => Version.parseSemantically(version).map(MillPlugin.apply) case _ => None } } diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/Project.scala b/modules/core/shared/src/main/scala/scaladex/core/model/Project.scala index 598fdce44..d2d0b63e6 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/Project.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/Project.scala @@ -72,7 +72,7 @@ object Project { def from(org: String, repo: String): Reference = Reference(Organization(org), Repository(repo)) - def from(string: String): Reference = + def unsafe(string: String): Reference = string.split('/') match { case Array(org, repo) => from(org, repo) } diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/ProjectDependency.scala b/modules/core/shared/src/main/scala/scaladex/core/model/ProjectDependency.scala index be10ae95b..4100cf4bc 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/ProjectDependency.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/ProjectDependency.scala @@ -2,8 +2,8 @@ package scaladex.core.model case class ProjectDependency( source: Project.Reference, - sourceVersion: SemanticVersion, + sourceVersion: Version, target: Project.Reference, - targetVersion: SemanticVersion, + targetVersion: Version, scope: ArtifactDependency.Scope ) diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/ProjectHeader.scala b/modules/core/shared/src/main/scala/scaladex/core/model/ProjectHeader.scala index 345c9f3dc..2632b3431 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/ProjectHeader.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/ProjectHeader.scala @@ -23,14 +23,14 @@ final case class ProjectHeader( preferStableVersion: Boolean ) { lazy val defaultArtifact: Artifact = getDefaultArtifact(None, None) - lazy val latestVersion: SemanticVersion = defaultArtifact.version + lazy val latestVersion: Version = defaultArtifact.version lazy val latestArtifacts: Seq[Artifact] = artifacts.filter(_.version == latestVersion) lazy val latestLanguages: Seq[Language] = latestArtifacts.map(_.language).distinct.sorted lazy val latestPlatforms: Seq[Platform] = latestArtifacts.map(_.platform).distinct.sorted - def allArtifactNames: Seq[Artifact.Name] = artifacts.map(_.artifactName).distinct.sorted + def allArtifactNames: Seq[Artifact.Name] = artifacts.map(_.name).distinct.sorted def platforms(artifactName: Artifact.Name): Seq[Platform] = - artifacts.filter(_.artifactName == artifactName).map(_.platform).distinct.sorted(Platform.ordering.reverse) + artifacts.filter(_.name == artifactName).map(_.platform).distinct.sorted(Platform.ordering.reverse) def versionsUrl: String = artifactsUrl(getDefaultArtifact(None, None), withBinaryVersion = false) @@ -39,11 +39,11 @@ final case class ProjectHeader( def versionsUrl(platform: Platform): String = artifactsUrl(getDefaultArtifact(None, Some(platform))) private def artifactsUrl(defaultArtifact: Artifact, withBinaryVersion: Boolean = true): String = { - val preReleaseFilter = Option.when(preferStableVersion && defaultArtifact.version.isStable)("stable-only=true") - val binaryVersionFilter = Option.when(withBinaryVersion)(s"binary-versions=${defaultArtifact.binaryVersion.label}") + val preReleaseFilter = Option.when(preferStableVersion && defaultArtifact.version.isStable)("stableOnly=true") + val binaryVersionFilter = Option.when(withBinaryVersion)(s"binary-version=${defaultArtifact.binaryVersion.value}") val filters = preReleaseFilter.toSeq ++ binaryVersionFilter val queryParams = if (filters.nonEmpty) "?" + filters.mkString("&") else "" - s"/$ref/artifacts/${defaultArtifact.artifactName}$queryParams" + s"/$ref/artifacts/${defaultArtifact.name}$queryParams" } def getDefaultArtifact(language: Option[Language], platform: Option[Platform]): Artifact = { @@ -53,13 +53,13 @@ final case class ProjectHeader( def byName(artifacts: Seq[Artifact]): Option[Artifact] = defaultArtifactName.toSeq - .flatMap(defaultName => artifacts.filter(a => defaultName == a.artifactName)) + .flatMap(defaultName => artifacts.filter(a => defaultName == a.name)) .maxByOption(a => (a.binaryVersion, a.releaseDate)) - def ofVersion(version: SemanticVersion): Artifact = + def ofVersion(version: Version): Artifact = filteredArtifacts .filter(_.version == version) - .maxBy(a => (a.binaryVersion, a.artifactName, a.releaseDate))( + .maxBy(a => (a.binaryVersion, a.name, a.releaseDate))( Ordering.Tuple3(Ordering[BinaryVersion], Ordering[Artifact.Name].reverse, Ordering[Instant]) ) diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/SemanticVersion.scala b/modules/core/shared/src/main/scala/scaladex/core/model/SemanticVersion.scala deleted file mode 100644 index 84802f897..000000000 --- a/modules/core/shared/src/main/scala/scaladex/core/model/SemanticVersion.scala +++ /dev/null @@ -1,135 +0,0 @@ -package scaladex.core.model - -import fastparse.NoWhitespace._ -import fastparse._ -import scaladex.core.util.Parsers._ - -/** - * Semantic version, separation of possible combinations - * - * @param major the major version number - * @param minor the minor version number - * @param patch the patch version number - * @param patch2 the second patch version number (to support a.b.c.d) - * @param preRelease the pre release name - * @param metadata the release metadata - */ -case class SemanticVersion( - major: Int, - minor: Option[Int] = None, - patch: Option[Int] = None, - patch2: Option[Int] = None, - preRelease: Option[PreRelease] = None, - metadata: Option[String] = None -) extends Ordered[SemanticVersion] { - - def isSemantic: Boolean = - patch.isDefined && - patch2.isEmpty && - preRelease.forall(_.isSemantic) - - def isPreRelease: Boolean = - preRelease.isDefined || metadata.isDefined - - def isStable: Boolean = - isSemantic && !isPreRelease - - override def toString: String = this match { - case MajorVersion(major) => s"$major.x" - case _ => encode - } - - def encode: String = { - val minorPart = minor.map(m => s".$m").getOrElse("") - val patchPart = patch.map(p => s".$p").getOrElse("") - val patch2Part = patch2.map(p2 => s".$p2").getOrElse("") - val preReleasePart = preRelease.map(r => s"-$r").getOrElse("") - val metadataPart = metadata.map(m => s"+$m").getOrElse("") - s"$major$minorPart$patchPart$patch2Part$preReleasePart$metadataPart" - } - - override def compare(that: SemanticVersion): Int = - SemanticVersion.ordering.compare(this, that) -} - -object MajorVersion { - def apply(major: Int): SemanticVersion = SemanticVersion(major) - - def unapply(version: SemanticVersion): Option[Int] = version match { - case SemanticVersion(major, None, None, None, None, None) => Some(major) - case _ => None - } -} - -object MinorVersion { - def apply(major: Int, minor: Int): SemanticVersion = SemanticVersion(major, Some(minor)) - - def unapply(version: SemanticVersion): Option[(Int, Int)] = version match { - case SemanticVersion(major, Some(minor), None, None, None, None) => Some((major, minor)) - case _ => None - } -} - -object PatchVersion { - def apply(major: Int, minor: Int, patch: Int): SemanticVersion = SemanticVersion(major, Some(minor), Some(patch)) - - def unapply(version: SemanticVersion): Option[(Int, Int, Int)] = version match { - case SemanticVersion(major, Some(minor), Some(patch), None, None, None) => Some((major, minor, patch)) - case _ => None - } -} - -object PreReleaseVersion { - def apply(major: Int, minor: Int, patch: Int, preRelease: PreRelease): SemanticVersion = - SemanticVersion(major, Some(minor), Some(patch), preRelease = Some(preRelease)) - - def unapply(version: SemanticVersion): Option[(Int, Int, Int, PreRelease)] = version match { - case SemanticVersion(major, Some(minor), Some(patch), None, Some(preRelease), None) => - Some((major, minor, patch, preRelease)) - case _ => None - } -} - -object SemanticVersion { - implicit val ordering: Ordering[SemanticVersion] = Ordering.by { - case SemanticVersion(major, minor, patch, patch2, preRelease, metadata) => - ( - major, - minor.getOrElse(Int.MaxValue), - patch.getOrElse(Int.MaxValue), - patch2.getOrElse(Int.MaxValue), - preRelease, - metadata - ) - } - - // We prefer the latest stable artifact. - val PreferStable: Ordering[SemanticVersion] = - Ordering.by[SemanticVersion, Boolean](_.isStable).orElse(ordering) - - private def MajorP[A: P]: P[Int] = Number - - // http://semver.org/#spec-item-10 - private def MetaDataP[A: P] = "+" ~ AnyChar.rep.! - - private def MinorP[A: P] = ("." ~ Number).? // not really valid SemVer - private def PatchP[A: P] = ("." ~ Number).? // not really valid SemVer - private def Patch2P[A: P] = ("." ~ Number).? // not really valid SemVer - - def Parser[A: P]: P[SemanticVersion] = - ("v".? ~ MajorP ~ MinorP ~ PatchP ~ Patch2P ~ ("-" ~ PreRelease.Parser).? ~ MetaDataP.?) - .map { - case (major, minor, patch, patch2, preRelease, metadata) => - SemanticVersion(major, minor, patch, patch2, preRelease, metadata) - } - - private def FullParser[A: P]: P[SemanticVersion] = Start ~ Parser ~ End - - def parse(version: String): Option[SemanticVersion] = - fastparse.parse(version, x => FullParser(x)) match { - case Parsed.Success(v, _) => Some(v) - case _ => None - } - - def from(version: String): SemanticVersion = parse(version).get -} diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/Version.scala b/modules/core/shared/src/main/scala/scaladex/core/model/Version.scala new file mode 100644 index 000000000..59b444158 --- /dev/null +++ b/modules/core/shared/src/main/scala/scaladex/core/model/Version.scala @@ -0,0 +1,115 @@ +package scaladex.core.model + +import fastparse.NoWhitespace._ +import fastparse._ +import scaladex.core.util.Parsers._ + +sealed trait Version extends Ordered[Version] { + def value: String + def isSemantic: Boolean + + /** + * A stable semantic version, such as 2.0.3, 2.3 or 2 + */ + def isStable: Boolean + + /** + * A pre-release semantic version, such as 2.3.0-RC1 + */ + def isPreRelease: Boolean + + override def compare(that: Version): Int = Version.ordering.compare(this, that) + override def toString: String = this match { + case Version.Major(major) => s"$major.x" + case _ => value + } +} + +object Version { + implicit val ordering: Ordering[Version] = Ordering.by { + case Custom(value) => (Int.MinValue, Int.MinValue, Int.MinValue, Int.MinValue, None, Some(value)) + case SemanticLike(maj, min, p, p2, pr, m) => + (maj, min.getOrElse(Int.MaxValue), p.getOrElse(Int.MaxValue), p2.getOrElse(Int.MaxValue), pr, m) + } + + // We prefer the latest stable artifact. + val PreferStable: Ordering[Version] = + Ordering.by[Version, Boolean](_.isStable).orElse(ordering) + + final case class SemanticLike( + major: Int, + minor: Option[Int] = None, + patch: Option[Int] = None, + patch2: Option[Int] = None, + preRelease: Option[PreRelease] = None, + metadata: Option[String] = None + ) extends Version { + override def isSemantic: Boolean = + patch.isDefined && patch2.isEmpty && preRelease.forall(_.isSemantic) + + override def isPreRelease: Boolean = preRelease.isDefined || metadata.isDefined + + override def isStable: Boolean = isSemantic && !isPreRelease + + override def value: String = { + val minorPart = minor.map(m => s".$m").getOrElse("") + val patchPart = patch.map(p => s".$p").getOrElse("") + val patch2Part = patch2.map(p2 => s".$p2").getOrElse("") + val preReleasePart = preRelease.map(r => s"-$r").getOrElse("") + val metadataPart = metadata.map(m => s"+$m").getOrElse("") + s"$major$minorPart$patchPart$patch2Part$preReleasePart$metadataPart" + } + } + + final case class Custom(value: String) extends Version { + override def isSemantic: Boolean = false + override def isPreRelease: Boolean = false + override def isStable: Boolean = false + } + + def apply(maj: Int): Version = SemanticLike(maj) + def apply(maj: Int, min: Int): Version = SemanticLike(maj, Some(min)) + def apply(maj: Int, min: Int, patch: Int): Version = Version.SemanticLike(maj, Some(min), Some(patch)) + def apply(maj: Int, min: Int, patch: Int, preRelease: PreRelease): SemanticLike = + Version.SemanticLike(maj, Some(min), Some(patch), None, Some(preRelease)) + + object Major { + def unapply(version: SemanticLike): Option[Int] = version match { + case SemanticLike(major, None, None, None, None, None) => Some(major) + case _ => None + } + } + + object Minor { + def unapply(version: SemanticLike): Option[(Int, Int)] = version match { + case SemanticLike(maj, Some(min), None, None, None, None) => Some((maj, min)) + case _ => None + } + } + + object Patch { + def unapply(version: SemanticLike): Option[(Int, Int, Int)] = version match { + case SemanticLike(major, Some(min), Some(patch), None, None, None) => Some((major, min, patch)) + case _ => None + } + } + + private def MajorP[A: P]: P[Int] = Number + + // http://semver.org/#spec-item-10 + private def MetaDataP[A: P] = "+" ~ AnyChar.rep.! + + private def MinorP[A: P] = ("." ~ Number).? // not really valid SemVer + private def PatchP[A: P] = ("." ~ Number).? // not really valid SemVer + private def Patch2P[A: P] = ("." ~ Number).? // not really valid SemVer + + def SemanticParser[A: P]: P[Version.SemanticLike] = + ("v".? ~ MajorP ~ MinorP ~ PatchP ~ Patch2P ~ ("-" ~ PreRelease.Parser).? ~ MetaDataP.?) + .map { case (maj, min, p, p2, pr, m) => Version.SemanticLike(maj, min, p, p2, pr, m) } + + private def Parser[A: P]: P[Version.SemanticLike] = Start ~ SemanticParser ~ End + + def parseSemantically(version: String): Option[Version.SemanticLike] = tryParse(version, x => Parser(x)) + + def apply(version: String): Version = parseSemantically(version).getOrElse(Version.Custom(version)) +} diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/search/ProjectDocument.scala b/modules/core/shared/src/main/scala/scaladex/core/model/search/ProjectDocument.scala index 076b2aa88..7f400b439 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/search/ProjectDocument.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/search/ProjectDocument.scala @@ -2,6 +2,7 @@ package scaladex.core.model.search import java.time.Instant +import scaladex.core.api.AutocompletionResponse import scaladex.core.model._ // Project document indexed by the search engine @@ -15,7 +16,7 @@ final case class ProjectDocument( updateDate: Option[Instant], languages: Seq[Language], platforms: Seq[Platform], - latestVersion: Option[SemanticVersion], + latestVersion: Option[Version], dependents: Long, category: Option[Category], formerReferences: Seq[Project.Reference], @@ -28,6 +29,9 @@ final case class ProjectDocument( def scalaNativeVersions: Seq[ScalaNative] = platforms.collect { case v: ScalaNative => v } def sbtVersions: Seq[SbtPlugin] = platforms.collect { case v: SbtPlugin => v } def millVersions: Seq[MillPlugin] = platforms.collect { case v: MillPlugin => v } + + def toAutocompletion: AutocompletionResponse = + AutocompletionResponse(organization.value, repository.value, githubInfo.flatMap(_.description).getOrElse("")) } object ProjectDocument { diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/search/SearchParams.scala b/modules/core/shared/src/main/scala/scaladex/core/model/search/SearchParams.scala index 5045dbb9b..356a851d4 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/search/SearchParams.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/search/SearchParams.scala @@ -1,15 +1,15 @@ package scaladex.core.model.search import scaladex.core.api.AutocompletionParams -import scaladex.core.model.Project +import scaladex.core.model._ case class SearchParams( queryString: String = "", sorting: Sorting = Sorting.Stars, userRepos: Set[Project.Reference] = Set(), topics: Seq[String] = Nil, - languages: Seq[String] = Nil, - platforms: Seq[String] = Nil, + languages: Seq[Language] = Nil, + platforms: Seq[Platform] = Nil, contributingSearch: Boolean = false ) { def toAutocomplete: AutocompletionParams = AutocompletionParams( diff --git a/modules/core/shared/src/main/scala/scaladex/core/service/MavenCentralClient.scala b/modules/core/shared/src/main/scala/scaladex/core/service/MavenCentralClient.scala index 3181da9f7..c19a49c2c 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/service/MavenCentralClient.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/service/MavenCentralClient.scala @@ -5,11 +5,10 @@ import java.time.Instant import scala.concurrent.Future import scaladex.core.model.Artifact -import scaladex.core.model.Artifact.MavenReference -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version trait MavenCentralClient { def getAllArtifactIds(groupId: Artifact.GroupId): Future[Seq[Artifact.ArtifactId]] - def getAllVersions(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[SemanticVersion]] - def getPomFile(mavenReference: MavenReference): Future[Option[(String, Instant)]] + def getAllVersions(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[Version]] + def getPomFile(mavenReference: Artifact.Reference): Future[Option[(String, Instant)]] } diff --git a/modules/core/shared/src/main/scala/scaladex/core/service/ProjectService.scala b/modules/core/shared/src/main/scala/scaladex/core/service/ProjectService.scala index f9e0976ec..5633af1bc 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/service/ProjectService.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/service/ProjectService.scala @@ -4,15 +4,80 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import scaladex.core.model._ +import scaladex.core.model.search.PageParams +import scaladex.core.model.search.SearchParams +import scaladex.core.util.ScalaExtensions._ -class ProjectService(database: WebDatabase)(implicit context: ExecutionContext) { - def getProjectHeader(project: Project): Future[Option[ProjectHeader]] = { +class ProjectService(database: WebDatabase, searchEngine: SearchEngine)(implicit context: ExecutionContext) { + def getProjects(languages: Seq[Language], platforms: Seq[Platform]): Future[Seq[Project.Reference]] = { + val searchParams = SearchParams(languages = languages, platforms = platforms) + for { + firstPage <- searchEngine.find(searchParams, PageParams(0, 10000)) + p = firstPage.pagination + otherPages <- 1.until(p.pageCount).map(PageParams(_, 10000)).mapSync(p => searchEngine.find(searchParams, p)) + } yield (firstPage +: otherPages).flatMap(_.items).map(_.document.reference) + } + + def getProject(ref: Project.Reference): Future[Option[Project]] = database.getProject(ref) + + def getVersions( + ref: Project.Reference, + binaryVersions: Seq[BinaryVersion], + artifactNames: Seq[Artifact.Name], + stableOnly: Boolean + ): Future[Seq[Version]] = + for (artifacts <- getArtifactRefs(ref, binaryVersions.toSet, artifactNames.toSet, stableOnly = stableOnly)) + yield artifacts + .groupBy(_.version) + .filter { + case (_, artifacts) => + (artifactNames.isEmpty || artifacts.map(_.name).distinct.size == artifactNames.size) && + (binaryVersions.isEmpty || artifacts.map(_.binaryVersion).distinct.size == binaryVersions.size) + } + .keys + .toSeq + .sorted(Ordering[Version].reverse) + + def getLatestProjectVersion(ref: Project.Reference): Future[Seq[Artifact.Reference]] = + getHeader(ref).flatMap { + case None => Future.successful(Seq.empty) + case Some(header) => getProjectVersion(ref, header.latestVersion) + } + + def getProjectVersion(ref: Project.Reference, version: Version): Future[Seq[Artifact.Reference]] = + database.getProjectArtifactRefs(ref, version) + + def getArtifactRefs( + ref: Project.Reference, + binaryVersion: Option[BinaryVersion], + artifactName: Option[Artifact.Name], + stableOnly: Boolean + ): Future[Seq[Artifact.Reference]] = getArtifactRefs(ref, binaryVersion.toSet, artifactName.toSet, stableOnly) + + private def getArtifactRefs( + ref: Project.Reference, + binaryVersions: Set[BinaryVersion], + artifactNames: Set[Artifact.Name], + stableOnly: Boolean + ): Future[Seq[Artifact.Reference]] = + for (artifacts <- database.getProjectArtifactRefs(ref, stableOnly)) yield artifacts.filter { a => + (binaryVersions.isEmpty || binaryVersions.contains(a.binaryVersion)) && + (artifactNames.isEmpty || artifactNames.contains(a.name)) + } + + def getHeader(ref: Project.Reference): Future[Option[ProjectHeader]] = + database.getProject(ref).flatMap { + case None => Future.successful(None) + case Some(p) => getHeader(p) + } + + def getHeader(project: Project): Future[Option[ProjectHeader]] = { val ref = project.reference for { - latestArtifacts <- database.getLatestArtifacts(ref) + latestArtifacts <- database.getProjectLatestArtifacts(ref) versionCount <- database.countVersions(ref) } yield ProjectHeader( - project.reference, + ref, latestArtifacts, versionCount, project.settings.defaultArtifact, diff --git a/modules/core/shared/src/main/scala/scaladex/core/service/SchedulerDatabase.scala b/modules/core/shared/src/main/scala/scaladex/core/service/SchedulerDatabase.scala index 85f0256b7..1b356b6eb 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/service/SchedulerDatabase.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/service/SchedulerDatabase.scala @@ -5,33 +5,32 @@ import java.time.Instant import scala.concurrent.Future import scaladex.core.model.Artifact -import scaladex.core.model.Artifact.MavenReference import scaladex.core.model.ArtifactDependency import scaladex.core.model.Project import scaladex.core.model.ProjectDependency -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version trait SchedulerDatabase extends WebDatabase { // project and github def getAllProjects(): Future[Seq[Project]] + def getAllProjectArtifacts(ref: Project.Reference): Future[Seq[Artifact]] def insertProject(project: Project): Future[Unit] def updateProjectCreationDate(ref: Project.Reference, creationDate: Instant): Future[Unit] - def computeAllProjectsCreationDates(): Future[Seq[(Instant, Project.Reference)]] + def computeProjectsCreationDates(): Future[Seq[(Instant, Project.Reference)]] + def getProjectDependencies(projectRef: Project.Reference): Future[Seq[ArtifactDependency]] // project dependencies - def computeProjectDependencies(reference: Project.Reference, version: SemanticVersion): Future[Seq[ProjectDependency]] + def computeProjectDependencies(reference: Project.Reference, version: Version): Future[Seq[ProjectDependency]] def insertProjectDependencies(projectDependencies: Seq[ProjectDependency]): Future[Int] def deleteProjectDependencies(ref: Project.Reference): Future[Int] // artifacts and its dependencies def insertArtifacts(artifacts: Seq[Artifact]): Future[Unit] // for init process def insertDependencies(dependencies: Seq[ArtifactDependency]): Future[Unit] - def updateArtifacts(artifacts: Seq[Artifact], newRef: Project.Reference): Future[Int] - def updateArtifactReleaseDate(reference: MavenReference, releaseDate: Instant): Future[Int] - def getAllGroupIds(): Future[Seq[Artifact.GroupId]] - def getArtifactIds(ref: Project.Reference): Future[Seq[(Artifact.GroupId, String)]] - def getAllMavenReferences(): Future[Seq[Artifact.MavenReference]] - def getMavenReferences(ref: Project.Reference): Future[Seq[Artifact.MavenReference]] - def getDependencies(projectRef: Project.Reference): Future[Seq[ArtifactDependency]] - def updateLatestVersion(ref: MavenReference): Future[Unit] + def updateArtifacts(artifacts: Seq[Artifact.Reference], newRef: Project.Reference): Future[Int] + def updateArtifactReleaseDate(reference: Artifact.Reference, releaseDate: Instant): Future[Int] + def getGroupIds(): Future[Seq[Artifact.GroupId]] + def getArtifactIds(ref: Project.Reference): Future[Seq[(Artifact.GroupId, Artifact.ArtifactId)]] + def getArtifactRefs(): Future[Seq[Artifact.Reference]] + def updateLatestVersion(ref: Artifact.Reference): Future[Unit] } diff --git a/modules/core/shared/src/main/scala/scaladex/core/service/WebDatabase.scala b/modules/core/shared/src/main/scala/scaladex/core/service/WebDatabase.scala index f3152942a..37cde2cf8 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/service/WebDatabase.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/service/WebDatabase.scala @@ -9,13 +9,14 @@ import scaladex.core.model._ trait WebDatabase { // artifacts def insertArtifact(artifact: Artifact): Future[Boolean] - def getArtifacts(groupId: Artifact.GroupId, artifactId: String): Future[Seq[Artifact]] - def getArtifacts(projectRef: Project.Reference): Future[Seq[Artifact]] - def getArtifacts(ref: Project.Reference, artifactName: Artifact.Name, stableOnly: Boolean): Future[Seq[Artifact]] - def getArtifacts(ref: Project.Reference, artifactName: Artifact.Name, version: SemanticVersion): Future[Seq[Artifact]] - def getArtifactsByName(projectRef: Project.Reference, artifactName: Artifact.Name): Future[Seq[Artifact]] - def getLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]] - def getArtifactByMavenReference(mavenRef: Artifact.MavenReference): Future[Option[Artifact]] + def getArtifactVersions( + groupId: Artifact.GroupId, + artifactId: Artifact.ArtifactId, + stableOnly: Boolean + ): Future[Seq[Version]] + def getArtifacts(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[Artifact]] + def getArtifact(ref: Artifact.Reference): Future[Option[Artifact]] + def getLatestArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] def getAllArtifacts(language: Option[Language], platform: Option[Platform]): Future[Seq[Artifact]] def countArtifacts(): Future[Long] @@ -28,6 +29,20 @@ trait WebDatabase { def updateProjectSettings(ref: Project.Reference, settings: Project.Settings): Future[Unit] def getAllProjectsStatuses(): Future[Map[Project.Reference, GithubStatus]] def getProject(projectRef: Project.Reference): Future[Option[Project]] + def getProjectArtifactRefs(ref: Project.Reference, stableOnly: Boolean): Future[Seq[Artifact.Reference]] + def getProjectArtifactRefs(ref: Project.Reference, name: Artifact.Name): Future[Seq[Artifact.Reference]] + def getProjectArtifactRefs(ref: Project.Reference, version: Version): Future[Seq[Artifact.Reference]] + def getProjectArtifacts( + ref: Project.Reference, + artifactName: Artifact.Name, + stableOnly: Boolean + ): Future[Seq[Artifact]] + def getProjectArtifacts( + ref: Project.Reference, + artifactName: Artifact.Name, + version: Version + ): Future[Seq[Artifact]] + def getProjectLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]] def getFormerReferences(projectRef: Project.Reference): Future[Seq[Project.Reference]] def countVersions(ref: Project.Reference): Future[Long] @@ -38,7 +53,7 @@ trait WebDatabase { // project dependencies def countProjectDependents(projectRef: Project.Reference): Future[Long] - def getProjectDependencies(ref: Project.Reference, version: SemanticVersion): Future[Seq[ProjectDependency]] + def getProjectDependencies(ref: Project.Reference, version: Version): Future[Seq[ProjectDependency]] def getProjectDependents(ref: Project.Reference): Future[Seq[ProjectDependency]] // users diff --git a/modules/core/shared/src/main/scala/scaladex/core/util/Parsers.scala b/modules/core/shared/src/main/scala/scaladex/core/util/Parsers.scala index c73a718c4..a431c135f 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/util/Parsers.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/util/Parsers.scala @@ -1,5 +1,7 @@ package scaladex.core.util +import scala.util.Try + trait Parsers { import fastparse._ @@ -7,13 +9,15 @@ trait Parsers { def Alpha[A: P]: P[String] = CharPred(c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')).! - def Digit[A: P]: P[String] = CharIn("0123456789").! + def Digit[A: P]: P[String] = CharIn("0-9").! def Number[A: P]: P[Int] = { import NoWhitespace._ - Digit.rep(1).!.map(_.toInt) + Digit.rep(1).!.flatMap(i => tryP(i.toInt)) } + def tryP[T, A: P](p: => T): P[T] = Try(Pass(p)).getOrElse(Fail) + def tryParse[T](input: ParserInputSource, parser: P[_] => P[T]): Option[T] = fastparse.parse(input, parser) match { case Parsed.Success(v, _) => Some(v) diff --git a/modules/core/shared/src/main/scala/scaladex/core/web/ArtifactPageParams.scala b/modules/core/shared/src/main/scala/scaladex/core/web/ArtifactPageParams.scala index d56cc6d1d..85d1f4ec7 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/web/ArtifactPageParams.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/web/ArtifactPageParams.scala @@ -4,5 +4,5 @@ import scaladex.core.model.BinaryVersion case class ArtifactPageParams(binaryVersion: Option[BinaryVersion]) { override def toString: String = - binaryVersion.map(bv => s"?binary-versions=$bv").getOrElse("") + binaryVersion.map(bv => s"?binary-version=$bv").getOrElse("") } diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactDependencyTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactDependencyTests.scala index ce5327514..b1eab1155 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactDependencyTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactDependencyTests.scala @@ -2,53 +2,29 @@ package scaladex.core.model import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -import scaladex.core.model.Artifact._ import scaladex.core.model.ArtifactDependency.Scope class ArtifactDependencyTests extends AnyFunSpec with Matchers { describe("ordering") { it("should order correctly 1") { - val ref1 = (MavenReference("org.specs2", "specs2-junit", "4.8.3"), Scope("test")) - val ref2 = ( - MavenReference("org.scala-lang", "scala3-library_3", "3.0.0"), - Scope("compile") - ) - val dependencies = getFulldependencies(Seq(ref1, ref2)) - dependencies.sorted shouldBe getFulldependencies(Seq(ref2, ref1)) + val dep1 = dep(Artifact.Reference.from("org.specs2", "specs2-junit", "4.8.3"), Scope("test")) + val dep2 = dep(Artifact.Reference.from("org.scala-lang", "scala3-library_3", "3.0.0"), Scope("compile")) + Seq(dep1, dep2).sorted shouldBe Seq(dep2, dep1) } it("should order correctly 2") { - val ref1 = - (MavenReference("org.scala-lang", "scala3-library_3", "3.0.0"), Scope("test")) - val ref2 = ( - MavenReference("org.scala-lang", "scala3-library_3", "3.0.0"), - Scope("provided") - ) - val ref3 = ( - MavenReference("org.scala-lang", "scala3-library_3", "3.0.0"), - Scope("compile") - ) - val dependencies = getFulldependencies(Seq(ref1, ref2, ref3)) - dependencies.sorted shouldBe getFulldependencies(Seq(ref3, ref2, ref1)) + val dep1 = dep(Artifact.Reference.from("org.scala-lang", "scala3-library_3", "3.0.0"), Scope("test")) + val dep2 = dep(Artifact.Reference.from("org.scala-lang", "scala3-library_3", "3.0.0"), Scope("provided")) + val dep3 = dep(Artifact.Reference.from("org.scala-lang", "scala3-library_3", "3.0.0"), Scope("compile")) + Seq(dep1, dep2, dep3).sorted shouldBe Seq(dep3, dep2, dep1) } it("should order correctly 3") { - val ref1 = ( - MavenReference("org.scala-lang", "scala3-library_3", "3.0.0"), - Scope("compile") - ) - val ref2 = (MavenReference("a", "specs2-junit", "4.8.3"), Scope("compile")) - val dependencies = getFulldependencies(Seq(ref1, ref2)) - dependencies.sorted shouldBe getFulldependencies(Seq(ref2, ref1)) + val dep1 = dep(Artifact.Reference.from("org.scala-lang", "scala3-library_3", "3.0.0"), Scope("compile")) + val dep2 = dep(Artifact.Reference.from("a", "specs2-junit", "4.8.3"), Scope("compile")) + Seq(dep1, dep2).sorted shouldBe Seq(dep2, dep1) } } - private def getFulldependencies( - refs: Seq[(MavenReference, Scope)] - ): Seq[ArtifactDependency.Direct] = - refs.map { - case (ref, scope) => - val mavenRef: MavenReference = - MavenReference("org.typelevel", "cats-core_3", "2.6.1") - ArtifactDependency.Direct(ArtifactDependency(mavenRef, ref, scope), None) - } - + private val source = Artifact.Reference.from("org.typelevel", "cats-core_3", "2.6.1") + private def dep(target: Artifact.Reference, scope: Scope): ArtifactDependency.Direct = + ArtifactDependency.Direct(ArtifactDependency(source, target, scope), None) } diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactIdTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactIdTests.scala index 3f315c901..ea8e452dd 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactIdTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactIdTests.scala @@ -9,90 +9,90 @@ class ArtifactIdTests extends AsyncFunSpec with Matchers { it("parses scalajs") { val artifactId = "cats-core_sjs0.6_2.11" val expected = ArtifactId(Name("cats-core"), BinaryVersion(ScalaJs.`0.6`, Scala.`2.11`)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } it("parses scala-native") { val artifactId = "cats-core_native0.4_2.11" val expected = ArtifactId(Name("cats-core"), BinaryVersion(ScalaNative.`0.4`, Scala.`2.11`)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } it("parses scala3 versions") { val artifactId = "circe_cats-core_3" val expected = ArtifactId(Name("circe_cats-core"), BinaryVersion(Jvm, Scala.`3`)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } it("parses scala3 compiler") { val artifactId = "scala3-compiler_3" val expected = ArtifactId(Name("scala3-compiler"), BinaryVersion(Jvm, Scala.`3`)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } it("parses sbt") { val artifactId = "sbt-microsites_2.12_1.0" val expected = ArtifactId(Name("sbt-microsites"), BinaryVersion(SbtPlugin.`1.0`, Scala.`2.12`)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } it("parse Java Artifact") { val artifactId = "sparrow" val expected = ArtifactId(Name("sparrow"), BinaryVersion(Jvm, Java)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } - it("should not parse full scala version") { - ArtifactId.parse("scalafix-core_2.12.2") shouldBe None - } + // it("should not parse full scala version") { + // ArtifactId("scalafix-core_2.12.2") shouldBe None + // } - it("should not parse correctly") { - ArtifactId.parse("scalafix-core_2.10_0.12") shouldBe None - } + // it("should not parse correctly") { + // ArtifactId("scalafix-core_2.10_0.12") shouldBe None + // } it("handles special case") { val artifactId = "banana_jvm_2.11" val expected = ArtifactId(Name("banana_jvm"), BinaryVersion(Jvm, Scala.`2.11`)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } it("handles java artifacts") { val artifactId = "virtual-schema-common-java" val expected = ArtifactId(Name("virtual-schema-common-java"), BinaryVersion(Jvm, Java)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } it("handles special char '_' in artifact name") { val artifactId = "pan-domain-auth-play_2-8_2.12" val expected = ArtifactId(Name("pan-domain-auth-play_2-8"), BinaryVersion(Jvm, Scala.`2.12`)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } it("parses Mill plugin") { val artifactId = "mill-scalafix_mill0.10_2.13" val expected = ArtifactId(Name("mill-scalafix"), BinaryVersion(MillPlugin.`0.10`, Scala.`2.13`)) - val result = ArtifactId.parse(artifactId) - result should contain(expected) - result.get.value shouldBe artifactId + val result = ArtifactId(artifactId) + result shouldBe expected + result.value shouldBe artifactId } } } diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactSelectionTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactSelectionTests.scala index 93431c18f..aa4c79101 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactSelectionTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactSelectionTests.scala @@ -1,119 +1,30 @@ package scaladex.core.model -import java.time.Instant - import org.scalatest.funspec.AsyncFunSpec import org.scalatest.matchers.should.Matchers -import scaladex.core.model.Artifact._ class ArtifactSelectionTests extends AsyncFunSpec with Matchers { - - def emptyArtifact( - maven: MavenReference, - reference: Project.Reference - ): Artifact = { - val artifactId = - ArtifactId.parse(maven.artifactId).getOrElse(throw new Exception(s"cannot parse ${maven.artifactId}")) - val version = SemanticVersion.parse(maven.version).get - Artifact( - GroupId(maven.groupId), - artifactId.value, - version, - artifactId.name, - reference, - None, - Instant.ofEpochMilli(1475505237265L), - None, - Set.empty, - isNonStandardLib = false, - artifactId.binaryVersion.platform, - artifactId.binaryVersion.language, - None, - None, - None, - Nil - ) - } - - def prepare( - projectRef: Project.Reference, - groupdId: String, - artifacts: List[(String, String)] - ): Seq[Artifact] = - artifacts - .map { - case (artifactId, rawVersion) => - emptyArtifact( - MavenReference(groupdId, artifactId, rawVersion), - projectRef - ) - } - it("latest version pre release scala") { - - val projectRef = Project.Reference.from("typelevel", "cats") - val project = Project.default(projectRef) - val groupdId = "org.typelevel" - val artifacts = prepare( - projectRef, - groupdId, - List( - ("cats-core_2.11", "0.6.0"), - ("cats-core_2.11", "0.6.0-M2"), - ("cats-core_2.11", "0.6.0-M1"), - ("cats-core_2.11", "0.5.0"), - ("cats-core_2.11", "0.4.1"), - ("cats-core_2.11", "0.4.0"), - ("cats-core_2.10", "0.6.0"), - ("cats-core_2.10", "0.6.0-M2"), - ("cats-core_2.10", "0.6.0-M1"), - ("cats-core_2.10", "0.5.0"), - ("cats-core_2.10", "0.4.1"), - ("cats-core_2.10", "0.4.0"), - ("cats-core_sjs0.6_2.11", "0.6.0"), - ("cats-core_sjs0.6_2.11", "0.6.0-M2"), - ("cats-core_sjs0.6_2.11", "0.6.0-M1"), - ("cats-core_sjs0.6_2.11", "0.5.0"), - ("cats-core_sjs0.6_2.11", "0.4.1"), - ("cats-core_sjs0.6_2.11", "0.4.0"), - ("cats-core_sjs0.6_2.10", "0.6.0"), - ("cats-core_sjs0.6_2.10", "0.6.0-M2"), - ("cats-core_sjs0.6_2.10", "0.6.0-M1"), - ("cats-core_sjs0.6_2.10", "0.5.0"), - ("cats-core_sjs0.6_2.10", "0.4.1"), - ("cats-core_sjs0.6_2.10", "0.4.0") - ) - ) - + val project = Project.default(Project.Reference.from("typelevel", "cats")) + val artifactIds = Seq("cats-core_2.11", "cats-core_2.10", "cats-core_sjs0.6_2.11", "cats-core_sjs0.6_2.10") + val versions = Seq("0.6.0", "0.6.0-M2", "0.6.0-M1", "0.5.0", "0.4.1", "0.4.0") + val artifacts = for { + artifactId <- artifactIds + version <- versions + } yield Artifact.Reference.from("org.typelevel", artifactId, version) val result = ArtifactSelection.empty.defaultArtifact(artifacts, project) - result should contain(artifacts.head) } it("selected artifact") { - val projectRef = Project.Reference.from("akka", "akka") - val project = Project.default(projectRef) + val project = Project.default(Project.Reference.from("akka", "akka")) val groupdId = "com.typesafe.akka" - val artifacts = - prepare( - projectRef, - groupdId, - List( - ("akka-distributed-data-experimental_2.11", "2.4.8"), - ("akka-actors_2.11", "2.4.8") - ) - ) - + val artifacts = Seq( + Artifact.Reference.from(groupdId, "akka-distributed-data-experimental_2.11", "2.4.8"), + Artifact.Reference.from(groupdId, "akka-actors_2.11", "2.4.8") + ) val selection = ArtifactSelection(None, Some(Artifact.Name("akka-distributed-data-experimental"))) - val result = selection.defaultArtifact(artifacts, project) - val expected = prepare( - projectRef, - groupdId, - List( - ("akka-distributed-data-experimental_2.11", "2.4.8") - ) - ) - result should contain(expected.head) + result should contain(artifacts.head) } } diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactTests.scala index 44a2ed4ab..5e0de182d 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/ArtifactTests.scala @@ -14,7 +14,7 @@ class ArtifactTests extends AnyFunSpec with Matchers { groupId = "org.scalamacros", artifactId = "paradise_2.12.3", version = "2.1.1", - binaryVersion = BinaryVersion(Jvm, Scala(PatchVersion(2, 12, 3))), + binaryVersion = BinaryVersion(Jvm, Scala(Version("2.12.3"))), artifactName = Some(Name("paradise")) ).sbtInstall @@ -30,7 +30,7 @@ class ArtifactTests extends AnyFunSpec with Matchers { groupId = "org.scalaz", artifactId = "scalaz-core_2.13.0-M1", version = "7.2.14", - binaryVersion = BinaryVersion(Jvm, Scala(PreReleaseVersion(2, 13, 0, Milestone(1)))), + binaryVersion = BinaryVersion(Jvm, Scala(Version("2.13.0-M1"))), artifactName = Some(Name("scalaz-core")) ).sbtInstall @@ -115,7 +115,7 @@ class ArtifactTests extends AnyFunSpec with Matchers { groupId = "org.http4s", version = "0.18.12", artifactId = "http4s-core_2.12", - binaryVersion = BinaryVersion(Jvm, Scala(PatchVersion(2, 12, 3))) + binaryVersion = BinaryVersion(Jvm, Scala(Version("2.12.3"))) ).millInstall val expected = @@ -213,14 +213,11 @@ class ArtifactTests extends AnyFunSpec with Matchers { // be parsed or not, we just want to test methods in artifacts like sbtInstall // in fact those tests don't make sense, since it's not supposed to happen except if an Artifact is created without parsing. val artifactIdResult = - artifactName.map(name => ArtifactId(name, binaryVersion)).orElse(Artifact.ArtifactId.parse(artifactId)).get + artifactName.map(name => ArtifactId(name, binaryVersion)).getOrElse(Artifact.ArtifactId(artifactId)) Artifact( groupId = GroupId(groupId), - artifactId = artifactId, - version = SemanticVersion.parse(version).get, - artifactName = artifactIdResult.name, - platform = artifactIdResult.binaryVersion.platform, - language = artifactIdResult.binaryVersion.language, + artifactId = artifactIdResult, + version = Version(version), projectRef = projectRef.getOrElse(Project.Reference.from("", "")), description = None, releaseDate = Instant.now(), diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/BinaryVersionTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/BinaryVersionTests.scala index 3d86ec7fd..0414781e0 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/BinaryVersionTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/BinaryVersionTests.scala @@ -7,9 +7,9 @@ import org.scalatest.prop.TableDrivenPropertyChecks class BinaryVersionTests extends AnyFunSpec with Matchers with OptionValues with TableDrivenPropertyChecks { it("should be ordered") { - val `0.6.7` = PatchVersion(0, 6, 7) - val `0.6.18` = PatchVersion(0, 6, 18) - val `0.3.0` = PatchVersion(0, 3, 0) + val `0.6.7` = Version("0.6.7") + val `0.6.18` = Version("0.6.18") + val `0.3.0` = Version("0.3.0") val obtained = List( BinaryVersion(ScalaJs(`0.6.7`), Scala.`2.10`), @@ -51,7 +51,7 @@ class BinaryVersionTests extends AnyFunSpec with Matchers with OptionValues with forAll(cases) { (input, expected) => BinaryVersion.parse(input) should contain(expected) - expected.encode shouldBe input + expected.value shouldBe input } } } diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/LanguageTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/LanguageTests.scala index 02c91bb1a..55008121d 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/LanguageTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/LanguageTests.scala @@ -7,14 +7,15 @@ class LanguageTests extends AsyncFunSpec with Matchers { describe("Scala 3 versions") { it("Scala 3 label") { Scala.`3`.label shouldBe "3.x" + Scala.`3`.value shouldBe "3" } it("should not accept minor versions") { - Scala(MinorVersion(3, 0)).isValid shouldBe false + Scala(Version(3, 0)).isValid shouldBe false } it("should not accept patch versions") { - Scala(PatchVersion(3, 0, 1)).isValid shouldBe false + Scala(Version("3.0.1")).isValid shouldBe false } } } diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/PlatformTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/PlatformTests.scala index 171c4d2f2..d25df54ed 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/PlatformTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/PlatformTests.scala @@ -11,10 +11,10 @@ import scaladex.core.model.ScalaNative class PlatformTests extends AnyFunSpec with Matchers { it("should yield a Platform from its label") { - Platform.fromLabel("sjs1").get shouldBe ScalaJs.`1.x` - Platform.fromLabel("jvm").get shouldBe Jvm - Platform.fromLabel("native0.4").get shouldBe ScalaNative.`0.4` - Platform.fromLabel("sbt1.0").get shouldBe SbtPlugin.`1.0` - Platform.fromLabel("mill0.10").get shouldBe MillPlugin.`0.10` + Platform.parse("sjs1").get shouldBe ScalaJs.`1.x` + Platform.parse("jvm").get shouldBe Jvm + Platform.parse("native0.4").get shouldBe ScalaNative.`0.4` + Platform.parse("sbt1.0").get shouldBe SbtPlugin.`1.0` + Platform.parse("mill0.10").get shouldBe MillPlugin.`0.10` } } diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/SemanticVersionTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/SemanticVersionTests.scala index b1f0bae42..9f1f315a5 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/SemanticVersionTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/SemanticVersionTests.scala @@ -3,39 +3,39 @@ package scaladex.core.model import org.scalatest.funspec.AsyncFunSpec import org.scalatest.matchers.should.Matchers import org.scalatest.prop.TableDrivenPropertyChecks -import scaladex.core.model.SemanticVersion.PreferStable import scaladex.core.test.Values._ class SemanticVersionTests extends AsyncFunSpec with Matchers with TableDrivenPropertyChecks { it("should parse any version") { val inputs = Table( ("input", "output"), - ("1", MajorVersion(1)), - ("2.12", MinorVersion(2, 12)), - ("0.6", MinorVersion(0, 6)), - ("2.13.0", PatchVersion(2, 13, 0)), - ("0.4.0", PatchVersion(0, 4, 0)), - ("0.4.0-M2", PreReleaseVersion(0, 4, 0, Milestone(2))), - ("0.23.0-RC1", PreReleaseVersion(0, 23, 0, ReleaseCandidate(1))), - ("3.0.0-M1", PreReleaseVersion(3, 0, 0, Milestone(1))), - ("1.1-M1", SemanticVersion(1, Some(1), preRelease = Some(Milestone(1)))), - ("1.2.3.4", SemanticVersion(1, Some(2), Some(3), Some(4))), - ("1.1.1-xyz", SemanticVersion(1, Some(1), Some(1), preRelease = Some(OtherPreRelease("xyz")))), - ("1.1.1+some.meta~data", SemanticVersion(1, Some(1), Some(1), metadata = Some("some.meta~data"))) + ("1", Version(1)), + ("2.12", Version(2, 12)), + ("0.6", Version(0, 6)), + ("2.13.0", Version(2, 13, 0)), + ("0.4.0", Version(0, 4, 0)), + ("0.4.0-M2", Version(0, 4, 0, Milestone(2))), + ("0.23.0-RC1", Version(0, 23, 0, ReleaseCandidate(1))), + ("3.0.0-M1", Version(3, 0, 0, Milestone(1))), + ("1.1-M1", Version.SemanticLike(1, Some(1), preRelease = Some(Milestone(1)))), + ("1.2.3.4", Version.SemanticLike(1, Some(2), Some(3), Some(4))), + ("1.1.1-xyz", Version.SemanticLike(1, Some(1), Some(1), preRelease = Some(OtherPreRelease("xyz")))), + ("1.1.1+some.meta~data", Version.SemanticLike(1, Some(1), Some(1), metadata = Some("some.meta~data"))) ) - forAll(inputs)((input, output) => SemanticVersion.parse(input) should contain(output)) + forAll(inputs)((input, output) => Version(input) shouldBe output) } it("should be ordered") { - val inputs = Table[SemanticVersion, SemanticVersion]( + val inputs = Table[Version, Version]( ("lower", "higher"), - (MajorVersion(1), MajorVersion(2)), - (MinorVersion(1, 1), MajorVersion(1)), - (MajorVersion(1), MinorVersion(2, 1)), - (SemanticVersion(1, Some(2), preRelease = Some(Milestone(1))), MinorVersion(1, 2)), - (PreReleaseVersion(1, 2, 0, Milestone(1)), MinorVersion(1, 2)), - (MajorVersion(1), SemanticVersion(2, Some(0), preRelease = Some(Milestone(1)))) + (Version(1), Version(2)), + (Version(1, 1), Version(1)), + (Version(1), Version(2, 1)), + (Version.SemanticLike(1, Some(2), preRelease = Some(Milestone(1))), Version(1, 2)), + (Version(1, 2, 0, Milestone(1)), Version(1, 2)), + (Version(1), Version.SemanticLike(2, Some(0), preRelease = Some(Milestone(1)))), + (Version.Custom("abc"), Version(1, 2, 3)) ) forAll(inputs)((lower, higher) => lower shouldBe <(higher)) @@ -44,17 +44,17 @@ class SemanticVersionTests extends AsyncFunSpec with Matchers with TableDrivenPr it("should allow us to prefer stable over pre-releases") { val versions = Seq(`7.0.0`, `7.1.0`, `7.2.0-PREVIEW.1`) versions.max shouldBe `7.2.0-PREVIEW.1` - versions.max(PreferStable) shouldBe `7.1.0` + versions.max(Version.PreferStable) shouldBe `7.1.0` } it("should encode and decode any version") { - val inputs = Table[SemanticVersion]( + val inputs = Table[Version]( "semanticVersion", - MajorVersion(2345), - MinorVersion(1, 3), - SemanticVersion(1, Some(2), Some(3), Some(4)) + Version(2345), + Version(1, 3), + Version.SemanticLike(1, Some(2), Some(3), Some(4)) ) - forAll(inputs)(v => SemanticVersion.parse(v.encode).get shouldBe v) + forAll(inputs)(v => Version(v.value) shouldBe v) } } diff --git a/modules/core/shared/src/test/scala/scaladex/core/model/VersionFilteringTests.scala b/modules/core/shared/src/test/scala/scaladex/core/model/VersionFilteringTests.scala index 08489070d..63562dd43 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/model/VersionFilteringTests.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/model/VersionFilteringTests.scala @@ -55,7 +55,7 @@ class VersionFilteringTests extends AnyFunSuite { "0.8.1-RC3", "0.8.1-RC2", "0.8.1-RC1" - ).flatMap(SemanticVersion.parse) + ).map(Version.apply) val obtained = versions.filter(_.isSemantic) @@ -94,7 +94,7 @@ class VersionFilteringTests extends AnyFunSuite { "0.8.1-RC3", "0.8.1-RC2", "0.8.1-RC1" - ).flatMap(SemanticVersion.parse) + ).map(Version.apply) assert((obtained.toSet -- expected.toSet).isEmpty) assert((expected.toSet -- obtained.toSet).isEmpty) diff --git a/modules/core/shared/src/test/scala/scaladex/core/test/InMemoryDatabase.scala b/modules/core/shared/src/test/scala/scaladex/core/test/InMemoryDatabase.scala index 6e5cb63b5..33887e9cc 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/test/InMemoryDatabase.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/test/InMemoryDatabase.scala @@ -6,23 +6,15 @@ import java.util.UUID import scala.collection.mutable import scala.concurrent.Future -import scaladex.core.model.Artifact -import scaladex.core.model.ArtifactDependency -import scaladex.core.model.GithubInfo -import scaladex.core.model.GithubStatus -import scaladex.core.model.Language -import scaladex.core.model.Platform -import scaladex.core.model.Project -import scaladex.core.model.ProjectDependency -import scaladex.core.model.SemanticVersion -import scaladex.core.model.UserInfo -import scaladex.core.model.UserState +import scaladex.core.model._ import scaladex.core.service.SchedulerDatabase class InMemoryDatabase extends SchedulerDatabase { + private val allProjects = mutable.Map[Project.Reference, Project]() - private val allArtifacts = mutable.Map[Project.Reference, Seq[Artifact]]() + private val allArtifacts = mutable.Map[Artifact.Reference, Artifact]() private val allDependencies = mutable.Buffer[ArtifactDependency]() + private val latestArtifacts = mutable.Map[(Artifact.GroupId, Artifact.ArtifactId), Artifact.Reference]() def reset(): Unit = { allProjects.clear() @@ -31,8 +23,8 @@ class InMemoryDatabase extends SchedulerDatabase { } override def insertArtifact(artifact: Artifact): Future[Boolean] = { - val isNewArtifact = !allArtifacts.values.flatten.exists(a => a.mavenReference == artifact.mavenReference) - allArtifacts.addOne(artifact.projectRef -> (allArtifacts.getOrElse(artifact.projectRef, Seq.empty) :+ artifact)) + val isNewArtifact = !allArtifacts.contains(artifact.reference) + allArtifacts += artifact.reference -> artifact Future.successful(isNewArtifact) } @@ -42,36 +34,58 @@ class InMemoryDatabase extends SchedulerDatabase { Future.successful(isNewProject) } - override def insertProject(project: Project): Future[Unit] = ??? + override def insertProject(project: Project): Future[Unit] = { + allProjects += project.reference -> project + Future.unit + } - override def insertArtifacts(allArtifacts: Seq[Artifact]): Future[Unit] = ??? + override def insertArtifacts(artifacts: Seq[Artifact]): Future[Unit] = { + artifacts.foreach(a => allArtifacts += a.reference -> a) + Future.unit + } override def insertDependencies(dependencies: Seq[ArtifactDependency]): Future[Unit] = { allDependencies ++= dependencies - Future.successful(()) + Future.unit } override def deleteProjectDependencies(ref: Project.Reference): Future[Int] = ??? override def updateProjectSettings(ref: Project.Reference, settings: Project.Settings): Future[Unit] = { allProjects.update(ref, allProjects(ref).copy(settings = settings)) - Future.successful(()) + Future.unit } override def getProject(projectRef: Project.Reference): Future[Option[Project]] = Future.successful(allProjects.get(projectRef)) - override def getArtifacts(groupId: Artifact.GroupId, artifactId: String): Future[Seq[Artifact]] = - Future.successful { - allArtifacts.values.flatten.filter { artifact: Artifact => - artifact.groupId == groupId && artifact.artifactId == artifactId - }.toSeq - } + override def getArtifacts(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[Artifact]] = { + val res = allArtifacts.values.filter(a => a.groupId == groupId && a.artifactId == artifactId).toSeq + Future.successful(res) + } + + override def getAllProjectArtifacts(ref: Project.Reference): Future[Seq[Artifact]] = + Future.successful(getProjectArtifactsSync(ref)) + + private def getProjectArtifactsSync(ref: Project.Reference): Seq[Artifact] = + allArtifacts.values.filter(_.projectRef == ref).toSeq + + override def getProjectArtifactRefs( + ref: Project.Reference, + stableOnly: Boolean + ): Future[Seq[Artifact.Reference]] = + Future.successful(getProjectArtifactsSync(ref).map(_.reference)) + + override def getProjectArtifactRefs(ref: Project.Reference, name: Artifact.Name): Future[Seq[Artifact.Reference]] = + Future.successful(getProjectArtifactsSync(ref).map(_.reference).filter(_.name == name)) - override def getArtifacts(projectRef: Project.Reference): Future[Seq[Artifact]] = - Future.successful(allArtifacts.getOrElse(projectRef, Nil)) + override def getProjectArtifactRefs( + ref: Project.Reference, + version: Version + ): Future[Seq[Artifact.Reference]] = + Future.successful(getProjectArtifactsSync(ref).map(_.reference).filter(_.version == version)) - override def getDependencies(projectRef: Project.Reference): Future[Seq[ArtifactDependency]] = ??? + override def getProjectDependencies(projectRef: Project.Reference): Future[Seq[ArtifactDependency]] = ??? override def getFormerReferences(projectRef: Project.Reference): Future[Seq[Project.Reference]] = { val result = allProjects.view @@ -81,15 +95,26 @@ class InMemoryDatabase extends SchedulerDatabase { Future.successful(result) } - override def getArtifactsByName(projectRef: Project.Reference, artifactName: Artifact.Name): Future[Seq[Artifact]] = + override def getProjectArtifacts( + ref: Project.Reference, + artifactName: Artifact.Name, + stableOnly: Boolean + ): Future[Seq[Artifact]] = { + val res = getProjectArtifactsSync(ref).filter(a => a.name == artifactName && (!stableOnly || a.version.isStable)) + Future.successful(res) + } + + override def getProjectArtifacts( + ref: Project.Reference, + artifactName: Artifact.Name, + version: Version + ): Future[Seq[Artifact]] = Future.successful( - allArtifacts - .getOrElse(projectRef, Nil) - .filter(_.artifactName == artifactName) + getProjectArtifactsSync(ref).filter(a => a.name == artifactName && a.version == version) ) - override def getArtifactByMavenReference(mavenRef: Artifact.MavenReference): Future[Option[Artifact]] = - Future.successful(allArtifacts.values.iterator.flatten.find(a => a.mavenReference == mavenRef)) + override def getArtifact(ref: Artifact.Reference): Future[Option[Artifact]] = + Future.successful(allArtifacts.get(ref)) override def getAllArtifacts( maybeLanguage: Option[Language], @@ -98,13 +123,11 @@ class InMemoryDatabase extends SchedulerDatabase { val constraint = (maybeLanguage, maybePlatform) match { case (Some(language), Some(platform)) => (artifact: Artifact) => artifact.language == language && artifact.platform == platform - case (Some(language), _) => - (artifact: Artifact) => artifact.language == language - case (_, Some(platform)) => - (artifact: Artifact) => artifact.platform == platform - case _ => (_: Artifact) => true + case (Some(language), _) => (artifact: Artifact) => artifact.language == language + case (_, Some(platform)) => (artifact: Artifact) => artifact.platform == platform + case _ => (_: Artifact) => true } - Future.successful(allArtifacts.values.flatten.toSeq.filter(constraint)) + Future.successful(allArtifacts.values.toSeq.filter(constraint)) } override def getDirectDependencies(artifact: Artifact): Future[List[ArtifactDependency.Direct]] = @@ -114,19 +137,20 @@ class InMemoryDatabase extends SchedulerDatabase { Future.successful(Nil) override def countArtifacts(): Future[Long] = - Future.successful(allArtifacts.values.flatten.size) + Future.successful(allArtifacts.size) override def getAllProjectsStatuses(): Future[Map[Project.Reference, GithubStatus]] = Future.successful(allProjects.view.mapValues(p => p.githubStatus).toMap) - override def getAllProjects(): Future[Seq[Project]] = ??? + override def getAllProjects(): Future[Seq[Project]] = + Future.successful(allProjects.values.toSeq) override def computeProjectDependencies( ref: Project.Reference, - version: SemanticVersion + version: Version ): Future[Seq[ProjectDependency]] = ??? - override def computeAllProjectsCreationDates(): Future[Seq[(Instant, Project.Reference)]] = ??? + override def computeProjectsCreationDates(): Future[Seq[(Instant, Project.Reference)]] = ??? override def updateProjectCreationDate(ref: Project.Reference, creationDate: Instant): Future[Unit] = Future.successful(allProjects.update(ref, allProjects(ref).copy(creationDate = Some(creationDate)))) @@ -136,17 +160,15 @@ class InMemoryDatabase extends SchedulerDatabase { override def countProjectDependents(ref: Project.Reference): Future[Long] = Future.successful(0) - override def updateArtifacts(allArtifacts: Seq[Artifact], newRef: Project.Reference): Future[Int] = ??? - override def getAllGroupIds(): Future[Seq[Artifact.GroupId]] = ??? - override def getAllMavenReferences(): Future[Seq[Artifact.MavenReference]] = ??? - - override def getMavenReferences(ref: Project.Reference): Future[Seq[Artifact.MavenReference]] = ??? + override def updateArtifacts(allArtifacts: Seq[Artifact.Reference], newRef: Project.Reference): Future[Int] = ??? + override def getGroupIds(): Future[Seq[Artifact.GroupId]] = ??? + override def getArtifactRefs(): Future[Seq[Artifact.Reference]] = ??? override def insertUser(userId: UUID, userInfo: UserInfo): Future[Unit] = ??? override def updateUser(userId: UUID, userInfo: UserState): Future[Unit] = ??? override def getUser(userId: UUID): Future[Option[UserState]] = ??? override def getAllUsers(): Future[Seq[(UUID, UserInfo)]] = ??? override def deleteUser(userId: UUID): Future[Unit] = ??? - override def updateArtifactReleaseDate(reference: Artifact.MavenReference, releaseDate: Instant): Future[Int] = ??? + override def updateArtifactReleaseDate(reference: Artifact.Reference, releaseDate: Instant): Future[Int] = ??? override def updateGithubInfoAndStatus( ref: Project.Reference, @@ -157,33 +179,15 @@ class InMemoryDatabase extends SchedulerDatabase { allProjects.update(ref, allProjects(ref).copy(githubInfo = Some(githubInfo), githubStatus = githubStatus)) ) - override def getArtifacts( - ref: Project.Reference, - artifactName: Artifact.Name, - stableOnly: Boolean - ): Future[Seq[Artifact]] = - // TODO: use stableOnly to filter - Future.successful(allArtifacts.getOrElse(ref, Seq.empty).filter(_.artifactName == artifactName)) override def getProjectDependencies( ref: Project.Reference, - version: SemanticVersion + version: Version ): Future[Seq[ProjectDependency]] = Future.successful(Seq.empty) override def getProjectDependents(ref: Project.Reference): Future[Seq[ProjectDependency]] = Future.successful(Seq.empty) override def countVersions(ref: Project.Reference): Future[Long] = - Future.successful { - allArtifacts.getOrElse(ref, Seq.empty).map(_.version).distinct.size - } - - override def getArtifacts( - ref: Project.Reference, - artifactName: Artifact.Name, - version: SemanticVersion - ): Future[Seq[Artifact]] = - Future.successful { - allArtifacts.getOrElse(ref, Seq.empty).filter(a => a.artifactName == artifactName && a.version == version) - } + Future.successful(getProjectArtifactsSync(ref).map(_.version).distinct.size) override def updateGithubStatus(ref: Project.Reference, status: GithubStatus): Future[Unit] = Future.successful( @@ -201,18 +205,33 @@ class InMemoryDatabase extends SchedulerDatabase { Future.successful(()) } - override def getLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]] = { - val res = allArtifacts(ref) - .groupBy(a => (a.groupId, a.artifactId)) - .values - .map(artifacts => artifacts.maxBy(_.releaseDate)) - .toSeq + override def getProjectLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]] = { + val res = getProjectArtifactsSync(ref) + .flatMap(a => latestArtifacts.get((a.groupId, a.artifactId))) + .distinct + .map(allArtifacts.apply) Future.successful(res) } - override def getArtifactIds(ref: Project.Reference): Future[Seq[(Artifact.GroupId, String)]] = - Future.successful(allArtifacts(ref).map(a => (a.groupId, a.artifactId)).toSeq) + override def getArtifactIds(ref: Project.Reference): Future[Seq[(Artifact.GroupId, Artifact.ArtifactId)]] = + Future.successful(getProjectArtifactsSync(ref).map(a => (a.groupId, a.artifactId)).distinct.toSeq) - override def updateLatestVersion(ref: Artifact.MavenReference): Future[Unit] = - Future.successful(()) + override def updateLatestVersion(ref: Artifact.Reference): Future[Unit] = { + latestArtifacts += (ref.groupId, ref.artifactId) -> ref + Future.unit + } + + override def getArtifactVersions( + groupId: Artifact.GroupId, + artifactId: Artifact.ArtifactId, + stableOnly: Boolean + ): Future[Seq[Version]] = { + val res = allArtifacts.keys.collect { + case Artifact.Reference(g, a, version) if g == groupId && a == artifactId => version + }.toSeq + Future.successful(res) + } + + override def getLatestArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] = + Future.successful(latestArtifacts.get((groupId, artifactId)).map(allArtifacts.apply)) } diff --git a/modules/core/shared/src/test/scala/scaladex/core/test/InMemorySearchEngine.scala b/modules/core/shared/src/test/scala/scaladex/core/test/InMemorySearchEngine.scala index f4d8a4d11..18f9ea7ae 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/test/InMemorySearchEngine.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/test/InMemorySearchEngine.scala @@ -9,12 +9,8 @@ import scaladex.core.model.Language import scaladex.core.model.Platform import scaladex.core.model.Project import scaladex.core.model.TopicCount -import scaladex.core.model.search.AwesomeParams -import scaladex.core.model.search.Page -import scaladex.core.model.search.PageParams -import scaladex.core.model.search.ProjectDocument -import scaladex.core.model.search.ProjectHit -import scaladex.core.model.search.SearchParams +import scaladex.core.model.search.Pagination +import scaladex.core.model.search._ import scaladex.core.service.SearchEngine class InMemorySearchEngine extends SearchEngine { @@ -47,7 +43,15 @@ class InMemorySearchEngine extends SearchEngine { page: PageParams ): Future[Page[ProjectDocument]] = ??? - override def find(params: SearchParams, page: PageParams): Future[Page[ProjectHit]] = ??? + override def find(params: SearchParams, page: PageParams): Future[Page[ProjectHit]] = + Future.successful { + val hits = allDocuments.values + .filter(doc => (params.languages.toSet -- doc.languages).isEmpty) + .filter(doc => (params.platforms.toSet -- doc.platforms).isEmpty) + .toSeq + .map(ProjectHit(_, Seq.empty)) + Page(Pagination(1, 1, hits.size), hits) + } override def autocomplete(params: SearchParams, limit: Int): Future[Seq[ProjectDocument]] = ??? diff --git a/modules/core/shared/src/test/scala/scaladex/core/test/Values.scala b/modules/core/shared/src/test/scala/scaladex/core/test/Values.scala index 73cadf920..0c096e8b2 100644 --- a/modules/core/shared/src/test/scala/scaladex/core/test/Values.scala +++ b/modules/core/shared/src/test/scala/scaladex/core/test/Values.scala @@ -3,30 +3,10 @@ package scaladex.core.test import java.time.Instant import java.time.temporal.ChronoUnit -import scaladex.core.model.Artifact import scaladex.core.model.Artifact._ -import scaladex.core.model.ArtifactDependency import scaladex.core.model.ArtifactDependency.Scope -import scaladex.core.model.BinaryVersion -import scaladex.core.model.Category -import scaladex.core.model.Contributor -import scaladex.core.model.GithubCommitActivity -import scaladex.core.model.GithubContributor -import scaladex.core.model.GithubInfo -import scaladex.core.model.GithubIssue -import scaladex.core.model.GithubStatus -import scaladex.core.model.Jvm -import scaladex.core.model.License -import scaladex.core.model.MajorVersion -import scaladex.core.model.PatchVersion -import scaladex.core.model.Project import scaladex.core.model.Project.Settings -import scaladex.core.model.ProjectHeader -import scaladex.core.model.Scala -import scaladex.core.model.ScalaJs -import scaladex.core.model.ScalaNative -import scaladex.core.model.SemanticVersion -import scaladex.core.model.Url +import scaladex.core.model._ import scaladex.core.model.search.ProjectDocument object Values { @@ -34,15 +14,15 @@ object Values { val ok: GithubStatus = GithubStatus.Ok(now) val unknown: GithubStatus = GithubStatus.Unknown(now) - val `2.6.1` = PatchVersion(2, 6, 1) - val `4`: SemanticVersion = MajorVersion(4) - val `2.7.0` = PatchVersion(2, 7, 0) - val `7.0.0` = PatchVersion(7, 0, 0) - val `7.1.0` = PatchVersion(7, 1, 0) - val `7.2.0-PREVIEW.1` = SemanticVersion.parse("7.2.0-PREVIEW.1").get - val `7.2.0-PREVIEW.2` = SemanticVersion.parse("7.2.0-PREVIEW.2").get - val `7.2.0` = PatchVersion(7, 2, 0) - val `7.3.0` = PatchVersion(7, 3, 0) + val `2.6.1` = Version(2, 6, 1) + val `4`: Version = Version(4) + val `2.5.0` = Version(2, 5, 0) + val `7.0.0` = Version(7, 0, 0) + val `7.1.0` = Version(7, 1, 0) + val `7.2.0-PREVIEW.1` = Version("7.2.0-PREVIEW.1") + val `7.2.0-PREVIEW.2` = Version("7.2.0-PREVIEW.2") + val `7.2.0` = Version(7, 2, 0) + val `7.3.0` = Version(7, 3, 0) val `_2.13`: BinaryVersion = BinaryVersion(Jvm, Scala.`2.13`) val `_3`: BinaryVersion = BinaryVersion(Jvm, Scala.`3`) @@ -58,15 +38,12 @@ object Values { object Scalafix { val reference: Project.Reference = Project.Reference.from("scalacenter", "scalafix") + val creationDate: Instant = Instant.ofEpochMilli(1475505237265L) - val artifactId: ArtifactId = ArtifactId.parse("scalafix-core_2.13").get val artifact: Artifact = Artifact( groupId = GroupId("ch.epfl.scala"), - artifactId = artifactId.value, - version = SemanticVersion.parse("0.9.30").get, - artifactName = artifactId.name, - platform = artifactId.binaryVersion.platform, - language = artifactId.binaryVersion.language, + artifactId = ArtifactId("scalafix-core_2.13"), + version = Version("0.9.30"), projectRef = reference, description = None, releaseDate = creationDate, @@ -119,14 +96,10 @@ object Values { object PlayJsonExtra { val reference: Project.Reference = Project.Reference.from("xuwei-k", "play-json-extra") val creationDate: Instant = Instant.ofEpochMilli(1411736618000L) - val artifactId: ArtifactId = ArtifactId.parse("play-json-extra_2.11").get val artifact: Artifact = Artifact( groupId = GroupId("com.github.xuwei-k"), - artifactId = artifactId.value, - version = SemanticVersion.parse("0.1.1-play2.3-M1").get, - artifactName = artifactId.name, - platform = artifactId.binaryVersion.platform, - language = artifactId.binaryVersion.language, + artifactId = ArtifactId("play-json-extra_2.11"), + version = Version("0.1.1-play2.3-M1"), projectRef = reference, description = None, releaseDate = creationDate, @@ -140,13 +113,13 @@ object Values { ) val dependency: ArtifactDependency = ArtifactDependency( - source = Cats.`core_3:2.6.1`.mavenReference, - target = artifact.mavenReference, + source = Cats.`core_3:2.6.1`.reference, + target = artifact.reference, Scope("compile") ) val githubInfo: GithubInfo = GithubInfo.empty val settings: Project.Settings = Project.Settings.empty.copy( - defaultArtifact = Some(artifact.artifactName), + defaultArtifact = Some(artifact.name), category = Some(Category.Json) ) } @@ -178,22 +151,23 @@ object Values { `_3`, `2.6.1`, description = Some("Cats core"), - fullScalaVersion = SemanticVersion.parse("3.0.0"), + fullScalaVersion = Some(Version("3.0.0")), scaladocUrl = Some(Url("http://typelevel.org/cats/api/")), versionScheme = Some("semver-spec"), developers = developers("org.typelevel:cats-core_3:jar:2.6.1") ) val `core_2.13:2.6.1`: Artifact = getArtifact("cats-core", `_2.13`, `2.6.1`, description = Some("Cats core")) val `core_3:4`: Artifact = getArtifact("cats-core", `_3`, `4`, description = Some("Cats core")) - val `core_3:2.7.0`: Artifact = getArtifact( + val `core_2.13:2.5.0`: Artifact = getArtifact( "cats-core", - `_3`, - `2.7.0`, + `_2.13`, + `2.5.0`, description = Some("Cats core"), - fullScalaVersion = SemanticVersion.parse("3.0.2"), + fullScalaVersion = Some(Version(2, 13, 5)), scaladocUrl = Some(Url("http://typelevel.org/cats/api/")), versionScheme = Some("semver-spec"), - developers = developers("org.typelevel:cats-core_3:jar:2.7.0") + developers = developers("org.typelevel:cats-core_2.13:jar:2.5.0"), + creationDelta = -1 // created earlier ) val `core_sjs1_3:2.6.1`: Artifact = getArtifact( "cats-core", @@ -208,37 +182,31 @@ object Values { getArtifact("cats-core", `_sjs0.6_2.13`, `2.6.1`, description = Some("Cats core")) val `core_native04_2.13:2.6.1`: Artifact = getArtifact("cats-core", `_native0.4_2.13`, `2.6.1`, description = Some("Cats core")) - val `kernel_2.13`: Artifact = getArtifact("cats-kernel", `_2.13`, `2.6.1`) + val `kernel_2.13:2.6.1`: Artifact = getArtifact("cats-kernel", `_2.13`, `2.6.1`) val `kernel_3:2.6.1`: Artifact = getArtifact("cats-kernel", `_3`, `2.6.1`) val `laws_3:2.6.1`: Artifact = getArtifact("cats-laws", `_3`, `2.6.1`) - val allArtifacts: Seq[Artifact] = - Seq( - `core_3:2.6.1`, - `core_3:2.7.0`, - `core_sjs1_3:2.6.1`, - `core_sjs06_2.13:2.6.1`, - `core_native04_2.13:2.6.1`, - `kernel_3:2.6.1`, - `laws_3:2.6.1` - ) + val coreArtifacts: Seq[Artifact] = Seq( + `core_3:2.6.1`, + `core_2.13:2.5.0`, + `core_sjs1_3:2.6.1`, + `core_sjs06_2.13:2.6.1`, + `core_native04_2.13:2.6.1` + ) + val allArtifacts: Seq[Artifact] = coreArtifacts ++ Seq(`kernel_3:2.6.1`, `laws_3:2.6.1`) val dependencies: Seq[ArtifactDependency] = Seq( ArtifactDependency( - source = `core_3:2.6.1`.mavenReference, - target = `kernel_3:2.6.1`.mavenReference, + source = `core_3:2.6.1`.reference, + target = `kernel_3:2.6.1`.reference, Scope("compile") ), ArtifactDependency( - source = `core_3:2.6.1`.mavenReference, - target = `laws_3:2.6.1`.mavenReference, + source = `core_3:2.6.1`.reference, + target = `laws_3:2.6.1`.reference, Scope("compile") ), ArtifactDependency( - source = `core_3:2.6.1`.mavenReference, - target = MavenReference( - "com.gu", - "ztmp-scala-automation_2.10", - "1.9" - ), // dependency with a corresponding getArtifact + source = `core_3:2.6.1`.reference, + target = Artifact.Reference.from("com.gu", "ztmp-scala-automation_2.10", "1.9"), Scope("compile") ) ) @@ -273,24 +241,21 @@ object Values { private def getArtifact( name: String, binaryVersion: BinaryVersion, - version: SemanticVersion, + version: Version, description: Option[String] = None, - fullScalaVersion: Option[SemanticVersion] = None, + fullScalaVersion: Option[Version] = None, developers: Seq[Contributor] = Nil, scaladocUrl: Option[Url] = None, - versionScheme: Option[String] = None - ): Artifact = { - val artifactId = ArtifactId(Name(name), binaryVersion) + versionScheme: Option[String] = None, + creationDelta: Int = 0 + ): Artifact = Artifact( groupId = groupId, - artifactId = artifactId.value, + artifactId = ArtifactId(Name(name), binaryVersion), version = version, - artifactName = artifactId.name, - platform = binaryVersion.platform, - language = binaryVersion.language, projectRef = reference, description = description, - releaseDate = Instant.ofEpochMilli(1620911032000L), + releaseDate = Instant.ofEpochMilli(1620911032000L + 1000L * creationDelta), resolver = None, licenses = Set(license), isNonStandardLib = false, @@ -299,41 +264,32 @@ object Values { scaladocUrl = scaladocUrl, versionScheme = versionScheme ) - } } object CatsEffect { val dependency: ArtifactDependency = ArtifactDependency( - source = MavenReference( - "cats-effect", - "cats-effect-kernel_3", - "3.2.3" - ), - target = Cats.`core_3:2.6.1`.mavenReference, + source = Artifact.Reference.from("typelevel", "cats-effect-kernel_3", "3.2.3"), + target = Cats.`core_3:2.6.1`.reference, Scope("compile") ) val testDependency: ArtifactDependency = ArtifactDependency( - source = MavenReference( - "cats-effect", - "cats-effect-kernel_3", - "3.2.3" - ), - target = MavenReference("typelevel", "scalacheck_3", "1.15.4"), + source = Artifact.Reference.from("typelevel", "cats-effect-kernel_3", "3.2.3"), + target = Artifact.Reference.from("typelevel", "scalacheck_3", "1.15.4"), Scope("test") ) } object SbtCrossProject { val reference: Project.Reference = Project.Reference.from("portable-scala", "sbt-crossproject") - val mavenReference: MavenReference = - MavenReference("org.portable-scala", "sbt-scalajs-crossproject_2.12_1.0", "1.3.2") + val artifactRef: Artifact.Reference = + Artifact.Reference.from("org.portable-scala", "sbt-scalajs-crossproject_2.12_1.0", "1.3.2") val creationDate: Instant = Instant.ofEpochSecond(1688667180L) } object Scala3 { val organization: Project.Organization = Project.Organization("scala") - val reference: Project.Reference = Project.Reference.from("scala/scala3") + val reference: Project.Reference = Project.Reference.unsafe("scala/scala3") } } diff --git a/modules/data/src/main/scala/scaladex/data/Main.scala b/modules/data/src/main/scala/scaladex/data/Main.scala index 12da1ec04..99b98f2ab 100644 --- a/modules/data/src/main/scala/scaladex/data/Main.scala +++ b/modules/data/src/main/scala/scaladex/data/Main.scala @@ -33,12 +33,10 @@ object Main extends LazyLogging { } /** - * Update data: - * - pull the latest data from the 'contrib' repository - * - download data from Bintray and update the ElasticSearch index - * - commit the new state of the 'index' repository + * - subIndex: dump the database to JSON files under scaladex-index or scaladex-small-index + * - init: load the JSON files to the database * - * @param args: "central" or "init" + * @param args: "subIndex" or "init" */ def run(args: Array[String]): Unit = { val config = IndexConfig.load() @@ -104,7 +102,6 @@ object Main extends LazyLogging { logger.info(s"Executing $name") val (_, duration) = TimeUtils.measure(run()) logger.info(s"$name done in ${duration.prettyPrint}") - system.terminate() } } diff --git a/modules/data/src/main/scala/scaladex/data/SubIndex.scala b/modules/data/src/main/scala/scaladex/data/SubIndex.scala index 2a5ce3d62..d2febc0b5 100644 --- a/modules/data/src/main/scala/scaladex/data/SubIndex.scala +++ b/modules/data/src/main/scala/scaladex/data/SubIndex.scala @@ -15,7 +15,7 @@ class SubIndex(filesystem: Storage, database: SchedulerDatabase)(implicit ec: Ex def run(): Future[Unit] = { val projectSelection = Using.resource(Source.fromResource("subindex.txt", getClass.getClassLoader)) { source => - source.getLines().map(Project.Reference.from).toSet + source.getLines().map(Project.Reference.unsafe).toSet } for { @@ -29,9 +29,10 @@ class SubIndex(filesystem: Storage, database: SchedulerDatabase)(implicit ec: Ex } private def saveProject(project: Project): Future[Unit] = { - logger.info(s"Saving ${project.reference}") - val artifactsF = database.getArtifacts(project.reference) - val dependenciesF = database.getDependencies(project.reference) + val ref = project.reference + logger.info(s"Saving $ref") + val artifactsF = database.getAllProjectArtifacts(ref) + val dependenciesF = database.getProjectDependencies(ref) for { artifacts <- artifactsF dependencies <- dependenciesF diff --git a/modules/data/src/main/scala/scaladex/data/maven/ArtifactModel.scala b/modules/data/src/main/scala/scaladex/data/maven/ArtifactModel.scala index 119d6de8a..c9ed7ead9 100644 --- a/modules/data/src/main/scala/scaladex/data/maven/ArtifactModel.scala +++ b/modules/data/src/main/scala/scaladex/data/maven/ArtifactModel.scala @@ -83,7 +83,7 @@ case class Dependency( exclusions: Set[Exclusion] = Set(), optional: Boolean = false ) { - val mavenRef: Artifact.MavenReference = Artifact.MavenReference(groupId, artifactId, version) + val reference: Artifact.Reference = Artifact.Reference.from(groupId, artifactId, version) override def toString: String = s"$groupId $artifactId $version" } diff --git a/modules/infra/src/it/scala/scaladex/infra/GithubClientImplTests.scala b/modules/infra/src/it/scala/scaladex/infra/GithubClientImplTests.scala index cc7f94eab..d8880445a 100644 --- a/modules/infra/src/it/scala/scaladex/infra/GithubClientImplTests.scala +++ b/modules/infra/src/it/scala/scaladex/infra/GithubClientImplTests.scala @@ -39,7 +39,7 @@ class GithubClientImplTests extends AsyncFunSpec with Matchers { yield response should matchPattern { case GithubResponse.Ok(_) => () } } it("getRepository with no license") { - for (response <- client.getRepository(Project.Reference.from("mainstreethub/sbt-parent-plugin"))) + for (response <- client.getRepository(Project.Reference.unsafe("mainstreethub/sbt-parent-plugin"))) yield response match { case GithubResponse.Ok(repo) => repo.licenseName shouldBe empty case _ => fail() diff --git a/modules/infra/src/main/resources/migrations/V24__fix_language_3.sql b/modules/infra/src/main/resources/migrations/V24__fix_language_3.sql new file mode 100644 index 000000000..46c67f273 --- /dev/null +++ b/modules/infra/src/main/resources/migrations/V24__fix_language_3.sql @@ -0,0 +1 @@ +UPDATE artifacts SET language_version='3' WHERE language_version='3.x'; diff --git a/modules/infra/src/main/scala/scaladex/infra/Codecs.scala b/modules/infra/src/main/scala/scaladex/infra/Codecs.scala index 09886cc98..45ce3a424 100644 --- a/modules/infra/src/main/scala/scaladex/infra/Codecs.scala +++ b/modules/infra/src/main/scala/scaladex/infra/Codecs.scala @@ -12,6 +12,8 @@ import scaladex.core.util.Secret import scaladex.infra.github.GithubModel object Codecs { + implicit val customConfig: Configuration = Configuration.default.withDefaults + implicit val organization: Codec[Project.Organization] = fromString(_.value, Project.Organization.apply) implicit val repository: Codec[Project.Repository] = fromString(_.value, Project.Repository.apply) implicit val reference: Codec[Project.Reference] = deriveCodec @@ -41,16 +43,16 @@ object Codecs { implicit val projectCodec: Codec[Project] = deriveCodec implicit val groupIdCodec: Codec[Artifact.GroupId] = fromString(_.value, Artifact.GroupId.apply) - implicit val semanticVersionCodec: Codec[SemanticVersion] = fromString(_.encode, SemanticVersion.from) - implicit val platformCodec: Codec[Platform] = fromString(_.label, Platform.fromLabel(_).get) - implicit val languageCodec: Codec[Language] = fromString(_.label, Language.fromLabel(_).get) + implicit val artifactIdCodec: Codec[Artifact.ArtifactId] = fromString(_.value, Artifact.ArtifactId.apply) + implicit val semanticVersionCodec: Codec[Version] = fromString(_.value, Version.apply) + implicit val platformCodec: Codec[Platform] = fromString(_.value, Platform.parse(_).get) + implicit val languageCodec: Codec[Language] = fromString(_.value, Language.parse(_).get) implicit val resolverCodec: Codec[Resolver] = deriveCodec implicit val licenseCodec: Codec[License] = fromString(_.shortName, License.allByShortName.apply) - implicit val customConfig: Configuration = Configuration.default.withDefaults + implicit val artifactRefCodec: Codec[Artifact.Reference] = deriveCodec implicit val artifactCodec: Codec[Artifact] = deriveConfiguredCodec[Artifact] implicit val scopeCodec: Codec[ArtifactDependency.Scope] = fromString(_.value, ArtifactDependency.Scope.apply) - implicit val mavenRefCodec: Codec[Artifact.MavenReference] = deriveCodec implicit val dependenciesCodec: Codec[ArtifactDependency] = deriveCodec implicit val userStateCodec: Codec[UserState] = deriveCodec diff --git a/modules/infra/src/main/scala/scaladex/infra/CoursierResolver.scala b/modules/infra/src/main/scala/scaladex/infra/CoursierResolver.scala index 11b7474e3..ba597c11b 100644 --- a/modules/infra/src/main/scala/scaladex/infra/CoursierResolver.scala +++ b/modules/infra/src/main/scala/scaladex/infra/CoursierResolver.scala @@ -29,9 +29,9 @@ class CoursierResolver(implicit val ec: ExecutionContext) extends PomResolver wi .withArtifactTypes(Set(Type.pom)) .withRepositories(repositories) - def resolveSync(ref: Artifact.MavenReference): Path = { - val dep = Dependency(Module(Organization(ref.groupId), ModuleName(ref.artifactId)), ref.version) - .withPublication(ref.artifactId, Type.pom) + def resolveSync(ref: Artifact.Reference): Path = { + val dep = Dependency(Module(Organization(ref.groupId.value), ModuleName(ref.artifactId.value)), ref.version.value) + .withPublication(ref.artifactId.value, Type.pom) fetchPoms .addDependencies(dep) .run() diff --git a/modules/infra/src/main/scala/scaladex/infra/ElasticsearchEngine.scala b/modules/infra/src/main/scala/scaladex/infra/ElasticsearchEngine.scala index c51eaf5e5..a3e834e49 100644 --- a/modules/infra/src/main/scala/scaladex/infra/ElasticsearchEngine.scala +++ b/modules/infra/src/main/scala/scaladex/infra/ElasticsearchEngine.scala @@ -256,12 +256,10 @@ class ElasticsearchEngine(esClient: ElasticClient, index: String)(implicit ec: E .map(addMissing(params.topics)) override def countByLanguages(params: SearchParams): Future[Seq[(Language, Int)]] = - languageAggregation(filteredSearchQuery(params)) - .map(addMissing(params.languages.flatMap(Language.fromLabel))) + languageAggregation(filteredSearchQuery(params)).map(addMissing(params.languages)) override def countByPlatforms(params: SearchParams): Future[Seq[(Platform, Int)]] = - platformAggregations(filteredSearchQuery(params)) - .map(addMissing(params.platforms.flatMap(Platform.fromLabel))) + platformAggregations(filteredSearchQuery(params)).map(addMissing(params.platforms)) override def find( category: Category, @@ -270,7 +268,7 @@ class ElasticsearchEngine(esClient: ElasticClient, index: String)(implicit ec: E ): Future[Page[ProjectDocument]] = { val query = must( termQuery("category", category.label), - binaryVersionQuery(params.languages.map(_.label), params.platforms.map(_.label)) + binaryVersionQuery(params.languages, params.platforms) ) val request = searchRequest(query, params.sorting) @@ -289,7 +287,7 @@ class ElasticsearchEngine(esClient: ElasticClient, index: String)(implicit ec: E .map { versionAgg => for { (version, count) <- versionAgg.toList - language <- Language.fromLabel(version) + language <- Language.parse(version) } yield (language, count) } .map(_.sortBy(_._1)(Language.ordering.reverse)) @@ -299,7 +297,7 @@ class ElasticsearchEngine(esClient: ElasticClient, index: String)(implicit ec: E .map { versionAgg => for { (version, count) <- versionAgg.toList - platform <- Platform.fromLabel(version) + platform <- Platform.parse(version) } yield (platform, count) } .map(_.sortBy(_._1)(Platform.ordering.reverse)) @@ -330,7 +328,7 @@ class ElasticsearchEngine(esClient: ElasticClient, index: String)(implicit ec: E private def awesomeQuery(category: Category, params: AwesomeParams): Query = must( termQuery("category", category.label), - binaryVersionQuery(params.languages.map(_.label), params.platforms.map(_.label)) + binaryVersionQuery(params.languages, params.platforms) ) private def filteredSearchQuery(params: SearchParams): Query = @@ -431,13 +429,13 @@ class ElasticsearchEngine(esClient: ElasticClient, index: String)(implicit ec: E termQuery("repository.keyword", repo.repository.value) ) - private def binaryVersionQuery(languages: Seq[String], platforms: Seq[String]): Query = - must(languages.map(termQuery("languages", _)) ++ platforms.map(termQuery("platforms", _))) + private def binaryVersionQuery(languages: Seq[Language], platforms: Seq[Platform]): Query = + must(languages.map(l => termQuery("languages", l.value)) ++ platforms.map(p => termQuery("platforms", p.value))) private def binaryVersionQuery(binaryVersion: BinaryVersion): Query = must( - termQuery("platforms", binaryVersion.platform.label), - termQuery("languages", binaryVersion.language.label) + termQuery("platforms", binaryVersion.platform.value), + termQuery("languages", binaryVersion.language.value) ) private val contributingQuery = boolQuery().must( diff --git a/modules/infra/src/main/scala/scaladex/infra/GithubClientImpl.scala b/modules/infra/src/main/scala/scaladex/infra/GithubClientImpl.scala index bc317b0ed..e581b355d 100644 --- a/modules/infra/src/main/scala/scaladex/infra/GithubClientImpl.scala +++ b/modules/infra/src/main/scala/scaladex/infra/GithubClientImpl.scala @@ -221,7 +221,7 @@ class GithubClientImpl(token: Secret)(implicit val system: ActorSystem) val filtered = if (filterPermissions.isEmpty) repos else repos.filter(repo => filterPermissions.contains(repo.viewerPermission)) - filtered.map(repo => Project.Reference.from(repo.nameWithOwner)) + filtered.map(repo => Project.Reference.unsafe(repo.nameWithOwner)) } def getOrganizationRepositories( @@ -234,7 +234,7 @@ class GithubClientImpl(token: Secret)(implicit val system: ActorSystem) val filtered = if (filterPermissions.isEmpty) repos else repos.filter(repo => filterPermissions.contains(repo.viewerPermission)) - filtered.map(repo => Project.Reference.from(repo.nameWithOwner)) + filtered.map(repo => Project.Reference.unsafe(repo.nameWithOwner)) } private def getUserOrganizationsPage( diff --git a/modules/infra/src/main/scala/scaladex/infra/MavenCentralClientImpl.scala b/modules/infra/src/main/scala/scaladex/infra/MavenCentralClientImpl.scala index 6ab8c06a4..202c7f440 100644 --- a/modules/infra/src/main/scala/scaladex/infra/MavenCentralClientImpl.scala +++ b/modules/infra/src/main/scala/scaladex/infra/MavenCentralClientImpl.scala @@ -22,9 +22,8 @@ import org.apache.pekko.http.scaladsl.unmarshalling.Unmarshaller import org.apache.pekko.stream.scaladsl.Flow import org.apache.pekko.util.ByteString import scaladex.core.model.Artifact -import scaladex.core.model.Artifact.MavenReference import scaladex.core.model.SbtPlugin -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version import scaladex.core.service.MavenCentralClient import scaladex.core.util.JsoupUtils @@ -55,11 +54,11 @@ class MavenCentralClientImpl()(implicit val system: ActorSystem) page <- responseFuture.entity.dataBytes.runFold(ByteString(""))(_ ++ _).map(_.utf8String) } yield { val artifactIds = JsoupUtils.listDirectories(uri, page) - artifactIds.flatMap(Artifact.ArtifactId.parse) + artifactIds.map(Artifact.ArtifactId.apply) } } - def getAllVersions(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[SemanticVersion]] = { + def getAllVersions(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[Version]] = { val uri = s"$baseUri/${groupId.mavenUrl}/${artifactId.value}/" val request = HttpRequest(uri = uri) @@ -67,7 +66,7 @@ class MavenCentralClientImpl()(implicit val system: ActorSystem) responseFuture <- queueRequest(request) page <- Unmarshaller.stringUnmarshaller(responseFuture.entity) versions = JsoupUtils.listDirectories(uri, page) - versionParsed = versions.flatMap(SemanticVersion.parse) + versionParsed = versions.map(Version.apply) } yield versionParsed future.recoverWith { case NonFatal(exception) => @@ -76,14 +75,14 @@ class MavenCentralClientImpl()(implicit val system: ActorSystem) } } - override def getPomFile(mavenReference: Artifact.MavenReference): Future[Option[(String, Instant)]] = { + override def getPomFile(ref: Artifact.Reference): Future[Option[(String, Instant)]] = { val future = for { - responseOpt <- getHttpResponse(mavenReference) - res <- responseOpt.map(getPomFileWithLastModifiedTime).getOrElse(Future.successful(None)) + response <- getHttpResponse(ref) + res <- getPomFileWithLastModifiedTime(response) } yield res future.recoverWith { case NonFatal(exception) => - logger.warn(s"Could not get pom file of $mavenReference because of $exception") + logger.warn(s"Could not get pom file of $ref because of $exception") Future.successful(None) } } @@ -98,29 +97,21 @@ class MavenCentralClientImpl()(implicit val system: ActorSystem) case _ => Future.successful(None) } - private def getHttpResponse(mavenReference: MavenReference): Future[Option[HttpResponse]] = { - val groupIdUrl: String = mavenReference.groupId.replace('.', '/') - Artifact.ArtifactId - .parse(mavenReference.artifactId) - .map { artifactId => - val pomUrl = getPomUrl(artifactId, mavenReference.version) - val uri = s"$baseUri/${groupIdUrl}/${mavenReference.artifactId}/${mavenReference.version}/$pomUrl" - val request = HttpRequest(uri = uri) - queueRequest(request).map(Option.apply) - } - .getOrElse { - logger.info(s"not able to parse ${mavenReference.artifactId} as ArtifactId") - Future.successful(None) - } + private def getHttpResponse(ref: Artifact.Reference): Future[HttpResponse] = { + val groupIdUrl: String = ref.groupId.value.replace('.', '/') + val pomUrl = getPomFileName(ref.artifactId, ref.version) + val uri = s"$baseUri/${groupIdUrl}/${ref.artifactId.value}/${ref.version.value}/$pomUrl" + val request = HttpRequest(uri = uri) + queueRequest(request) } // Wed, 04 Nov 2020 23:36:02 GMT private val dateFormatter = DateTimeFormatter.RFC_1123_DATE_TIME private[infra] def parseDate(dateStr: String): Instant = ZonedDateTime.parse(dateStr, dateFormatter).toInstant - private def getPomUrl(artifactId: Artifact.ArtifactId, version: String): String = + private def getPomFileName(artifactId: Artifact.ArtifactId, version: Version): String = artifactId.binaryVersion.platform match { - case SbtPlugin(_) => s"${artifactId.name.value}-$version.pom" - case _ => s"${artifactId.value}-${version}.pom" + case SbtPlugin(_) => s"${artifactId.name.value}-${version.value}.pom" + case _ => s"${artifactId.value}-${version.value}.pom" } } diff --git a/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala b/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala index 56a54d55e..d84fc985a 100644 --- a/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala +++ b/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala @@ -10,26 +10,9 @@ import cats.effect.IO import com.typesafe.scalalogging.LazyLogging import com.zaxxer.hikari.HikariDataSource import doobie.implicits._ -import scaladex.core.model.Artifact -import scaladex.core.model.ArtifactDependency -import scaladex.core.model.GithubInfo -import scaladex.core.model.GithubStatus -import scaladex.core.model.Language -import scaladex.core.model.Platform -import scaladex.core.model.Project -import scaladex.core.model.ProjectDependency -import scaladex.core.model.SemanticVersion -import scaladex.core.model.UserInfo -import scaladex.core.model.UserState +import scaladex.core.model._ import scaladex.core.service.SchedulerDatabase -import scaladex.infra.sql.ArtifactDependencyTable -import scaladex.infra.sql.ArtifactTable -import scaladex.infra.sql.DoobieUtils -import scaladex.infra.sql.GithubInfoTable -import scaladex.infra.sql.ProjectDependenciesTable -import scaladex.infra.sql.ProjectSettingsTable -import scaladex.infra.sql.ProjectTable -import scaladex.infra.sql.UserSessionsTable +import scaladex.infra.sql._ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) extends SchedulerDatabase with LazyLogging { private val flyway = DoobieUtils.flyway(datasource) @@ -37,40 +20,34 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten def dropTables: IO[Unit] = IO(flyway.clean()) override def insertArtifact(artifact: Artifact): Future[Boolean] = - run(ArtifactTable.insertIfNotExist(artifact)).map(_ >= 1) + run(ArtifactTable.insertIfNotExist.run(artifact)).map(_ >= 1) + + override def getArtifactVersions( + groupId: Artifact.GroupId, + artifactId: Artifact.ArtifactId, + stableOnly: Boolean + ): Future[Seq[Version]] = + run(ArtifactTable.selectVersionByGroupIdAndArtifactId(stableOnly).to[Seq]((groupId, artifactId))) + + override def getLatestArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] = + run(ArtifactTable.selectLatestArtifact.option((groupId, artifactId))) override def insertArtifacts(artifacts: Seq[Artifact]): Future[Unit] = - run(ArtifactTable.insertIfNotExist(artifacts)).map(_ => ()) + run(ArtifactTable.insertIfNotExist.updateMany(artifacts)).map(_ => ()) - override def updateArtifacts(artifacts: Seq[Artifact], newRef: Project.Reference): Future[Int] = { - val mavenReferences = artifacts.map(r => newRef -> r.mavenReference) - run(ArtifactTable.updateProjectRef.updateMany(mavenReferences)) + override def updateArtifacts(artifacts: Seq[Artifact.Reference], newRef: Project.Reference): Future[Int] = { + val references = artifacts.map(newRef -> _) + run(ArtifactTable.updateProjectRef.updateMany(references)) } - override def updateArtifactReleaseDate(reference: Artifact.MavenReference, releaseDate: Instant): Future[Int] = - run(ArtifactTable.updateReleaseDate.run((releaseDate, reference))) + override def updateArtifactReleaseDate(ref: Artifact.Reference, releaseDate: Instant): Future[Int] = + run(ArtifactTable.updateReleaseDate.run((releaseDate, ref))) - override def getArtifacts(groupId: Artifact.GroupId, artifactId: String): Future[Seq[Artifact]] = + override def getArtifacts(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[Artifact]] = run(ArtifactTable.selectArtifactByGroupIdAndArtifactId.to[Seq](groupId, artifactId)) - override def getArtifacts(projectRef: Project.Reference): Future[Seq[Artifact]] = - run(ArtifactTable.selectArtifactByProject.to[Seq](projectRef)) - - override def getArtifacts( - ref: Project.Reference, - artifactName: Artifact.Name, - version: SemanticVersion - ): Future[Seq[Artifact]] = - run(ArtifactTable.selectArtifactBy.to[Seq](ref, artifactName, version)) - - override def getArtifactsByName(ref: Project.Reference, artifactName: Artifact.Name): Future[Seq[Artifact]] = - run(ArtifactTable.selectArtifactByProjectAndName.to[Seq]((ref, artifactName))) - - override def getLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]] = - run(ArtifactTable.selectLatestArtifacts.to[Seq](ref)) - - override def getArtifactByMavenReference(mavenRef: Artifact.MavenReference): Future[Option[Artifact]] = - run(ArtifactTable.selectByMavenReference.option(mavenRef)) + override def getArtifact(ref: Artifact.Reference): Future[Option[Artifact]] = + run(ArtifactTable.selectByReference.option(ref)) override def getAllArtifacts(language: Option[Language], platform: Option[Platform]): Future[Seq[Artifact]] = run(ArtifactTable.selectAllArtifacts(language, platform).to[Seq]) @@ -116,12 +93,50 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten override def updateProjectSettings(ref: Project.Reference, settings: Project.Settings): Future[Unit] = run(ProjectSettingsTable.insertOrUpdate.run((ref, settings, settings))).map(_ => ()) - override def getProject(projectRef: Project.Reference): Future[Option[Project]] = - run(ProjectTable.selectByReference.option(projectRef)) + override def getProject(ref: Project.Reference): Future[Option[Project]] = + run(ProjectTable.selectByReference.option(ref)) + + override def getProjectArtifactRefs(ref: Project.Reference, stableOnly: Boolean): Future[Seq[Artifact.Reference]] = + run(ArtifactTable.selectArtifactRefByProject(stableOnly).to[Seq](ref)) + + override def getProjectArtifactRefs(ref: Project.Reference, name: Artifact.Name): Future[Seq[Artifact.Reference]] = + run(ArtifactTable.selectArtifactRefByProjectAndName.to[Seq]((ref, name))) + + override def getProjectArtifactRefs( + ref: Project.Reference, + version: Version + ): Future[Seq[Artifact.Reference]] = + run(ArtifactTable.selectArtifactRefByProjectAndVersion.to[Seq]((ref, version))) + + override def getAllProjectArtifacts(ref: Project.Reference): Future[Seq[Artifact]] = + run(ArtifactTable.selectArtifactByProject.to[Seq](ref)) + + override def getProjectArtifacts( + ref: Project.Reference, + name: Artifact.Name, + stableOnly: Boolean + ): Future[Seq[Artifact]] = + run(ArtifactTable.selectArtifactByProjectAndName(stableOnly).to[Seq](ref, name)) + + override def getProjectArtifacts( + ref: Project.Reference, + name: Artifact.Name, + version: Version + ): Future[Seq[Artifact]] = + run(ArtifactTable.selectArtifactByProjectAndNameAndVersion.to[Seq](ref, name, version)) + + override def getProjectLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]] = + run(ArtifactTable.selectLatestArtifacts.to[Seq](ref)) - override def getDependencies(projectRef: Project.Reference): Future[Seq[ArtifactDependency]] = + override def getProjectDependencies(projectRef: Project.Reference): Future[Seq[ArtifactDependency]] = run(ArtifactDependencyTable.selectDependencyFromProject.to[Seq](projectRef)) + override def getProjectDependencies( + ref: Project.Reference, + version: Version + ): Future[Seq[ProjectDependency]] = + run(ProjectDependenciesTable.getDependencies.to[Seq]((ref, version))) + override def getFormerReferences(projectRef: Project.Reference): Future[Seq[Project.Reference]] = run(ProjectTable.selectByNewReference.to[Seq](projectRef)) @@ -135,10 +150,10 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten run(ArtifactDependencyTable.count.unique) override def getDirectDependencies(artifact: Artifact): Future[Seq[ArtifactDependency.Direct]] = - run(ArtifactDependencyTable.selectDirectDependency.to[Seq](artifact.mavenReference)) + run(ArtifactDependencyTable.selectDirectDependency.to[Seq](artifact.reference)) override def getReverseDependencies(artifact: Artifact): Future[Seq[ArtifactDependency.Reverse]] = - run(ArtifactDependencyTable.selectReverseDependency.to[Seq](artifact.mavenReference)) + run(ArtifactDependencyTable.selectReverseDependency.to[Seq](artifact.reference)) def countGithubInfo(): Future[Long] = run(GithubInfoTable.count.unique) @@ -148,7 +163,7 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten override def computeProjectDependencies( ref: Project.Reference, - version: SemanticVersion + version: Version ): Future[Seq[ProjectDependency]] = run(ArtifactDependencyTable.computeProjectDependencies.to[Seq]((ref, version))) @@ -162,32 +177,24 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten override def countProjectDependents(projectRef: Project.Reference): Future[Long] = run(ProjectDependenciesTable.countDependents.unique(projectRef)) - override def getProjectDependencies( - ref: Project.Reference, - version: SemanticVersion - ): Future[Seq[ProjectDependency]] = - run(ProjectDependenciesTable.getDependencies.to[Seq]((ref, version))) - override def getProjectDependents(ref: Project.Reference): Future[Seq[ProjectDependency]] = run(ProjectDependenciesTable.getDependents.to[Seq](ref)) - override def computeAllProjectsCreationDates(): Future[Seq[(Instant, Project.Reference)]] = + override def computeProjectsCreationDates(): Future[Seq[(Instant, Project.Reference)]] = run(ArtifactTable.selectOldestByProject.to[Seq]) override def updateProjectCreationDate(ref: Project.Reference, creationDate: Instant): Future[Unit] = run(ProjectTable.updateCreationDate.run((creationDate, ref))).map(_ => ()) - override def getAllGroupIds(): Future[Seq[Artifact.GroupId]] = + override def getGroupIds(): Future[Seq[Artifact.GroupId]] = run(ArtifactTable.selectGroupIds.to[Seq]) - override def getArtifactIds(ref: Project.Reference): Future[Seq[(Artifact.GroupId, String)]] = + override def getArtifactIds(ref: Project.Reference): Future[Seq[(Artifact.GroupId, Artifact.ArtifactId)]] = run(ArtifactTable.selectArtifactIds.to[Seq](ref)) - override def getAllMavenReferences(): Future[Seq[Artifact.MavenReference]] = - run(ArtifactTable.selectAllMavenReferences.to[Seq]) + override def getArtifactRefs(): Future[Seq[Artifact.Reference]] = + run(ArtifactTable.selectReferences.to[Seq]) - override def getMavenReferences(ref: Project.Reference): Future[Seq[Artifact.MavenReference]] = - run(ArtifactTable.selectMavenReferences.to[Seq](ref)) override def insertUser(userId: UUID, userInfo: UserInfo): Future[Unit] = run(UserSessionsTable.insert.run((userId, userInfo)).map(_ => ())) @@ -203,14 +210,7 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten override def deleteUser(userId: UUID): Future[Unit] = run(UserSessionsTable.deleteById.run(userId).map(_ => ())) - override def getArtifacts( - ref: Project.Reference, - artifactName: Artifact.Name, - stableOnly: Boolean - ): Future[Seq[Artifact]] = - run(ArtifactTable.selectArtifactByParams(stableOnly).to[Seq](ref, artifactName)) - - override def updateLatestVersion(ref: Artifact.MavenReference): Future[Unit] = { + override def updateLatestVersion(ref: Artifact.Reference): Future[Unit] = { val transaction = for { _ <- ArtifactTable.setLatestVersion.run(ref) _ <- ArtifactTable.unsetOthersLatestVersion.run(ref) diff --git a/modules/infra/src/main/scala/scaladex/infra/elasticsearch/RawProjectDocument.scala b/modules/infra/src/main/scala/scaladex/infra/elasticsearch/RawProjectDocument.scala index 5549ea23c..6ddc3be7e 100644 --- a/modules/infra/src/main/scala/scaladex/infra/elasticsearch/RawProjectDocument.scala +++ b/modules/infra/src/main/scala/scaladex/infra/elasticsearch/RawProjectDocument.scala @@ -11,7 +11,7 @@ import scaladex.core.model.Category import scaladex.core.model.Language import scaladex.core.model.Platform import scaladex.core.model.Project -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version import scaladex.core.model.search.GithubInfoDocument import scaladex.core.model.search.ProjectDocument @@ -41,9 +41,9 @@ case class RawProjectDocument( hasCli, creationDate, updateDate, - languages.flatMap(Language.fromLabel).sorted, - platforms.flatMap(Platform.fromLabel).sorted, - latestVersion.flatMap(SemanticVersion.parse), + languages.flatMap(Language.parse).sorted, + platforms.flatMap(Platform.parse).sorted, + latestVersion.map(Version.apply), dependents, category.flatMap(Category.byLabel.get), formerReferences, @@ -67,9 +67,9 @@ object RawProjectDocument { hasCli, creationDate, updateDate, - languages.map(_.label), - platforms.map(_.label), - latestVersion.map(_.encode), + languages.map(_.value), + platforms.map(_.value), + latestVersion.map(_.value), dependents, category.map(_.label), formerReferences, diff --git a/modules/infra/src/main/scala/scaladex/infra/migrations/V13_2__update_new_fields_in_artifacts.scala b/modules/infra/src/main/scala/scaladex/infra/migrations/V13_2__update_new_fields_in_artifacts.scala index e83f767de..f77945016 100644 --- a/modules/infra/src/main/scala/scaladex/infra/migrations/V13_2__update_new_fields_in_artifacts.scala +++ b/modules/infra/src/main/scala/scaladex/infra/migrations/V13_2__update_new_fields_in_artifacts.scala @@ -6,10 +6,8 @@ import doobie.util.update.Update import org.flywaydb.core.api.migration.BaseJavaMigration import org.flywaydb.core.api.migration.Context import scaladex.core.model.Artifact -import scaladex.core.model.Artifact.MavenReference import scaladex.infra.sql.DoobieUtils.Mappings._ -import scaladex.infra.sql.DoobieUtils.selectRequest -import scaladex.infra.sql.DoobieUtils.updateRequest +import scaladex.infra.sql.DoobieUtils._ class V13_2__update_new_fields_in_artifacts extends BaseJavaMigration with ScaladexBaseMigration with LazyLogging { override def migrate(context: Context): Unit = @@ -30,9 +28,9 @@ class V13_2__update_new_fields_in_artifacts extends BaseJavaMigration with Scala } val selectArtifact: Query0[Artifact] = selectRequest("artifacts", Seq("*")) - val updateNewFields: Update[(Boolean, Boolean, MavenReference)] = + val updateNewFields: Update[(Boolean, Boolean, Artifact.Reference)] = updateRequest("artifacts", Seq("is_semantic", "is_prerelease"), Seq("group_id", "artifact_id", "version")) - private def update(artifact: Artifact): (Boolean, Boolean, MavenReference) = - (artifact.version.isSemantic, artifact.version.isPreRelease, artifact.mavenReference) + private def update(artifact: Artifact): (Boolean, Boolean, Artifact.Reference) = + (artifact.version.isSemantic, artifact.version.isPreRelease, artifact.reference) } diff --git a/modules/infra/src/main/scala/scaladex/infra/migrations/V17__add_mill_platform.scala b/modules/infra/src/main/scala/scaladex/infra/migrations/V17__add_mill_platform.scala index 23fa4fd89..baec653ca 100644 --- a/modules/infra/src/main/scala/scaladex/infra/migrations/V17__add_mill_platform.scala +++ b/modules/infra/src/main/scala/scaladex/infra/migrations/V17__add_mill_platform.scala @@ -12,31 +12,27 @@ class V17__add_mill_platform extends BaseJavaMigration with ScaladexBaseMigratio override def migrate(context: Context): Unit = { val migrateIO = for { oldArtifacts <- run(xa)(selectArtifact.to[Seq]) - newArtifacts = oldArtifacts.map { a => - val newId = Artifact.ArtifactId.parse(a.artifactId).get - a.copy(artifactName = newId.name, platform = newId.binaryVersion.platform) - } - (toUpdate, toDelete) = newArtifacts.partition(a => isValidMillPlugin(a)) + (toUpdate, toDelete) = oldArtifacts.partition(a => isValidMillPlugin(a)) _ <- run(xa)(updateNewFields.updateMany(toUpdate.map(update))) - _ <- run(xa)(delete.updateMany(toDelete.map(_.mavenReference))) + _ <- run(xa)(delete.updateMany(toDelete.map(_.reference))) } yield () migrateIO.unsafeRunSync() } val selectArtifact: Query0[Artifact] = selectRequest("artifacts", Seq("*"), where = Seq("artifact_name LIKE '%_mill0_%'")) - val updateNewFields: Update[(Artifact.Name, Platform, Artifact.MavenReference)] = + val updateNewFields: Update[(Artifact.Name, Platform, Artifact.Reference)] = updateRequest("artifacts", Seq("artifact_name", "platform"), Seq("group_id", "artifact_id", "version")) - val delete: Update[Artifact.MavenReference] = deleteRequest("artifacts", Seq("group_id", "artifact_id", "version")) + val delete: Update[Artifact.Reference] = deleteRequest("artifacts", Seq("group_id", "artifact_id", "version")) - private def update(artifact: Artifact): (Artifact.Name, Platform, Artifact.MavenReference) = - (artifact.artifactName, artifact.platform, artifact.mavenReference) + private def update(artifact: Artifact): (Artifact.Name, Platform, Artifact.Reference) = + (artifact.name, artifact.platform, artifact.reference) private def isValidMillPlugin(artifact: Artifact): Boolean = artifact.platform match { - case MillPlugin(MinorVersion(_, _)) => true - case MillPlugin(v @ MajorVersion(_)) => + case MillPlugin(Version.Minor(_, _)) => true + case MillPlugin(v @ Version.Major(_)) => throw new Exception(s"Unexpected artifact with Mill version $v in ${artifact.artifactId}") case MillPlugin(_) => false case p => throw new Exception(s"Unexpected platform $p in ${artifact.artifactId}") diff --git a/modules/infra/src/main/scala/scaladex/infra/migrations/V7_2__edit_platform_and_language.scala b/modules/infra/src/main/scala/scaladex/infra/migrations/V7_2__edit_platform_and_language.scala index 1e583427b..d9d9d548f 100644 --- a/modules/infra/src/main/scala/scaladex/infra/migrations/V7_2__edit_platform_and_language.scala +++ b/modules/infra/src/main/scala/scaladex/infra/migrations/V7_2__edit_platform_and_language.scala @@ -9,7 +9,6 @@ import doobie.implicits._ import doobie.util.update.Update import org.flywaydb.core.api.migration.BaseJavaMigration import org.flywaydb.core.api.migration.Context -import scaladex.core.model.Artifact.MavenReference import scaladex.core.model._ import scaladex.infra.sql.DoobieUtils.Mappings._ import scaladex.infra.sql.DoobieUtils.selectRequest @@ -39,7 +38,7 @@ class V7_2__edit_platform_and_language extends BaseJavaMigration with ScaladexBa val selectArtifact: Query0[OldArtifact] = selectRequest("artifacts", Seq("*")) - val updatePlatformAndLanguage: Update[(Platform, Language, MavenReference)] = + val updatePlatformAndLanguage: Update[(Platform, Language, Artifact.Reference)] = updateRequest("artifacts", Seq("platform", "language_version"), Seq("group_id", "artifact_id", "version")) } @@ -47,7 +46,7 @@ object V7_2__edit_platform_and_language { case class OldArtifact( groupId: Artifact.GroupId, artifactId: String, - version: SemanticVersion, + version: Version, artifactName: Artifact.Name, binaryVersion: BinaryVersion, projectRef: Project.Reference, @@ -57,8 +56,8 @@ object V7_2__edit_platform_and_language { licenses: Set[License], isNonStandardLib: Boolean ) { - def update: (Platform, Language, MavenReference) = { - val mavenRef = MavenReference(groupId = groupId.value, artifactId = artifactId, version = version.toString) + def update: (Platform, Language, Artifact.Reference) = { + val mavenRef = Artifact.Reference(groupId, Artifact.ArtifactId(artifactId), version) (binaryVersion.platform, binaryVersion.language, mavenRef) } } diff --git a/modules/infra/src/main/scala/scaladex/infra/migrations/V9__fix_platform_and_language.scala b/modules/infra/src/main/scala/scaladex/infra/migrations/V9__fix_platform_and_language.scala index 5540ca911..b1ea26321 100644 --- a/modules/infra/src/main/scala/scaladex/infra/migrations/V9__fix_platform_and_language.scala +++ b/modules/infra/src/main/scala/scaladex/infra/migrations/V9__fix_platform_and_language.scala @@ -4,7 +4,6 @@ import doobie.Query0 import doobie.util.update.Update import org.flywaydb.core.api.migration.BaseJavaMigration import org.flywaydb.core.api.migration.Context -import scaladex.core.model.Artifact.MavenReference import scaladex.core.model._ import scaladex.infra.sql.DoobieUtils.Mappings._ import scaladex.infra.sql.DoobieUtils.selectRequest @@ -13,16 +12,13 @@ import scaladex.infra.sql.DoobieUtils.updateRequest class V9__fix_platform_and_language extends BaseJavaMigration with ScaladexBaseMigration with LazyLogging { override def migrate(context: Context): Unit = try { - (for { - artifactToFix <- run(xa)(selectArtifact.to[Seq]) - artifactToFixWithIds = artifactToFix.flatMap(a => Artifact.ArtifactId.parse(a.artifactId).map(a -> _)) - _ <- run(xa) { - updatePlatformAndLanguage.updateMany(artifactToFixWithIds.map { - case (artifact, id) => (id.binaryVersion.platform, id.binaryVersion.language, artifact.mavenReference) - }) - } - } yield ()) - .unsafeRunSync() + val request = + for { + artifacts0 <- run(xa)(selectArtifact.to[Seq]) + artifacts1 = artifacts0.map(a => (a.platform, a.language, a.reference)) + _ <- run(xa)(updatePlatformAndLanguage.updateMany(artifacts1)) + } yield () + request.unsafeRunSync() } catch { case e: Throwable => @@ -33,7 +29,7 @@ class V9__fix_platform_and_language extends BaseJavaMigration with ScaladexBaseM val selectArtifact: Query0[Artifact] = selectRequest("artifacts", Seq("*"), where = Seq("language_version = 'Java'", "version ~ '^[^.]*$'")) - val updatePlatformAndLanguage: Update[(Platform, Language, MavenReference)] = + val updatePlatformAndLanguage: Update[(Platform, Language, Artifact.Reference)] = updateRequest("artifacts", Seq("platform", "language_version"), Seq("group_id", "artifact_id", "version")) } diff --git a/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactDependencyTable.scala b/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactDependencyTable.scala index db4193de5..7b830c319 100644 --- a/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactDependencyTable.scala +++ b/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactDependencyTable.scala @@ -6,7 +6,7 @@ import scaladex.core.model.Artifact import scaladex.core.model.ArtifactDependency import scaladex.core.model.Project import scaladex.core.model.ProjectDependency -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version import scaladex.infra.sql.DoobieUtils.Mappings._ import scaladex.infra.sql.DoobieUtils._ @@ -38,8 +38,7 @@ object ArtifactDependencyTable { s"d.target_version = t.version)" private val dependencyAndArtifactFields = - fields.map("d." + _) ++ - ArtifactTable.fields.map("a." + _) + fields.map("d." + _) ++ ArtifactTable.mainFields.map("a." + _) val insertIfNotExist: Update[ArtifactDependency] = insertOrUpdateRequest(table, fields, fields) @@ -47,25 +46,25 @@ object ArtifactDependencyTable { val count: doobie.Query0[Long] = selectRequest(table, Seq("COUNT(*)")) - val select: Query[Artifact.MavenReference, ArtifactDependency] = + val select: Query[Artifact.Reference, ArtifactDependency] = selectRequest(table, fields, sourceFields) - val selectDirectDependency: doobie.Query[Artifact.MavenReference, ArtifactDependency.Direct] = + val selectDirectDependency: doobie.Query[Artifact.Reference, ArtifactDependency.Direct] = selectRequest( tableWithTargetArtifact, dependencyAndArtifactFields, sourceFields.map(f => s"d.$f") ) - val selectReverseDependency: Query[Artifact.MavenReference, ArtifactDependency.Reverse] = + val selectReverseDependency: Query[Artifact.Reference, ArtifactDependency.Reverse] = selectRequest( tableWithSourceArtifact, dependencyAndArtifactFields, targetFields.map(f => s"d.$f") ) - val computeProjectDependencies: Query[(Project.Reference, SemanticVersion), ProjectDependency] = - selectRequest1[(Project.Reference, SemanticVersion, Project.Reference), ProjectDependency]( + val computeProjectDependencies: Query[(Project.Reference, Version), ProjectDependency] = + selectRequest1[(Project.Reference, Version, Project.Reference), ProjectDependency]( fullJoin, Seq("d.organization", "d.repository", "d.version", "t.organization", "t.repository", "t.version", "d.scope"), where = Seq("d.organization=?", "d.repository=?", "d.version=?", "(t.organization<>? OR t.repository<>?)"), diff --git a/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactTable.scala b/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactTable.scala index 2cac7f3f3..692381914 100644 --- a/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactTable.scala +++ b/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactTable.scala @@ -4,24 +4,20 @@ import java.time.Instant import doobie._ import doobie.util.update.Update -import scaladex.core.model.Artifact -import scaladex.core.model.Language -import scaladex.core.model.Platform -import scaladex.core.model.Project -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Artifact._ +import scaladex.core.model._ import scaladex.infra.sql.DoobieUtils.Mappings._ import scaladex.infra.sql.DoobieUtils._ object ArtifactTable { private[sql] val table = "artifacts" - val mavenReferenceFields: Seq[String] = Seq("group_id", "artifact_id", "version") + val referenceFields: Seq[String] = Seq("group_id", "artifact_id", "version") val projectReferenceFields: Seq[String] = Seq("organization", "repository") // Don't use `select *...` but `select fields` // the table have more fields than in Artifact instance - private[sql] val fields = mavenReferenceFields ++ - Seq("artifact_name") ++ + private[sql] val mainFields = referenceFields ++ projectReferenceFields ++ Seq( "description", @@ -29,25 +25,20 @@ object ArtifactTable { "resolver", "licenses", "is_non_standard_Lib", - "platform", - "language_version", "full_scala_version", "scaladoc_url", "version_scheme", "developers" ) // these field are usually excluded when we read artifacts from the artifacts table. - val versionFields: Seq[String] = Seq("is_semantic", "is_prerelease") + val extraFields: Seq[String] = Seq("artifact_name", "platform", "language_version", "is_prerelease") val isLatestVersion: String = "is_latest_version" - def insertIfNotExist(artifact: Artifact): ConnectionIO[Int] = - insertIfNotExist.run((artifact, artifact.version.isSemantic, artifact.version.isPreRelease, artifact)) - - def insertIfNotExist(artifacts: Seq[Artifact]): ConnectionIO[Int] = - insertIfNotExist.updateMany(artifacts.map(a => (a, a.version.isSemantic, a.version.isPreRelease, a))) - - private[sql] val insertIfNotExist: Update[(Artifact, Boolean, Boolean, Artifact)] = - insertOrUpdateRequest(table, fields ++ versionFields, mavenReferenceFields, fields) + val insertIfNotExist: Update[Artifact] = { + type T = (Artifact, Name, Platform, Language, Boolean, Artifact) + insertOrUpdateRequest[T](table, mainFields ++ extraFields, referenceFields, mainFields) + .contramap[Artifact](a => (a, a.name, a.platform, a.language, a.version.isPreRelease, a)) + } val count: Query0[Long] = selectRequest(table, Seq("COUNT(*)")) @@ -55,65 +46,98 @@ object ArtifactTable { val countVersionsByProject: Query[Project.Reference, Long] = selectRequest(table, Seq("Count(DISTINCT version)"), projectReferenceFields) - val updateProjectRef: Update[(Project.Reference, Artifact.MavenReference)] = - updateRequest(table, projectReferenceFields, mavenReferenceFields) + val updateProjectRef: Update[(Project.Reference, Reference)] = + updateRequest(table, projectReferenceFields, referenceFields) - val updateReleaseDate: Update[(Instant, Artifact.MavenReference)] = - updateRequest(table, Seq("release_date"), mavenReferenceFields) + val updateReleaseDate: Update[(Instant, Reference)] = + updateRequest(table, Seq("release_date"), referenceFields) def selectAllArtifacts(language: Option[Language], platform: Option[Platform]): Query0[Artifact] = { - val where = language.map(v => s"language_version='${v.label}'").toSeq ++ platform.map(p => s"platform='${p.label}'") - selectRequest(table, fields, where = where) + val where = language.map(v => s"language_version='${v.value}'").toSeq ++ platform.map(p => s"platform='${p.value}'") + selectRequest(table, mainFields, where = where) } - val selectArtifactByGroupIdAndArtifactId: Query[(Artifact.GroupId, String), Artifact] = - selectRequest(table, fields, Seq("group_id", "artifact_id")) + val selectArtifactByGroupIdAndArtifactId: Query[(GroupId, ArtifactId), Artifact] = + selectRequest(table, mainFields, Seq("group_id", "artifact_id")) + + def selectVersionByGroupIdAndArtifactId(stableOnly: Boolean): Query[(GroupId, ArtifactId), Version] = + selectRequest1( + table, + Seq("version"), + keys = Seq("group_id", "artifact_id"), + where = stableOnlyFilter(stableOnly).toSeq + ) + + val selectLatestArtifact: Query[(GroupId, ArtifactId), Artifact] = + selectRequest1(table, mainFields, keys = Seq("group_id", "artifact_id"), where = Seq("is_latest_version")) val selectArtifactByProject: Query[Project.Reference, Artifact] = selectRequest1( table, - fields, - where = projectReferenceFields.map(f => s"$f=?"), + mainFields, + keys = projectReferenceFields, orderBy = Some("release_date DESC"), limit = Some(10000) ) - val selectArtifactBy: Query[(Project.Reference, Artifact.Name, SemanticVersion), Artifact] = + def selectArtifactRefByProject(stableOnly: Boolean): Query[Project.Reference, Reference] = + // implicit val artifactNamesMeta = Meta[Array[String]].timap(_.toSeq.map(Name.apply))(_.map(_.value).toArray) + // val nameFilter = if (names.isEmpty) "?::varchar[] IS NOT NULL" else "artifact_name = ANY(?)" + selectRequest1[Project.Reference, Reference]( + table, + referenceFields, + keys = projectReferenceFields, + where = stableOnlyFilter(stableOnly) + ) + + def selectArtifactRefByProjectAndName: Query[(Project.Reference, Artifact.Name), Reference] = selectRequest1( table, - fields, - where = Seq("organization=?", "repository=?", "artifact_name=?", "version=?") + referenceFields, + keys = projectReferenceFields ++ Seq("artifact_name") ) - def selectArtifactByParams(stableOnly: Boolean): Query[(Project.Reference, Artifact.Name), Artifact] = { - val preReleaseFilter = if (stableOnly) "is_prerelease=false" else "true" - Query[(Project.Reference, Artifact.Name), Artifact]( - s"""|SELECT ${fields.mkString(", ")} - |FROM $table - |WHERE organization=? AND repository=? AND artifact_name=? AND $preReleaseFilter - |""".stripMargin + def selectArtifactRefByProjectAndVersion: Query[(Project.Reference, Version), Reference] = + selectRequest1( + table, + referenceFields, + keys = projectReferenceFields ++ Seq("version") + ) + + def selectArtifactByProjectAndName(stableOnly: Boolean): Query[(Project.Reference, Name), Artifact] = + selectRequest1( + table, + mainFields, + keys = Seq("organization", "repository", "artifact_name"), + where = stableOnlyFilter(stableOnly).toSeq + ) + + val selectArtifactByProjectAndNameAndVersion: Query[(Project.Reference, Name, Version), Artifact] = + selectRequest1( + table, + mainFields, + keys = Seq("organization", "repository", "artifact_name", "version") ) - } - val selectArtifactByProjectAndName: Query[(Project.Reference, Artifact.Name), Artifact] = - selectRequest(table, fields, projectReferenceFields :+ "artifact_name") + private def stableOnlyFilter(stableOnly: Boolean): Seq[String] = + if (stableOnly) Seq("is_prerelease=false") else Seq.empty - val selectByMavenReference: Query[Artifact.MavenReference, Artifact] = - selectRequest(table, fields, mavenReferenceFields) + val selectByReference: Query[Reference, Artifact] = + selectRequest(table, mainFields, referenceFields) - val selectGroupIds: Query0[Artifact.GroupId] = + val selectGroupIds: Query0[GroupId] = selectRequest(table, Seq("DISTINCT group_id")) - val selectArtifactIds: Query[Project.Reference, (Artifact.GroupId, String)] = + val selectArtifactIds: Query[Project.Reference, (GroupId, ArtifactId)] = selectRequest(table, Seq("DISTINCT group_id", "artifact_id"), keys = projectReferenceFields) - val selectAllMavenReferences: Query0[Artifact.MavenReference] = + val selectReferences: Query0[Reference] = selectRequest(table, Seq("DISTINCT group_id", "artifact_id", "\"version\"")) - val selectMavenReferences: Query[Project.Reference, Artifact.MavenReference] = + val selectReferencesByProject: Query[Project.Reference, Reference] = selectRequest(table, Seq("DISTINCT group_id", "artifact_id", "\"version\""), keys = projectReferenceFields) - val selectMavenReferenceWithNoReleaseDate: Query0[Artifact.MavenReference] = + val selectMavenReferenceWithNoReleaseDate: Query0[Reference] = selectRequest(table, Seq("group_id", "artifact_id", "\"version\""), where = Seq("release_date is NULL")) val selectOldestByProject: Query0[(Instant, Project.Reference)] = @@ -125,12 +149,12 @@ object ArtifactTable { ) def selectLatestArtifacts: Query[Project.Reference, Artifact] = - selectRequest1(table, fields, where = Seq("organization=?", "repository=?", "is_latest_version=true")) + selectRequest1(table, mainFields, where = Seq("organization=?", "repository=?", "is_latest_version=true")) - def setLatestVersion: Update[Artifact.MavenReference] = + def setLatestVersion: Update[Reference] = updateRequest0(table, set = Seq("is_latest_version=true"), where = Seq("group_id=?", "artifact_id=?", "version=?")) - def unsetOthersLatestVersion: Update[Artifact.MavenReference] = + def unsetOthersLatestVersion: Update[Reference] = updateRequest0( table, set = Seq("is_latest_version=false"), diff --git a/modules/infra/src/main/scala/scaladex/infra/sql/DoobieUtils.scala b/modules/infra/src/main/scala/scaladex/infra/sql/DoobieUtils.scala index 7903e1e00..bf62fccd1 100644 --- a/modules/infra/src/main/scala/scaladex/infra/sql/DoobieUtils.scala +++ b/modules/infra/src/main/scala/scaladex/infra/sql/DoobieUtils.scala @@ -13,6 +13,8 @@ import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource import doobie._ import doobie.hikari.HikariTransactor +import doobie.postgres.Instances +import doobie.postgres.JavaTimeInstances import io.circe._ import org.flywaydb.core.Flyway import scaladex.core.model.Project._ @@ -113,13 +115,15 @@ object DoobieUtils { def selectRequest1[A: Write, B: Read]( table: String, fields: Seq[String], + keys: Seq[String] = Seq.empty, where: Seq[String] = Seq.empty, groupBy: Seq[String] = Seq.empty, orderBy: Option[String] = None, limit: Option[Long] = None ): Query[A, B] = { val fieldsStr = fields.mkString(", ") - val whereStr = if (where.nonEmpty) where.mkString(" WHERE ", " AND ", "") else "" + val allWhere = keys.map(k => s"$k=?") ++ where + val whereStr = if (allWhere.nonEmpty) allWhere.mkString(" WHERE ", " AND ", "") else "" val groupByStr = if (groupBy.nonEmpty) groupBy.mkString(" GROUP BY ", ", ", "") else "" val orderByStr = orderBy.map(o => s" ORDER BY $o").getOrElse("") val limitStr = limit.map(l => s" LIMIT $l").getOrElse("") @@ -131,7 +135,7 @@ object DoobieUtils { Update(s"DELETE FROM $table WHERE $whereK") } - object Mappings { + object Mappings extends Instances with JavaTimeInstances { implicit val contributorMeta: Meta[Seq[GithubContributor]] = Meta[String].timap(fromJson[Seq[GithubContributor]](_).get)(toJson(_)) implicit val githubIssuesMeta: Meta[Seq[GithubIssue]] = @@ -146,28 +150,26 @@ object DoobieUtils { ) implicit val artifactNamesMeta: Meta[Set[Artifact.Name]] = Meta[String].timap(_.split(",").filter(_.nonEmpty).map(Artifact.Name.apply).toSet)(_.mkString(",")) - implicit val semanticVersionMeta: Meta[SemanticVersion] = - Meta[String].timap(SemanticVersion.parse(_).get)(_.encode) - implicit val artifactIdMeta: Meta[Artifact.ArtifactId] = - Meta[String].timap(Artifact.ArtifactId.parse(_).get)(_.value) + implicit val semanticVersionMeta: Meta[Version] = Meta[String].timap(Version(_))(_.value) + implicit val artifactIdMeta: Meta[Artifact.ArtifactId] = Meta[String].timap(Artifact.ArtifactId(_))(_.value) implicit val binaryVersionMeta: Meta[BinaryVersion] = Meta[String].timap { x => BinaryVersion .parse(x) .getOrElse(throw new Exception(s"Failed to parse $x as BinaryVersion")) - }(_.encode) + }(_.value) implicit val platformMeta: Meta[Platform] = Meta[String].timap { x => Platform - .fromLabel(x) + .parse(x) .getOrElse(throw new Exception(s"Failed to parse $x as Platform")) - }(_.label) + }(_.value) implicit val languageVersionMeta: Meta[Language] = Meta[String].timap { x => Language - .fromLabel(x) + .parse(x) .getOrElse(throw new Exception(s"Failed to parse $x as Language")) - }(_.label) + }(_.value) implicit val scopeMeta: Meta[ArtifactDependency.Scope] = Meta[String].timap(ArtifactDependency.Scope.apply)(_.value) implicit val licenseMeta: Meta[License] = @@ -180,15 +182,15 @@ object DoobieUtils { Meta[String].timap(fromJson[Seq[License]](_).get.toSet)(toJson(_)) implicit val resolverMeta: Meta[Resolver] = Meta[String].timap(Resolver.from(_).get)(_.name) - implicit val instantMeta: Meta[Instant] = doobie.postgres.implicits.JavaTimeInstantMeta + implicit val developerMeta: Meta[Seq[Contributor]] = + Meta[String].timap(fromJson[Seq[Contributor]](_).get)(toJson(_)) implicit val secretMeta: Meta[Secret] = Meta[String].imap[Secret](Secret.apply)(_.decode) implicit val uuidMeta: Meta[UUID] = Meta[String].imap[UUID](UUID.fromString)(_.toString) implicit val projectReferenceMeta: Meta[Set[Project.Reference]] = - Meta[String].timap(_.split(",").filter(_.nonEmpty).map(Project.Reference.from).toSet)(_.mkString(",")) + Meta[String].timap(_.split(",").filter(_.nonEmpty).map(Project.Reference.unsafe).toSet)(_.mkString(",")) implicit val projectOrganizationMeta: Meta[Set[Project.Organization]] = Meta[String].timap(_.split(",").filter(_.nonEmpty).map(Project.Organization.apply).toSet)(_.mkString(",")) - implicit val categoryMeta: Meta[Category] = Meta[String].timap(Category.byLabel)(_.label) implicit val projectReferenceRead: Read[Project.Reference] = @@ -221,7 +223,7 @@ object DoobieUtils { ("Failed", updateDate, None, None, Some(errorCode), Some(errorMessage)) } - implicit val projectReader: Read[Project] = + implicit val projectRead: Read[Project] = Read[(Organization, Repository, Option[Instant], GithubStatus, Option[GithubInfo], Option[Project.Settings])] .map { case (organization, repository, creationDate, githubStatus, githubInfo, settings) => @@ -251,8 +253,6 @@ object DoobieUtils { Read[(Set[Project.Reference], Set[Project.Organization], UserInfo)].map { case (repos, orgs, info) => UserState(repos, orgs, info) } - implicit val developerMeta: Meta[Seq[Contributor]] = - Meta[String].timap(fromJson[Seq[Contributor]](_).get)(toJson(_)) private def toJson[A](v: A)(implicit e: Encoder[A]): String = e.apply(v).noSpaces diff --git a/modules/infra/src/main/scala/scaladex/infra/sql/ProjectDependenciesTable.scala b/modules/infra/src/main/scala/scaladex/infra/sql/ProjectDependenciesTable.scala index 4dde68ad7..4bbf57473 100644 --- a/modules/infra/src/main/scala/scaladex/infra/sql/ProjectDependenciesTable.scala +++ b/modules/infra/src/main/scala/scaladex/infra/sql/ProjectDependenciesTable.scala @@ -3,7 +3,7 @@ package scaladex.infra.sql import doobie._ import scaladex.core.model.Project import scaladex.core.model.ProjectDependency -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version import scaladex.infra.sql.DoobieUtils.Mappings._ import scaladex.infra.sql.DoobieUtils._ @@ -27,7 +27,7 @@ object ProjectDependenciesTable { val getDependents: Query[Project.Reference, ProjectDependency] = selectRequest(table, allFields, Seq("target_organization", "target_repository")) - val getDependencies: Query[(Project.Reference, SemanticVersion), ProjectDependency] = + val getDependencies: Query[(Project.Reference, Version), ProjectDependency] = selectRequest(table, allFields, sourceFields) val deleteBySource: Update[Project.Reference] = diff --git a/modules/infra/src/test/scala/scaladex/infra/ElasticsearchEngineTests.scala b/modules/infra/src/test/scala/scaladex/infra/ElasticsearchEngineTests.scala index e62fa3d5c..666645240 100644 --- a/modules/infra/src/test/scala/scaladex/infra/ElasticsearchEngineTests.scala +++ b/modules/infra/src/test/scala/scaladex/infra/ElasticsearchEngineTests.scala @@ -160,13 +160,13 @@ class ElasticsearchEngineTests extends AsyncFreeSpec with Matchers with BeforeAn } } - "should evaluate to field access syntax of the given field" in { + "field access syntax of the given field" in { val field = "githubInfo.stars" val accessExpr = ElasticsearchEngine.fieldAccess(field) accessExpr shouldBe "doc['githubInfo.stars'].value" } - "should evaluate to a field access that checks for nullability, and provides a default value" in { + "field access that checks for nullability, and provides a default value" in { val field = "githubInfo.stars" val accessExpr = ElasticsearchEngine.fieldAccess(field, default = "0") accessExpr shouldBe "(doc['githubInfo.stars'].size() != 0 ? doc['githubInfo.stars'].value : 0)" @@ -174,7 +174,7 @@ class ElasticsearchEngineTests extends AsyncFreeSpec with Matchers with BeforeAn private def projectDocument(ref: String, stars: Int, scalaPercentage: Int): ProjectDocument = { val githubInfo = GithubInfoDocument.empty.copy(stars = Some(stars), scalaPercentage = Some(scalaPercentage)) - ProjectDocument.default(Project.Reference.from(ref)).copy(githubInfo = Some(githubInfo)) + ProjectDocument.default(Project.Reference.unsafe(ref)).copy(githubInfo = Some(githubInfo)) } } diff --git a/modules/infra/src/test/scala/scaladex/infra/MavenCentralClientImplTests.scala b/modules/infra/src/test/scala/scaladex/infra/MavenCentralClientImplTests.scala index e5e3ce48a..4be19deb0 100644 --- a/modules/infra/src/test/scala/scaladex/infra/MavenCentralClientImplTests.scala +++ b/modules/infra/src/test/scala/scaladex/infra/MavenCentralClientImplTests.scala @@ -7,14 +7,14 @@ import org.scalatest.funspec.AsyncFunSpec import org.scalatest.matchers.should.Matchers import scaladex.core.model.Artifact import scaladex.core.model.Artifact._ -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version class MavenCentralClientImplTests extends AsyncFunSpec with Matchers { implicit val system: ActorSystem = ActorSystem("maven-central-client-tests") val client = new MavenCentralClientImpl() val groupId: GroupId = GroupId("ch.epfl.scala") - val artifactId: ArtifactId = ArtifactId.parse("sbt-scalafix_2.12_1.0").get - val version: SemanticVersion = SemanticVersion.parse("0.9.23").get + val artifactId: ArtifactId = ArtifactId("sbt-scalafix_2.12_1.0") + val version: Version = Version("0.9.23") it(s"retrieve artifactIds for ${groupId.value}") { for { @@ -32,34 +32,32 @@ class MavenCentralClientImplTests extends AsyncFunSpec with Matchers { for { res <- client.getAllVersions( GroupId("ru.tinkoff"), - ArtifactId.parse("typed-schema-swagger-typesafe_2.12").get + ArtifactId("typed-schema-swagger-typesafe_2.12") ) - } yield res should contain(SemanticVersion.parse("0.15.2").get) + } yield res should contain(Version("0.15.2")) } it(s"retrieve pomfile for maven reference of sbt plugin") { for { - res <- client.getPomFile(Artifact.MavenReference("ch.epfl.scala", "sbt-scalafix_2.12_1.0", "0.9.23")) + res <- client.getPomFile(Artifact.Reference.from("ch.epfl.scala", "sbt-scalafix_2.12_1.0", "0.9.23")) } yield res.get._1.startsWith(" database.updateProjectCreationDate(ref, creationDate) } projects <- database.getAllProjects() } yield { @@ -232,7 +235,7 @@ class SqlDatabaseTests extends AsyncFunSpec with BaseDatabaseSuite with Matchers it("should insert, update and get user session from id") { val userId = UUID.randomUUID() val userInfo = UserInfo("login", Some("name"), "", Secret("token")) - val userState = UserState(Set(Scalafix.reference), Set(Organization("scalacenter")), userInfo) + val userState = UserState(Set(Scalafix.reference), Set(Project.Organization("scalacenter")), userInfo) for { _ <- database.insertUser(userId, userInfo) state1 <- database.getUser(userId) @@ -264,91 +267,44 @@ class SqlDatabaseTests extends AsyncFunSpec with BaseDatabaseSuite with Matchers } yield obtained shouldBe None } - it("get artifact from maven reference") { + it("get artifact by ref") { for { - _ <- database.insertArtifact(Cats.`core_3:2.6.1`) - catsCore <- database.getArtifactByMavenReference(Cats.`core_3:2.6.1`.mavenReference) - } yield catsCore should contain(Cats.`core_3:2.6.1`) - } - - it("get artifact from maven reference if version is only major") { - for { - _ <- database.insertArtifact(Cats.`core_3:4`) - catsCore <- database.getArtifactByMavenReference(Cats.`core_3:4`.mavenReference) - } yield catsCore should contain(Cats.`core_3:4`) - } - - it("get all artifacts given no language or platform") { - val testArtifacts = Seq(Scalafix.artifact, Cats.`core_3:4`, PlayJsonExtra.artifact) - for { - _ <- database.insertArtifacts(testArtifacts) - storedArtifacts <- database.getAllArtifacts(None, None) - } yield storedArtifacts.forall(testArtifacts.contains) shouldBe true - } - - it("should return no artifacts given a language that has no artifacts stored") { - for { - _ <- database.insertArtifact(Scalafix.artifact) // Scalafix has Scala version 2.13 - storedArtifacts <- database.getAllArtifacts(Some(Scala.`3`), None) - } yield storedArtifacts.size shouldBe 0 - } - - it("should return no artifacts given a platform that has no artifacts stored") { - for { - _ <- database.insertArtifact(Scalafix.artifact) // Scalafix is a JVM-platform artifact - storedArtifacts <- database.getAllArtifacts(None, Some(ScalaJs.`1.x`)) - } yield storedArtifacts.size shouldBe 0 - } - - it("should return artifacts that conform to the given language and platform") { - val testArtifacts = Seq( - Cats.`core_3:4`, - Scalafix.artifact.copy(version = SemanticVersion(3)) - ) - for { - _ <- database.insertArtifacts(testArtifacts) - storedArtifacts <- database.getAllArtifacts(Some(Scala.`3`), Some(Jvm)) - } yield storedArtifacts.forall(testArtifacts.contains) shouldBe true - } - - it( - "should not return artifacts given both language and platform, and the database contains no artifacts that conform" - ) { - val testArtifacts = Seq( - Cats.`core_3:4`, - Scalafix.artifact.copy(version = SemanticVersion(3)) - ) - for { - _ <- database.insertArtifacts(testArtifacts) - storedArtifacts <- database.getAllArtifacts(Some(Scala.`3`), Some(ScalaJs(SemanticVersion(3)))) - } yield storedArtifacts.size shouldBe 0 - } - - it("should not return any artifacts when the database is empty, given a group id and artifact id") { - for { - retrievedArtifacts <- database.getArtifacts(Cats.`core_3:4`.groupId, Cats.`core_3:4`.artifactId) - } yield retrievedArtifacts.size shouldBe 0 + _ <- database.insertArtifacts(Seq(Cats.`core_3:2.6.1`, Cats.`core_3:4`)) + obtained1 <- database.getArtifact(Cats.`core_3:2.6.1`.reference) + obtained2 <- database.getArtifact(Cats.`core_3:4`.reference) + } yield { + obtained1 should contain(Cats.`core_3:2.6.1`) + obtained2 should contain(Cats.`core_3:4`) + } } - it("get an artifact from its group id and artifact id") { + it("get all artifacts by language and platform") { + val artifacts = Seq(Scalafix.artifact, Cats.`core_sjs1_3:2.6.1`, PlayJsonExtra.artifact) for { - isStoredSuccessfully <- database.insertArtifact(Cats.`core_3:4`) - retrievedArtifacts <- database.getArtifacts(Cats.`core_3:4`.groupId, Cats.`core_3:4`.artifactId) + _ <- database.insertArtifacts(artifacts) + obtained1 <- database.getAllArtifacts(None, None) + obtained2 <- database.getAllArtifacts(Some(Scala.`3`), None) + obtained3 <- database.getAllArtifacts(None, Some(ScalaJs.`1.x`)) + obtained4 <- database.getAllArtifacts(Some(Scala.`2.13`), Some(Jvm)) + obtained5 <- database.getAllArtifacts(Some(Scala.`3`), Some(ScalaJs.`0.6`)) } yield { - isStoredSuccessfully shouldBe true - retrievedArtifacts.size shouldBe 1 - retrievedArtifacts.headOption shouldBe Some(Cats.`core_3:4`) + obtained1 should contain theSameElementsAs artifacts + obtained2 should contain theSameElementsAs Seq(Cats.`core_sjs1_3:2.6.1`) + obtained3 should contain theSameElementsAs Seq(Cats.`core_sjs1_3:2.6.1`) + obtained4 should contain theSameElementsAs Seq(Scalafix.artifact) + obtained5 shouldBe empty } } - it("get all artifacts from group id and artifact id") { - val testArtifacts = Seq(Cats.`core_3:4`, Cats.`core_3:2.7.0`) + it("getArtifact by groupId and artifactId") { + val artifacts = Seq(Cats.`core_2.13:2.5.0`, Cats.`core_3:2.6.1`) for { - _ <- database.insertArtifacts(testArtifacts) - retrievedArtifacts <- database.getArtifacts(Artifact.GroupId("org.typelevel"), "cats-core_3") + _ <- database.insertArtifacts(artifacts) + obtained1 <- database.getArtifacts(Cats.groupId, Artifact.ArtifactId("cats-core_3")) + obtained2 <- database.getArtifacts(Cats.groupId, Artifact.ArtifactId("cats-core_2.13")) } yield { - retrievedArtifacts.size shouldBe 2 - retrievedArtifacts should contain theSameElementsAs testArtifacts + obtained1 should contain theSameElementsAs Seq(Cats.`core_3:2.6.1`) + obtained2 should contain theSameElementsAs Seq(Cats.`core_2.13:2.5.0`) } } } diff --git a/modules/infra/src/test/scala/scaladex/infra/sql/ArtifactTableTests.scala b/modules/infra/src/test/scala/scaladex/infra/sql/ArtifactTableTests.scala index 7ce48fe32..8c81ecb69 100644 --- a/modules/infra/src/test/scala/scaladex/infra/sql/ArtifactTableTests.scala +++ b/modules/infra/src/test/scala/scaladex/infra/sql/ArtifactTableTests.scala @@ -2,14 +2,18 @@ package scaladex.infra.sql import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -import scaladex.core.model.Jvm -import scaladex.core.model.Scala -import scaladex.core.model.ScalaJs +import scaladex.core.model._ import scaladex.infra.BaseDatabaseSuite class ArtifactTableTests extends AnyFunSpec with BaseDatabaseSuite with Matchers { import ArtifactTable._ it("check insertIfNotExist")(check(insertIfNotExist)) + it("check selectArtifactRefByProject") { + check(selectArtifactRefByProject(true)) + check(selectArtifactRefByProject(true)) + } + it("check selectArtifactRefByProjectAndName")(check(selectArtifactRefByProjectAndName)) + it("check selectArtifactRefByProjectAndVersion")(check(selectArtifactRefByProjectAndVersion)) it("check selectAllArtifacts") { check(selectAllArtifacts(None, None)) check(selectAllArtifacts(Some(Scala.`2.13`), None)) @@ -18,20 +22,24 @@ class ArtifactTableTests extends AnyFunSpec with BaseDatabaseSuite with Matchers } it("check selectArtifactByGroupIdAndArtifactId")(check(selectArtifactByGroupIdAndArtifactId)) it("check selectArtifactByProject")(check(selectArtifactByProject)) - it("check selectArtifactByProjectAndName")(check(selectArtifactByProjectAndName)) + it("check selectArtifactByProjectAndName") { + check(selectArtifactByProjectAndName(stableOnly = true)) + check(selectArtifactByProjectAndName(stableOnly = false)) + } it("check selectOldestByProject")(check(selectOldestByProject)) it("check updateProjectRef")(check(updateProjectRef)) it("check selectGroupIds")(check(selectGroupIds)) - it("check selectAllMavenReferences")(check(selectAllMavenReferences)) - it("check selectMavenReferences")(check(selectMavenReferences)) + it("check selectReferences")(check(selectReferences)) + it("check selectReferencesByProject")(check(selectReferencesByProject)) it("check updateReleaseDate")(check(updateReleaseDate)) - it("check selectByMavenReference")(check(selectByMavenReference)) + it("check selectByReference")(check(selectByReference)) it("check countVersionsByProject")(check(countVersionsByProject)) - it("check selectArtifactByParams") { - check(selectArtifactByParams(false)) - check(selectArtifactByParams(true)) - } it("check selectMavenReferenceWithNoReleaseDate")(check(selectMavenReferenceWithNoReleaseDate)) + it("check selectVersionByGroupIdAndArtifactId") { + check(selectVersionByGroupIdAndArtifactId(false)) + check(selectVersionByGroupIdAndArtifactId(true)) + } + it("check selectLatestArtifact")(check(selectLatestArtifact)) it("check selectLatestArtifacts")(check(selectLatestArtifacts)) it("check setLatestVersion")(check(setLatestVersion)) it("check unsetOthersLatestVersion")(check(unsetOthersLatestVersion)) diff --git a/modules/infra/src/test/scala/scaladex/infra/sql/ProjectSettingsTableTests.scala b/modules/infra/src/test/scala/scaladex/infra/sql/ProjectSettingsTableTests.scala index 25c272b2d..86a0a8f35 100644 --- a/modules/infra/src/test/scala/scaladex/infra/sql/ProjectSettingsTableTests.scala +++ b/modules/infra/src/test/scala/scaladex/infra/sql/ProjectSettingsTableTests.scala @@ -5,7 +5,5 @@ import org.scalatest.matchers.should.Matchers import scaladex.infra.BaseDatabaseSuite class ProjectSettingsTableTests extends AnyFunSpec with BaseDatabaseSuite with Matchers { - describe("should generate query for") { - it("check insertOrUpdate")(check(ProjectSettingsTable.insertOrUpdate)) - } + it("check insertOrUpdate")(check(ProjectSettingsTable.insertOrUpdate)) } diff --git a/modules/server/src/it/scala/scaladex/RelevanceTest.scala b/modules/server/src/it/scala/scaladex/RelevanceTest.scala index e636d95ac..a29752ecd 100644 --- a/modules/server/src/it/scala/scaladex/RelevanceTest.scala +++ b/modules/server/src/it/scala/scaladex/RelevanceTest.scala @@ -12,15 +12,14 @@ import cats.effect.IO import cats.effect.ContextShift import scala.concurrent.ExecutionContext -import scaladex.core.model.Project -import scaladex.core.model.search.SearchParams +import scaladex.core.model.search._ import scaladex.infra.{ElasticsearchEngine, FilesystemStorage, SqlDatabase} import scaladex.infra.sql.DoobieUtils import scaladex.server.service.SearchSynchronizer -import scaladex.core.model.search.PageParams import scaladex.server.service.DependencyUpdater import scaladex.core.service.ProjectService import scaladex.server.service.ArtifactService +import scaladex.core.model._ class RelevanceTest extends TestKit(ActorSystem("SbtActorTest")) with AsyncFunSuiteLike with BeforeAndAfterAll { @@ -38,7 +37,7 @@ class RelevanceTest extends TestKit(ActorSystem("SbtActorTest")) with AsyncFunSu val database = new SqlDatabase(datasource, xa) val filesystem = FilesystemStorage(config.filesystem) - val projectService = new ProjectService(database) + val projectService = new ProjectService(database, searchEngine) val searchSync = new SearchSynchronizer(database, projectService, searchEngine) val projectDependenciesUpdater = new DependencyUpdater(database, projectService) val artifactService = new ArtifactService(database) @@ -108,7 +107,7 @@ class RelevanceTest extends TestKit(ActorSystem("SbtActorTest")) with AsyncFunSu test("Scala.js targetTypes") { top( - SearchParams(platforms = List("sjs1")), + SearchParams(platforms = Seq(ScalaJs.`1.x`)), List( "scala-js" -> "scala-js" ) @@ -117,7 +116,7 @@ class RelevanceTest extends TestKit(ActorSystem("SbtActorTest")) with AsyncFunSu test("filter _sjs1_2.13") { top( - SearchParams(languages = Seq("2.13"), platforms = Seq("sjs1")), + SearchParams(languages = Seq(Scala.`2.13`), platforms = Seq(ScalaJs.`1.x`)), List( "scala-js" -> "scala-js" ) @@ -126,7 +125,7 @@ class RelevanceTest extends TestKit(ActorSystem("SbtActorTest")) with AsyncFunSu test("filter Scala Native 0.4 platform") { top( - SearchParams(platforms = List("native0.4")), + SearchParams(platforms = List(ScalaNative.`0.4`)), List( ("scalaz", "scalaz"), ("scopt", "scopt"), @@ -137,7 +136,7 @@ class RelevanceTest extends TestKit(ActorSystem("SbtActorTest")) with AsyncFunSu test("filter _native0.4_2.13") { top( - SearchParams(languages = Seq("2.13"), platforms = Seq("native0.4")), + SearchParams(languages = Seq(Scala.`2.13`), platforms = Seq(ScalaNative.`0.4`)), List( ("scalaz", "scalaz"), ("scopt", "scopt"), @@ -156,7 +155,7 @@ class RelevanceTest extends TestKit(ActorSystem("SbtActorTest")) with AsyncFunSu "typelevel/scalacheck", "typelevel/cats" ) - .map(Project.Reference.from) + .map(Project.Reference.unsafe) val missing = expected.filter(ref => !mostDependedRefs.contains(ref)) assert(missing.isEmpty) } diff --git a/modules/server/src/main/scala/scaladex/server/Server.scala b/modules/server/src/main/scala/scaladex/server/Server.scala index cec67c8b9..436d428ce 100644 --- a/modules/server/src/main/scala/scaladex/server/Server.scala +++ b/modules/server/src/main/scala/scaladex/server/Server.scala @@ -14,6 +14,7 @@ import org.apache.pekko.http.scaladsl._ import org.apache.pekko.http.scaladsl.model.StatusCodes import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server._ +import scaladex.core.service.ProjectService import scaladex.data.util.PidLock import scaladex.infra.DataPaths import scaladex.infra.ElasticsearchEngine @@ -27,9 +28,11 @@ import scaladex.server.route.AuthenticationApi import scaladex.server.route._ import scaladex.server.route.api._ import scaladex.server.service.AdminService +import scaladex.server.service.ArtifactService import scaladex.server.service.MavenCentralService import scaladex.server.service.PublishProcess import scaladex.view.html.notfound + object Server extends LazyLogging { def main(args: Array[String]): Unit = @@ -138,14 +141,16 @@ object Server extends LazyLogging { val githubAuth = GithubAuthImpl(config.oAuth2) + val projectService = new ProjectService(webDatabase, searchEngine) + val artifactService = new ArtifactService(webDatabase) val searchPages = new SearchPages(config.env, searchEngine) val frontPage = new FrontPage(config.env, webDatabase, searchEngine) val adminPages = new AdminPage(config.env, adminService) - val projectPages = new ProjectPages(config.env, webDatabase, searchEngine) + val projectPages = new ProjectPages(config.env, projectService, artifactService, webDatabase, searchEngine) val artifactPages = new ArtifactPages(config.env, webDatabase) val awesomePages = new AwesomePages(config.env, searchEngine) val publishApi = new PublishApi(githubAuth, publishProcess) - val apiEndpoints = new ApiEndpointsImpl(webDatabase, searchEngine) + val apiEndpoints = new ApiEndpointsImpl(projectService, artifactService, searchEngine) val oldSearchApi = new OldSearchApi(searchEngine, webDatabase) val badges = new Badges(webDatabase) val authentication = new AuthenticationApi(config.oAuth2.clientId, config.session, githubAuth, webDatabase) diff --git a/modules/server/src/main/scala/scaladex/server/route/ArtifactPages.scala b/modules/server/src/main/scala/scaladex/server/route/ArtifactPages.scala index 0e97e8b4b..7429a2b57 100644 --- a/modules/server/src/main/scala/scaladex/server/route/ArtifactPages.scala +++ b/modules/server/src/main/scala/scaladex/server/route/ArtifactPages.scala @@ -18,9 +18,9 @@ class ArtifactPages(env: Env, database: WebDatabase)(implicit executionContext: def route(user: Option[UserState]): Route = concat( get { - path("artifacts" / mavenReferenceM / "scaladoc" ~ RemainingPath) { (mavenRef, dri) => + path("artifacts" / artifactRefM / "scaladoc" ~ RemainingPath) { (ref, dri) => val scaladocUriF = for { - artifact <- database.getArtifactByMavenReference(mavenRef).map(_.get) + artifact <- database.getArtifact(ref).map(_.get) project <- database.getProject(artifact.projectRef) } yield project.flatMap(_.scaladoc(artifact).map(doc => Uri(doc.link))) diff --git a/modules/server/src/main/scala/scaladex/server/route/AwesomePages.scala b/modules/server/src/main/scala/scaladex/server/route/AwesomePages.scala index 5a948026a..6a2fd8150 100644 --- a/modules/server/src/main/scala/scaladex/server/route/AwesomePages.scala +++ b/modules/server/src/main/scala/scaladex/server/route/AwesomePages.scala @@ -39,13 +39,13 @@ class AwesomePages(env: Env, searchEngine: SearchEngine)(implicit ec: ExecutionC private val awesomeParams: Directive1[AwesomeParams] = parameters( - "languages".repeated, - "platforms".repeated, + "language".repeated, + "platform".repeated, "sort".? ).tmap { case (languageParams, platformParams, sortParam) => - val scalaVersions = languageParams.flatMap(Language.fromLabel).collect { case v: Scala => v }.toSeq - val platforms = platformParams.flatMap(Platform.fromLabel).toSeq + val scalaVersions = languageParams.flatMap(Language.parse).collect { case v: Scala => v }.toSeq + val platforms = platformParams.flatMap(Platform.parse).toSeq val sorting = sortParam.flatMap(Sorting.byLabel.get).getOrElse(Sorting.Stars) Tuple1(AwesomeParams(scalaVersions, platforms, sorting)) } diff --git a/modules/server/src/main/scala/scaladex/server/route/Badges.scala b/modules/server/src/main/scala/scaladex/server/route/Badges.scala index 22ff32f70..ae7cfbef3 100644 --- a/modules/server/src/main/scala/scaladex/server/route/Badges.scala +++ b/modules/server/src/main/scala/scaladex/server/route/Badges.scala @@ -20,8 +20,8 @@ import scaladex.core.model.SbtPlugin import scaladex.core.model.Scala import scaladex.core.model.ScalaJs import scaladex.core.model.ScalaNative -import scaladex.core.model.SemanticVersion -import scaladex.core.model.SemanticVersion.PreferStable +import scaladex.core.model.Version +import scaladex.core.model.Version.PreferStable import scaladex.core.service.WebDatabase class Badges(database: WebDatabase)(implicit executionContext: ExecutionContext) { @@ -96,7 +96,7 @@ class Badges(database: WebDatabase)(implicit executionContext: ExecutionContext) val res = database.getProject(ref).flatMap { case None => Future.successful(error("project not found")) case Some(project) => - val bv = binaryVersion.flatMap(BinaryVersion.fromLabel) + val bv = binaryVersion.flatMap(BinaryVersion.parse) getDefaultArtifact(project, bv, artifactName).map { case None => error("no published artifacts") case Some(artifact) => @@ -116,11 +116,12 @@ class Badges(database: WebDatabase)(implicit executionContext: ExecutionContext) // in case targetType is defined we choose the most recent corresponding platform parameters("targetType".?, "platform".?) { (targetTypeParam, platformParam) => shields { (color, style, logo, logoWidth) => - val artifactsF = database.getArtifactsByName(reference, artifactName) + // TODO use projectHeader + val artifactsF = database.getProjectArtifactRefs(reference, artifactName) onSuccess(artifactsF) { artifacts => val availablePlatforms = artifacts.map(_.binaryVersion.platform).distinct val platform = platformParam - .flatMap(Platform.fromLabel) + .flatMap(Platform.parse) .orElse { targetTypeParam.map(_.toUpperCase).flatMap { case "JVM" => Some(Jvm) @@ -153,9 +154,10 @@ class Badges(database: WebDatabase)(implicit executionContext: ExecutionContext) project: Project, binaryVersion: Option[BinaryVersion], artifact: Option[Artifact.Name] - ): Future[Option[Artifact]] = { + ): Future[Option[Artifact.Reference]] = { val artifactSelection = ArtifactSelection(binaryVersion, artifact) - database.getArtifacts(project.reference).map { artifacts => + // TODO use projectHeader + database.getProjectArtifactRefs(project.reference, stableOnly = false).map { artifacts => val (stableArtifacts, nonStableArtifacts) = artifacts.partition(_.version.isStable) artifactSelection .defaultArtifact(stableArtifacts, project) @@ -165,23 +167,23 @@ class Badges(database: WebDatabase)(implicit executionContext: ExecutionContext) } object Badges { - private def summaryOfLatestVersions(artifacts: Seq[Artifact]): String = { + private def summaryOfLatestVersions(artifacts: Seq[Artifact.Reference]): String = { val versionsByScalaVersions = artifacts .groupMap(_.binaryVersion.language)(_.version) .collect { case (Scala(v), version) => Scala(v) -> version } summaryOfLatestVersions(versionsByScalaVersions) } - private[route] def summaryOfLatestVersions(versionsByScalaVersions: Map[Scala, Seq[SemanticVersion]]): String = + private[route] def summaryOfLatestVersions(versionsByScalaVersions: Map[Scala, Seq[Version]]): String = versionsByScalaVersions.view .mapValues(_.max(PreferStable)) .groupMap { case (_, latestVersion) => latestVersion } { case (scalaVersion, _) => scalaVersion } .toSeq - .sortBy(_._1)(SemanticVersion.ordering.reverse) + .sortBy(_._1)(Version.ordering.reverse) .map { case (latestVersion, scalaVersions) => val scalaVersionsStr = - scalaVersions.map(_.version).toSeq.sorted(SemanticVersion.ordering.reverse).mkString(", ") + scalaVersions.map(_.version).toSeq.sorted(Version.ordering.reverse).mkString(", ") s"$latestVersion (Scala $scalaVersionsStr)" } .mkString(", ") diff --git a/modules/server/src/main/scala/scaladex/server/route/FrontPage.scala b/modules/server/src/main/scala/scaladex/server/route/FrontPage.scala index a2cac3663..7e3c23a50 100644 --- a/modules/server/src/main/scala/scaladex/server/route/FrontPage.scala +++ b/modules/server/src/main/scala/scaladex/server/route/FrontPage.scala @@ -41,42 +41,42 @@ class FrontPage(env: Env, database: WebDatabase, searchEngine: SearchEngine)(imp "Scala", languages.collect { case (sv @ Scala.`3`, count) => - EcosystemVersion(Scala.`3`.version, count, Url(s"search?languages=${sv.label}")) + EcosystemVersion(Scala.`3`.version, count, Url(s"search?language=${sv.value}")) } ) val scala2Ecosystem = EcosystemHighlight( "Scala", languages.collect { case (sv: Scala, count) if sv.version < Scala.`3`.version => - EcosystemVersion(sv.version, count, Url(s"search?languages=${sv.label}")) + EcosystemVersion(sv.version, count, Url(s"search?language=${sv.value}")) } ) val scalajsEcosystem = EcosystemHighlight( "Scala.js", platforms.collect { case (sjs: ScalaJs, count) => - EcosystemVersion(sjs.version, count, search = Url(s"search?platforms=${sjs.label}")) + EcosystemVersion(sjs.version, count, search = Url(s"search?platform=${sjs.value}")) } ) val scalaNativeEcosystem = EcosystemHighlight( "Scala Native", platforms.collect { case (sn: ScalaNative, count) => - EcosystemVersion(sn.version, count, search = Url(s"search?platforms=${sn.label}")) + EcosystemVersion(sn.version, count, search = Url(s"search?platform=${sn.value}")) } ) val sbtPluginEcosystem = EcosystemHighlight( "sbt", platforms.collect { case (sbtP: SbtPlugin, count) => - EcosystemVersion(sbtP.version, count, search = Url(s"search?platforms=${sbtP.label}")) + EcosystemVersion(sbtP.version, count, search = Url(s"search?platform=${sbtP.value}")) } ) val millPluginEcosystem = EcosystemHighlight( "Mill", platforms.collect { case (millP: MillPlugin, count) => - EcosystemVersion(millP.version, count, search = Url(s"search?platforms=${millP.label}")) + EcosystemVersion(millP.version, count, search = Url(s"search?platform=${millP.value}")) } ) diff --git a/modules/server/src/main/scala/scaladex/server/route/ProjectPages.scala b/modules/server/src/main/scala/scaladex/server/route/ProjectPages.scala index 77654fedb..8f1dde90e 100644 --- a/modules/server/src/main/scala/scaladex/server/route/ProjectPages.scala +++ b/modules/server/src/main/scala/scaladex/server/route/ProjectPages.scala @@ -27,11 +27,16 @@ import scaladex.view.html.forbidden import scaladex.view.html.notfound import scaladex.view.project.html -class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEngine)( +class ProjectPages( + env: Env, + projectService: ProjectService, + artifactService: ArtifactService, + database: SchedulerDatabase, + searchEngine: SearchEngine +)( implicit executionContext: ExecutionContext ) extends LazyLogging { - private val projectService = new ProjectService(database) - private val artifactService = new ArtifactService(database) + private val searchSynchronizer = new SearchSynchronizer(database, projectService, searchEngine) def route(user: Option[UserState]): Route = @@ -43,7 +48,7 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn path(projectM / "artifacts") { ref => artifactsParams { params => getProjectOrRedirect(ref, user) { project => - for (header <- projectService.getProjectHeader(project)) yield { + for (header <- projectService.getHeader(project)) yield { val allArtifacts = header.toSeq.flatMap(_.artifacts) val binaryVersions = allArtifacts @@ -52,7 +57,7 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn .sorted(BinaryVersion.ordering.reverse) val groupedArtifacts = allArtifacts - .groupBy(_.artifactName) + .groupBy(_.name) .map { case (name, artifacts) => val latestVersion = artifacts.maxBy(_.version).version @@ -66,7 +71,7 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn } .toSeq .sortBy { case (name, version, _) => (version, name) }( - Ordering.Tuple2(SemanticVersion.ordering.reverse, Artifact.Name.ordering) + Ordering.Tuple2(Version.ordering.reverse, Artifact.Name.ordering) ) val page = html.artifacts(env, user, project, header, groupedArtifacts, params, binaryVersions) complete(page) @@ -79,25 +84,23 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn path(projectM / "artifacts" / artifactNameM) { (ref, artifactName) => artifactsParams { params => getProjectOrRedirect(ref, user) { project => - val artifactsF = database.getArtifacts(ref, artifactName, params.stableOnly) - val headerF = projectService.getProjectHeader(project).map(_.get) + val artifactsF = database.getProjectArtifacts(ref, artifactName, params.stableOnly) + val headerF = projectService.getHeader(project).map(_.get) for (artifacts <- artifactsF; header <- headerF) yield { val binaryVersions = artifacts .map(_.binaryVersion) .distinct .sorted(BinaryVersion.ordering.reverse) - val artifactsByVersion = - artifacts - .groupBy(_.version) - .filter { - case (_, artifacts) => - params.binaryVersions - .forall(binaryVersion => artifacts.exists(_.binaryVersion == binaryVersion)) - } - .map { case (version, artifacts) => (artifacts.map(_.releaseDate).min, version) -> artifacts } + val artifactsByVersion = artifacts + .groupBy(_.version) + .filter { + case (_, artifacts) => + params.binaryVersions.forall(binaryVersion => artifacts.exists(_.binaryVersion == binaryVersion)) + } + .map { case (version, artifacts) => (artifacts.map(_.releaseDate).min, version) -> artifacts } val sortedArtifactsByVersion = SortedMap.from(artifactsByVersion)( - Ordering.Tuple2(Ordering[Instant].reverse, Ordering[SemanticVersion].reverse) + Ordering.Tuple2(Ordering[Instant].reverse, Ordering[Version].reverse) ) val page = html.versions( env, @@ -119,8 +122,8 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn path(projectM / "artifacts" / artifactNameM / versionM) { (ref, artifactName, artifactVersion) => artifactParams { params => getProjectOrRedirect(ref, user) { project => - val headerF = projectService.getProjectHeader(project) - val artifactsF = database.getArtifacts(ref, artifactName, artifactVersion) + val headerF = projectService.getHeader(project) + val artifactsF = database.getProjectArtifacts(ref, artifactName, artifactVersion) for { artifacts <- artifactsF header <- headerF @@ -153,8 +156,8 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn path(projectM / "version-matrix") { ref => getProjectOrRedirect(ref, user) { project => for { - artifacts <- database.getArtifacts(project.reference) - header <- projectService.getProjectHeader(project) + artifacts <- projectService.getArtifactRefs(project.reference, None, None, false) + header <- projectService.getHeader(project) } yield { val binaryVersionByPlatforms = artifacts .map(_.binaryVersion) @@ -167,9 +170,10 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn val artifactsByVersions = artifacts .groupBy(_.version) - .map { case (version, artifacts) => (version, artifacts.groupBy(_.artifactName).toSeq.sortBy(_._1)) } + .view + .mapValues(artifacts => artifacts.groupMap(_.name)(_.binaryVersion).toSeq.sortBy(_._1)) .toSeq - .sortBy(_._1)(SemanticVersion.ordering.reverse) + .sortBy(_._1)(Version.ordering.reverse) val page = html.versionMatrix(env, user, project, header, binaryVersionByPlatforms, artifactsByVersions) complete(page) } @@ -210,7 +214,7 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn // redirect to new artifacts page path(projectM / artifactNameM)((projectRef, artifactName) => parameter("binaryVersion".?) { binaryVersion => - val filter = binaryVersion.map(bv => s"?binary-versions=$bv").getOrElse("") + val filter = binaryVersion.map(bv => s"?binary-version=$bv").getOrElse("") redirect(s"/$projectRef/artifacts/$artifactName$filter", StatusCodes.MovedPermanently) } ) @@ -248,7 +252,7 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn private val artifactsParams: Directive1[ArtifactsPageParams] = parameters( - "binary-versions".repeated, + "binary-version".repeated, "stable-only".as[Boolean].withDefault(false) ).tmap { case (rawbinaryVersions, preReleases) => @@ -272,10 +276,10 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn ): Route = getProjectOrRedirect(ref, Some(user)) { project => for { - artifacts <- database.getArtifacts(ref) - header <- projectService.getProjectHeader(project) + artifacts <- projectService.getArtifactRefs(ref, None, None, false) + header <- projectService.getHeader(project) } yield { - val page = html.editproject(env, user, project, header, artifacts) + val page = html.editproject(env, user, project, header, artifacts.map(_.name).distinct) complete(page) } } @@ -283,7 +287,7 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn private def getProjectPage(ref: Project.Reference, user: Option[UserState]): Route = getProjectOrRedirect(ref, user) { project => for { - header <- projectService.getProjectHeader(project) + header <- projectService.getHeader(project) directDependencies <- header .map(h => database.getProjectDependencies(ref, h.latestVersion)) @@ -314,7 +318,7 @@ class ProjectPages(env: Env, database: SchedulerDatabase, searchEngine: SearchEn private def getBadges(ref: Project.Reference, user: Option[UserState]): Route = getProjectOrRedirect(ref, user) { project => - for (header <- projectService.getProjectHeader(project).map(_.get)) yield { + for (header <- projectService.getHeader(project).map(_.get)) yield { val artifact = header.getDefaultArtifact(None, None) val page = html.badges(env, user, project, header, artifact) complete(StatusCodes.OK, page) diff --git a/modules/server/src/main/scala/scaladex/server/route/SearchPages.scala b/modules/server/src/main/scala/scaladex/server/route/SearchPages.scala index 25b8dcc98..82820c009 100644 --- a/modules/server/src/main/scala/scaladex/server/route/SearchPages.scala +++ b/modules/server/src/main/scala/scaladex/server/route/SearchPages.scala @@ -5,12 +5,8 @@ import scala.concurrent.ExecutionContext import org.apache.pekko.http.scaladsl.model.Uri._ import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server._ -import scaladex.core.model.Env -import scaladex.core.model.UserState -import scaladex.core.model.search.Page -import scaladex.core.model.search.PageParams -import scaladex.core.model.search.SearchParams -import scaladex.core.model.search.Sorting +import scaladex.core.model._ +import scaladex.core.model.search._ import scaladex.core.service.SearchEngine import scaladex.server.TwirlSupport._ import scaladex.view.search.html.searchresult @@ -37,9 +33,9 @@ class SearchPages(env: Env, searchEngine: SearchEngine)( parameters( "q" ? "*", "sort".?, - "topics".as[String].*, - "languages".as[String].*, - "platforms".as[String].*, + "topic".as[String].*, + "language".as[String].*, + "platform".as[String].*, "you".?, "contributingSearch".as[Boolean] ? false ).tmap { @@ -51,8 +47,8 @@ class SearchPages(env: Env, searchEngine: SearchEngine)( sorting, userRepos, topics = topics.toSeq, - languages = languages.toSeq, - platforms = platforms.toSeq, + languages = languages.flatMap(Language.parse).toSeq, + platforms = platforms.flatMap(Platform.parse).toSeq, contributingSearch = contributingSearch ) } diff --git a/modules/server/src/main/scala/scaladex/server/route/api/ApiDocumentation.scala b/modules/server/src/main/scala/scaladex/server/route/api/ApiDocumentation.scala index d3b1bce07..a08d0ccbb 100644 --- a/modules/server/src/main/scala/scaladex/server/route/api/ApiDocumentation.scala +++ b/modules/server/src/main/scala/scaladex/server/route/api/ApiDocumentation.scala @@ -9,10 +9,22 @@ import scaladex.core.api.Endpoints * Documentation of the public HTTP API of Scaladex */ object ApiDocumentation extends Endpoints with openapi.Endpoints with openapi.JsonEntitiesFromSchemas { - val api: OpenApi = openApi(Info(title = "Scaladex API", version = "0.1.0"))( - listProjects, - listProjectArtifacts, - getArtifact, - autocomplete + val apiV0: OpenApi = openApi(Info(title = "Scaladex API", version = "v0"))( + getProjects(v0), + getProjectArtifacts(v0), + getArtifactVersions(v0), + getArtifact(v0) + ) + + val apiV1: OpenApi = openApi(Info(title = "Scaladex API", version = "v1"))( + getProjects(v1), + getProjectV1, + getProjectVersionsV1, + getLatestProjectVersionV1, + getProjectVersionV1, + getProjectArtifacts(v1), + getArtifactVersions(v1), + getLatestArtifactV1, + getArtifact(v1) ) } diff --git a/modules/server/src/main/scala/scaladex/server/route/api/ApiEndpointsImpl.scala b/modules/server/src/main/scala/scaladex/server/route/api/ApiEndpointsImpl.scala index 30a6c5155..eb49f3267 100644 --- a/modules/server/src/main/scala/scaladex/server/route/api/ApiEndpointsImpl.scala +++ b/modules/server/src/main/scala/scaladex/server/route/api/ApiEndpointsImpl.scala @@ -6,47 +6,93 @@ import endpoints4s.pekkohttp.server import org.apache.pekko.http.cors.scaladsl.CorsDirectives.cors import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server.Route -import scaladex.core.api.ArtifactResponse -import scaladex.core.api.AutocompletionResponse import scaladex.core.api.Endpoints -import scaladex.core.model.UserState +import scaladex.core.api.ProjectResponse +import scaladex.core.model._ +import scaladex.core.service.ProjectService import scaladex.core.service.SearchEngine -import scaladex.core.service.WebDatabase +import scaladex.server.service.ArtifactService -class ApiEndpointsImpl(database: WebDatabase, searchEngine: SearchEngine)( +class ApiEndpointsImpl(projectService: ProjectService, artifactService: ArtifactService, searchEngine: SearchEngine)( implicit ec: ExecutionContext ) extends Endpoints with server.Endpoints with server.JsonEntitiesFromSchemas { - def routes(user: Option[UserState]): Route = - cors()( - concat( - listProjects.implementedByAsync { _ => - for (projectStatuses <- database.getAllProjectsStatuses()) yield projectStatuses.iterator.collect { - case (ref, status) if status.isOk || status.isFailed || status.isUnknown => ref - }.toSeq - }, - listProjectArtifacts.implementedByAsync { ref => - for (artifacts <- database.getArtifacts(ref)) - yield artifacts.map(_.mavenReference) - }, - getArtifact.implementedByAsync { mavenRef => - for (artifactOpt <- database.getArtifactByMavenReference(mavenRef)) - yield artifactOpt.map(ArtifactResponse.apply) - }, - autocomplete.implementedByAsync { params => - val searchParams = params.withUser(user) - for (projects <- searchEngine.autocomplete(searchParams, 5)) - yield projects.map { project => - AutocompletionResponse( - project.organization.value, - project.repository.value, - project.githubInfo.flatMap(_.description).getOrElse("") - ) - } - } - ) - ) + def routes(user: Option[UserState]): Route = cors()(concat(webApi(user), v0Api, v1Api)) + + private def webApi(user: Option[UserState]): Route = + autocomplete.implementedByAsync { params => + val searchParams = params.withUser(user) + for (projects <- searchEngine.autocomplete(searchParams, 5)) yield projects.map(_.toAutocompletion) + } + + private def v0Api: Route = concat( + getProjects(v0).implementedByAsync(params => projectService.getProjects(params.languages, params.platforms)), + getProjectArtifacts(v0).implementedByAsync { + case (ref, params) => + projectService.getArtifactRefs(ref, params.binaryVersion, params.artifactName, params.stableOnly) + }, + getArtifactVersions(v0).implementedByAsync { + case (groupId, artifactId, stableOnly) => + artifactService.getVersions(groupId, artifactId, stableOnly) + }, + getArtifact(v0).implementedByAsync { mavenRef => + for (artifact <- artifactService.getArtifact(mavenRef)) yield artifact.map(_.toResponse) + } + ) + private def v1Api: Route = concat( + getProjects(v1).implementedByAsync(params => projectService.getProjects(params.languages, params.platforms)), + getProjectV1.implementedByAsync(ref => + for (project <- projectService.getProject(ref)) yield project.map(toResponse) + ), + getProjectVersionsV1.implementedByAsync { + case (ref, params) => + projectService.getVersions(ref, params.binaryVersions, params.artifactNames, params.stableOnly) + }, + getLatestProjectVersionV1.implementedByAsync(ref => projectService.getLatestProjectVersion(ref)), + getProjectVersionV1.implementedByAsync { case (ref, version) => projectService.getProjectVersion(ref, version) }, + getProjectArtifacts(v1).implementedByAsync { + case (ref, params) => + projectService.getArtifactRefs(ref, params.binaryVersion, params.artifactName, params.stableOnly) + }, + getLatestArtifactV1.implementedByAsync { + case (groupId, artifactId) => + for (artifact <- artifactService.getLatestArtifact(groupId, artifactId)) yield artifact.map(_.toResponse) + }, + getArtifactVersions(v1).implementedByAsync { + case (groupId, artifactId, stableOnly) => + artifactService.getVersions(groupId, artifactId, stableOnly) + }, + getArtifact(v1).implementedByAsync { mavenRef => + for (artifact <- artifactService.getArtifact(mavenRef)) yield artifact.map(_.toResponse) + } + ) + + private def toResponse(project: Project): ProjectResponse = { + import project._ + import settings._ + ProjectResponse( + organization, + repository, + githubInfo.flatMap(_.homepage), + githubInfo.flatMap(_.description), + githubInfo.flatMap(_.logo), + githubInfo.flatMap(_.stars), + githubInfo.flatMap(_.forks), + githubInfo.flatMap(_.issues), + githubInfo.toSet.flatMap((s: GithubInfo) => s.topics), + githubInfo.flatMap(_.contributingGuide), + githubInfo.flatMap(_.codeOfConduct), + githubInfo.flatMap(_.license), + defaultArtifact, + customScalaDoc, + documentationLinks, + contributorsWanted, + cliArtifacts, + category, + chatroom + ) + } } diff --git a/modules/server/src/main/scala/scaladex/server/route/api/DocumentationRoutes.scala b/modules/server/src/main/scala/scaladex/server/route/api/DocumentationRoutes.scala index a774da807..a9dd2afaa 100644 --- a/modules/server/src/main/scala/scaladex/server/route/api/DocumentationRoutes.scala +++ b/modules/server/src/main/scala/scaladex/server/route/api/DocumentationRoutes.scala @@ -3,6 +3,7 @@ package scaladex.server.route.api import endpoints4s.openapi.model.OpenApi import endpoints4s.pekkohttp.server import org.apache.pekko.http.cors.scaladsl.CorsDirectives.cors +import org.apache.pekko.http.scaladsl.server.Directives.concat import org.apache.pekko.http.scaladsl.server.Route /** @@ -10,9 +11,15 @@ import org.apache.pekko.http.scaladsl.server.Route */ object DocumentationRoute extends server.Endpoints with server.JsonEntitiesFromEncodersAndDecoders { val route: Route = cors() { - endpoint( - get(path / "api" / "open-api.json"), - ok(jsonResponse[OpenApi]) - ).implementedBy(_ => ApiDocumentation.api) + concat( + endpoint( + get(path / "api" / "open-api.json"), + ok(jsonResponse[OpenApi]) + ).implementedBy(_ => ApiDocumentation.apiV0), + endpoint( + get(path / "api" / "v1" / "open-api.json"), + ok(jsonResponse[OpenApi]) + ).implementedBy(_ => ApiDocumentation.apiV1) + ) } } diff --git a/modules/server/src/main/scala/scaladex/server/route/api/OldSearchApi.scala b/modules/server/src/main/scala/scaladex/server/route/api/OldSearchApi.scala index 6d6a89161..ac13ea739 100644 --- a/modules/server/src/main/scala/scaladex/server/route/api/OldSearchApi.scala +++ b/modules/server/src/main/scala/scaladex/server/route/api/OldSearchApi.scala @@ -18,7 +18,7 @@ import scaladex.core.model.SbtPlugin import scaladex.core.model.Scala import scaladex.core.model.ScalaJs import scaladex.core.model.ScalaNative -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version import scaladex.core.model.search.PageParams import scaladex.core.model.search.ProjectDocument import scaladex.core.service.SearchEngine @@ -56,24 +56,24 @@ object OldSearchApi { ): Option[BinaryVersion] = { val binaryVersion = (targetType, scalaVersion, scalaJsVersion, scalaNativeVersion, sbtVersion) match { case (Some("JVM"), Some(sv), _, _, _) => - SemanticVersion.parse(sv).map(sv => BinaryVersion(Jvm, Scala(sv))) + Version.parseSemantically(sv).map(sv => BinaryVersion(Jvm, Scala(sv))) case (Some("JS"), Some(sv), Some(jsv), _, _) => for { - sv <- SemanticVersion.parse(sv) - jsv <- SemanticVersion.parse(jsv) + sv <- Version.parseSemantically(sv) + jsv <- Version.parseSemantically(jsv) } yield BinaryVersion(ScalaJs(jsv), Scala(sv)) case (Some("NATIVE"), Some(sv), _, Some(snv), _) => for { - sv <- SemanticVersion.parse(sv) - snv <- SemanticVersion.parse(snv) + sv <- Version.parseSemantically(sv) + snv <- Version.parseSemantically(snv) } yield BinaryVersion(ScalaNative(snv), Scala(sv)) case (Some("SBT"), Some(sv), _, _, Some(sbtv)) => for { - sv <- SemanticVersion.parse(sv) - sbtv <- SemanticVersion.parse(sbtv) + sv <- Version.parseSemantically(sv) + sbtv <- Version.parseSemantically(sbtv) } yield BinaryVersion(SbtPlugin(sbtv), Scala(sv)) case (Some("JVM"), None, None, None, None) => Some(BinaryVersion(Jvm, Java)) @@ -186,25 +186,26 @@ class OldSearchApi(searchEngine: SearchEngine, database: WebDatabase)( ) for { projectOpt <- database.getProject(projectRef) - artifacts <- database.getArtifacts(projectRef) + stableOnly = projectOpt.map(_.settings.preferStableVersion).getOrElse(false) + artifacts <- database.getProjectArtifactRefs(projectRef, stableOnly) } yield for { project <- projectOpt filteredArtifacts = selection.filterArtifacts(artifacts, project) selected <- filteredArtifacts.headOption } yield { val (deprecatedArtifacts, artifacts) = filteredArtifacts - .map(_.artifactName) + .map(_.name) .distinct .partition(project.settings.deprecatedArtifacts.contains) // Sort semantic versions by descending order - val versions = filteredArtifacts.map(_.version).distinct.sorted(Ordering[SemanticVersion].reverse) + val versions = filteredArtifacts.map(_.version).distinct.sorted(Ordering[Version].reverse) OldSearchApi.ArtifactOptions( artifacts = artifacts.map(_.value), deprecatedArtifacts = deprecatedArtifacts.map(_.value), versions.map(_.toString), selected.groupId.value, - selected.artifactId, - selected.version.toString + selected.artifactId.value, + selected.version.value ) } } diff --git a/modules/server/src/main/scala/scaladex/server/route/package.scala b/modules/server/src/main/scala/scaladex/server/route/package.scala index 2e01315ab..f70c2eeda 100644 --- a/modules/server/src/main/scala/scaladex/server/route/package.scala +++ b/modules/server/src/main/scala/scaladex/server/route/package.scala @@ -3,14 +3,12 @@ package scaladex.server import java.time.Instant import org.apache.pekko.http.scaladsl.server.Directive1 -import org.apache.pekko.http.scaladsl.server.Directives.Segment import org.apache.pekko.http.scaladsl.server.Directives._ -import org.apache.pekko.http.scaladsl.server.PathMatcher import org.apache.pekko.http.scaladsl.server.PathMatcher1 import org.apache.pekko.http.scaladsl.unmarshalling.Unmarshaller import scaladex.core.model.Artifact import scaladex.core.model.Project -import scaladex.core.model.SemanticVersion +import scaladex.core.model.Version import scaladex.core.model.search.PageParams package object route { @@ -22,10 +20,10 @@ package object route { } val artifactNameM: PathMatcher1[Artifact.Name] = Segment.map(Artifact.Name.apply) - val versionM: PathMatcher1[SemanticVersion] = Segment.flatMap(SemanticVersion.parse) + val versionM: PathMatcher1[Version] = Segment.map(Version.apply) - val mavenReferenceM: PathMatcher[Tuple1[Artifact.MavenReference]] = (Segment / Segment / Segment).tmap { - case (groupId, artifactId, version) => Tuple1(Artifact.MavenReference(groupId, artifactId, version)) + val artifactRefM: PathMatcher1[Artifact.Reference] = (Segment / Segment / Segment).tmap { + case (groupId, artifactId, version) => Tuple1(Artifact.Reference.from(groupId, artifactId, version)) } val instantUnmarshaller: Unmarshaller[String, Instant] = diff --git a/modules/server/src/main/scala/scaladex/server/service/AdminService.scala b/modules/server/src/main/scala/scaladex/server/service/AdminService.scala index fb096fead..31c66eb76 100644 --- a/modules/server/src/main/scala/scaladex/server/service/AdminService.scala +++ b/modules/server/src/main/scala/scaladex/server/service/AdminService.scala @@ -28,7 +28,7 @@ class AdminService( extends LazyLogging { import actorSystem.dispatcher - val projectService = new ProjectService(database) + val projectService = new ProjectService(database, searchEngine) val searchSynchronizer = new SearchSynchronizer(database, projectService, searchEngine) val projectDependenciesUpdater = new DependencyUpdater(database, projectService) val userSessionService = new UserSessionService(database) @@ -146,7 +146,7 @@ class AdminService( private def updateProjectCreationDate(): Future[String] = for { - creationDates <- database.computeAllProjectsCreationDates() + creationDates <- database.computeProjectsCreationDates() _ <- creationDates.mapSync { case (creationDate, ref) => database.updateProjectCreationDate(ref, creationDate) } } yield s"Updated ${creationDates.size} creation dates" } diff --git a/modules/server/src/main/scala/scaladex/server/service/ArtifactConverter.scala b/modules/server/src/main/scala/scaladex/server/service/ArtifactConverter.scala index 407d690fd..4b76ee696 100644 --- a/modules/server/src/main/scala/scaladex/server/service/ArtifactConverter.scala +++ b/modules/server/src/main/scala/scaladex/server/service/ArtifactConverter.scala @@ -11,13 +11,7 @@ import scaladex.data.maven.Dependency import scaladex.data.maven.SbtPluginTarget import scaladex.infra.DataPaths -private case class ArtifactMeta( - artifactName: String, - binaryVersion: BinaryVersion, - isNonStandard: Boolean -) { - def artifactId: String = if (isNonStandard) artifactName else s"$artifactName${binaryVersion.label}" -} +private case class ArtifactMeta(artifactId: Artifact.ArtifactId, isNonStandard: Boolean) class ArtifactConverter(paths: DataPaths) extends LazyLogging { private val nonStandardLibs = NonStandardLib.load(paths) @@ -27,47 +21,36 @@ class ArtifactConverter(paths: DataPaths) extends LazyLogging { projectRef: Project.Reference, creationDate: Instant ): Option[(Artifact, Seq[ArtifactDependency])] = - for { - version <- SemanticVersion.parse(pom.version) - meta <- extractMeta(pom) - } yield { + extractMeta(pom).map { meta => val artifact = Artifact( Artifact.GroupId(pom.groupId), meta.artifactId, - version, - Artifact.Name(meta.artifactName), + Version(pom.version), projectRef, pom.description, creationDate, None, pom.licenses.flatMap(l => License.get(l.name)).toSet, meta.isNonStandard, - meta.binaryVersion.platform, - meta.binaryVersion.language, extractScalaVersion(pom), pom.scaladocUrl, pom.versionScheme, pom.developers.distinct ) val dependencies = pom.dependencies.map { dep => - ArtifactDependency( - artifact.mavenReference, - dep.mavenRef, - dep.scope.map(Scope.apply).getOrElse(Scope.compile) - ) + val scope = dep.scope.map(Scope.apply).getOrElse(Scope.compile) + ArtifactDependency(artifact.reference, dep.reference, scope) }.distinct (artifact, dependencies) } - private def extractScalaVersion(pom: ArtifactModel): Option[SemanticVersion] = { + private def extractScalaVersion(pom: ArtifactModel): Option[Version] = { val scalaDependencies = pom.dependencies.filter { dep => dep.groupId == "org.scala-lang" && (dep.artifactId == "scala-library" || dep.artifactId == "scala3-library_3") } - val fullScalaVersion = scalaDependencies.sortBy(_.artifactId).lastOption.map(_.version) - - fullScalaVersion.flatMap(SemanticVersion.parse) + fullScalaVersion.flatMap(Version.parseSemantically) } /** @@ -90,35 +73,24 @@ class ArtifactConverter(paths: DataPaths) extends LazyLogging { // This is a usual Scala library (whose artifact name is suffixed by the Scala binary version) // For example: akka-actors_2.12 case None => - Artifact.ArtifactId.parse(pom.artifactId).map { - case Artifact.ArtifactId(artifactName, binaryVersion) => - ArtifactMeta( - artifactName = artifactName.value, - binaryVersion = binaryVersion, - isNonStandard = false - ) - } + Some(ArtifactMeta(Artifact.ArtifactId(pom.artifactId), isNonStandard = false)) // Or it can be an sbt-plugin published as a maven style. In such a case the Scala target // is not suffixed to the artifact name but can be found in the model’s `sbtPluginTarget` member. case Some(SbtPluginTarget(rawScalaVersion, rawSbtVersion)) => - SemanticVersion - .parse(rawScalaVersion) - .zip(SemanticVersion.parse(rawSbtVersion)) match { - case Some((scalaVersion, sbtVersion)) => - Some( - ArtifactMeta( - artifactName = pom.artifactId.stripSuffix(s"_${rawScalaVersion}_$rawSbtVersion"), - binaryVersion = BinaryVersion(SbtPlugin(sbtVersion), Scala(scalaVersion)), - isNonStandard = false - ) - ) - case _ => - logger.error( - s"Unable to decode the Scala target: $rawScalaVersion $rawSbtVersion" - ) + Version + .parseSemantically(rawScalaVersion) + .zip(Version.parseSemantically(rawSbtVersion)) + .map { + case (scalaVersion, sbtVersion) => + val name = Artifact.Name(pom.artifactId.stripSuffix(s"_${rawScalaVersion}_$rawSbtVersion")) + val binaryVersion = BinaryVersion(SbtPlugin(sbtVersion), Scala(scalaVersion)) + ArtifactMeta(Artifact.ArtifactId(name, binaryVersion), isNonStandard = false) + } + .orElse { + logger.error(s"Unable to decode the Scala target: $rawScalaVersion $rawSbtVersion") None - } + } } // For example: io.gatling @@ -128,34 +100,30 @@ class ArtifactConverter(paths: DataPaths) extends LazyLogging { dep.groupId == "org.scala-lang" && (dep.artifactId == "scala-library" || dep.artifactId == "scala3-library_3") } - version <- SemanticVersion.parse(dep.version) - } yield - // we assume binary compatibility - ArtifactMeta( - artifactName = pom.artifactId, - binaryVersion = BinaryVersion(Jvm, Scala.fromFullVersion(version)), - isNonStandard = true - ) + version <- Version.parseSemantically(dep.version) + } yield { + val name = Artifact.Name(pom.artifactId) + val binaryVersion = BinaryVersion(Jvm, Scala.fromFullVersion(version)) + ArtifactMeta(Artifact.ArtifactId(name, binaryVersion), isNonStandard = true) + } // For example: typesafe config case Some(BinaryVersionLookup.Java) => Some( ArtifactMeta( - artifactName = pom.artifactId, - binaryVersion = BinaryVersion(Jvm, Java), + Artifact.ArtifactId(Artifact.Name(pom.artifactId), BinaryVersion(Jvm, Java)), isNonStandard = true ) ) // For example: scala-compiler case Some(BinaryVersionLookup.FromArtifactVersion) => - for (version <- SemanticVersion.parse(pom.version)) + for (version <- Version.parseSemantically(pom.version)) yield ArtifactMeta( - artifactName = pom.artifactId, - binaryVersion = BinaryVersion(Jvm, Scala.fromFullVersion(version)), + Artifact.ArtifactId(Artifact.Name(pom.artifactId), BinaryVersion(Jvm, Scala.fromFullVersion(version))), isNonStandard = true ) } // we need to filter out binary versions that are not valid - artifactMetaOption.filter(_.binaryVersion.isValid) + artifactMetaOption.filter(_.artifactId.binaryVersion.isValid) } } diff --git a/modules/server/src/main/scala/scaladex/server/service/ArtifactService.scala b/modules/server/src/main/scala/scaladex/server/service/ArtifactService.scala index 3ddd9ecd1..a9dd278b9 100644 --- a/modules/server/src/main/scala/scaladex/server/service/ArtifactService.scala +++ b/modules/server/src/main/scala/scaladex/server/service/ArtifactService.scala @@ -12,6 +12,19 @@ import scaladex.core.service.SchedulerDatabase import scaladex.core.util.ScalaExtensions._ class ArtifactService(database: SchedulerDatabase)(implicit ec: ExecutionContext) extends LazyLogging { + def getVersions( + groupId: Artifact.GroupId, + artifactId: Artifact.ArtifactId, + stableOnly: Boolean + ): Future[Seq[Version]] = + database.getArtifactVersions(groupId, artifactId, stableOnly) + + def getLatestArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] = + database.getLatestArtifact(groupId, artifactId) + + def getArtifact(ref: Artifact.Reference): Future[Option[Artifact]] = + database.getArtifact(ref) + def insertArtifact(artifact: Artifact, dependencies: Seq[ArtifactDependency]): Future[Boolean] = { val unknownStatus = GithubStatus.Unknown(Instant.now) for { @@ -30,7 +43,9 @@ class ArtifactService(database: SchedulerDatabase)(implicit ec: ExecutionContext total <- moved .map { case (source, dest) => - database.getArtifacts(source).flatMap(artifacts => database.updateArtifacts(artifacts, dest)) + database + .getProjectArtifactRefs(source, stableOnly = false) + .flatMap(artifacts => database.updateArtifacts(artifacts, dest)) } .sequence .map(_.sum) @@ -58,14 +73,18 @@ class ArtifactService(database: SchedulerDatabase)(implicit ec: ExecutionContext } } yield artifactIds.size - def updateLatestVersion(groupId: Artifact.GroupId, artifactId: String, preferStableVersion: Boolean): Future[Unit] = + def updateLatestVersion( + groupId: Artifact.GroupId, + artifactId: Artifact.ArtifactId, + preferStableVersion: Boolean + ): Future[Unit] = for { artifacts <- database.getArtifacts(groupId, artifactId) latestVersion = computeLatestVersion(artifacts.map(_.version), preferStableVersion) - _ <- database.updateLatestVersion(Artifact.MavenReference(groupId, artifactId, latestVersion)) + _ <- database.updateLatestVersion(Artifact.Reference(groupId, artifactId, latestVersion)) } yield () - def computeLatestVersion(versions: Seq[SemanticVersion], preferStableVersion: Boolean): SemanticVersion = { + def computeLatestVersion(versions: Seq[Version], preferStableVersion: Boolean): Version = { def maxStable = versions.filter(_.isStable).maxOption def max = versions.max if (preferStableVersion) maxStable.getOrElse(max) else max diff --git a/modules/server/src/main/scala/scaladex/server/service/DependencyUpdater.scala b/modules/server/src/main/scala/scaladex/server/service/DependencyUpdater.scala index bcfe42f1c..ae4e3c762 100644 --- a/modules/server/src/main/scala/scaladex/server/service/DependencyUpdater.scala +++ b/modules/server/src/main/scala/scaladex/server/service/DependencyUpdater.scala @@ -31,7 +31,7 @@ class DependencyUpdater(database: SchedulerDatabase, projectService: ProjectServ database.deleteProjectDependencies(project.reference).map(_ => ()) else for { - header <- projectService.getProjectHeader(project) + header <- projectService.getHeader(project) dependencies <- header .map(h => database.computeProjectDependencies(project.reference, h.latestVersion)) .getOrElse(Future.successful(Seq.empty)) diff --git a/modules/server/src/main/scala/scaladex/server/service/MavenCentralService.scala b/modules/server/src/main/scala/scaladex/server/service/MavenCentralService.scala index 656d92bdb..9e5953a45 100644 --- a/modules/server/src/main/scala/scaladex/server/service/MavenCentralService.scala +++ b/modules/server/src/main/scala/scaladex/server/service/MavenCentralService.scala @@ -24,12 +24,12 @@ class MavenCentralService( def findNonStandard(): Future[String] = { val nonStandardLibs = NonStandardLib.load(dataPaths) for { - mavenReferenceFromDatabase <- database.getAllMavenReferences() + knownRefs <- database.getArtifactRefs() result <- nonStandardLibs.mapSync { lib => val groupId = Artifact.GroupId(lib.groupId) // get should not throw: it is a fixed set of artifactIds - val artifactId = Artifact.ArtifactId.parse(lib.artifactId).get - findAndIndexMissingArtifacts(groupId, artifactId, mavenReferenceFromDatabase.toSet) + val artifactId = Artifact.ArtifactId(lib.artifactId) + findAndIndexMissingArtifacts(groupId, artifactId, knownRefs.toSet) } } yield s"Inserted ${result.sum} missing poms" } @@ -37,14 +37,11 @@ class MavenCentralService( private def findAndIndexMissingArtifacts( groupId: GroupId, artifactId: ArtifactId, - knownRefs: Set[MavenReference] + knownRefs: Set[Artifact.Reference] ): Future[Int] = for { versions <- mavenCentralClient.getAllVersions(groupId, artifactId) - mavenReferences = versions.map(v => - MavenReference(groupId = groupId.value, artifactId = artifactId.value, version = v.toString) - ) - missingVersions = mavenReferences.filterNot(knownRefs) + missingVersions = versions.map(Artifact.Reference(groupId, artifactId, _)).filterNot(knownRefs) _ = if (missingVersions.nonEmpty) logger.warn(s"${missingVersions.size} artifacts are missing for ${groupId.value}:${artifactId.value}") missingPomFiles <- missingVersions.map(ref => mavenCentralClient.getPomFile(ref).map(_.map(ref -> _))).sequence @@ -59,16 +56,16 @@ class MavenCentralService( def findMissing(): Future[String] = for { - mavenReferenceFromDatabase <- database.getAllMavenReferences().map(_.toSet) - groupIds = mavenReferenceFromDatabase.map(_.groupId).toSeq.sorted.map(Artifact.GroupId) + knownRefs <- database.getArtifactRefs().map(_.toSet) + groupIds = knownRefs.map(_.groupId).toSeq.sorted // we sort just to estimate through the logs the percentage of progress - result <- groupIds.mapSync(g => findAndIndexMissingArtifacts(g, None, mavenReferenceFromDatabase)) + result <- groupIds.mapSync(g => findAndIndexMissingArtifacts(g, None, knownRefs)) } yield s"Inserted ${result.sum} missing poms" private def findAndIndexMissingArtifacts( groupId: GroupId, artifactNameOpt: Option[Artifact.Name], - knownRefs: Set[MavenReference] + knownRefs: Set[Artifact.Reference] ): Future[Int] = for { artifactIds <- mavenCentralClient.getAllArtifactIds(groupId) @@ -81,8 +78,8 @@ class MavenCentralService( def syncOne(groupId: GroupId, artifactNameOpt: Option[Artifact.Name]): Future[String] = for { - mavenReferenceFromDatabase <- database.getAllMavenReferences() - result <- findAndIndexMissingArtifacts(groupId, artifactNameOpt, mavenReferenceFromDatabase.toSet) + knownRefs <- database.getArtifactRefs() + result <- findAndIndexMissingArtifacts(groupId, artifactNameOpt, knownRefs.toSet) } yield s"Inserted $result poms" def republishArtifacts(): Future[String] = @@ -98,8 +95,8 @@ class MavenCentralService( private def republishArtifacts(projectRef: Project.Reference): Future[(Int, Int)] = for { - mavenReferences <- database.getMavenReferences(projectRef) - publishResult <- mavenReferences.mapSync(republishArtifact(projectRef, _)) + refs <- database.getProjectArtifactRefs(projectRef, stableOnly = false) + publishResult <- refs.mapSync(republishArtifact(projectRef, _)) } yield { val successes = publishResult.count(_ == PublishResult.Success) val failures = publishResult.size - successes @@ -107,7 +104,7 @@ class MavenCentralService( (successes, failures) } - private def republishArtifact(projectRef: Project.Reference, ref: MavenReference): Future[PublishResult] = + private def republishArtifact(projectRef: Project.Reference, ref: Artifact.Reference): Future[PublishResult] = mavenCentralClient.getPomFile(ref).flatMap { case Some((pomFile, creationDate)) => publishProcess.republishPom(projectRef, ref, pomFile, creationDate) case _ => Future.successful(PublishResult.InvalidPom) diff --git a/modules/server/src/main/scala/scaladex/server/service/PublishProcess.scala b/modules/server/src/main/scala/scaladex/server/service/PublishProcess.scala index 4b10d7f73..4cd19aa83 100644 --- a/modules/server/src/main/scala/scaladex/server/service/PublishProcess.scala +++ b/modules/server/src/main/scala/scaladex/server/service/PublishProcess.scala @@ -98,17 +98,17 @@ class PublishProcess( def republishPom( repo: Project.Reference, - ref: Artifact.MavenReference, + ref: Artifact.Reference, data: String, creationDate: Instant ): Future[PublishResult] = Future(loadPom(data).get) .flatMap { pom => converter.convert(pom, repo, creationDate).map(_._1) match { - case Some(artifact) if artifact.mavenReference == ref => + case Some(artifact) if artifact.reference == ref => database.insertArtifact(artifact).map(_ => PublishResult.Success) case Some(artifact) => - logger.error(s"Unexpected ref ${artifact.mavenReference}") + logger.error(s"Unexpected ref ${artifact.reference}") Future.successful(PublishResult.InvalidPom) case None => logger.warn(s"Cannot convert $ref to valid Scala artifact.") diff --git a/modules/server/src/main/scala/scaladex/server/service/SearchSynchronizer.scala b/modules/server/src/main/scala/scaladex/server/service/SearchSynchronizer.scala index 9de44fb94..29cf37e01 100644 --- a/modules/server/src/main/scala/scaladex/server/service/SearchSynchronizer.scala +++ b/modules/server/src/main/scala/scaladex/server/service/SearchSynchronizer.scala @@ -22,10 +22,7 @@ class SearchSynchronizer(database: SchedulerDatabase, service: ProjectService, s // Create a map of project reference to their old references movedProjects = allProjectsAndStatus - .collect { - case (p, GithubStatus.Moved(_, newRef)) => - newRef -> p.reference - } + .collect { case (p, GithubStatus.Moved(_, newRef)) => newRef -> p.reference } .groupMap { case (newRef, _) => newRef } { case (_, ref) => ref } projectsToDelete = allProjectsAndStatus.collect { case (p, GithubStatus.NotFound(_)) => p.reference } @@ -57,7 +54,7 @@ class SearchSynchronizer(database: SchedulerDatabase, service: ProjectService, s private def insertDocument(project: Project, formerReferences: Seq[Project.Reference]): Future[Unit] = for { - header <- service.getProjectHeader(project) + header <- service.getHeader(project) dependents <- database.countProjectDependents(project.reference) document = ProjectDocument(project, header, dependents, formerReferences) _ <- searchEngine.insert(document) diff --git a/modules/server/src/test/scala/scaladex/server/route/BadgesTests.scala b/modules/server/src/test/scala/scaladex/server/route/BadgesTests.scala index 5c95e1741..c37397947 100644 --- a/modules/server/src/test/scala/scaladex/server/route/BadgesTests.scala +++ b/modules/server/src/test/scala/scaladex/server/route/BadgesTests.scala @@ -19,25 +19,22 @@ class BadgesTests extends ControllerBaseSuite with BeforeAndAfterAll { val badgesRoute: Route = new Badges(database).route - override protected def beforeAll(): Unit = Await.result(insertCatsArtifacts(), Duration.Inf) - - def insertCatsArtifacts(): Future[Unit] = - for { - _ <- database.insertProjectRef(Cats.reference, unknown) - _ <- Future.traverse(Cats.allArtifacts)(database.insertArtifact(_)) - } yield () + override protected def beforeAll(): Unit = { + val f = Future.traverse(Cats.allArtifacts)(artifactService.insertArtifact(_, Seq.empty)) + Await.result(f, Duration.Inf) + } - it("should fallback to JVM artifacts") { + it("fallback to JVM artifacts") { Get(s"/${Cats.reference}/cats-core/latest-by-scala-version.svg") ~> badgesRoute ~> check { status shouldEqual StatusCodes.TemporaryRedirect val redirection = headers.collectFirst { case Location(uri) => uri } redirection should contain( - Uri("https://img.shields.io/badge/cats--core_--_JVM-2.7.0_(Scala_3.x)-green.svg?") + Uri("https://img.shields.io/badge/cats--core_--_JVM-2.6.1_(Scala_3.x),_2.5.0_(Scala_2.13)-green.svg?") ) } } - it("should fallback to sjs1 when targetType is js") { + it("fallback to sjs1 when targetType is js") { Get(s"/${Cats.reference}/cats-core/latest-by-scala-version.svg?targetType=js") ~> badgesRoute ~> check { status shouldEqual StatusCodes.TemporaryRedirect val redirection = headers.collectFirst { case Location(uri) => uri } @@ -47,7 +44,7 @@ class BadgesTests extends ControllerBaseSuite with BeforeAndAfterAll { } } - it("should return latest version for Scala.js 0.6") { + it("latest version for Scala.js 0.6") { Get(s"/${Cats.reference}/cats-core/latest-by-scala-version.svg?platform=sjs0.6") ~> badgesRoute ~> check { status shouldEqual StatusCodes.TemporaryRedirect val redirection = headers.collectFirst { case Location(uri) => uri } @@ -57,7 +54,7 @@ class BadgesTests extends ControllerBaseSuite with BeforeAndAfterAll { } } - it("should return latest version for Scala native 0.4") { + it("latest version for Scala native 0.4") { Get(s"/${Cats.reference}/cats-core/latest-by-scala-version.svg?platform=native0.4") ~> badgesRoute ~> check { status shouldEqual StatusCodes.TemporaryRedirect val redirection = headers.collectFirst { case Location(uri) => uri } diff --git a/modules/server/src/test/scala/scaladex/server/route/ControllerBaseSuite.scala b/modules/server/src/test/scala/scaladex/server/route/ControllerBaseSuite.scala index b0d3def66..67113a99d 100644 --- a/modules/server/src/test/scala/scaladex/server/route/ControllerBaseSuite.scala +++ b/modules/server/src/test/scala/scaladex/server/route/ControllerBaseSuite.scala @@ -6,6 +6,7 @@ import java.nio.file.Path import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest import org.scalatest.funspec.AsyncFunSpec import org.scalatest.matchers.should.Matchers +import scaladex.core.service.ProjectService import scaladex.core.service.SearchEngine import scaladex.core.test.InMemoryDatabase import scaladex.core.test.InMemorySearchEngine @@ -13,6 +14,8 @@ import scaladex.core.test.MockGithubAuth import scaladex.infra.DataPaths import scaladex.infra.FilesystemStorage import scaladex.server.config.ServerConfig +import scaladex.server.service.ArtifactService +import scaladex.server.service.SearchSynchronizer trait ControllerBaseSuite extends AsyncFunSpec with Matchers with ScalatestRouteTest { val index: Path = Files.createTempDirectory("scaladex-index") @@ -23,8 +26,12 @@ trait ControllerBaseSuite extends AsyncFunSpec with Matchers with ScalatestRoute val githubAuth = MockGithubAuth val database: InMemoryDatabase = new InMemoryDatabase() - val searchEngine: SearchEngine = new InMemorySearchEngine() + + val projectService = new ProjectService(database, searchEngine) + val artifactService = new ArtifactService(database) + val searchSync = new SearchSynchronizer(database, projectService, searchEngine) + val dataPaths: DataPaths = DataPaths.from(config.filesystem) val localStorage: FilesystemStorage = FilesystemStorage(config.filesystem) } diff --git a/modules/server/src/test/scala/scaladex/server/route/ProjectPagesTests.scala b/modules/server/src/test/scala/scaladex/server/route/ProjectPagesTests.scala index af76e6db4..a11c282e5 100644 --- a/modules/server/src/test/scala/scaladex/server/route/ProjectPagesTests.scala +++ b/modules/server/src/test/scala/scaladex/server/route/ProjectPagesTests.scala @@ -25,13 +25,12 @@ class ProjectPagesTests extends ControllerBaseSuite with BeforeAndAfterEach { private def insertPlayJsonExtra(): Future[Unit] = for { - _ <- database.insertProjectRef(PlayJsonExtra.reference, unknown) - _ <- database.insertArtifact(PlayJsonExtra.artifact) + _ <- artifactService.insertArtifact(PlayJsonExtra.artifact, Seq.empty) _ <- database.updateProjectCreationDate(PlayJsonExtra.reference, PlayJsonExtra.creationDate) _ <- database.updateGithubInfoAndStatus(PlayJsonExtra.reference, PlayJsonExtra.githubInfo, ok) } yield () - val projectPages = new ProjectPages(config.env, database, searchEngine) + val projectPages = new ProjectPages(config.env, projectService, artifactService, database, searchEngine) val artifactPages = new ArtifactPages(config.env, database) val route: Route = projectPages.route(None) ~ artifactPages.route(None) @@ -60,10 +59,10 @@ class ProjectPagesTests extends ControllerBaseSuite with BeforeAndAfterEach { val destination = PlayJsonExtra.reference.copy(repository = Project.Repository("play-json-extra-2")) val moved = GithubStatus.Moved(now, destination) Await.result(database.moveProject(PlayJsonExtra.reference, PlayJsonExtra.githubInfo, moved), Duration.Inf) - Get(s"/${PlayJsonExtra.reference}/artifacts/play-json-extra?binary-versions=_2.13") ~> route ~> check { + Get(s"/${PlayJsonExtra.reference}/artifacts/play-json-extra?binary-version=_2.13") ~> route ~> check { status shouldEqual StatusCodes.MovedPermanently val location = headers.collectFirst { case Location(uri) => uri } - location should contain(Uri(s"http://example.com/$destination/artifacts/play-json-extra?binary-versions=_2.13")) + location should contain(Uri(s"http://example.com/$destination/artifacts/play-json-extra?binary-version=_2.13")) } } diff --git a/modules/server/src/test/scala/scaladex/server/route/api/ApiEndpointsImplTests.scala b/modules/server/src/test/scala/scaladex/server/route/api/ApiEndpointsImplTests.scala index a841f6ccd..57bc037cb 100644 --- a/modules/server/src/test/scala/scaladex/server/route/api/ApiEndpointsImplTests.scala +++ b/modules/server/src/test/scala/scaladex/server/route/api/ApiEndpointsImplTests.scala @@ -1,28 +1,29 @@ package scaladex.server.route.api import scala.concurrent.Await -import scala.concurrent.Future import scala.concurrent.duration.Duration import org.apache.pekko.http.scaladsl.model.MediaTypes import org.apache.pekko.http.scaladsl.model.StatusCodes import org.apache.pekko.http.scaladsl.unmarshalling.FromEntityUnmarshaller import org.apache.pekko.http.scaladsl.unmarshalling.Unmarshaller +import org.scalactic.source.Position +import org.scalatest.Assertion import org.scalatest.BeforeAndAfterEach import scaladex.core.api.ArtifactResponse -import scaladex.core.model.Artifact -import scaladex.core.model.Project +import scaladex.core.model._ import scaladex.core.test.Values._ +import scaladex.core.util.ScalaExtensions._ import scaladex.server.route.ControllerBaseSuite class ApiEndpointsImplTests extends ControllerBaseSuite with BeforeAndAfterEach { - val endpoints = new ApiEndpointsImpl(database, searchEngine) + val endpoints = new ApiEndpointsImpl(projectService, artifactService, searchEngine) import endpoints._ override protected def beforeAll(): Unit = { val insertions = for { - _ <- database.insertProjectRef(Cats.reference, unknown) - _ <- Future.traverse(Cats.allArtifacts)(database.insertArtifact(_)) + _ <- Cats.allArtifacts.mapSync(artifactService.insertArtifact(_, Seq.empty)) + _ <- searchSync.syncAll() } yield () Await.result(insertions, Duration.Inf) } @@ -32,45 +33,175 @@ class ApiEndpointsImplTests extends ControllerBaseSuite with BeforeAndAfterEach .forContentTypes(MediaTypes.`application/json`) .map(data => stringCodec[T].decode(data).toEither.toOption.get) - it("list all project references") { - Get("/api/projects") ~> routes(None) ~> check { + describe("v0") { + testGet("/api/projects") { status shouldBe StatusCodes.OK - val artifacts = responseAs[Seq[Project.Reference]] - val expected = Seq(Cats.reference) - artifacts shouldBe expected + responseAs[Seq[Project.Reference]] shouldBe Seq(Cats.reference) } - } - it("list all artifact references of project") { - Get(s"/api/projects/${Cats.reference}/artifacts") ~> routes(None) ~> check { + testGet(s"/api/projects/${Cats.reference}/artifacts") { status shouldBe StatusCodes.OK - val artifacts = responseAs[Seq[Artifact.MavenReference]] - val expected = Cats.allArtifacts.map(_.mavenReference) - artifacts.size shouldBe expected.size - artifacts.forall(expected.contains) shouldBe true + responseAs[Seq[Artifact.Reference]] should contain theSameElementsAs Cats.allArtifacts.map(_.reference) } - } - it("empty list of artifacts for unknown project") { - Get("/api/projects/unknown/unknown/artifacts") ~> routes(None) ~> check { + testGet("/api/projects/unknown/unknown/artifacts") { status shouldBe StatusCodes.OK // TODO this should be not found - val artifacts = responseAs[Seq[Artifact.MavenReference]] - artifacts.size shouldBe 0 + val artifacts = responseAs[Seq[Artifact.Reference]] + artifacts shouldBe empty + } + + testGet("/api/artifacts/org.typelevel/cats-core_3/2.6.1") { + status shouldBe StatusCodes.OK + responseAs[ArtifactResponse] shouldBe Cats.`core_3:2.6.1`.toResponse } - } - it("unknown artifact") { - Get("/api/artifacts/unknown/unknown_3/1.0.0") ~> routes(None) ~> check { + testGet("/api/artifacts/unknown/unknown_3/1.0.0") { status shouldBe StatusCodes.NotFound } } - it("find artifact") { - Get("/api/artifacts/org.typelevel/cats-core_3/2.6.1") ~> routes(None) ~> check { + describe("v1") { + testGet("/api/v1/projects") { status shouldBe StatusCodes.OK - val artifact = responseAs[ArtifactResponse] - val expected = ArtifactResponse(Cats.`core_3:2.6.1`) - artifact shouldBe expected + responseAs[Seq[Project.Reference]] shouldBe Seq(Cats.reference) + } + + testGet("/api/v1/projects?platform=jvm&language=3") { + status shouldBe StatusCodes.OK + responseAs[Seq[Project.Reference]] shouldBe Seq(Cats.reference) + } + + testGet("/api/v1/projects?platform=sbt1&platform=jvm") { + status shouldBe StatusCodes.OK + responseAs[Seq[Project.Reference]] shouldBe empty + } + + testGet("/api/v1/projects?language=3&language=2.12") { + status shouldBe StatusCodes.OK + responseAs[Seq[Project.Reference]] shouldBe empty + } + + // fail parsing platform + testGet("/api/v1/projects?platform=foo") { + status shouldBe StatusCodes.BadRequest + // TODO should return error message + } + + // fail parsing language + testGet("/api/v1/projects?language=bar") { + status shouldBe StatusCodes.BadRequest + // TODO should return error message + } + + testGet(s"/api/v1/projects/${Cats.reference}/versions") { + status shouldBe StatusCodes.OK + responseAs[Seq[Version]] should contain theSameElementsAs Seq(`2.6.1`, `2.5.0`) + } + + testGet(s"/api/v1/projects/${Cats.reference}/versions?binary-version=_sjs1_3") { + status shouldBe StatusCodes.OK + responseAs[Seq[Version]] should contain theSameElementsAs Seq(`2.6.1`) + } + + testGet(s"/api/v1/projects/${Cats.reference}/versions?artifact-name=cats-kernel") { + status shouldBe StatusCodes.OK + responseAs[Seq[Version]] should contain theSameElementsAs Seq(`2.6.1`) + } + + testGet( + s"/api/v1/projects/${Cats.reference}/versions?binary-version=_3&binary-version=_sjs1_3&artifact-name=cats-core" + ) { + status shouldBe StatusCodes.OK + responseAs[Seq[Version]] should contain theSameElementsAs Seq(`2.6.1`) + } + + testGet( + s"/api/v1/projects/${Cats.reference}/versions?binary-version=_3&artifact-name=cats-kernel&artifact-name=cats-core" + ) { + status shouldBe StatusCodes.OK + responseAs[Seq[Version]] should contain theSameElementsAs Seq(`2.6.1`) + } + + testGet("/api/v1/projects/unknown/unknown/versions") { + status shouldBe StatusCodes.OK // TODO this should be not found + responseAs[Seq[Version]] shouldBe empty + } + + testGet(s"/api/v1/projects/${Cats.reference}/versions/latest") { + status shouldBe StatusCodes.OK + import Cats._ + val expected = Seq( + `core_3:2.6.1`, + `core_sjs1_3:2.6.1`, + `core_sjs06_2.13:2.6.1`, + `core_native04_2.13:2.6.1`, + `kernel_3:2.6.1`, + `laws_3:2.6.1` + ).map(_.reference) + responseAs[Seq[Artifact.Reference]] should contain theSameElementsAs expected + } + + testGet(s"/api/v1/projects/${Cats.reference}/versions/2.6.1") { + status shouldBe StatusCodes.OK + val expected = Cats.allArtifacts.filter(_.version == `2.6.1`).map(_.reference) + responseAs[Seq[Artifact.Reference]] should contain theSameElementsAs expected + } + + testGet(s"/api/v1/projects/${Cats.reference}/artifacts") { + status shouldBe StatusCodes.OK + responseAs[Seq[Artifact.Reference]] should contain theSameElementsAs Cats.allArtifacts.map(_.reference) + } + + testGet(s"/api/v1/projects/${Cats.reference}/artifacts?binary-version=_3") { + status shouldBe StatusCodes.OK + import Cats._ + val expected = Seq(`core_3:2.6.1`, `kernel_3:2.6.1`, `laws_3:2.6.1`).map(_.reference) + responseAs[Seq[Artifact.Reference]] should contain theSameElementsAs expected + } + + testGet(s"/api/v1/projects/${Cats.reference}/artifacts?artifact-name=cats-core") { + status shouldBe StatusCodes.OK + val expected = Cats.coreArtifacts.map(_.reference) + responseAs[Seq[Artifact.Reference]] should contain theSameElementsAs expected + } + + testGet(s"/api/v1/projects/${Cats.reference}/artifacts?artifact-name=cats-core&binary-version=_3") { + status shouldBe StatusCodes.OK + import Cats._ + val expected = Seq(`core_3:2.6.1`).map(_.reference) + responseAs[Seq[Artifact.Reference]] should contain theSameElementsAs expected + } + + testGet("/api/v1/projects/unknown/unknown/artifacts") { + status shouldBe StatusCodes.OK // TODO this should be not found + responseAs[Seq[Artifact.Reference]] shouldBe empty + } + + testGet("/api/v1/projects/unknown/unknown/artifacts?binary-version=foo") { + status shouldBe StatusCodes.BadRequest // failed to parse binaryVersion + // TODO return error message + } + + testGet("/api/v1/artifacts/org.typelevel/cats-core_3") { + status shouldBe StatusCodes.OK + responseAs[Seq[Version]] should contain theSameElementsAs Seq(`2.6.1`) + } + + testGet("/api/v1/artifacts/org.typelevel/cats-core_3/latest") { + status shouldBe StatusCodes.OK + responseAs[ArtifactResponse] shouldBe Cats.`core_3:2.6.1`.toResponse + } + + testGet("/api/v1/artifacts/org.typelevel/cats-core_2.13/2.5.0") { + status shouldBe StatusCodes.OK + responseAs[ArtifactResponse] shouldBe Cats.`core_2.13:2.5.0`.toResponse + } + + testGet("/api/v1/artifacts/unknown/unknown_3/1.0.0") { + status shouldBe StatusCodes.NotFound } } + + private def testGet(route: String)(body: => Assertion)(implicit pos: Position): Unit = + it(route)(Get(route) ~> routes(None) ~> check(body)) } diff --git a/modules/server/src/test/scala/scaladex/server/route/api/OldSearchApiTests.scala b/modules/server/src/test/scala/scaladex/server/route/api/OldSearchApiTests.scala index fc409a7a9..bced6889e 100644 --- a/modules/server/src/test/scala/scaladex/server/route/api/OldSearchApiTests.scala +++ b/modules/server/src/test/scala/scaladex/server/route/api/OldSearchApiTests.scala @@ -32,10 +32,7 @@ class OldSearchApiTests extends ControllerBaseSuite with PlayJsonSupport { } def insertAllCatsArtifacts(): Future[Unit] = - for { - _ <- database.insertProjectRef(Cats.reference, unknown) - _ <- Future.traverse(Cats.allArtifacts)(database.insertArtifact(_)) - } yield () + Future.traverse(Cats.allArtifacts)(artifactService.insertArtifact(_, Seq.empty)).map(_ => ()) describe("route") { it("should find project") { @@ -45,10 +42,10 @@ class OldSearchApiTests extends ControllerBaseSuite with PlayJsonSupport { Get("/api/project?organization=typelevel&repository=cats") ~> searchApi.routes ~> check { val result = responseAs[OldSearchApi.ArtifactOptions] (result.artifacts should contain).theSameElementsInOrderAs(Seq("cats-core", "cats-kernel", "cats-laws")) - (result.versions should contain).theSameElementsInOrderAs(Seq(`2.7.0`, `2.6.1`).map(_.toString)) + (result.versions should contain).theSameElementsInOrderAs(Seq(`2.6.1`, `2.5.0`).map(_.toString)) result.groupId shouldBe Cats.groupId.value - result.artifactId shouldBe Cats.`core_3:2.7.0`.artifactId - result.version shouldBe `2.7.0`.toString + result.artifactId shouldBe "cats-core_3" + result.version shouldBe `2.6.1`.toString } } } diff --git a/modules/server/src/test/scala/scaladex/server/route/api/PublishApiTests.scala b/modules/server/src/test/scala/scaladex/server/route/api/PublishApiTests.scala index dbcdceaf0..1e8b971d8 100644 --- a/modules/server/src/test/scala/scaladex/server/route/api/PublishApiTests.scala +++ b/modules/server/src/test/scala/scaladex/server/route/api/PublishApiTests.scala @@ -28,7 +28,7 @@ class PublishApiTests extends ControllerBaseSuite with BeforeAndAfterEach { it("sonatype should publish any artifact") { implicit val customTimeout = RouteTestTimeout(8.seconds) - val pomFile = pomResolver.resolveSync(Cats.`core_3:2.6.1`.mavenReference) + val pomFile = pomResolver.resolveSync(Cats.`core_3:2.6.1`.reference) val creationDate = Cats.`core_3:2.6.1`.releaseDate.getEpochSecond val entity = HttpEntity.fromPath(ContentTypes.`application/octet-stream`, pomFile) val request = Put(s"/publish?created=$creationDate&path=$pomFile", entity) @@ -36,27 +36,27 @@ class PublishApiTests extends ControllerBaseSuite with BeforeAndAfterEach { request ~> publishApi.routes ~> check { status shouldBe StatusCodes.Created - for (artifacts <- database.getArtifacts(Cats.reference)) - yield artifacts should contain theSameElementsAs Seq(Cats.`core_3:2.6.1`) + for (artifact <- database.getArtifact(Cats.`core_3:2.6.1`.reference)) + yield artifact should contain(Cats.`core_3:2.6.1`) } } it("admin should publish any artifact") { - val pomFile = pomResolver.resolveSync(Cats.`core_3:2.7.0`.mavenReference) - val creationDate = Cats.`core_3:2.7.0`.releaseDate.getEpochSecond + val pomFile = pomResolver.resolveSync(Cats.`core_2.13:2.5.0`.reference) + val creationDate = Cats.`core_2.13:2.5.0`.releaseDate.getEpochSecond val entity = HttpEntity.fromPath(ContentTypes.`application/octet-stream`, pomFile) val request = Put(s"/publish?created=$creationDate&path=$pomFile", entity) .addCredentials(admin) request ~> publishApi.routes ~> check { status shouldBe StatusCodes.Created - for (artifacts <- database.getArtifacts(Cats.reference)) - yield artifacts should contain theSameElementsAs Seq(Cats.`core_3:2.7.0`) + for (artifacts <- database.getArtifact(Cats.`core_2.13:2.5.0`.reference)) + yield artifacts should contain(Cats.`core_2.13:2.5.0`) } } it("owner should publish artifact of its project") { - val pomFile = pomResolver.resolveSync(Cats.`core_sjs1_3:2.6.1`.mavenReference) + val pomFile = pomResolver.resolveSync(Cats.`core_sjs1_3:2.6.1`.reference) val creationDate = Cats.`core_sjs1_3:2.6.1`.releaseDate.getEpochSecond val entity = HttpEntity.fromPath(ContentTypes.`application/octet-stream`, pomFile) val request = Put(s"/publish?created=$creationDate&path=$pomFile", entity) @@ -64,13 +64,13 @@ class PublishApiTests extends ControllerBaseSuite with BeforeAndAfterEach { request ~> publishApi.routes ~> check { status shouldBe StatusCodes.Created - for (artifacts <- database.getArtifacts(Cats.reference)) - yield artifacts should contain theSameElementsAs Seq(Cats.`core_sjs1_3:2.6.1`) + for (artifacts <- database.getArtifact(Cats.`core_sjs1_3:2.6.1`.reference)) + yield artifacts should contain(Cats.`core_sjs1_3:2.6.1`) } } it("user should not publish artifcat of project it does not own") { - val pomFile = pomResolver.resolveSync(Scalafix.artifact.mavenReference) + val pomFile = pomResolver.resolveSync(Scalafix.artifact.reference) val creationDate = Scalafix.artifact.releaseDate.getEpochSecond val entity = HttpEntity.fromPath(ContentTypes.`application/octet-stream`, pomFile) val request = Put(s"/publish?created=$creationDate&path=$pomFile", entity) @@ -78,23 +78,20 @@ class PublishApiTests extends ControllerBaseSuite with BeforeAndAfterEach { request ~> publishApi.routes ~> check { // status shouldBe StatusCodes.Forbidden - for (artifacts <- database.getArtifacts(Scalafix.reference)) + for (artifacts <- database.getArtifact(Scalafix.artifact.reference)) yield artifacts shouldBe empty } } it("publish sbt plugin with cross version") { implicit val customTimeout = RouteTestTimeout(2.minutes) - val pomFile = pomResolver.resolveSync(SbtCrossProject.mavenReference) + val pomFile = pomResolver.resolveSync(SbtCrossProject.artifactRef) val creationDate = SbtCrossProject.creationDate.getEpochSecond val entity = HttpEntity.fromPath(ContentTypes.`application/octet-stream`, pomFile) val request = Put(s"/publish?created=$creationDate&path=$pomFile", entity).addCredentials(admin) request ~> publishApi.routes ~> check { - for (artifacts <- database.getArtifacts(SbtCrossProject.reference)) - yield { - val mavenRefs = artifacts.map(_.mavenReference) - mavenRefs should contain theSameElementsAs Seq(SbtCrossProject.mavenReference) - } + for (artifacts <- database.getProjectArtifactRefs(SbtCrossProject.reference, stableOnly = false)) + yield artifacts should contain theSameElementsAs Seq(SbtCrossProject.artifactRef) } } } diff --git a/modules/template/src/main/scala/scaladex/view/InstallTab.scala b/modules/template/src/main/scala/scaladex/view/InstallTab.scala index 57b5862e9..c859da6a0 100644 --- a/modules/template/src/main/scala/scaladex/view/InstallTab.scala +++ b/modules/template/src/main/scala/scaladex/view/InstallTab.scala @@ -9,7 +9,7 @@ case class InstallTab(ref: String, title: String, install: String, description: object InstallTab { def allOf(artifact: Artifact, cliArtifacts: Set[Artifact.Name]): Seq[InstallTab] = { val coursierTab = - if (cliArtifacts.contains(artifact.artifactName)) + if (cliArtifacts.contains(artifact.name)) artifact.csLaunch.map( InstallTab( "coursier", diff --git a/modules/template/src/main/scala/scaladex/view/html/package.scala b/modules/template/src/main/scala/scaladex/view/html/package.scala index ca38945fb..4aaa543aa 100644 --- a/modules/template/src/main/scala/scaladex/view/html/package.scala +++ b/modules/template/src/main/scala/scaladex/view/html/package.scala @@ -54,9 +54,9 @@ package object html { val newUri = uri .appendQuery("sort" -> params.sorting.label) - .appendQuery("topics", params.topics) - .appendQuery("languages", params.languages) - .appendQuery("platforms", params.platforms) + .appendQuery("topic", params.topics) + .appendQuery("language", params.languages.map(_.value)) + .appendQuery("platform", params.platforms.map(_.value)) .appendQuery("you", you) .appendQuery("q" -> params.queryString) .appendQuery("page" -> page.toString) @@ -72,33 +72,33 @@ package object html { def paginationUri(category: Category, params: AwesomeParams)(page: Int): Uri = Uri(s"/awesome/${category.label}") .appendQuery("sort" -> params.sorting.label) - .appendQuery("languages", params.languages.map(_.label)) - .appendQuery("platforms", params.platforms.map(_.label)) + .appendQuery("language", params.languages.map(_.value)) + .appendQuery("platform", params.platforms.map(_.value)) .appendQuery("page" -> page.toString) def awesomeCategoryUri(category: Category, params: AwesomeParams): Uri = Uri(s"/awesome/${category.label}") .appendQuery("sort" -> params.sorting.label) - .appendQuery("languages", params.languages.map(_.label)) - .appendQuery("platforms", params.platforms.map(_.label)) + .appendQuery("language", params.languages.map(_.value)) + .appendQuery("platform", params.platforms.map(_.value)) def versionsUri(ref: Project.Reference, artifactName: Artifact.Name, params: ArtifactsPageParams): Uri = Uri(s"/$ref/artifacts/$artifactName") - .appendQuery("binary-versions", params.binaryVersions.map(_.label)) + .appendQuery("binary-version", params.binaryVersions.map(_.value)) .appendQuery(("stable-only", params.stableOnly.toString)) def versionsUri(ref: Project.Reference, artifactName: Artifact.Name, binaryVersion: Option[BinaryVersion]): Uri = Uri(s"/$ref/artifacts/$artifactName") - .appendQuery("binary-versions", binaryVersion.map(_.label)) + .appendQuery("binary-version", binaryVersion.map(_.value)) def artifactsUri(ref: Project.Reference, params: ArtifactsPageParams): Uri = Uri(s"/$ref/artifacts") - .appendQuery("binary-versions", params.binaryVersions.map(_.label)) + .appendQuery("binary-version", params.binaryVersions.map(_.value)) .appendQuery(("stable-only", params.stableOnly.toString)) def artifactsUri(ref: Project.Reference, binaryVersion: Option[BinaryVersion]): Uri = Uri(s"/$ref/artifacts") - .appendQuery("binary-versions", binaryVersion.map(_.label)) + .appendQuery("binary-version", binaryVersion.map(_.value)) // https://www.reddit.com/r/scala/comments/4n73zz/scala_puzzle_gooooooogle_pagination/d41jor5 def paginationRender(selected: Int, max: Int, toShow: Int = 10): (Option[Int], List[Int], Option[Int]) = { diff --git a/modules/template/src/main/scala/scaladex/view/model/EcosystemHighlight.scala b/modules/template/src/main/scala/scaladex/view/model/EcosystemHighlight.scala index 997cc583c..a28b7f91d 100644 --- a/modules/template/src/main/scala/scaladex/view/model/EcosystemHighlight.scala +++ b/modules/template/src/main/scala/scaladex/view/model/EcosystemHighlight.scala @@ -1,9 +1,9 @@ package scaladex.view.model -import scaladex.core.model.SemanticVersion import scaladex.core.model.Url +import scaladex.core.model.Version -final case class EcosystemVersion(version: SemanticVersion, libraryCount: Int, search: Url) +final case class EcosystemVersion(version: Version, libraryCount: Int, search: Url) object EcosystemVersion { val ordering: Ordering[EcosystemVersion] = Ordering.by(_.version) diff --git a/modules/template/src/main/twirl/scaladex/view/awesome/awesomeScala.scala.html b/modules/template/src/main/twirl/scaladex/view/awesome/awesomeScala.scala.html index cfb042236..ff0b2f9c7 100644 --- a/modules/template/src/main/twirl/scaladex/view/awesome/awesomeScala.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/awesome/awesomeScala.scala.html @@ -31,9 +31,9 @@

Awesome Scala

- @for(platform <- platforms) { - } @@ -42,9 +42,9 @@

Awesome Scala

- @for(language <- languages) { - } diff --git a/modules/template/src/main/twirl/scaladex/view/awesome/filter.scala.html b/modules/template/src/main/twirl/scaladex/view/awesome/filter.scala.html index 7d96a0682..b235190d4 100644 --- a/modules/template/src/main/twirl/scaladex/view/awesome/filter.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/awesome/filter.scala.html @@ -22,7 +22,7 @@

Filters

  • @@ -36,7 +36,7 @@

    Filters

  • diff --git a/modules/template/src/main/twirl/scaladex/view/awesome/resultList.scala.html b/modules/template/src/main/twirl/scaladex/view/awesome/resultList.scala.html index 1547e3ba3..d9e117bdf 100644 --- a/modules/template/src/main/twirl/scaladex/view/awesome/resultList.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/awesome/resultList.scala.html @@ -13,7 +13,7 @@

    @project.reference @for(latestVersion <- project.latestVersion){ -   @latestVersion.encode +   @latestVersion.value }

    @@ -74,7 +74,7 @@

    @if(github.topics.nonEmpty) { @for(topic <- github.topics) { - + @topic diff --git a/modules/template/src/main/twirl/scaladex/view/awesome/sorting.scala.html b/modules/template/src/main/twirl/scaladex/view/awesome/sorting.scala.html index 93f62f4e3..d91a775e1 100644 --- a/modules/template/src/main/twirl/scaladex/view/awesome/sorting.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/awesome/sorting.scala.html @@ -9,11 +9,11 @@
    @for(language <- params.languages){ - + } @for(platform <- params.platforms){ - + }
    diff --git a/modules/template/src/main/twirl/scaladex/view/frontpage.scala.html b/modules/template/src/main/twirl/scaladex/view/frontpage.scala.html index 442e9904d..ac02ca191 100644 --- a/modules/template/src/main/twirl/scaladex/view/frontpage.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/frontpage.scala.html @@ -115,7 +115,7 @@

    @highlight.ecosystem @highlight.currentVersion.version @for(TopicCount(topic, count) <- topics) { -
  • @topic@count
  • +
  • @topic@count
  • } } diff --git a/modules/template/src/main/twirl/scaladex/view/project/artifact.scala.html b/modules/template/src/main/twirl/scaladex/view/project/artifact.scala.html index 680c07522..dbbacade4 100644 --- a/modules/template/src/main/twirl/scaladex/view/project/artifact.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/project/artifact.scala.html @@ -32,7 +32,7 @@ @for((platform, binaryVersions) <- binaryVersions.groupBy(_.platform)) { @for(binaryVersion <- binaryVersions.sortBy(_.language)(Language.ordering.reverse)) { - @@ -43,9 +43,9 @@

    - @artifact.artifactName + @artifact.name @artifact.version - @if(project.settings.deprecatedArtifacts.contains(artifact.artifactName)) { + @if(project.settings.deprecatedArtifacts.contains(artifact.name)) { Deprecated }

    @@ -67,9 +67,9 @@

    } } @if(artifact.resolver.isEmpty) { - @info("Files") { View all } + @info("Files") { View all } } - @artifact.fullScalaVersion.map{ version => @info("Full Scala Version") { @version.toString }} + @artifact.fullScalaVersion.map{ version => @info("Full Scala Version") { @version }} @developers

    @installBox(InstallTab.allOf(artifact, project.settings.cliArtifacts)) diff --git a/modules/template/src/main/twirl/scaladex/view/project/artifacts.scala.html b/modules/template/src/main/twirl/scaladex/view/project/artifacts.scala.html index 90b659e82..574af508e 100644 --- a/modules/template/src/main/twirl/scaladex/view/project/artifacts.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/project/artifacts.scala.html @@ -17,7 +17,7 @@ user: Option[UserState], project: Project, header: Option[ProjectHeader], - artifacts: Seq[(Artifact.Name, SemanticVersion, Seq[Artifact])], + artifacts: Seq[(Artifact.Name, Version, Seq[Artifact])], params: ArtifactsPageParams, allBinaryVersions: Seq[BinaryVersion] ) @@ -64,7 +64,7 @@ ) {

    @platform:
    @for(binaryVersion <- binaryVersions.map(_.binaryVersion).distinct.sorted.reverse) { - + @binaryVersion.language.label } diff --git a/modules/template/src/main/twirl/scaladex/view/project/artifactsFilters.scala.html b/modules/template/src/main/twirl/scaladex/view/project/artifactsFilters.scala.html index 3cc05f513..7e4dd9534 100644 --- a/modules/template/src/main/twirl/scaladex/view/project/artifactsFilters.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/project/artifactsFilters.scala.html @@ -2,8 +2,6 @@ @import scaladex.core.web.ArtifactsPageParams @import scaladex.view.html._ -@import java.time.Instant - @( ref: Project.Reference, params: ArtifactsPageParams, @@ -12,13 +10,13 @@ )
    - @platform @@ -41,7 +41,7 @@ @allBinaryVersions.map { binaryVersion => @@ -59,22 +59,19 @@ @version - @artifactsByNames.map { case (artifactName, artifacts) => - @defining(artifacts.map(a => a.binaryVersion -> a).toMap) { binaryVersionToArtifact => - - @artifactName - @allBinaryVersions.map { binaryVersion => - - @if(binaryVersionToArtifact.contains(binaryVersion)) { - - } - + @artifactsByNames.map { case (artifactName, binaryVersions) => + + @artifactName + @allBinaryVersions.map { binaryVersion => + + @if(binaryVersions.contains(binaryVersion)) { + } - - } + + } + } } diff --git a/modules/template/src/main/twirl/scaladex/view/project/versions.scala.html b/modules/template/src/main/twirl/scaladex/view/project/versions.scala.html index 9a7d1a573..edcc95412 100644 --- a/modules/template/src/main/twirl/scaladex/view/project/versions.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/project/versions.scala.html @@ -18,7 +18,7 @@ header: ProjectHeader, artifactName: Artifact.Name, allBinaryVersions: Seq[BinaryVersion], - artifactByVersions: SortedMap[(Instant, SemanticVersion), Seq[Artifact]], + artifactByVersions: SortedMap[(Instant, Version), Seq[Artifact]], params: ArtifactsPageParams ) @@ -77,7 +77,7 @@

    ) {

    @platform:
    @for(binaryVersion <- binaryVersions) { - + @binaryVersion.language.label } diff --git a/modules/template/src/main/twirl/scaladex/view/search/filter.scala.html b/modules/template/src/main/twirl/scaladex/view/search/filter.scala.html index 1040e0a4b..7437d596a 100644 --- a/modules/template/src/main/twirl/scaladex/view/search/filter.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/search/filter.scala.html @@ -24,8 +24,8 @@

    Filters

    @for((language, count) <- languages) {
  • @@ -38,8 +38,8 @@

    Filters

    @for((platform, count) <- platforms) {
  • @@ -53,7 +53,7 @@

    Filters

  • diff --git a/modules/template/src/main/twirl/scaladex/view/search/resultList.scala.html b/modules/template/src/main/twirl/scaladex/view/search/resultList.scala.html index 4b8d39167..72f72d237 100644 --- a/modules/template/src/main/twirl/scaladex/view/search/resultList.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/search/resultList.scala.html @@ -25,7 +25,7 @@

    @project.organization/@project.repository

    @project.reference @for(latestVersion <- project.latestVersion){ -   @latestVersion.encode +   @latestVersion.value }

    @@ -88,7 +88,7 @@

    @if(github.topics.nonEmpty) { @for(topic <- github.topics) { - + @topic diff --git a/modules/template/src/main/twirl/scaladex/view/search/sorting.scala.html b/modules/template/src/main/twirl/scaladex/view/search/sorting.scala.html index e0c35d6fd..50921fc8d 100644 --- a/modules/template/src/main/twirl/scaladex/view/search/sorting.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/search/sorting.scala.html @@ -11,15 +11,15 @@ } @for(topic <- params.topics){ - + } @for(language <- params.languages){ - + } @for(platform <- params.platforms){ - + } @if(params.contributingSearch){ diff --git a/modules/template/src/main/twirl/scaladex/view/searchinput.scala.html b/modules/template/src/main/twirl/scaladex/view/searchinput.scala.html index d15bd74db..c0f5e2981 100644 --- a/modules/template/src/main/twirl/scaladex/view/searchinput.scala.html +++ b/modules/template/src/main/twirl/scaladex/view/searchinput.scala.html @@ -19,15 +19,15 @@ } @for(topic <- params.topics){ - + } @for(language <- params.languages){ - + } @for(platform <- params.platforms){ - + } @if(params.contributingSearch) { diff --git a/modules/webclient/src/main/scala/scaladex/client/Dom.scala b/modules/webclient/src/main/scala/scaladex/client/Dom.scala index 780949c01..7f0d54b83 100644 --- a/modules/webclient/src/main/scala/scaladex/client/Dom.scala +++ b/modules/webclient/src/main/scala/scaladex/client/Dom.scala @@ -5,15 +5,16 @@ import org.scalajs.dom.HTMLInputElement import org.scalajs.dom.Node import org.scalajs.dom.document import scaladex.core.api.AutocompletionParams +import scaladex.core.model._ object Dom { def getSearchRequest: Option[AutocompletionParams] = for (query <- getSearchQuery) yield AutocompletionParams( query = query, - topics = getSearchFilter("topics"), - languages = getSearchFilter("languages"), - platforms = getSearchFilter("platforms"), + topics = getSearchFilter("topic"), + languages = getSearchFilter("language").flatMap(Language.parse), + platforms = getSearchFilter("platform").flatMap(Platform.parse), contributingSearch = getById[HTMLInputElement]("contributing-search").map(_.value).contains("true"), you = getById[HTMLInputElement]("you").map(_.value).contains("✓") )