Skip to content

Commit

Permalink
added /webhooks/{webhookId}/tags (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
mfriesen committed Feb 6, 2021
1 parent 8a1c54c commit c1c0d04
Show file tree
Hide file tree
Showing 20 changed files with 1,127 additions and 28 deletions.
Binary file modified dynamodb-documents/documentsTableSchema.numbers
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public void addTags(final String siteId, final String documentId,
|| !SYSTEM_DEFINED_TAGS.contains(tag.getKey());

DocumentTagToAttributeValueMap mapper =
new DocumentTagToAttributeValueMap(this.df, siteId, documentId);
new DocumentTagToAttributeValueMap(this.df, PREFIX_DOCS, siteId, documentId);

List<Map<String, AttributeValue>> valueList =
tags.stream().filter(predicate).map(mapper).collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import static com.formkiq.stacks.dynamodb.DbKeys.GSI2_PK;
import static com.formkiq.stacks.dynamodb.DbKeys.GSI2_SK;
import static com.formkiq.stacks.dynamodb.DbKeys.PK;
import static com.formkiq.stacks.dynamodb.DbKeys.PREFIX_DOCS;
import static com.formkiq.stacks.dynamodb.DbKeys.PREFIX_TAG;
import static com.formkiq.stacks.dynamodb.DbKeys.PREFIX_TAGS;
import static com.formkiq.stacks.dynamodb.DbKeys.SK;
Expand Down Expand Up @@ -53,16 +52,20 @@ public class DocumentTagToAttributeValueMap

/** {@link SimpleDateFormat} in ISO Standard format. */
private SimpleDateFormat df;
/** Primary Key Prefix. */
private String keyPrefix;

/**
* constructor.
*
* @param dateformat {@link SimpleDateFormat}
* @param pkPrefix {@link String}
* @param siteId {@link String}
* @param documentId {@link String}
*/
public DocumentTagToAttributeValueMap(final SimpleDateFormat dateformat, final String siteId,
final String documentId) {
public DocumentTagToAttributeValueMap(final SimpleDateFormat dateformat, final String pkPrefix,
final String siteId, final String documentId) {
this.keyPrefix = pkPrefix;
this.site = siteId;
this.document = documentId;
this.df = dateformat;
Expand Down Expand Up @@ -94,7 +97,7 @@ private Map<String, AttributeValue> buildTagAttributeValue(final String siteId,
Map<String, AttributeValue> pkvalues = new HashMap<String, AttributeValue>();

pkvalues.put(PK,
AttributeValue.builder().s(createDatabaseKey(siteId, PREFIX_DOCS + documentId)).build());
AttributeValue.builder().s(createDatabaseKey(siteId, this.keyPrefix + documentId)).build());
pkvalues.put(SK, AttributeValue.builder().s(PREFIX_TAGS + tagKey).build());

pkvalues.put(GSI1_PK, AttributeValue.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,52 @@
*/
package com.formkiq.stacks.dynamodb;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import com.formkiq.stacks.common.objects.DynamicObject;

/** Services for Querying, Updating Webhooks. */
public interface WebhooksService {

/**
* Add Tags to Webhook.
*
* @param siteId Optional Grouping siteId
* @param webhookId {@link String}
* @param tags {@link Collection} {@link DocumentTag}
* @param timeToLive {@link String}
*/
void addTags(String siteId, String webhookId, Collection<DocumentTag> tags, String timeToLive);

/**
* Delete Webhook.
*
* @param siteId Optional Grouping siteId
* @param id {@link String}
*/
void deleteWebhook(String siteId, String id);

/**
* Find Webhook Tag.
*
* @param siteId Optional Grouping siteId
* @param webhookId {@link String}
* @param tagKey {@link String}
*
* @return {@link DynamicObject}
*/
DynamicObject findTag(String siteId, String webhookId, String tagKey);

/**
* Find Webhook Tags.
*
* @param siteId Optional Grouping siteId
* @param webhookId {@link String}
*
* @return {@link DynamicObject} {@link PaginationResults}
*/
PaginationResults<DynamicObject> findTags(String siteId, String webhookId);

/**
* Find Webhook.
Expand Down Expand Up @@ -74,4 +106,6 @@ public interface WebhooksService {
* @param obj {@link DynamicObject}
*/
void updateWebhook(String siteId, String webhookId, DynamicObject obj);


}
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
import software.amazon.awssdk.services.dynamodb.model.KeysAndAttributes;
import software.amazon.awssdk.services.dynamodb.model.Put;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItem;
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;

/** Implementation of the {@link WebhooksService}. */
Expand Down Expand Up @@ -75,13 +78,76 @@ public WebhooksServiceImpl(final DynamoDbConnectionBuilder builder, final String
this.df = new SimpleDateFormat(DocumentService.DATE_FORMAT);
}

@Override
public void addTags(final String siteId, final String webhookId,
final Collection<DocumentTag> tags, final String timeToLive) {

if (tags != null) {

DocumentTagToAttributeValueMap mapper =
new DocumentTagToAttributeValueMap(this.df, PREFIX_WEBHOOK, siteId, webhookId);

List<Map<String, AttributeValue>> valueList =
tags.stream().map(mapper).collect(Collectors.toList());

if (timeToLive != null) {
valueList.forEach(v -> addN(v, "TimeToLive", timeToLive));
}

List<Put> putitems = valueList.stream()
.map(values -> Put.builder().tableName(this.documentTableName).item(values).build())
.collect(Collectors.toList());

List<TransactWriteItem> writes = putitems.stream()
.map(i -> TransactWriteItem.builder().put(i).build()).collect(Collectors.toList());

if (!writes.isEmpty()) {
this.dynamoDB
.transactWriteItems(TransactWriteItemsRequest.builder().transactItems(writes).build());
}
}
}

@Override
public void deleteWebhook(final String siteId, final String id) {
Map<String, AttributeValue> key = keysGeneric(siteId, PREFIX_WEBHOOK + id, "webhook");
this.dynamoDB
.deleteItem(DeleteItemRequest.builder().tableName(this.documentTableName).key(key).build());
}

@Override
public DynamicObject findTag(final String siteId, final String webhookId, final String tagKey) {
Map<String, AttributeValue> pkvalues =
keysGeneric(siteId, PREFIX_WEBHOOK + webhookId, PREFIX_TAGS + tagKey);
GetItemResponse response =
this.dynamoDB.getItem(
GetItemRequest.builder().tableName(this.documentTableName).key(pkvalues).build());
AttributeValueToDynamicObject transform = new AttributeValueToDynamicObject();
return !response.item().isEmpty() ? transform.apply(response.item()) : null;
}

@Override
public PaginationResults<DynamicObject> findTags(final String siteId,
final String webhookId) {

String expression = PK + " = :pk and begins_with(" + SK + ", :sk)";

Map<String, AttributeValue> values =
queryKeys(keysGeneric(siteId, PREFIX_WEBHOOK + webhookId, PREFIX_TAGS));

QueryRequest q = QueryRequest.builder().tableName(this.documentTableName)
.keyConditionExpression(expression).expressionAttributeValues(values)
.build();

QueryResponse result = this.dynamoDB.query(q);
AttributeValueToDynamicObject transform = new AttributeValueToDynamicObject();

List<DynamicObject> objs =
result.items().stream().map(i -> transform.apply(i)).collect(Collectors.toList());

return new PaginationResults<>(objs, new QueryResponseToPagination().apply(result));
}

@Override
public DynamicObject findWebhook(final String siteId, final String id) {

Expand Down Expand Up @@ -126,7 +192,7 @@ public List<DynamicObject> findWebhooks(final String siteId) {

return Collections.emptyList();
}

@Override
public String saveWebhook(final String siteId, final String name, final String userId,
final Date ttl) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,28 @@ public void testUpdateWebhooks01() {
result.getString("TimeToLive"));
}
}

/**
* Add Webhook Tag.
*/
@Test
public void testAddTags01() {
// given
String hook0 = this.service.saveWebhook(null, "test", "joe", null);
String hook1 = this.service.saveWebhook(null, "test2", "joe2", null);
DocumentTag tag0 = new DocumentTag(hook0, "category", "person", new Date(), "joe");
DocumentTag tag1 = new DocumentTag(hook0, "type", "person2", new Date(), "joe");

// when
this.service.addTags(null, hook0, Arrays.asList(tag0, tag1), null);

// then
assertNull(this.service.findTag(null, hook1, "category"));
DynamicObject tag = this.service.findTag(null, hook0, "category");
assertEquals("category", tag.getString("tagKey"));
assertEquals("person", tag.getString("tagValue"));

assertEquals(2, this.service.findTags(null, hook0).getResults().size());
assertEquals(0, this.service.findTags(null, hook1).getResults().size());
}
}
2 changes: 1 addition & 1 deletion lambda-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies {
testImplementation group: 'software.amazon.awssdk', name: 'apigateway', version: '2.15.25'
testImplementation group: 'software.amazon.awssdk', name: 'sts', version: '2.15.25'

testImplementation group: 'com.formkiq.stacks', name: 'client', version:'1.5.2'
testImplementation group: 'com.formkiq.stacks', name: 'client', version:'1.5.3'
testImplementation group: 'junit', name: 'junit', version:'4.+'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import com.formkiq.stacks.api.handler.VersionRequestHandler;
import com.formkiq.stacks.api.handler.WebhooksIdRequestHandler;
import com.formkiq.stacks.api.handler.WebhooksRequestHandler;
import com.formkiq.stacks.api.handler.WebhooksTagsRequestHandler;
import com.formkiq.stacks.dynamodb.DocumentItemDynamoDb;
import com.formkiq.stacks.dynamodb.DocumentTag;
import com.formkiq.stacks.dynamodb.DocumentTagType;
Expand Down Expand Up @@ -161,6 +162,8 @@ public ApiGatewayRequestHandler findRequestHandler(final String method, final St
case "/documents/{documentId}/upload":
return new DocumentsIdUploadRequestHandler();

case "/webhooks/{webhookId}/tags":
return new WebhooksTagsRequestHandler();
case "/webhooks/{webhookId}":
return new WebhooksIdRequestHandler();
case "/webhooks":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* MIT License
*
* Copyright (c) 2018 - 2020 FormKiQ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.formkiq.stacks.api.handler;

import static com.formkiq.lambda.apigateway.ApiResponseStatus.SC_CREATED;
import static com.formkiq.lambda.apigateway.ApiResponseStatus.SC_OK;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.formkiq.lambda.apigateway.ApiAuthorizer;
import com.formkiq.lambda.apigateway.ApiGatewayRequestEvent;
import com.formkiq.lambda.apigateway.ApiGatewayRequestEventUtil;
import com.formkiq.lambda.apigateway.ApiGatewayRequestHandler;
import com.formkiq.lambda.apigateway.ApiMapResponse;
import com.formkiq.lambda.apigateway.ApiMessageResponse;
import com.formkiq.lambda.apigateway.ApiRequestHandlerResponse;
import com.formkiq.lambda.apigateway.ApiResponse;
import com.formkiq.lambda.apigateway.AwsServiceCache;
import com.formkiq.lambda.apigateway.exception.BadException;
import com.formkiq.lambda.apigateway.exception.NotFoundException;
import com.formkiq.stacks.common.objects.DynamicObject;
import com.formkiq.stacks.dynamodb.DocumentTag;
import com.formkiq.stacks.dynamodb.DocumentTagType;
import com.formkiq.stacks.dynamodb.PaginationResults;

/** {@link ApiGatewayRequestHandler} for "/webhooks/{webhookId}/tags". */
public class WebhooksTagsRequestHandler
implements ApiGatewayRequestHandler, ApiGatewayRequestEventUtil {

@Override
public ApiRequestHandlerResponse get(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final ApiAuthorizer authorizer,
final AwsServiceCache awsServices) throws Exception {

String siteId = getSiteId(event);
String id = getPathParameter(event, "webhookId");
PaginationResults<DynamicObject> list = awsServices.webhookService().findTags(siteId, id);

List<Map<String, Object>> tags = list.getResults().stream().map(m -> {
Map<String, Object> map = new HashMap<>();

map.put("insertedDate", m.getString("inserteddate"));
map.put("webhookId", id);
map.put("type", m.getString("type"));
map.put("userId", m.getString("userId"));
map.put("value", m.getString("tagValue"));
map.put("key", m.getString("tagKey"));

return map;
}).collect(Collectors.toList());

return new ApiRequestHandlerResponse(SC_OK, new ApiMapResponse(Map.of("tags", tags)));
}

@Override
public String getRequestUrl() {
return "/webhooks/{webhookId}/tags";
}

@Override
public ApiRequestHandlerResponse post(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final ApiAuthorizer authorizer,
final AwsServiceCache awsServices) throws Exception {

DocumentTag tag = fromBodyToObject(logger, event, DocumentTag.class);

if (tag.getKey() == null || tag.getKey().length() == 0) {
throw new BadException("invalid json body");
}

String siteId = getSiteId(event);
String id = getPathParameter(event, "webhookId");

tag.setType(DocumentTagType.USERDEFINED);
tag.setInsertedDate(new Date());
tag.setUserId(getCallingCognitoUsername(event));

DynamicObject webhook = awsServices.webhookService().findWebhook(siteId, id);
if (webhook == null) {
throw new NotFoundException("Webhook 'id' not found");
}

awsServices.webhookService().addTags(siteId, id, Arrays.asList(tag),
webhook.getString("TimeToLive"));

ApiResponse resp = new ApiMessageResponse("Created Tag '" + tag.getKey() + "'.");
return new ApiRequestHandlerResponse(SC_CREATED, resp);
}
}
Loading

0 comments on commit c1c0d04

Please sign in to comment.