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 { // booking, _ := transformers.BookingProtoToType(b) 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) 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) 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("driver journey") journeyResp, err := h.services.GRPC.CarpoolService.GetPlannedTrip(context.Background(), &proto.GetPlannedTripRequest{ Id: journeyId, }) if err != nil { log.Error().Err(err).Msg("could not get driver 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 driver journey") w.WriteHeader(http.StatusNotFound) return } if r.Method == "POST" { if passengerId == "" { log.Error().Err(err).Msg("could not get driver journey") w.WriteHeader(http.StatusNotFound) return } /*if _, err := h.services.GRPC.CarpoolService.CreateBooking(context.Background(), &proto.CreateCarpoolBookingRequest{ Booking: &proto.CarpoolServiceBooking{ Passenger: &proto.CarpoolServiceUser{ Id: passengerId, }, Driver: &proto.CarpoolServiceUser{ Id: driverId, }, } PassengerId: passengerId, DriverId: driverId, DriverJourneyId: journeyId, }); err != nil { log.Error().Err(err).Msg("cannot create booking") w.WriteHeader(http.StatusInternalServerError) return }*/ 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 } /*driverjourney, err := transformers.DriverJourneyProtoToType(journey.DriverJourney) if err != nil { log.Error().Err(err).Msg("could not transform driver journey type") 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 }