package application import ( "bytes" "context" "encoding/json" "errors" "fmt" "image/png" "io" "net/http" "sort" "strings" "time" formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators" "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification" profilepictures "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/profile-pictures" "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting" filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage" agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi" agendastorage "git.coopgo.io/coopgo-platform/agenda/storage" "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" "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/solidarity-transport/servers/grpc/proto/gen" solidaritytransformers "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers" solidaritytypes "git.coopgo.io/coopgo-platform/solidarity-transport/types" "github.com/google/uuid" "github.com/rs/zerolog/log" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" ) type BeneficiariesResult struct { Accounts []mobilityaccountsstorage.Account CacheID string } func (h *ApplicationHandler) GetBeneficiaries(ctx context.Context, searchFilter string, archivedFilter bool) (*BeneficiariesResult, error) { accounts, err := h.getBeneficiariesWithFilters(ctx, searchFilter, archivedFilter) if err != nil { return nil, err } sort.Sort(sorting.BeneficiariesByName(accounts)) cacheID := uuid.NewString() h.cache.PutWithTTL(cacheID, accounts, 1*time.Hour) return &BeneficiariesResult{ Accounts: accounts, CacheID: cacheID, }, nil } func (h *ApplicationHandler) CreateBeneficiary(ctx context.Context, firstName, lastName, email string, birthdate *time.Time, phoneNumber, fileNumber string, address any, gender string, otherProperties any) (string, error) { // Get current group g := ctx.Value(identification.GroupKey) if g == nil { return "", fmt.Errorf("no group found in context") } group := g.(storage.Group) // Create data map for the beneficiary dataMap := map[string]any{ "first_name": firstName, "last_name": lastName, "email": email, "phone_number": phoneNumber, "file_number": fileNumber, "gender": gender, } // Convert birthdate to string format for structpb compatibility if birthdate != nil { dataMap["birthdate"] = birthdate.Format("2006-01-02") } if address != nil { dataMap["address"] = address } if otherProperties != nil { dataMap["other_properties"] = otherProperties } data, err := structpb.NewValue(dataMap) if err != nil { return "", err } request := &mobilityaccounts.RegisterRequest{ Account: &mobilityaccounts.Account{ Namespace: "parcoursmob_beneficiaries", Data: data.GetStructValue(), }, } resp, err := h.services.GRPC.MobilityAccounts.Register(ctx, request) if err != nil { return "", err } subscribe := &groupsmanagement.SubscribeRequest{ Groupid: group.ID, Memberid: resp.Account.Id, } _, err = h.services.GRPC.GroupsManagement.Subscribe(ctx, subscribe) if err != nil { return "", err } return resp.Account.Id, nil } type BeneficiaryDataResult struct { Account mobilityaccountsstorage.Account Bookings []fleetsstorage.Booking Organizations []any Documents []filestorage.FileInfo EventsList []Event_Beneficiary SolidarityTransportStats map[string]int64 SolidarityTransportBookings []*solidaritytypes.Booking SolidarityDriversMap map[string]mobilityaccountsstorage.Account OrganizedCarpoolStats map[string]int64 OrganizedCarpoolBookings []*proto.CarpoolServiceBooking OrganizedCarpoolDriversMap map[string]mobilityaccountsstorage.Account WalletBalance float64 } func (h *ApplicationHandler) GetBeneficiaryData(ctx context.Context, beneficiaryID string) (*BeneficiaryDataResult, error) { // Get beneficiary account request := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request) if err != nil { return nil, err } // Security check: ensure this is actually a beneficiary account if resp.Account.Namespace != "parcoursmob_beneficiaries" { return nil, fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, resp.Account.Namespace) } account := resp.Account.ToStorageType() // Get documents documents := h.filestorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + beneficiaryID) // Get events subscriptions subscriptionRequest := &agenda.GetSubscriptionByUserRequest{ Subscriber: beneficiaryID, } subscriptionResp, err := h.services.GRPC.Agenda.GetSubscriptionByUser(ctx, subscriptionRequest) if err != nil { return nil, err } events := []agendastorage.Event{} currentTime := time.Now().Truncate(24 * time.Hour) for _, e := range subscriptionResp.Subscription { eventRequest := &agenda.GetEventRequest{ Id: e.Eventid, } eventResp, err := h.services.GRPC.Agenda.GetEvent(ctx, eventRequest) if err != nil { return nil, err } events = append(events, eventResp.Event.ToStorageType()) } sort.Sort(sorting.EventsByStartdate(events)) // Get bookings bookingsRequest := &fleets.GetDriverBookingsRequest{ Driver: beneficiaryID, } bookingsResp, err := h.services.GRPC.Fleets.GetDriverBookings(ctx, bookingsRequest) if err != nil { return nil, err } bookings := []fleetsstorage.Booking{} for _, b := range bookingsResp.Bookings { bookings = append(bookings, b.ToStorageType()) } // Build events list var eventsList []Event_Beneficiary var statusEvent int for _, e := range events { if e.Startdate.After(currentTime) { statusEvent = 1 } else if e.Startdate.Before(currentTime) && e.Enddate.After(currentTime) || e.Enddate.Equal(currentTime) { statusEvent = 2 } else { statusEvent = 3 } event := Event{ NameVal: e.Name, DateVal: e.Startdate, DateEndVal: e.Enddate, TypeVal: e.Type, IDVal: e.ID, DbVal: "/app/agenda/", IconSet: "calendar", StatusVal: statusEvent, } eventsList = append(eventsList, event) } // Add vehicle bookings to events list var statusBooking int for _, b := range bookings { if b.Enddate.After(currentTime) || b.Enddate.Equal(currentTime) { getVehicleRequest := &fleets.GetVehicleRequest{ Vehicleid: b.Vehicleid, } getVehicleResp, err := h.services.GRPC.Fleets.GetVehicle(ctx, getVehicleRequest) if err != nil { return nil, err } if b.Startdate.After(currentTime) { statusBooking = 1 } else if b.Startdate.Before(currentTime) && b.Enddate.After(currentTime) || b.Enddate.Equal(currentTime) { statusBooking = 2 } else { statusBooking = 3 } event := Event{ NameVal: getVehicleResp.Vehicle.ToStorageType().Data["name"].(string), DateVal: b.Startdate, DateEndVal: b.Enddate, TypeVal: "Réservation de véhicule", IDVal: b.ID, DbVal: "/app/vehicles-management/bookings/", IconSet: "vehicle", StatusVal: statusBooking, } eventsList = append(eventsList, event) } } // Get solidarity transport bookings (all statuses for display) solidarityResp, err := h.services.GRPC.SolidarityTransport.GetSolidarityTransportBookings(ctx, &gen.GetSolidarityTransportBookingsRequest{ Passengerid: beneficiaryID, StartDate: timestamppb.New(time.Now().Add(-365 * 24 * time.Hour)), EndDate: timestamppb.New(time.Now().Add(365 * 24 * time.Hour)), }) protoBookings := []*gen.SolidarityTransportBooking{} if err == nil { protoBookings = solidarityResp.Bookings } else { log.Error().Err(err).Msg("error retrieving solidarity transport bookings for beneficiary") } // Convert proto bookings to types with geojson.Feature solidarityTransportBookings := []*solidaritytypes.Booking{} for _, protoBooking := range protoBookings { booking, err := solidaritytransformers.BookingProtoToType(protoBooking) if err != nil { log.Error().Err(err).Msg("error converting booking proto to type") continue } solidarityTransportBookings = append(solidarityTransportBookings, booking) } // Collect unique driver IDs driverIDs := []string{} driverIDsMap := make(map[string]bool) for _, booking := range solidarityTransportBookings { if booking.DriverId != "" { if !driverIDsMap[booking.DriverId] { driverIDs = append(driverIDs, booking.DriverId) driverIDsMap[booking.DriverId] = true } } } // Get drivers in batch driversMap := make(map[string]mobilityaccountsstorage.Account) if len(driverIDs) > 0 { driversResp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, &mobilityaccounts.GetAccountsBatchRequest{ Accountids: driverIDs, }) if err == nil { for _, account := range driversResp.Accounts { a := account.ToStorageType() driversMap[a.ID] = a } } } // Calculate stats only for validated bookings solidarityTransportStats := map[string]int64{ "count": 0, "km": 0, } for _, b := range solidarityTransportBookings { if b.Status == "VALIDATED" { solidarityTransportStats["count"] = solidarityTransportStats["count"] + 1 if b.Journey != nil { solidarityTransportStats["km"] = solidarityTransportStats["km"] + b.Journey.PassengerDistance } // Add to events list event := Event{ NameVal: fmt.Sprintf("%s (%d km)", b.Journey.PassengerDrop.Properties.MustString("label", ""), b.Journey.PassengerDistance), DateVal: b.Journey.PassengerPickupDate, DateEndVal: b.Journey.PassengerPickupDate, TypeVal: "Transport solidaire", IDVal: b.Id, DbVal: "/app/solidarity-transport/bookings/", IconSet: "vehicle", StatusVal: 1, } eventsList = append(eventsList, event) } } // Get organized carpool bookings carpoolBookingsResp, err := h.services.GRPC.CarpoolService.GetUserBookings(ctx, &proto.GetUserBookingsRequest{ UserId: beneficiaryID, }) organizedCarpoolBookings := []*proto.CarpoolServiceBooking{} if err == nil { organizedCarpoolBookings = carpoolBookingsResp.Bookings } else { log.Error().Err(err).Msg("error retrieving organized carpool bookings for beneficiary") } // Collect unique driver IDs from organized carpool bookings carpoolDriverIDs := []string{} carpoolDriverIDsMap := make(map[string]bool) for _, booking := range organizedCarpoolBookings { if booking.Driver != nil && booking.Driver.Id != "" { if !carpoolDriverIDsMap[booking.Driver.Id] { carpoolDriverIDs = append(carpoolDriverIDs, booking.Driver.Id) carpoolDriverIDsMap[booking.Driver.Id] = true } } } // Get organized carpool drivers in batch organizedCarpoolDriversMap := make(map[string]mobilityaccountsstorage.Account) if len(carpoolDriverIDs) > 0 { carpoolDriversResp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, &mobilityaccounts.GetAccountsBatchRequest{ Accountids: carpoolDriverIDs, }) if err == nil { for _, account := range carpoolDriversResp.Accounts { a := account.ToStorageType() organizedCarpoolDriversMap[a.ID] = a } } } // Calculate organized carpool stats (only confirmed bookings) organizedCarpoolStats := map[string]int64{ "count": 0, "km": 0, } for _, cb := range organizedCarpoolBookings { if cb.Status == proto.CarpoolServiceBookingStatus_CONFIRMED { organizedCarpoolStats["count"]++ if cb.Distance != nil { organizedCarpoolStats["km"] += *cb.Distance } // Build journey name from drop address and distance for events journeyName := "Covoiturage" if cb.PassengerDropAddress != nil { if cb.Distance != nil { journeyName = fmt.Sprintf("%s (%d km)", *cb.PassengerDropAddress, *cb.Distance) } else { journeyName = *cb.PassengerDropAddress } } // Get departure date departureDate := time.Now() if cb.PassengerPickupDate != nil { departureDate = cb.PassengerPickupDate.AsTime() } event := Event{ NameVal: journeyName, DateVal: departureDate, DateEndVal: departureDate, TypeVal: "Covoiturage solidaire", IDVal: cb.Id, DbVal: "/app/organized-carpool/bookings/", IconSet: "vehicle", StatusVal: 1, } eventsList = append(eventsList, event) } } sortByDate(eventsList) // Get organizations groupsRequest := &groupsmanagement.GetGroupsRequest{ Namespaces: []string{"parcoursmob_organizations"}, Member: beneficiaryID, } groupsResp, err := h.services.GRPC.GroupsManagement.GetGroups(ctx, groupsRequest) if err != nil { return nil, err } organizations := []any{} for _, o := range groupsResp.Groups { organizations = append(organizations, o.ToStorageType()) } // Calculate wallet balance walletBalance := h.calculateWalletBalance(account) return &BeneficiaryDataResult{ Account: account, Bookings: bookings, Organizations: organizations, Documents: documents, EventsList: eventsList, SolidarityTransportStats: solidarityTransportStats, SolidarityTransportBookings: solidarityTransportBookings, SolidarityDriversMap: driversMap, OrganizedCarpoolStats: organizedCarpoolStats, OrganizedCarpoolBookings: organizedCarpoolBookings, OrganizedCarpoolDriversMap: organizedCarpoolDriversMap, WalletBalance: walletBalance, }, nil } type BeneficiaryResult struct { Account mobilityaccountsstorage.Account } func (h *ApplicationHandler) GetBeneficiary(ctx context.Context, beneficiaryID string) (*BeneficiaryResult, error) { request := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request) if err != nil { return nil, err } // Security check: ensure this is actually a beneficiary account if resp.Account.Namespace != "parcoursmob_beneficiaries" { return nil, fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, resp.Account.Namespace) } return &BeneficiaryResult{ Account: resp.Account.ToStorageType(), }, nil } func (h *ApplicationHandler) UpdateBeneficiary(ctx context.Context, beneficiaryID, firstName, lastName, email string, birthdate *time.Time, phoneNumber, fileNumber string, address any, gender string, otherProperties any) (string, error) { // Security check: verify the account exists and is a beneficiary getRequest := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest) if err != nil { return "", err } if getResp.Account.Namespace != "parcoursmob_beneficiaries" { return "", fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace) } // Create data map for the beneficiary dataMap := map[string]any{ "first_name": firstName, "last_name": lastName, "email": email, "phone_number": phoneNumber, "file_number": fileNumber, "gender": gender, } // Handle birthdate conversion for protobuf compatibility if birthdate != nil { dataMap["birthdate"] = birthdate.Format(time.RFC3339) } if address != nil { dataMap["address"] = address } if otherProperties != nil { dataMap["other_properties"] = otherProperties } data, err := structpb.NewValue(dataMap) if err != nil { return "", err } request := &mobilityaccounts.UpdateDataRequest{ Account: &mobilityaccounts.Account{ Id: beneficiaryID, Namespace: "parcoursmob_beneficiaries", Data: data.GetStructValue(), }, } resp, err := h.services.GRPC.MobilityAccounts.UpdateData(ctx, request) if err != nil { return "", err } return resp.Account.Id, nil } func (h *ApplicationHandler) ArchiveBeneficiary(ctx context.Context, beneficiaryID string) error { // Security check: verify the account exists and is a beneficiary getRequest := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest) if err != nil { return err } if getResp.Account.Namespace != "parcoursmob_beneficiaries" { return fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace) } data, err := structpb.NewValue(map[string]any{ "archived": true, }) if err != nil { return err } request := &mobilityaccounts.UpdateDataRequest{ Account: &mobilityaccounts.Account{ Id: beneficiaryID, Namespace: "parcoursmob_beneficiaries", Data: data.GetStructValue(), }, } _, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, request) return err } func (h *ApplicationHandler) UnarchiveBeneficiary(ctx context.Context, beneficiaryID string) error { // Security check: verify the account exists and is a beneficiary getRequest := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest) if err != nil { return err } if getResp.Account.Namespace != "parcoursmob_beneficiaries" { return fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace) } data, err := structpb.NewValue(map[string]any{ "archived": false, }) if err != nil { return err } request := &mobilityaccounts.UpdateDataRequest{ Account: &mobilityaccounts.Account{ Id: beneficiaryID, Namespace: "parcoursmob_beneficiaries", Data: data.GetStructValue(), }, } _, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, request) return err } func (h *ApplicationHandler) GetBeneficiaryPicture(ctx context.Context, beneficiaryID string) ([]byte, string, error) { request := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request) if err != nil { return nil, "", err } // Security check: ensure this is actually a beneficiary account // if resp.Account.Namespace != "parcoursmob_beneficiaries" { // return nil, "", fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, resp.Account.Namespace) // } account := resp.Account.ToStorageType() firstName, ok := account.Data["first_name"].(string) if !ok || firstName == "" { firstName = "U" } lastName, ok := account.Data["last_name"].(string) if !ok || lastName == "" { lastName = "U" } initials := strings.ToUpper(string(firstName[0]) + string(lastName[0])) picture := profilepictures.DefaultProfilePicture(initials) buffer := new(bytes.Buffer) if err := png.Encode(buffer, picture); err != nil { return nil, "", err } return buffer.Bytes(), "image/png", nil } func (h *ApplicationHandler) AddBeneficiaryDocument(ctx context.Context, beneficiaryID string, file io.Reader, filename string, fileSize int64, documentType, documentName string) error { // Security check: verify the account exists and is a beneficiary getRequest := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest) if err != nil { return err } if getResp.Account.Namespace != "parcoursmob_beneficiaries" { return fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace) } fileid := uuid.NewString() metadata := map[string]string{ "type": documentType, "name": documentName, } if err := h.filestorage.Put(file, filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s_%s", beneficiaryID, fileid, filename), fileSize, metadata); err != nil { return err } return nil } func (h *ApplicationHandler) GetBeneficiaryDocument(ctx context.Context, beneficiaryID, document string) (io.Reader, *filestorage.FileInfo, error) { // Security check: verify the account exists and is a beneficiary getRequest := &mobilityaccounts.GetAccountRequest{ Id: beneficiaryID, } getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest) if err != nil { return nil, nil, err } if getResp.Account.Namespace != "parcoursmob_beneficiaries" { return nil, nil, fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace) } file, info, err := h.filestorage.Get(filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s", beneficiaryID, document)) if err != nil { return nil, nil, err } return file, info, nil } func (h *ApplicationHandler) DeleteBeneficiaryDocument(ctx context.Context, beneficiaryID, document string) error { return h.DeleteDocument(ctx, BeneficiaryDocumentConfig, beneficiaryID, document) } func (h *ApplicationHandler) getBeneficiariesWithFilters(ctx context.Context, searchFilter string, archivedFilter bool) ([]mobilityaccountsstorage.Account, error) { accounts := []mobilityaccountsstorage.Account{} g := ctx.Value(identification.GroupKey) if g == nil { return accounts, errors.New("no group provided") } group := g.(storage.Group) request := &mobilityaccounts.GetAccountsBatchRequest{ Accountids: group.Members, } resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, request) if err != nil { log.Error().Err(err).Msg("issue in mobilityaccounts call") return accounts, err } for _, account := range resp.Accounts { if h.filterAccount(account, searchFilter, archivedFilter) { a := account.ToStorageType() accounts = append(accounts, a) } } return accounts, err } func (h *ApplicationHandler) filterAccount(a *mobilityaccounts.Account, searchFilter string, archivedFilter bool) bool { // Search filter if searchFilter != "" { name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string) if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter)) { return false } } // Archived filter if archivedFilter { if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived { return true } return false } else { if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived { return false } } return true } type Event_Beneficiary interface { Name() string Date() time.Time DateEnd() time.Time Type() string Db() string ID() string Icons() string Status() int } type Event struct { IDVal string NameVal string DateVal time.Time DateEndVal time.Time TypeVal string DbVal string Deleted bool IconSet string StatusVal int } func (e Event) Name() string { return e.NameVal } func (e Event) Date() time.Time { return e.DateVal } func (e Event) DateEnd() time.Time { return e.DateEndVal } func (e Event) Type() string { return e.TypeVal } func (e Event) ID() string { return e.IDVal } func (e Event) Db() string { return e.DbVal } func (e Event) Icons() string { return e.IconSet } func (e Event) Status() int { return e.StatusVal } func sortByDate(events []Event_Beneficiary) { sort.Slice(events, func(i, j int) bool { return events[i].Date().After(events[j].Date()) }) } // Utility functions needed by other modules func filterAccount(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 } } archivedFilter, ok := r.URL.Query()["archived"] if ok && archivedFilter[0] == "true" { if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived { return true } return false } else { if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived { return false } } return true } func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]mobilityaccountsstorage.Account, error) { accounts := []mobilityaccountsstorage.Account{} g := r.Context().Value(identification.GroupKey) if g == nil { return accounts, errors.New("no group provided") } group := g.(storage.Group) request := &mobilityaccounts.GetAccountsBatchRequest{ Accountids: group.Members, } resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), request) if err != nil { log.Error().Err(err).Msg("issue in mobilityaccounts call") return accounts, err } for _, account := range resp.Accounts { if filterAccount(r, account) { a := account.ToStorageType() accounts = append(accounts, a) } } return accounts, err } type BeneficiariesForm 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"` Gender string `json:"gender"` OtherProperties any `json:"other_properties,omitempty"` } func parseBeneficiariesForm(r *http.Request) (map[string]any, error) { if err := r.ParseForm(); err != nil { return nil, err } 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 := BeneficiariesForm{ 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("other_properties") != "" { var a any json.Unmarshal([]byte(r.PostFormValue("other_properties")), &a) formData.OtherProperties = 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 }