package application import ( "context" "encoding/json" "fmt" "sort" "sync" "time" "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification" "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting" agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi" agendastorage "git.coopgo.io/coopgo-platform/agenda/storage" fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi" fleetstorage "git.coopgo.io/coopgo-platform/fleets/storage" "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" "github.com/paulmach/orb" "github.com/paulmach/orb/geojson" "github.com/rs/zerolog/log" "google.golang.org/protobuf/types/known/timestamppb" ) type DashboardResult struct { Accounts []mobilityaccountsstorage.Account Members []mobilityaccountsstorage.Account Events []agendastorage.Event Bookings []fleetstorage.Booking SolidarityDrivers []mobilityaccountsstorage.Account OrganizedCarpoolDrivers []mobilityaccountsstorage.Account } func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddressGeoLayer, driverAddressGeoCode string) (*DashboardResult, error) { g := ctx.Value(identification.GroupKey) if g == nil { return nil, fmt.Errorf("no group found in context") } group := g.(storage.Group) // Load geography polygons for driver address filtering var driverAddressPolygons []orb.Polygon if driverAddressGeoLayer != "" && driverAddressGeoCode != "" { polygons, err := h.loadGeographyPolygon(driverAddressGeoLayer, driverAddressGeoCode) if err != nil { log.Warn().Err(err).Msg("failed to load driver address geography filter") } else { driverAddressPolygons = polygons } } // Get accounts (recent beneficiaries) request := &mobilityaccounts.GetAccountsBatchRequest{ Accountids: group.Members, } resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, request) if err != nil { return nil, err } accounts := []mobilityaccountsstorage.Account{} // We only display the 5 most recent here count := len(resp.Accounts) min := count - 5 if min < 0 { min = 0 } for _, account := range resp.Accounts[min:] { // Check if not archived if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived { a := account.ToStorageType() accounts = append([]mobilityaccountsstorage.Account{a}, accounts...) } } // Fetch remaining data in parallel using goroutines var wg sync.WaitGroup var mu sync.Mutex var members []mobilityaccountsstorage.Account var events []agendastorage.Event var bookings []fleetstorage.Booking var solidarityDrivers []mobilityaccountsstorage.Account var organizedCarpoolDrivers []mobilityaccountsstorage.Account // Get members wg.Add(1) go func() { defer wg.Done() m, _, err := h.groupmembers(group.ID) if err == nil { mu.Lock() members = m mu.Unlock() } }() // Get events wg.Add(1) go func() { defer wg.Done() eventsresp, err := h.services.GRPC.Agenda.GetEvents(ctx, &agenda.GetEventsRequest{ Namespaces: []string{"parcoursmob_dispositifs"}, Mindate: timestamppb.Now(), }) if err == nil { mu.Lock() for _, e := range eventsresp.Events { events = append(events, e.ToStorageType()) } sort.Sort(sorting.EventsByStartdate(events)) mu.Unlock() } }() // Get bookings wg.Add(1) go func() { defer wg.Done() bookingsresp, err := h.services.GRPC.Fleets.GetBookings(ctx, &fleets.GetBookingsRequest{ Namespaces: []string{"parcoursmob_dispositifs"}, }) if err == nil { mu.Lock() for _, b := range bookingsresp.Bookings { if b.Enddate.AsTime().After(time.Now()) { bookings = append(bookings, b.ToStorageType()) } } mu.Unlock() } }() // Get solidarity transport drivers wg.Add(1) go func() { defer wg.Done() solidarityRequest := &mobilityaccounts.GetAccountsRequest{ Namespaces: []string{"solidarity_drivers"}, } solidarityResp, err := h.services.GRPC.MobilityAccounts.GetAccounts(ctx, solidarityRequest) if err == nil { mu.Lock() for _, account := range solidarityResp.Accounts { // Only include non-archived drivers with addresses if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived { if address, ok := account.Data.AsMap()["address"]; ok && address != nil { // Apply geography filter if specified if len(driverAddressPolygons) > 0 { if addr, ok := account.Data.AsMap()["address"].(map[string]interface{}); ok { jsonAddr, err := json.Marshal(addr) if err == nil { addrGeojson, err := geojson.UnmarshalFeature(jsonAddr) if err == nil && addrGeojson.Geometry != nil { if point, ok := addrGeojson.Geometry.(orb.Point); ok { if isPointInGeographies(point, driverAddressPolygons) { solidarityDrivers = append(solidarityDrivers, account.ToStorageType()) } } } } } } else { solidarityDrivers = append(solidarityDrivers, account.ToStorageType()) } } } } mu.Unlock() } }() // Get organized carpool drivers wg.Add(1) go func() { defer wg.Done() carpoolRequest := &mobilityaccounts.GetAccountsRequest{ Namespaces: []string{"organized_carpool_drivers"}, } carpoolResp, err := h.services.GRPC.MobilityAccounts.GetAccounts(ctx, carpoolRequest) if err == nil { mu.Lock() for _, account := range carpoolResp.Accounts { // Only include non-archived drivers with addresses if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived { if address, ok := account.Data.AsMap()["address"]; ok && address != nil { // Apply geography filter if specified if len(driverAddressPolygons) > 0 { if addr, ok := account.Data.AsMap()["address"].(map[string]interface{}); ok { jsonAddr, err := json.Marshal(addr) if err == nil { addrGeojson, err := geojson.UnmarshalFeature(jsonAddr) if err == nil && addrGeojson.Geometry != nil { if point, ok := addrGeojson.Geometry.(orb.Point); ok { if isPointInGeographies(point, driverAddressPolygons) { organizedCarpoolDrivers = append(organizedCarpoolDrivers, account.ToStorageType()) } } } } } } else { organizedCarpoolDrivers = append(organizedCarpoolDrivers, account.ToStorageType()) } } } } mu.Unlock() } }() // Wait for all goroutines to complete wg.Wait() return &DashboardResult{ Accounts: accounts, Members: members, Events: events, Bookings: bookings, SolidarityDrivers: solidarityDrivers, OrganizedCarpoolDrivers: organizedCarpoolDrivers, }, nil }