This commit is contained in:
Milan Paunovic
2025-01-24 01:12:18 +01:00
parent 580104d052
commit 14be80c6f0
2 changed files with 444 additions and 36 deletions

View File

@ -1771,3 +1771,411 @@ exports.updateHouseholdTimestampOnEventUpdate = functions.firestore
throw error;
}
});
async function syncEventToGoogle(event, accessToken, refreshToken, creatorId) {
try {
console.log('[GOOGLE_SYNC] Starting to sync event to Google Calendar', {
eventId: event.id,
creatorId
});
let token = accessToken;
// Construct the Google Calendar event
const googleEvent = {
summary: event.title,
start: {
dateTime: event.allDay ? undefined : event.startDate.toISOString(),
date: event.allDay ? event.startDate.toISOString().split('T')[0] : undefined
},
end: {
dateTime: event.allDay ? undefined : event.endDate.toISOString(),
date: event.allDay ? new Date(event.endDate.getTime() + 24*60*60*1000).toISOString().split('T')[0] : undefined
},
visibility: event.private ? 'private' : 'default',
id: event.id
};
const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${event.id}`;
let response = await fetch(url, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(googleEvent)
});
// Handle token refresh if needed
if (response.status === 401 && refreshToken) {
console.log('[GOOGLE_SYNC] Token expired, refreshing...');
const { refreshedGoogleToken } = await refreshGoogleToken(refreshToken);
token = refreshedGoogleToken;
// Update the token in Firestore
await db.collection("Profiles").doc(creatorId).update({
[`googleAccounts.${event.email}.accessToken`]: refreshedGoogleToken
});
// Retry with new token
response = await fetch(url, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${refreshedGoogleToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(googleEvent)
});
}
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to sync event: ${errorData.error?.message || response.statusText}`);
}
console.log('[GOOGLE_SYNC] Successfully synced event to Google Calendar', {
eventId: event.id,
creatorId
});
return true;
} catch (error) {
console.error('[GOOGLE_SYNC] Error syncing event to Google Calendar:', error);
throw error;
}
}
async function deleteEventFromGoogle(eventId, accessToken, refreshToken, creatorId, email) {
try {
console.log('[GOOGLE_DELETE] Starting to delete event from Google Calendar', {
eventId,
creatorId
});
let token = accessToken;
const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`;
let response = await fetch(url, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});
// Handle token refresh if needed
if (response.status === 401 && refreshToken) {
console.log('[GOOGLE_DELETE] Token expired, refreshing...');
const { refreshedGoogleToken } = await refreshGoogleToken(refreshToken);
token = refreshedGoogleToken;
// Update the token in Firestore
await db.collection("Profiles").doc(creatorId).update({
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
});
// Retry with new token
response = await fetch(url, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${refreshedGoogleToken}`
}
});
}
if (!response.ok && response.status !== 404) {
const errorData = await response.json();
throw new Error(`Failed to delete event: ${errorData.error?.message || response.statusText}`);
}
console.log('[GOOGLE_DELETE] Successfully deleted event from Google Calendar', {
eventId,
creatorId
});
return true;
} catch (error) {
console.error('[GOOGLE_DELETE] Error deleting event from Google Calendar:', error);
throw error;
}
}
// Cloud Function to handle event updates
exports.syncEventToGoogleCalendar = functions.firestore
.document('Events/{eventId}')
.onWrite(async (change, context) => {
const eventId = context.params.eventId;
const afterData = change.after.exists ? change.after.data() : null;
const beforeData = change.before.exists ? change.before.data() : null;
// Skip if this is a Google-originated event
if (afterData?.externalOrigin === 'google' || beforeData?.externalOrigin === 'google') {
console.log('[GOOGLE_SYNC] Skipping sync for Google-originated event', { eventId });
return null;
}
try {
// Handle deletion
if (!afterData && beforeData) {
console.log('[GOOGLE_SYNC] Processing event deletion', { eventId });
// Only proceed if this was previously synced with Google
if (!beforeData.email) {
return null;
}
const creatorDoc = await db.collection('Profiles').doc(beforeData.creatorId).get();
const creatorData = creatorDoc.data();
if (!creatorData?.googleAccounts?.[beforeData.email]) {
return null;
}
const accountData = creatorData.googleAccounts[beforeData.email];
await deleteEventFromGoogle(
eventId,
accountData.accessToken,
accountData.refreshToken,
beforeData.creatorId,
beforeData.email
);
return null;
}
// Handle creation or update
if (afterData) {
// Skip if no creator or email is set
if (!afterData.creatorId || !afterData.email) {
return null;
}
const creatorDoc = await db.collection('Profiles').doc(afterData.creatorId).get();
const creatorData = creatorDoc.data();
if (!creatorData?.googleAccounts?.[afterData.email]) {
return null;
}
const accountData = creatorData.googleAccounts[afterData.email];
await syncEventToGoogle(
afterData,
accountData.accessToken,
accountData.refreshToken,
afterData.creatorId
);
}
return null;
} catch (error) {
console.error('[GOOGLE_SYNC] Error in sync function:', error);
// Store the error for later retry or monitoring
await db.collection('SyncErrors').add({
eventId,
error: error.message,
timestamp: admin.firestore.FieldValue.serverTimestamp(),
type: 'google'
});
throw error;
}
}););
let token = accessToken;
// Construct the Google Calendar event
const googleEvent = {
summary: event.title,
start: {
dateTime: event.allDay ? undefined : event.startDate.toISOString(),
date: event.allDay ? event.startDate.toISOString().split('T')[0] : undefined
},
end: {
dateTime: event.allDay ? undefined : event.endDate.toISOString(),
date: event.allDay ? new Date(event.endDate.getTime() + 24*60*60*1000).toISOString().split('T')[0] : undefined
},
visibility: event.private ? 'private' : 'default',
id: event.id
};
const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${event.id}`;
let response = await fetch(url, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(googleEvent)
});
// Handle token refresh if needed
if (response.status === 401 && refreshToken) {
console.log('[GOOGLE_SYNC] Token expired, refreshing...');
const { refreshedGoogleToken } = await refreshGoogleToken(refreshToken);
token = refreshedGoogleToken;
// Update the token in Firestore
await db.collection("Profiles").doc(creatorId).update({
[`googleAccounts.${event.email}.accessToken`]: refreshedGoogleToken
});
// Retry with new token
response = await fetch(url, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${refreshedGoogleToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(googleEvent)
});
}
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to sync event: ${errorData.error?.message || response.statusText}`);
}
console.log('[GOOGLE_SYNC] Successfully synced event to Google Calendar', {
eventId: event.id,
creatorId
});
return true;
} catch (error) {
console.error('[GOOGLE_SYNC] Error syncing event to Google Calendar:', error);
throw error;
}
}
async function deleteEventFromGoogle(eventId, accessToken, refreshToken, creatorId, email) {
try {
console.log('[GOOGLE_DELETE] Starting to delete event from Google Calendar', {
eventId,
creatorId
});
let token = accessToken;
const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`;
let response = await fetch(url, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});
// Handle token refresh if needed
if (response.status === 401 && refreshToken) {
console.log('[GOOGLE_DELETE] Token expired, refreshing...');
const { refreshedGoogleToken } = await refreshGoogleToken(refreshToken);
token = refreshedGoogleToken;
// Update the token in Firestore
await db.collection("Profiles").doc(creatorId).update({
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
});
// Retry with new token
response = await fetch(url, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${refreshedGoogleToken}`
}
});
}
if (!response.ok && response.status !== 404) {
const errorData = await response.json();
throw new Error(`Failed to delete event: ${errorData.error?.message || response.statusText}`);
}
console.log('[GOOGLE_DELETE] Successfully deleted event from Google Calendar', {
eventId,
creatorId
});
return true;
} catch (error) {
console.error('[GOOGLE_DELETE] Error deleting event from Google Calendar:', error);
throw error;
}
}
exports.handleEventDelete = functions.firestore
.document('Events/{eventId}')
.onDelete(async (snapshot, context) => {
const deletedEvent = snapshot.data();
const eventId = context.params.eventId;
// Skip if this was a Google-originated event to prevent sync loops
if (deletedEvent?.externalOrigin === 'google') {
console.log('[GOOGLE_DELETE] Skipping delete sync for Google-originated event', { eventId });
return null;
}
try {
// Only proceed if this was synced with Google (has an email)
if (!deletedEvent?.email) {
console.log('[GOOGLE_DELETE] Event not synced with Google, skipping', { eventId });
return null;
}
const creatorDoc = await admin.firestore()
.collection('Profiles')
.doc(deletedEvent.creatorId)
.get();
if (!creatorDoc.exists) {
console.log('[GOOGLE_DELETE] Creator profile not found', {
eventId,
creatorId: deletedEvent.creatorId
});
return null;
}
const creatorData = creatorDoc.data();
const googleAccount = creatorData?.googleAccounts?.[deletedEvent.email];
if (!googleAccount) {
console.log('[GOOGLE_DELETE] No Google account found for email', {
eventId,
email: deletedEvent.email
});
return null;
}
await deleteEventFromGoogle(
eventId,
googleAccount.accessToken,
googleAccount.refreshToken,
deletedEvent.creatorId,
deletedEvent.email
);
console.log('[GOOGLE_DELETE] Successfully deleted event from Google Calendar', {
eventId,
email: deletedEvent.email
});
} catch (error) {
console.error('[GOOGLE_DELETE] Error deleting event from Google Calendar:', error);
// Store the error for monitoring
await admin.firestore()
.collection('SyncErrors')
.add({
eventId,
error: error.message,
timestamp: admin.firestore.FieldValue.serverTimestamp(),
type: 'google_delete'
});
throw error;
}
});