From 5ff4651f9153cd7820b2cee47737f80289a52819 Mon Sep 17 00:00:00 2001
From: Jean-Pierre Portier <141755467+JPPortier@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:34:35 +0200
Subject: [PATCH] Add auto subscribe app tutorial (#76)
* docs (SMS): Add tutorial for SMS auto-subscribe-app
* ci: Adding examples compilation to CI/CD
---
.github/workflows/samples-compilation.yaml | 2 +
examples/compile.sh | 3 +
.../sms/auto-subscribe-app/README.md | 59 ++++++++++
.../tutorials/sms/auto-subscribe-app/pom.xml | 52 +++++++++
.../src/main/java/com/mycompany/app/App.java | 12 ++
.../app/AutoSubscribeController.java | 37 ++++++
.../mycompany/app/AutoSubscribeService.java | 107 ++++++++++++++++++
.../main/java/com/mycompany/app/Config.java | 33 ++++++
.../java/com/mycompany/app/GroupManager.java | 61 ++++++++++
.../src/main/resources/application.yaml | 14 +++
pom.xml | 1 +
11 files changed, 381 insertions(+)
create mode 100755 examples/compile.sh
create mode 100644 examples/tutorials/sms/auto-subscribe-app/README.md
create mode 100644 examples/tutorials/sms/auto-subscribe-app/pom.xml
create mode 100644 examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/App.java
create mode 100644 examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeController.java
create mode 100644 examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeService.java
create mode 100644 examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/Config.java
create mode 100644 examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/GroupManager.java
create mode 100644 examples/tutorials/sms/auto-subscribe-app/src/main/resources/application.yaml
diff --git a/.github/workflows/samples-compilation.yaml b/.github/workflows/samples-compilation.yaml
index 50322bac..7ec7c8f8 100644
--- a/.github/workflows/samples-compilation.yaml
+++ b/.github/workflows/samples-compilation.yaml
@@ -21,6 +21,8 @@ jobs:
cd sample-app
mvn -B clean package
mvn -B -f pom-webhooks.xml clean package
+ cd ../examples
+ ./compile.sh
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
diff --git a/examples/compile.sh b/examples/compile.sh
new file mode 100755
index 00000000..d527bf73
--- /dev/null
+++ b/examples/compile.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+(cd tutorials/sms/auto-subscribe-app && mvn clean package)
diff --git a/examples/tutorials/sms/auto-subscribe-app/README.md b/examples/tutorials/sms/auto-subscribe-app/README.md
new file mode 100644
index 00000000..00b74bbc
--- /dev/null
+++ b/examples/tutorials/sms/auto-subscribe-app/README.md
@@ -0,0 +1,59 @@
+# auto-subscribe application sample
+
+This directory contains sample related to Java SDK tutorials: [](https://developers.sinch.com/docs/sms/tutorials/sms/tutorials/java-sdk/auto-subscribe)
+
+## 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 is using the SpringBoot configuration file: [`application.yaml`](src/main/resources/application.yaml) file and set:
+
+#### 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
+Located in `server` section:
+- port: The port to be used to listen incoming request. Default: 8090
+
+### Starting server locally
+
+Compile and run the application as server onto you localhost.
+```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:
+```
+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 is having to be used to configure your callback from [Sinch dashboard](https://dashboard.sinch.com/sms/api/services)
\ No newline at end of file
diff --git a/examples/tutorials/sms/auto-subscribe-app/pom.xml b/examples/tutorials/sms/auto-subscribe-app/pom.xml
new file mode 100644
index 00000000..3c929796
--- /dev/null
+++ b/examples/tutorials/sms/auto-subscribe-app/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
+
+ com.sinch.sdk
+ sinch-sdk-java-sample-webhooks-app
+ 0.0.1-SNAPSHOT
+ Sinch Java SDK auto-subscribe Sample Application
+ Demo Project for auto-subscribe
+
+
+ [1.0.0,)
+ 21
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ com.sinch.sdk
+ sinch-sdk-java
+ ${sinch.sdk.java.version}
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/App.java b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/App.java
new file mode 100644
index 00000000..195ae602
--- /dev/null
+++ b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/App.java
@@ -0,0 +1,12 @@
+package com.mycompany.app;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class App {
+
+ public static void main(String[] args) {
+ SpringApplication.run(App.class, args);
+ }
+}
diff --git a/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeController.java b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeController.java
new file mode 100644
index 00000000..9a6c0b3e
--- /dev/null
+++ b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeController.java
@@ -0,0 +1,37 @@
+package com.mycompany.app;
+
+import com.sinch.sdk.domains.sms.SMSService;
+import com.sinch.sdk.domains.sms.models.InboundText;
+import java.util.Objects;
+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
+public class AutoSubscribeController {
+
+ private final SMSService smsService;
+ private final AutoSubscribeService service;
+
+ @Autowired
+ public AutoSubscribeController(SMSService smsService, AutoSubscribeService service) {
+ this.smsService = smsService;
+ this.service = service;
+ }
+
+ @PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE)
+ public void smsDeliveryEvent(@RequestBody String body) {
+
+ // decode the request payload
+ var event = smsService.webHooks().parse(body);
+
+ // let business layer process the request
+ if (Objects.requireNonNull(event) instanceof InboundText e) {
+ service.processInboundEvent(e);
+ } else {
+ throw new IllegalStateException("Unexpected value: " + event);
+ }
+ }
+}
diff --git a/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeService.java b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeService.java
new file mode 100644
index 00000000..1be13967
--- /dev/null
+++ b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeService.java
@@ -0,0 +1,107 @@
+package com.mycompany.app;
+
+import com.sinch.sdk.domains.sms.SMSService;
+import com.sinch.sdk.domains.sms.models.Group;
+import com.sinch.sdk.domains.sms.models.InboundText;
+import com.sinch.sdk.domains.sms.models.requests.GroupUpdateRequestParameters;
+import com.sinch.sdk.domains.sms.models.requests.SendSmsBatchTextRequest;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.logging.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AutoSubscribeService {
+
+ private static final Logger LOGGER = Logger.getLogger(AutoSubscribeService.class.getName());
+
+ private final SMSService smsService;
+ private final Group group;
+
+ @Autowired
+ public AutoSubscribeService(SMSService smsService) {
+ this.smsService = smsService;
+ this.group = new GroupManager(smsService).getGroup();
+ }
+
+ public void processInboundEvent(InboundText event) {
+
+ LOGGER.info("Received event:" + event);
+
+ var from = event.getFrom();
+ var to = event.getTo();
+ var body = event.getBody().trim();
+
+ var membersList = getMembersList(group);
+ var isMemberInGroup = isMemberInGroup(membersList, from);
+
+ String response;
+
+ if (body.equals("SUBSCRIBE")) {
+ response = subscribe(group, isMemberInGroup, to, from);
+ } else if (body.equals("STOP")) {
+ response = unsubscribe(group, isMemberInGroup, to, from);
+ } else {
+ response =
+ "Thanks for your interest. If you want to subscribe to this group, text \"SUBSCRIBE\" to +%s"
+ .formatted(to);
+ }
+
+ sendResponse(to, from, response);
+ }
+
+ private Collection getMembersList(Group group) {
+ return smsService.groups().listMembers(group.getId());
+ }
+
+ private boolean isMemberInGroup(Collection membersList, String member) {
+ return membersList.contains(member);
+ }
+
+ private String subscribe(
+ Group group, boolean isMemberInGroup, String groupPhoneNumber, String member) {
+
+ if (isMemberInGroup) {
+ return "You already subscribed to '%s'. Text \"STOP\" to +%s to leave this group."
+ .formatted(group.getName(), groupPhoneNumber);
+ }
+
+ var request =
+ GroupUpdateRequestParameters.builder().setAdd(Collections.singletonList(member)).build();
+
+ smsService.groups().update(group.getId(), request);
+ return "Congratulations! You are now subscribed to '%s'. Text \"STOP\" to +%s to leave this group."
+ .formatted(group.getName(), groupPhoneNumber);
+ }
+
+ private String unsubscribe(
+ Group group, boolean isMemberInGroup, String groupPhoneNumber, String member) {
+
+ if (!isMemberInGroup) {
+ return "You did not subscribed to '%s'. Text \"SUBSCRIBE\" to +%s to join this group."
+ .formatted(group.getName(), groupPhoneNumber);
+ }
+
+ var request =
+ GroupUpdateRequestParameters.builder().setRemove(Collections.singletonList(member)).build();
+
+ smsService.groups().update(group.getId(), request);
+ return "We're sorry to see you go. You can always rejoin '%s' by texting \"SUBSCRIBE\" to +%s."
+ .formatted(group.getName(), groupPhoneNumber);
+ }
+
+ private void sendResponse(String from, String to, String response) {
+
+ var request =
+ SendSmsBatchTextRequest.builder()
+ .setTo(Collections.singletonList(to))
+ .setBody(response)
+ .setFrom(from)
+ .build();
+
+ smsService.batches().send(request);
+
+ LOGGER.info("Replied: '%s'".formatted(response));
+ }
+}
diff --git a/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/Config.java b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/Config.java
new file mode 100644
index 00000000..0d311e4c
--- /dev/null
+++ b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/Config.java
@@ -0,0 +1,33 @@
+package com.mycompany.app;
+
+import com.sinch.sdk.SinchClient;
+import com.sinch.sdk.domains.sms.SMSService;
+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;
+
+ @Bean
+ public SMSService smsService() {
+
+ var configuration =
+ Configuration.builder()
+ .setProjectId(projectId)
+ .setKeyId(keyId)
+ .setKeySecret(keySecret)
+ .build();
+
+ return new SinchClient(configuration).sms();
+ }
+}
diff --git a/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/GroupManager.java b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/GroupManager.java
new file mode 100644
index 00000000..b445c8a3
--- /dev/null
+++ b/examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/GroupManager.java
@@ -0,0 +1,61 @@
+package com.mycompany.app;
+
+import com.sinch.sdk.domains.sms.GroupsService;
+import com.sinch.sdk.domains.sms.SMSService;
+import com.sinch.sdk.domains.sms.models.Group;
+import com.sinch.sdk.domains.sms.models.requests.GroupCreateRequestParameters;
+import java.util.Optional;
+import java.util.logging.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GroupManager {
+
+ private static final Logger LOGGER = Logger.getLogger(GroupManager.class.getName());
+
+ private final String GROUP_NAME = "Sinch Pirates";
+
+ private final SMSService smsService;
+
+ @Autowired
+ public GroupManager(SMSService smsService) {
+ this.smsService = smsService;
+ }
+
+ public Group getGroup() {
+
+ GroupsService service = smsService.groups();
+
+ // ensure we do not create a new group if already existing with same name
+ Optional group = retrieveGroup(service);
+ return group.orElseGet(() -> createGroup(service));
+ }
+
+ /*
+ * Retrieve group ID if group is existing
+ */
+ private Optional retrieveGroup(GroupsService service) {
+
+ Optional found =
+ service.list().stream().filter(group -> group.getName().equals(GROUP_NAME)).findFirst();
+
+ found.ifPresent(
+ group ->
+ LOGGER.info("Group '%s' find with id '%s'".formatted(group.getName(), group.getId())));
+ return found;
+ }
+
+ /*`
+ * Create a new group
+ */
+ private Group createGroup(GroupsService service) {
+
+ var request = GroupCreateRequestParameters.builder().setName(GROUP_NAME).build();
+
+ var group = service.create(request);
+
+ LOGGER.info("Group '%s' created with id '%s'".formatted(group.getName(), group.getId()));
+ return group;
+ }
+}
diff --git a/examples/tutorials/sms/auto-subscribe-app/src/main/resources/application.yaml b/examples/tutorials/sms/auto-subscribe-app/src/main/resources/application.yaml
new file mode 100644
index 00000000..b6da7d67
--- /dev/null
+++ b/examples/tutorials/sms/auto-subscribe-app/src/main/resources/application.yaml
@@ -0,0 +1,14 @@
+# springboot related config file
+
+logging:
+ level:
+ com: INFO
+
+server:
+ port: 8090
+
+credentials:
+ project-id:
+ key-id:
+ key-secret:
+
diff --git a/pom.xml b/pom.xml
index 07dfbc42..3ca65749 100644
--- a/pom.xml
+++ b/pom.xml
@@ -301,6 +301,7 @@
sample-app/src/main/java/com/sinch/sample/webhooks/sms/SmsController.java
sample-app/src/main/java/com/sinch/sample/webhooks/verification/VerificationController.java
sample-app/src/main/java/com/sinch/sample/webhooks/voice/VoiceController.java
+ examples/tutorials/sms/auto-subscribe-app/src/main/java/com/mycompany/app/AutoSubscribeController.java
1.18.1