app/com/gu/memsub/services/JsonDynamoService.scala (60 lines of code) (raw):

package com.gu.memsub.services import com.amazonaws.auth.profile.ProfileCredentialsProvider import com.amazonaws.auth.{AWSCredentialsProviderChain, EnvironmentVariableCredentialsProvider, InstanceProfileCredentialsProvider} import com.amazonaws.regions.Regions import com.amazonaws.services.dynamodbv2.{AmazonDynamoDBClient, AmazonDynamoDBClientBuilder} import com.amazonaws.services.dynamodbv2.document.spec.{GetItemSpec, QuerySpec, ScanSpec} import com.amazonaws.services.dynamodbv2.document._ import com.amazonaws.services.dynamodbv2.model.TableDescription import com.gu.aws.CredentialsProvider import play.api.libs.json._ import scala.collection.JavaConverters._ import scala.concurrent.{ExecutionContext, Future} import scala.language.higherKinds import scala.util.Try import scalaz.Monad class JsonDynamoService[A, M[_]](table: Table)(implicit m: Monad[M]) { implicit val itemFormat = JsonDynamoService.itemFormat def all(implicit formatter: Reads[A]): M[Seq[A]] = Monad[M].point { val items: Iterator[Item] = table.scan().iterator().asScala items.flatMap(i => Json.fromJson[A](Json.toJson[Item](i)).asOpt).toList } def add(p: A)(implicit formatter: Writes[A]): M[Unit] = Monad[M].point { val item = Json.fromJson[Item](Json.toJson(p)) .getOrElse(throw new IllegalStateException(s"Unable to convert $p to item")) table.putItem(item) () } def find[B](b: B)(implicit of: OWrites[B], r: Reads[A]): M[List[A]] = Monad[M].point { val primaryKey = table.describe().getKeySchema.get(0).getAttributeName val jsonItem = Json.toJson(b) val dynamoResult = (jsonItem \ primaryKey).validate[String].asOpt.fold { itemFormat.reads(jsonItem).fold(err => Seq.empty, { itemFromJson => val filters = itemFromJson.asMap().asScala.map { case (k, v) => new ScanFilter(k).eq(v): ScanFilter }.toSeq table.scan(new ScanSpec().withScanFilters(filters:_*)).iterator().asScala.toSeq }) } { keyValue => Option(table.getItem(new GetItemSpec().withPrimaryKey(primaryKey, keyValue))).toSeq } dynamoResult.flatMap(i => Json.fromJson[A](Json.toJson[Item](i)).asOpt).toList } } object JsonDynamoService { val itemFormat = Format( new Reads[Item] { def reads(in: JsValue): JsResult[Item] = Try(JsSuccess(Item.fromJSON(in.toString))).getOrElse(JsError(s"unable to deserialise $in")) }, new Writes[Item] { def writes(o: Item): JsValue = Json.parse(o.toJSON) } ) def forTable[A](table: String)(implicit e: ExecutionContext): JsonDynamoService[A, Future] = { import scalaz.std.scalaFuture._ val dynamoDBClient = AmazonDynamoDBClient.builder .withCredentials(CredentialsProvider) .withRegion(Regions.EU_WEST_1) .build() new JsonDynamoService[A, Future](new DynamoDB(dynamoDBClient).getTable(table))(futureInstance) } }