From b99c57d65c1419a9aff36b233e096d40762377e6 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Thu, 14 Apr 2022 09:44:09 -0700 Subject: [PATCH 1/3] Adding database method to get artifact via ids * New method: `WebDatabase#getArtifact(Artifact.groupId, Artifact.artifactId) * Tests added in: * `ArtifactTableTests.scala` * `SqlDatabaseTests.scala` * `Meta` instance created for `Artifact.ArtifactId`. --- .../scaladex/core/service/WebDatabase.scala | 1 + .../scaladex/core/test/InMemoryDatabase.scala | 7 +++++ .../scala/scaladex/infra/SqlDatabase.scala | 3 ++ .../scaladex/infra/sql/ArtifactTable.scala | 3 ++ .../scaladex/infra/sql/DoobieUtils.scala | 2 ++ .../scaladex/infra/SqlDatabaseTests.scala | 28 +++++++++++++++++++ .../infra/sql/ArtifactTableTests.scala | 1 + 7 files changed, 45 insertions(+) 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 a9f88e19c..0511aa7a6 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 @@ -22,6 +22,7 @@ trait WebDatabase { def getProject(projectRef: Project.Reference): Future[Option[Project]] def getFormerReferences(projectRef: Project.Reference): Future[Seq[Project.Reference]] def countInverseProjectDependencies(projectRef: Project.Reference): Future[Int] + def getArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] def getArtifacts(projectRef: Project.Reference): Future[Seq[Artifact]] def getArtifactsByName(projectRef: Project.Reference, artifactName: Artifact.Name): Future[Seq[Artifact]] def getArtifactByMavenReference(mavenRef: Artifact.MavenReference): Future[Option[Artifact]] 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 b7fd5fbde..766088484 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 @@ -57,6 +57,13 @@ class InMemoryDatabase extends SchedulerDatabase { override def getProject(projectRef: Project.Reference): Future[Option[Project]] = Future.successful(projects.get(projectRef)) + override def getArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] = + Future.successful { + artifacts.values.flatten.find { artifact: Artifact => + artifact.groupId == groupId && artifact.artifactId == artifactId.value + } + } + override def getArtifacts(projectRef: Project.Reference): Future[Seq[Artifact]] = Future.successful(artifacts.getOrElse(projectRef, Nil)) diff --git a/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala b/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala index 2a086b81c..4b646ad7f 100644 --- a/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala +++ b/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala @@ -129,6 +129,9 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten override def getProject(projectRef: Project.Reference): Future[Option[Project]] = run(ProjectTable.selectByReference.option(projectRef)) + override def getArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] = + run(ArtifactTable.selectArtifactByGroupIdAndArtifactId.option(groupId, artifactId)) + override def getArtifacts(projectRef: Project.Reference): Future[Seq[Artifact]] = run(ArtifactTable.selectArtifactByProject.to[Seq](projectRef)) 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 2acc136c0..529eda7aa 100644 --- a/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactTable.scala +++ b/modules/infra/src/main/scala/scaladex/infra/sql/ArtifactTable.scala @@ -65,6 +65,9 @@ object ArtifactTable { val selectArtifactByLanguageAndPlatform: Query[(Language, Platform), Artifact] = selectRequest(table, fields, keys = Seq("language_version", "platform")) + val selectArtifactByGroupIdAndArtifactId: Query[(Artifact.GroupId, Artifact.ArtifactId), Artifact] = + selectRequest(table, fields, Seq("group_id", "artifact_id")) + val selectArtifactByProject: Query[Project.Reference, Artifact] = { val where = projectReferenceFields.map(f => s"$f=?").mkString(" AND ") selectRequest1( 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 7cafb2572..a38de7e1d 100644 --- a/modules/infra/src/main/scala/scaladex/infra/sql/DoobieUtils.scala +++ b/modules/infra/src/main/scala/scaladex/infra/sql/DoobieUtils.scala @@ -153,6 +153,8 @@ object DoobieUtils { 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 binaryVersionMeta: Meta[BinaryVersion] = Meta[String].timap { x => BinaryVersion diff --git a/modules/infra/src/test/scala/scaladex/infra/SqlDatabaseTests.scala b/modules/infra/src/test/scala/scaladex/infra/SqlDatabaseTests.scala index 00484c7fa..1767880ac 100644 --- a/modules/infra/src/test/scala/scaladex/infra/SqlDatabaseTests.scala +++ b/modules/infra/src/test/scala/scaladex/infra/SqlDatabaseTests.scala @@ -334,4 +334,32 @@ class SqlDatabaseTests extends AsyncFunSpec with BaseDatabaseSuite with Matchers ) } yield storedArtifacts.size shouldBe 0 } + + it("should not return any artifacts when the database is empty, given a group id and artifact id") { + val testArtifact = Cats.`core_3:4` + val testArtifactId = Artifact.ArtifactId + .parse(testArtifact.artifactId) + .getOrElse(fail("Parsing an artifact id should not have failed")) + for { + maybeRetrievedArtifact <- database.getArtifact(testArtifact.groupId, testArtifactId) + storedArtifacts <- database.getAllArtifacts(None, None) + } yield { + maybeRetrievedArtifact shouldBe None + storedArtifacts.size shouldBe 0 + } + } + + it("should return an artifact, given a group id an artifact id of a stored artifact") { + val testArtifact = Cats.`core_3:4` + val testArtifactId = Artifact.ArtifactId + .parse(testArtifact.artifactId) + .getOrElse(fail("Parsing an artifact id should not have failed")) + for { + isStoredSuccessfully <- database.insertArtifact(testArtifact, dependencies = Cats.dependencies, now) + maybeRetrievedArtifact <- database.getArtifact(testArtifact.groupId, testArtifactId) + } yield { + isStoredSuccessfully shouldBe true + maybeRetrievedArtifact shouldBe Some(testArtifact) + } + } } 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 1626a89ae..919b0ed1d 100644 --- a/modules/infra/src/test/scala/scaladex/infra/sql/ArtifactTableTests.scala +++ b/modules/infra/src/test/scala/scaladex/infra/sql/ArtifactTableTests.scala @@ -10,6 +10,7 @@ class ArtifactTableTests extends AnyFunSpec with BaseDatabaseSuite with Matchers it("check selectArtifactByLanguage")(check(ArtifactTable.selectArtifactByLanguage)) it("check selectArtifactByPlatform")(check(ArtifactTable.selectArtifactByPlatform)) it("check selectArtifactByLanguageAndPlatform")(check(ArtifactTable.selectArtifactByLanguageAndPlatform)) + it("check selectArtifactByGroupIdAndArtifactId")(check(ArtifactTable.selectArtifactByGroupIdAndArtifactId)) it("check selectArtifactByProject")(check(ArtifactTable.selectArtifactByProject)) it("check selectArtifactByProjectAndName")(check(ArtifactTable.selectArtifactByProjectAndName)) it("check findOldestArtifactsPerProjectReference")(check(ArtifactTable.selectOldestByProject)) From b2421080ced3b97a8b9a2ae4fa7fb95516476e4a Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 15 Apr 2022 08:43:58 -0700 Subject: [PATCH 2/3] Adding `/api/artifacts//` * Endpoints added along with documentation, return value is of type `Page[ArtifactMetadataResponse]`. * `WebDatabase#getArtifact: Future[Option[Artifact]]` has been refactored to `WebDatabase#getArtifact: Future[Seq[Artifact]]` given the fact that there can be more than one artifact with the same `group_id` and `artifcact_id` can be diff. versions. * Tests added for endpoints. --- .../api/artifact/ArtifactEndpointSchema.scala | 10 ++++++ .../core/api/artifact/ArtifactEndpoints.scala | 19 +++++++++++ .../api/artifact/ArtifactMetadataParams.scala | 3 ++ .../artifact/ArtifactMetadataResponse.scala | 9 +++++ .../scala/scaladex/core/model/Artifact.scala | 9 +++++ .../scaladex/core/model/search/Page.scala | 9 +++++ .../scaladex/core/service/WebDatabase.scala | 2 +- .../scaladex/core/test/InMemoryDatabase.scala | 6 ++-- .../scala/scaladex/infra/SqlDatabase.scala | 4 +-- .../scaladex/infra/SqlDatabaseTests.scala | 28 +++++++++++----- .../server/route/api/ApiDocumentation.scala | 2 +- .../server/route/api/ArtifactApi.scala | 25 ++++++++++++++ .../server/route/api/ArtifactApiTests.scala | 33 +++++++++++++++---- 13 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactMetadataParams.scala create mode 100644 modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactMetadataResponse.scala diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactEndpointSchema.scala b/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactEndpointSchema.scala index b2406377a..d1d1e2d62 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactEndpointSchema.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactEndpointSchema.scala @@ -10,4 +10,14 @@ trait ArtifactEndpointSchema extends PaginationSchema { .xmap[ArtifactResponse] { case (groupId, artifactId) => ArtifactResponse(groupId, artifactId) } { case ArtifactResponse(groupId, artifactId) => (groupId, artifactId) } + + implicit val artifactMetadataResponseSchema: JsonSchema[ArtifactMetadataResponse] = + field[String]("version") + .zip(optField[String]("projectReference")) + .zip(field[String]("releaseDate")) + .zip(field[String]("language")) + .zip(field[String]("platform")) + .xmap[ArtifactMetadataResponse](ArtifactMetadataResponse.tupled)( + Function.unlift(ArtifactMetadataResponse.unapply) + ) } diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactEndpoints.scala b/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactEndpoints.scala index 2f2e8c905..9a6c69a4b 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactEndpoints.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactEndpoints.scala @@ -17,10 +17,29 @@ trait ArtifactEndpoints docs = Some("Filter the results matching the given platform only (e.g., 'jvm', 'sjs1', 'native0.4', 'sbt1.0')") )).xmap((ArtifactParams.apply _).tupled)(Function.unlift(ArtifactParams.unapply)) + val artifactMetadataEndpointParams: Path[ArtifactMetadataParams] = (segment[String]( + name = "group_id", + docs = Some( + "Filter the results matching the given group id only (e.g., 'org.typelevel', 'org.scala-lang', 'org.apache.spark')" + ) + ) / segment[String]( + name = "artifact_id", + docs = Some( + "Filter the results matching the given artifact id only (e.g., 'cats-core_3', 'cats-core_sjs0.6_2.13')" + ) + )).xmap(ArtifactMetadataParams.tupled)(Function.unlift(ArtifactMetadataParams.unapply)) + // Artifact endpoint definition val artifact: Endpoint[ArtifactParams, Page[ArtifactResponse]] = endpoint( get(path / "api" / "artifacts" /? artifactEndpointParams), ok(jsonResponse[Page[ArtifactResponse]]) ) + + // Artifact metadata endpoint definition + val artifactMetadata: Endpoint[ArtifactMetadataParams, Page[ArtifactMetadataResponse]] = + endpoint( + get(path / "api" / "artifacts" / artifactMetadataEndpointParams), + ok(jsonResponse[Page[ArtifactMetadataResponse]]) + ) } diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactMetadataParams.scala b/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactMetadataParams.scala new file mode 100644 index 000000000..f8985e4a1 --- /dev/null +++ b/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactMetadataParams.scala @@ -0,0 +1,3 @@ +package scaladex.core.api.artifact + +final case class ArtifactMetadataParams(groupId: String, artifactId: String) diff --git a/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactMetadataResponse.scala b/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactMetadataResponse.scala new file mode 100644 index 000000000..b6872c20d --- /dev/null +++ b/modules/core/shared/src/main/scala/scaladex/core/api/artifact/ArtifactMetadataResponse.scala @@ -0,0 +1,9 @@ +package scaladex.core.api.artifact + +final case class ArtifactMetadataResponse( + version: String, + projectReference: Option[String], + releaseDate: String, + language: String, + platform: String +) 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 c396c2539..806f9d7ef 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 @@ -5,6 +5,7 @@ import java.time.Instant import fastparse.P import fastparse.Start import fastparse._ +import scaladex.core.api.artifact.ArtifactMetadataResponse import scaladex.core.model.PatchVersion import scaladex.core.model.Project.DocumentationLink import scaladex.core.util.Parsers._ @@ -243,4 +244,12 @@ object Artifact { s"http://search.maven.org/#artifactdetails|$groupId|$artifactId|$version|jar" } + def toMetadataResponse(artifact: Artifact): ArtifactMetadataResponse = + ArtifactMetadataResponse( + version = artifact.version.toString, + projectReference = Some(artifact.projectRef.toString), + releaseDate = artifact.releaseDate.toString, + language = artifact.language.toString, + platform = artifact.platform.toString + ) } diff --git a/modules/core/shared/src/main/scala/scaladex/core/model/search/Page.scala b/modules/core/shared/src/main/scala/scaladex/core/model/search/Page.scala index 84f93560e..f4a02f89e 100644 --- a/modules/core/shared/src/main/scala/scaladex/core/model/search/Page.scala +++ b/modules/core/shared/src/main/scala/scaladex/core/model/search/Page.scala @@ -5,3 +5,12 @@ case class Page[A](pagination: Pagination, items: Seq[A]) { def flatMap[B](f: A => Iterable[B]): Page[B] = Page(pagination, items.flatMap(f)) } + +object Page { + + def empty[A]: Page[A] = + Page( + pagination = Pagination(current = 1, pageCount = 1, totalSize = 0), + items = Seq.empty + ) +} 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 0511aa7a6..6bf863fb3 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 @@ -22,7 +22,7 @@ trait WebDatabase { def getProject(projectRef: Project.Reference): Future[Option[Project]] def getFormerReferences(projectRef: Project.Reference): Future[Seq[Project.Reference]] def countInverseProjectDependencies(projectRef: Project.Reference): Future[Int] - def getArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] + def getArtifacts(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[Artifact]] def getArtifacts(projectRef: Project.Reference): Future[Seq[Artifact]] def getArtifactsByName(projectRef: Project.Reference, artifactName: Artifact.Name): Future[Seq[Artifact]] def getArtifactByMavenReference(mavenRef: Artifact.MavenReference): Future[Option[Artifact]] 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 766088484..cb654f017 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 @@ -57,11 +57,11 @@ class InMemoryDatabase extends SchedulerDatabase { override def getProject(projectRef: Project.Reference): Future[Option[Project]] = Future.successful(projects.get(projectRef)) - override def getArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] = + override def getArtifacts(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Seq[Artifact]] = Future.successful { - artifacts.values.flatten.find { artifact: Artifact => + artifacts.values.flatten.filter { artifact: Artifact => artifact.groupId == groupId && artifact.artifactId == artifactId.value - } + }.toSeq } override def getArtifacts(projectRef: Project.Reference): Future[Seq[Artifact]] = diff --git a/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala b/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala index 4b646ad7f..307cef40e 100644 --- a/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala +++ b/modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala @@ -129,8 +129,8 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten override def getProject(projectRef: Project.Reference): Future[Option[Project]] = run(ProjectTable.selectByReference.option(projectRef)) - override def getArtifact(groupId: Artifact.GroupId, artifactId: Artifact.ArtifactId): Future[Option[Artifact]] = - run(ArtifactTable.selectArtifactByGroupIdAndArtifactId.option(groupId, artifactId)) + 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)) diff --git a/modules/infra/src/test/scala/scaladex/infra/SqlDatabaseTests.scala b/modules/infra/src/test/scala/scaladex/infra/SqlDatabaseTests.scala index 1767880ac..3e2a6de4b 100644 --- a/modules/infra/src/test/scala/scaladex/infra/SqlDatabaseTests.scala +++ b/modules/infra/src/test/scala/scaladex/infra/SqlDatabaseTests.scala @@ -341,12 +341,8 @@ class SqlDatabaseTests extends AsyncFunSpec with BaseDatabaseSuite with Matchers .parse(testArtifact.artifactId) .getOrElse(fail("Parsing an artifact id should not have failed")) for { - maybeRetrievedArtifact <- database.getArtifact(testArtifact.groupId, testArtifactId) - storedArtifacts <- database.getAllArtifacts(None, None) - } yield { - maybeRetrievedArtifact shouldBe None - storedArtifacts.size shouldBe 0 - } + retrievedArtifacts <- database.getArtifacts(testArtifact.groupId, testArtifactId) + } yield retrievedArtifacts.size shouldBe 0 } it("should return an artifact, given a group id an artifact id of a stored artifact") { @@ -356,10 +352,26 @@ class SqlDatabaseTests extends AsyncFunSpec with BaseDatabaseSuite with Matchers .getOrElse(fail("Parsing an artifact id should not have failed")) for { isStoredSuccessfully <- database.insertArtifact(testArtifact, dependencies = Cats.dependencies, now) - maybeRetrievedArtifact <- database.getArtifact(testArtifact.groupId, testArtifactId) + retrievedArtifacts <- database.getArtifacts(testArtifact.groupId, testArtifactId) } yield { isStoredSuccessfully shouldBe true - maybeRetrievedArtifact shouldBe Some(testArtifact) + retrievedArtifacts.size shouldBe 1 + retrievedArtifacts.headOption shouldBe Some(testArtifact) + } + } + + it("should return all versions of an artifact given a group id and an artifact id") { + val testArtifacts = Seq(Cats.`core_3:4`, Cats.`core_3:2.7.0`) + val groupId = Artifact.GroupId("org.typelevel") + val artifactId = Artifact.ArtifactId + .parse("cats-core_3") + .getOrElse(fail("Parsing an artifact id should not have failed")) + for { + _ <- database.insertArtifacts(testArtifacts) + retrievedArtifacts <- database.getArtifacts(groupId, artifactId) + } yield { + retrievedArtifacts.size shouldBe 2 + retrievedArtifacts should contain theSameElementsAs testArtifacts } } } 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 7fc3696d1..15cc47ad6 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 @@ -20,5 +20,5 @@ object ApiDocumentation title = "Scaladex API", version = "0.1.0" ) - )(autocomplete, artifact) + )(autocomplete, artifact, artifactMetadata) } diff --git a/modules/server/src/main/scala/scaladex/server/route/api/ArtifactApi.scala b/modules/server/src/main/scala/scaladex/server/route/api/ArtifactApi.scala index 2f8427436..98dd55689 100644 --- a/modules/server/src/main/scala/scaladex/server/route/api/ArtifactApi.scala +++ b/modules/server/src/main/scala/scaladex/server/route/api/ArtifactApi.scala @@ -1,13 +1,18 @@ package scaladex.server.route.api import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route import ch.megard.akka.http.cors.scaladsl.CorsDirectives.cors import endpoints4s.akkahttp.server import scaladex.core.api.artifact.ArtifactEndpoints +import scaladex.core.api.artifact.ArtifactMetadataParams +import scaladex.core.api.artifact.ArtifactMetadataResponse import scaladex.core.api.artifact.ArtifactParams import scaladex.core.api.artifact.ArtifactResponse +import scaladex.core.model.Artifact import scaladex.core.model.Language import scaladex.core.model.Platform import scaladex.core.model.search.Page @@ -41,6 +46,26 @@ class ArtifactApi(database: WebDatabase)( items = distinctArtifacts ) } + } ~ artifactMetadata.implementedByAsync { + case ArtifactMetadataParams(groupId, artifactId) => + val parsedGroupId = Artifact.GroupId(groupId) + Artifact.ArtifactId.parse(artifactId).fold(Future.successful(Page.empty[ArtifactMetadataResponse])) { + parsedArtifactId => + val futureArtifacts = database.getArtifacts(parsedGroupId, parsedArtifactId) + val futureResponses = futureArtifacts.map(_.map(Artifact.toMetadataResponse)) + futureResponses.map { resp => + // TODO: The values below are placeholders, will need to populate them w. real data. + // See: https://github.com/scalacenter/scaladex/pull/992#discussion_r841500215 + Page( + pagination = Pagination( + current = 1, + pageCount = 1, + totalSize = resp.size + ), + items = resp + ) + } + } } } } diff --git a/modules/server/src/test/scala/scaladex/server/route/api/ArtifactApiTests.scala b/modules/server/src/test/scala/scaladex/server/route/api/ArtifactApiTests.scala index 72e579a42..42c5f6ce4 100644 --- a/modules/server/src/test/scala/scaladex/server/route/api/ArtifactApiTests.scala +++ b/modules/server/src/test/scala/scaladex/server/route/api/ArtifactApiTests.scala @@ -9,6 +9,7 @@ import akka.http.scaladsl.server.Route import cats.implicits.toTraverseOps import org.scalatest.BeforeAndAfterEach import play.api.libs.json.Reads +import scaladex.core.api.artifact.ArtifactMetadataResponse import scaladex.core.api.artifact.ArtifactResponse import scaladex.core.model.Artifact import scaladex.core.model.Language @@ -25,7 +26,10 @@ class ArtifactApiTests extends ControllerBaseSuite with BeforeAndAfterEach with val artifactRoute: Route = ArtifactApi(database).routes implicit val jsonPaginationReader: Reads[Pagination] = PlayJsonCodecs.paginationSchema.reads - implicit def jsonPageReader: Reads[Page[ArtifactResponse]] = PlayJsonCodecs.pageSchema[ArtifactResponse].reads + implicit def jsonArtifactResponsePageReader: Reads[Page[ArtifactResponse]] = + PlayJsonCodecs.pageSchema[ArtifactResponse].reads + implicit def jsonArtifactMetadataResponsePageReader: Reads[Page[ArtifactMetadataResponse]] = + PlayJsonCodecs.pageSchema[ArtifactMetadataResponse].reads override protected def beforeAll(): Unit = Await.result(insertAllCatsArtifacts(), Duration.Inf) @@ -114,16 +118,33 @@ class ArtifactApiTests extends ControllerBaseSuite with BeforeAndAfterEach with } } + it("should not return any artifacts given a group id and artifact id for an artifact not stored") { + Get("/api/artifacts/ca.ubc.cs/test-package_3") ~> artifactRoute ~> check { + status shouldBe StatusCodes.OK + val response = responseAs[Page[ArtifactMetadataResponse]] + response shouldBe Page.empty[ArtifactMetadataResponse] + } + } + + it("should return artifacts with the given group id and artifact id") { + Get("/api/artifacts/org.typelevel/cats-core_3") ~> artifactRoute ~> check { + status shouldBe StatusCodes.OK + val response = responseAs[Page[ArtifactMetadataResponse]] + val expectedArtifactMetadata = Seq(Cats.`core_3:2.6.1`, Cats.`core_3:2.7.0`).map(Artifact.toMetadataResponse) + response match { + case Page(_, artifacts) => + artifacts.size shouldBe 2 + artifacts should contain theSameElementsAs expectedArtifactMetadata + } + } + } + it("should not return artifacts if the database is empty") { database.reset() Get(s"/api/artifacts") ~> artifactRoute ~> check { status shouldBe StatusCodes.OK val response = responseAs[Page[ArtifactResponse]] - response match { - case Page(Pagination(_, _, numStoredArtifacts), artifacts) => - numStoredArtifacts shouldBe 0 - artifacts.size shouldBe 0 - } + response shouldBe Page.empty[ArtifactResponse] } } } From c0631361518bf6b99382d12e9fdf0ece30353649 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 15 Apr 2022 11:51:14 -0700 Subject: [PATCH 3/3] Adding additional tests * Adding a test for `/api/artifacts// * Group id is invalid, but artifact id is valid. * Group id is valid, but artifact id is invalid. --- .../server/route/api/ArtifactApiTests.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/modules/server/src/test/scala/scaladex/server/route/api/ArtifactApiTests.scala b/modules/server/src/test/scala/scaladex/server/route/api/ArtifactApiTests.scala index 42c5f6ce4..d69977eec 100644 --- a/modules/server/src/test/scala/scaladex/server/route/api/ArtifactApiTests.scala +++ b/modules/server/src/test/scala/scaladex/server/route/api/ArtifactApiTests.scala @@ -126,6 +126,23 @@ class ArtifactApiTests extends ControllerBaseSuite with BeforeAndAfterEach with } } + it("should not return any artifacts given a valid group id and an artifact id that does not parse") { + val malformedArtifactId = "badArtifactId" + Get(s"/api/artifacts/org.apache.spark/$malformedArtifactId") ~> artifactRoute ~> check { + status shouldBe StatusCodes.OK + val response = responseAs[Page[ArtifactMetadataResponse]] + response shouldBe Page.empty[ArtifactMetadataResponse] + } + } + + it("should not return any artifacts given an invalid group id and a valid artifact id") { + Get("/api/artifacts/ca.ubc.cs/cats-core_3") ~> artifactRoute ~> check { + status shouldBe StatusCodes.OK + val response = responseAs[Page[ArtifactMetadataResponse]] + response shouldBe Page.empty[ArtifactMetadataResponse] + } + } + it("should return artifacts with the given group id and artifact id") { Get("/api/artifacts/org.typelevel/cats-core_3") ~> artifactRoute ~> check { status shouldBe StatusCodes.OK