diff --git a/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/data/RepositoryDataType.scala b/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/data/RepositoryDataType.scala index 0d01484c..521c838d 100644 --- a/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/data/RepositoryDataType.scala +++ b/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/data/RepositoryDataType.scala @@ -1,7 +1,7 @@ package com.advancedtelematic.tuf.reposerver.data import akka.http.scaladsl.model.Uri -import com.advancedtelematic.libats.data.DataType.Checksum +import com.advancedtelematic.libats.data.DataType.{Checksum, Namespace} import com.advancedtelematic.libtuf.data.ClientDataType._ import com.advancedtelematic.libtuf.data.TufDataType.{RepoId, TargetFilename} @@ -16,4 +16,15 @@ object RepositoryDataType { import StorageMethod._ case class TargetItem(repoId: RepoId, filename: TargetFilename, uri: Option[Uri], checksum: Checksum, length: Long, custom: Option[TargetCustom] = None, storageMethod: StorageMethod = Managed) + + case class RepoNamespace(repoId: RepoId, namespace: Namespace) + + object RepoNamespace { + import io.circe.Codec + import io.circe.generic.semiauto._ + import com.advancedtelematic.libats.codecs.CirceCodecs._ + + implicit val repoNamespaceCodec: Codec[RepoNamespace] = deriveCodec[RepoNamespace] + } + } diff --git a/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Repository.scala b/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Repository.scala index 4d19dbbd..86d9edb4 100644 --- a/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Repository.scala +++ b/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Repository.scala @@ -1,7 +1,6 @@ package com.advancedtelematic.tuf.reposerver.db import java.time.Instant - import scala.util.Success import scala.util.Failure import akka.NotUsed @@ -9,7 +8,7 @@ import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.util.FastFuture import akka.stream.scaladsl.Source import com.advancedtelematic.libats.data.DataType.Namespace -import com.advancedtelematic.libats.data.ErrorCode +import com.advancedtelematic.libats.data.{ErrorCode, PaginationResult} import com.advancedtelematic.libats.http.Errors.{EntityAlreadyExists, MissingEntity, MissingEntityId, RawError} import com.advancedtelematic.libtuf.data.TufDataType.{JsonSignedPayload, RepoId, RoleType, TargetFilename} import com.advancedtelematic.libtuf.data.TufDataType.RoleType.RoleType @@ -225,7 +224,7 @@ protected[db] class RepoNamespaceRepository()(implicit db: Database, ec: Executi val AlreadyExists = EntityAlreadyExists[(RepoId, Namespace)]() def persist(repoId: RepoId, namespace: Namespace): Future[Unit] = db.run { - (repoNamespaces += (repoId, namespace)).handleIntegrityErrors(AlreadyExists) + (repoNamespaces += RepoNamespace(repoId, namespace)).handleIntegrityErrors(AlreadyExists) } def ensureNotExists(namespace: Namespace): Future[Unit] = @@ -250,6 +249,10 @@ protected[db] class RepoNamespaceRepository()(implicit db: Database, ec: Executi .result .map(_ > 0) } + + def list(offset: Long, limit: Long): Future[PaginationResult[RepoNamespace]] = db.run { + repoNamespaces.paginateResult(offset, limit) + } } object FilenameCommentRepository { diff --git a/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Schema.scala b/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Schema.scala index 4e46df26..f9bb27b2 100644 --- a/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Schema.scala +++ b/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Schema.scala @@ -1,7 +1,6 @@ package com.advancedtelematic.tuf.reposerver.db import java.time.Instant - import akka.http.scaladsl.model.Uri import com.advancedtelematic.libats.data.DataType.{Checksum, Namespace} import com.advancedtelematic.libtuf.data.ClientDataType.{DelegatedRoleName, TargetCustom} @@ -13,7 +12,7 @@ import com.advancedtelematic.libtuf_server.data.Requests.TargetComment import com.advancedtelematic.tuf.reposerver.db.DBDataType.{DbDelegation, DbSignedRole} import SlickValidatedString._ import com.advancedtelematic.tuf.reposerver.data.RepositoryDataType.StorageMethod.StorageMethod -import com.advancedtelematic.tuf.reposerver.data.RepositoryDataType.TargetItem +import com.advancedtelematic.tuf.reposerver.data.RepositoryDataType.{RepoNamespace, TargetItem} object Schema { import com.advancedtelematic.libats.slick.codecs.SlickRefined._ @@ -56,13 +55,13 @@ object Schema { protected [db] val signedRoles = TableQuery[SignedRoleTable] - class RepoNamespaceTable(tag: Tag) extends Table[(RepoId, Namespace)](tag, "repo_namespaces") { + class RepoNamespaceTable(tag: Tag) extends Table[RepoNamespace](tag, "repo_namespaces") { def repoId = column[RepoId]("repo_id") def namespace = column[Namespace]("namespace") def pk = primaryKey("repo_namespaces_pk", namespace) - override def * = (repoId, namespace) + override def * = (repoId, namespace) <> ((RepoNamespace.apply _).tupled, RepoNamespace.unapply) } protected [db] val repoNamespaces = TableQuery[RepoNamespaceTable] diff --git a/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/http/RepoResource.scala b/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/http/RepoResource.scala index 6043a4fc..a5d0ff99 100644 --- a/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/http/RepoResource.scala +++ b/reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/http/RepoResource.scala @@ -384,5 +384,8 @@ class RepoResource(keyserverClient: KeyserverClient, namespaceValidation: Namesp createRepo(namespace, repoId) } ~ modifyRepoRoutes(repoId) + } ~ + (path("repos") & parameters('offset.as[Long], 'limit.as[Long]) ) { (offset, limit) => + complete(repoNamespaceRepo.list(offset, limit)) } } diff --git a/reposerver/src/test/scala/com/advancedtelematic/tuf/reposerver/http/RepoResourceSpec.scala b/reposerver/src/test/scala/com/advancedtelematic/tuf/reposerver/http/RepoResourceSpec.scala index 8c8819a9..15bc2c37 100644 --- a/reposerver/src/test/scala/com/advancedtelematic/tuf/reposerver/http/RepoResourceSpec.scala +++ b/reposerver/src/test/scala/com/advancedtelematic/tuf/reposerver/http/RepoResourceSpec.scala @@ -2,7 +2,6 @@ package com.advancedtelematic.tuf.reposerver.http import java.time.Instant import java.time.temporal.ChronoUnit - import akka.http.scaladsl.model.Multipart.FormData.BodyPart import akka.http.scaladsl.model._ import akka.http.scaladsl.model.headers._ @@ -14,7 +13,7 @@ import cats.syntax.option._ import cats.syntax.show._ import com.advancedtelematic.libats.codecs.CirceCodecs._ import com.advancedtelematic.libats.data.DataType.HashMethod -import com.advancedtelematic.libats.data.ErrorRepresentation +import com.advancedtelematic.libats.data.{ErrorRepresentation, PaginationResult} import com.advancedtelematic.libats.data.RefinedUtils.RefineTry import com.advancedtelematic.libats.http.Errors.RawError import com.advancedtelematic.libtuf.crypt.CanonicalJson._ @@ -28,6 +27,7 @@ import com.advancedtelematic.libtuf_server.crypto.Sha256Digest import com.advancedtelematic.libtuf_server.data.Requests._ import com.advancedtelematic.libtuf_server.keyserver.KeyserverClient import com.advancedtelematic.libtuf_server.repo.server.DataType.SignedRole +import com.advancedtelematic.tuf.reposerver.data.RepositoryDataType.RepoNamespace import com.advancedtelematic.tuf.reposerver.db.SignedRoleDbTestUtil._ import com.advancedtelematic.tuf.reposerver.db.SignedRoleRepositorySupport import com.advancedtelematic.tuf.reposerver.target_store.TargetStoreEngine.{TargetBytes, TargetRetrieveResult} @@ -1090,6 +1090,20 @@ class RepoResourceSpec extends TufReposerverSpec with RepoResourceSpecUtil } } + test("GET list of repo namespaces") { + withRandomNamepace { implicit ns => + val newRepoId = Post(apiUri("user_repo"), CreateRepositoryRequest(KeyType.default)).namespaced ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[RepoId] + } + + Get(apiUri(s"repos?offset=0&limit=1000")) ~> routes ~> check { + responseAs[PaginationResult[RepoNamespace]].values.map(_.repoId) should contain(newRepoId) + status shouldBe StatusCodes.OK + } + } + } + implicit class ErrorRepresentationOps(value: ErrorRepresentation) { def firstErrorCause: Option[String] = value.cause.flatMap(_.as[NonEmptyList[String]].toOption).map(_.head)