Skip to content

Commit 6659610

Browse files
committed
wip -- post and test mosaic definitions post
1 parent bf39e28 commit 6659610

File tree

6 files changed

+125
-11
lines changed

6 files changed

+125
-11
lines changed

application/src/main/scala/com/azavea/franklin/api/services/CollectionsService.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ class CollectionsService[F[_]: Concurrent](
157157

158158
(for {
159159
itemsInCollection <- StacItemDao.checkItemsInCollection(mosaicDefinition.items, collectionId)
160-
itemAssetValidity <- itemsInCollection traverse { _ =>
161-
StacItemDao.checkAssets(mosaicDefinition.items)
160+
itemAssetValidity <- itemsInCollection flatTraverse { _ =>
161+
StacItemDao.checkAssets(mosaicDefinition.items, collectionId)
162162
}
163163
inserted <- itemAssetValidity traverse { _ =>
164164
MosaicDefinitionDao.insert(mosaicDefinition, collectionId)

application/src/main/scala/com/azavea/franklin/database/StacItemDao.scala

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import cats.syntax.all._
77
import com.azavea.franklin.datamodel.ItemAsset
88
import com.azavea.franklin.datamodel.PaginationToken
99
import com.azavea.franklin.datamodel.SearchMethod
10-
import com.azavea.franklin.error.MosaicDefinitionError
10+
import com.azavea.franklin.error.{ItemsDoNotExist, ItemsMissingAsset, MosaicDefinitionError}
1111
import com.azavea.franklin.extensions.paging.PagingLinkExtension
1212
import com.azavea.stac4s._
1313
import com.azavea.stac4s.extensions.periodic.PeriodicExtent
@@ -416,10 +416,43 @@ object StacItemDao extends Dao[StacItem] {
416416
def checkItemsInCollection(
417417
items: NonEmptyList[ItemAsset],
418418
collectionId: String
419-
): ConnectionIO[Either[MosaicDefinitionError, Unit]] = ???
419+
): ConnectionIO[Either[MosaicDefinitionError, Unit]] = {
420+
val iaToString = (ia: ItemAsset) => s""""${ia.itemId}""""
421+
val itemStrings = items.toList map iaToString
422+
val itemStringArray =
423+
s"""{ ${itemStrings.mkString(", ")} }"""
424+
fr"""
425+
with item_ids as (
426+
select unnest($itemStringArray :: text[]) as item_id
427+
)
428+
select item_ids.item_id
429+
from item_ids left join collection_items on item_ids.item_id = collection_items.id
430+
where
431+
collection_items.collection is null or
432+
collection_items.collection <> $collectionId
433+
""".query[String].to[List] map {
434+
case Nil => Right(())
435+
case items => Left(ItemsDoNotExist(items, collectionId))
436+
}
437+
}
420438

421439
def checkAssets(
422-
items: NonEmptyList[ItemAsset]
423-
): ConnectionIO[Either[MosaicDefinitionError, Unit]] = ???
440+
items: NonEmptyList[ItemAsset],
441+
collectionId: String
442+
): ConnectionIO[Either[MosaicDefinitionError, Unit]] =
443+
items.toList flatTraverse { itemAsset =>
444+
getCollectionItem(collectionId, itemAsset.itemId) map { itemO =>
445+
itemO.fold(List(itemAsset.itemId))(item =>
446+
if (item.assets.contains(itemAsset.assetName)) {
447+
List.empty[String]
448+
} else {
449+
List(item.id)
450+
}
451+
)
452+
}
453+
} map {
454+
case Nil => Right(())
455+
case ids => Left(ItemsMissingAsset(items.filter(ia => ids.contains(ia.itemId))))
456+
}
424457

425458
}

application/src/main/scala/com/azavea/franklin/datamodel/MosaicDefinition.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import cats.kernel.Eq
55
import cats.syntax.apply._
66
import cats.syntax.contravariant._
77
import com.azavea.stac4s.TwoDimBbox
8+
import geotrellis.vector.Geometry
89
import io.circe.DecodingFailure
910
import io.circe.generic.JsonCodec
1011
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
@@ -17,6 +18,11 @@ final case class MapCenter(longitude: Double, latitude: Double, zoom: Int)
1718

1819
object MapCenter {
1920

21+
def fromGeometry[G <: Geometry](geometry: G, zoom: Int) = {
22+
val geoCenter = geometry.getCentroid()
23+
MapCenter(geoCenter.getX(), geoCenter.getY(), zoom)
24+
}
25+
2026
implicit val decMapCenter: Decoder[MapCenter] = { cursor =>
2127
cursor.as[List[Json]] flatMap {
2228
case j1 :: j2 :: j3 :: Nil =>
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package com.azavea.franklin.error
22

3+
import com.azavea.franklin.datamodel.ItemAsset
34
import com.azavea.franklin.datamodel.MapCenter
45

56
sealed abstract class MosaicDefinitionError {
67
val msg: String
78
}
89

9-
final case class ItemMissingAsset(itemId: String, assetKey: String) extends MosaicDefinitionError {
10-
val msg = s"Item $itemId does not have an asset named $assetKey"
10+
final case class ItemsMissingAsset(itemAssets: List[ItemAsset]) extends MosaicDefinitionError {
11+
12+
private val itemAssetList =
13+
(itemAssets map { ia => s"(${ia.itemId}, ${ia.assetName})" }).mkString(", ")
14+
val msg = s"""Some items don't have the requested assets: $itemAssetList"""
1115
}
1216

13-
final case class ItemDoesNotExist(itemId: String, collectionId: String)
17+
final case class ItemsDoNotExist(itemIds: List[String], collectionId: String)
1418
extends MosaicDefinitionError {
15-
val msg = s"Item $itemId does not exist in collection $collectionId"
19+
private val itemList = itemIds.mkString(", ")
20+
val msg = s"Some items do not exist in collection $collectionId: $itemList"
1621
}

application/src/test/scala/com/azavea/franklin/api/TestServices.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class TestServices[F[_]: Concurrent](xa: Transactor[F])(
3333
"http",
3434
NonNegInt(30),
3535
true,
36-
false,
36+
true,
3737
false
3838
)
3939

application/src/test/scala/com/azavea/franklin/api/services/CollectionsServiceSpec.scala

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
package com.azavea.franklin.api.services
22

3+
import cats.data.NonEmptyList
34
import cats.data.OptionT
45
import cats.effect.IO
56
import cats.syntax.all._
67
import com.azavea.franklin.Generators
78
import com.azavea.franklin.api.{TestClient, TestServices}
89
import com.azavea.franklin.database.TestDatabaseSpec
910
import com.azavea.franklin.datamodel.CollectionsResponse
11+
import com.azavea.franklin.datamodel.ItemAsset
12+
import com.azavea.franklin.datamodel.MapCenter
13+
import com.azavea.franklin.datamodel.MosaicDefinition
14+
import com.azavea.stac4s.StacItem
1015
import com.azavea.stac4s.testing.JvmInstances._
1116
import com.azavea.stac4s.testing._
1217
import com.azavea.stac4s.{StacCollection, StacLinkType}
18+
import io.circe.syntax._
1319
import org.http4s.circe.CirceEntityDecoder._
20+
import org.http4s.circe.CirceEntityEncoder._
1421
import org.http4s.{Method, Request, Uri}
22+
import org.specs2.execute.Result
1523
import org.specs2.{ScalaCheck, Specification}
1624

1725
import java.net.URLEncoder
1826
import java.nio.charset.StandardCharsets
27+
import java.util.UUID
1928

2029
class CollectionsServiceSpec
2130
extends Specification
@@ -29,6 +38,7 @@ class CollectionsServiceSpec
2938
- create and delete collections $createDeleteCollectionExpectation
3039
- list collections $listCollectionsExpectation
3140
- get collections by id $getCollectionsExpectation
41+
- create a mosaic definition $createMosaicDefinitionExpectation
3242
"""
3343

3444
val testServices: TestServices[IO] = new TestServices[IO](transactor)
@@ -97,4 +107,64 @@ class CollectionsServiceSpec
97107
)
98108
}
99109

110+
@SuppressWarnings(Array("TraversableHead"))
111+
def createMosaicDefinitionExpectation = prop {
112+
(stacCollection: StacCollection, stacItem: StacItem) =>
113+
val expectationIO = (testClient, testServices.collectionsService).tupled flatMap {
114+
case (client, collectionsService) =>
115+
client.getCollectionItemsResource(List(stacItem), stacCollection) use {
116+
case (collection, items) =>
117+
val encodedCollectionId =
118+
URLEncoder.encode(collection.id, StandardCharsets.UTF_8.toString)
119+
val item = items.head
120+
val mosaicDefinition = if (item.assets.isEmpty) {
121+
val name = "bogus asset name"
122+
MosaicDefinition(
123+
UUID.randomUUID,
124+
Option("Testing mosaic definition"),
125+
MapCenter.fromGeometry(item.geometry, 8),
126+
NonEmptyList.of(ItemAsset(item.id, name)),
127+
2,
128+
30,
129+
item.bbox
130+
)
131+
} else {
132+
val name = item.assets.keys.head
133+
MosaicDefinition(
134+
UUID.randomUUID,
135+
Option("Testing mosaic definition"),
136+
MapCenter.fromGeometry(item.geometry, 8),
137+
NonEmptyList.of(ItemAsset(item.id, name)),
138+
2,
139+
30,
140+
item.bbox
141+
)
142+
}
143+
144+
val request =
145+
Request[IO](
146+
method = Method.POST,
147+
Uri.unsafeFromString(s"/collections/$encodedCollectionId/mosaic")
148+
).withEntity(
149+
mosaicDefinition
150+
)
151+
152+
collectionsService.routes.run(request).value flatMap {
153+
case Some(resp) =>
154+
if (stacItem.assets.isEmpty) {
155+
IO.pure(resp.status.code must beTypedEqualTo(404): Result)
156+
} else {
157+
resp.as[MosaicDefinition] map { result =>
158+
result must beEqualTo(mosaicDefinition): Result
159+
}
160+
}
161+
case None => IO.pure(failure: Result)
162+
}
163+
}
164+
165+
}
166+
expectationIO.unsafeRunSync
167+
168+
}
169+
100170
}

0 commit comments

Comments
 (0)