diff --git a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/DynamoDbModule.java b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/DynamoDbModule.java index 32025a9..11639cc 100644 --- a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/DynamoDbModule.java +++ b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/DynamoDbModule.java @@ -5,13 +5,17 @@ import java.net.URI; import javax.annotation.Nullable; import javax.inject.Named; +import javax.inject.Singleton; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; @Module public class DynamoDbModule { @Provides + @Singleton public DynamoDbClient dynamoDbClient(@Named("dynamoDbEndpoint") @Nullable URI dynamoDbEndpoint) { var builder = DynamoDbClient.builder(); if (dynamoDbEndpoint != null) { @@ -22,7 +26,16 @@ public DynamoDbClient dynamoDbClient(@Named("dynamoDbEndpoint") @Nullable URI dy } @Provides + @Singleton public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) { return DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); } + + @Provides + @Singleton + public DynamoDbTable immersionTrackerTable( + DynamoDbEnhancedClient dynamoDbEnhancedClient) { + var schema = TableSchema.fromBean(ImmersionTrackerItem.class); + return dynamoDbEnhancedClient.table("immersion_tracker", schema); + } } diff --git a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/GetProgressHandler.java b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/GetProgressHandler.java index c5f9a52..8bc622d 100644 --- a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/GetProgressHandler.java +++ b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/GetProgressHandler.java @@ -3,13 +3,13 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.google.common.annotations.VisibleForTesting; -import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; -import software.amazon.awssdk.enhanced.dynamodb.TableSchema; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import java.util.List; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; -public class GetProgressHandler implements RequestHandler { - private final DynamoDbClient dynamoDbClient; - private final DynamoDbEnhancedClient dynamoDbEnhancedClient; +public class GetProgressHandler implements RequestHandler> { + private final DynamoDbTable immersionTrackerTable; public GetProgressHandler() { this(ImmersionTrackerFactory.create()); @@ -17,16 +17,23 @@ public GetProgressHandler() { @VisibleForTesting GetProgressHandler(ImmersionTrackerFactory factory) { - this.dynamoDbEnhancedClient = factory.dynamoDbEnhancedClient(); - this.dynamoDbClient = factory.dynamoDbClient(); + this.immersionTrackerTable = factory.immersionTrackerTable(); } @Override - public String handleRequest(Object s, Context context) { + public List handleRequest(Object s, Context context) { - var schema = TableSchema.fromBean(ImmersionTrackerRecord.class); - var table = dynamoDbEnhancedClient.table("immersion_tracker", schema); + // TODO: auth + var user = "jordansimsmith"; - return table.scan().items().stream().toList().toString(); + var query = + immersionTrackerTable.query( + QueryEnhancedRequest.builder() + .queryConditional( + QueryConditional.keyEqualTo( + b -> b.partitionValue(ImmersionTrackerItem.formatPk(user)))) + .build()); + + return query.items().stream().toList(); } } diff --git a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerFactory.java b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerFactory.java index 2e4d6a8..ff503b9 100644 --- a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerFactory.java +++ b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerFactory.java @@ -5,16 +5,20 @@ import java.net.URI; import javax.annotation.Nullable; import javax.inject.Named; +import javax.inject.Singleton; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +@Singleton @Component(modules = {DynamoDbModule.class}) public interface ImmersionTrackerFactory { - DynamoDbClient dynamoDbClient(); DynamoDbEnhancedClient dynamoDbEnhancedClient(); + DynamoDbTable immersionTrackerTable(); + @Component.Builder interface Builder { @BindsInstance diff --git a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerItem.java b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerItem.java new file mode 100644 index 0000000..3a0f2e0 --- /dev/null +++ b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerItem.java @@ -0,0 +1,186 @@ +package com.jordansimsmith.immersiontracker; + +import java.util.Objects; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + +@DynamoDbBean +public class ImmersionTrackerItem { + private String pk; + private String sk; + private String user; + private String folderName; + private String fileName; + private Integer timestamp; + private Integer tvdbId; + private String tvdbName; + private String tvdbImage; + + @DynamoDbPartitionKey + @DynamoDbAttribute("pk") + public String getPk() { + return pk; + } + + public void setPk(String pk) { + this.pk = pk; + } + + @DynamoDbSortKey + @DynamoDbAttribute("sk") + public String getSk() { + return sk; + } + + public void setSk(String sk) { + this.sk = sk; + } + + @DynamoDbAttribute("user") + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + @DynamoDbAttribute("folder_name") + public String getFolderName() { + return folderName; + } + + public void setFolderName(String folderName) { + this.folderName = folderName; + } + + @DynamoDbAttribute("file_name") + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + @DynamoDbAttribute("timestamp") + public Integer getTimestamp() { + return timestamp; + } + + public void setTimestamp(Integer timestamp) { + this.timestamp = timestamp; + } + + @DynamoDbAttribute("tvdb_id") + public Integer getTvdbId() { + return tvdbId; + } + + public void setTvdbId(Integer tvdbId) { + this.tvdbId = tvdbId; + } + + @DynamoDbAttribute("tvdb_name") + public String getTvdbName() { + return tvdbName; + } + + public void setTvdbName(String tvdbName) { + this.tvdbName = tvdbName; + } + + @DynamoDbAttribute("tvdb_image") + public String getTvdbImage() { + return tvdbImage; + } + + public void setTvdbImage(String tvdbImage) { + this.tvdbImage = tvdbImage; + } + + @Override + public String toString() { + return "ImmersionTrackerItem{" + + "pk='" + + pk + + '\'' + + ", sk='" + + sk + + '\'' + + ", user='" + + user + + '\'' + + ", folderName='" + + folderName + + '\'' + + ", fileName='" + + fileName + + '\'' + + ", timestamp=" + + timestamp + + ", tvdbId=" + + tvdbId + + ", tvdbName=" + + tvdbName + + ", tvdbImage='" + + tvdbImage + + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ImmersionTrackerItem that = (ImmersionTrackerItem) o; + return Objects.equals(pk, that.pk) + && Objects.equals(sk, that.sk) + && Objects.equals(user, that.user) + && Objects.equals(folderName, that.folderName) + && Objects.equals(fileName, that.fileName) + && Objects.equals(timestamp, that.timestamp) + && Objects.equals(tvdbId, that.tvdbId) + && Objects.equals(tvdbName, that.tvdbName) + && Objects.equals(tvdbImage, that.tvdbImage); + } + + @Override + public int hashCode() { + return Objects.hash(pk, sk, user, folderName, fileName, timestamp, tvdbId, tvdbName, tvdbImage); + } + + public static String formatPk(String user) { + return "USER#" + user; + } + + public static String formatEpisodeSk(String folderName, String fileName) { + return "EPISODE#" + folderName + "#" + fileName; + } + + public static String formatShowSk(String folderName) { + return "SHOW#" + folderName; + } + + public static ImmersionTrackerItem createEpisode( + String user, String folderName, String fileName, int timestamp) { + var episode = new ImmersionTrackerItem(); + episode.setPk(formatPk(user)); + episode.setSk(formatEpisodeSk(folderName, fileName)); + episode.setFolderName(folderName); + episode.setFileName(fileName); + episode.setTimestamp(timestamp); + episode.setUser(user); + return episode; + } + + public static ImmersionTrackerItem createShow(String user, String folderName) { + var show = new ImmersionTrackerItem(); + show.setPk(formatPk(user)); + show.setSk(formatShowSk(folderName)); + show.setUser(user); + return show; + } +} diff --git a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerRecord.java b/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerRecord.java deleted file mode 100644 index 0655649..0000000 --- a/immersion_tracker_api/src/main/java/com/jordansimsmith/immersiontracker/ImmersionTrackerRecord.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.jordansimsmith.immersiontracker; - -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; - -@DynamoDbBean -public class ImmersionTrackerRecord { - private String pk; - private String sk; - - @DynamoDbPartitionKey - public String getPk() { - return pk; - } - - public void setPk(String pk) { - this.pk = pk; - } - - @DynamoDbSortKey - public String getSk() { - return sk; - } - - public void setSk(String sk) { - this.sk = sk; - } -} diff --git a/immersion_tracker_api/src/test/java/com/jordansimsmith/immersiontracker/GetProgressHandlerIntegrationTest.java b/immersion_tracker_api/src/test/java/com/jordansimsmith/immersiontracker/GetProgressHandlerIntegrationTest.java index 65eec8e..70817bf 100644 --- a/immersion_tracker_api/src/test/java/com/jordansimsmith/immersiontracker/GetProgressHandlerIntegrationTest.java +++ b/immersion_tracker_api/src/test/java/com/jordansimsmith/immersiontracker/GetProgressHandlerIntegrationTest.java @@ -7,15 +7,12 @@ import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.*; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter; @Testcontainers public class GetProgressHandlerIntegrationTest { - - private DynamoDbClient dynamodbClient; - private DynamoDbEnhancedClient dynamoDbEnhancedClient; + private DynamoDbTable immersionTrackerTable; private GetProgressHandler getProgressHandler; @@ -23,48 +20,41 @@ public class GetProgressHandlerIntegrationTest { @BeforeEach void setUp() { - dynamodbClient = - DynamoDbClient.builder().endpointOverride(dynamoDbContainer.getEndpoint()).build(); - dynamoDbEnhancedClient = - DynamoDbEnhancedClient.builder().dynamoDbClient(dynamodbClient).build(); - - var req = - CreateTableRequest.builder() - .tableName("immersion_tracker") - .keySchema( - KeySchemaElement.builder().keyType(KeyType.HASH).attributeName("pk").build(), - KeySchemaElement.builder().keyType(KeyType.RANGE).attributeName("sk").build()) - .attributeDefinitions( - AttributeDefinition.builder() - .attributeName("pk") - .attributeType(ScalarAttributeType.S) - .build(), - AttributeDefinition.builder() - .attributeName("sk") - .attributeType(ScalarAttributeType.S) - .build()) - .billingMode(BillingMode.PAY_PER_REQUEST) - .build(); - dynamodbClient.createTable(req); - var factory = ImmersionTrackerFactory.builder().dynamoDbEndpoint(dynamoDbContainer.getEndpoint()).build(); - getProgressHandler = new GetProgressHandler(factory); - } - @Test - void test1() { - assertThat(dynamodbClient.listTables().tableNames()).contains("immersion_tracker"); + var dynamoDbClient = factory.dynamoDbClient(); + immersionTrackerTable = factory.immersionTrackerTable(); + immersionTrackerTable.createTable(); + try (var waiter = DynamoDbWaiter.builder().client(dynamoDbClient).build()) { + var res = + waiter + .waitUntilTableExists(b -> b.tableName(immersionTrackerTable.tableName()).build()) + .matched(); + res.response().orElseThrow(); + } - var res = getProgressHandler.handleRequest(null, null); - assertThat(res).isEqualTo("[]"); + getProgressHandler = new GetProgressHandler(factory); } @Test - void test2() { - assertThat(dynamodbClient.listTables().tableNames()).doesNotContain("my_nonexisting_table"); + void handleRequestShouldQueryItems() { + // arrange + var episode1 = ImmersionTrackerItem.createEpisode("jordansimsmith", "show1", "episode1", 123); + var episode2 = ImmersionTrackerItem.createEpisode("jordansimsmith", "show1", "episode2", 456); + var show = ImmersionTrackerItem.createShow("jordansimsmith", "show1"); + immersionTrackerTable.putItem(episode1); + immersionTrackerTable.putItem(episode2); + immersionTrackerTable.putItem(show); + + // act var res = getProgressHandler.handleRequest(null, null); - assertThat(res).isEqualTo("[]"); + + // assert + assertThat(res).contains(episode1); + assertThat(res).contains(episode2); + assertThat(res).contains(show); + assertThat(res).hasSize(3); } }