package application import ( "context" "encoding/json" "fmt" "net/http" "slices" "sort" "strconv" "strings" "sync" "time" "git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting" carpoolproto "git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/proto" fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi" fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage" groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi" groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage" mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi" mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage" "git.coopgo.io/coopgo-platform/multimodal-routing/libs/transit/motis" "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen" "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers" "github.com/google/uuid" "github.com/gorilla/mux" "github.com/paulmach/orb/geojson" "github.com/rs/zerolog/log" //"gitlab.scity.coop/maas/navitia-golang" //"gitlab.scity.coop/maas/navitia-golang/types" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" ) var ( Depart any Arrive any ) func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Request) { r.ParseForm() var ( transit_results []*motis.Itinerary carpool_results []*geojson.FeatureCollection vehicle_results []fleetsstorage.Vehicle ) // availableDrivers := []mobilityaccountsstorage.Account{} drivers := map[string]mobilityaccountsstorage.Account{} driverJourneys := []*gen.SolidarityTransportDriverJourney{} var organizedCarpools []*carpoolproto.CarpoolServiceDriverJourney // navitiaCh := make(chan *navitia.JourneyResults, 1) // carpoolCh := make(chan []any, 1) locTime, errTime := time.LoadLocation("Europe/Paris") if errTime != nil { log.Panic().Err(errTime).Msg("Tried to load timezone location Europe/Paris. Error. Missing zones in container ?") } departuredate := r.FormValue("departuredate") departuretime := r.FormValue("departuretime") departuredatetime, _ := time.ParseInLocation("2006-01-02 15:04", fmt.Sprintf("%s %s", departuredate, departuretime), locTime) departure := r.FormValue("departure") destination := r.FormValue("destination") kb_results := []any{} searched := false var ( departuregeo *geojson.Feature destinationgeo *geojson.Feature // journeys *navitia.JourneyResults // carpoolresults []any ) if departuredate != "" && departuretime != "" && departure != "" && destination != "" { searched = true var err error departuregeo, err = geojson.UnmarshalFeature([]byte(departure)) if err != nil { log.Error().Err(err).Msg("error unmarshalling departure") w.WriteHeader(http.StatusBadRequest) return } destinationgeo, err = geojson.UnmarshalFeature([]byte(destination)) if err != nil { log.Error().Err(err).Msg("error unmarshalling destination") w.WriteHeader(http.StatusBadRequest) return } // SOLIDARITY TRANSPORT drivers, err = h.services.GetAccountsInNamespacesMap([]string{"solidarity_drivers", "organized_carpool_drivers"}) if err != nil { drivers = map[string]mobilityaccountsstorage.Account{} } protodep, _ := transformers.GeoJsonToProto(departuregeo) protodest, _ := transformers.GeoJsonToProto(destinationgeo) log.Debug().Time("departure time", departuredatetime).Msg("calling driver journeys with ...") res, err := h.services.GRPC.SolidarityTransport.GetDriverJourneys(context.Background(), &gen.GetDriverJourneysRequest{ Departure: protodep, Arrival: protodest, DepartureDate: timestamppb.New(departuredatetime), }) if err != nil { log.Error().Err(err).Msg("error in grpc call to GetDriverJourneys") } else { driverJourneys = slices.Collect(func(yield func(*gen.SolidarityTransportDriverJourney) bool) { for _, dj := range res.DriverJourneys { if a, ok := drivers[dj.DriverId].Data["archived"]; ok { if archived, ok := a.(bool); ok { if archived { continue } } } if !yield(dj) { return } } }) // res.DriverJourneys sort.Slice(driverJourneys, func(i, j int) bool { return driverJourneys[i].DriverDistance < driverJourneys[j].DriverDistance }) } // ORGANIZED CARPOOL organizedCarpoolsRes, err := h.services.GRPC.CarpoolService.DriverJourneys(context.Background(), &carpoolproto.DriverJourneysRequest{ DepartureLat: departuregeo.Point().Lat(), DepartureLng: departuregeo.Point().Lon(), ArrivalLat: destinationgeo.Point().Lat(), ArrivalLng: destinationgeo.Point().Lon(), DepartureDate: timestamppb.New(departuredatetime), }) if err != nil { log.Error().Err(err).Msg("error retrieving organized carpools") } else { organizedCarpools = organizedCarpoolsRes.DriverJourneys sort.Slice(organizedCarpools, func(i, j int) bool { return *organizedCarpools[i].Distance < *organizedCarpools[j].Distance }) } var wg sync.WaitGroup // CARPOOL OPERATORS carpools := make(chan *geojson.FeatureCollection) go h.services.InteropCarpool.Search(carpools, *departuregeo, *destinationgeo, departuredatetime) wg.Add(1) go func() { defer wg.Done() for c := range carpools { carpool_results = append(carpool_results, c) } }() // TRANSIT transitch := make(chan *motis.Itinerary) go func(transitch chan *motis.Itinerary, departure *geojson.Feature, destination *geojson.Feature, datetime *time.Time) { defer close(transitch) // timetableView := false searchWindows := 1800 response, err := h.services.TransitRouting.PlanWithResponse(context.Background(), &motis.PlanParams{ FromPlace: fmt.Sprintf("%f,%f", departure.Point().Lat(), departure.Point().Lon()), ToPlace: fmt.Sprintf("%f,%f", destination.Point().Lat(), destination.Point().Lon()), Time: datetime, // TimetableView: &timetableView, SearchWindow: &searchWindows, }) if err != nil { log.Error().Err(err).Msg("error retrieving transit data from MOTIS server") return } for _, i := range response.JSON200.Itineraries { transitch <- &i } }(transitch, departuregeo, destinationgeo, &departuredatetime) wg.Add(1) go func() { defer wg.Done() for itinerary := range transitch { transit_results = append(transit_results, itinerary) } }() // VEHICLES vehiclech := make(chan fleetsstorage.Vehicle) go h.vehicleRequest(vehiclech, departuredatetime.Add(-24*time.Hour), departuredatetime.Add(168*time.Hour)) wg.Add(1) go func() { defer wg.Done() for vehicle := range vehiclech { vehicle_results = append(vehicle_results, vehicle) } slices.SortFunc(vehicle_results, sorting.VehiclesByDistanceFrom(*departuregeo)) }() // journeys_results = <-navitiaCh wg.Wait() // KNOWLEDGE BASE departureGeo, _ := h.services.Geography.GeoSearch(departuregeo) kbData := h.config.Get("knowledge_base") if kb, ok := kbData.([]any); ok { log.Debug().Msg("1") for _, sol := range kb { log.Debug().Msg("2") if solution, ok := sol.(map[string]any); ok { log.Debug().Msg("3") if g, ok := solution["geography"]; ok { log.Debug().Msg("4") if geography, ok := g.([]any); ok { log.Debug().Msg("5") for _, gg := range geography { log.Debug().Msg("6") if geog, ok := gg.(map[string]any); ok { log.Debug().Msg("7") if layer, ok := geog["layer"].(string); ok { log.Debug().Msg("8") code := geog["code"] log.Debug().Any("code", fmt.Sprintf("%s", code)).Any("departure geo", departureGeo[layer].Properties.MustString("code")).Msg("9") if strings.Compare(fmt.Sprintf("%v", code), departureGeo[layer].Properties.MustString("code")) == 0 { log.Debug().Msg("10") kb_results = append(kb_results, solution) break } } } } } } } } } } beneficiaries, err := h.beneficiaries(r) if err != nil { log.Error().Err(err).Msg("issue retrieving beneficiaries") w.WriteHeader(http.StatusInternalServerError) return } h.Renderer.JourneysSearch(w, r, carpool_results, transit_results, vehicle_results, searched, departuregeo, destinationgeo, departuredate, departuretime, driverJourneys, drivers, organizedCarpools, beneficiaries, kb_results) } func (h *ApplicationHandler) vehicleRequest(vehiclech chan fleetsstorage.Vehicle, start time.Time, end time.Time) { defer close(vehiclech) vehiclerequest := &fleets.GetVehiclesRequest{ Namespaces: []string{"parcoursmob"}, } vehicleresp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), vehiclerequest) if err != nil { log.Error().Err(err).Msg("") return } for _, vehicle := range vehicleresp.Vehicles { v := vehicle.ToStorageType() if v.Free(start, end) { vehiclech <- v } } } type GroupsModule []groupstorage.Group func (a GroupsModule) Len() int { return len(a) } func (a GroupsModule) Less(i, j int) bool { return strings.Compare(a[i].Data["name"].(string), a[j].Data["name"].(string)) < 0 } func (a GroupsModule) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (h *ApplicationHandler) GroupsGestion(w http.ResponseWriter, r *http.Request) { request := &groupsmanagement.GetGroupsRequest{ Namespaces: []string{"parcoursmob_groups_covoiturage"}, } resp, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), request) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } groups := []groupstorage.Group{} for _, group := range resp.Groups { g := group.ToStorageType() groups = append(groups, g) } cacheid := uuid.NewString() sort.Sort(GroupsModule(groups)) h.cache.PutWithTTL(cacheid, groups, 1*time.Hour) h.Renderer.GroupsGestion(w, r, groups, cacheid) } func filterAcc(r *http.Request, a *mobilityaccounts.Account) bool { searchFilter, ok := r.URL.Query()["search"] if ok && len(searchFilter[0]) > 0 { name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string) if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter[0])) { return false } } return true } func (h *ApplicationHandler) CreateGroup(w http.ResponseWriter, r *http.Request) { var beneficiary any var ( departurgeo *geojson.Feature dstinationgeo *geojson.Feature ) searched := false if r.FormValue("beneficiaryid") != "" { searched = true requestbeneficiary := &mobilityaccounts.GetAccountRequest{ Id: r.FormValue("beneficiaryid"), } respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } beneficiary = respbeneficiary.Account.ToStorageType() if r.Method == "POST" { departure := r.FormValue("departure") destination := r.FormValue("destination") if departure != "" && destination != "" { var err error departurgeo, err = geojson.UnmarshalFeature([]byte(departure)) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusBadRequest) return } dstinationgeo, err = geojson.UnmarshalFeature([]byte(destination)) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusBadRequest) return } } if r.FormValue("departure") != "" { var a any json.Unmarshal([]byte(r.FormValue("departure")), &a) Depart = a } if r.FormValue("destination") != "" { var a any json.Unmarshal([]byte(r.FormValue("destination")), &a) Arrive = a } r.ParseForm() if r.FormValue("name") == "" { log.Error().Msg("invalid name") w.WriteHeader(http.StatusBadRequest) return } if r.FormValue("number") == "" { log.Error().Msg("invalid number of personne") w.WriteHeader(http.StatusBadRequest) return } planDays := map[string]any{ "lundi": r.FormValue("lundi") == "on", "mardi": r.FormValue("mardi") == "on", "mercredi": r.FormValue("mercredi") == "on", "jeudi": r.FormValue("jeudi") == "on", "vendredi": r.FormValue("vendredi") == "on", "samedi": r.FormValue("samedi") == "on", "dimanche": r.FormValue("dimanche") == "on", } groupidd := uuid.NewString() dataMap := map[string]any{ "name": r.FormValue("name"), "number": r.FormValue("number"), "driver_first_name": respbeneficiary.Account.ToStorageType().Data["first_name"], "driver_last_name": respbeneficiary.Account.ToStorageType().Data["last_name"], "depart": Depart, "arrive": Arrive, "departdate": r.FormValue("departdate"), "date": r.FormValue("date"), "enddate": r.FormValue("enddate"), "departtime": r.FormValue("departtime"), "time": r.FormValue("time"), "planDays": planDays, "recurrent": r.FormValue("recurrent"), "pontuelle": r.FormValue("ponctuelle"), } data, err := structpb.NewValue(dataMap) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } request_organization := &groupsmanagement.AddGroupRequest{ Group: &groupsmanagement.Group{ Id: groupidd, Namespace: "parcoursmob_groups_covoiturage", Data: data.GetStructValue(), }, } _, err = h.services.GRPC.GroupsManagement.AddGroup(context.TODO(), request_organization) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", request_organization.Group.ToStorageType().ID), http.StatusFound) return } } accountsBeneficaire, err := h.beneficiaries(r) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusBadRequest) return } h.Renderer.CreateGroup(w, r, Depart, Arrive, searched, beneficiary, accountsBeneficaire, departurgeo, dstinationgeo) } func (h *ApplicationHandler) DisplayGroupCovoiturage(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) groupid := vars["groupid"] request := &groupsmanagement.GetGroupRequest{ Id: groupid, } resp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), request) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } accounts := []any{} requesst := &mobilityaccounts.GetAccountsBatchRequest{ Accountids: resp.Group.Members, } ressp, _ := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), requesst) for _, account := range ressp.Accounts { if filterAcc(r, account) { a := account.ToStorageType() accounts = append(accounts, a) } } cacheid := uuid.NewString() h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour) r.ParseForm() var beneficiary any searched := false if r.FormValue("beneficiaryid") != "" { searched = true requestbeneficiary := &mobilityaccounts.GetAccountRequest{ Id: r.FormValue("beneficiaryid"), } respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } beneficiary = respbeneficiary.Account.ToStorageType() subscribe := &groupsmanagement.SubscribeRequest{ Groupid: resp.Group.ToStorageType().ID, Memberid: respbeneficiary.Account.Id, } _, err = h.services.GRPC.GroupsManagement.Subscribe(context.TODO(), subscribe) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } /*******************Code to store more information about mermbers groupscovoiturage**************/ if r.FormValue("departure") != "" { var a any json.Unmarshal([]byte(r.FormValue("departure")), &a) Depart = a } if r.FormValue("destination") != "" { var a any json.Unmarshal([]byte(r.FormValue("destination")), &a) Arrive = a } r.ParseForm() dataMap := map[string]any{ "depart": Depart, "arrive": Arrive, } id := uuid.NewString() data, err := structpb.NewValue(dataMap) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } request_organizatio := &groupsmanagement.AddGroupMemberRequest{ Group: &groupsmanagement.GroupMember{ Id: id, Memberid: respbeneficiary.Account.Id, Groupid: resp.Group.ToStorageType().ID, Data: data.GetStructValue(), }, } _, err = h.services.GRPC.GroupsManagement.AddGroupMember(context.TODO(), request_organizatio) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", resp.Group.ToStorageType().ID), http.StatusFound) return } //////////find all groups to store the adresse passenger/////// // grp := &groupsmanagement.GetGroupsBatchMemberRequest{ // Groupids: []string{resp.Group.ToStorageType().ID}, // } // s, err := h.services.GRPC.GroupsManagement.GetGroupsBatchMember(context.TODO(), grp) // if err != nil { // log.Error().Err(err).Msg("") // w.WriteHeader(http.StatusInternalServerError) // return // } // groups := map[string]any{} // if err == nil { // for _, g := range s.Groups { // groups[g.Memberid] = g.ToStorageType() // } // } //////////find all groups to store the adresse passenger/////// ///////////try to optimise the code //////////////////////////// groups, _ := h.services.GetGroupsMemberMap(resp.Group.ToStorageType().ID) var number string = strconv.Itoa(len(resp.Group.Members)) accountsBeneficaire, err := h.beneficiaries(r) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusBadRequest) return } h.Renderer.DisplayGroupCovoiturage(w, r, number, resp.Group.ToStorageType().ID, Depart, Arrive, accounts, cacheid, searched, beneficiary, resp.Group.ToStorageType(), accountsBeneficaire, groups) } func (h *ApplicationHandler) UpdateGroupCovoiturage(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] groupid := vars["groupid"] memberid := vars["memberid"] if r.Method == "POST" { //////////get groupid covoiturage////////// request := &groupsmanagement.GetGroupRequest{ Id: groupid, } resp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), request) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } //////////////////////////get group member//////////////////////////////// reequest := &groupsmanagement.GetGroupMemberRequest{ Id: id, } ressp, err := h.services.GRPC.GroupsManagement.GetGroupMember(context.TODO(), reequest) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } req := &groupsmanagement.UnsubscribeMemberRequest{ Id: ressp.Group.Id, } _, errr := h.services.GRPC.GroupsManagement.UnsubscribeMember(context.TODO(), req) if errr != nil { log.Error().Err(errr).Msg("Issue in groups management service - InsubscribeMember") w.WriteHeader(http.StatusInternalServerError) return } members := resp.Group.Members for i := 0; i < len(members); i++ { if members[i] == memberid { members = append(members[:i], members[(i+1):]...) resp.Group.Members = members reequest := &groupsmanagement.UnsubscribeRequest{ Groupid: resp.Group.ToStorageType().ID, Memberid: memberid, } _, err := h.services.GRPC.GroupsManagement.Unsubscribe(context.TODO(), reequest) if err != nil { log.Error().Err(err).Msg("") w.WriteHeader(http.StatusInternalServerError) return } } } http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", groupid), http.StatusFound) /* I must add "return" to resolve the err http: superfluous response.WriteHeader call from git.coopgo.io/coopgo-apps/parcoursmob/renderer.(*Renderer).Render (renderer.go:50) */ return } h.Renderer.UpdateGroupCovoiturage(w, r, groupid, memberid) }