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