From 51d5aa4361120e1c3a18a223db8b6fe562fca32a Mon Sep 17 00:00:00 2001 From: Richard Yang Date: Fri, 21 May 2021 16:51:34 -0500 Subject: [PATCH 1/2] init --- .../rolecall/jsonobjects/GenericMessage.java | 34 +++++++++++++++++++ .../restcontrollers/Notification.java | 30 ++++++++++++++++ .../services/NotificationServices.java | 18 ++++++++++ 3 files changed, 82 insertions(+) create mode 100644 backend/src/main/java/com/google/rolecall/jsonobjects/GenericMessage.java create mode 100644 backend/src/main/java/com/google/rolecall/restcontrollers/Notification.java create mode 100644 backend/src/main/java/com/google/rolecall/services/NotificationServices.java diff --git a/backend/src/main/java/com/google/rolecall/jsonobjects/GenericMessage.java b/backend/src/main/java/com/google/rolecall/jsonobjects/GenericMessage.java new file mode 100644 index 00000000..2324ad51 --- /dev/null +++ b/backend/src/main/java/com/google/rolecall/jsonobjects/GenericMessage.java @@ -0,0 +1,34 @@ +package com.google.rolecall.jsonobjects; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; + +/* Json representation of User class for serializing and deserializing. */ +@AutoValue +@JsonDeserialize(builder = AutoValue_GenericMessage.Builder.class) +public abstract class GenericMessage { + @Nullable + @JsonProperty("message") + public abstract String message(); + + + /* Every UserInfo should be unique unless it's being comapred to itself */ + @Override + public boolean equals(Object object) { + return this == object; + } + + public static Builder newBuilder() { + return new AutoValue_GenericMessage.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + @JsonProperty("message") + public abstract Builder setMessage(String message); + + public abstract GenericMessage build(); + } +} diff --git a/backend/src/main/java/com/google/rolecall/restcontrollers/Notification.java b/backend/src/main/java/com/google/rolecall/restcontrollers/Notification.java new file mode 100644 index 00000000..09c6d624 --- /dev/null +++ b/backend/src/main/java/com/google/rolecall/restcontrollers/Notification.java @@ -0,0 +1,30 @@ +package com.google.rolecall.restcontrollers; +import com.google.rolecall.jsonobjects.ResponseSchema; +import com.google.rolecall.jsonobjects.GenericMessage; +import com.google.rolecall.restcontrollers.Annotations.Endpoint; +import com.google.rolecall.restcontrollers.Annotations.Post; +import com.google.rolecall.services.NotificationServices; +import java.util.concurrent.CompletableFuture; + +/** Endpoints for manipulating User objects. */ +@Endpoint("/api/notification") +public class Notification extends AsyncRestEndpoint { + + private final NotificationServices service; + + @Post + public CompletableFuture> processNotification() { + try { + service.process(); + } catch(Exception e) { + return CompletableFuture.failedFuture(e); + } + + ResponseSchema response = new ResponseSchema<>(GenericMessage.newBuilder().setMessage("OK").build()); + return CompletableFuture.completedFuture(response); + } + + public Notification(NotificationServices service) { + this.service = service; + } +} diff --git a/backend/src/main/java/com/google/rolecall/services/NotificationServices.java b/backend/src/main/java/com/google/rolecall/services/NotificationServices.java new file mode 100644 index 00000000..9dba0bff --- /dev/null +++ b/backend/src/main/java/com/google/rolecall/services/NotificationServices.java @@ -0,0 +1,18 @@ +package com.google.rolecall.services; + + +import org.springframework.stereotype.Service; + +/* Utility classes for accessing Users while mantaining database consistencies. */ +@Service("noticiationServices") +public class NotificationServices { + + + public void process() { + + } + + public NotificationServices() { + + } +} From dd30fea9fe5c5b48d490818abf2cca1dacead6ac Mon Sep 17 00:00:00 2001 From: Richard Yang Date: Mon, 24 May 2021 13:09:46 -0500 Subject: [PATCH 2/2] SMS and email sending implementation --- backend/pom.xml | 25 ++++-- .../java/com/google/rolecall/Constants.java | 2 + .../services/NotificationServices.java | 61 +++++++++++++-- .../services/PerformanceServices.java | 3 +- .../google/rolecall/util/CPSNotification.java | 77 ++++++++++++++++--- .../main/resources/application-dev.properties | 4 + 6 files changed, 151 insertions(+), 21 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index 89ade177..6dfef7d0 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -170,11 +170,26 @@ com.google.cloud google-cloud-pubsub - - com.google.code.gson - gson - 2.8.6 - + + com.google.code.gson + gson + 2.8.6 + + + com.sendgrid + java-http-client + 4.3.6 + + + com.sendgrid + sendgrid-java + 4.2.1 + + + com.twilio.sdk + twilio + 8.13.0 + diff --git a/backend/src/main/java/com/google/rolecall/Constants.java b/backend/src/main/java/com/google/rolecall/Constants.java index a8939654..150aef20 100644 --- a/backend/src/main/java/com/google/rolecall/Constants.java +++ b/backend/src/main/java/com/google/rolecall/Constants.java @@ -57,6 +57,8 @@ public static class Permissions { public static class Notifications { public static final String PROJECT_ID = "absolute-water-286821"; public static final String TOPIC_ID = "rolecall"; + public static final String SUBSCRIPTION_ID = "rolecall_notif"; + public static final String PERF_PUB_PREFIX = "Performance Published: "; } // Prevents object creation diff --git a/backend/src/main/java/com/google/rolecall/services/NotificationServices.java b/backend/src/main/java/com/google/rolecall/services/NotificationServices.java index 9dba0bff..c8fa4113 100644 --- a/backend/src/main/java/com/google/rolecall/services/NotificationServices.java +++ b/backend/src/main/java/com/google/rolecall/services/NotificationServices.java @@ -1,18 +1,69 @@ package com.google.rolecall.services; +import java.io.IOException; +import java.util.List; +import com.google.rolecall.Constants; +import com.google.rolecall.util.CPSNotification; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.sendgrid.*; + +import com.twilio.Twilio; +import com.twilio.rest.api.v2010.account.Message; -/* Utility classes for accessing Users while mantaining database consistencies. */ @Service("noticiationServices") public class NotificationServices { - + @Autowired + private org.springframework.core.env.Environment environment; + String topicId; + + public void process() throws Exception { + List notifications = this.getNotifications(); + for (CPSNotification notif : notifications) { + if (!notif.email.equals("")) { + this.sendEmail(notif.email, notif.message); + } + if (!notif.phone.equals("")) { + this.sendSMS(notif.phone, notif.message); + } + } + } + + private void sendSMS(String phoneNo, String content) { + String accountSid = this.environment.getProperty("twilio.account.sid"); + String authToken = this.environment.getProperty("twilio.auth.token"); + Twilio.init(accountSid, authToken); + Message.creator(new com.twilio.type.PhoneNumber(phoneNo), + new com.twilio.type.PhoneNumber(this.environment.getProperty("twilio.from.number")), content).create(); + } + + private void sendEmail(String email, String messsage) throws IOException { + Email from = new Email("yangzhengcn@gmail.com"); // for test accout. Jon will provide actual billable account before + // releasing + String subject = messsage; + Email to = new Email(email); + Content content = new Content("text/plain", messsage); + Mail mail = new Mail(from, subject, to, content); + SendGrid sg = new SendGrid(this.environment.getProperty("sendgrid.api.key")); + Request request = new Request(); + try { + request.setMethod(Method.POST); + request.setEndpoint("mail/send"); + request.setBody(mail.build()); + sg.api(request); + } catch (IOException ex) { + throw ex; + } + } - public void process() { - + private List getNotifications() throws Exception { + String subId = Constants.Notifications.SUBSCRIPTION_ID + "_" + this.environment.getActiveProfiles()[0]; + List notifs = CPSNotification.readMessages(subId); + return notifs; } public NotificationServices() { - } } diff --git a/backend/src/main/java/com/google/rolecall/services/PerformanceServices.java b/backend/src/main/java/com/google/rolecall/services/PerformanceServices.java index 1369b562..22fbd1ef 100644 --- a/backend/src/main/java/com/google/rolecall/services/PerformanceServices.java +++ b/backend/src/main/java/com/google/rolecall/services/PerformanceServices.java @@ -1,5 +1,6 @@ package com.google.rolecall.services; +import com.google.rolecall.Constants; import com.google.rolecall.jsonobjects.PerformanceCastInfo; import com.google.rolecall.jsonobjects.PerformanceCastMemberInfo; import com.google.rolecall.jsonobjects.PerformanceInfo; @@ -75,7 +76,7 @@ public ServiceResult editPerformance(PerformanceInfo newPerformance if (performance.getStatus().equals(Performance.Status.PUBLISHED)) { String[] profiles = this.environment.getActiveProfiles(); String profile = profiles[0]; - String message = performance.getDescription(); + String message = Constants.Notifications.PERF_PUB_PREFIX + performance.getDescription(); // get all performers List sections = newPerformance.performanceSections(); diff --git a/backend/src/main/java/com/google/rolecall/util/CPSNotification.java b/backend/src/main/java/com/google/rolecall/util/CPSNotification.java index 03f8d1e4..7381ef0d 100644 --- a/backend/src/main/java/com/google/rolecall/util/CPSNotification.java +++ b/backend/src/main/java/com/google/rolecall/util/CPSNotification.java @@ -2,22 +2,32 @@ import com.google.rolecall.Constants; import com.google.rolecall.models.User; +import com.google.pubsub.v1.AcknowledgeRequest; +import com.google.pubsub.v1.ProjectSubscriptionName; import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.PullRequest; +import com.google.pubsub.v1.PullResponse; +import com.google.pubsub.v1.ReceivedMessage; import com.google.pubsub.v1.TopicName; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import com.google.cloud.pubsub.v1.Publisher; +import com.google.cloud.pubsub.v1.stub.GrpcSubscriberStub; +import com.google.cloud.pubsub.v1.stub.SubscriberStub; +import com.google.cloud.pubsub.v1.stub.SubscriberStubSettings; import com.google.gson.Gson; import com.google.protobuf.ByteString; public class CPSNotification { - String email; - String phone; - String message; - String profile; + public String email; + public String phone; + public String message; + public String profile; public CPSNotification(User user, String message, String profile) { - - this.profile = profile; + + this.profile = profile; this.email = user.getEmail(); this.phone = user.getPhoneNumber(); this.message = message; @@ -27,14 +37,16 @@ public void send() { Publisher publisher = null; try { Gson gson = new Gson(); - TopicName topicName = TopicName.of(Constants.Notifications.PROJECT_ID, Constants.Notifications.TOPIC_ID + "_" +this.profile); + TopicName topicName = TopicName.of(Constants.Notifications.PROJECT_ID, + Constants.Notifications.TOPIC_ID + "_" + this.profile); publisher = Publisher.newBuilder(topicName).build(); - PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(gson.toJson(this))).build(); - publisher.publish(pubsubMessage); + PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(gson.toJson(this))) + .build(); + publisher.publish(pubsubMessage); } catch (Exception e) { e.printStackTrace(); } finally { - if (publisher != null) { + if (publisher != null) { publisher.shutdown(); try { publisher.awaitTermination(1, TimeUnit.MINUTES); @@ -46,4 +58,49 @@ public void send() { } + public static List readMessages(String subscriptionId) throws Exception { + List notifications = new ArrayList(); + SubscriberStub subscriber = null; + try { + + SubscriberStubSettings subscriberStubSettings = SubscriberStubSettings.newBuilder() + .setTransportChannelProvider(SubscriberStubSettings.defaultGrpcTransportProviderBuilder() + .setMaxInboundMessageSize(20 * 1024 * 1024) // 20MB (maximum message size). + .build()) + .build(); + subscriber = GrpcSubscriberStub.create(subscriberStubSettings); + + String subscriptionName = ProjectSubscriptionName.format(Constants.Notifications.PROJECT_ID, + subscriptionId); + PullRequest pullRequest = PullRequest.newBuilder().setMaxMessages(100).setSubscription(subscriptionName) + .build(); + + PullResponse pullResponse = subscriber.pullCallable().call(pullRequest); + List ackIds = new ArrayList<>(); + for (ReceivedMessage message : pullResponse.getReceivedMessagesList()) { + Gson gson = new Gson(); + CPSNotification notif = gson.fromJson(message.getMessage().getData().toStringUtf8(), + CPSNotification.class); + notifications.add(notif); + ackIds.add(message.getAckId()); + } + // Acknowledge received messages. + if(!ackIds.isEmpty()) { + AcknowledgeRequest acknowledgeRequest = AcknowledgeRequest.newBuilder().setSubscription(subscriptionName) + .addAllAckIds(ackIds).build(); + subscriber.acknowledgeCallable().call(acknowledgeRequest); + } + + } catch (Exception e) { + throw e; + } finally { + if (subscriber != null) { + // When finished with the publisher, shutdown to free up resources. + subscriber.shutdown(); + subscriber.awaitTermination(1, TimeUnit.MINUTES); + } + + } + return notifications; + } } diff --git a/backend/src/main/resources/application-dev.properties b/backend/src/main/resources/application-dev.properties index 4a5f9006..f250cacb 100644 --- a/backend/src/main/resources/application-dev.properties +++ b/backend/src/main/resources/application-dev.properties @@ -30,3 +30,7 @@ spring.jpa.hibernate.ddl-auto = update admin.first.name=System admin.last.name=Admin admin.email=admin@rolecall.com +sendgrid.api.key=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 +twilio.account.sid=ACce909d86c5a052cb475816d8ccb47a1b +twilio.auth.token=ab61c661e150c249c60130a4664dfb0b +twilio.from.number=+17752411428 \ No newline at end of file