mirror of
https://github.com/urosran/cally.git
synced 2025-07-16 01:56:16 +00:00
Calendar improvements
This commit is contained in:
@ -1780,26 +1780,35 @@ async function syncEventToGoogle(event, accessToken, refreshToken, 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
|
||||
date: event.allDay ? event.startDate.toISOString().split('T')[0] : undefined,
|
||||
timeZone: 'UTC'
|
||||
},
|
||||
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
|
||||
date: event.allDay ? new Date(event.endDate.getTime() + 24*60*60*1000).toISOString().split('T')[0] : undefined,
|
||||
timeZone: 'UTC'
|
||||
},
|
||||
visibility: event.private ? 'private' : 'default',
|
||||
id: event.id
|
||||
status: 'confirmed',
|
||||
reminders: {
|
||||
useDefault: true
|
||||
},
|
||||
// Add extendedProperties to store our Firestore ID
|
||||
extendedProperties: {
|
||||
private: {
|
||||
firestoreId: event.id
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${event.id}`;
|
||||
|
||||
// For new events, use POST to create
|
||||
const url = 'https://www.googleapis.com/calendar/v3/calendars/primary/events';
|
||||
let response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
@ -1820,7 +1829,7 @@ async function syncEventToGoogle(event, accessToken, refreshToken, creatorId) {
|
||||
|
||||
// Retry with new token
|
||||
response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${refreshedGoogleToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
@ -1834,9 +1843,16 @@ async function syncEventToGoogle(event, accessToken, refreshToken, creatorId) {
|
||||
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
|
||||
const responseData = await response.json();
|
||||
|
||||
// Store the Google Calendar event ID in Firestore
|
||||
await db.collection('Events').doc(event.id).update({
|
||||
googleEventId: responseData.id
|
||||
});
|
||||
|
||||
console.log('[GOOGLE_SYNC] Successfully created event in Google Calendar', {
|
||||
firestoreId: event.id,
|
||||
googleEventId: responseData.id
|
||||
});
|
||||
|
||||
return true;
|
||||
@ -1900,282 +1916,353 @@ async function deleteEventFromGoogle(eventId, accessToken, refreshToken, creator
|
||||
}
|
||||
}
|
||||
|
||||
const createEventHash = (event) => {
|
||||
const str = `${event.startDate?.seconds || ''}-${event.endDate?.seconds || ''}-${
|
||||
event.title || ''
|
||||
}-${event.location || ''}-${event.allDay ? 'true' : 'false'}`;
|
||||
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return hash.toString(36);
|
||||
};
|
||||
|
||||
// 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;
|
||||
async function fetchEventsFromFirestore(userId, profileData, isFamilyView) {
|
||||
const db = admin.firestore();
|
||||
const eventsQuery = db.collection("Events");
|
||||
let constraints;
|
||||
const familyId = profileData?.familyId;
|
||||
|
||||
// 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;
|
||||
if (profileData?.userType === "FAMILY_DEVICE") {
|
||||
constraints = [
|
||||
eventsQuery.where("familyId", "==", familyId)
|
||||
];
|
||||
} else {
|
||||
if (isFamilyView) {
|
||||
constraints = [
|
||||
eventsQuery.where("familyId", "==", familyId),
|
||||
eventsQuery.where("creatorId", "==", userId),
|
||||
eventsQuery.where("attendees", "array-contains", userId)
|
||||
];
|
||||
} else {
|
||||
constraints = [
|
||||
eventsQuery.where("creatorId", "==", userId),
|
||||
eventsQuery.where("attendees", "array-contains", userId)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const snapshots = await Promise.all(constraints.map(query => query.get()));
|
||||
|
||||
const uniqueEvents = new Map();
|
||||
const processedHashes = new Set();
|
||||
const creatorIds = new Set();
|
||||
|
||||
snapshots.forEach(snapshot => {
|
||||
snapshot.docs.forEach(doc => {
|
||||
const event = doc.data();
|
||||
const hash = createEventHash(event);
|
||||
|
||||
if (!processedHashes.has(hash)) {
|
||||
processedHashes.add(hash);
|
||||
creatorIds.add(event.creatorId);
|
||||
uniqueEvents.set(doc.id, event);
|
||||
}
|
||||
|
||||
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;
|
||||
const creatorIdsArray = Array.from(creatorIds);
|
||||
const creatorProfiles = new Map();
|
||||
const BATCH_SIZE = 10;
|
||||
|
||||
// Update the token in Firestore
|
||||
await db.collection("Profiles").doc(creatorId).update({
|
||||
[`googleAccounts.${event.email}.accessToken`]: refreshedGoogleToken
|
||||
for (let i = 0; i < creatorIdsArray.length; i += BATCH_SIZE) {
|
||||
const chunk = creatorIdsArray.slice(i, i + BATCH_SIZE);
|
||||
const profilesSnapshot = await db
|
||||
.collection("Profiles")
|
||||
.where(admin.firestore.FieldPath.documentId(), "in", chunk)
|
||||
.get();
|
||||
|
||||
profilesSnapshot.docs.forEach(doc => {
|
||||
creatorProfiles.set(doc.id, doc.data()?.eventColor || '#ff69b4');
|
||||
});
|
||||
}
|
||||
|
||||
// Retry with new token
|
||||
response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
return Array.from(uniqueEvents.entries()).map(([id, event]) => ({
|
||||
...event,
|
||||
id,
|
||||
start: event.allDay
|
||||
? new Date(new Date(event.startDate.seconds * 1000).setHours(0, 0, 0, 0))
|
||||
: new Date(event.startDate.seconds * 1000),
|
||||
end: event.allDay
|
||||
? new Date(new Date(event.endDate.seconds * 1000).setHours(0, 0, 0, 0))
|
||||
: new Date(event.endDate.seconds * 1000),
|
||||
hideHours: event.allDay,
|
||||
eventColor: creatorProfiles.get(event.creatorId) || '#ff69b4',
|
||||
notes: event.notes
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching events:', error);
|
||||
throw new functions.https.HttpsError('internal', 'Error fetching events');
|
||||
}
|
||||
}
|
||||
|
||||
exports.fetchEvents = functions.https.onCall(async (data, context) => {
|
||||
if (!context.auth) {
|
||||
throw new functions.https.HttpsError(
|
||||
'unauthenticated',
|
||||
'User must be authenticated'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { isFamilyView } = data;
|
||||
const userId = context.auth.uid;
|
||||
|
||||
const profileDoc = await admin.firestore()
|
||||
.collection('Profiles')
|
||||
.doc(userId)
|
||||
.get();
|
||||
|
||||
if (!profileDoc.exists) {
|
||||
throw new functions.https.HttpsError(
|
||||
'not-found',
|
||||
'User profile not found'
|
||||
);
|
||||
}
|
||||
|
||||
const profileData = profileDoc.data();
|
||||
const events = await fetchEventsFromFirestore(userId, profileData, isFamilyView);
|
||||
|
||||
return { events };
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in fetchEvents:', error);
|
||||
throw new functions.https.HttpsError(
|
||||
'internal',
|
||||
error.message || 'An unknown error occurred'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
exports.syncNewEventToGoogle = functions.firestore
|
||||
.document('Events/{eventId}')
|
||||
.onCreate(async (snapshot, context) => {
|
||||
const newEvent = snapshot.data();
|
||||
const eventId = context.params.eventId;
|
||||
|
||||
// Don't sync if this event came from Google
|
||||
if (newEvent.externalOrigin === 'google') {
|
||||
console.log('[GOOGLE_SYNC] Skipping sync for Google-originated event:', eventId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the creator's Google account credentials
|
||||
const creatorDoc = await db.collection('Profiles').doc(newEvent.creatorId).get();
|
||||
const creatorData = creatorDoc.data();
|
||||
|
||||
if (!creatorData?.googleAccounts) {
|
||||
console.log('[GOOGLE_SYNC] Creator has no Google accounts:', newEvent.creatorId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the first Google account (assuming one account per user)
|
||||
const [email, accountData] = Object.entries(creatorData.googleAccounts)[0];
|
||||
|
||||
if (!accountData?.accessToken) {
|
||||
console.log('[GOOGLE_SYNC] No access token found for creator:', newEvent.creatorId);
|
||||
return;
|
||||
}
|
||||
|
||||
await syncEventToGoogle(
|
||||
{
|
||||
...newEvent,
|
||||
email,
|
||||
startDate: new Date(newEvent.startDate.seconds * 1000),
|
||||
endDate: new Date(newEvent.endDate.seconds * 1000)
|
||||
},
|
||||
accountData.accessToken,
|
||||
accountData.refreshToken,
|
||||
newEvent.creatorId
|
||||
);
|
||||
|
||||
console.log('[GOOGLE_SYNC] Successfully synced new event to Google:', eventId);
|
||||
} catch (error) {
|
||||
console.error('[GOOGLE_SYNC] Error syncing new event to Google:', error);
|
||||
}
|
||||
});
|
||||
|
||||
exports.syncEventToGoogleOnUpdate = functions.firestore
|
||||
.document('Events/{eventId}')
|
||||
.onUpdate(async (change, context) => {
|
||||
const eventBefore = change.before.data();
|
||||
const eventAfter = change.after.data();
|
||||
const eventId = context.params.eventId;
|
||||
|
||||
if (eventAfter.externalOrigin === 'google') {
|
||||
console.log('[GOOGLE_SYNC] Skipping sync for Google-originated event:', eventId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (JSON.stringify(eventBefore) === JSON.stringify(eventAfter)) {
|
||||
console.log('[GOOGLE_SYNC] No changes detected for event:', eventId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const creatorDoc = await db.collection('Profiles').doc(eventAfter.creatorId).get();
|
||||
const creatorData = creatorDoc.data();
|
||||
|
||||
if (!creatorData?.googleAccounts) {
|
||||
console.log('[GOOGLE_SYNC] Creator has no Google accounts:', eventAfter.creatorId);
|
||||
return;
|
||||
}
|
||||
|
||||
const [email, accountData] = Object.entries(creatorData.googleAccounts)[0];
|
||||
|
||||
if (!accountData?.accessToken) {
|
||||
console.log('[GOOGLE_SYNC] No access token found for creator:', eventAfter.creatorId);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`;
|
||||
const googleEvent = {
|
||||
summary: eventAfter.title,
|
||||
start: {
|
||||
dateTime: eventAfter.allDay ? undefined : new Date(eventAfter.startDate.seconds * 1000).toISOString(),
|
||||
date: eventAfter.allDay ? new Date(eventAfter.startDate.seconds * 1000).toISOString().split('T')[0] : undefined,
|
||||
timeZone: 'UTC'
|
||||
},
|
||||
end: {
|
||||
dateTime: eventAfter.allDay ? undefined : new Date(eventAfter.endDate.seconds * 1000).toISOString(),
|
||||
date: eventAfter.allDay ? new Date(new Date(eventAfter.endDate.seconds * 1000).getTime() + 24*60*60*1000).toISOString().split('T')[0] : undefined,
|
||||
timeZone: 'UTC'
|
||||
},
|
||||
visibility: eventAfter.private ? 'private' : 'default',
|
||||
status: 'confirmed',
|
||||
reminders: {
|
||||
useDefault: true
|
||||
}
|
||||
};
|
||||
|
||||
let response = await fetch(url, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${refreshedGoogleToken}`,
|
||||
'Authorization': `Bearer ${accountData.accessToken}`,
|
||||
'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}`);
|
||||
}
|
||||
if (response.status === 401 && accountData.refreshToken) {
|
||||
console.log('[GOOGLE_SYNC] Token expired, refreshing...');
|
||||
const { refreshedGoogleToken } = await refreshGoogleToken(accountData.refreshToken);
|
||||
await db.collection("Profiles").doc(eventAfter.creatorId).update({
|
||||
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
|
||||
});
|
||||
|
||||
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}`
|
||||
response = await fetch(url, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${refreshedGoogleToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(googleEvent)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle token refresh if needed
|
||||
if (response.status === 401 && refreshToken) {
|
||||
console.log('[GOOGLE_DELETE] Token expired, refreshing...');
|
||||
const { refreshedGoogleToken } = await refreshGoogleToken(refreshToken);
|
||||
token = refreshedGoogleToken;
|
||||
// If event doesn't exist in Google Calendar, create it using insert
|
||||
if (response.status === 404) {
|
||||
console.log('[GOOGLE_SYNC] Event not found in Google Calendar, creating new event');
|
||||
const insertUrl = 'https://www.googleapis.com/calendar/v3/calendars/primary/events';
|
||||
response = await fetch(insertUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accountData.accessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...googleEvent,
|
||||
id: eventId
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Update the token in Firestore
|
||||
await db.collection("Profiles").doc(creatorId).update({
|
||||
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error?.message || response.statusText);
|
||||
}
|
||||
|
||||
// Retry with new token
|
||||
response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${refreshedGoogleToken}`
|
||||
}
|
||||
});
|
||||
console.log('[GOOGLE_SYNC] Successfully synced event update to Google:', eventId);
|
||||
} catch (error) {
|
||||
console.error('[GOOGLE_SYNC] Error syncing event update to Google:', error);
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
exports.syncEventToGoogleOnDelete = 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;
|
||||
if (deletedEvent.externalOrigin === 'google') {
|
||||
console.log('[GOOGLE_SYNC] Skipping delete sync for Google-originated event:', eventId);
|
||||
return;
|
||||
}
|
||||
|
||||
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 creatorDoc = await db.collection('Profiles').doc(deletedEvent.creatorId).get();
|
||||
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;
|
||||
if (!creatorData?.googleAccounts) {
|
||||
console.log('[GOOGLE_SYNC] Creator has no Google accounts:', deletedEvent.creatorId);
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteEventFromGoogle(
|
||||
eventId,
|
||||
googleAccount.accessToken,
|
||||
googleAccount.refreshToken,
|
||||
deletedEvent.creatorId,
|
||||
deletedEvent.email
|
||||
);
|
||||
const [email, accountData] = Object.entries(creatorData.googleAccounts)[0];
|
||||
|
||||
console.log('[GOOGLE_DELETE] Successfully deleted event from Google Calendar', {
|
||||
eventId,
|
||||
email: deletedEvent.email
|
||||
if (!accountData?.accessToken) {
|
||||
console.log('[GOOGLE_SYNC] No access token found for creator:', deletedEvent.creatorId);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`;
|
||||
let response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accountData.accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
} 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'
|
||||
if (response.status === 401 && accountData.refreshToken) {
|
||||
console.log('[GOOGLE_SYNC] Token expired, refreshing...');
|
||||
const { refreshedGoogleToken } = await refreshGoogleToken(accountData.refreshToken);
|
||||
await db.collection("Profiles").doc(deletedEvent.creatorId).update({
|
||||
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
|
||||
});
|
||||
|
||||
throw error;
|
||||
response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${refreshedGoogleToken}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!response.ok && response.status !== 404) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error?.message || response.statusText);
|
||||
}
|
||||
|
||||
console.log('[GOOGLE_SYNC] Successfully deleted event from Google:', eventId);
|
||||
} catch (error) {
|
||||
console.error('[GOOGLE_SYNC] Error deleting event from Google:', error);
|
||||
}
|
||||
});
|
||||
|
||||
exports.sendOverviews = functions.pubsub
|
Reference in New Issue
Block a user