initial commit 2

This commit is contained in:
Senad Uka
2018-04-25 13:16:36 +02:00
parent c1520d169c
commit 99c10b75fb
167 changed files with 25057 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
package authenticateroute
import (
"sync"
"time"
b64 "encoding/base64"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
"bitbucket.org/nemt/nemt-portal-api/infra/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
}
func controllerInstance(cfg *config.Config, svc *applicationservice.Service) *controller {
once.Do(func() {
instance = &controller{
cfg: cfg,
svc: svc,
}
})
return instance
}
func (c *controller) handlePortal(ctx echo.Context) error {
err := auth.ValidateAppKey(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
var user viewmodel.User
if err = ctx.Bind(&user); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if user.Email == nil || len(*user.Email) == 0 || len(user.Pass) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "email and pass are required", false)
}
pass, err := b64.StdEncoding.DecodeString(user.Pass)
if err != nil {
return routeutils.ResponseAPIAuthError(ctx, "Invalid email or password", false)
}
user.Pass = string(pass)
user, err = c.svc.Users.FullLogin("email", *user.Email, user.Pass, "SP")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if user.ID == "" {
return routeutils.ResponseAPIAuthError(ctx, "Invalid email or password", false)
}
token, err := auth.GenerateToken(c.cfg, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
response := viewmodel.AuthResponse{
Token: token,
ServerTime: time.Now().Unix(),
ValidTime: time.Now().Add(auth.TokenExpiration).Unix(),
User: user,
}
return routeutils.ResponseAPIOK(ctx, response)
}
func (c *controller) handleMember(ctx echo.Context) error {
err := auth.ValidateAppKey(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
var user viewmodel.User
if err = ctx.Bind(&user); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if user.PhoneNumber == nil || len(*user.PhoneNumber) == 0 || len(user.Pass) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "phonenumber and pass are required", false)
}
pass, err := b64.StdEncoding.DecodeString(user.Pass)
if err != nil {
return routeutils.ResponseAPIAuthError(ctx, "Invalid phonenumber or password", false)
}
user.Pass = string(pass)
user, err = c.svc.Users.FullLogin("phone_number", *user.PhoneNumber, user.Pass, "US")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if user.ID == "" {
return routeutils.ResponseAPIAuthError(ctx, "Invalid phonenumber or password", false)
}
token, err := auth.GenerateToken(c.cfg, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
response := viewmodel.AuthResponse{
Token: token,
ServerTime: time.Now().Unix(),
ValidTime: time.Now().Add(auth.TokenExpiration).Unix(),
User: user,
}
return routeutils.ResponseAPIOK(ctx, response)
}

View File

@@ -0,0 +1,20 @@
package authenticateroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
portalRoute = "/portal"
memberRoute = "/member"
)
// Register authenticate route
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(cfg, svc)
r.POST(portalRoute, ctrl.handlePortal)
r.POST(memberRoute, ctrl.handleMember)
}

View File

@@ -0,0 +1,17 @@
package docsroute
import (
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = "/"
yamlRoute = "/yaml"
)
// Register registers the handlers for doc routes
func Register(r *echo.Group, cfg *config.Config) {
r.Static(yamlRoute, cfg.App.Docs.YAMLPath)
r.Static(rootRoute, cfg.App.Docs.SwaggerPath)
}

View File

@@ -0,0 +1,49 @@
package eligibilityroute
import (
"sync"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/third/eligibility/bcbsi"
"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/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
bcbsi *bcbsi.Service
}
func controllerInstance(cfg *config.Config, svc *applicationservice.Service) *controller {
once.Do(func() {
instance = &controller{
cfg: cfg,
svc: svc,
bcbsi: bcbsi.New(cfg),
}
})
return instance
}
func (c *controller) handleEligibility(ctx echo.Context) error {
var eligibility viewmodel.Eligibility
if err := ctx.Bind(&eligibility); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ret, err := c.bcbsi.BXE.CheckEligibility(eligibility)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, ret)
}

View File

@@ -0,0 +1,17 @@
package eligibilityroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
)
// Register authenticate route
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(cfg, svc)
r.POST(rootRoute, ctrl.handleEligibility)
}

View File

@@ -0,0 +1,290 @@
package externalroute
import (
"fmt"
"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/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
tnc *tncservice.Service
notification *notificationservice.Service
}
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,
}
})
return instance
}
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) handleCancel(ctx echo.Context) error {
var rideUUID = ctx.Param("ride_uuid")
var userUUID = ctx.Param("user_uuid")
ride, err := c.svc.Rides.GetByUUIDAndUserUUID(rideUUID, userUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
lyftRide := viewmodel.RideRequest{RideID: ride.InternalID}
if ride.Status.Key == "scheduled" && !strings.Contains(ride.InternalID, "s_") {
lyftRide.RideID = "s_" + ride.InternalID
}
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if err = c.tnc.Lyft.CancelRide(lyftRide); err != nil {
if err.Error() != "ride_not_found" {
fmt.Println("Error to cancel with Lyft: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
}
} else {
fmt.Println("In Production")
if err = c.tnc.LyftProd.CancelRide(lyftRide); err != nil {
fmt.Println("Error to cancel with Lyft: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
}
err = c.svc.Rides.UpdateStatus(rideUUID, "canceled")
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
// ride.Status.Key = "canceled"
// err = c.svc.Notification.SendNotification(ride.Status.Key, ride, viewmodel.RideRequest{})
// if err != nil {
// fmt.Println("Error to notify: ", err.Error())
// }
ctx.Response().Header().Set("Content-Type", "application/json")
return routeutils.ResponseNoContent(ctx, nil)
}
func (c *controller) handleMessage(ctx echo.Context) error {
var rideUUID = ctx.Param("ride_uuid")
var userUUID = ctx.Param("user_uuid")
ride, err := c.svc.Rides.GetByUUIDAndUserUUID(rideUUID, userUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
message := make(map[string]string)
err = ctx.Bind(&message)
if err != nil || message["message"] == "" {
return routeutils.ResponseAPIValidationError(ctx, "message body param is mandatory")
}
if err != nil || message["to"] == "" {
return routeutils.ResponseAPIValidationError(ctx, "to param is mandatory")
}
notifications := make([]viewmodel.Notification, 0)
if message["to"] == "driver" {
notifications = append(notifications, c.getDriverNotification(ride, "sms", "A message to driver", message["message"], ride.Driver.PhoneNumber, *ride.User.PhoneNumber))
} else {
notifications = append(notifications, c.getDriverNotification(ride, "sms", "A message to dispatcher", message["message"], *ride.CreatedUser.PhoneNumber, *ride.User.PhoneNumber))
notifications = append(notifications, c.getDriverNotification(ride, "email", "A message to dispatcher", message["message"], *ride.CreatedUser.Email, *ride.User.Email))
}
notifications, err = c.svc.Notification.SendNotifications(notifications)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, notifications)
}
func (c *controller) handle(ctx echo.Context) error {
var rideUUID = ctx.Param("ride_uuid")
var userUUID = ctx.Param("user_uuid")
ride, err := c.svc.Rides.GetByUUIDAndUserUUID(rideUUID, userUUID)
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" {
var lyftRide viewmodel.RideRequest
var err error
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
}
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
ride.Route.Location = ride.Route.Origin
ride.Route.Location.Address = ""
ride.Route.Location.Name = ""
switch ride.Status.Key {
case "accepted":
if lyftRide.Origin.ETASeconds != nil {
ride.Route.ETA = *lyftRide.Origin.ETASeconds
} else {
ride.Route.ETA = 0
if c.cfg.App.Debug {
ride.Route.ETA = 350
}
}
case "pickedUp":
case "arrived":
if lyftRide.Destination.ETASeconds != nil {
ride.Route.ETA = *lyftRide.Destination.ETASeconds
} else {
ride.Route.ETA = 0
if c.cfg.App.Debug {
ride.Route.ETA = 350
}
}
}
if lyftRide.Location.Latitude != 0 && lyftRide.Location.Longitude != 0 {
ride.Route.Location.Latitude = lyftRide.Location.Latitude
ride.Route.Location.Longitude = lyftRide.Location.Longitude
ride.Route.Location.Bearing = lyftRide.Location.Bearing
}
}
ride.InternalID = ""
ride.User.Gender = nil
ride.User.Email = nil
ride.User.PhoneNumber = nil
ride.Passenger = viewmodel.UserLyft{}
// ride.Visit = viewmodel.Visit{
// ExternalID: ride.Visit.ExternalID,
// }
ride.CreatedUser = viewmodel.User{}
return routeutils.ResponseAPIOK(ctx, ride)
}
func (c *controller) handleReady(ctx echo.Context) error {
var rideUUID = ctx.Param("ride_uuid")
var userUUID = ctx.Param("user_uuid")
ride, err := c.svc.Rides.GetByUUIDAndUserUUID(rideUUID, userUUID)
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "")
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
var nextRide viewmodel.Ride
if ride.TripType.Key == "from_visit_call" && ride.Status.Key == "willCall" {
nextRide = ride
} else if ride.Visit.TripType.Key == "roundtrip_call" && ride.TripType.Key == "to_visit" {
roundTripRide, err := c.svc.Rides.GetByVisitUUIDAndTripType(ride.Visit.UUID, "from_visit_call", authUser)
if err != nil {
fmt.Println("Error to get next ride: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
nextRide = roundTripRide
} else {
fmt.Println("Error on get next ride ")
return routeutils.ResponseAPINotFoundError(ctx)
}
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
}
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
lyftRide.RideID = ""
lyftRide.Destination = ride.Route.Destination
lyftRide.Origin = ride.Route.Origin
name := nextRide.User.Name
names := strings.Split(name, " ")
lyftRide.Passenger.FirstName = names[0]
lyftRide.Passenger.LastName = " "
lyftRide.Passenger.PhoneNumber = *nextRide.User.PhoneNumber
lyftRide.RideType = "lyft"
if c.cfg.LyftProd.UserUUID != nextRide.CreatedUser.ID {
lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide)
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.RequestRide(lyftRide)
}
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
currentTime := time.Now()
lyftRide.PickupTime = &currentTime
lyftRide.ReturnTime = &currentTime
nextRide.PickupTime = &currentTime
nextRide, err = c.svc.Rides.UpdateNewRide(nextRide, lyftRide, authUser)
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
go func() {
err = c.svc.Notification.SendNotification(lyftRide.Status, nextRide, lyftRide)
if err != nil {
fmt.Println("Error to notify user: ", err.Error())
}
}()
return routeutils.ResponseAPIOK(ctx, nextRide)
}

View File

@@ -0,0 +1,25 @@
package externalroute
import (
"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/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = "/:ride_uuid/:user_uuid"
cancelRoute = "/:ride_uuid/:user_uuid/cancel"
messageRoute = "/:ride_uuid/:user_uuid/message"
readyRoute = "/:ride_uuid/:user_uuid/ready"
)
// Register authenticate route
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service, tnc *tncservice.Service, notification *notificationservice.Service) {
ctrl := controllerInstance(cfg, svc, tnc, notification)
r.GET(rootRoute, ctrl.handle)
r.PUT(cancelRoute, ctrl.handleCancel)
r.POST(messageRoute, ctrl.handleMessage)
r.POST(readyRoute, ctrl.handleReady)
}

View File

@@ -0,0 +1,27 @@
package healthroute
import (
"net/http"
"sync"
"github.com/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
}
func controllerInstance() *controller {
once.Do(func() {
instance = &controller{}
})
return instance
}
func (c *controller) handle(ctx echo.Context) error {
return ctx.String(http.StatusOK, "OK")
}

View File

@@ -0,0 +1,15 @@
package healthroute
import (
"github.com/labstack/echo"
)
const (
rootRoute = "/"
)
func Register(r *echo.Group) {
ctrl := controllerInstance()
r.GET(rootRoute, ctrl.handle)
}

View File

@@ -0,0 +1,193 @@
package lyfthookroute
import (
"fmt"
"io/ioutil"
"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/labstack/echo"
"github.com/pquerna/ffjson/ffjson"
)
var (
instance *controller
once sync.Once
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
tnc *tncservice.Service
notification *notificationservice.Service
}
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,
}
})
return instance
}
func (c *controller) handleStateChange(ctx echo.Context) error {
var rideUUID = ctx.Param("ride_uuid")
var status = ctx.Param("status")
user, err := c.svc.Users.GetByUUID("573c70ff-733d-11e7-ba8f-0a6ad3fcdeaa", "")
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideUUID, user)
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
// var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
_, err = c.tnc.Lyft.GetRideStatus(viewmodel.RideRequest{RideID: ride.InternalID}, status)
go func() {
secondCall := func() {
ride.InternalID = "s_" + ride.InternalID
c.tnc.Lyft.GetRideStatus(viewmodel.RideRequest{RideID: ride.InternalID}, status)
}
time.AfterFunc(1*time.Second, secondCall)
}()
} else {
_, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
}
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
// if ride.Route.ETA ==
// UUID, _ := uuid.NewV4()
// hook := viewmodel.WebhookResponse{
// Event: lyftRide,
// EventID: UUID.String(),
// EventType: "ride.status.updated",
// HREF: "Test",
// OccurredAt: time.Now().UTC().Format("2006-01-02T15:04:05-0700"),
// }
// ride, err = c.svc.Rides.Update(hook)
// if err != nil {
// fmt.Println("Error: ", err.Error())
// return routeutils.HandleAPIError(ctx, err)
// }
// lyftRide.Driver.FirstName = ride.Driver.FirstName
// lyftRide.Driver.LastName = ride.Driver.LastName
// lyftRide.Status = ride.Status.Key
// lyftRide.Vehicle.Color = ride.Vehicle.Color
// lyftRide.Vehicle.LicensePlate = ride.Vehicle.LicensePlate
// lyftRide.Vehicle.Make = ride.Vehicle.Make
// lyftRide.Vehicle.Model = ride.Vehicle.Model
// lyftRide.Destination.ETASeconds = &ride.Route.ETA
// if lyftRide.Status == "canceled" {
// lyftRide.CanceledBy = "driver"
// }
// if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" {
// ride.Route.Location = ride.Route.Origin
// ride.Route.Location.Address = ""
// ride.Route.Location.Name = ""
// switch ride.Status.Key {
// case "accepted":
// if lyftRide.Origin.ETASeconds != nil {
// ride.Route.ETA = *lyftRide.Origin.ETASeconds
// } else {
// ride.Route.ETA = 0
// if c.cfg.App.Debug {
// ride.Route.ETA = 350
// }
// }
// case "pickedUp":
// case "arrived":
// if lyftRide.Destination.ETASeconds != nil {
// ride.Route.ETA = *lyftRide.Destination.ETASeconds
// } else {
// ride.Route.ETA = 0
// if c.cfg.App.Debug {
// ride.Route.ETA = 350
// }
// }
// }
// if lyftRide.Location.Latitude != 0 && lyftRide.Location.Longitude != 0 {
// ride.Route.Location.Latitude = lyftRide.Location.Latitude
// ride.Route.Location.Longitude = lyftRide.Location.Longitude
// ride.Route.Location.Bearing = lyftRide.Location.Bearing
// }
// }
// err = c.svc.Notification.SendNotification(status, ride, lyftRide)
// if err != nil {
// fmt.Println("Error: ", err.Error())
// return routeutils.HandleAPIError(ctx, err)
// }
return routeutils.ResponseAPIOK(ctx, ride)
}
func (c *controller) handle(ctx echo.Context) error {
fmt.Println("RECEIVING HOOK: ")
body, err := ioutil.ReadAll(ctx.Request().Body)
if err != nil {
fmt.Println("Error to read the body of the webhook: ", err.Error())
} else {
fmt.Println(string(body))
}
var requestRide viewmodel.WebhookResponse
err = ffjson.Unmarshal(body, &requestRide)
if err != nil {
fmt.Println(err)
return routeutils.ResponseAPIValidationError(ctx, "invalid parameters")
}
defer ctx.Request().Body.Close()
// err = ctx.Bind(&requestRide)
// if err != nil {
// fmt.Println(err)
// return routeutils.ResponseAPIValidationError(ctx, "invalid parameters")
// }
requestRide.Event.RideID = strings.Replace(requestRide.Event.RideID, "s_", "", -1)
requestRide.Event.RideID = strings.Replace(requestRide.Event.RideID, "sandboxevent-", "", -1)
ride, err := c.svc.Rides.Update(requestRide)
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
switch requestRide.EventType {
case "ride.status.updated":
go func() {
if err := c.svc.Notification.SendNotification(requestRide.Event.Status, ride, requestRide.Event); err != nil {
fmt.Println("Error: ", err.Error())
}
}()
default:
fmt.Println(requestRide.EventType, " :", requestRide.Event.Status)
}
ctx.Response().Header().Set("Content-Type", "application/json")
return routeutils.ResponseAPIOK(ctx, ride)
}

View File

@@ -0,0 +1,26 @@
package lyfthookroute
import (
"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/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
stateChangeRoute = "/:ride_uuid/:status"
)
// Register authenticate route
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service, tnc *tncservice.Service, notification *notificationservice.Service) {
ctrl := controllerInstance(cfg, svc, tnc, notification)
r.POST(rootRoute, ctrl.handle)
if cfg.App.Debug {
r.GET(stateChangeRoute, ctrl.handleStateChange)
}
}

View File

@@ -0,0 +1,156 @@
package notificationroute
import (
"fmt"
"net/http"
"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"
)
var (
instance *controller
once sync.Once
upgrader = websocket.Upgrader{}
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
tnc *tncservice.Service
notification *notificationservice.Service
}
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,
}
})
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
EnableCompression: true,
Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
fmt.Println("Error on websocket: ", status, " - ", reason)
},
}
go svc.Notification.DeliverMessage()
return instance
}
func (c *controller) handle(ctx echo.Context) error {
return routeutils.ResponseAPIOK(ctx, "OK")
}
func (c *controller) handleNotification(ctx echo.Context) error {
sRead := ctx.QueryParam("read")
isRead := (sRead == "true")
userUUID, err := routeutils.GetAndValidateStringParam(ctx, "user_uuid", "User ID is mandatory")
if err != nil {
return err
}
contactType, err := routeutils.GetAndValidateStringParam(ctx, "status", "User ID is mandatory")
if err != nil {
return err
}
n, err := c.svc.Notification.GetByUserUUIDAndReadStatus(viewmodel.User{ID: userUUID}, contactType, isRead)
if err != nil {
fmt.Println("Error to get notifications: ", err.Error())
}
return routeutils.ResponseAPIOK(ctx, n)
}
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()
userUUID := ctx.QueryParam("id")
if userUUID == "" {
fmt.Println("User not found")
}
user, err := c.svc.Users.GetByUUID(userUUID, "SP")
if err != nil {
fmt.Println("User not found on our system: ", err.Error())
}
if user.ID == "" {
user, err = c.svc.Users.GetByUUID(userUUID, "US")
if err != nil {
fmt.Println("User not found on our system: ", err.Error())
}
}
_, err = c.svc.Notification.Subscribe(user, ws)
if err != nil {
fmt.Println("Error to subscribe user: ", err.Error())
}
n, err := c.svc.Notification.GetByUserUUIDAndReadStatus(user, "app", false)
if err != nil {
fmt.Println("Error to get notifications: ", err.Error())
}
loc, _ := time.LoadLocation("America/Chicago")
for _, i := range n {
m := viewmodel.Message{
DeliveryID: user.ID,
NotificationID: i.UUID,
CreateDate: i.Created.In(loc),
Read: i.Read,
Content: viewmodel.MessageContent{
Payload: i.Ride,
Subject: i.Subject,
Content: i.Message,
Type: i.MessageType,
},
}
if err := c.svc.Notification.SendMessage(m); err != nil {
fmt.Println("Error to send message: ", err.Error())
}
}
for {
var m viewmodel.Message
if err := ws.ReadJSON(&m); err != nil {
_, ok := err.(*websocket.CloseError)
if !ok {
fmt.Println("Error to read message: ", err.Error())
}
}
if m.Content.Type == "disable-notification" {
if err := c.svc.Notification.ReadStatus(m.NotificationID, true); err != nil {
fmt.Println("Error to disable notification: ", err.Error())
}
} else {
if err := c.svc.Notification.SendMessage(m); err != nil {
fmt.Println("Error to send message: ", err.Error())
}
}
}
}

View File

@@ -0,0 +1,25 @@
package notificationroute
import (
"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/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
userNotificationRoute = "/:user_uuid/:status"
webhookRoute = "/ws"
)
// Register authenticate route
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service, tnc *tncservice.Service, notification *notificationservice.Service) {
ctrl := controllerInstance(cfg, svc, tnc, notification)
r.GET(rootRoute, ctrl.handle)
r.GET(webhookRoute, ctrl.handleSocket)
r.GET(userNotificationRoute, ctrl.handleNotification)
}

View File

@@ -0,0 +1,304 @@
package organizationroute
import (
"sync"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
"bitbucket.org/nemt/nemt-portal-api/domain"
"bitbucket.org/nemt/nemt-portal-api/infra/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/cache"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
}
func controllerInstance(cfg *config.Config, svc *applicationservice.Service) *controller {
once.Do(func() {
instance = &controller{
cfg: cfg,
svc: svc,
}
})
return instance
}
func (c *controller) handleTypes(ctx echo.Context) error {
cache := cache.Instance(c.cfg)
cacheKey := ctx.Request().Method + ctx.Request().URL.EscapedPath() + ctx.Request().URL.RawQuery
resp := []viewmodel.OrganizationType{}
err := cache.GetStruct(cacheKey, &resp)
if err != nil {
if err != domain.ErrCacheMiss {
ctx.Logger().Errorf(domain.LogProblemGettingFromCache, err)
}
resp, err = c.svc.Organization.GetAllTypes()
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
cache.SetStruct(cacheKey, resp)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleAddOrganization(ctx echo.Context) error {
var org viewmodel.Organization
err := ctx.Bind(&org)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
org.Author.ID = authUser.ID
org.LastEditor.ID = authUser.ID
resp, err := c.svc.Organization.AddOrganization(org, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handle(ctx echo.Context) error {
orgType, _ := routeutils.GetAndValidateStringQueryParam(ctx, "type", "Type is mandatory")
resp, err := c.svc.Organization.GetByType(orgType)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleDetail(ctx echo.Context) error {
orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Organization.GetByUUID(orgUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleParent(ctx echo.Context) error {
var parent viewmodel.Organization
err := ctx.Bind(&parent)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Organization.SetParentOrganization(orgUUID, parent.UUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleChild(ctx echo.Context) error {
var child viewmodel.Organization
err := ctx.Bind(&child)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
_, err = c.svc.Organization.SetParentOrganization(child.UUID, orgUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Organization.GetByUUID(orgUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleNameSearch(ctx echo.Context) error {
name, err := routeutils.GetAndValidateStringQueryParam(ctx, "name", "Name is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
searchType := ""
searchType, _ = routeutils.GetAndValidateStringQueryParam(ctx, "type", "Type is mandatory")
cache := cache.Instance(c.cfg)
cacheKey := ctx.Request().Method + ctx.Request().URL.RawPath + ctx.Request().URL.RawQuery
resp := []viewmodel.Organization{}
err = cache.GetStruct(cacheKey, &resp)
if err != nil {
if err != domain.ErrCacheMiss {
ctx.Logger().Errorf(domain.LogProblemGettingFromCache, err)
}
resp, err = c.svc.Organization.GetByName(name, searchType)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
cache.SetStruct(cacheKey, resp)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleRemoveAddress(ctx echo.Context) error {
var address viewmodel.OrganizationAddress
err := ctx.Bind(&address)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
uInt, err := auth.GetTokenDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
createdUser := uInt.(map[string]interface{})
address.UpdatedUser.ID = createdUser["useruuid"].(string)
err = c.svc.Organization.InactivateOrganizationAddress(orgUUID, address)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Organization.GetByUUID(orgUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleAddAddress(ctx echo.Context) error {
var address viewmodel.OrganizationAddress
err := ctx.Bind(&address)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
uInt, err := auth.GetTokenDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
createdUser := uInt.(map[string]interface{})
address.CreatedUser.ID = createdUser["useruuid"].(string)
address.UpdatedUser.ID = address.CreatedUser.ID
_, err = c.svc.Organization.SetOrganizationAddress(orgUUID, address)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Organization.GetByUUID(orgUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleRemoveContact(ctx echo.Context) error {
var contact viewmodel.OrganizationContact
err := ctx.Bind(&contact)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
uInt, err := auth.GetTokenDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
createdUser := uInt.(map[string]interface{})
contact.UpdatedUser.ID = createdUser["useruuid"].(string)
err = c.svc.Organization.InactivateOrganizationContact(orgUUID, contact)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Organization.GetByUUID(orgUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleAddContact(ctx echo.Context) error {
var contact viewmodel.OrganizationContact
err := ctx.Bind(&contact)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
uInt, err := auth.GetTokenDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
createdUser := uInt.(map[string]interface{})
contact.CreatedUser.ID = createdUser["useruuid"].(string)
contact.UpdatedUser.ID = contact.CreatedUser.ID
_, err = c.svc.Organization.SetOrganizationContact(orgUUID, contact)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Organization.GetByUUID(orgUUID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}

View File

@@ -0,0 +1,39 @@
package organizationroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
detailRoute = "/:org_uuid"
parentRoute = "/:org_uuid/parent"
childRoute = "/:org_uuid/child"
addressRoute = "/:org_uuid/address"
contactRoute = "/:org_uuid/contact"
typeRoute = "/type"
nameRoute = "/name"
)
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(cfg, svc)
r.GET(rootRoute, ctrl.handle)
r.POST(rootRoute, ctrl.handleAddOrganization)
r.GET(detailRoute, ctrl.handleDetail)
r.GET(typeRoute, ctrl.handleTypes)
r.GET(nameRoute, ctrl.handleNameSearch)
r.POST(parentRoute, ctrl.handleParent)
r.POST(childRoute, ctrl.handleChild)
r.POST(addressRoute, ctrl.handleAddAddress)
r.PUT(addressRoute, ctrl.handleRemoveAddress)
r.POST(contactRoute, ctrl.handleAddContact)
r.PUT(contactRoute, ctrl.handleRemoveContact)
}

View File

@@ -0,0 +1,55 @@
package placesroute
import (
"sync"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
"golang.org/x/net/context"
"googlemaps.github.io/maps"
)
var (
instance *controller
once sync.Once
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
maps *maps.Client
}
func controllerInstance(cfg *config.Config, svc *applicationservice.Service) *controller {
once.Do(func() {
c, _ := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI="))
instance = &controller{
cfg: cfg,
svc: svc,
maps: c,
}
})
return instance
}
func (c *controller) handle(ctx echo.Context) error {
name, err := routeutils.GetAndValidateStringQueryParam(ctx, "name", "name is mandatory")
if err != nil {
return err
}
req := maps.PlaceAutocompleteRequest{
Input: name,
Language: "en",
}
resp, err := c.maps.PlaceAutocomplete(context.Background(), &req)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}

View File

@@ -0,0 +1,17 @@
package placesroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
)
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(cfg, svc)
r.GET(rootRoute, ctrl.handle)
}

View File

@@ -0,0 +1,103 @@
package profileroute
import (
"sync"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
"bitbucket.org/nemt/nemt-portal-api/domain"
"bitbucket.org/nemt/nemt-portal-api/infra/cache"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
}
func controllerInstance(cfg *config.Config, svc *applicationservice.Service) *controller {
once.Do(func() {
instance = &controller{
cfg: cfg,
svc: svc,
}
})
return instance
}
func (c *controller) handle(ctx echo.Context) error {
cache := cache.Instance(c.cfg)
cacheKey := ctx.Request().Method + ctx.Request().URL.EscapedPath() + ctx.Request().URL.RawQuery
resp := []viewmodel.Profile{}
err := cache.GetStruct(cacheKey, &resp)
if err != nil {
if err != domain.ErrCacheMiss {
ctx.Logger().Errorf(domain.LogProblemGettingFromCache, err)
}
resp, err = c.svc.Profile.GetVisibles(true)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
cache.SetStruct(cacheKey, resp)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleKey(ctx echo.Context) error {
key, err := routeutils.GetAndValidateStringParam(ctx, "key", "Key is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
cache := cache.Instance(c.cfg)
cacheKey := ctx.Request().Method + ctx.Request().URL.EscapedPath() + ctx.Request().URL.RawQuery
resp := viewmodel.Profile{}
err = cache.GetStruct(cacheKey, &resp)
if err != nil {
if err != domain.ErrCacheMiss {
ctx.Logger().Errorf(domain.LogProblemGettingFromCache, err)
}
resp, err = c.svc.Profile.GetByKey(key)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
cache.SetStruct(cacheKey, resp)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleOrg(ctx echo.Context) error {
orgType, err := routeutils.GetAndValidateStringParam(ctx, "org_key", "Org is mandatory")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
cache := cache.Instance(c.cfg)
cacheKey := ctx.Request().Method + ctx.Request().URL.EscapedPath() + ctx.Request().URL.RawQuery
resp := []viewmodel.Profile{}
err = cache.GetStruct(cacheKey, &resp)
if err != nil {
if err != domain.ErrCacheMiss {
ctx.Logger().Errorf(domain.LogProblemGettingFromCache, err)
}
resp, err = c.svc.Profile.GetByOrganizationType(orgType)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
cache.SetStruct(cacheKey, resp)
}
return routeutils.ResponseAPIOK(ctx, resp)
}

View File

@@ -0,0 +1,21 @@
package profileroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
keyRoute = "/:key"
orgRoute = "/org/:org_key"
)
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(cfg, svc)
r.GET(rootRoute, ctrl.handle)
r.GET(keyRoute, ctrl.handleKey)
r.GET(orgRoute, ctrl.handleOrg)
}

View File

@@ -0,0 +1,178 @@
package providerroute
import (
"sync"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/third/npd"
"bitbucket.org/nemt/nemt-portal-api/application/third/npd/npdmodel"
"bitbucket.org/nemt/nemt-portal-api/infra/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
npd *npd.Service
}
func controllerInstance(cfg *config.Config, svc *applicationservice.Service) *controller {
once.Do(func() {
instance = &controller{
cfg: cfg,
svc: svc,
npd: npd.New(cfg),
}
})
return instance
}
func (c *controller) handle(ctx echo.Context) error {
var providers []npdmodel.ProviderResponse
if err := ctx.Bind(&providers); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
entities, err := c.svc.Provider.Save(providers, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, entities)
}
func (c *controller) handleParticipating(ctx echo.Context) error {
query := ctx.QueryParam("query")
var lat float64
var long float64
var distance int64
distance = 10
planCode := ""
productID := ""
mukID := ""
providerID := ""
sort := "distance"
lat, err := routeutils.GetAndValidateFloatQueryParam(ctx, "lat", "latitude is mandatory")
if err != nil {
lat = 0
}
long, err = routeutils.GetAndValidateFloatQueryParam(ctx, "long", "longitude is mandatory")
if err != nil {
long = 0
}
planCode, _ = routeutils.GetAndValidateStringQueryParam(ctx, "plan_code", "Plan Code is mandatory")
productID, _ = routeutils.GetAndValidateStringQueryParam(ctx, "product_id", "Product ID is mandatory")
providerID, _ = routeutils.GetAndValidateStringQueryParam(ctx, "provider_id", "Provider ID is mandatory")
distance, err = routeutils.GetAndValidateIntQueryParam(ctx, "distance", "distance is mandatory")
if err != nil {
distance = 1000000
}
sort, err = routeutils.GetAndValidateStringQueryParam(ctx, "sort", "sort is mandatory")
if err != nil || (sort != "distance" && sort != "name") {
sort = "distance"
}
mukID, _ = routeutils.GetAndValidateStringQueryParam(ctx, "muk_id", "Muk ID is mandatory")
authUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
// if (lat != 0 && long == 0) || (lat == 0 && long != 0) {
// return routeutils.ResponseAPIFieldValidationError(ctx, "coordinates", "Invalid coordinates")
// } else {
// lat = 41.886406
// long = -87.624225
// }
lat = 40.442875
long = -80.003112
if len(mukID) > 0 {
query = ""
providerID = ""
}
providers, err := c.svc.Provider.Get(query, lat, long, distance, planCode, productID, mukID, providerID, sort, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, providers)
}
func (c *controller) handleList(ctx echo.Context) error {
name := ctx.QueryParam("name")
searchBy := ctx.QueryParam("searchBy")
// lat, err := routeutils.GetAndValidateFloatQueryParam(ctx, "lat", "latitude is mandatory")
// if err != nil {
// return err
// }
// long, err := routeutils.GetAndValidateFloatQueryParam(ctx, "long", "longitude is mandatory")
// if err != nil {
// return err
// }
distance, err := routeutils.GetAndValidateIntQueryParam(ctx, "distance", "distance is mandatory")
if err != nil {
distance = 10
}
limit, err := routeutils.GetAndValidateIntQueryParam(ctx, "limit", "limit is mandatory")
if err != nil {
limit = 50
}
sortBy, err := routeutils.GetAndValidateStringQueryParam(ctx, "sortby", "limit is mandatory")
if err != nil || (sortBy != "distance" && sortBy != "name") {
sortBy = "distance"
}
providerParams := npdmodel.ProviderSearchParams{
Name: name,
SearchBy: searchBy,
Latitude: 40.442875,
Longitude: -80.003112,
Distance: distance,
Limit: limit,
Offset: 0,
SortBy: sortBy,
FacetAcceptNewPatients: "Y",
FacetExtendedOfficeHours: "Y",
FacetGender: "Y",
FacetHospitalAffiliations: "Y",
FacetLanguage: "Y",
FacetOfficeLanguages: "Y",
FacetProviderAffiliations: "Y",
FacetProviderEntityName: "Y",
FacetSummaryScore: "Y",
}
providers, err := c.npd.Provider.GetProviders(providerParams)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, providers)
}

View File

@@ -0,0 +1,20 @@
package providerroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
participatingroute = "/participating"
)
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(cfg, svc)
r.GET(rootRoute, ctrl.handleList)
r.GET(participatingroute, ctrl.handleParticipating)
r.POST(rootRoute, ctrl.handle)
}

50
server/router/router.go Normal file
View File

@@ -0,0 +1,50 @@
package router
import (
"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/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/authenticateroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/docsroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/eligibilityroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/externalroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/healthroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/lyfthookroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/notificationroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/organizationroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/placesroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/profileroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/providerroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/tncroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/twilioroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/usersroute"
"bitbucket.org/nemt/nemt-portal-api/server/router/visitroute"
"github.com/labstack/echo"
)
// Register registers the API routes
func Register(e *echo.Echo, cfg *config.Config, svc *applicationservice.Service, tnc *tncservice.Service, notification *notificationservice.Service) {
healthroute.Register(e.Group("/health"))
prefixGroup := e.Group(cfg.HTTP.Prefix)
if cfg.App.Debug {
docsroute.Register(prefixGroup.Group("/docs"), cfg)
}
notificationroute.Register(prefixGroup.Group("/notification"), cfg, svc, tnc, notification)
twilioroute.Register(prefixGroup.Group("/twilio"), cfg, svc, tnc, notification)
lyfthookroute.Register(prefixGroup.Group("/lyfthook"), cfg, svc, tnc, notification)
externalroute.Register(prefixGroup.Group("/ext"), cfg, svc, tnc, notification)
authenticateroute.Register(prefixGroup.Group("/authenticate"), cfg, svc)
appGroup := prefixGroup.Group("/" + cfg.App.Name)
usersroute.Register(appGroup.Group("/users"), cfg, svc)
eligibilityroute.Register(appGroup.Group("/eligibility"), cfg, svc)
tncroute.Register(appGroup.Group("/rides"), cfg, svc, tnc, notification)
visitroute.Register(appGroup.Group("/visits"), cfg, svc)
providerroute.Register(appGroup.Group("/provider"), cfg, svc)
placesroute.Register(appGroup.Group("/places"), cfg, svc)
profileroute.Register(appGroup.Group("/profile"), cfg, svc)
organizationroute.Register(appGroup.Group("/organization"), cfg, svc)
}

View File

@@ -0,0 +1,130 @@
package routeutils
import (
"strconv"
"strings"
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
"github.com/labstack/echo"
)
// GetAndValidateIntParam gets the integer param value and validates it, returning a validation error in case it's invalid
func GetAndValidateIntParam(c echo.Context, paramName string, errorMessage string) (ID int64, err error) {
IDstr := c.Param(paramName)
if strings.TrimSpace(IDstr) == "" {
err = errors.NewValidationError(paramName, errorMessage)
}
ID, err = strconv.ParseInt(IDstr, 10, 64)
if ID == 0 {
err = errors.NewValidationError(paramName, errorMessage)
}
return
}
// GetAndValidateIntQueryParam gets the integer param value and validates it, returning a validation error in case it's invalid
func GetAndValidateIntQueryParam(c echo.Context, paramName string, errorMessage string) (ID int64, err error) {
IDstr := c.QueryParam(paramName)
if strings.TrimSpace(IDstr) == "" {
err = errors.NewValidationError(paramName, errorMessage)
}
ID, err = strconv.ParseInt(IDstr, 10, 64)
if ID == 0 {
err = errors.NewValidationError(paramName, errorMessage)
}
return
}
// GetAndValidateFloatParam gets the integer param value and validates it, returning a validation error in case it's invalid
func GetAndValidateFloatQueryParam(c echo.Context, paramName string, errorMessage string) (ID float64, err error) {
IDstr := c.QueryParam(paramName)
if strings.TrimSpace(IDstr) == "" {
err = errors.NewValidationError(paramName, errorMessage)
}
ID, err = strconv.ParseFloat(IDstr, 64)
if ID == 0 {
err = errors.NewValidationError(paramName, errorMessage)
}
return
}
// GetAndValidateStringQueryParam gets the integer param value and validates it, returning a validation error in case it's invalid
func GetAndValidateStringQueryParam(c echo.Context, paramName string, errorMessage string) (ID string, err error) {
ID = c.QueryParam(paramName)
if strings.TrimSpace(ID) == "" {
err = errors.NewValidationError(paramName, errorMessage)
}
return
}
// GetAndValidateStringParam gets the string param value and validates it, returning a validation error in case it's invalid
func GetAndValidateStringParam(c echo.Context, paramName string, errorMessage string) (ID string, err error) {
ID = c.Param(paramName)
if strings.TrimSpace(ID) == "" {
err = errors.NewValidationError(paramName, errorMessage)
}
return
}
// GetPagingParams gets the standard paging params from the URL, returning a validation error in case it's invalid
func GetPagingParams(c echo.Context) (page int64, quantity int64) {
pg := c.QueryParam("page")
ipp := c.QueryParam("quantity")
page, _ = strconv.ParseInt(pg, 10, 64)
quantity, _ = strconv.ParseInt(ipp, 10, 64)
if page < 1 {
page = 1
}
if quantity < 1 || quantity > 1000 {
quantity = 10
}
return
}
// GetMultipleQueryParam returns a list of values for multiple query params
func GetMultipleQueryParam(c echo.Context, paramName string) (retVal []string) {
retVal = make([]string, 0)
for k, param := range map[string][]string(c.QueryParams()) {
if k == paramName {
for _, value := range param {
retVal = append(retVal, value)
}
break
}
}
return retVal
}
// GetMultipleIntQueryParam returns a list of integer values from multiple query params
func GetMultipleIntQueryParam(c echo.Context, paramName string) (retVal []int64, err error) {
retVal = make([]int64, 0)
for _, value := range GetMultipleQueryParam(c, paramName) {
intValue, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return retVal, errors.Wrap(err)
}
retVal = append(retVal, intValue)
}
return retVal, nil
}

View File

@@ -0,0 +1,204 @@
package routeutils
import (
"database/sql"
"fmt"
"net"
"net/http"
"os/exec"
"reflect"
"strings"
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/go-sql-driver/mysql"
"github.com/labstack/echo"
)
// resultWrapper has fields for standard message responses
type resultWrapper struct {
Error bool `json:"error,omitempty"`
Message string `json:"message,omitempty"`
Redirect bool `json:"redirect,omitempty"`
Data interface{} `json:"data,omitempty"`
}
// ResponseNoContent returns a standard API success with no content response
func ResponseNoContent(c echo.Context, data interface{}) error {
return c.NoContent(http.StatusNoContent)
}
// ResponseAPIOK returns a standard API success response
func ResponseAPIOK(c echo.Context, data interface{}) error {
return c.JSON(http.StatusOK, data)
}
// ResponseAPIError returns a standard API error to the response
func ResponseAPIError(c echo.Context, status int, message string, redirect bool) error {
returnValue := resultWrapper{
Error: true,
Message: message,
Redirect: redirect,
}
return c.JSON(status, returnValue)
}
// ResponseAPIAuthError returns a standard API auth error to the response
func ResponseAPIAuthError(c echo.Context, message string, redirect bool) error {
return ResponseAPIError(c, http.StatusUnauthorized, message, redirect)
}
// ResponseAPIServiceError returns a standard API service unavailable error to the response
func ResponseAPIServiceError(c echo.Context, message string) error {
return ResponseAPIError(c, http.StatusServiceUnavailable, message, false)
}
// ResponseAPIValidationError returns a standard API validation error to the response
func ResponseAPIValidationError(c echo.Context, message string) error {
return ResponseAPIError(c, http.StatusUnprocessableEntity, message, false)
}
// ResponseAPIFieldValidationError returns a standard API field validation error to the response
func ResponseAPIFieldValidationError(c echo.Context, field string, message string) error {
err := errors.NewValidationError(field, message)
return HandleAPIError(c, err)
}
// ResponseAPINotFoundError returns a standard API not found error to the response
func ResponseAPINotFoundError(c echo.Context) error {
return ResponseAPIError(c, http.StatusNotFound, "Not Found", false)
}
func ignoreDefaultWrappedErrors(c echo.Context, errorToHandle *errors.WrappedError, handler func(echo.Context, error) error) error {
err := errorToHandle.GetOriginalError()
if err != nil {
switch e := (*err).(type) {
case *errors.ValidationError:
case *errors.NotAuthorizedError:
case *errors.HTTPError:
case awserr.Error:
default:
if e != sql.ErrNoRows {
c.Logger().Errorf("%s", e.Error())
}
}
return handler(c, *err)
}
return nil
}
// HandleAPIError applies the default error handling to the response
func HandleAPIError(c echo.Context, errorToHandle error) (err error) {
statusCode := http.StatusServiceUnavailable
errorMessage := "Service Unavailable"
if errorToHandle != nil {
logger := c.Logger()
errorString := errorToHandle.Error()
switch e := errorToHandle.(type) {
case *errors.WrappedError:
return ignoreDefaultWrappedErrors(c, e, HandleAPIError)
case *errors.ApplicationError:
logger.Errorf("Application Error: %s - %v", e.Path, e.Message)
case *errors.ValidationError:
return ResponseAPIValidationError(c, errorString)
case *errors.NotAuthorizedError:
statusCode = http.StatusUnauthorized
errorMessage = errorString
case *errors.HTTPError:
statusCode = e.Status
errorMessage = e.Message
case *mysql.MySQLError:
logger.Errorf("MySQL Error: %v - %v", e.Number, e.Message)
errorMessage = "Service temporarily unavailable"
case net.Error:
logger.Errorf("Network/Connection error: %v", e.Error())
errorMessage = "Service temporarily unavailable"
case *echo.HTTPError:
statusCode = e.Code
errorMessage = fmt.Sprint(e.Message)
case *exec.ExitError:
logger.Errorf("Exec Error: %s - %v", e.ProcessState, e.Error())
default:
if e == sql.ErrNoRows {
logger.Debugf("SQL Error: %v", e.Error())
return ResponseAPINotFoundError(c)
} else if e == sql.ErrTxDone || strings.HasPrefix(errorString, "sql: ") {
logger.Errorf("SQL Error: %v", e.Error())
return ResponseAPIServiceError(c, "Service temporarily unavailable")
}
logger.Errorf("%v: %v", reflect.TypeOf(e), e.Error())
}
}
return ResponseAPIError(c, statusCode, errorMessage, false)
}
// HandleHTMLError applies the default error handling to the response
func HandleHTMLError(c echo.Context, errorToHandle error) error {
statusCode := http.StatusServiceUnavailable
errorMessage := "Service Unavailable"
if errorToHandle != nil {
logger := c.Logger()
errorString := errorToHandle.Error()
switch e := errorToHandle.(type) {
case *errors.WrappedError:
return ignoreDefaultWrappedErrors(c, e, HandleHTMLError)
case *errors.ApplicationError:
logger.Errorf("Application Error: %s - %v", e.Path, e.Message)
case *errors.ValidationError:
return ResponseAPIValidationError(c, errorString)
case *errors.NotAuthorizedError:
return ResponseAPIError(c, http.StatusUnauthorized, errorString, false)
case *errors.HTTPError:
errorMessage = e.Message
case *mysql.MySQLError:
logger.Errorf("MySQL Error: %v - %v", e.Number, e.Message)
errorMessage = "Service temporarily unavailable"
case net.Error:
logger.Errorf("Network/Connection error: %v", e.Error())
errorMessage = "Service temporarily unavailable"
case *echo.HTTPError:
logger.Errorf("%v: %v - %v", reflect.TypeOf(e), e.Code, e.Message)
statusCode = e.Code
default:
if e == sql.ErrNoRows {
logger.Debugf("SQL Error: %v", e.Error())
statusCode = http.StatusNotFound
errorMessage = "Not Found"
} else if e == sql.ErrTxDone || strings.HasPrefix(errorString, "sql: ") {
logger.Errorf("SQL Error: %v", e.Error())
errorMessage = "Service temporarily unavailable"
} else {
logger.Errorf("%v: %v", reflect.TypeOf(e), e.Error())
}
}
}
return c.HTML(statusCode, errorMessage)
}

View File

@@ -0,0 +1,872 @@
package tncroute
import (
"fmt"
"net/http"
"strconv"
"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/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
uuid "github.com/satori/go.uuid"
"google.golang.org/api/googleapi/transport"
urlshortener "google.golang.org/api/urlshortener/v1"
"github.com/gorilla/websocket"
)
var (
instance *controller
once sync.Once
upgrader = websocket.Upgrader{}
)
type controller struct {
cfg *config.Config
svc *applicationservice.Service
tnc *tncservice.Service
notification *notificationservice.Service
}
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,
}
})
return instance
}
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()
for {
// Write
err := ws.WriteMessage(websocket.TextMessage, []byte("Hello, Client!"))
if err != nil {
ctx.Logger().Error(err)
}
// Read
_, msg, err := ws.ReadMessage()
if err != nil {
ctx.Logger().Error(err)
}
fmt.Printf("%s\n", msg)
}
}
func (c *controller) handleMessage(ctx echo.Context) error {
rideID := ctx.Param("ride_uuid")
if rideID == "" {
return routeutils.ResponseAPIValidationError(ctx, "rideID param is mandatory")
}
message := make(map[string]string)
err := ctx.Bind(&message)
if err != nil || message["message"] == "" {
return routeutils.ResponseAPIValidationError(ctx, "message body param is mandatory")
}
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideID, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
notifications := make([]viewmodel.Notification, 0)
notifications = append(notifications, c.svc.Notification.GetNotification(ride, "sms", "A message to member", message["message"], false, "message"))
notifications = append(notifications, c.svc.Notification.GetNotification(ride, "app", "A message to member", message["message"], false, "message"))
notifications, err = c.svc.Notification.SendNotifications(notifications)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, notifications)
}
func (c *controller) getURLShortened(ride viewmodel.Ride) (string, error) {
const (
url = "https://portal.bcbsinstitute.com/#/ride/%s/%s"
)
svc, err := urlshortener.New(&http.Client{
Transport: &transport.APIKey{Key: c.cfg.GoogleShortener.APIKey},
})
if err != nil {
return "", err
}
userURL := fmt.Sprintf(url, ride.UUID, ride.User.ID)
shortURL, err := svc.Url.Insert(&urlshortener.Url{
Kind: "urlshortener#url", // Not really needed
LongUrl: userURL,
}).Do()
if err != nil {
return "", err
}
return shortURL.Id, nil
}
func (c *controller) handleShare(ctx echo.Context) error {
const (
message = "%s shared a link with you for an upcoming ride: %s"
)
rideID := ctx.Param("ride_uuid")
if rideID == "" {
return routeutils.ResponseAPIValidationError(ctx, "rideID param is mandatory")
}
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideID, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
url, err := c.getURLShortened(ride)
if err != nil {
fmt.Println("Error to short url: ", err)
}
notifications := make([]viewmodel.Notification, 0)
notifications = append(notifications, c.svc.Notification.GetNotification(ride, "sms", "A ride was shared with you", fmt.Sprintf(message, ride.CreatedUser.Name, url), false, "message"))
notifications = append(notifications, c.svc.Notification.GetNotification(ride, "email", "A ride was shared with you", fmt.Sprintf(message, ride.CreatedUser.Name, url), false, "message"))
notifications = append(notifications, c.svc.Notification.GetNotification(ride, "app", "A ride was shared with you", fmt.Sprintf(message, ride.CreatedUser.Name, url), false, "message"))
notifications, err = c.svc.Notification.SendNotifications(notifications)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, notifications)
}
func (c *controller) handleMessageDriver(ctx echo.Context) error {
rideID := ctx.Param("ride_uuid")
if rideID == "" {
return routeutils.ResponseAPIValidationError(ctx, "rideID param is mandatory")
}
message := make(map[string]string)
err := ctx.Bind(&message)
if err != nil || message["message"] == "" {
return routeutils.ResponseAPIValidationError(ctx, "message body param is mandatory")
}
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideID, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
notifications := make([]viewmodel.Notification, 0)
if ride.Status.Key == "accepted" || ride.Status.Key == "arrived" || ride.Status.Key == "pickedUp" {
notifications = append(notifications, c.getDriverNotification(ride, "sms", "A message to driver", message["message"], ride.Driver.PhoneNumber, *ride.CreatedUser.PhoneNumber))
notifications, err = c.svc.Notification.SendNotifications(notifications)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
}
return routeutils.ResponseAPIOK(ctx, notifications)
}
func (c *controller) handleList(ctx echo.Context) error {
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
entity, err := c.svc.Rides.GetAll(user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ctx.Response().Header().Set("Content-Type", "application/json")
return routeutils.ResponseAPIOK(ctx, entity)
}
func (c *controller) handle(ctx echo.Context) error {
var requestRide viewmodel.RideRequest
err := ctx.Bind(&requestRide)
if err != nil {
fmt.Println(err)
return routeutils.ResponseAPIValidationError(ctx, "invalid parameters")
}
user, err := c.svc.Users.GetByUUID(requestRide.UserUUID, "US")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if user.ID == "" {
return routeutils.ResponseAPIValidationError(ctx, "User not found")
}
createdUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
name := user.Name
names := strings.Split(name, " ")
requestRide.Passenger.FirstName = names[0]
requestRide.Passenger.LastName = " "
requestRide.Passenger.PhoneNumber = *user.PhoneNumber
var resp viewmodel.RideRequest
if requestRide.TripType.Key != "from_visit_call" {
if requestRide.TripType.Key == "from_visit" {
newOrigin := requestRide.Origin
requestRide.Origin = requestRide.Destination
requestRide.Destination = newOrigin
}
if c.cfg.LyftProd.UserUUID != createdUser.ID {
resp, err = c.tnc.Lyft.RequestRide(requestRide)
} else {
fmt.Println("In Production")
resp, err = c.tnc.LyftProd.RequestRide(requestRide)
}
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if requestRide.TripType.Key == "from_visit" {
newOrigin := resp.Origin
resp.Origin = resp.Destination
resp.Destination = newOrigin
}
resp.RideID = strings.Replace(resp.RideID, "s_", "", -1)
} else {
resp = requestRide
UUID, _ := uuid.NewV4()
resp.RideID = UUID.String()
}
if resp.Status == "scheduled" || requestRide.TripType.Key == "from_visit_call" {
if requestRide.PickupTime == nil {
currentDate := time.Now()
requestRide.PickupTime = &currentDate
}
requestMS := (requestRide.PickupTime.UnixNano() / int64(time.Millisecond))
generateDate := time.Now()
generateMS := (generateDate.UnixNano() / int64(time.Millisecond))
resp.RequestAt = requestRide.PickupTime
resp.RequestAtMS = &requestMS
resp.GeneratedAt = &generateDate
resp.GeneratedAtMS = &generateMS
}
resp.Passenger.FirstName = names[0]
resp.Passenger.LastName = names[len(names)-1]
resp.Passenger.PhoneNumber = *user.PhoneNumber
if resp.Passenger.ImageURL == nil {
imageURL := " "
resp.Passenger.ImageURL = &imageURL
}
resp.UserUUID = requestRide.UserUUID
if requestRide.TripType.Key == "from_visit" {
resp.Origin.Name = requestRide.Destination.Name
resp.Origin.ID = requestRide.Destination.ID
resp.Destination.Name = requestRide.Origin.Name
resp.Destination.ID = requestRide.Origin.ID
} else {
resp.Origin.Name = requestRide.Origin.Name
resp.Origin.ID = requestRide.Origin.ID
resp.Destination.Name = requestRide.Destination.Name
resp.Destination.ID = requestRide.Destination.ID
}
resp.Distance = requestRide.Distance
resp.Duration = requestRide.Duration
resp.ETA = requestRide.ETA
resp.PickupTime = requestRide.PickupTime
resp.VisitDate = requestRide.VisitDate
resp.VisitTime = requestRide.VisitTime
resp.VisitExternalID = requestRide.VisitExternalID
resp.CreateUserUUID = createdUser.ID
resp.Visit.TripType = requestRide.TripType
if resp.TripType.Key == "from_visit_call" {
resp.Status = "willCall"
resp.Passenger.UserID = &resp.UserUUID
}
resp.TripType.Key = requestRide.TripType.Key
if requestRide.TripType.Key == "roundtrip" || requestRide.TripType.Key == "roundtrip_call" {
resp.TripType.Key = "to_visit"
} else if requestRide.TripType.Key == "from_visit_call" {
resp.TripType.Key = "from_visit_call"
}
entity, err := c.svc.Rides.Save(resp)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
go func() {
err = c.svc.Notification.SendNotification(resp.Status, entity, resp)
if err != nil {
fmt.Println("Error to notify user: ", err.Error())
}
}()
if requestRide.TripType.Key == "roundtrip" || requestRide.TripType.Key == "roundtrip_call" {
newRide := requestRide
if requestRide.TripType.Key == "roundtrip" {
destination := newRide.Origin
newRide.Origin = newRide.Destination
newRide.Destination = destination
if requestRide.ReturnTime != nil {
newRide.PickupTime = requestRide.ReturnTime
}
scheduledRide := make(map[string]interface{})
scheduledRide["timestamp_ms"] = requestRide.PickupTime.Unix()
newRide.ScheduledPickupRange = scheduledRide
newRide.Passenger.FirstName = names[0]
newRide.Passenger.LastName = " "
newRide.Passenger.PhoneNumber = *user.PhoneNumber
if c.cfg.LyftProd.UserUUID != createdUser.ID {
newRide, err = c.tnc.Lyft.RequestRide(newRide)
} else {
fmt.Println("In Production")
newRide, err = c.tnc.LyftProd.RequestRide(newRide)
}
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
destination = newRide.Origin
newRide.Origin = newRide.Destination
newRide.Destination = destination
if newRide.Error != "" {
fmt.Println("Error to schedule a ride on lyft: ", newRide.Error, newRide.ErrorDescription)
} else {
fmt.Println("Ride Scheduled: ", newRide.Status)
}
newRide.TripType.Key = "from_visit"
} else {
newRide.TripType.Key = "from_visit_call"
newRide.Status = "willCall"
newRide.RideID = entity.UUID
}
newRide.Visit = entity.Visit
newRide.PickupTime = requestRide.ReturnTime
requestMS := (newRide.PickupTime.UnixNano() / int64(time.Millisecond))
generateDate := time.Now()
generateMS := (generateDate.UnixNano() / int64(time.Millisecond))
newRide.RequestAt = newRide.PickupTime
newRide.RequestAtMS = &requestMS
newRide.GeneratedAt = &generateDate
newRide.GeneratedAtMS = &generateMS
newRide.Passenger.FirstName = names[0]
newRide.Passenger.LastName = names[len(names)-1]
newRide.Passenger.PhoneNumber = *user.PhoneNumber
newRide.Passenger.UserID = &requestRide.UserUUID
if newRide.Passenger.ImageURL == nil {
imageURL := " "
newRide.Passenger.ImageURL = &imageURL
}
newRide.UserUUID = requestRide.UserUUID
newRide.Origin.Name = requestRide.Destination.Name
newRide.Origin.ID = requestRide.Destination.ID
newRide.Destination.Name = requestRide.Origin.Name
newRide.Destination.ID = requestRide.Origin.ID
newRide.Distance = requestRide.Distance
newRide.Duration = requestRide.Duration
newRide.ETA = requestRide.ETA
newRide.VisitDate = requestRide.VisitDate
newRide.VisitTime = requestRide.VisitTime
newRide.VisitExternalID = requestRide.VisitExternalID
newRide.CreateUserUUID = createdUser.ID
roudtripRide, err := c.svc.Rides.Save(newRide)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
go func() {
err = c.svc.Notification.SendNotification(newRide.Status, roudtripRide, newRide)
if err != nil {
fmt.Println("Error to notify user: ", err.Error())
}
}()
}
ctx.Response().Header().Set("Content-Type", "application/json")
return routeutils.ResponseAPIOK(ctx, entity)
}
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) handleRawLyft(ctx echo.Context) error {
rideUUID := ctx.Param("ride_uuid")
if rideUUID == "" {
return routeutils.ResponseAPIValidationError(ctx, "ride_uuid param is mandatory")
}
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideUUID, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
}
if err != nil {
fmt.Println("Error getting lyft details: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, lyftRide)
}
func (c *controller) handleRideETA(ctx echo.Context) error {
rideUUID := ctx.Param("ride_uuid")
if rideUUID == "" {
return routeutils.ResponseAPIValidationError(ctx, "ride_uuid param is mandatory")
}
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideUUID, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" {
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
}
if err != nil {
fmt.Println("Error getting lyft details: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
ride.Route.Location = ride.Route.Origin
ride.Route.Location.Address = ""
ride.Route.Location.Name = ""
switch lyftRide.Status {
case "scheduled":
ride.Route.ETA = ride.PickupTime.UnixNano() / int64(time.Second)
case "accepted":
if lyftRide.Origin.ETASeconds != nil {
ride.Route.ETA = *lyftRide.Origin.ETASeconds
} else {
ride.Route.ETA = 0
if c.cfg.App.Debug {
ride.Route.ETA = 350
}
}
case "arrived", "pickedUp":
if lyftRide.Destination.ETASeconds != nil {
ride.Route.ETA = *lyftRide.Destination.ETASeconds
} else {
ride.Route.ETA = 0
if c.cfg.App.Debug {
ride.Route.ETA = 350
}
}
}
if lyftRide.Location.Latitude != 0 && lyftRide.Location.Longitude != 0 {
ride.Route.Location = lyftRide.Location
}
}
return routeutils.ResponseAPIOK(ctx, ride)
}
func (c *controller) handleCancel(ctx echo.Context) error {
var requestRide viewmodel.RideRequest
rideUUID := ctx.Param("ride_uuid")
if rideUUID == "" {
return routeutils.ResponseAPIValidationError(ctx, "ride_uuid param is mandatory")
}
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideUUID, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
requestRide.RideID = ride.InternalID
if ride.Status.Key == "scheduled" && !strings.Contains(ride.InternalID, "s_") {
requestRide.RideID = "s_" + ride.InternalID
}
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
err = c.tnc.Lyft.CancelRide(requestRide)
if err != nil && err.Error() == "ride_not_found" {
err = nil
}
} else {
fmt.Println("In Production")
err = c.tnc.LyftProd.CancelRide(requestRide)
}
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
err = c.svc.Rides.UpdateStatus(rideUUID, "canceled")
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
// ride.Status.Key = "canceled"
// go func() {
// err = c.svc.Notification.SendNotification(ride.Status.Key, ride, viewmodel.RideRequest{})
// if err != nil {
// fmt.Println("Error: ", err.Error())
// }
// }()
ctx.Response().Header().Set("Content-Type", "application/json")
return routeutils.ResponseNoContent(ctx, nil)
}
func (c *controller) handleETA(ctx echo.Context) error {
sLat := ctx.Param("lat")
if sLat == "" {
return routeutils.ResponseAPIValidationError(ctx, "lat param is mandatory")
}
sLog := ctx.Param("log")
if sLog == "" {
return routeutils.ResponseAPIValidationError(ctx, "log param is mandatory")
}
sDestLat := ctx.Param("destlat")
if sLat == "" {
return routeutils.ResponseAPIValidationError(ctx, "lat param is mandatory")
}
sDestLog := ctx.Param("destlong")
if sLog == "" {
return routeutils.ResponseAPIValidationError(ctx, "log param is mandatory")
}
lat, err := strconv.ParseFloat(strings.TrimSpace(sLat), 64)
if err != nil {
return routeutils.ResponseAPIValidationError(ctx, "lat invalid")
}
log, err := strconv.ParseFloat(strings.TrimSpace(sLog), 64)
if err != nil {
return routeutils.ResponseAPIValidationError(ctx, "log invalid")
}
destlat, err := strconv.ParseFloat(strings.TrimSpace(sDestLat), 64)
if err != nil {
return routeutils.ResponseAPIValidationError(ctx, "destination lat invalid")
}
destlog, err := strconv.ParseFloat(strings.TrimSpace(sDestLog), 64)
if err != nil {
return routeutils.ResponseAPIValidationError(ctx, "destination log invalid")
}
params := map[string]interface{}{}
params["endLat"] = destlat
params["endLng"] = destlog
params["rideType"] = "lyft"
resp, err := c.tnc.Lyft.GetCost(lat, log, params)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleRide(ctx echo.Context) error {
rideUUID := ctx.Param("ride_uuid")
if rideUUID == "" {
return routeutils.ResponseAPIValidationError(ctx, "ride_uuid param is mandatory")
}
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideUUID, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" {
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
}
if err != nil {
fmt.Println("Error getting lyft details: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
ride.Route.Location = ride.Route.Origin
ride.Route.Location.Address = ""
ride.Route.Location.Name = ""
switch lyftRide.Status {
case "scheduled":
ride.Route.ETA = ride.PickupTime.UnixNano() / int64(time.Second)
case "accepted":
if lyftRide.Origin.ETASeconds != nil {
ride.Route.ETA = *lyftRide.Origin.ETASeconds
} else {
ride.Route.ETA = 0
if c.cfg.App.Debug {
ride.Route.ETA = 350
}
}
case "arrived":
if lyftRide.Destination.ETASeconds != nil {
ride.Route.ETA = *lyftRide.Destination.ETASeconds
} else {
ride.Route.ETA = 0
}
case "pickedUp":
if lyftRide.Destination.ETASeconds != nil {
ride.Route.ETA = *lyftRide.Destination.ETASeconds
} else {
ride.Route.ETA = 0
if c.cfg.App.Debug {
ride.Route.ETA = 350
}
}
}
if lyftRide.Location.Latitude != 0 && lyftRide.Location.Longitude != 0 {
ride.Route.Location = lyftRide.Location
}
}
return routeutils.ResponseAPIOK(ctx, ride)
}
func (c *controller) handleDrivers(ctx echo.Context) error {
sLat := ctx.Param("lat")
if sLat == "" {
return routeutils.ResponseAPIValidationError(ctx, "lat param is mandatory")
}
sLog := ctx.Param("log")
if sLog == "" {
return routeutils.ResponseAPIValidationError(ctx, "log param is mandatory")
}
lat, err := strconv.ParseFloat(strings.TrimSpace(sLat), 64)
if err != nil {
return routeutils.ResponseAPIValidationError(ctx, "lat invalid")
}
log, err := strconv.ParseFloat(strings.TrimSpace(sLog), 64)
if err != nil {
return routeutils.ResponseAPIValidationError(ctx, "log invalid")
}
resp, err := c.tnc.Lyft.GetDrivers(lat, log)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleTypes(ctx echo.Context) error {
sLat := ctx.Param("lat")
if sLat == "" {
return routeutils.ResponseAPIValidationError(ctx, "lat param is mandatory")
}
sLog := ctx.Param("log")
if sLog == "" {
return routeutils.ResponseAPIValidationError(ctx, "log param is mandatory")
}
lat, err := strconv.ParseFloat(strings.TrimSpace(sLat), 64)
if err != nil {
return routeutils.ResponseAPIValidationError(ctx, "lat invalid")
}
log, err := strconv.ParseFloat(strings.TrimSpace(sLog), 64)
if err != nil {
return routeutils.ResponseAPIValidationError(ctx, "log invalid")
}
resp, err := c.tnc.Lyft.GetTypes(lat, log, nil)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleReady(ctx echo.Context) error {
var rideUUID = ctx.Param("ride_uuid")
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
ride, err := c.svc.Rides.GetByUUID(rideUUID, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
var nextRide viewmodel.Ride
if ride.TripType.Key == "from_visit_call" && ride.Status.Key == "willCall" {
nextRide = ride
} else if ride.Visit.TripType.Key == "roundtrip_call" && ride.TripType.Key == "to_visit" {
roundTripRide, err := c.svc.Rides.GetByVisitUUIDAndTripType(ride.Visit.UUID, "from_visit_call", user)
if err != nil {
fmt.Println("Error to get next ride: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
nextRide = roundTripRide
} else {
fmt.Println("Error on get next ride ")
return routeutils.ResponseAPINotFoundError(ctx)
}
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
}
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
lyftRide.RideID = ""
lyftRide.Destination = ride.Route.Destination
lyftRide.Origin = ride.Route.Origin
name := nextRide.User.Name
names := strings.Split(name, " ")
lyftRide.Passenger.FirstName = names[0]
lyftRide.Passenger.LastName = " "
lyftRide.Passenger.PhoneNumber = *nextRide.User.PhoneNumber
lyftRide.RideType = "lyft"
if c.cfg.LyftProd.UserUUID != nextRide.CreatedUser.ID {
lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide)
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.RequestRide(lyftRide)
}
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
currentTime := time.Now()
lyftRide.PickupTime = &currentTime
lyftRide.ReturnTime = &currentTime
nextRide.PickupTime = &currentTime
nextRide, err = c.svc.Rides.UpdateNewRide(nextRide, lyftRide, user)
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
go func() {
err = c.svc.Notification.SendNotification(lyftRide.Status, nextRide, lyftRide)
if err != nil {
fmt.Println("Error to notify user: ", err.Error())
}
}()
return routeutils.ResponseAPIOK(ctx, nextRide)
}

View File

@@ -0,0 +1,52 @@
package tncroute
import (
"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/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
rideRoute = "/:ride_uuid"
rawRideRoute = "/:ride_uuid/raw"
cancelRoute = "/:ride_uuid/cancel"
shareRoute = "/:ride_uuid/share"
readyRoute = "/:ride_uuid/ready"
etaDetailsRoute = "/:ride_uuid/eta"
etaRoute = "/eta/:lat/:log/:destlat/:destlong"
ridersRoute = "/drivers/:lat/:log"
typesRoute = "/types/:lat/:log"
messageRoute = "/:ride_uuid/message"
messageDriverRoute = "/:ride_uuid/message/driver"
wsRoute = "/ws"
)
// Register authenticate route
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service, tnc *tncservice.Service, notification *notificationservice.Service) {
ctrl := controllerInstance(cfg, svc, tnc, notification)
r.GET(rootRoute, ctrl.handleList)
r.POST(rootRoute, ctrl.handle)
r.GET(rideRoute, ctrl.handleRide)
r.POST(cancelRoute, ctrl.handleCancel)
r.POST(readyRoute, ctrl.handleReady)
r.POST(messageRoute, ctrl.handleMessage)
r.POST(shareRoute, ctrl.handleShare)
r.POST(messageDriverRoute, ctrl.handleMessageDriver)
r.GET(etaRoute, ctrl.handleETA)
r.GET(etaDetailsRoute, ctrl.handleRideETA)
r.GET(ridersRoute, ctrl.handleDrivers)
r.GET(typesRoute, ctrl.handleTypes)
r.GET(wsRoute, ctrl.handleSocket)
if cfg.App.Debug {
r.GET(rawRideRoute, ctrl.handleRawLyft)
}
}

View File

@@ -0,0 +1,363 @@
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
}

View File

@@ -0,0 +1,21 @@
package twilioroute
import (
"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/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = ""
wsRoute = "/ws"
)
// Register authenticate route
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service, tnc *tncservice.Service, notification *notificationservice.Service) {
ctrl := controllerInstance(cfg, svc, tnc, notification)
r.POST(rootRoute, ctrl.handleTwilio)
r.GET(wsRoute, ctrl.handleSocket)
}

View File

@@ -0,0 +1,444 @@
package usersroute
import (
b64 "encoding/base64"
"fmt"
"math/rand"
"sync"
"time"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
"bitbucket.org/nemt/nemt-portal-api/domain"
"bitbucket.org/nemt/nemt-portal-api/infra/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/cache"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
svc *applicationservice.Service
cfg *config.Config
}
func controllerInstance(svc *applicationservice.Service, cfg *config.Config) *controller {
once.Do(func() {
instance = &controller{
svc: svc,
cfg: cfg,
}
})
return instance
}
func (c *controller) handleGetAll(ctx echo.Context) error {
page, quantity := routeutils.GetPagingParams(ctx)
list, err := c.svc.Users.GetAll(quantity, page)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, list)
}
func (c *controller) handleGetByID(ctx echo.Context) error {
userID, err := routeutils.GetAndValidateStringParam(ctx, "user_uuid", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
item, err := c.svc.Users.GetByUUID(userID, "US")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
rides, err := c.svc.Rides.GetByUserUUID(userID, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
item.Rides = rides
return routeutils.ResponseAPIOK(ctx, item)
}
func (c *controller) handlePortalAddress(ctx echo.Context) error {
userID, err := routeutils.GetAndValidateStringParam(ctx, "user_uuid", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
item, err := c.svc.Users.GetByUUID(userID, "")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
createdUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if item.ID == "" {
return routeutils.ResponseAPIValidationError(ctx, "User not found")
} else {
var Address viewmodel.Address
if err := ctx.Bind(&Address); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
Address.User = item
Address.CreatedUserUUID = createdUser.ID
Address, err = c.svc.Users.SaveAddress(Address)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
Address.User, err = c.svc.Users.GetByUUID(userID, "")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, Address)
}
}
func (c *controller) handleRemoveAddress(ctx echo.Context) error {
addressID, err := routeutils.GetAndValidateStringParam(ctx, "address_uuid", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
err = c.svc.Users.RemoveAddress(addressID)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseNoContent(ctx, addressID)
}
func (c *controller) handleMemberAddress(ctx echo.Context) error {
userID, err := routeutils.GetAndValidateStringParam(ctx, "user_uuid", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
item, err := c.svc.Users.GetByUUID(userID, "")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
createdUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if item.ID == "" {
return routeutils.ResponseAPIValidationError(ctx, "User not found")
} else {
var Address viewmodel.Address
if err := ctx.Bind(&Address); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
Address.User = item
Address.CreatedUserUUID = createdUser.ID
Address, err = c.svc.Users.SaveAddress(Address)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
Address.User, err = c.svc.Users.GetByUUID(userID, "US")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, Address)
}
}
func (c *controller) handleMemberDetail(ctx echo.Context) error {
userID, err := routeutils.GetAndValidateStringParam(ctx, "user_uuid", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
item, err := c.svc.Users.GetByUUID(userID, "US")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
rides, err := c.svc.Rides.GetByUserUUID(userID, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
item.Rides = rides
if item.ID == "" {
return routeutils.ResponseAPIValidationError(ctx, "User not found")
} else {
return routeutils.ResponseAPIOK(ctx, item)
}
}
func (c *controller) handlePortalDetail(ctx echo.Context) error {
userID, err := routeutils.GetAndValidateStringParam(ctx, "user_uuid", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
item, err := c.svc.Users.GetByUUID(userID, "SP")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if item.ID == "" {
return routeutils.ResponseAPIValidationError(ctx, "User not found")
} else {
return routeutils.ResponseAPIOK(ctx, item)
}
}
func (c *controller) stringWithCharset(length int, charset string) string {
b := make([]byte, length)
var seededRand *rand.Rand = rand.New(
rand.NewSource(time.Now().UnixNano()))
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
func (c *controller) generatePassword(n int) string {
const (
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
return c.stringWithCharset(n, charset)
}
func (c *controller) handleGetMember(ctx echo.Context) error {
users, err := c.svc.Users.GetUsersByProfile("US")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
rides, err := c.svc.Rides.GetAll(authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
for i := range users {
for _, r := range rides {
if r.User.ID == users[i].ID {
users[i].Rides = append(users[i].Rides, r)
}
}
}
return routeutils.ResponseAPIOK(ctx, users)
}
func (c *controller) handleGetPortal(ctx echo.Context) error {
users, err := c.svc.Users.GetUsersByProfile("")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, users)
}
func (c *controller) handleMember(ctx echo.Context) error {
var user viewmodel.User
if err := ctx.Bind(&user); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if user.PhoneNumber == nil && user.Email == nil || len(*user.PhoneNumber) == 0 && len(*user.Email) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "phonenumber or email is required", false)
}
if len(user.Pass) == 0 {
user.Pass = c.generatePassword(8)
} else {
pass, err := b64.StdEncoding.DecodeString(user.Pass)
if err != nil {
return routeutils.ResponseAPIAuthError(ctx, "Invalid password", false)
}
user.Pass = string(pass)
}
if user.BirthDate == nil || user.BirthDate.IsZero() {
return routeutils.ResponseAPIAuthError(ctx, "birthdate is required", false)
}
if user.Member == nil || len(*user.Member) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "member is required", false)
}
if user.Gender == nil || len(*user.Gender) == 0 || (*user.Gender != "M" && *user.Gender != "F" && *user.Gender != "U") {
return routeutils.ResponseAPIAuthError(ctx, "gender is required", false)
}
if len(user.Name) == 0 && len(user.First) == 0 && len(user.Last) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "name is required", false)
}
if len(user.First) != 0 && len(user.Last) != 0 {
user.Name = fmt.Sprintf("%s %s", user.First, user.Last)
}
profile, err := c.svc.Profile.GetByKey("US")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
user.Profiles = append(user.Profiles, profile)
user, err = c.svc.Users.Create(user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, user)
}
func (c *controller) handleBulkPortal(ctx echo.Context) error {
var users []viewmodel.User
if err := ctx.Bind(&users); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
for i, _ := range users {
if len(users[i].Profiles) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "profile is required", false)
}
if users[i].PhoneNumber == nil || len(*users[i].PhoneNumber) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "phonenumber is required", false)
}
if users[i].Email == nil || len(*users[i].Email) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "email is required", false)
}
if len(users[i].Pass) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "password is required", false)
}
pass, err := b64.StdEncoding.DecodeString(users[i].Pass)
if err != nil {
return routeutils.ResponseAPIAuthError(ctx, "Invalid password", false)
}
users[i].Pass = string(pass)
if len(users[i].Name) == 0 && len(users[i].First) == 0 && len(users[i].Last) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "name is required", false)
}
if len(users[i].First) != 0 && len(users[i].Last) != 0 {
users[i].Name = fmt.Sprintf("%s %s", users[i].First, users[i].Last)
}
}
returnUser, err := c.svc.Users.CreateBulk(users)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, returnUser)
}
func (c *controller) handlePortal(ctx echo.Context) error {
var user viewmodel.User
if err := ctx.Bind(&user); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if len(user.Profiles) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "profile is required", false)
}
if user.PhoneNumber == nil || len(*user.PhoneNumber) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "phonenumber is required", false)
}
if user.Email == nil || len(*user.Email) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "email is required", false)
}
if len(user.Pass) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "password is required", false)
}
pass, err := b64.StdEncoding.DecodeString(user.Pass)
if err != nil {
return routeutils.ResponseAPIAuthError(ctx, "Invalid password", false)
}
user.Pass = string(pass)
if len(user.Name) == 0 && len(user.First) == 0 && len(user.Last) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "name is required", false)
}
if len(user.First) != 0 && len(user.Last) != 0 {
user.Name = fmt.Sprintf("%s %s", user.First, user.Last)
}
user, err = c.svc.Users.Create(user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, user)
}
func (c *controller) handleContactType(ctx echo.Context) error {
cache := cache.Instance(c.cfg)
cacheKey := ctx.Request().Method + ctx.Request().URL.EscapedPath() + ctx.Request().URL.RawQuery
resp := []viewmodel.ContactType{}
err := cache.GetStruct(cacheKey, &resp)
if err != nil {
if err != domain.ErrCacheMiss {
ctx.Logger().Errorf(domain.LogProblemGettingFromCache, err)
}
resp, err = c.svc.Users.GetContactType()
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
cache.SetStruct(cacheKey, resp)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleTokenInfo(ctx echo.Context) error {
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, user)
}

View File

@@ -0,0 +1,54 @@
package usersroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = "/"
idRoute = "/:user_uuid/"
memberRoute = "/member"
memberDetailRoute = "/member/:user_uuid"
memberAddressRoute = "/member/:user_uuid/address"
memberRemoveAddressRoute = "/member/:user_uuid/address/:address_uuid"
userRoute = "/portal"
userDetailRoute = "/portal/:user_uuid"
userAddressRoute = "/portal/:user_uuid/address"
userRemoveAddressRoute = "/portal/:user_uuid/address/:address_uuid"
portalRoute = "/portal"
portalBulkRoute = "/portal/bulk"
contacttypeRoute = "/contacttype"
tokenInfoRoute = "/tokeninfo"
)
// Register registers the routes in the echo group
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(svc, cfg)
r.GET(rootRoute, ctrl.handleGetAll)
r.GET(idRoute, ctrl.handleGetByID)
r.POST(memberAddressRoute, ctrl.handleMemberAddress)
r.PUT(memberRemoveAddressRoute, ctrl.handleRemoveAddress)
r.GET(memberDetailRoute, ctrl.handleMemberDetail)
r.GET(memberRoute, ctrl.handleGetMember)
r.GET(userDetailRoute, ctrl.handlePortalDetail)
r.GET(userRoute, ctrl.handleGetPortal)
r.POST(userAddressRoute, ctrl.handlePortalAddress)
r.PUT(userRemoveAddressRoute, ctrl.handleRemoveAddress)
//Can be cached
r.GET(contacttypeRoute, ctrl.handleContactType)
r.POST(memberRoute, ctrl.handleMember)
r.POST(portalRoute, ctrl.handlePortal)
r.POST(portalBulkRoute, ctrl.handleBulkPortal)
if cfg.App.Debug {
r.GET(tokenInfoRoute, ctrl.handleTokenInfo)
}
}

View File

@@ -0,0 +1,64 @@
package visitroute
import (
"sync"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
)
var (
instance *controller
once sync.Once
)
type controller struct {
svc *applicationservice.Service
cfg *config.Config
}
func controllerInstance(svc *applicationservice.Service, cfg *config.Config) *controller {
once.Do(func() {
instance = &controller{
svc: svc,
cfg: cfg,
}
})
return instance
}
func (c *controller) handleGetByID(ctx echo.Context) error {
visit_uuid := ctx.Param("visit_uuid")
if visit_uuid == "" {
return routeutils.ResponseAPIValidationError(ctx, "visit_uuid param is mandatory")
}
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Visits.GetByUUID(visit_uuid, user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}
func (c *controller) handleGetAll(ctx echo.Context) error {
user, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
resp, err := c.svc.Visits.GetAll(user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, resp)
}

View File

@@ -0,0 +1,20 @@
package visitroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
rootRoute = "/"
idRoute = "/:visit_uuid"
)
// Register registers the routes in the echo group
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(svc, cfg)
r.GET(rootRoute, ctrl.handleGetAll)
r.GET(idRoute, ctrl.handleGetByID)
}

73
server/server.go Normal file
View File

@@ -0,0 +1,73 @@
package server
import (
"fmt"
"sync"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/entitymapping"
"bitbucket.org/nemt/nemt-portal-api/application/notificationservice"
"bitbucket.org/nemt/nemt-portal-api/application/tncservice"
"bitbucket.org/nemt/nemt-portal-api/domain/contract"
"bitbucket.org/nemt/nemt-portal-api/domain/service"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
"bitbucket.org/nemt/nemt-portal-api/infra/logger"
"bitbucket.org/nemt/nemt-portal-api/server/router"
"bitbucket.org/nemt/nemt-portal-api/server/serverconfig"
"github.com/labstack/echo"
)
var (
instance *Server
once sync.Once
)
// Server runs the application
type Server struct {
srv *echo.Echo
svc *service.Service
cfg *config.Config
log *logger.Logger
cache contract.CacheManager
}
// Instance returns an instance of Service
func Instance(svc *service.Service, cfg *config.Config, log *logger.Logger, cache contract.CacheManager) *Server {
once.Do(func() {
instance = &Server{
srv: echo.New(),
svc: svc,
cfg: cfg,
log: log,
cache: cache,
}
})
return instance
}
// Run setup and executes the server
func (s *Server) Run() error {
s.srv.HideBanner = true
s.srv.Debug = s.cfg.App.Debug
err := serverconfig.SetMiddlewares(s.srv, s.cfg, s.log, s.svc)
if err != nil {
return errors.Wrap(err)
}
entityMapper := entitymapping.New()
notificationService := notificationservice.New(s.svc, entityMapper, s.cfg, s.cache)
appService := applicationservice.New(s.svc, entityMapper, notificationService, s.cfg)
tncService := tncservice.New(s.svc, entityMapper, s.cfg, notificationService)
router.Register(s.srv, s.cfg, appService, tncService, notificationService)
err = s.srv.Start(fmt.Sprintf(":%d", s.cfg.HTTP.Port))
if err != nil {
return errors.Wrap(err)
}
return nil
}

View File

@@ -0,0 +1,25 @@
package serverconfig
import (
"bitbucket.org/nemt/nemt-portal-api/infra/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func setJWTMiddleware(e *echo.Echo, cfg *config.Config) error {
key, err := auth.GetCertPublicKey(cfg.HTTP.Auth.CertificatePath)
if err != nil {
return errors.Wrap(err)
}
e.Pre(middlewareErrorWrapper(middleware.JWTWithConfig(middleware.JWTConfig{
TokenLookup: "header:" + auth.AppTokenHeaderName,
Skipper: authSkipper,
SigningKey: key,
SigningMethod: auth.TokenSigningMethod.Name,
})))
return nil
}

View File

@@ -0,0 +1,93 @@
package serverconfig
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
"bitbucket.org/nemt/nemt-portal-api/infra/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/infra/logger"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/casbin/casbin"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"strings"
)
type (
// Config defines the config for CasbinAuth middleware.
Config struct {
// Skipper defines a function to skip middleware.
Skipper middleware.Skipper
// Enforcer CasbinAuth main rule.
// Required.
Enforcer *casbin.Enforcer
Application *config.Config
Svc *applicationservice.Service
Logger *logger.Logger
}
)
var (
// DefaultConfig is the default CasbinAuth middleware config.
DefaultConfig = Config{
Skipper: middleware.DefaultSkipper,
}
)
// MiddlewareWithConfig returns a CasbinAuth middleware with config.
// See `Middleware()`.
func MiddlewareWithConfig(cfg *config.Config, svc *applicationservice.Service, log *logger.Logger) echo.MiddlewareFunc {
config := &DefaultConfig
config.Svc = svc
config.Logger = log
config.Application = cfg
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) || config.CheckPermission(c) {
return next(c)
}
return routeutils.HandleHTMLError(c, echo.ErrForbidden)
}
}
}
func setAuthorizationMiddleware(e *echo.Echo, log *logger.Logger, cfg *config.Config, svc *applicationservice.Service) {
e.Use(MiddlewareWithConfig(cfg, svc, log))
}
// CheckPermission checks the user/method/path combination from the request.
// Returns true (permission granted) or false (permission forbidden)
func (a *Config) CheckPermission(c echo.Context) bool {
user, err := auth.GetUserDetail(c, a.Application)
if err != nil {
a.Logger.Warningf("Cannot get user details. %v ", err)
}
method := c.Request().Method
path := c.Request().URL.Path
//objectOrganization := a.organizationGoverningObject(c, user)
return a.Enforcer.Enforce(user, path, method)
}
func (a *Config) organizationGoverningObject(c echo.Context, userDetails viewmodel.User) (result viewmodel.Organization) {
existingUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) > 0
newUser := strings.Contains(c.Request().URL.Path, "/users") && len(c.ParamValues()) <= 0
switch {
case existingUser:
user, _ := a.Svc.Users.GetByUUID(c.ParamValues()[0], "")
result = user.Organizations[0]
case newUser:
result = userDetails.Organizations[0]
}
return
}

View File

@@ -0,0 +1,11 @@
[request_definition]
r = role, obj, act
[policy_definition]
p = role, obj, act
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = keymatch(r.role, p.role) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")

View File

@@ -0,0 +1,10 @@
package serverconfig
import (
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func setBodyLimitMiddleware(e *echo.Echo) {
e.Use(middleware.BodyLimit("4M"))
}

View File

@@ -0,0 +1,204 @@
package serverconfig
import (
"bufio"
"bytes"
"fmt"
"io"
"net"
"net/http"
"regexp"
"strings"
"time"
"bitbucket.org/nemt/nemt-portal-api/domain"
"bitbucket.org/nemt/nemt-portal-api/infra/cache"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
// bodyResponseWriter implements the http.ResponseWriter interface
type bodyResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w *bodyResponseWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(code)
}
func (w *bodyResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
func (w *bodyResponseWriter) Flush() {
w.ResponseWriter.(http.Flusher).Flush()
}
func (w *bodyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
}
func (w *bodyResponseWriter) CloseNotify() <-chan bool {
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
// CacheConfig defines the config for Cache middleware.
type CacheConfig struct {
// Skipper determines if the request should skip this middleware
Skipper middleware.Skipper
// Cache expiration/lifetime
Expiration time.Duration
// VaryByQuery contains a list of query parameters to include in cache key
VaryByQuery []string
}
// DefaultCacheConfig is the default Cache middleware config.
var DefaultCacheConfig = CacheConfig{
Skipper: middleware.DefaultSkipper,
}
// CacheMiddleware returns a middleware that protects requests agains Cache attacks.
func CacheMiddleware(cfg *config.Config) echo.MiddlewareFunc {
config := DefaultCacheConfig
config.Expiration = cfg.Cache.DefaultExpiration
return CacheMiddlewareWithConfig(cfg, config)
}
// CacheMiddlewareWithConfig returns a Cache middleware with config.
// See: `CacheMiddleware()`.
func CacheMiddlewareWithConfig(cfg *config.Config, config CacheConfig) echo.MiddlewareFunc {
if config.Skipper == nil {
config.Skipper = DefaultCacheConfig.Skipper
}
if config.Expiration < 0 {
config.Expiration = cfg.Cache.DefaultExpiration
}
cache := cache.Instance(cfg)
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
req := c.Request()
skip := config.Skipper(c) || req.Method != echo.GET
if !skip {
for _, val := range req.Header["Cache-Control"] {
if val == "no-cache" {
skip = true
break
}
}
}
if skip {
return next(c)
}
res := c.Response()
var cacheKey = getCacheKey(c, config)
var contentTypeCacheKey = fmt.Sprintf("%s-content-type", cacheKey)
var contentType = "application/json"
output, err := cache.GetItem(cacheKey)
if err == nil {
var responseStatus = http.StatusOK
if len(output) == 0 {
responseStatus = http.StatusNoContent
}
contentType, err = cache.GetString(contentTypeCacheKey)
if err != nil {
return routeutils.HandleAPIError(c, err)
}
expiration, err := cache.GetExpiration(cacheKey)
if err != nil {
return routeutils.HandleAPIError(c, err)
}
if expiration > 0 {
res.Header()["Cache-Control"] = []string{fmt.Sprintf("max-age=%v", expiration.Seconds())}
}
return c.Blob(responseStatus, contentType, []byte(output))
} else if err == domain.ErrCacheMiss {
resBody := new(bytes.Buffer)
mw := io.MultiWriter(res.Writer, resBody)
writer := &bodyResponseWriter{Writer: mw, ResponseWriter: res.Writer}
res.Writer = writer
err = next(c)
if err != nil {
return err
}
headers := writer.Header()
cache.SetExpiration(cacheKey, config.Expiration)
if config.Expiration != 0 {
res.Header()["Cache-Control"] = []string{fmt.Sprintf("max-age=%v", config.Expiration.Seconds())}
}
contentTypeHeader, ok := headers[echo.HeaderContentType]
if ok {
contentType = contentTypeHeader[0]
}
err = cache.SetItem(cacheKey, resBody.Bytes())
if err != nil {
return routeutils.HandleAPIError(c, err)
}
err = cache.SetString(contentTypeCacheKey, contentType)
if err != nil {
return routeutils.HandleAPIError(c, err)
}
} else if err != nil {
return routeutils.HandleAPIError(c, err)
}
return nil
}
}
}
func getCacheKey(c echo.Context, config CacheConfig) string {
var req = c.Request()
var re = regexp.MustCompile("(?i)[^a-z0-9_]+")
var key = req.URL.Path
if len(config.VaryByQuery) > 0 {
var query = "q"
for _, queryKey := range config.VaryByQuery {
for k, v := range req.URL.Query() {
if k == queryKey {
query = fmt.Sprintf("%s-%s-%s", query, k, v)
break
}
}
}
query = strings.Trim(re.ReplaceAllString(query, "-"), "-")
if strings.TrimSpace(query) != "" {
key = fmt.Sprintf("%s-%v", key, query)
}
}
return strings.Trim(re.ReplaceAllString(key, "-"), "-")
}

View File

@@ -0,0 +1,17 @@
package serverconfig
import (
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func setCORSMiddleware(e *echo.Echo, cfg *config.Config) {
middlewareConfig := middleware.DefaultCORSConfig
if !cfg.App.Debug {
middlewareConfig.AllowOrigins = cfg.HTTP.Auth.FrontendURLs
}
e.Pre(middleware.CORSWithConfig(middlewareConfig))
}

View File

@@ -0,0 +1,10 @@
package serverconfig
import (
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func setGZIPMiddleware(e *echo.Echo) {
e.Use(middleware.Gzip())
}

151
server/serverconfig/log.go Normal file
View File

@@ -0,0 +1,151 @@
package serverconfig
import (
"encoding/json"
"io"
"strconv"
"time"
"bitbucket.org/nemt/nemt-portal-api/infra/logger"
"github.com/Sirupsen/logrus"
"github.com/labstack/echo"
"github.com/labstack/gommon/log"
)
type echoLogger struct {
*logger.Logger
}
// Printj prints a message in json format
func (l *echoLogger) Printj(data log.JSON) {
bytes, _ := json.Marshal(data)
l.Logger.Print(string(bytes))
}
// Debugj logs a debug message in json format
func (l *echoLogger) Debugj(data log.JSON) {
bytes, _ := json.Marshal(data)
l.Logger.Debug(string(bytes))
}
// Infoj logs an info message in json format
func (l *echoLogger) Infoj(data log.JSON) {
bytes, _ := json.Marshal(data)
l.Logger.Info(string(bytes))
}
// Warnj logs a warning message in json format
func (l *echoLogger) Warnj(data log.JSON) {
bytes, _ := json.Marshal(data)
l.Logger.Warn(string(bytes))
}
// Errorj logs an error message in json format
func (l *echoLogger) Errorj(data log.JSON) {
bytes, _ := json.Marshal(data)
l.Logger.Error(string(bytes))
}
// Panicj logs a panic message in json format
func (l *echoLogger) Panicj(data log.JSON) {
bytes, _ := json.Marshal(data)
l.Logger.Panic(string(bytes))
}
// Fatalj logs a fatal message in json format
func (l *echoLogger) Fatalj(data log.JSON) {
bytes, _ := json.Marshal(data)
l.Logger.Fatal(string(bytes))
}
// Level returns the echo logging level
func (l *echoLogger) Level() log.Lvl {
return log.Lvl(l.Logger.Level)
}
// SetLevel sets the echo logging level
func (l *echoLogger) SetLevel(level log.Lvl) {
l.Logger.Level = logrus.Level(level)
}
// Output returns the echo logging output
func (l *echoLogger) Output() io.Writer {
return l.Writer()
}
// SetOutput sets the echo logging output
func (l *echoLogger) SetOutput(output io.Writer) {
l.Logger.Logger.Out = output
}
// Prefix returns the echo logging output
func (l *echoLogger) Prefix() string {
return l.Logger.Data["app"].(string)
}
// SetPrefix returns the echo logging output
func (l *echoLogger) SetPrefix(prefix string) {
l.Logger.Data["app"] = prefix
}
func loggerMiddlewareHandler(c echo.Context, next echo.HandlerFunc, log *logger.Logger) error {
req := c.Request()
res := c.Response()
start := time.Now()
err := next(c)
if err != nil {
return err
}
stop := time.Now()
p := req.URL.Path
if p == "" {
p = "/"
}
bytesIn := req.Header.Get(echo.HeaderContentLength)
if bytesIn == "" {
bytesIn = "0"
}
requestID := req.Header.Get(echo.HeaderXRequestID)
if requestID == "" {
requestID = res.Header().Get(echo.HeaderXRequestID)
}
log.WithFields(map[string]interface{}{
"id": requestID,
"time_rfc3339": time.Now().Format(time.RFC3339),
"remote_ip": c.RealIP(),
"host": req.Host,
"uri": req.RequestURI,
"method": req.Method,
"path": p,
"referer": req.Referer(),
"user_agent": req.UserAgent(),
"status": res.Status,
"latency": strconv.FormatInt(stop.Sub(start).Nanoseconds()/1000, 10),
"latency_human": stop.Sub(start).String(),
"bytes_in": bytesIn,
"bytes_out": strconv.FormatInt(res.Size, 10),
"headers": req.Header,
"query": req.URL.Query(),
}).Info("Handled request")
return nil
}
func loggerMiddleware(log *logger.Logger) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
return loggerMiddlewareHandler(c, next, log)
}
}
}
func setLogMiddleware(e *echo.Echo, log *logger.Logger) {
e.Logger = &echoLogger{log}
e.Pre(loggerMiddleware(log))
}

View File

@@ -0,0 +1,73 @@
package serverconfig
import (
"time"
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
// RateLimitMiddlewareConfig defines the config for RateLimit middleware.
type RateLimitMiddlewareConfig struct {
Skipper middleware.Skipper
// Max number of allowed requests on a timeframe
MaxRequests int64
// The timeframe duration for the requests count
BucketDuration time.Duration
// Error response message
ResponseMessage string
// Error response
ResponseStatus int
}
// DefaultRateLimitMiddlewareConfig is the default RateLimit middleware config.
var DefaultRateLimitMiddlewareConfig = RateLimitMiddlewareConfig{
Skipper: middleware.DefaultSkipper,
MaxRequests: 10,
BucketDuration: time.Second,
ResponseMessage: "Too Many Requests",
ResponseStatus: http.StatusServiceUnavailable,
}
// RateLimitMiddleware returns a middleware that protects the API agains massive requests.
func RateLimitMiddleware() echo.MiddlewareFunc {
return RateLimitMiddlewareWithConfig(DefaultRateLimitMiddlewareConfig)
}
// RateLimitMiddlewareWithConfig returns a RateLimitMiddleware with configuration parameters.
// See: `RateLimitMiddleware()`.
func RateLimitMiddlewareWithConfig(config RateLimitMiddlewareConfig) echo.MiddlewareFunc {
if config.MaxRequests == 0 {
config.MaxRequests = DefaultRateLimitMiddlewareConfig.MaxRequests
}
if config.BucketDuration == 0 {
config.BucketDuration = DefaultRateLimitMiddlewareConfig.BucketDuration
}
// limiter := tollbooth.NewLimiterExpiringBuckets(config.MaxRequests, config.BucketDuration, time.Hour, time.Second*0)
// limiter.Message = config.ResponseMessage
// limiter.StatusCode = config.ResponseStatus
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
// err := tollbooth.LimitByRequest(limiter, c.Request())
// if err != nil {
// tollbooth.SetResponseHeaders(limiter, c.Response().Writer)
// return errors.NewHTTPError(limiter.StatusCode, limiter.Message)
// }
return next(c)
}
}
}
func setRateLimitMiddleware(e *echo.Echo) {
e.Pre(RateLimitMiddleware())
}

View File

@@ -0,0 +1,10 @@
package serverconfig
import (
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func setRecoverMiddleware(e *echo.Echo) {
e.Use(middleware.Recover())
}

View File

@@ -0,0 +1,10 @@
package serverconfig
import (
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func setRequestIDMiddleware(e *echo.Echo) {
e.Use(middleware.RequestID())
}

View File

@@ -0,0 +1,15 @@
package serverconfig
import (
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func setRouteMiddleware(e *echo.Echo) {
middlewareConfig := middleware.DefaultTrailingSlashConfig
middlewareConfig.RedirectCode = http.StatusPermanentRedirect
e.Use(middleware.AddTrailingSlashWithConfig(middlewareConfig))
}

View File

@@ -0,0 +1,28 @@
package serverconfig
import (
"bitbucket.org/nemt/nemt-portal-api/domain/service"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
"bitbucket.org/nemt/nemt-portal-api/infra/logger"
"github.com/labstack/echo"
)
// SetMiddlewares attaches middlewares to server
func SetMiddlewares(server *echo.Echo, cfg *config.Config, log *logger.Logger, svc *service.Service) error {
setRecoverMiddleware(server)
setGZIPMiddleware(server)
setRequestIDMiddleware(server)
setLogMiddleware(server, log)
setCORSMiddleware(server, cfg)
setBodyLimitMiddleware(server)
setRateLimitMiddleware(server)
//setAuthorizationMiddleware(server, log, svc)
err := setJWTMiddleware(server, cfg)
if err != nil {
return errors.Wrap(err)
}
return nil
}

View File

@@ -0,0 +1,50 @@
package serverconfig
import (
"strings"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
)
// authSkipper is the default skipper for authentication and authorization
func authSkipper(ctx echo.Context) bool {
path := ctx.Request().URL.Path
return (strings.HasPrefix(path, "/health") ||
strings.HasPrefix(path, "/v1/authenticate") ||
strings.HasPrefix(path, "/v1/docs") ||
strings.HasPrefix(path, "/v1/twilio") ||
strings.HasPrefix(path, "/v1/twilio/ws") ||
strings.Contains(path, "/v1/ext") ||
strings.Contains(path, "/v1/notification/ws") ||
strings.HasPrefix(path, "/v1/lyfthook") ||
strings.HasPrefix(path, "/v1/docs"))
}
// appSkipper is the default skipper for the application routes
func appSkipper(ctx echo.Context) bool {
path := ctx.Request().URL.Path
return strings.HasPrefix(path, "/v1/twilio")
}
// docsSkipper is the default skipper for documentation
func docsSkipper(ctx echo.Context) bool {
path := ctx.Request().URL.Path
return strings.HasPrefix(path, "/v1/docs")
}
// middlewareErrorWrapper wraps a middleware and applies the default error handling to response
func middlewareErrorWrapper(middleware echo.MiddlewareFunc) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
handlerFunc := middleware(next)
return func(c echo.Context) error {
err := handlerFunc(c)
if err != nil {
return routeutils.HandleAPIError(c, err)
}
return nil
}
}
}