Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#413] Add /metadata.set endpoint #414

Merged
merged 10 commits into from Dec 2, 2020
@@ -0,0 +1,41 @@
package co.airy.core.api.communication;

import co.airy.avro.communication.MetadataAction;
import co.airy.avro.communication.MetadataActionType;
import co.airy.core.api.communication.payload.SetMetadataRequestPayload;
import co.airy.payload.response.EmptyResponsePayload;
import co.airy.payload.response.RequestErrorResponsePayload;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.time.Instant;

@RestController
public class MetadataController {
private final Stores stores;

public MetadataController(Stores stores) {
this.stores = stores;
}

@PostMapping("/metadata.set")
ResponseEntity<?> setMetadata(@RequestBody @Valid SetMetadataRequestPayload setMetadataRequestPayload) {
final MetadataAction metadataAction = MetadataAction.newBuilder()
.setActionType(MetadataActionType.SET)
.setTimestamp(Instant.now().toEpochMilli())
.setConversationId(setMetadataRequestPayload.getConversationId())
.setValue(setMetadataRequestPayload.getValue())
.setKey(setMetadataRequestPayload.getKey())
.build();
try {
stores.storeMetadata(metadataAction);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new RequestErrorResponsePayload(e.getMessage()));
}
return ResponseEntity.ok(new EmptyResponsePayload());
}
}
@@ -0,0 +1,21 @@
package co.airy.core.api.communication.payload;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotNull;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class SetMetadataRequestPayload {
@NotNull
private String conversationId;
@NotNull
private String key;
@NotNull
private String value;
}
@@ -0,0 +1,94 @@
package co.airy.core.api.communication;

import co.airy.avro.communication.Channel;
import co.airy.avro.communication.ChannelConnectionState;
import co.airy.core.api.communication.util.TestConversation;
import co.airy.kafka.schema.application.ApplicationCommunicationChannels;
import co.airy.kafka.schema.application.ApplicationCommunicationMessages;
import co.airy.kafka.schema.application.ApplicationCommunicationMetadata;
import co.airy.kafka.schema.application.ApplicationCommunicationReadReceipts;
import co.airy.kafka.test.KafkaTestHelper;
import co.airy.kafka.test.junit.SharedKafkaTestResource;
import co.airy.spring.core.AirySpringBootApplication;
import co.airy.spring.test.WebTestHelper;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.UUID;

import static co.airy.test.Timing.retryOnException;
import static org.hamcrest.core.Is.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class)
@TestPropertySource(value = "classpath:test.properties")
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
public class MetadataControllerTest {
@RegisterExtension
public static final SharedKafkaTestResource sharedKafkaTestResource = new SharedKafkaTestResource();

private static KafkaTestHelper kafkaTestHelper;

@Autowired
private WebTestHelper webTestHelper;

private static final ApplicationCommunicationMessages applicationCommunicationMessages = new ApplicationCommunicationMessages();
private static final ApplicationCommunicationChannels applicationCommunicationChannels = new ApplicationCommunicationChannels();
private static final ApplicationCommunicationMetadata applicationCommunicationMetadata = new ApplicationCommunicationMetadata();
private static final ApplicationCommunicationReadReceipts applicationCommunicationReadReceipts = new ApplicationCommunicationReadReceipts();

@BeforeAll
static void beforeAll() throws Exception {
kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource,
applicationCommunicationMessages,
applicationCommunicationChannels,
applicationCommunicationMetadata,
applicationCommunicationReadReceipts);

kafkaTestHelper.beforeAll();
}

@AfterAll
static void afterAll() throws Exception {
kafkaTestHelper.afterAll();
}

@BeforeEach
void beforeEach() throws Exception {
webTestHelper.waitUntilHealthy();
}
@Test
void canSetMetadata() throws Exception {
final Channel channel = Channel.newBuilder()
.setConnectionState(ChannelConnectionState.CONNECTED)
.setId("channel-id")
.setName("channel-name")
.setSource("facebook")
.setSourceChannelId("ps-id")
.build();
final String conversationId = UUID.randomUUID().toString();

kafkaTestHelper.produceRecord(new ProducerRecord<>(applicationCommunicationChannels.name(), channel.getId(), channel));
kafkaTestHelper.produceRecords(TestConversation.generateRecords(conversationId, channel, 1));

retryOnException(
() -> webTestHelper.post("/metadata.set",
"{\"conversation_id\":\"" + conversationId + "\", \"key\": \"awesome.key\", \"value\": \"awesome-value\"}",
"user-id")
.andExpect(status().isOk()),
"Error setting metadata"
);
}
}
19 changes: 19 additions & 0 deletions docs/docs/api/http.md
Expand Up @@ -643,3 +643,22 @@ The response comes in two parts:
- `total`

The total number of elements across all pages.

### Metadata

Airy provides a mechanism to add metadata to a conversation. A metadata entry consists of a key and a value.
A key can use the dot notation to represent namespaces.
paulodiniz marked this conversation as resolved.
Show resolved Hide resolved

### Setting metadata

`POST /metadata.set`

```json
{
"conversation_id": "conversation-id",
"key": "source.contact.first_name",
paulodiniz marked this conversation as resolved.
Show resolved Hide resolved
"value": "Grace"
}
```

This endpoint returns `200` if the operation was successful.