Skip to content

Commit

Permalink
Implement GET /shows endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
jordansimsmith committed Oct 16, 2024
1 parent 1b9778b commit 8d4a2b7
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 40 deletions.
24 changes: 24 additions & 0 deletions immersion_tracker_api/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,29 @@ java_binary(
],
)

java_binary(
name = "get-shows-handler",
srcs = glob(["src/main/java/com/jordansimsmith/immersiontracker/GetShowsHandler.java"]),
create_executable = False,
resources = glob([
"src/main/resources/logback.xml",
]),
deps = [
":lib",
"//lib/time:lib",
"@maven//:ch_qos_logback_logback_classic",
"@maven//:ch_qos_logback_logback_core",
"@maven//:com_amazonaws_aws_lambda_java_core",
"@maven//:com_amazonaws_aws_lambda_java_events",
"@maven//:com_fasterxml_jackson_core_jackson_annotations",
"@maven//:com_fasterxml_jackson_core_jackson_databind",
"@maven//:com_google_code_findbugs_jsr305",
"@maven//:com_google_guava_guava",
"@maven//:software_amazon_awssdk_dynamodb",
"@maven//:software_amazon_awssdk_dynamodb_enhanced",
],
)

java_test_suite(
name = "integration-tests",
size = "medium",
Expand All @@ -85,6 +108,7 @@ java_test_suite(
":auth-handler",
":dagger",
":get-progress-handler",
":get-shows-handler",
":lib",
"//lib/dynamodb:lib",
"//lib/json:lib",
Expand Down
53 changes: 52 additions & 1 deletion immersion_tracker_api/infra/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,35 @@ resource "aws_lambda_function" "get_progress" {
tags = local.tags
}

data "external" "get_shows_handler_location" {
program = ["bash", "${path.module}/resolve_location.sh"]

query = {
target = "//immersion_tracker_api:get-shows-handler_deploy.jar"
}
}

data "local_file" "get_shows_handler_file" {
filename = data.external.get_shows_handler_location.result.location
}

resource "aws_lambda_function" "get_shows" {
filename = data.local_file.get_shows_handler_file.filename
function_name = "${local.application_id}_get_shows"
role = aws_iam_role.lambda_role.arn
source_code_hash = data.local_file.get_shows_handler_file.content_base64sha256
handler = "com.jordansimsmith.immersiontracker.GetShowsHandler"
runtime = "java17"
memory_size = 512
timeout = 10
tags = local.tags
}

resource "aws_lambda_permission" "api_gateway" {
for_each = toset([
aws_lambda_function.auth.function_name,
aws_lambda_function.get_progress.function_name
aws_lambda_function.get_progress.function_name,
aws_lambda_function.get_shows.function_name,
])

statement_id = "AllowAPIGatewayInvoke"
Expand Down Expand Up @@ -266,6 +291,29 @@ resource "aws_api_gateway_integration" "get_progress" {
uri = aws_lambda_function.get_progress.invoke_arn
}

resource "aws_api_gateway_resource" "get_shows" {
rest_api_id = aws_api_gateway_rest_api.immersion_tracker.id
parent_id = aws_api_gateway_rest_api.immersion_tracker.root_resource_id
path_part = "shows"
}

resource "aws_api_gateway_method" "get_shows" {
rest_api_id = aws_api_gateway_rest_api.immersion_tracker.id
resource_id = aws_api_gateway_resource.get_shows.id
http_method = "GET"
authorization = "CUSTOM"
authorizer_id = aws_api_gateway_authorizer.immersion_tracker.id
}

resource "aws_api_gateway_integration" "get_shows" {
rest_api_id = aws_api_gateway_rest_api.immersion_tracker.id
resource_id = aws_api_gateway_resource.get_shows.id
http_method = aws_api_gateway_method.get_shows.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.get_shows.invoke_arn
}

resource "aws_api_gateway_deployment" "immersion_tracker" {
rest_api_id = aws_api_gateway_rest_api.immersion_tracker.id

Expand All @@ -276,6 +324,9 @@ resource "aws_api_gateway_deployment" "immersion_tracker" {
aws_api_gateway_resource.get_progress,
aws_api_gateway_method.get_progress,
aws_api_gateway_integration.get_progress,
aws_api_gateway_resource.get_shows,
aws_api_gateway_method.get_shows,
aws_api_gateway_integration.get_shows,
]))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ public class GetProgressHandler
private final DynamoDbTable<ImmersionTrackerItem> immersionTrackerTable;

@VisibleForTesting
record ProgressResponse(
record GetProgressResponse(
@JsonProperty("total_episodes_watched") int totalEpisodesWatched,
@JsonProperty("total_hours_watched") int totalHoursWatched,
@JsonProperty("episodes_watched_today") int episodesWatchedToday,
@JsonProperty("shows") List<ShowProgress> shows) {}
@JsonProperty("shows") List<Show> shows) {}

@VisibleForTesting
record ShowProgress(
record Show(
@Nullable @JsonProperty("name") String name,
@JsonProperty("episodes_watched") int episodesWatched) {}

Expand Down Expand Up @@ -106,26 +106,23 @@ private APIGatewayV2HTTPResponse doHandleRequest(APIGatewayV2HTTPEvent event, Co
var unknownShows = showEpisodes.stream().filter(e -> e.show() == null).toList();
var unknownShowsProgress =
!unknownShows.isEmpty()
? Stream.of(new ShowProgress(null, unknownShows.size()))
: Stream.<ShowProgress>empty();
? Stream.of(new Show(null, unknownShows.size()))
: Stream.<Show>empty();
var knownShows =
showEpisodes.stream()
.filter(e -> e.show() != null)
.collect(Collectors.groupingBy(e -> Objects.requireNonNull(e.show().getTvdbId())));
var knownShowsProgress =
knownShows.values().stream()
.map(
e ->
new ShowProgress(
Objects.requireNonNull(e.get(0).show()).getTvdbName(), e.size()));
.map(e -> new Show(Objects.requireNonNull(e.get(0).show()).getTvdbName(), e.size()));

var progresses =
Stream.concat(unknownShowsProgress, knownShowsProgress)
.sorted(Comparator.comparing(e -> e.episodesWatched, Comparator.reverseOrder()))
.toList();

var res =
new ProgressResponse(
new GetProgressResponse(
totalEpisodesWatched, totalHoursWatched, episodesWatchedToday, progresses);

return APIGatewayV2HTTPResponse.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.jordansimsmith.immersiontracker;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional;
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest;

public class GetShowsHandler
implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {

private final ObjectMapper objectMapper;
private final DynamoDbTable<ImmersionTrackerItem> immersionTrackerTable;

@VisibleForTesting
record GetShowsResponse(@JsonProperty("shows") List<Show> shows) {}

@VisibleForTesting
record Show(
@JsonProperty("folder_name") String folderName,
@Nullable @JsonProperty("tvdb_id") Integer tvdbId,
@Nullable @JsonProperty("tvdb_name") String tvdbName,
@Nullable @JsonProperty("tvdb_image") String tvdbImage) {}

public GetShowsHandler() {
this(ImmersionTrackerFactory.create());
}

@VisibleForTesting
GetShowsHandler(ImmersionTrackerFactory factory) {
this.objectMapper = factory.objectMapper();
this.immersionTrackerTable = factory.immersionTrackerTable();
}

@Override
public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
try {
return doHandleRequest(event, context);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private APIGatewayV2HTTPResponse doHandleRequest(APIGatewayV2HTTPEvent event, Context context)
throws Exception {
var user = event.getQueryStringParameters().get("user");
Preconditions.checkNotNull(user);

var query =
immersionTrackerTable.query(
QueryEnhancedRequest.builder()
.queryConditional(
QueryConditional.sortBeginsWith(
Key.builder()
.partitionValue(ImmersionTrackerItem.formatPk(user))
.sortValue(ImmersionTrackerItem.SHOW_PREFIX)
.build()))
.build());
var items = query.items().stream().toList();
var shows =
items.stream()
.map(i -> new Show(i.getFolderName(), i.getTvdbId(), i.getTvdbName(), i.getTvdbImage()))
.toList();

var res = new GetShowsResponse(shows);

return APIGatewayV2HTTPResponse.builder()
.withStatusCode(200)
.withHeaders(Map.of("Content-Type", "application/json; charset=utf-8"))
.withBody(objectMapper.writeValueAsString(res))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ public class ImmersionTrackerItem {
public static final String EPISODE_PREFIX = "EPISODE" + DELIMITER;
public static final String SHOW_PREFIX = "SHOW" + DELIMITER;

public static final String PK = "pk";
public static final String SK = "sk";
public static final String USER = "user";
public static final String FOLDER_NAME = "folder_name";
public static final String FILE_NAME = "file_name";
public static final String TIMESTAMP = "timestamp";
public static final String TVDB_ID = "tvdb_id";
public static final String TVDB_NAME = "tvdb_name";
public static final String TVDB_IMAGE = "tvdb_image";

private String pk;
private String sk;
private String user;
Expand All @@ -24,7 +34,7 @@ public class ImmersionTrackerItem {
private String tvdbImage;

@DynamoDbPartitionKey
@DynamoDbAttribute("pk")
@DynamoDbAttribute(PK)
public String getPk() {
return pk;
}
Expand All @@ -34,7 +44,7 @@ public void setPk(String pk) {
}

@DynamoDbSortKey
@DynamoDbAttribute("sk")
@DynamoDbAttribute(SK)
public String getSk() {
return sk;
}
Expand All @@ -43,7 +53,7 @@ public void setSk(String sk) {
this.sk = sk;
}

@DynamoDbAttribute("user")
@DynamoDbAttribute(USER)
public String getUser() {
return user;
}
Expand All @@ -52,7 +62,7 @@ public void setUser(String user) {
this.user = user;
}

@DynamoDbAttribute("folder_name")
@DynamoDbAttribute(FOLDER_NAME)
public String getFolderName() {
return folderName;
}
Expand All @@ -61,7 +71,7 @@ public void setFolderName(String folderName) {
this.folderName = folderName;
}

@DynamoDbAttribute("file_name")
@DynamoDbAttribute(FILE_NAME)
public String getFileName() {
return fileName;
}
Expand All @@ -70,7 +80,7 @@ public void setFileName(String fileName) {
this.fileName = fileName;
}

@DynamoDbAttribute("timestamp")
@DynamoDbAttribute(TIMESTAMP)
public Integer getTimestamp() {
return timestamp;
}
Expand All @@ -79,7 +89,7 @@ public void setTimestamp(Integer timestamp) {
this.timestamp = timestamp;
}

@DynamoDbAttribute("tvdb_id")
@DynamoDbAttribute(TVDB_ID)
public Integer getTvdbId() {
return tvdbId;
}
Expand All @@ -88,7 +98,7 @@ public void setTvdbId(Integer tvdbId) {
this.tvdbId = tvdbId;
}

@DynamoDbAttribute("tvdb_name")
@DynamoDbAttribute(TVDB_NAME)
public String getTvdbName() {
return tvdbName;
}
Expand All @@ -97,7 +107,7 @@ public void setTvdbName(String tvdbName) {
this.tvdbName = tvdbName;
}

@DynamoDbAttribute("tvdb_image")
@DynamoDbAttribute(TVDB_IMAGE)
public String getTvdbImage() {
return tvdbImage;
}
Expand Down
Loading

0 comments on commit 8d4a2b7

Please sign in to comment.