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 }