parcoursmob/core/application/agenda.go

603 lines
16 KiB
Go
Executable File

package application
import (
"context"
"fmt"
"io"
"sort"
"time"
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
"git.coopgo.io/coopgo-apps/parcoursmob/services"
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"
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"
ics "github.com/arran4/golang-ical"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
type AgendaEventsResult struct {
Events []agendastorage.Event
Groups map[string]any
}
func (h *ApplicationHandler) GetAgendaEvents(ctx context.Context, minDate, maxDate *time.Time) (*AgendaEventsResult, error) {
request := &agenda.GetEventsRequest{
Namespaces: []string{"parcoursmob_dispositifs"},
}
if minDate != nil {
request.Mindate = timestamppb.New(*minDate)
}
if maxDate != nil {
request.Maxdate = timestamppb.New(*maxDate)
}
resp, err := h.services.GRPC.Agenda.GetEvents(ctx, request)
if err != nil {
return nil, err
}
responses := []agendastorage.Event{}
groupids := []string{}
for _, e := range resp.Events {
groupids = append(groupids, e.Owners...)
responses = append(responses, e.ToStorageType())
}
sort.Sort(sorting.EventsByStartdate(responses))
groups := map[string]any{}
if len(groupids) > 0 {
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(ctx, &groupsmanagement.GetGroupsBatchRequest{
Groupids: groupids,
})
if err == nil {
for _, g := range groupsresp.Groups {
groups[g.Id] = g.ToStorageType()
}
}
}
return &AgendaEventsResult{
Events: responses,
Groups: groups,
}, nil
}
func (h *ApplicationHandler) CreateAgendaEvent(ctx context.Context, name, eventType, description string, address any, allday bool, startdate, enddate *time.Time, starttime, endtime string, maxSubscribers int, file io.Reader, filename string, fileSize int64, documentType, documentName string) (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)
data, _ := structpb.NewStruct(map[string]any{
"address": address,
})
request := &agenda.CreateEventRequest{
Event: &agenda.Event{
Namespace: "parcoursmob_dispositifs",
Owners: []string{group.ID},
Type: eventType,
Name: name,
Description: description,
Startdate: timestamppb.New(*startdate),
Enddate: timestamppb.New(*enddate),
Starttime: starttime,
Endtime: endtime,
Allday: allday,
MaxSubscribers: int64(maxSubscribers),
Data: data,
Deleted: false,
},
}
resp, err := h.services.GRPC.Agenda.CreateEvent(ctx, request)
if err != nil {
return "", err
}
// Handle file upload if provided
if file != nil && filename != "" {
fileid := uuid.NewString()
metadata := map[string]string{
"file_type": documentType,
"file_name": documentName,
}
if err := h.filestorage.Put(file, filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s_%s", resp.Event.Id, fileid, filename), fileSize, metadata); err != nil {
return "", err
}
}
return resp.Event.Id, nil
}
type AgendaEventResult struct {
Event agendastorage.Event
Group storage.Group
Documents []filestorage.FileInfo
Subscribers map[string]any
Accounts []any
}
func (h *ApplicationHandler) GetAgendaEvent(ctx context.Context, eventID string) (*AgendaEventResult, error) {
request := &agenda.GetEventRequest{
Id: eventID,
}
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, request)
if err != nil {
return nil, err
}
grouprequest := &groupsmanagement.GetGroupRequest{
Id: resp.Event.Owners[0],
}
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, grouprequest)
if err != nil {
return nil, err
}
subscribers := map[string]any{}
accids := []string{}
for _, v := range resp.Event.Subscriptions {
accids = append(accids, v.Subscriber)
}
if len(accids) > 0 {
subscriberresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
ctx,
&mobilityaccounts.GetAccountsBatchRequest{
Accountids: accids,
},
)
if err == nil {
for _, sub := range subscriberresp.Accounts {
subscribers[sub.Id] = sub.ToStorageType()
}
}
}
g := ctx.Value(identification.GroupKey)
if g == nil {
return nil, fmt.Errorf("no group found in context")
}
group := g.(storage.Group)
accountids := []string{}
for _, m := range group.Members {
if !contains(resp.Event.Subscriptions, m) {
accountids = append(accountids, m)
}
}
accounts := []any{}
if len(accountids) > 0 {
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
ctx,
&mobilityaccounts.GetAccountsBatchRequest{
Accountids: accountids,
},
)
if err == nil {
for _, acc := range accountresp.Accounts {
accounts = append(accounts, acc)
}
}
}
documents := h.filestorage.List(filestorage.PREFIX_AGENDA + "/" + eventID)
return &AgendaEventResult{
Event: resp.Event.ToStorageType(),
Group: groupresp.Group.ToStorageType(),
Documents: documents,
Subscribers: subscribers,
Accounts: accounts,
}, nil
}
func (h *ApplicationHandler) SubscribeToAgendaEvent(ctx context.Context, eventID, subscriber string, subscriptionData map[string]any) error {
datapb, err := structpb.NewStruct(subscriptionData)
if err != nil {
return err
}
request := &agenda.SubscribeEventRequest{
Eventid: eventID,
Subscriber: subscriber,
Data: datapb,
}
_, err = h.services.GRPC.Agenda.SubscribeEvent(ctx, request)
return err
}
func (h *ApplicationHandler) UnsubscribeFromAgendaEvent(ctx context.Context, eventID, subscribeID, motif, currentUserID, currentUserDisplayName, currentUserEmail, currentGroupID, currentGroupName string) error {
// Get the event first
request := &agenda.GetEventRequest{
Id: eventID,
}
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, request)
if err != nil {
return err
}
// Find subscription data for the subscriber being removed
var s_b_id, s_b_name, s_b_email, s_b_group_id, s_b_group_name string
for i := range resp.Event.Subscriptions {
if resp.Event.Subscriptions[i].Subscriber == subscribeID {
s_b_id = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["id"].GetStringValue()
s_b_name = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["display_name"].GetStringValue()
s_b_email = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["email"].GetStringValue()
s_b_group_id = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["group"].GetStructValue().Fields["id"].GetStringValue()
s_b_group_name = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["group"].GetStructValue().Fields["name"].GetStringValue()
break
}
}
data := map[string]any{
"subscribed_by": map[string]any{
"user": map[string]any{
"id": s_b_id,
"display_name": s_b_name,
"email": s_b_email,
},
"group": map[string]any{
"id": s_b_group_id,
"name": s_b_group_name,
},
},
"unsubscribed_by": map[string]any{
"user": map[string]any{
"id": currentUserID,
"display_name": currentUserDisplayName,
"email": currentUserEmail,
},
"group": map[string]any{
"id": currentGroupID,
"name": currentGroupName,
},
},
"motif": motif,
}
datapb, err := structpb.NewStruct(data)
if err != nil {
return err
}
deleteRequest := &agenda.DeleteSubscriptionRequest{
Subscriber: subscribeID,
Eventid: eventID,
Data: datapb,
}
_, err = h.services.GRPC.Agenda.DeleteSubscription(ctx, deleteRequest)
if err != nil {
return err
}
// Send email notification
emailData := map[string]any{
"motif": motif,
"user": currentUserDisplayName,
"subscriber": fmt.Sprintf("http://localhost:9000/app/beneficiaries/%s", subscribeID),
"link": fmt.Sprintf("http://localhost:9000/app/agenda/%s", eventID),
}
if err := h.emailing.Send("delete_subscriber.request", s_b_email, emailData); err != nil {
log.Error().Err(err).Msg("Cannot send email")
// Don't return error for email failure
}
return nil
}
type AgendaEventHistoryResult struct {
Event agendastorage.Event
Group storage.Group
Subscribers map[string]any
Accounts []any
}
func (h *ApplicationHandler) GetAgendaEventHistory(ctx context.Context, eventID string) (*AgendaEventHistoryResult, error) {
request := &agenda.GetEventRequest{
Id: eventID,
}
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, request)
if err != nil {
return nil, err
}
grouprequest := &groupsmanagement.GetGroupRequest{
Id: resp.Event.Owners[0],
}
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, grouprequest)
if err != nil {
return nil, err
}
subscribers := map[string]any{}
accids := []string{}
for _, v := range resp.Event.DeletedSubscription {
accids = append(accids, v.Subscriber)
}
if len(accids) > 0 {
subscriberresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
ctx,
&mobilityaccounts.GetAccountsBatchRequest{
Accountids: accids,
},
)
if err == nil {
for _, sub := range subscriberresp.Accounts {
subscribers[sub.Id] = sub.ToStorageType()
}
}
}
g := ctx.Value(identification.GroupKey)
if g == nil {
return nil, fmt.Errorf("no group found in context")
}
group := g.(storage.Group)
accountids := []string{}
for _, m := range group.Members {
if !contains(resp.Event.DeletedSubscription, m) {
accountids = append(accountids, m)
}
}
accounts := []any{}
if len(accountids) > 0 {
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
ctx,
&mobilityaccounts.GetAccountsBatchRequest{
Accountids: accountids,
},
)
if err == nil {
for _, acc := range accountresp.Accounts {
accounts = append(accounts, acc)
}
}
}
return &AgendaEventHistoryResult{
Event: resp.Event.ToStorageType(),
Group: groupresp.Group.ToStorageType(),
Subscribers: subscribers,
Accounts: accounts,
}, nil
}
func (h *ApplicationHandler) AddEventDocument(ctx context.Context, eventID string, file io.Reader, filename string, fileSize int64, documentType, documentName string) error {
fileid := uuid.NewString()
metadata := map[string]string{
"type": documentType,
"name": documentName,
}
if err := h.filestorage.Put(file, filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s_%s", eventID, fileid, filename), fileSize, metadata); err != nil {
return err
}
return nil
}
func (h *ApplicationHandler) GetEventDocument(ctx context.Context, eventID, document string) (io.Reader, *filestorage.FileInfo, error) {
file, info, err := h.filestorage.Get(filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s", eventID, document))
if err != nil {
return nil, nil, err
}
return file, info, nil
}
func contains(s []*agenda.Subscription, e string) bool {
for _, a := range s {
if a.Subscriber == e {
return true
}
}
return false
}
func (h *ApplicationHandler) UpdateAgendaEvent(ctx context.Context, eventID, name, eventType, description string, address any, allday bool, startdate, enddate *time.Time, starttime, endtime string, maxSubscribers int) (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)
// Get existing event first
getRequest := &agenda.GetEventRequest{
Id: eventID,
}
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, getRequest)
if err != nil {
return "", err
}
data, _ := structpb.NewStruct(map[string]any{
"address": address,
})
request := &agenda.UpdateEventRequest{
Event: &agenda.Event{
Namespace: "parcoursmob_dispositifs",
Id: eventID,
Owners: []string{group.ID},
Type: eventType,
Name: name,
Description: description,
Startdate: timestamppb.New(*startdate),
Enddate: timestamppb.New(*enddate),
Starttime: starttime,
Endtime: endtime,
Allday: allday,
MaxSubscribers: int64(maxSubscribers),
Data: data,
Subscriptions: resp.Event.Subscriptions,
},
}
updateResp, err := h.services.GRPC.Agenda.UpdateEvent(ctx, request)
if err != nil {
return "", err
}
return updateResp.Event.Id, nil
}
func (h *ApplicationHandler) DeleteAgendaEvent(ctx context.Context, eventID string) error {
request := &agenda.GetEventRequest{
Id: eventID,
}
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, request)
if err != nil {
return err
}
updateRequest := &agenda.UpdateEventRequest{
Event: &agenda.Event{
Namespace: resp.Event.Namespace,
Id: resp.Event.Id,
Owners: resp.Event.Owners,
Type: resp.Event.Type,
Name: resp.Event.Name,
Description: resp.Event.Description,
Startdate: resp.Event.Startdate,
Enddate: resp.Event.Enddate,
Starttime: resp.Event.Starttime,
Endtime: resp.Event.Endtime,
Allday: resp.Event.Allday,
MaxSubscribers: int64(resp.Event.MaxSubscribers),
Data: resp.Event.Data,
Subscriptions: resp.Event.Subscriptions,
Deleted: true,
},
}
_, err = h.services.GRPC.Agenda.UpdateEvent(ctx, updateRequest)
return err
}
type CalendarResult struct {
CalendarData string
}
func (h *ApplicationHandler) GenerateGlobalCalendar(ctx context.Context) (*CalendarResult, error) {
events, err := h.services.GetAgendaEvents()
if err != nil {
log.Error().Err(err).Msg("error retrieving agenda events")
return nil, err
}
calendar, err := h.icsCalendar(events)
if err != nil {
return nil, err
}
return &CalendarResult{
CalendarData: calendar.Serialize(),
}, nil
}
func (h *ApplicationHandler) GenerateOrganizationCalendar(ctx context.Context, groupID string) (*CalendarResult, error) {
events, err := h.services.GetAgendaEvents()
if err != nil {
log.Error().Err(err).Msg("error retrieving agenda events")
return nil, err
}
filteredEvents := []services.AgendaEvent{}
for _, e := range events {
for _, g := range e.Owners {
if g == groupID {
filteredEvents = append(filteredEvents, e)
break
}
}
}
calendar, err := h.icsCalendar(filteredEvents)
if err != nil {
return nil, err
}
return &CalendarResult{
CalendarData: calendar.Serialize(),
}, nil
}
func (h *ApplicationHandler) icsCalendar(events []services.AgendaEvent) (*ics.Calendar, error) {
calendar := ics.NewCalendarFor(h.config.GetString("service_name"))
for _, e := range events {
vevent := ics.NewEvent(e.ID)
vevent.SetSummary(e.Name)
vevent.SetDescription(e.Description)
if e.Allday {
vevent.SetAllDayStartAt(e.Startdate)
if e.Enddate.After(e.Startdate) {
vevent.SetAllDayEndAt(e.Enddate.Add(24 * time.Hour))
}
} else {
timeloc, err := time.LoadLocation("Europe/Paris")
if err != nil {
log.Error().Err(err).Msg("Tried to load timezone location Europe/Paris. Error. Missing zones in container ?")
return nil, err
}
vevent.SetStartAt(e.Startdate.In(timeloc))
vevent.SetEndAt(e.Enddate.In(timeloc))
}
calendar.AddVEvent(vevent)
}
return calendar, nil
}