Skip to content

Commit

Permalink
Implement UpdateShowHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
jordansimsmith committed Oct 24, 2024
1 parent c9be1b6 commit 1e1650e
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 11 deletions.
26 changes: 26 additions & 0 deletions immersion_tracker_api/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ java_library(
"//lib/json:lib",
"//lib/secrets:lib",
"//lib/time:lib",
"@maven//:com_fasterxml_jackson_core_jackson_annotations",
"@maven//:com_fasterxml_jackson_core_jackson_databind",
"@maven//:com_google_guava_guava",
"@maven//:software_amazon_awssdk_dynamodb",
"@maven//:software_amazon_awssdk_dynamodb_enhanced",
],
Expand Down Expand Up @@ -91,6 +93,29 @@ java_binary(
],
)

java_binary(
name = "update-show-handler",
srcs = glob(["src/main/java/com/jordansimsmith/immersiontracker/UpdateShowHandler.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_binary(
name = "sync-episodes-handler",
srcs = glob(["src/main/java/com/jordansimsmith/immersiontracker/SyncEpisodesHandler.java"]),
Expand Down Expand Up @@ -134,6 +159,7 @@ java_test_suite(
":get-shows-handler",
":lib",
":sync-episodes-handler",
":update-show-handler",
"//lib/dynamodb:lib",
"//lib/json:lib",
"//lib/secrets:lib",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.jordansimsmith.immersiontracker;

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 com.jordansimsmith.secrets.Secrets;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class HttpTvdbClient implements TvdbClient {
@VisibleForTesting static final String SECRET = "immersion_tracker_api";

private final ObjectMapper objectMapper;
private final Secrets secrets;
private final HttpClient httpClient;

public HttpTvdbClient(ObjectMapper objectMapper, Secrets secrets, HttpClient httpClient) {
this.objectMapper = objectMapper;
this.secrets = secrets;
this.httpClient = httpClient;
}

private record LoginRequest(@JsonProperty("apikey") String apiKey) {}

private record LoginResponse(
@JsonProperty("status") String status, @JsonProperty("data") LoginData data) {}

private record LoginData(@JsonProperty("token") String token) {}

private record SeriesResponse(
@JsonProperty("status") String status, @JsonProperty("data") SeriesData data) {}

private record SeriesData(@JsonProperty("name") String name, @JsonProperty String image) {}

@Override
public Show getShow(int id) {
try {
return doGetShow(id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private Show doGetShow(int id) throws Exception {
var secret = secrets.get(SECRET);
var apikey = objectMapper.readTree(secret).get("tvdb_api_key").asText(null);
Preconditions.checkNotNull(apikey);

var loginReq =
HttpRequest.newBuilder()
.uri(new URI("https://api4.thetvdb.com/v4/login"))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.POST(
HttpRequest.BodyPublishers.ofString(
objectMapper.writeValueAsString(new LoginRequest(apikey))))
.build();
var loginRes = httpClient.send(loginReq, HttpResponse.BodyHandlers.ofString());

if (loginRes.statusCode() != 200) {
throw new IOException(
"tvdb.com login request failed with status code " + loginRes.statusCode());
}

var loginResBody = objectMapper.readValue(loginRes.body(), LoginResponse.class);
if (!loginResBody.status.equals("success")) {
throw new IOException("tvdb.com login request failed with status " + loginResBody.status);
}

var token = loginResBody.data.token;
Preconditions.checkNotNull(token);

var seriesReq =
HttpRequest.newBuilder()
.uri(new URI("https://api4.thetvdb.com/v4/series/" + id))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.GET()
.build();
var seriesRes = httpClient.send(seriesReq, HttpResponse.BodyHandlers.ofString());

if (seriesRes.statusCode() != 200) {
throw new IOException(
"tvdb.com series request failed with status code " + seriesRes.statusCode());
}

var seriesResBody = objectMapper.readValue(seriesRes.body(), SeriesResponse.class);
if (!seriesResBody.status.equals("success")) {
throw new IOException("tvdb.com series request failed with status " + seriesResBody.status);
}

Preconditions.checkNotNull(seriesResBody.data.name);
Preconditions.checkNotNull(seriesResBody.data.image);
return new Show(id, seriesResBody.data.name, seriesResBody.data.image);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public interface ImmersionTrackerFactory {

DynamoDbTable<ImmersionTrackerItem> immersionTrackerTable();

TvdbClient tvdbClient();

static ImmersionTrackerFactory create() {
return DaggerImmersionTrackerFactory.create();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jordansimsmith.immersiontracker;

import java.util.Objects;
import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute;
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;
Expand All @@ -22,6 +23,7 @@ public class ImmersionTrackerItem {
public static final String TVDB_ID = "tvdb_id";
public static final String TVDB_NAME = "tvdb_name";
public static final String TVDB_IMAGE = "tvdb_image";
public static final String VERSION = "version";

private String pk;
private String sk;
Expand All @@ -32,6 +34,7 @@ public class ImmersionTrackerItem {
private Integer tvdbId;
private String tvdbName;
private String tvdbImage;
private Long version;

@DynamoDbPartitionKey
@DynamoDbAttribute(PK)
Expand Down Expand Up @@ -116,6 +119,16 @@ public void setTvdbImage(String tvdbImage) {
this.tvdbImage = tvdbImage;
}

@DynamoDbVersionAttribute()
@DynamoDbAttribute(VERSION)
public Long getVersion() {
return version;
}

public void setVersion(Long version) {
this.version = version;
}

@Override
public String toString() {
return "ImmersionTrackerItem{"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.jordansimsmith.immersiontracker;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jordansimsmith.secrets.Secrets;
import dagger.Module;
import dagger.Provides;
import java.net.http.HttpClient;
import javax.inject.Singleton;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
Expand All @@ -16,4 +19,11 @@ public DynamoDbTable<ImmersionTrackerItem> immersionTrackerTable(
var schema = TableSchema.fromBean(ImmersionTrackerItem.class);
return dynamoDbEnhancedClient.table("immersion_tracker", schema);
}

@Provides
@Singleton
public TvdbClient tvdbClient(ObjectMapper objectMapper, Secrets secrets) {
var httpClient = HttpClient.newBuilder().build();
return new HttpTvdbClient(objectMapper, secrets, httpClient);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,6 @@ public class SyncEpisodesHandler
private final ObjectMapper objectMapper;
private final DynamoDbTable<ImmersionTrackerItem> immersionTrackerTable;

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

SyncEpisodesHandler(ImmersionTrackerFactory factory) {
this.clock = factory.clock();
this.objectMapper = factory.objectMapper();
this.immersionTrackerTable = factory.immersionTrackerTable();
}

@VisibleForTesting
record SyncEpisodesRequest(@JsonProperty("episodes") List<Episode> episodes) {}

Expand All @@ -43,6 +33,17 @@ record Episode(
@VisibleForTesting
record SyncEpisodesResponse(@JsonProperty("episodes_added") int episodesAdded) {}

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

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

@Override
public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.jordansimsmith.immersiontracker;

public interface TvdbClient {
record Show(int id, String name, String image) {}

Show getShow(int id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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.Map;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.Key;

public class UpdateShowHandler
implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {
private final ObjectMapper objectMapper;
private final DynamoDbTable<ImmersionTrackerItem> immersionTrackerTable;
private final TvdbClient tvdbClient;

@VisibleForTesting
record UpdateShowRequest(
@JsonProperty("folder_name") String folderName, @JsonProperty("tvdb_id") int tvdbId) {}

@VisibleForTesting
record UpdateShowResponse() {}

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

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

@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 body = objectMapper.readValue(event.getBody(), UpdateShowRequest.class);

var show =
immersionTrackerTable.getItem(
Key.builder()
.partitionValue(ImmersionTrackerItem.formatPk(user))
.sortValue(ImmersionTrackerItem.formatShowSk(body.folderName))
.build());
Preconditions.checkNotNull(show);

var tvdbShow = tvdbClient.getShow(body.tvdbId);

show.setTvdbId(tvdbShow.id());
show.setTvdbName(tvdbShow.name());
show.setTvdbImage(tvdbShow.image());
immersionTrackerTable.updateItem(show);

var res = new UpdateShowResponse();
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
@@ -0,0 +1,21 @@
package com.jordansimsmith.immersiontracker;

import java.util.HashMap;
import java.util.Map;

public class FakeTvdbClient implements TvdbClient {
private final Map<Integer, Show> shows = new HashMap<>();

@Override
public Show getShow(int id) {
return shows.get(id);
}

public void addShow(Show show) {
shows.put(show.id(), show);
}

public void reset() {
shows.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
SecretsTestModule.class,
ObjectMapperModule.class,
DynamoDbTestModule.class,
ImmersionTrackerModule.class
ImmersionTrackerTestModule.class
})
public interface ImmersionTrackerTestFactory extends ImmersionTrackerFactory {
FakeClock fakeClock();

FakeSecrets fakeSecrets();

FakeTvdbClient fakeTvdbClient();

DynamoDbClient dynamoDbClient();

@Component.Factory
Expand Down
Loading

0 comments on commit 1e1650e

Please sign in to comment.