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

feature (template/server): Provide a server side application template for webhooks #10

Merged
merged 6 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mvn -f pom-ci.xml clean spotless:apply

(cd templates/client && mvn clean package) || exit 1
(cd templates/server && mvn clean package) || exit 1
(cd tutorials && ./compile.sh) || exit 1
(cd getting-started && ./compile.sh) || exit 1

78 changes: 78 additions & 0 deletions templates/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Backend application built using Sinch Java SDK to handle incoming webhooks

This directory contains a server application based onto [Sinch SDK java](https://github.com/sinch/sinch-sdk-java)

## Requirements

- JDK 8 or later (Sinch SDK Java is requiring java 8 only but client application can use latest available version)
- [Maven](https://maven.apache.org/)
- [SpringBoot](https://spring.io/projects/spring-boot)
- [Sinch account](https://dashboard.sinch.com)
- [ngrok](https://ngrok.com/docs)

## Configuration

### Configure application settings

com.mycompany.app.Application settings are using the `SpringBoot` configuration file: [`application.yaml`](src/main/resources/application.yaml) file and enable to configure:

#### Sinch credentials
Located in `credentials` section (*you can find all of the credentials you need on your [Sinch dashboard](https://dashboard.sinch.com)*):
- `project-id`: YOUR_project_id
- `key-id`: YOUR_access_key_id
- `key-secret`: YOUR_access_key_secret

#### Server port
<em>Default: 8090</em>

Located in `server` section:
- port: The port to be used to listen to incoming requests. <em>Default: 8090</em>

## Usage

### Start server
1. Edit configuration file

See above for Configuration paragraph

2. Start server locally.

Compile and run the application as server locally.
```bash
mvn spring-boot:run
```
### EndPoints
When server is online, declared controllers will respond to following endpoints

| Service | Endpoint |
|--------------|--------------------|
| Numbers | /NumbersEvent |
| SMS | /SmsEvent |
| Verification | /VerificationEvent |
| Voice | /VoiceEvent |

## Use ngrok to forward request to local server

Forwarding request to same `8090` port used above:

*Note: The `8090` value is coming from default config and can be changed (see [Server port](#Server port) configuration section)*

```bash
ngrok http 8090
```

ngrok output will contains output like:
```
ngrok (Ctrl+C to quit)

...
Forwarding https://0e64-78-117-86-140.ngrok-free.app -> http://localhost:8090

```
The line
```
Forwarding https://0e64-78-117-86-140.ngrok-free.app -> http://localhost:8090
```
Contains `https://0e64-78-117-86-140.ngrok-free.app` value.

This value must be used to configure callback's URL from your [Sinch dashboard](https://dashboard.sinch.com/sms/api/services)
51 changes: 51 additions & 0 deletions templates/server/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>my.company.com</groupId>
<artifactId>sinch-java-sdk-server-application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Sinch Java SDK Server Application</name>

<properties>
<sinch.sdk.java.version>[1.0.0,)</sinch.sdk.java.version>
<java.version>21</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.sinch.sdk</groupId>
<artifactId>sinch-sdk-java</artifactId>
<version>${sinch.sdk.java.version}</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.mycompany.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
53 changes: 53 additions & 0 deletions templates/server/src/main/java/com/mycompany/app/Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.mycompany.app;

import com.sinch.sdk.SinchClient;
import com.sinch.sdk.core.utils.StringUtil;
import com.sinch.sdk.models.Configuration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

@org.springframework.context.annotation.Configuration
public class Config {

@Value("${credentials.project-id}")
String projectId;

@Value("${credentials.key-id}")
String keyId;

@Value("${credentials.key-secret}")
String keySecret;

@Value("${credentials.application-api-key}")
String applicationKey;

@Value("${credentials.application-api-secret}")
String applicationSecret;

@Bean
public SinchClient sinchClient() {

Configuration.Builder builder = Configuration.builder();

if (!StringUtil.isEmpty(projectId)) {
builder.setProjectId(projectId);
}

if (!StringUtil.isEmpty(keyId)) {
builder.setKeyId(keyId);
}
if (!StringUtil.isEmpty(keySecret)) {
builder.setKeySecret(keySecret);
}

if (!StringUtil.isEmpty(applicationKey)) {
builder.setApplicationKey(applicationKey);
}

if (!StringUtil.isEmpty(applicationSecret)) {
builder.setApplicationSecret(applicationSecret);
}

return new SinchClient(builder.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.mycompany.app.numbers;

import com.sinch.sdk.SinchClient;
import com.sinch.sdk.domains.numbers.api.v1.WebHooksService;
import com.sinch.sdk.domains.numbers.models.v1.webhooks.NumberEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController("Numbers")
public class Controller {

private final SinchClient sinchClient;
private final WebhooksBusinessLogic webhooksBusinessLogic;

@Autowired
public Controller(SinchClient sinchClient, WebhooksBusinessLogic webhooksBusinessLogic) {
this.sinchClient = sinchClient;
this.webhooksBusinessLogic = webhooksBusinessLogic;
}

@PostMapping(
value = "/NumbersEvent",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public void NumbersEvent(@RequestBody String body) {

WebHooksService webhooks = sinchClient.numbers().v1().webhooks();

// decode the request payload
NumberEvent event = webhooks.parseEvent(body);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, the incoming request should be verified before being processed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.
Created a ticket to add this missing feature to webhooks service
Thank you to highlight this


// let business layer process the request
webhooksBusinessLogic.numbersEvent(event);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although thanks to the Spring Boot configuration (typically the @ResponseBody annotation included with @RestController) and the method returning void, this will return an empty body with a status 200 (which is expected), it might be clearer to explicitly a ResponseEntity to make the intended behavior clear

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good [educational] idea 👍
Applied to all controllers

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.mycompany.app.numbers;

import com.sinch.sdk.domains.numbers.models.v1.webhooks.NumberEvent;
import java.util.logging.Logger;
import org.springframework.stereotype.Component;

@Component("NumbersWebhooksBusinessLogic")
public class WebhooksBusinessLogic {

private static final Logger LOGGER = Logger.getLogger(WebhooksBusinessLogic.class.getName());

public void numbersEvent(NumberEvent event) {

LOGGER.info("Handle event :" + event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.mycompany.app.sms;

import com.sinch.sdk.SinchClient;
import com.sinch.sdk.domains.sms.WebHooksService;
import com.sinch.sdk.domains.sms.models.DeliveryReportBatch;
import com.sinch.sdk.domains.sms.models.DeliveryReportRecipient;
import com.sinch.sdk.domains.sms.models.InboundBinary;
import com.sinch.sdk.domains.sms.models.InboundText;
import com.sinch.sdk.domains.sms.models.webhooks.WebhooksEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController("SMS")
public class Controller {

private final SinchClient sinchClient;
private final WebhooksBusinessLogic webhooksBusinessLogic;

@Autowired
public Controller(SinchClient sinchClient, WebhooksBusinessLogic webhooksBusinessLogic) {
this.sinchClient = sinchClient;
this.webhooksBusinessLogic = webhooksBusinessLogic;
}

@PostMapping(
value = "/SmsEvent",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public void smsDeliveryEvent(@RequestBody String body) {

WebHooksService webhooks = sinchClient.sms().webHooks();

// decode the request payload
WebhooksEvent event = webhooks.parse(body);

// let business layer process the request
switch (event) {
case InboundBinary e -> webhooksBusinessLogic.processInboundEvent(e);
case InboundText e -> webhooksBusinessLogic.processInboundEvent(e);
case DeliveryReportRecipient e -> webhooksBusinessLogic.processDeliveryReportEvent(e);
case DeliveryReportBatch e -> webhooksBusinessLogic.processDeliveryReportEvent(e);
default -> throw new IllegalStateException("Unexpected value: " + event);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.mycompany.app.sms;

import com.sinch.sdk.domains.sms.models.DeliveryReportBatch;
import com.sinch.sdk.domains.sms.models.DeliveryReportRecipient;
import com.sinch.sdk.domains.sms.models.InboundBinary;
import com.sinch.sdk.domains.sms.models.InboundText;
import com.sinch.sdk.domains.sms.models.webhooks.WebhooksEvent;
import java.util.logging.Logger;
import org.springframework.stereotype.Component;

@Component("SMSWebhooksBusinessLogic")
public class WebhooksBusinessLogic {

private static final Logger LOGGER = Logger.getLogger(WebhooksBusinessLogic.class.getName());

public void processInboundEvent(InboundText event) {
trace(event);
}

public void processInboundEvent(InboundBinary event) {
trace(event);
}

public void processDeliveryReportEvent(DeliveryReportRecipient event) {
trace(event);
}

public void processDeliveryReportEvent(DeliveryReportBatch event) {
trace(event);
}

private void trace(WebhooksEvent event) {
LOGGER.info("Handle event :" + event);
}
}
Loading
Loading