Skip to content

Commit

Permalink
#145 - GET documents/{id}/fulltext returns 400 when using Typesense
Browse files Browse the repository at this point in the history
  • Loading branch information
mfriesen committed Jul 23, 2023
1 parent 0b9f0b1 commit 08084f5
Show file tree
Hide file tree
Showing 15 changed files with 533 additions and 340 deletions.
3 changes: 3 additions & 0 deletions lambda-api/config/checkstyle/import-control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<allow pkg="com.amazonaws.services.lambda.runtime" />
<allow pkg="com.amazonaws.xray" />
<allow pkg="com.formkiq.module.ocr" />
<allow pkg="com.formkiq.module.typesense" />
<allow pkg="software.amazon.awssdk" />

<allow pkg="com.formkiq.aws.dynamodb" />
Expand Down Expand Up @@ -82,6 +83,7 @@

<allow pkg="com.formkiq.aws.services.lambda" />
<allow pkg="com.formkiq.module.typesense" />
<allow pkg="com.formkiq.module.http" />
<allow pkg="com.formkiq.module.ocr" />
<allow pkg="com.formkiq.stacks.api" />
<allow pkg="com.formkiq.stacks.common.formats" />
Expand All @@ -92,6 +94,7 @@
<allow pkg="java.time" />
<allow pkg="java.text" />


</subpackage>

<subpackage name="util">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import com.formkiq.module.lambdaservices.ClassServiceExtension;
import com.formkiq.module.ocr.DocumentOcrService;
import com.formkiq.module.ocr.DocumentOcrServiceExtension;
import com.formkiq.module.typesense.TypeSenseService;
import com.formkiq.module.typesense.TypeSenseServiceExtension;
import com.formkiq.plugins.tagschema.DocumentTagSchemaPlugin;
import com.formkiq.plugins.tagschema.DocumentTagSchemaPluginExtension;
import com.formkiq.stacks.api.handler.ConfigurationApiKeysRequestHandler;
Expand Down Expand Up @@ -131,10 +133,10 @@
*/
public abstract class AbstractCoreRequestHandler extends AbstractRestApiRequestHandler {

/** Is Public Urls Enabled. */
private static boolean isEnablePublicUrls;
/** {@link AwsServiceCache}. */
private static AwsServiceCache awsServices;
/** Is Public Urls Enabled. */
private static boolean isEnablePublicUrls;
/** Url Class Map. */
private static final Map<String, ApiGatewayRequestHandler> URL_MAP = new HashMap<>();

Expand Down Expand Up @@ -241,6 +243,49 @@ public static void configureHandler(final Map<String, String> map, final Region
S3ConnectionBuilder s3 = new S3ConnectionBuilder(enableAwsXray).setRegion(region)
.setCredentials(credentialsProvider).setEndpointOverride(awsServiceEndpoints.get("s3"));

registerExtensions(schemaEvents, s3);

awsServices = new AwsServiceCache().environment(map).debug("true".equals(map.get("DEBUG")));

if (awsServices.hasModule("typesense")) {
AwsCredentials creds = awsServices.getExtension(AwsCredentials.class);
AwsServiceCache.register(TypeSenseService.class,
new TypeSenseServiceExtension(region, creds));
}

isEnablePublicUrls = isEnablePublicUrls(map);

setAuthorizerType(ApiAuthorizerType.valueOf(map.get("USER_AUTHENTICATION").toUpperCase()));
}

/**
* Get {@link AwsServiceCache}.
*
* @return {@link AwsServiceCache}
*/
public static AwsServiceCache getAwsServicesCache() {
return awsServices;
}

/**
* Whether to enable public urls.
*
* @param map {@link Map}
* @return boolean
*/
private static boolean isEnablePublicUrls(final Map<String, String> map) {
return "true".equals(map.getOrDefault("ENABLE_PUBLIC_URLS", "false"));
}

/**
* Register Extensions.
*
* @param schemaEvents {@link DocumentTagSchemaPlugin}
* @param s3 {@link S3ConnectionBuilder}
*/
private static void registerExtensions(final DocumentTagSchemaPlugin schemaEvents,
final S3ConnectionBuilder s3) {

AwsServiceCache.register(ActionsService.class, new ActionsServiceExtension());
AwsServiceCache.register(SsmService.class, new SsmServiceExtension());
AwsServiceCache.register(S3Service.class, new S3ServiceExtension(s3));
Expand All @@ -258,22 +303,6 @@ public static void configureHandler(final Map<String, String> map, final Region
AwsServiceCache.register(DocumentOcrService.class, new DocumentOcrServiceExtension());
AwsServiceCache.register(DynamoDbService.class, new DynamoDbServiceExtension());
AwsServiceCache.register(WebhooksService.class, new WebhooksServiceExtension());

awsServices = new AwsServiceCache().environment(map).debug("true".equals(map.get("DEBUG")));

isEnablePublicUrls = isEnablePublicUrls(map);

setAuthorizerType(ApiAuthorizerType.valueOf(map.get("USER_AUTHENTICATION").toUpperCase()));
}

/**
* Whether to enable public urls.
*
* @param map {@link Map}
* @return boolean
*/
private static boolean isEnablePublicUrls(final Map<String, String> map) {
return "true".equals(map.getOrDefault("ENABLE_PUBLIC_URLS", "false"));
}

/** constructor. */
Expand Down Expand Up @@ -312,15 +341,6 @@ public AwsServiceCache getAwsServices() {
return awsServices;
}

/**
* Get {@link AwsServiceCache}.
*
* @return {@link AwsServiceCache}
*/
public static AwsServiceCache getAwsServicesCache() {
return awsServices;
}

@Override
public Map<String, ApiGatewayRequestHandler> getUrlMap() {
return URL_MAP;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,176 @@
*/
package com.formkiq.stacks.api.handler;

import static com.formkiq.aws.services.lambda.ApiResponseStatus.SC_OK;
import static com.formkiq.module.http.HttpResponseStatus.is2XX;
import static com.formkiq.module.http.HttpResponseStatus.is404;
import java.net.http.HttpResponse;
import java.util.Map;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.formkiq.aws.dynamodb.model.DocumentToFulltextDocument;
import com.formkiq.aws.services.lambda.ApiAuthorization;
import com.formkiq.aws.services.lambda.ApiGatewayRequestEvent;
import com.formkiq.aws.services.lambda.ApiGatewayRequestEventUtil;
import com.formkiq.aws.services.lambda.ApiGatewayRequestHandler;
import com.formkiq.aws.services.lambda.ApiMapResponse;
import com.formkiq.aws.services.lambda.ApiRequestHandlerResponse;
import com.formkiq.aws.services.lambda.exceptions.BadException;
import com.formkiq.aws.services.lambda.exceptions.DocumentNotFoundException;
import com.formkiq.module.lambdaservices.AwsServiceCache;
import com.formkiq.module.typesense.TypeSenseService;

/** {@link ApiGatewayRequestHandler} for "/documents/{documentId}/fulltext". */
public class DocumentsFulltextRequestHandler extends AbstractPaymentRequiredRequestHandler {
public class DocumentsFulltextRequestHandler
implements ApiGatewayRequestHandler, ApiGatewayRequestEventUtil {

/** {@link DocumentsFulltextRequestHandler} URL. */
public static final String URL = "/documents/{documentId}/fulltext";

/**
* Build Document from Reqeust Body.
*
* @param body {@link Map}
* @return {@link Map}
* @throws BadException BadException
*/
private Map<String, Object> buildDocumentFromRequestBody(final Map<String, Object> body)
throws BadException {
DocumentToFulltextDocument fulltext = new DocumentToFulltextDocument();
Map<String, Object> document = fulltext.apply(body);

if (body.containsKey("contentUrls")) {
throw new BadException("'contentUrls' are not supported by Typesense");
}

if (body.containsKey("tags")) {
throw new BadException("'tags' are not supported with Typesense");
}

String text = document.get("text").toString();
if (body.containsKey("content")) {
text += body.get("content");
}

body.put("text", text);

return document;
}

private TypeSenseService checkTypesenseInstalled(final AwsServiceCache awsservice)
throws BadException {
if (!awsservice.hasModule("typesense")) {
throw new BadException("'typesense' is not configured");
}

return awsservice.getExtension(TypeSenseService.class);
}

@Override
public ApiRequestHandlerResponse delete(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final ApiAuthorization authorization,
final AwsServiceCache awsservice) throws Exception {

TypeSenseService typeSenseService = checkTypesenseInstalled(awsservice);

String siteId = authorization.siteId();
String documentId = event.getPathParameters().get("documentId");

typeSenseService.deleteDocument(siteId, documentId);

Map<String, Object> map = Map.of("message", "Deleted document '" + documentId + "'");
return new ApiRequestHandlerResponse(SC_OK, new ApiMapResponse(map));
}

@SuppressWarnings("unchecked")
@Override
public ApiRequestHandlerResponse get(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final ApiAuthorization authorization,
final AwsServiceCache awsservice) throws Exception {

String siteId = authorization.siteId();
String documentId = event.getPathParameters().get("documentId");

TypeSenseService typeSenseService = checkTypesenseInstalled(awsservice);

HttpResponse<String> response = typeSenseService.getDocument(siteId, documentId);

if (is2XX(response)) {

Map<String, Object> body = GSON.fromJson(response.body(), Map.class);
body.put("documentId", body.get("id"));
body.remove("id");

body.put("content", body.get("text"));
body.remove("text");

ApiMapResponse resp = new ApiMapResponse();
resp.setMap(body);
return new ApiRequestHandlerResponse(SC_OK, resp);
}

return handleError(response, documentId);
}

@Override
public String getRequestUrl() {
return URL;
}

private ApiRequestHandlerResponse handleError(final HttpResponse<String> response,
final String documentId) throws DocumentNotFoundException, BadException {
if (is404(response)) {
throw new DocumentNotFoundException(documentId);
}

throw new BadException(response.body());
}

@Override
public ApiRequestHandlerResponse patch(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final ApiAuthorization authorization,
final AwsServiceCache awsservice) throws Exception {

TypeSenseService typeSenseService = checkTypesenseInstalled(awsservice);

String siteId = authorization.siteId();
String documentId = event.getPathParameters().get("documentId");

Map<String, Object> body = fromBodyToMap(event);

Map<String, Object> document = buildDocumentFromRequestBody(body);

HttpResponse<String> response = typeSenseService.updateDocument(siteId, documentId, document);

if (is2XX(response)) {
Map<String, Object> map = Map.of("message", "Updated document to Typesense");
return new ApiRequestHandlerResponse(SC_OK, new ApiMapResponse(map));
}

return handleError(response, documentId);
}

@Override
public ApiRequestHandlerResponse put(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final ApiAuthorization authorization,
final AwsServiceCache awsservice) throws Exception {

TypeSenseService typeSenseService = checkTypesenseInstalled(awsservice);

String siteId = authorization.siteId();
String documentId = event.getPathParameters().get("documentId");

Map<String, Object> body = fromBodyToMap(event);

Map<String, Object> document = buildDocumentFromRequestBody(body);

HttpResponse<String> response =
typeSenseService.addOrUpdateDocument(siteId, documentId, document);

if (is2XX(response)) {
Map<String, Object> map = Map.of("message", "Add document to Typesense");
return new ApiRequestHandlerResponse(SC_OK, new ApiMapResponse(map));
}

return handleError(response, documentId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,27 @@
*/
package com.formkiq.stacks.api.handler;

import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.formkiq.aws.services.lambda.ApiAuthorization;
import com.formkiq.aws.services.lambda.ApiGatewayRequestEvent;
import com.formkiq.aws.services.lambda.ApiGatewayRequestHandler;
import com.formkiq.aws.services.lambda.ApiRequestHandlerResponse;
import com.formkiq.aws.services.lambda.exceptions.BadException;
import com.formkiq.module.lambdaservices.AwsServiceCache;

/** {@link ApiGatewayRequestHandler} for "/documents/{documentId}/fulltext/tags/{tagKey}". */
public class DocumentsFulltextRequestTagsKeyHandler extends AbstractPaymentRequiredRequestHandler {
public class DocumentsFulltextRequestTagsKeyHandler implements ApiGatewayRequestHandler {

/** {@link DocumentsFulltextRequestTagsKeyHandler} URL. */
public static final String URL = "/documents/{documentId}/fulltext/tags/{tagKey}";

@Override
public ApiRequestHandlerResponse delete(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final ApiAuthorization authorization,
final AwsServiceCache awsservice) throws Exception {
throw new BadException("Typesense does not support this API");
}

@Override
public String getRequestUrl() {
return URL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,29 @@
*/
package com.formkiq.stacks.api.handler;

import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.formkiq.aws.services.lambda.ApiAuthorization;
import com.formkiq.aws.services.lambda.ApiGatewayRequestEvent;
import com.formkiq.aws.services.lambda.ApiGatewayRequestHandler;
import com.formkiq.aws.services.lambda.ApiRequestHandlerResponse;
import com.formkiq.aws.services.lambda.exceptions.BadException;
import com.formkiq.module.lambdaservices.AwsServiceCache;

/**
* {@link ApiGatewayRequestHandler} for "/documents/{documentId}/fulltext/tags/{tagKey}/{tagValue}".
*/
public class DocumentsFulltextRequestTagsKeyValueHandler
extends AbstractPaymentRequiredRequestHandler {
public class DocumentsFulltextRequestTagsKeyValueHandler implements ApiGatewayRequestHandler {

/** {@link DocumentsFulltextRequestTagsKeyValueHandler} URL. */
public static final String URL = "/documents/{documentId}/fulltext/tags/{tagKey}/{tagValue}";

@Override
public ApiRequestHandlerResponse delete(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final ApiAuthorization authorization,
final AwsServiceCache awsservice) throws Exception {
throw new BadException("Typesense does not support this API");
}

@Override
public String getRequestUrl() {
return URL;
Expand Down
Loading

0 comments on commit 08084f5

Please sign in to comment.