Event creation.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Helpers;
|
||||
|
||||
public sealed record PreparedCalendarEventCreateModel(
|
||||
CalendarItem CalendarItem,
|
||||
List<CalendarEventAttendee> Attendees,
|
||||
List<Reminder> Reminders);
|
||||
|
||||
public static class CalendarEventComposeMapper
|
||||
{
|
||||
public static PreparedCalendarEventCreateModel Prepare(CalendarEventComposeResult composeResult, AccountCalendar assignedCalendar, Guid? calendarItemId = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(composeResult);
|
||||
ArgumentNullException.ThrowIfNull(assignedCalendar);
|
||||
|
||||
var itemId = calendarItemId ?? Guid.NewGuid();
|
||||
var effectiveTimeZoneId = string.IsNullOrWhiteSpace(composeResult.TimeZoneId)
|
||||
? TimeZoneInfo.Local.Id
|
||||
: composeResult.TimeZoneId;
|
||||
var utcNow = DateTimeOffset.UtcNow;
|
||||
|
||||
var calendarItem = new CalendarItem
|
||||
{
|
||||
Id = itemId,
|
||||
CalendarId = assignedCalendar.Id,
|
||||
AssignedCalendar = assignedCalendar,
|
||||
Title = composeResult.Title?.Trim() ?? string.Empty,
|
||||
Description = composeResult.HtmlNotes ?? string.Empty,
|
||||
Location = composeResult.Location?.Trim() ?? string.Empty,
|
||||
StartDate = composeResult.StartDate,
|
||||
DurationInSeconds = Math.Max(0, (composeResult.EndDate - composeResult.StartDate).TotalSeconds),
|
||||
StartTimeZone = effectiveTimeZoneId,
|
||||
EndTimeZone = effectiveTimeZoneId,
|
||||
CreatedAt = utcNow,
|
||||
UpdatedAt = utcNow,
|
||||
Recurrence = composeResult.Recurrence ?? string.Empty,
|
||||
OrganizerDisplayName = assignedCalendar.MailAccount?.SenderName ?? string.Empty,
|
||||
OrganizerEmail = assignedCalendar.MailAccount?.Address ?? string.Empty,
|
||||
Status = CalendarItemStatus.Accepted,
|
||||
Visibility = CalendarItemVisibility.Public,
|
||||
ShowAs = composeResult.ShowAs,
|
||||
IsHidden = false,
|
||||
IsLocked = false
|
||||
};
|
||||
|
||||
var attendees = composeResult.Attendees?
|
||||
.Where(attendee => attendee != null)
|
||||
.Select(attendee => new CalendarEventAttendee
|
||||
{
|
||||
Id = attendee.Id == Guid.Empty ? Guid.NewGuid() : attendee.Id,
|
||||
CalendarItemId = itemId,
|
||||
Name = attendee.Name ?? string.Empty,
|
||||
Email = attendee.Email ?? string.Empty,
|
||||
Comment = attendee.Comment,
|
||||
AttendenceStatus = attendee.AttendenceStatus,
|
||||
IsOrganizer = attendee.IsOrganizer,
|
||||
IsOptionalAttendee = attendee.IsOptionalAttendee,
|
||||
ResolvedContact = attendee.ResolvedContact
|
||||
})
|
||||
.ToList() ?? [];
|
||||
|
||||
var reminders = composeResult.SelectedReminders?
|
||||
.Where(reminder => reminder != null)
|
||||
.Select(reminder => new Reminder
|
||||
{
|
||||
Id = reminder.Id == Guid.Empty ? Guid.NewGuid() : reminder.Id,
|
||||
CalendarItemId = itemId,
|
||||
DurationInSeconds = reminder.DurationInSeconds,
|
||||
ReminderType = reminder.ReminderType
|
||||
})
|
||||
.ToList() ?? [];
|
||||
|
||||
return new PreparedCalendarEventCreateModel(calendarItem, attendees, reminders);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.Graph.Models;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
namespace Wino.Core.Helpers;
|
||||
|
||||
public static class CalendarRecurrenceMapper
|
||||
{
|
||||
public static PatternedRecurrence CreateOutlookRecurrence(CalendarItem calendarItem)
|
||||
{
|
||||
if (calendarItem == null || string.IsNullOrWhiteSpace(calendarItem.Recurrence))
|
||||
return null;
|
||||
|
||||
var ruleLine = calendarItem.Recurrence
|
||||
.Split(Domain.Constants.CalendarEventRecurrenceRuleSeperator, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
.FirstOrDefault(line => line.StartsWith("RRULE:", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ruleLine))
|
||||
return null;
|
||||
|
||||
var components = ruleLine["RRULE:".Length..]
|
||||
.Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(part => part.Split('=', 2, StringSplitOptions.TrimEntries))
|
||||
.Where(parts => parts.Length == 2)
|
||||
.ToDictionary(parts => parts[0].ToUpperInvariant(), parts => parts[1], StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (!components.TryGetValue("FREQ", out var frequency))
|
||||
return null;
|
||||
|
||||
var pattern = new RecurrencePattern
|
||||
{
|
||||
Interval = ParseInt(components, "INTERVAL", 1),
|
||||
FirstDayOfWeek = DayOfWeekObject.Monday
|
||||
};
|
||||
|
||||
var byDays = ParseByDays(components);
|
||||
var startDate = calendarItem.StartDate;
|
||||
|
||||
switch (frequency.ToUpperInvariant())
|
||||
{
|
||||
case "DAILY":
|
||||
pattern.Type = RecurrencePatternType.Daily;
|
||||
break;
|
||||
case "WEEKLY":
|
||||
pattern.Type = RecurrencePatternType.Weekly;
|
||||
pattern.DaysOfWeek = byDays.Any()
|
||||
? byDays.Select(day => (DayOfWeekObject?)day).ToList()
|
||||
: [(DayOfWeekObject?)MapDay(startDate.DayOfWeek)];
|
||||
break;
|
||||
case "MONTHLY":
|
||||
if (byDays.Any())
|
||||
{
|
||||
pattern.Type = RecurrencePatternType.RelativeMonthly;
|
||||
pattern.DaysOfWeek = byDays.Select(day => (DayOfWeekObject?)day).ToList();
|
||||
pattern.Index = MapWeekIndex(startDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
pattern.Type = RecurrencePatternType.AbsoluteMonthly;
|
||||
pattern.DayOfMonth = ParseInt(components, "BYMONTHDAY", startDate.Day);
|
||||
}
|
||||
break;
|
||||
case "YEARLY":
|
||||
pattern.Month = ParseInt(components, "BYMONTH", startDate.Month);
|
||||
|
||||
if (byDays.Any())
|
||||
{
|
||||
pattern.Type = RecurrencePatternType.RelativeYearly;
|
||||
pattern.DaysOfWeek = byDays.Select(day => (DayOfWeekObject?)day).ToList();
|
||||
pattern.Index = MapWeekIndex(startDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
pattern.Type = RecurrencePatternType.AbsoluteYearly;
|
||||
pattern.DayOfMonth = ParseInt(components, "BYMONTHDAY", startDate.Day);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
var recurrenceRange = CreateRange(components, calendarItem);
|
||||
return new PatternedRecurrence
|
||||
{
|
||||
Pattern = pattern,
|
||||
Range = recurrenceRange
|
||||
};
|
||||
}
|
||||
|
||||
private static RecurrenceRange CreateRange(IReadOnlyDictionary<string, string> components, CalendarItem calendarItem)
|
||||
{
|
||||
var startDate = CreateDate(calendarItem.StartDate);
|
||||
|
||||
if (components.TryGetValue("UNTIL", out var untilValue) &&
|
||||
TryParseUntil(untilValue, out var untilDate))
|
||||
{
|
||||
return new RecurrenceRange
|
||||
{
|
||||
Type = RecurrenceRangeType.EndDate,
|
||||
StartDate = startDate,
|
||||
EndDate = CreateDate(untilDate),
|
||||
RecurrenceTimeZone = calendarItem.StartTimeZone
|
||||
};
|
||||
}
|
||||
|
||||
return new RecurrenceRange
|
||||
{
|
||||
Type = RecurrenceRangeType.NoEnd,
|
||||
StartDate = startDate,
|
||||
RecurrenceTimeZone = calendarItem.StartTimeZone
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryParseUntil(string untilValue, out DateTime untilDate)
|
||||
{
|
||||
untilDate = default;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(untilValue))
|
||||
return false;
|
||||
|
||||
return DateTime.TryParseExact(
|
||||
untilValue,
|
||||
["yyyyMMdd", "yyyyMMdd'T'HHmmss", "yyyyMMdd'T'HHmmss'Z'"],
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
||||
out untilDate)
|
||||
|| DateTime.TryParse(untilValue, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out untilDate);
|
||||
}
|
||||
|
||||
private static List<DayOfWeekObject> ParseByDays(IReadOnlyDictionary<string, string> components)
|
||||
{
|
||||
if (!components.TryGetValue("BYDAY", out var byDayValue) || string.IsNullOrWhiteSpace(byDayValue))
|
||||
return [];
|
||||
|
||||
return byDayValue
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(MapDay)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static int ParseInt(IReadOnlyDictionary<string, string> components, string key, int fallback)
|
||||
=> components.TryGetValue(key, out var value) && int.TryParse(value, out var parsedValue) ? parsedValue : fallback;
|
||||
|
||||
private static DayOfWeekObject MapDay(string dayToken)
|
||||
{
|
||||
return dayToken.ToUpperInvariant() switch
|
||||
{
|
||||
"MO" => DayOfWeekObject.Monday,
|
||||
"TU" => DayOfWeekObject.Tuesday,
|
||||
"WE" => DayOfWeekObject.Wednesday,
|
||||
"TH" => DayOfWeekObject.Thursday,
|
||||
"FR" => DayOfWeekObject.Friday,
|
||||
"SA" => DayOfWeekObject.Saturday,
|
||||
"SU" => DayOfWeekObject.Sunday,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(dayToken), dayToken, null)
|
||||
};
|
||||
}
|
||||
|
||||
private static DayOfWeekObject MapDay(DayOfWeek dayOfWeek)
|
||||
{
|
||||
return dayOfWeek switch
|
||||
{
|
||||
DayOfWeek.Monday => DayOfWeekObject.Monday,
|
||||
DayOfWeek.Tuesday => DayOfWeekObject.Tuesday,
|
||||
DayOfWeek.Wednesday => DayOfWeekObject.Wednesday,
|
||||
DayOfWeek.Thursday => DayOfWeekObject.Thursday,
|
||||
DayOfWeek.Friday => DayOfWeekObject.Friday,
|
||||
DayOfWeek.Saturday => DayOfWeekObject.Saturday,
|
||||
DayOfWeek.Sunday => DayOfWeekObject.Sunday,
|
||||
_ => DayOfWeekObject.Monday
|
||||
};
|
||||
}
|
||||
|
||||
private static WeekIndex MapWeekIndex(DateTime date)
|
||||
{
|
||||
var occurrence = ((date.Day - 1) / 7) + 1;
|
||||
|
||||
return occurrence switch
|
||||
{
|
||||
1 => WeekIndex.First,
|
||||
2 => WeekIndex.Second,
|
||||
3 => WeekIndex.Third,
|
||||
4 => WeekIndex.Fourth,
|
||||
_ => WeekIndex.Last
|
||||
};
|
||||
}
|
||||
|
||||
private static Date CreateDate(DateTime dateTime) => new(dateTime.Year, dateTime.Month, dateTime.Day);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Requests.Calendar;
|
||||
using Wino.Core.Requests.Folder;
|
||||
using Wino.Core.Requests.Mail;
|
||||
|
||||
@@ -56,6 +57,22 @@ public static class SynchronizationActionHelper
|
||||
}
|
||||
}
|
||||
|
||||
var calendarRequests = requests.OfType<ICalendarActionRequest>();
|
||||
foreach (var calendarRequest in calendarRequests)
|
||||
{
|
||||
var description = GetCalendarActionDescription(calendarRequest);
|
||||
|
||||
if (description != null)
|
||||
{
|
||||
items.Add(new SynchronizationActionItem
|
||||
{
|
||||
AccountId = accountId,
|
||||
AccountName = accountName,
|
||||
Description = description
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -107,4 +124,13 @@ public static class SynchronizationActionHelper
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetCalendarActionDescription(ICalendarActionRequest request)
|
||||
{
|
||||
return request switch
|
||||
{
|
||||
CreateCalendarEventRequest => Translator.SyncAction_CreatingEvent,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user