364 lines
13 KiB
Go
364 lines
13 KiB
Go
package twilioroute
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
|
|
"bitbucket.org/nemt/nemt-portal-api/application/notificationservice"
|
|
"bitbucket.org/nemt/nemt-portal-api/application/tncservice"
|
|
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
|
|
"bitbucket.org/nemt/nemt-portal-api/infra/config"
|
|
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/labstack/echo"
|
|
"github.com/ttacon/libphonenumber"
|
|
)
|
|
|
|
var (
|
|
instance *controller
|
|
once sync.Once
|
|
upgrader = websocket.Upgrader{
|
|
ReadBufferSize: 1024,
|
|
WriteBufferSize: 1024,
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
return true
|
|
},
|
|
}
|
|
)
|
|
|
|
type controller struct {
|
|
cfg *config.Config
|
|
svc *applicationservice.Service
|
|
tnc *tncservice.Service
|
|
notification *notificationservice.Service
|
|
broadcast chan viewmodel.SocketMessage
|
|
clients map[*websocket.Conn]viewmodel.SocketMessage
|
|
}
|
|
|
|
func controllerInstance(cfg *config.Config, svc *applicationservice.Service, tnc *tncservice.Service, notification *notificationservice.Service) *controller {
|
|
once.Do(func() {
|
|
instance = &controller{
|
|
cfg: cfg,
|
|
svc: svc,
|
|
tnc: tnc,
|
|
notification: notification,
|
|
broadcast: make(chan viewmodel.SocketMessage),
|
|
clients: make(map[*websocket.Conn]viewmodel.SocketMessage),
|
|
}
|
|
go instance.handleMessages()
|
|
})
|
|
return instance
|
|
}
|
|
|
|
type Response struct {
|
|
Message *Message `xml:"Message,omitempty"`
|
|
}
|
|
|
|
type Message struct {
|
|
To string `xml:"to,attr"`
|
|
Body string `xml:"Body,omitempty"`
|
|
}
|
|
|
|
func (c *controller) getDriverNotification(ride viewmodel.Ride, notificationType string, subject string, message string, to string, from string) viewmodel.Notification {
|
|
retVal := viewmodel.Notification{
|
|
Type: notificationType,
|
|
Message: message,
|
|
Subject: subject,
|
|
Ride: ride,
|
|
User: ride.User,
|
|
CreatedUser: ride.CreatedUser,
|
|
To: to,
|
|
From: from,
|
|
}
|
|
|
|
return retVal
|
|
}
|
|
|
|
func (c *controller) handleTwilio(ctx echo.Context) error {
|
|
var twilioResponse viewmodel.TwilioWebhook
|
|
err := ctx.Bind(&twilioResponse)
|
|
if err != nil {
|
|
fmt.Println("Error binding response: ", err.Error())
|
|
return routeutils.HandleAPIError(ctx, err)
|
|
}
|
|
|
|
// bResp, _ := json.MarshalIndent(twilioResponse, "", "\t")
|
|
// fmt.Println("Twilio Request")
|
|
// fmt.Println(string(bResp))
|
|
|
|
var resp Response
|
|
requestMessage := strings.ToUpper(strings.TrimSpace(twilioResponse.Body))
|
|
if requestMessage != "CANCEL RIDE" && requestMessage != "YES" && requestMessage != "NO" && !strings.HasPrefix(requestMessage, "DRIVER") && !strings.HasPrefix(requestMessage, "MEMBER") && requestMessage != "I AM READY" {
|
|
lastRide, err := c.svc.Rides.GetLastRideByPhoneNumber(twilioResponse.From, "", "")
|
|
if err != nil {
|
|
fmt.Println("Error to get the ride: ", err.Error())
|
|
}
|
|
|
|
notifications := make([]viewmodel.Notification, 0)
|
|
notifications = append(notifications, c.svc.Notification.GetNotification(lastRide, "sms", "Message to Dispatcher", twilioResponse.Body, true, "message"))
|
|
notifications = append(notifications, c.svc.Notification.GetNotification(lastRide, "app", "Message to Dispatcher", twilioResponse.Body, true, "message"))
|
|
notifications, err = c.svc.Notification.SendNotifications(notifications)
|
|
if err != nil {
|
|
fmt.Println("Error to notify: ", err.Error())
|
|
}
|
|
} else {
|
|
toNumber := twilioResponse.From
|
|
|
|
var message string
|
|
var lastRide viewmodel.Ride
|
|
isDriver := false
|
|
|
|
num, _ := libphonenumber.Parse(twilioResponse.From, "US")
|
|
if strings.HasPrefix(requestMessage, "MEMBER") {
|
|
isDriver = true
|
|
lastRide, err = c.svc.Rides.GetLastRideByDriversNumber(twilioResponse.From)
|
|
if err != nil {
|
|
fmt.Println("Error to get last driver ride: ", err.Error())
|
|
message = fmt.Sprintf("We received a request to cancel a ride from you at %s, but cannot find a ride for this mobile number.", libphonenumber.Format(num, libphonenumber.NATIONAL))
|
|
}
|
|
} else {
|
|
lastRide, err = c.svc.Rides.GetLastRideByPhoneNumber(twilioResponse.From, "", "")
|
|
if err != nil {
|
|
fmt.Println("Error to get last ride: ", err.Error())
|
|
message = fmt.Sprintf("We received a request to cancel a ride from you at %s, but cannot find a ride for this mobile number.", libphonenumber.Format(num, libphonenumber.NATIONAL))
|
|
}
|
|
}
|
|
if !isDriver {
|
|
if requestMessage == "I AM READY" && lastRide.UUID != "" {
|
|
if (lastRide.Visit.TripType.Key == "roundtrip_call" && (lastRide.TripType.Key == "to_visit" || lastRide.TripType.Key == "from_visit_call")) || lastRide.Visit.TripType.Key == "from_visit_call" {
|
|
var readyRide viewmodel.Ride
|
|
authUser, err := c.svc.Users.GetByUUID(lastRide.CreatedUser.ID, "")
|
|
if err != nil {
|
|
fmt.Println("Error to get created user: ", err.Error())
|
|
}
|
|
|
|
if lastRide.TripType.Key == "to_visit" {
|
|
readyRide, err = c.svc.Rides.GetByVisitUUIDAndTripType(lastRide.Visit.UUID, "from_visit_call", authUser)
|
|
if err != nil || readyRide.UUID == "" {
|
|
message = fmt.Sprintf("We received a request to send the return ride from you at %s, but you do not have the a return ride prepared.", libphonenumber.Format(num, libphonenumber.NATIONAL))
|
|
}
|
|
} else {
|
|
readyRide = lastRide
|
|
}
|
|
|
|
switch readyRide.Status.Key {
|
|
case "pending", "accepted", "scheduled":
|
|
message = "We already request your return ride, you should receive updates soon."
|
|
case "willCall":
|
|
var lyftRide viewmodel.RideRequest
|
|
lyftRide.RideID = ""
|
|
lyftRide.Destination = readyRide.Route.Destination
|
|
lyftRide.Origin = readyRide.Route.Origin
|
|
name := readyRide.User.Name
|
|
names := strings.Split(name, " ")
|
|
lyftRide.Passenger.FirstName = names[0]
|
|
lyftRide.Passenger.LastName = " "
|
|
lyftRide.Passenger.PhoneNumber = *readyRide.User.PhoneNumber
|
|
lyftRide.RideType = "lyft"
|
|
|
|
if c.cfg.LyftProd.UserUUID != readyRide.CreatedUser.ID {
|
|
lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide)
|
|
} else {
|
|
fmt.Println("In Production")
|
|
lyftRide, err = c.tnc.LyftProd.RequestRide(lyftRide)
|
|
}
|
|
if err != nil {
|
|
fmt.Println("Error to create a lyft ride on twilio: ", err.Error())
|
|
message = "There was a problem to call your ride"
|
|
}
|
|
|
|
readyRide, err = c.svc.Rides.UpdateNewRide(readyRide, lyftRide, authUser)
|
|
if err != nil {
|
|
fmt.Println("Error to update ride: ", err.Error())
|
|
message = "There was a problem to call your ride"
|
|
}
|
|
|
|
go func() {
|
|
err = c.svc.Notification.SendNotification(readyRide.Status.Key, readyRide, lyftRide)
|
|
if err != nil {
|
|
fmt.Println("Error to notify user: ", err.Error())
|
|
}
|
|
}()
|
|
|
|
message = fmt.Sprintf("Ride requested at %s, for pickup at %s.", lyftRide.RequestAt.Format("03:04 PM"), readyRide.Route.Destination.Name)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if requestMessage == "CANCEL RIDE" && lastRide.UUID != "" {
|
|
var visitDate time.Time
|
|
if lastRide.VisitTime != nil {
|
|
visitDate = *lastRide.VisitTime
|
|
} else {
|
|
visitDate = *lastRide.VisitDate
|
|
}
|
|
|
|
var pickupDate time.Time
|
|
if lastRide.PickupTime != nil {
|
|
pickupDate = *lastRide.PickupTime
|
|
}
|
|
|
|
message = fmt.Sprintf("Are you sure you want to cancel a ride to %s visit to %s scheduled for %s?\nReply YES to confirm this cancellation", visitDate.Format("01/02/2006 03:04 PM"), lastRide.Route.Destination.Name, pickupDate.Format("03:04 PM"))
|
|
}
|
|
|
|
if requestMessage == "YES" && lastRide.UUID != "" {
|
|
var lyftRide viewmodel.RideRequest
|
|
if lastRide.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
|
|
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: lastRide.InternalID})
|
|
if err != nil {
|
|
fmt.Println("Error: ", err.Error())
|
|
message = "There was a problem to find your ride"
|
|
}
|
|
|
|
if err = c.tnc.Lyft.CancelRide(lyftRide); err != nil {
|
|
fmt.Println("Error to cancel with Lyft: ", err.Error())
|
|
message = "There was a problem to cancel your ride with our transportation provider"
|
|
}
|
|
} else {
|
|
fmt.Println("In Production")
|
|
lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: lastRide.InternalID})
|
|
if err != nil {
|
|
fmt.Println("Error: ", err.Error())
|
|
message = "There was a problem to find your ride"
|
|
}
|
|
|
|
if err = c.tnc.LyftProd.CancelRide(lyftRide); err != nil {
|
|
fmt.Println("Error to cancel with Lyft: ", err.Error())
|
|
message = "There was a problem to cancel your ride with our transportation provider"
|
|
}
|
|
}
|
|
|
|
if err = c.svc.Rides.UpdateStatus(lastRide.UUID, "canceled"); err != nil {
|
|
fmt.Println("Error to cancel the ride: ", err.Error())
|
|
message = "There was a problem to cancel your ride on our systems"
|
|
}
|
|
|
|
var dateToFormat time.Time
|
|
if lastRide.VisitTime != nil {
|
|
dateToFormat = *lastRide.VisitTime
|
|
} else {
|
|
dateToFormat = *lastRide.VisitDate
|
|
}
|
|
|
|
var pickupDate time.Time
|
|
if lastRide.PickupTime != nil {
|
|
pickupDate = *lastRide.PickupTime
|
|
}
|
|
lastRide.Status.Key = "cancelled"
|
|
message = fmt.Sprintf("A ride to a %s visit to %s scheduled for %s has been cancelled.", dateToFormat.Format("01/02/2006 03:04 PM"), lastRide.Route.Destination.Name, pickupDate.Format("03:04 PM"))
|
|
}
|
|
|
|
// if requestMessage == "NO" && lastRide.UUID != "" {
|
|
// var dateToFormat time.Time
|
|
// if lastRide.VisitTime != nil {
|
|
// dateToFormat = *lastRide.VisitTime
|
|
// } else {
|
|
// dateToFormat = *lastRide.VisitDate
|
|
// }
|
|
// message = fmt.Sprintf("You ride is still scheduled on %s at %s", lastRide.Route.Destination.Name, dateToFormat.Format("01/02/2006 03:04 PM"))
|
|
// }
|
|
|
|
if strings.HasPrefix(requestMessage, "DRIVER") && lastRide.UUID != "" {
|
|
driverMessage := strings.Replace(requestMessage, "DRIVER", "", -1)
|
|
driverMessage = strings.TrimSpace(driverMessage) + "\nReply MEMBER and your message to send a message to the member."
|
|
|
|
notifications := make([]viewmodel.Notification, 0)
|
|
notifications = append(notifications, c.getDriverNotification(lastRide, "sms", "Message to the driver", driverMessage, lastRide.Driver.PhoneNumber, twilioResponse.To))
|
|
notifications = append(notifications, c.getDriverNotification(lastRide, "sms", "Message to the driver", requestMessage, *lastRide.CreatedUser.PhoneNumber, *lastRide.User.PhoneNumber))
|
|
notifications = append(notifications, c.getDriverNotification(lastRide, "email", "Message to the driver", driverMessage, *lastRide.CreatedUser.Email, *lastRide.User.Email))
|
|
notifications, err := c.svc.Notification.SendNotifications(notifications)
|
|
if err != nil {
|
|
message = "There was a problem to send a message to the driver."
|
|
fmt.Println("Error to send drivers notification: ", err.Error())
|
|
}
|
|
|
|
message = "Your message was sent to the driver via SMS."
|
|
}
|
|
} else {
|
|
driverMessage := strings.Replace(requestMessage, "MEMBER", "", -1)
|
|
driverMessage = strings.TrimSpace(driverMessage) + "\nReply DRIVER and your message to send a message to the driver."
|
|
|
|
userPhoneNumber := ""
|
|
createUserPhoneNumber := ""
|
|
userEmail := ""
|
|
createdUserEmail := ""
|
|
if lastRide.User.PhoneNumber != nil {
|
|
userPhoneNumber = *lastRide.User.PhoneNumber
|
|
}
|
|
|
|
if lastRide.CreatedUser.PhoneNumber != nil {
|
|
createUserPhoneNumber = *lastRide.CreatedUser.PhoneNumber
|
|
}
|
|
|
|
if lastRide.User.Email != nil {
|
|
userEmail = *lastRide.User.Email
|
|
}
|
|
|
|
if lastRide.CreatedUser.Email != nil {
|
|
createdUserEmail = *lastRide.CreatedUser.Email
|
|
}
|
|
|
|
notifications := make([]viewmodel.Notification, 0)
|
|
notifications = append(notifications, c.getDriverNotification(lastRide, "sms", "Message to the member from the driver", driverMessage, userPhoneNumber, twilioResponse.To))
|
|
notifications = append(notifications, c.getDriverNotification(lastRide, "sms", "Message to the member from the driver", requestMessage, createUserPhoneNumber, userPhoneNumber))
|
|
notifications = append(notifications, c.getDriverNotification(lastRide, "email", "Message to the member from the driver", driverMessage, createdUserEmail, userEmail))
|
|
notifications, err := c.svc.Notification.SendNotifications(notifications)
|
|
if err != nil {
|
|
message = "There was a problem to send a message to the member."
|
|
fmt.Println("Error to send member notification: ", err.Error())
|
|
}
|
|
|
|
message = "Your message was sent to the member via SMS."
|
|
}
|
|
resp.Message = &Message{
|
|
To: toNumber,
|
|
Body: message,
|
|
}
|
|
}
|
|
return ctx.XML(http.StatusOK, resp)
|
|
}
|
|
|
|
func (c *controller) handleMessages() {
|
|
for {
|
|
msg := <-c.broadcast
|
|
for client := range c.clients {
|
|
if c.clients[client].From == msg.To {
|
|
err := client.WriteJSON(msg)
|
|
if err != nil {
|
|
log.Printf("error: %v", err)
|
|
client.Close()
|
|
delete(c.clients, client)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *controller) handleSocket(ctx echo.Context) error {
|
|
ws, err := upgrader.Upgrade(ctx.Response(), ctx.Request(), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer ws.Close()
|
|
|
|
c.clients[ws] = viewmodel.SocketMessage{Filled: false}
|
|
for {
|
|
var msg viewmodel.SocketMessage
|
|
err := ws.ReadJSON(&msg)
|
|
if err != nil {
|
|
log.Printf("error: %v", err)
|
|
delete(c.clients, ws)
|
|
break
|
|
}
|
|
msg.Filled = true
|
|
c.clients[ws] = msg
|
|
}
|
|
return nil
|
|
}
|