1000 lines
29 KiB
Go
1000 lines
29 KiB
Go
package application
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/utils/form-validators"
|
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
"git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/proto"
|
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/mux"
|
|
"github.com/paulmach/orb"
|
|
"github.com/paulmach/orb/geojson"
|
|
"github.com/rs/zerolog/log"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
type OrganizedCarpoolDriversForm struct {
|
|
FirstName string `json:"first_name" validate:"required"`
|
|
LastName string `json:"last_name" validate:"required"`
|
|
Email string `json:"email" validate:"required,email"`
|
|
Birthdate *time.Time `json:"birthdate" validate:"required"`
|
|
PhoneNumber string `json:"phone_number" validate:"required,phoneNumber"`
|
|
FileNumber string `json:"file_number"`
|
|
Address any `json:"address,omitempty"`
|
|
AddressDestination any `json:"address_destination,omitempty"`
|
|
Gender string `json:"gender"`
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolOverview(w http.ResponseWriter, r *http.Request) {
|
|
accounts, err := h.organizedCarpoolDrivers(r)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("issue getting solidarity drivers")
|
|
accounts = []mobilityaccountsstorage.Account{}
|
|
}
|
|
|
|
accountsMap := map[string]mobilityaccountsstorage.Account{}
|
|
|
|
for _, a := range accounts {
|
|
accountsMap[a.ID] = a
|
|
}
|
|
|
|
beneficiariesMap, err := h.services.GetBeneficiariesMap()
|
|
if err != nil {
|
|
beneficiariesMap = map[string]mobilityaccountsstorage.Account{}
|
|
}
|
|
|
|
bookingsproto, err := h.services.GRPC.CarpoolService.GetCarpoolBookings(context.Background(), &proto.GetCarpoolBookingsRequest{
|
|
MinDate: timestamppb.Now(),
|
|
MaxDate: timestamppb.New(time.Now().Add(24 * 365 * time.Hour)),
|
|
})
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("issue retreving bookings")
|
|
}
|
|
|
|
bookings := []*proto.CarpoolServiceBooking{}
|
|
|
|
if err == nil {
|
|
for _, b := range bookingsproto.Bookings {
|
|
bookings = append(bookings, b)
|
|
}
|
|
}
|
|
|
|
slices.SortFunc(accounts, func(a, b mobilityaccountsstorage.Account) int {
|
|
return strings.Compare(
|
|
strings.ToLower(fmt.Sprintf("%s %s", a.Data["first_name"].(string), a.Data["last_name"].(string))),
|
|
strings.ToLower(fmt.Sprintf("%s %s", b.Data["first_name"].(string), b.Data["last_name"].(string))),
|
|
)
|
|
})
|
|
slices.SortFunc(bookings, func(a, b *proto.CarpoolServiceBooking) int {
|
|
return cmp.Compare(a.PassengerPickupDate.AsTime().Unix(), b.PassengerPickupDate.AsTime().Unix())
|
|
})
|
|
|
|
h.Renderer.OrganizedCarpoolOverview(w, r, accounts, accountsMap, beneficiariesMap, bookings)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolBookingDisplay(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
bookingId := vars["bookingid"]
|
|
|
|
resp, err := h.services.GRPC.CarpoolService.GetBooking(context.Background(), &proto.GetCarpoolBookingRequest{
|
|
BookingId: bookingId,
|
|
})
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not get carpool booking")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if resp.Booking == nil {
|
|
log.Error().Msg("carpool booking not found")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
driver, err := h.services.GetAccount(resp.Booking.Driver.Id)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("driver retrieval issue")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
passenger, err := h.services.GetAccount(resp.Booking.Passenger.Id)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("passenger retrieval issue")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Extract driver departure and arrival addresses from DriverRoute GeoJSON
|
|
var driverDepartureAddress, driverArrivalAddress string
|
|
if resp.Booking.DriverRoute != nil && resp.Booking.DriverRoute.Serialized != "" {
|
|
fc, err := geojson.UnmarshalFeatureCollection([]byte(resp.Booking.DriverRoute.Serialized))
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not unmarshal driver route geojson")
|
|
} else {
|
|
// Extract departure address (first feature)
|
|
if len(fc.Features) > 0 {
|
|
if addr, ok := fc.Features[0].Properties["label"]; ok {
|
|
if addrStr, ok := addr.(string); ok {
|
|
driverDepartureAddress = addrStr
|
|
}
|
|
}
|
|
}
|
|
// Extract arrival address (last feature)
|
|
if len(fc.Features) > 1 {
|
|
if addr, ok := fc.Features[1].Properties["label"]; ok {
|
|
if addrStr, ok := addr.(string); ok {
|
|
driverArrivalAddress = addrStr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
h.Renderer.OrganizedCarpoolBookingDisplay(w, r, resp.Booking, driver, passenger, driverDepartureAddress, driverArrivalAddress)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolBookingStatus(action string) func(w http.ResponseWriter, r *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
bookingId := vars["bookingid"]
|
|
|
|
var status proto.CarpoolServiceBookingStatus
|
|
if action == "confirm" {
|
|
status = proto.CarpoolServiceBookingStatus_CONFIRMED
|
|
} else if action == "cancel" {
|
|
status = proto.CarpoolServiceBookingStatus_CANCELLED
|
|
} else if action == "waitconfirmation" {
|
|
status = proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION
|
|
} else {
|
|
log.Error().Str("action", action).Msg("unknown booking action")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
_, err := h.services.GRPC.CarpoolService.UpdateBooking(context.Background(), &proto.UpdateCarpoolBookingRequest{
|
|
BookingId: bookingId,
|
|
Status: status,
|
|
})
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("update carpool booking status issue")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Redirect back to the booking display page
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/bookings/%s", bookingId), http.StatusSeeOther)
|
|
}
|
|
}
|
|
|
|
func (h *ApplicationHandler) organizedCarpoolDrivers(r *http.Request) ([]mobilityaccountsstorage.Account, error) {
|
|
accounts := []mobilityaccountsstorage.Account{}
|
|
|
|
request := &mobilityaccounts.GetAccountsRequest{
|
|
Namespaces: []string{"organized_carpool_drivers"},
|
|
}
|
|
|
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccounts(context.TODO(), request)
|
|
if err != nil {
|
|
return accounts, err
|
|
}
|
|
|
|
for _, account := range resp.Accounts {
|
|
if filterAccount(r, account) {
|
|
a := account.ToStorageType()
|
|
accounts = append(accounts, a)
|
|
}
|
|
}
|
|
|
|
return accounts, err
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolCreateDriver(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "POST" {
|
|
dataMap, err := parseOrganizedCarpoolDriversForm(r)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
data, err := structpb.NewValue(dataMap)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
request := &mobilityaccounts.RegisterRequest{
|
|
Account: &mobilityaccounts.Account{
|
|
Namespace: "organized_carpool_drivers",
|
|
Data: data.GetStructValue(),
|
|
},
|
|
}
|
|
|
|
resp, err := h.services.GRPC.MobilityAccounts.Register(context.TODO(), request)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", resp.Account.Id), http.StatusFound)
|
|
return
|
|
}
|
|
|
|
h.Renderer.OrganizedCarpoolCreateDriver(w, r)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolUpdateDriver(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
driverID := vars["driverid"]
|
|
|
|
driver, err := h.services.GetAccount(driverID)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Issue retrieving driver account")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if r.Method == "POST" {
|
|
dataMap, err := parseOrganizedCarpoolDriversUpdateForm(r)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
data, err := structpb.NewValue(dataMap)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
request := &mobilityaccounts.UpdateDataRequest{
|
|
Account: &mobilityaccounts.Account{
|
|
Id: driverID,
|
|
Namespace: "organized_carpool_drivers",
|
|
Data: data.GetStructValue(),
|
|
},
|
|
}
|
|
|
|
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", resp.Account.Id), http.StatusFound)
|
|
return
|
|
}
|
|
|
|
h.Renderer.OrganizedCarpoolUpdateDriver(w, r, driver)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolDriverDisplay(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
driverID := vars["driverid"]
|
|
|
|
documents := h.filestorage.List(filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS + "/" + driverID)
|
|
|
|
driver, err := h.services.GetAccount(driverID)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Issue retrieving driver account")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
log.Info().Any("driver data", driver.Data).Msg("driver retrieved")
|
|
|
|
trips := []*geojson.FeatureCollection{}
|
|
|
|
resp, err := h.services.GRPC.CarpoolService.GetRegularRoutes(context.Background(), &proto.GetRegularRoutesRequest{
|
|
UserId: driverID,
|
|
})
|
|
|
|
for _, r := range resp.Routes {
|
|
t, err := geojson.UnmarshalFeatureCollection([]byte(r.Serialized))
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not unmarshall feature collection")
|
|
continue
|
|
}
|
|
|
|
trips = append(trips, t)
|
|
}
|
|
|
|
h.Renderer.OrganizedCarpoolDriverDisplay(w, r, driver, trips, documents)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolArchiveDriver(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
driverID := vars["driverid"]
|
|
|
|
data, _ := structpb.NewValue(map[string]any{
|
|
"archived": true,
|
|
})
|
|
|
|
request := &mobilityaccounts.UpdateDataRequest{
|
|
Account: &mobilityaccounts.Account{
|
|
Id: driverID,
|
|
Namespace: "organized_carpool_drivers",
|
|
Data: data.GetStructValue(),
|
|
},
|
|
}
|
|
|
|
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", resp.Account.Id), http.StatusFound)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolUnarchiveDriver(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
driverID := vars["driverid"]
|
|
|
|
data, _ := structpb.NewValue(map[string]any{
|
|
"archived": false,
|
|
})
|
|
|
|
request := &mobilityaccounts.UpdateDataRequest{
|
|
Account: &mobilityaccounts.Account{
|
|
Id: driverID,
|
|
Namespace: "organized_carpool_drivers",
|
|
Data: data.GetStructValue(),
|
|
},
|
|
}
|
|
|
|
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", resp.Account.Id), http.StatusFound)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolDriverDocuments(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
driverID := vars["driverid"]
|
|
|
|
// r.ParseForm()
|
|
r.ParseMultipartForm(100 * 1024 * 1024)
|
|
|
|
document_type := r.FormValue("type")
|
|
document_name := r.FormValue("name")
|
|
|
|
file, header, err := r.FormFile("file-upload")
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
fileid := uuid.NewString()
|
|
|
|
metadata := map[string]string{
|
|
"type": document_type,
|
|
"name": document_name,
|
|
}
|
|
|
|
log.Debug().Any("metadata", metadata).Msg("Metadata")
|
|
|
|
if err := h.filestorage.Put(file, filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS, fmt.Sprintf("%s/%s_%s", driverID, fileid, header.Filename), header.Size, metadata); err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolDocumentDownload(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
driverID := vars["driverid"]
|
|
document := vars["document"]
|
|
|
|
file, info, err := h.filestorage.Get(filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS, fmt.Sprintf("%s/%s", driverID, document))
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", info.ContentType)
|
|
if _, err = io.Copy(w, file); err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolAddTrip(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
log.Error().Msg("Wrong method")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
log.Error().Err(err).Msg("error parsong availabilities form")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
driverID := vars["driverid"]
|
|
|
|
trips := []*proto.CarpoolFeatureCollection{}
|
|
|
|
outwardtime := r.PostFormValue("outwardtime")
|
|
returntime := r.PostFormValue("returntime")
|
|
dep := r.PostFormValue("address_departure")
|
|
dest := r.PostFormValue("address_destination")
|
|
|
|
departure, err := geojson.UnmarshalFeature([]byte(dep))
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed parsong departure geojson")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
destination, err := geojson.UnmarshalFeature([]byte(dest))
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed parsong departure geojson")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
outwardroute, err := h.services.Routing.Route([]orb.Point{departure.Point(), destination.Point()})
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed calling route search")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
returnroute, err := h.services.Routing.Route([]orb.Point{destination.Point(), departure.Point()})
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed calling route search")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
outwardschedules := []map[string]any{}
|
|
returnschedules := []map[string]any{}
|
|
|
|
if r.PostFormValue("days.monday") == "on" {
|
|
outwardschedules = append(outwardschedules, map[string]any{
|
|
"day": "MON",
|
|
"time_of_day": outwardtime,
|
|
})
|
|
returnschedules = append(returnschedules, map[string]any{
|
|
"day": "MON",
|
|
"time_of_day": returntime,
|
|
})
|
|
}
|
|
if r.PostFormValue("days.tuesday") == "on" {
|
|
outwardschedules = append(outwardschedules, map[string]any{
|
|
"day": "TUE",
|
|
"time_of_day": outwardtime,
|
|
})
|
|
returnschedules = append(returnschedules, map[string]any{
|
|
"day": "TUE",
|
|
"time_of_day": returntime,
|
|
})
|
|
}
|
|
if r.PostFormValue("days.wednesday") == "on" {
|
|
outwardschedules = append(outwardschedules, map[string]any{
|
|
"day": "WED",
|
|
"time_of_day": outwardtime,
|
|
})
|
|
returnschedules = append(returnschedules, map[string]any{
|
|
"day": "WED",
|
|
"time_of_day": returntime,
|
|
})
|
|
}
|
|
if r.PostFormValue("days.thursday") == "on" {
|
|
outwardschedules = append(outwardschedules, map[string]any{
|
|
"day": "THU",
|
|
"time_of_day": outwardtime,
|
|
})
|
|
returnschedules = append(returnschedules, map[string]any{
|
|
"day": "THU",
|
|
"time_of_day": returntime,
|
|
})
|
|
}
|
|
if r.PostFormValue("days.friday") == "on" {
|
|
outwardschedules = append(outwardschedules, map[string]any{
|
|
"day": "FRI",
|
|
"time_of_day": outwardtime,
|
|
})
|
|
returnschedules = append(returnschedules, map[string]any{
|
|
"day": "FRI",
|
|
"time_of_day": returntime,
|
|
})
|
|
}
|
|
if r.PostFormValue("days.saturday") == "on" {
|
|
outwardschedules = append(outwardschedules, map[string]any{
|
|
"day": "SAT",
|
|
"time_of_day": outwardtime,
|
|
})
|
|
returnschedules = append(returnschedules, map[string]any{
|
|
"day": "SAT",
|
|
"time_of_day": returntime,
|
|
})
|
|
}
|
|
if r.PostFormValue("days.sunday") == "on" {
|
|
outwardschedules = append(outwardschedules, map[string]any{
|
|
"day": "SUN",
|
|
"time_of_day": outwardtime,
|
|
})
|
|
returnschedules = append(returnschedules, map[string]any{
|
|
"day": "SUN",
|
|
"time_of_day": returntime,
|
|
})
|
|
}
|
|
|
|
outward_fc := geojson.NewFeatureCollection()
|
|
outward_fc.Append(departure)
|
|
outward_fc.Append(destination)
|
|
outward_fc.ExtraMembers = geojson.Properties{}
|
|
outward_fc.ExtraMembers["properties"] = map[string]any{
|
|
"is_driver": true,
|
|
"is_passenger": false,
|
|
"user": mobilityaccountsstorage.Account{
|
|
ID: driverID,
|
|
},
|
|
"polyline": outwardroute.Summary.Polyline,
|
|
"schedules": outwardschedules,
|
|
"driver_options": map[string]any{},
|
|
"passenger_options": map[string]any{},
|
|
}
|
|
outwardtrip, err := outward_fc.MarshalJSON()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed parsong return geojson")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
return_fc := geojson.NewFeatureCollection()
|
|
return_fc.Append(destination)
|
|
return_fc.Append(departure)
|
|
return_fc.ExtraMembers = geojson.Properties{}
|
|
return_fc.ExtraMembers["properties"] = map[string]any{
|
|
"is_driver": true,
|
|
"is_passenger": false,
|
|
"user": mobilityaccountsstorage.Account{
|
|
ID: driverID,
|
|
},
|
|
"polyline": returnroute.Summary.Polyline,
|
|
"schedules": returnschedules,
|
|
"driver_options": map[string]any{},
|
|
"passenger_options": map[string]any{},
|
|
}
|
|
returntrip, err := return_fc.MarshalJSON()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed parsong return geojson")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
trips = append(trips, &proto.CarpoolFeatureCollection{
|
|
Serialized: string(outwardtrip),
|
|
})
|
|
trips = append(trips, &proto.CarpoolFeatureCollection{
|
|
Serialized: string(returntrip),
|
|
})
|
|
|
|
req := &proto.CreateRegularRoutesRequest{
|
|
Routes: trips,
|
|
}
|
|
_, err = h.services.GRPC.CarpoolService.CreateRegularRoutes(context.Background(), req)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not create regular routes")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
log.Info().Msg("Finished creating carpool routes")
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolDeleteTrip(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
driverID := vars["driverid"]
|
|
tripID := vars["tripid"]
|
|
|
|
req := &proto.DeleteRegularRoutesRequest{
|
|
Ids: []string{tripID},
|
|
}
|
|
|
|
_, err := h.services.GRPC.CarpoolService.DeleteRegularRoutes(context.Background(), req)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not delete regular routes")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
|
}
|
|
|
|
func (h *ApplicationHandler) OrganizedCarpoolJourney(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
driverId := vars["driverid"]
|
|
journeyId := vars["journeyid"]
|
|
|
|
var passenger mobilityaccountsstorage.Account
|
|
passengerId := r.URL.Query().Get("passengerid")
|
|
log.Info().Str("journeyid", journeyId).Str("driverid", driverId).Str("passengerid", passengerId).Msg("organized carpool journey")
|
|
|
|
// Get the planned trip data
|
|
journeyResp, err := h.services.GRPC.CarpoolService.GetPlannedTrip(context.Background(), &proto.GetPlannedTripRequest{
|
|
Id: journeyId,
|
|
})
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not get carpool journey")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
journey, err := geojson.UnmarshalFeatureCollection([]byte(journeyResp.PlannedTrip.Serialized))
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not unmarshal carpool journey")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
departureDate := journey.ExtraMembers["departure_date"]
|
|
var departureTime *timestamppb.Timestamp
|
|
if departureDate != nil {
|
|
if dd, ok := departureDate.(string); ok {
|
|
dt, _ := time.Parse("2006-01-02 15:04", dd)
|
|
departureTime = timestamppb.New(dt)
|
|
}
|
|
}
|
|
|
|
// Extract operator from journey data
|
|
var operatorID string
|
|
if operator, ok := journey.ExtraMembers["operator"]; ok {
|
|
if op, ok := operator.(string); ok {
|
|
operatorID = op
|
|
}
|
|
}
|
|
if operatorID == "" {
|
|
operatorID = "example.coopgo.fr" // fallback to default
|
|
}
|
|
|
|
if departureTime == nil {
|
|
// Fallback to current time if we can't extract departure time
|
|
departureTime = timestamppb.New(time.Now())
|
|
}
|
|
|
|
if r.Method == "POST" {
|
|
if passengerId == "" {
|
|
log.Error().Msg("missing passenger ID for carpool booking")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
log.Error().Err(err).Msg("could not parse form input")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Extract journey properties from the geojson data
|
|
var journeyProps map[string]any
|
|
if props, ok := journey.ExtraMembers["properties"]; ok {
|
|
if propsMap, ok := props.(map[string]any); ok {
|
|
journeyProps = propsMap
|
|
}
|
|
}
|
|
|
|
if journeyProps == nil {
|
|
log.Error().Msg("could not extract journey properties")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Extract departure date from journey ExtraMembers
|
|
if depDate, ok := journey.ExtraMembers["departure_date"]; ok {
|
|
if depDateStr, ok := depDate.(string); ok {
|
|
if parsedTime, err := time.Parse("2006-01-02T15:04:05Z", depDateStr); err == nil {
|
|
departureTime = timestamppb.New(parsedTime)
|
|
} else if parsedTime, err := time.Parse("2006-01-02", depDateStr); err == nil {
|
|
departureTime = timestamppb.New(parsedTime)
|
|
} else {
|
|
log.Warn().Str("departure_date", depDateStr).Msg("could not parse departure date, using current time")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extract passenger pickup/drop coordinates and addresses from stored passenger data
|
|
var pickupLat, pickupLng, dropLat, dropLng float64
|
|
var pickupAddress, dropAddress string
|
|
|
|
// Check if we have passenger pickup and drop features in the journey's extra members
|
|
if pickupData, ok := journey.ExtraMembers["passenger_pickup"]; ok {
|
|
if pickupMap, ok := pickupData.(map[string]interface{}); ok {
|
|
if geometry, ok := pickupMap["geometry"].(map[string]interface{}); ok {
|
|
if coords, ok := geometry["coordinates"].([]interface{}); ok && len(coords) >= 2 {
|
|
if lng, ok := coords[0].(float64); ok {
|
|
pickupLng = lng
|
|
}
|
|
if lat, ok := coords[1].(float64); ok {
|
|
pickupLat = lat
|
|
}
|
|
}
|
|
}
|
|
if properties, ok := pickupMap["properties"].(map[string]interface{}); ok {
|
|
if label, ok := properties["label"].(string); ok {
|
|
pickupAddress = label
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if dropData, ok := journey.ExtraMembers["passenger_drop"]; ok {
|
|
if dropMap, ok := dropData.(map[string]interface{}); ok {
|
|
if geometry, ok := dropMap["geometry"].(map[string]interface{}); ok {
|
|
if coords, ok := geometry["coordinates"].([]interface{}); ok && len(coords) >= 2 {
|
|
if lng, ok := coords[0].(float64); ok {
|
|
dropLng = lng
|
|
}
|
|
if lat, ok := coords[1].(float64); ok {
|
|
dropLat = lat
|
|
}
|
|
}
|
|
}
|
|
if properties, ok := dropMap["properties"].(map[string]interface{}); ok {
|
|
if label, ok := properties["label"].(string); ok {
|
|
dropAddress = label
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Debug().Str("pickup_address", pickupAddress).Str("drop_address", dropAddress).Msg("Extracted addresses")
|
|
|
|
// Extract time from schedules if available and no specific departure_date was found
|
|
if departureTime.AsTime().Equal(time.Now().Truncate(time.Second)) {
|
|
if schedules, ok := journeyProps["schedules"]; ok {
|
|
if schedulesList, ok := schedules.([]any); ok && len(schedulesList) > 0 {
|
|
if schedule, ok := schedulesList[0].(map[string]any); ok {
|
|
if timeOfDay, ok := schedule["time_of_day"].(string); ok {
|
|
// Parse time and combine with current date
|
|
now := time.Now()
|
|
timeStr := fmt.Sprintf("%s %s", now.Format("2006-01-02"), timeOfDay)
|
|
if depTime, err := time.Parse("2006-01-02 15:04", timeStr); err == nil {
|
|
departureTime = timestamppb.New(depTime)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
motivation := r.PostFormValue("motivation")
|
|
|
|
// Create carpool booking using extracted journey data
|
|
booking := &proto.CarpoolServiceBooking{
|
|
Id: uuid.NewString(),
|
|
Driver: &proto.CarpoolServiceUser{
|
|
Id: driverId,
|
|
Operator: operatorID,
|
|
},
|
|
Passenger: &proto.CarpoolServiceUser{
|
|
Id: passengerId,
|
|
Operator: operatorID,
|
|
},
|
|
PassengerPickupDate: departureTime,
|
|
PassengerPickupLat: pickupLat,
|
|
PassengerPickupLng: pickupLng,
|
|
PassengerDropLat: dropLat,
|
|
PassengerDropLng: dropLng,
|
|
PassengerPickupAddress: &pickupAddress,
|
|
PassengerDropAddress: &dropAddress,
|
|
Status: proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION,
|
|
DriverJourneyId: journeyId,
|
|
}
|
|
|
|
bookingRes, err := h.services.GRPC.CarpoolService.CreateBooking(context.Background(), &proto.CreateCarpoolBookingRequest{
|
|
Booking: booking,
|
|
})
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("cannot create carpool booking")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
log.Info().Str("booking_id", bookingRes.Booking.Id).Msg("Carpool booking created successfully")
|
|
|
|
// Send SMS notification if requested
|
|
message := r.PostFormValue("message")
|
|
doNotSend := r.PostFormValue("do_not_send")
|
|
if message != "" && doNotSend != "on" {
|
|
send_message := strings.ReplaceAll(message, "{booking_id}", bookingRes.Booking.Id)
|
|
send_message = strings.ReplaceAll(send_message, "{motivation}", motivation)
|
|
log.Debug().Str("message", send_message).Msg("Carpool booking created: sending message")
|
|
h.GenerateSMS(driverId, send_message)
|
|
}
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/"), http.StatusFound)
|
|
return
|
|
}
|
|
|
|
driver, err := h.services.GetAccount(driverId)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not get driver")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
if passengerId != "" {
|
|
passenger, err = h.services.GetAccount(passengerId)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not get account")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
}
|
|
|
|
beneficiaries, err := h.beneficiaries(r)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
h.Renderer.OrganizedCarpoolJourney(w, r, journey, driver, passenger, beneficiaries)
|
|
}
|
|
|
|
func parseOrganizedCarpoolDriversForm(r *http.Request) (map[string]any, error) {
|
|
if err := r.ParseForm(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Info().Any("form content", r.Form).Msg("parsing form")
|
|
|
|
var date *time.Time
|
|
|
|
if r.PostFormValue("birthdate") != "" {
|
|
d, err := time.Parse("2006-01-02", r.PostFormValue("birthdate"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
date = &d
|
|
}
|
|
|
|
formData := OrganizedCarpoolDriversForm{
|
|
FirstName: r.PostFormValue("first_name"),
|
|
LastName: r.PostFormValue("last_name"),
|
|
Email: r.PostFormValue("email"),
|
|
Birthdate: date,
|
|
PhoneNumber: r.PostFormValue("phone_number"),
|
|
FileNumber: r.PostFormValue("file_number"),
|
|
Gender: r.PostFormValue("gender"),
|
|
}
|
|
|
|
if r.PostFormValue("address") != "" {
|
|
var a any
|
|
json.Unmarshal([]byte(r.PostFormValue("address")), &a)
|
|
|
|
formData.Address = a
|
|
}
|
|
|
|
if r.PostFormValue("address_destination") != "" {
|
|
var a any
|
|
json.Unmarshal([]byte(r.PostFormValue("address_destination")), &a)
|
|
|
|
formData.AddressDestination = a
|
|
}
|
|
|
|
validate := formvalidators.New()
|
|
if err := validate.Struct(formData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d, err := json.Marshal(formData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var dataMap map[string]any
|
|
err = json.Unmarshal(d, &dataMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return dataMap, nil
|
|
}
|
|
|
|
func parseOrganizedCarpoolDriversUpdateForm(r *http.Request) (map[string]any, error) {
|
|
if err := r.ParseForm(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Info().Any("form content", r.Form).Msg("parsing update form")
|
|
|
|
var date *time.Time
|
|
|
|
if r.PostFormValue("birthdate") != "" {
|
|
d, err := time.Parse("2006-01-02", r.PostFormValue("birthdate"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
date = &d
|
|
}
|
|
|
|
formData := OrganizedCarpoolDriversForm{
|
|
FirstName: r.PostFormValue("first_name"),
|
|
LastName: r.PostFormValue("last_name"),
|
|
Email: r.PostFormValue("email"),
|
|
Birthdate: date,
|
|
PhoneNumber: r.PostFormValue("phone_number"),
|
|
FileNumber: r.PostFormValue("file_number"),
|
|
Gender: r.PostFormValue("gender"),
|
|
}
|
|
|
|
if r.PostFormValue("address") != "" {
|
|
var a any
|
|
json.Unmarshal([]byte(r.PostFormValue("address")), &a)
|
|
|
|
formData.Address = a
|
|
}
|
|
|
|
if r.PostFormValue("address_destination") != "" {
|
|
var a any
|
|
json.Unmarshal([]byte(r.PostFormValue("address_destination")), &a)
|
|
|
|
formData.AddressDestination = a
|
|
}
|
|
|
|
validate := formvalidators.New()
|
|
if err := validate.Struct(formData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d, err := json.Marshal(formData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var dataMap map[string]any
|
|
err = json.Unmarshal(d, &dataMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Handle other_properties for update form
|
|
if r.PostFormValue("other_properties") != "" {
|
|
var otherProps map[string]any
|
|
if err := json.Unmarshal([]byte(r.PostFormValue("other_properties")), &otherProps); err == nil {
|
|
if dataMap["other_properties"] == nil {
|
|
dataMap["other_properties"] = make(map[string]any)
|
|
}
|
|
for k, v := range otherProps {
|
|
dataMap["other_properties"].(map[string]any)[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
return dataMap, nil
|
|
}
|