-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from sinch/voice-lead-capture-app-tutorial-pro…
…posal Voice lead capture app tutorial
- Loading branch information
Showing
10 changed files
with
533 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
.idea | ||
.vscode | ||
**/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
#!/bin/sh | ||
|
||
(cd sms/auto-subscribe-app && mvn clean package) | ||
(cd voice/capture-leads-app && mvn clean package) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# Qualify leads application sample | ||
|
||
This directory contains samples related to Java SDK tutorials: [qualify leads](https://developers.sinch.com/docs/voice/tutorials/qualify-leads/java) | ||
|
||
## DISCLAIMER | ||
|
||
This tutorial is based on mixing a command-line function with a server-side backend service. | ||
|
||
It is not a correct use of the CLI outside of an educational purpose. | ||
|
||
## Requirements | ||
|
||
- JDK 21 or later | ||
- [Maven](https://maven.apache.org/) | ||
- [ngrok](https://ngrok.com/docs) | ||
- [Sinch account](https://dashboard.sinch.com) | ||
|
||
## Usage | ||
|
||
### Configure application settings | ||
|
||
Application settings are using the SpringBoot configuration file: [`application.yaml`](src/main/resources/application.yaml) file and enable to configure: | ||
|
||
#### Required Sinch credentials | ||
|
||
Located in `credentials` section (*you can find all of the credentials you need on your [Sinch dashboard](https://dashboard.sinch.com)*): | ||
|
||
- `application-api-key`: YOUR_application_key | ||
- `application-api-secret`: YOUR_application_secret | ||
|
||
#### Other required values | ||
|
||
This tutorial uses other values that you should also assign: | ||
|
||
- `sinch-number`: This is the Sinch number assigned to your [Voice app](https://dashboard.sinch.com/voice/apps). | ||
- `sip-address`: If you are performing this tutorial with a SIP infrastructure, this is where you would enter your SIP address. | ||
|
||
#### Server port | ||
|
||
Located in `server` section: | ||
|
||
- port: The port to be used to listen to incoming requests. <em>Default: 8090</em> | ||
|
||
### Starting server locally | ||
|
||
Compile and run the application as server locally. | ||
|
||
```bash | ||
mvn spring-boot:run | ||
``` | ||
|
||
### 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: | ||
|
||
```shell | ||
ngrok (Ctrl+C to quit) | ||
|
||
... | ||
Forwarding https://0e64-78-117-86-140.ngrok-free.app -> http://localhost:8090 | ||
|
||
``` | ||
|
||
The line | ||
|
||
```shell | ||
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 the callback URL on your [Sinch dashboard](https://dashboard.sinch.com/voice/apps) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?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-sdk-java-tuturial-auto-subscribe</artifactId> | ||
<version>0.0.1-SNAPSHOT</version> | ||
<name>Sinch Java SDK Capture Leads Sample Application</name> | ||
<description>Demo Project for Capturing Leads</description> | ||
|
||
<properties> | ||
<sinch.sdk.java.version>[1.2.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> |
12 changes: 12 additions & 0 deletions
12
tutorials/voice/capture-leads-app/src/main/java/com/mycompany/app/Application.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
tutorials/voice/capture-leads-app/src/main/java/com/mycompany/app/Config.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.mycompany.app; | ||
|
||
import com.sinch.sdk.SinchClient; | ||
import com.sinch.sdk.domains.voice.VoiceService; | ||
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.application-api-key}") | ||
String applicationKey; | ||
|
||
@Value("${credentials.application-api-secret}") | ||
String applicationSecret; | ||
|
||
@Bean | ||
public VoiceService voiceService() { | ||
|
||
var configuration = | ||
Configuration.builder() | ||
.setApplicationKey(applicationKey) | ||
.setApplicationSecret(applicationSecret) | ||
.build(); | ||
|
||
return new SinchClient(configuration).voice(); | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
tutorials/voice/capture-leads-app/src/main/java/com/mycompany/app/voice/CLIHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package com.mycompany.app.voice; | ||
|
||
import com.sinch.sdk.core.utils.StringUtil; | ||
import com.sinch.sdk.domains.voice.CalloutsService; | ||
import com.sinch.sdk.domains.voice.VoiceService; | ||
import com.sinch.sdk.domains.voice.models.requests.CalloutRequestParametersCustom; | ||
import com.sinch.sdk.domains.voice.models.svaml.ActionConnectPstn; | ||
import com.sinch.sdk.domains.voice.models.svaml.AnsweringMachineDetection; | ||
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl; | ||
import com.sinch.sdk.models.E164PhoneNumber; | ||
import java.util.Scanner; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.boot.CommandLineRunner; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class CLIHelper implements CommandLineRunner { | ||
|
||
@Value("${sinch-number}") | ||
String sinchNumber; | ||
|
||
private final CalloutsService calloutsService; | ||
|
||
@Autowired | ||
public CLIHelper(VoiceService voiceService) { | ||
this.calloutsService = voiceService.callouts(); | ||
} | ||
|
||
@Override | ||
public void run(String... args) { | ||
|
||
while (true) { | ||
E164PhoneNumber phoneNumber = promptPhoneNumber(); | ||
|
||
proceedCallout(phoneNumber); | ||
} | ||
} | ||
|
||
void proceedCallout(E164PhoneNumber phoneNumber) { | ||
var response = | ||
calloutsService.custom( | ||
CalloutRequestParametersCustom.builder() | ||
.setIce( | ||
SVAMLControl.builder() | ||
.setAction( | ||
ActionConnectPstn.builder() | ||
.setNumber(phoneNumber) | ||
.setCli(sinchNumber) | ||
.setAnsweringMachineDetection( | ||
AnsweringMachineDetection.builder().setEnabled(true).build()) | ||
.build()) | ||
.build()) | ||
.build()); | ||
|
||
echo("Callout response: '%s'", response); | ||
} | ||
|
||
private E164PhoneNumber promptPhoneNumber() { | ||
String input; | ||
boolean valid; | ||
do { | ||
input = prompt("\nEnter the phone number you want to call"); | ||
valid = E164PhoneNumber.validate(input); | ||
if (!valid) { | ||
echo("Invalid number '%s'", input); | ||
} | ||
} while (!valid); | ||
|
||
return E164PhoneNumber.valueOf(input); | ||
} | ||
|
||
private String prompt(String prompt) { | ||
|
||
String input = null; | ||
Scanner scanner = new Scanner(System.in); | ||
|
||
while (StringUtil.isEmpty(input)) { | ||
System.out.println(prompt + " ([Q] to quit): "); | ||
input = scanner.nextLine(); | ||
} | ||
|
||
if ("Q".equalsIgnoreCase(input)) { | ||
System.out.println("Quit application"); | ||
System.exit(0); | ||
} | ||
|
||
return input.replaceAll(" ", ""); | ||
} | ||
|
||
private void echo(String text, Object... args) { | ||
System.out.println(" " + String.format(text, args)); | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
tutorials/voice/capture-leads-app/src/main/java/com/mycompany/app/voice/Controller.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package com.mycompany.app.voice; | ||
|
||
import com.sinch.sdk.domains.voice.VoiceService; | ||
import com.sinch.sdk.domains.voice.WebHooksService; | ||
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl; | ||
import com.sinch.sdk.domains.voice.models.webhooks.AnsweredCallEvent; | ||
import com.sinch.sdk.domains.voice.models.webhooks.PromptInputEvent; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.MediaType; | ||
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.RequestHeader; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.server.ResponseStatusException; | ||
|
||
@RestController("Voice") | ||
public class Controller { | ||
|
||
private final WebHooksService webhooks; | ||
private final ServerBusinessLogic webhooksBusinessLogic; | ||
|
||
@Autowired | ||
public Controller(VoiceService voiceService, ServerBusinessLogic webhooksBusinessLogic) { | ||
this.webhooks = voiceService.webhooks(); | ||
this.webhooksBusinessLogic = webhooksBusinessLogic; | ||
} | ||
|
||
@PostMapping( | ||
value = "/VoiceEvent", | ||
consumes = MediaType.APPLICATION_JSON_VALUE, | ||
produces = MediaType.APPLICATION_JSON_VALUE) | ||
public ResponseEntity<String> VoiceEvent( | ||
@RequestHeader Map<String, String> headers, @RequestBody String body) { | ||
|
||
validateRequest(headers, body); | ||
|
||
// decode the request payload | ||
var event = webhooks.unserializeWebhooksEvent(body); | ||
|
||
Optional<SVAMLControl> response = Optional.empty(); | ||
|
||
// let business layer process the request | ||
if (event instanceof AnsweredCallEvent e) { | ||
response = Optional.of(webhooksBusinessLogic.answeredCallEvent(e)); | ||
} | ||
if (event instanceof PromptInputEvent e) { | ||
response = Optional.of(webhooksBusinessLogic.promptInputEvent(e)); | ||
} | ||
|
||
if (response.isEmpty()) { | ||
return ResponseEntity.ok().body(""); | ||
} | ||
|
||
String serializedResponse = webhooks.serializeWebhooksResponse(response.get()); | ||
|
||
return ResponseEntity.ok().body(serializedResponse); | ||
} | ||
|
||
void validateRequest(Map<String, String> headers, String body) { | ||
|
||
var validAuth = | ||
webhooks.validateAuthenticatedRequest( | ||
// The HTTP verb this controller is managing | ||
"POST", | ||
// The URI this controller is managing | ||
"/VoiceEvent", | ||
// request headers | ||
headers, | ||
// request payload body | ||
body); | ||
|
||
// token validation failed | ||
if (!validAuth) { | ||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); | ||
} | ||
} | ||
} |
Oops, something went wrong.