928 lines
26 KiB
Go
928 lines
26 KiB
Go
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
|
|
}
|