* [#352] update exceptions on all update without reseting them * [#352] added tests
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
import { api } from "@/utils/apiUtils";
|
||||
import { TIMEZONES } from "@/utils/timezone-data";
|
||||
import {
|
||||
VCalComponent,
|
||||
VObjectProperty,
|
||||
} from "../../Calendars/types/CalendarData";
|
||||
import { getAllRecurrentEvent } from "../EventApi";
|
||||
import { CalendarEvent } from "../EventsTypes";
|
||||
import { makeTimezone, makeVevent } from "../eventUtils";
|
||||
|
||||
const METADATA_FIELDS = [
|
||||
"summary",
|
||||
"description",
|
||||
"location",
|
||||
"class",
|
||||
"transp",
|
||||
"attendee",
|
||||
"organizer",
|
||||
"x-openpaas-videoconference",
|
||||
] as const;
|
||||
|
||||
// Helper function to get field values from props
|
||||
const getFieldValues = (props: VObjectProperty[], fieldName: string) => {
|
||||
return props.filter(([k]) => k.toLowerCase() === fieldName.toLowerCase());
|
||||
};
|
||||
|
||||
// Helper function to find a single field value from props
|
||||
const findFieldValue = (props: VObjectProperty[], fieldName: string) => {
|
||||
return props.find(([k]) => k.toLowerCase() === fieldName.toLowerCase());
|
||||
};
|
||||
|
||||
// Helper function to serialize for comparison
|
||||
const serialize = (values: VObjectProperty[] | VCalComponent[]) => {
|
||||
return JSON.stringify(values);
|
||||
};
|
||||
|
||||
// Helper function to filter components by name
|
||||
const filterComponentsByName = (components: VCalComponent[], name: string) => {
|
||||
return components.filter(
|
||||
([componentName]) => componentName.toLowerCase() === name.toLowerCase()
|
||||
);
|
||||
};
|
||||
|
||||
// Detect which metadata fields changed between old and new master
|
||||
const detectChangedMetadataFields = (
|
||||
oldMasterProps: VObjectProperty[],
|
||||
newMasterProps: VObjectProperty[]
|
||||
) => {
|
||||
const changedFields = new Map<string, VObjectProperty[]>();
|
||||
|
||||
METADATA_FIELDS.forEach((fieldName) => {
|
||||
const oldValues = getFieldValues(oldMasterProps, fieldName);
|
||||
const newValues = getFieldValues(newMasterProps, fieldName);
|
||||
|
||||
if (serialize(oldValues) !== serialize(newValues)) {
|
||||
changedFields.set(fieldName.toLowerCase(), newValues);
|
||||
}
|
||||
});
|
||||
|
||||
return changedFields;
|
||||
};
|
||||
|
||||
// Check if VALARM component changed between old and new master
|
||||
const detectValarmChanges = (
|
||||
oldMaster: VCalComponent,
|
||||
updatedMaster: VCalComponent
|
||||
) => {
|
||||
const oldMasterComponents = oldMaster[2] || [];
|
||||
const newMasterComponents = updatedMaster[2] || [];
|
||||
|
||||
const oldValarm = filterComponentsByName(oldMasterComponents, "valarm");
|
||||
const newValarm = filterComponentsByName(newMasterComponents, "valarm");
|
||||
|
||||
const valarmChanged = serialize(oldValarm) !== serialize(newValarm);
|
||||
|
||||
return { valarmChanged, newValarm };
|
||||
};
|
||||
|
||||
// Apply changed metadata fields to a vevent's properties
|
||||
const applyMetadataChanges = (
|
||||
props: VObjectProperty[],
|
||||
changedFields: Map<string, VObjectProperty[]>
|
||||
) => {
|
||||
let newProps = [...props];
|
||||
|
||||
changedFields.forEach((newValues, fieldNameLower) => {
|
||||
// Remove old values of this changed field from exception
|
||||
const filteredProps = newProps.filter(
|
||||
([k]) => k.toLowerCase() !== fieldNameLower
|
||||
);
|
||||
|
||||
// Add new values from updated master
|
||||
newProps = [...filteredProps, ...newValues];
|
||||
});
|
||||
|
||||
return newProps;
|
||||
};
|
||||
|
||||
// Increment the sequence number in properties
|
||||
const incrementSequenceNumber = (props: VObjectProperty[]) => {
|
||||
const newProps = [...props];
|
||||
const sequenceIndex = newProps.findIndex(
|
||||
([k]) => k.toLowerCase() === "sequence"
|
||||
);
|
||||
|
||||
if (sequenceIndex !== -1) {
|
||||
const currentSequence = parseInt(
|
||||
(newProps[sequenceIndex][3] as string) || "0",
|
||||
10
|
||||
);
|
||||
newProps[sequenceIndex] = [
|
||||
newProps[sequenceIndex][0],
|
||||
newProps[sequenceIndex][1],
|
||||
newProps[sequenceIndex][2],
|
||||
currentSequence + 1,
|
||||
];
|
||||
} else {
|
||||
newProps.push(["sequence", {}, "integer", 1]);
|
||||
}
|
||||
|
||||
return newProps;
|
||||
};
|
||||
|
||||
// Update VALARM components if they changed
|
||||
const updateValarmComponents = (
|
||||
components: VCalComponent[],
|
||||
newValarm: VCalComponent[]
|
||||
) => {
|
||||
return components
|
||||
.filter(([name]) => name.toLowerCase() !== "valarm")
|
||||
.concat(newValarm);
|
||||
};
|
||||
|
||||
// Update a single vevent with metadata changes
|
||||
const updateVeventWithMetadataChanges = (
|
||||
vevent: VCalComponent,
|
||||
index: number,
|
||||
masterIndex: number,
|
||||
updatedMaster: VCalComponent,
|
||||
changedFields: Map<string, VObjectProperty[]>,
|
||||
valarmChanged: boolean,
|
||||
newValarm: VCalComponent[]
|
||||
): VCalComponent => {
|
||||
if (index === masterIndex) {
|
||||
return updatedMaster;
|
||||
}
|
||||
|
||||
const [veventType, props, components = []] = vevent;
|
||||
|
||||
// Apply metadata changes
|
||||
let newProps = applyMetadataChanges(props, changedFields);
|
||||
|
||||
// Increment sequence number if any changes were made
|
||||
if (changedFields.size > 0 || valarmChanged) {
|
||||
newProps = incrementSequenceNumber(newProps);
|
||||
}
|
||||
|
||||
// Handle VALARM component updates
|
||||
let updatedComponents = components;
|
||||
if (valarmChanged) {
|
||||
updatedComponents = updateValarmComponents(components, newValarm);
|
||||
}
|
||||
|
||||
return [veventType, newProps, updatedComponents];
|
||||
};
|
||||
|
||||
// Update all vevents with metadata changes while preserving overrides
|
||||
const updateVeventsPreservingOverrides = (
|
||||
vevents: VCalComponent[],
|
||||
oldMaster: VCalComponent,
|
||||
updatedMaster: VCalComponent,
|
||||
masterIndex: number
|
||||
) => {
|
||||
const oldMasterProps = oldMaster[1];
|
||||
const newMasterProps = updatedMaster[1];
|
||||
|
||||
// Detect which fields changed in the master
|
||||
const changedFields = detectChangedMetadataFields(
|
||||
oldMasterProps,
|
||||
newMasterProps
|
||||
);
|
||||
|
||||
// Check if VALARM component changed
|
||||
const { valarmChanged, newValarm } = detectValarmChanges(
|
||||
oldMaster,
|
||||
updatedMaster
|
||||
);
|
||||
|
||||
// Update all vevents
|
||||
return vevents.map((vevent, index) =>
|
||||
updateVeventWithMetadataChanges(
|
||||
vevent,
|
||||
index,
|
||||
masterIndex,
|
||||
updatedMaster,
|
||||
changedFields,
|
||||
valarmChanged,
|
||||
newValarm
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const updateSeries = async (
|
||||
event: CalendarEvent,
|
||||
calOwnerEmail?: string,
|
||||
removeOverrides: boolean = true
|
||||
) => {
|
||||
const vevents = (await getAllRecurrentEvent(event)) as VCalComponent[];
|
||||
const masterIndex = vevents.findIndex(
|
||||
([, props]) => !findFieldValue(props, "recurrence-id")
|
||||
);
|
||||
|
||||
if (masterIndex === -1) {
|
||||
throw new Error("No master VEVENT found for this series");
|
||||
}
|
||||
|
||||
const rrule = findFieldValue(vevents[masterIndex][1], "rrule");
|
||||
const tzid = event.timezone;
|
||||
const oldMaster = vevents[masterIndex];
|
||||
|
||||
const updatedMaster = makeVevent(
|
||||
event,
|
||||
tzid,
|
||||
calOwnerEmail,
|
||||
true
|
||||
) as VCalComponent;
|
||||
const newRrule = findFieldValue(updatedMaster[1], "rrule");
|
||||
if (!newRrule && rrule) {
|
||||
updatedMaster[1].push(rrule!);
|
||||
}
|
||||
|
||||
const timezoneData = TIMEZONES.zones[event.timezone];
|
||||
const vtimezone = makeTimezone(timezoneData, event);
|
||||
|
||||
let finalVevents: VCalComponent[];
|
||||
|
||||
if (removeOverrides) {
|
||||
// When date/time/timezone/repeat rules changed, remove all override instances
|
||||
finalVevents = [updatedMaster];
|
||||
} else {
|
||||
// When only properties changed, keep override instances and update their metadata
|
||||
finalVevents = updateVeventsPreservingOverrides(
|
||||
vevents,
|
||||
oldMaster,
|
||||
updatedMaster,
|
||||
masterIndex
|
||||
);
|
||||
}
|
||||
|
||||
const newJCal = [
|
||||
"vcalendar",
|
||||
[],
|
||||
[...finalVevents, vtimezone.component.jCal],
|
||||
];
|
||||
|
||||
return api(`dav${event.URL}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(newJCal),
|
||||
headers: {
|
||||
"content-type": "text/calendar; charset=utf-8",
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user