Files
old-svijetlastrana/server/router/twilioroute/controller.go
2018-04-25 13:16:36 +02:00

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
}