Skip to content

Commit ac6cb08

Browse files
authored
Merge pull request #573 from bclouser/github-master
Merge latest changes from toradex upstrem
2 parents a6018a8 + 853dbcb commit ac6cb08

File tree

8 files changed

+432
-78
lines changed

8 files changed

+432
-78
lines changed

libtuf-server/src/main/scala/com/advancedtelematic/libtuf_server/repo/client/ReposerverClient.scala

+34-8
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ trait ReposerverClient {
109109

110110
def setTargetComments(namespace: Namespace, targetFilename: TargetFilename, comment: String): Future[Unit]
111111
def fetchSingleTargetComments(namespace: Namespace, targetFilename: TargetFilename): Future[FilenameComment]
112-
def fetchTargetsComments(namespace: Namespace, targetNameContains: Option[String]): Future[PaginationResult[FilenameComment]]
112+
def fetchTargetsComments(namespace: Namespace, targetNameContains: Option[String], offset: Option[Long], limit: Option[Long]): Future[PaginationResult[FilenameComment]]
113+
def fetchTargetsCommentsByFilename(namespace: Namespace, filenames: Seq[TargetFilename]): Future[Seq[FilenameComment]]
113114
def deleteTarget(namespace: Namespace, targetFilename: TargetFilename): Future[Unit]
114115

115116
def editTarget(namespace: Namespace,
@@ -118,7 +119,10 @@ trait ReposerverClient {
118119
hardwareIds: Seq[HardwareIdentifier] = Seq.empty,
119120
proprietaryMeta: Option[Json] = None) : Future[ClientTargetItem]
120121
def fetchSingleTargetItem(namespace: Namespace, targetFilename: TargetFilename): Future[ClientTargetItem]
121-
def fetchTargetItems(namespace: Namespace, nameContains: Option[String] = None): Future[PaginationResult[ClientTargetItem]]
122+
def fetchTargetItems(namespace: Namespace,
123+
nameContains: Option[String] = None,
124+
offset: Option[Long] = None,
125+
limit: Option[Long] = None): Future[PaginationResult[ClientTargetItem]]
122126
def fetchDelegationMetadata(namespace: Namespace, roleName: String): Future[JsonSignedPayload]
123127
def fetchDelegationTargetItems(namespace: Namespace, nameContains: Option[String] = None): Future[PaginationResult[DelegationClientTargetItem]]
124128
def fetchSingleDelegationTargetItem(namespace: Namespace, targetFilename: TargetFilename): Future[Seq[DelegationClientTargetItem]]
@@ -148,6 +152,9 @@ class ReposerverHttpClient(reposerverUri: Uri, httpClient: HttpRequest => Future
148152

149153
private def apiUri(path: Path) =
150154
reposerverUri.withPath(reposerverUri.path / "api" / "v1" ++ Slash(path))
155+
private def paginationParams(offset: Option[Long], limit: Option[Long]): Map[String, String] = {
156+
Map("offset" -> offset, "limit" -> limit).collect { case (key, Some(value)) => key -> value.toString }
157+
}
151158

152159
override def createRoot(namespace: Namespace, keyType: KeyType): Future[RepoId] =
153160
Marshal(CreateRepositoryRequest(keyType)).to[RequestEntity].flatMap { entity =>
@@ -250,7 +257,10 @@ class ReposerverHttpClient(reposerverUri: Uri, httpClient: HttpRequest => Future
250257
execHttpUnmarshalledWithNamespace[ClientTargetItem](namespace, req).ok
251258
}
252259

253-
override def fetchTargetItems(namespace: Namespace, nameContains: Option[String] = None): Future[PaginationResult[ClientTargetItem]] = {
260+
override def fetchTargetItems(namespace: Namespace,
261+
nameContains: Option[String] = None,
262+
offset: Option[Long] = None,
263+
limit: Option[Long] = None): Future[PaginationResult[ClientTargetItem]] = {
254264
val reqUri = if (nameContains.isDefined)
255265
apiUri(Path(s"user_repo/target_items")).withQuery(Query("nameContains" -> nameContains.get))
256266
else
@@ -294,18 +304,34 @@ class ReposerverHttpClient(reposerverUri: Uri, httpClient: HttpRequest => Future
294304
execHttpUnmarshalledWithNamespace[Unit](namespace, req).ok
295305
}
296306

297-
override def fetchTargetsComments(namespace: Namespace, targetNameContains: Option[String]): Future[PaginationResult[FilenameComment]] = {
298-
val commentUri = if (targetNameContains.isDefined)
299-
apiUri(Path("user_repo/comments")).withQuery(Query("nameContains" -> targetNameContains.getOrElse("")))
300-
else
301-
apiUri(Path("user_repo/comments"))
307+
override def fetchTargetsComments(namespace: Namespace,
308+
targetNameContains: Option[String],
309+
offset: Option[Long],
310+
limit: Option[Long]): Future[PaginationResult[FilenameComment]] = {
311+
312+
val nameContainsMap = if (targetNameContains.isDefined)
313+
Map[String, String]("nameContains" -> targetNameContains.getOrElse(""))
314+
else Map.empty[String, String]
315+
316+
val commentUri = apiUri(Path("user_repo/comments")).withQuery(
317+
Query(paginationParams(offset, limit) .++ (nameContainsMap))
318+
)
302319
val req = HttpRequest(HttpMethods.GET, uri = commentUri)
303320

304321
execHttpUnmarshalledWithNamespace[PaginationResult[FilenameComment]](namespace, req).handleErrors {
305322
case error if error.status == StatusCodes.NotFound =>
306323
FastFuture.failed(NotFound)
307324
}
308325
}
326+
override def fetchTargetsCommentsByFilename(namespace: Namespace, filenames: Seq[TargetFilename]): Future[Seq[FilenameComment]] = {
327+
val filenameBody = HttpEntity(ContentTypes.`application/json`, filenames.asJson.noSpaces)
328+
val req = HttpRequest(HttpMethods.POST, uri = apiUri(Path("user_repo/list-target-comments")), entity = filenameBody)
329+
330+
execHttpUnmarshalledWithNamespace[Seq[FilenameComment]](namespace, req).handleErrors {
331+
case error if error.status == StatusCodes.NotFound =>
332+
FastFuture.failed(NotFound)
333+
}
334+
}
309335
override def fetchSingleTargetComments(namespace: Namespace, targetFilename: TargetFilename): Future[FilenameComment] = {
310336
val req = HttpRequest(HttpMethods.GET, uri = apiUri(Path(s"user_repo/comments/${targetFilename.value}")))
311337
execHttpUnmarshalledWithNamespace[FilenameComment](namespace, req).ok

reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Repository.scala

+55-19
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,37 @@ import akka.http.scaladsl.model.StatusCodes
1010
import akka.http.scaladsl.util.FastFuture
1111
import akka.stream.scaladsl.Source
1212
import com.advancedtelematic.libats.data.DataType.Namespace
13-
import com.advancedtelematic.libats.data.ErrorCode
13+
import com.advancedtelematic.libats.data.{ErrorCode, PaginationResult}
1414
import com.advancedtelematic.libats.http.Errors.{EntityAlreadyExists, MissingEntity, MissingEntityId, RawError}
1515
import com.advancedtelematic.libtuf.data.TufDataType.{JsonSignedPayload, RepoId, RoleType, TargetFilename, validTargetFilename}
1616
import com.advancedtelematic.libtuf.data.TufDataType.RoleType.RoleType
17-
import com.advancedtelematic.tuf.reposerver.data.RepoDataType._
18-
import com.advancedtelematic.libtuf_server.repo.server.DataType._
19-
import com.advancedtelematic.libats.slick.db.SlickExtensions._
20-
import com.advancedtelematic.libats.slick.codecs.SlickRefined._
21-
import com.advancedtelematic.libats.slick.db.SlickUUIDKey._
22-
import com.advancedtelematic.libats.slick.db.SlickAnyVal._
17+
import com.advancedtelematic.tuf.reposerver.data.RepoDataType.*
18+
import com.advancedtelematic.libtuf_server.repo.server.DataType.*
19+
import com.advancedtelematic.libats.slick.db.SlickExtensions.*
20+
import com.advancedtelematic.libats.slick.db.SlickPagination
21+
import com.advancedtelematic.libats.slick.codecs.SlickRefined.*
22+
import com.advancedtelematic.libats.slick.db.SlickUUIDKey.*
23+
import com.advancedtelematic.libats.slick.db.SlickAnyVal.*
2324
import com.advancedtelematic.libtuf.data.ClientDataType.{ClientTargetItem, DelegatedRoleName, DelegationFriendlyName, SnapshotRole, TargetCustom, TimestampRole, TufRole}
2425
import com.advancedtelematic.libtuf_server.data.Requests.TargetComment
25-
import com.advancedtelematic.libtuf_server.data.TufSlickMappings._
26+
import com.advancedtelematic.libtuf_server.data.TufSlickMappings.*
2627
import com.advancedtelematic.tuf.reposerver.db.DBDataType.{DbDelegation, DbSignedRole}
2728
import com.advancedtelematic.tuf.reposerver.db.TargetItemRepositorySupport.MissingNamespaceException
28-
import com.advancedtelematic.tuf.reposerver.http.Errors._
29+
import com.advancedtelematic.tuf.reposerver.http.Errors.*
2930
import com.advancedtelematic.libtuf_server.repo.server.Errors.SignedRoleNotFound
3031
import SlickMappings.{delegatedRoleNameMapper, delegationFriendlyNameMapper}
3132
import shapeless.ops.function.FnToProduct
3233
import shapeless.{Generic, HList, Succ}
3334
import com.advancedtelematic.libtuf_server.repo.server.SignedRoleProvider
3435
import com.advancedtelematic.tuf.reposerver.data.RepoDataType.TargetItem
36+
import com.advancedtelematic.tuf.reposerver.db.Schema.TargetItemTable
37+
import com.advancedtelematic.tuf.reposerver.http.PaginationParams.PaginationResultOps
38+
import slick.ast.Ordering
3539

3640
import scala.concurrent.{ExecutionContext, Future}
3741
import scala.util.control.NoStackTrace
38-
import slick.jdbc.MySQLProfile.api._
39-
import slick.lifted.AbstractTable
42+
import slick.jdbc.MySQLProfile.api.*
43+
import slick.lifted.{AbstractTable, ColumnOrdered}
4044

4145
trait DatabaseSupport {
4246
val ec: ExecutionContext
@@ -98,15 +102,37 @@ protected [db] class TargetItemRepository()(implicit db: Database, ec: Execution
98102
}.transactionally
99103
}
100104

101-
def findFor(repoId: RepoId, nameContains: Option[String] = None): Future[Seq[TargetItem]] = db.run {
105+
def findForQuery(repoId: RepoId,
106+
nameContains: Option[String] = None): Query[TargetItemTable, TargetItem, Seq] = {
102107
nameContains match {
103108
case Some(substring) =>
104-
targetItems.filter(_.repoId === repoId).filter(_.filename.mappedTo[String].like(s"%${substring}%")).result
109+
targetItems
110+
.filter(_.repoId === repoId)
111+
.filter(_.filename.mappedTo[String].like(s"%${substring}%"))
105112
case None =>
106-
targetItems.filter(_.repoId === repoId).result
113+
targetItems.filter(_.repoId === repoId)
107114
}
108115
}
109116

117+
def findFor(
118+
repoId: RepoId,
119+
nameContains: Option[String] = None
120+
): Future[Seq[TargetItem]] = db.run {
121+
findForQuery(repoId, nameContains).result
122+
}
123+
124+
def findForPaginated(repoId: RepoId,
125+
nameContains: Option[String] = None,
126+
offset: Option[Long] = None,
127+
limit: Option[Long] = None): Future[PaginationResult[TargetItem]] = db.run {
128+
val items = findForQuery(repoId, nameContains).sortBy(t => ColumnOrdered(t.updatedAt, Ordering().asc))
129+
val totalItemsLength = items.length.result
130+
val actualOffset = offset.orDefaultOffset
131+
val actualLimit = limit.orDefaultLimit
132+
val page = items.drop(actualOffset).take(actualLimit).result
133+
totalItemsLength.zip(page).map{ case (total, values) => PaginationResult(values, total, actualOffset, actualLimit) }
134+
}
135+
110136
def exists(repoId: RepoId, filename: TargetFilename): Future[Boolean] = {
111137
findByFilename(repoId, filename)
112138
.transform {
@@ -313,13 +339,23 @@ protected [db] class FilenameCommentRepository()(implicit db: Database, ec: Exec
313339
.failIfNone(CommentNotFound)
314340
}
315341

316-
def find(repoId: RepoId, nameContains: Option[String] = None): Future[Seq[(TargetFilename, TargetComment)]] = db.run {
342+
def find(repoId: RepoId, nameContains: Option[String] = None, offset: Option[Long], limit: Option[Long]): Future[PaginationResult[(TargetFilename, TargetComment)]] = db.run {
317343
val allFileNameComments = filenameComments.filter(_.repoId === repoId)
318-
val comments = if(nameContains.isDefined)
344+
val comments = if(nameContains.isDefined) {
319345
allFileNameComments.filter(_.filename.mappedTo[String].like(s"%${nameContains.get}%"))
320-
else allFileNameComments
321-
comments.map(filenameComment => (filenameComment.filename, filenameComment.comment))
322-
.result
346+
} else allFileNameComments
347+
348+
comments.sortBy(_.filename)
349+
.map(filenameComment => (filenameComment.filename, filenameComment.comment))
350+
.paginateResult(offset.orDefaultOffset, limit.orDefaultLimit)
351+
}
352+
353+
def findForFilenames(repoId: RepoId, filenames: Seq[TargetFilename]): Future[Seq[(TargetFilename, TargetComment)]] = {
354+
val trueRep: Rep[Boolean] = true
355+
val result = db.run {
356+
filenameComments.filter(_.repoId === repoId).filter( _.filename inSet filenames ).result
357+
}
358+
result.map(_.map{case(_, filename, comment) => (filename, comment)})
323359
}
324360

325361
def deleteAction(repoId: RepoId, filename: TargetFilename) =

reposerver/src/main/scala/com/advancedtelematic/tuf/reposerver/db/Schema.scala

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ object Schema {
3131
def checksum = column[Checksum]("checksum")
3232
def length = column[Long]("length")
3333
def storageMethod = column[StorageMethod]("storage_method")
34+
def updatedAt = column[Instant]("updated_at")(javaInstantMapping)
3435

3536
def pk = primaryKey("target_items_pk", (repoId, filename))
3637

@@ -71,6 +72,7 @@ object Schema {
7172
def repoId = column[RepoId]("repo_id")
7273
def filename = column[TargetFilename]("filename")
7374
def comment = column[TargetComment]("comment")
75+
def updatedAt = column[Instant]("updated_at")(javaInstantMapping)
7476

7577
def pk = primaryKey("repo_name_pk", (repoId, filename))
7678

Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package com.advancedtelematic.tuf.reposerver.http
22

3+
import akka.http.scaladsl.model.StatusCodes
34
import akka.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers.CsvSeq
4-
55
import com.advancedtelematic.libtuf.data.ClientDataType.TargetCustom
66
import com.advancedtelematic.libtuf.data.TufDataType.TargetFormat.TargetFormat
77
import com.advancedtelematic.libtuf.data.TufDataType.{HardwareIdentifier, TargetFormat, TargetName, TargetVersion}
8-
import io.circe._
9-
import akka.http.scaladsl.unmarshalling._
10-
import com.advancedtelematic.libats.http.RefinedMarshallingSupport._
11-
import com.advancedtelematic.libats.http.AnyvalMarshallingSupport._
8+
import io.circe.*
9+
import akka.http.scaladsl.unmarshalling.*
10+
import akka.http.scaladsl.util.FastFuture
11+
import com.advancedtelematic.libats.data.ErrorCode
12+
import com.advancedtelematic.libats.http.RefinedMarshallingSupport.*
13+
import com.advancedtelematic.libats.http.AnyvalMarshallingSupport.*
14+
import com.advancedtelematic.libats.http.Errors.RawError
15+
1216
import scala.collection.immutable
1317
import com.advancedtelematic.libtuf_server.data.Marshalling.targetFormatFromStringUnmarshaller
1418

@@ -27,3 +31,18 @@ object TargetCustomParameterExtractors {
2731
TargetCustom(name, version, hardwareIds, targetFormat.orElse(Some(TargetFormat.BINARY)))
2832
}
2933
}
34+
object CustomParameterUnmarshallers {
35+
val nonNegativeLong: Unmarshaller[String, Long] =
36+
PredefinedFromStringUnmarshallers.longFromStringUnmarshaller
37+
.flatMap {
38+
ec => mat => value =>
39+
if (value < 0) FastFuture.failed (RawError (ErrorCode("Bad Request"), StatusCodes.BadRequest, "Value cannot be negative") )
40+
else FastFuture.successful (value)
41+
}
42+
}
43+
object PaginationParams {
44+
implicit class PaginationResultOps(x: Option[Long]) {
45+
def orDefaultOffset: Long = x.getOrElse(0L)
46+
def orDefaultLimit: Long = x.getOrElse(50L)
47+
}
48+
}

0 commit comments

Comments
 (0)