Skip to content

Commit 8a3f340

Browse files
committed
add and test get and delete
1 parent 52f719c commit 8a3f340

File tree

5 files changed

+139
-7
lines changed

5 files changed

+139
-7
lines changed

.jvmopts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xmx3g

application/src/main/scala/com/azavea/franklin/api/endpoints/CollectionEndpoints.scala

+27-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import com.azavea.franklin.error.NotFound
77
import com.azavea.stac4s.StacCollection
88
import io.circe._
99
import sttp.capabilities.fs2.Fs2Streams
10+
import sttp.model.StatusCode
1011
import sttp.model.StatusCode.{NotFound => NF}
1112
import sttp.tapir._
1213
import sttp.tapir.generic.auto._
1314
import sttp.tapir.json.circe._
1415

16+
import java.util.UUID
17+
1518
class CollectionEndpoints[F[_]: Concurrent](
1619
enableTransactions: Boolean,
1720
enableTiles: Boolean,
@@ -72,9 +75,31 @@ class CollectionEndpoints[F[_]: Concurrent](
7275
oneOf(statusMapping(NF, jsonBody[NotFound].description("not found")))
7376
)
7477
.description("Create a mosaic from items in this collection")
75-
.name("collectionMosaic")
78+
.name("collectionMosaicPost")
79+
80+
val getMosaic: Endpoint[(String, UUID), NotFound, MosaicDefinition, Fs2Streams[F]] =
81+
base.get
82+
.in(path[String] / "mosaic" / path[UUID])
83+
.out(jsonBody[MosaicDefinition])
84+
.errorOut(
85+
oneOf(
86+
statusMapping(NF, jsonBody[NotFound].description("Mosaic does not exist in collection"))
87+
)
88+
)
89+
.description("Fetch a mosaic defined for this collection")
90+
.name("collectionMosaicGet")
91+
92+
val deleteMosaic: Endpoint[(String, UUID), NotFound, Unit, Fs2Streams[F]] =
93+
base.delete
94+
.in(path[String] / "mosaic" / path[UUID])
95+
.out(statusCode(StatusCode.NoContent))
96+
.errorOut(
97+
oneOf(
98+
statusMapping(NF, jsonBody[NotFound].description("Mosaic does not exist in collection"))
99+
)
100+
)
76101

77102
val endpoints = List(collectionsList, collectionUnique) ++ {
78-
if (enableTiles) List(collectionTiles, createMosaic) else Nil
103+
if (enableTiles) List(collectionTiles, createMosaic, getMosaic, deleteMosaic) else Nil
79104
} ++ { if (enableTransactions) List(createCollection, deleteCollection) else Nil }
80105
}

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

+29-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import sttp.tapir.server.http4s._
2929

3030
import java.net.URLDecoder
3131
import java.nio.charset.StandardCharsets
32+
import java.util.UUID
3233

3334
class CollectionsService[F[_]: Concurrent](
3435
xa: Transactor[F],
@@ -166,6 +167,29 @@ class CollectionsService[F[_]: Concurrent](
166167
} yield inserted.leftMap({ err => NF(err.msg) })).transact(xa)
167168
}
168169

170+
def getMosaic(
171+
rawCollectionId: String,
172+
mosaicId: UUID
173+
): F[Either[NF, MosaicDefinition]] = {
174+
val collectionId = URLDecoder.decode(rawCollectionId, StandardCharsets.UTF_8.toString)
175+
176+
MosaicDefinitionDao.getMosaicDefinition(collectionId, mosaicId).transact(xa) map {
177+
Either.fromOption(_, NF())
178+
}
179+
}
180+
181+
def deleteMosaic(
182+
rawCollectionId: String,
183+
mosaicId: UUID
184+
): F[Either[NF, Unit]] = {
185+
val collectionId = URLDecoder.decode(rawCollectionId, StandardCharsets.UTF_8.toString)
186+
187+
MosaicDefinitionDao.deleteMosaicDefinition(collectionId, mosaicId).transact(xa) map {
188+
case 0 => Left(NF())
189+
case _ => Right(())
190+
}
191+
}
192+
169193
val collectionEndpoints =
170194
new CollectionEndpoints[F](enableTransactions, enableTiles, apiConfig.path)
171195

@@ -179,7 +203,11 @@ class CollectionsService[F[_]: Concurrent](
179203
List(
180204
Http4sServerInterpreter.toRoutes(collectionEndpoints.collectionTiles)(getCollectionTiles),
181205
Http4sServerInterpreter
182-
.toRoutes(collectionEndpoints.createMosaic)(Function.tupled(createMosaic))
206+
.toRoutes(collectionEndpoints.createMosaic)(Function.tupled(createMosaic)),
207+
Http4sServerInterpreter
208+
.toRoutes(collectionEndpoints.getMosaic)(Function.tupled(getMosaic)),
209+
Http4sServerInterpreter
210+
.toRoutes(collectionEndpoints.deleteMosaic)(Function.tupled(deleteMosaic))
183211
)
184212
} else Nil) ++
185213
(if (enableTransactions) {

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

+14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import doobie.ConnectionIO
55
import doobie.implicits._
66
import doobie.postgres.implicits._
77

8+
import java.util.UUID
9+
810
object MosaicDefinitionDao extends Dao[MosaicDefinition] {
911
val tableName = "mosaic_definitions"
1012

@@ -16,4 +18,16 @@ object MosaicDefinitionDao extends Dao[MosaicDefinition] {
1618
): ConnectionIO[MosaicDefinition] =
1719
fr"insert into mosaic_definitions (id, collection, mosaic) values (${mosaicDefinition.id}, $collectionId, $mosaicDefinition)".update
1820
.withUniqueGeneratedKeys[MosaicDefinition]("mosaic")
21+
22+
private def collectionMosaicQB(collectionId: String, mosaicDefinitionId: UUID) =
23+
query.filter(mosaicDefinitionId).filter(fr"collection = $collectionId")
24+
25+
def getMosaicDefinition(
26+
collectionId: String,
27+
mosaicDefinitionId: UUID
28+
): ConnectionIO[Option[MosaicDefinition]] =
29+
collectionMosaicQB(collectionId, mosaicDefinitionId).selectOption
30+
31+
def deleteMosaicDefinition(collectionId: String, mosaicDefinitionId: UUID): ConnectionIO[Int] =
32+
collectionMosaicQB(collectionId, mosaicDefinitionId).delete
1933
}

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

+68-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import io.circe.syntax._
1919
import org.http4s.circe.CirceEntityDecoder._
2020
import org.http4s.circe.CirceEntityEncoder._
2121
import org.http4s.{Method, Request, Uri}
22+
import org.scalacheck.Prop
2223
import org.specs2.execute.Result
2324
import org.specs2.{ScalaCheck, Specification}
2425

@@ -35,10 +36,11 @@ class CollectionsServiceSpec
3536
This specification verifies that the collections service can run without crashing
3637

3738
The collections service should:
38-
- create and delete collections $createDeleteCollectionExpectation
39-
- list collections $listCollectionsExpectation
40-
- get collections by id $getCollectionsExpectation
41-
- create a mosaic definition $createMosaicDefinitionExpectation
39+
- create and delete collections createDeleteCollectionExpectation
40+
- list collections listCollectionsExpectation
41+
- get collections by id getCollectionsExpectation
42+
- create a mosaic definition createMosaicDefinitionExpectation
43+
- get a mosaic by id $getMosaicExpectation
4244
"""
4345

4446
val testServices: TestServices[IO] = new TestServices[IO](transactor)
@@ -167,4 +169,66 @@ class CollectionsServiceSpec
167169

168170
}
169171

172+
@SuppressWarnings(Array("TraversableHead"))
173+
def getMosaicExpectation = prop { (stacCollection: StacCollection, stacItem: StacItem) =>
174+
(!stacItem.assets.isEmpty) ==> {
175+
val expectationIO = (testClient, testServices.collectionsService).tupled flatMap {
176+
case (client, collectionsService) =>
177+
client.getCollectionItemsResource(List(stacItem), stacCollection) use {
178+
case (collection, items) =>
179+
val encodedCollectionId =
180+
URLEncoder.encode(collection.id, StandardCharsets.UTF_8.toString)
181+
val item = items.head
182+
val name = item.assets.keys.head
183+
val mosaicDefinition =
184+
MosaicDefinition(
185+
UUID.randomUUID,
186+
Option("Testing mosaic definition"),
187+
MapCenter.fromGeometry(item.geometry, 8),
188+
NonEmptyList.of(ItemAsset(item.id, name)),
189+
2,
190+
30,
191+
item.bbox
192+
)
193+
194+
val createRequest =
195+
Request[IO](
196+
method = Method.POST,
197+
Uri.unsafeFromString(s"/collections/$encodedCollectionId/mosaic")
198+
).withEntity(
199+
mosaicDefinition
200+
)
201+
202+
val getRequest =
203+
Request[IO](
204+
method = Method.GET,
205+
Uri.unsafeFromString(
206+
s"/collections/$encodedCollectionId/mosaic/${mosaicDefinition.id}"
207+
)
208+
)
209+
210+
val deleteRequest =
211+
Request[IO](
212+
method = Method.DELETE,
213+
Uri.unsafeFromString(
214+
s"/collections/$encodedCollectionId/mosaic/${mosaicDefinition.id}"
215+
)
216+
)
217+
218+
(for {
219+
_ <- collectionsService.routes.run(createRequest)
220+
resp <- collectionsService.routes.run(getRequest)
221+
decoded <- OptionT.liftF { resp.as[MosaicDefinition] }
222+
deleteResp <- collectionsService.routes.run(deleteRequest)
223+
} yield (decoded, deleteResp)).value map {
224+
case Some((respData, deleteResp)) =>
225+
respData === mosaicDefinition && deleteResp.status.code === 204: Prop
226+
case None => false: Prop
227+
}
228+
}
229+
230+
}
231+
expectationIO.unsafeRunSync
232+
}
233+
}
170234
}

0 commit comments

Comments
 (0)