mirror of
https://github.com/urosran/cally.git
synced 2025-07-14 17:25:46 +00:00
Syncing rework
This commit is contained in:
@ -190,7 +190,10 @@ exports.sendNotificationOnEventCreation = functions.firestore
|
||||
.document('Events/{eventId}')
|
||||
.onCreate(async (snapshot, context) => {
|
||||
const eventData = snapshot.data();
|
||||
const {familyId, creatorId, email, title, externalOrigin} = eventData;
|
||||
const familyId = eventData.familyId;
|
||||
const creatorId = eventData.creatorId;
|
||||
const title = eventData.title || '';
|
||||
const externalOrigin = eventData.externalOrigin || false;
|
||||
|
||||
if (!familyId || !creatorId) {
|
||||
console.error('Missing familyId or creatorId in event data');
|
||||
@ -209,10 +212,10 @@ exports.sendNotificationOnEventCreation = functions.firestore
|
||||
transaction.set(batchRef, {
|
||||
familyId,
|
||||
creatorId,
|
||||
externalOrigin,
|
||||
externalOrigin: externalOrigin || false,
|
||||
events: [{
|
||||
id: context.params.eventId,
|
||||
title: title,
|
||||
title: title || '',
|
||||
timestamp: new Date().toISOString()
|
||||
}],
|
||||
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
@ -224,7 +227,7 @@ exports.sendNotificationOnEventCreation = functions.firestore
|
||||
transaction.update(batchRef, {
|
||||
events: [...existingEvents, {
|
||||
id: context.params.eventId,
|
||||
title: title,
|
||||
title: title || '',
|
||||
timestamp: new Date().toISOString()
|
||||
}]
|
||||
});
|
||||
@ -256,7 +259,7 @@ exports.processEventBatches = functions.pubsub
|
||||
try {
|
||||
const pushTokens = await getPushTokensForFamily(
|
||||
familyId,
|
||||
externalOrigin ? null : creatorId
|
||||
creatorId
|
||||
);
|
||||
|
||||
if (pushTokens.length) {
|
||||
@ -269,24 +272,23 @@ exports.processEventBatches = functions.pubsub
|
||||
: `${events.length} new events have been added to the family calendar.`;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
sendNotifications(pushTokens, {
|
||||
title: externalOrigin ? 'Calendar Sync Complete' : 'New Family Calendar Events',
|
||||
body: notificationMessage,
|
||||
data: {
|
||||
type: externalOrigin ? 'sync' : 'manual',
|
||||
count: events.length
|
||||
}
|
||||
}),
|
||||
storeNotification({
|
||||
await sendNotifications(pushTokens, {
|
||||
title: 'New Family Calendar Events',
|
||||
body: notificationMessage,
|
||||
data: {
|
||||
type: externalOrigin ? 'sync' : 'manual',
|
||||
familyId,
|
||||
content: notificationMessage,
|
||||
timestamp: admin.firestore.FieldValue.serverTimestamp(),
|
||||
creatorId,
|
||||
eventCount: events.length
|
||||
})
|
||||
]);
|
||||
count: events.length
|
||||
}
|
||||
});
|
||||
|
||||
await storeNotification({
|
||||
type: externalOrigin ? 'sync' : 'manual',
|
||||
familyId,
|
||||
content: notificationMessage,
|
||||
timestamp: admin.firestore.FieldValue.serverTimestamp(),
|
||||
creatorId,
|
||||
eventCount: events.length
|
||||
});
|
||||
}
|
||||
|
||||
await doc.ref.update({
|
||||
@ -307,71 +309,6 @@ exports.processEventBatches = functions.pubsub
|
||||
await Promise.all(processPromises);
|
||||
});
|
||||
|
||||
|
||||
exports.processEventBatchesRealtime = functions.firestore
|
||||
.document('EventBatches/{batchId}')
|
||||
.onWrite(async (change, context) => {
|
||||
const batchData = change.after.data();
|
||||
|
||||
|
||||
if (!batchData || batchData.processed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {familyId, creatorId, externalOrigin, events} = batchData;
|
||||
|
||||
try {
|
||||
const pushTokens = await getPushTokensForFamily(
|
||||
familyId,
|
||||
externalOrigin ? null : creatorId
|
||||
);
|
||||
|
||||
if (pushTokens.length) {
|
||||
let notificationMessage;
|
||||
if (externalOrigin) {
|
||||
notificationMessage = `Calendar sync completed: ${events.length} ${events.length === 1 ? 'event has' : 'events have'} been added.`;
|
||||
} else {
|
||||
notificationMessage = events.length === 1
|
||||
? `New event "${events[0].title}" has been added to the family calendar.`
|
||||
: `${events.length} new events have been added to the family calendar.`;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
sendNotifications(pushTokens, {
|
||||
title: externalOrigin ? 'Calendar Sync Complete' : 'New Family Calendar Events',
|
||||
body: notificationMessage,
|
||||
data: {
|
||||
type: externalOrigin ? 'sync' : 'manual',
|
||||
count: events.length
|
||||
}
|
||||
}),
|
||||
storeNotification({
|
||||
type: externalOrigin ? 'sync' : 'manual',
|
||||
familyId,
|
||||
content: notificationMessage,
|
||||
timestamp: admin.firestore.FieldValue.serverTimestamp(),
|
||||
creatorId,
|
||||
eventCount: events.length
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
await change.after.ref.update({
|
||||
processed: true,
|
||||
processedAt: admin.firestore.FieldValue.serverTimestamp()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error processing batch ${context.params.batchId}:`, error);
|
||||
await change.after.ref.update({
|
||||
processed: true,
|
||||
processedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
async function addToUpdateBatch(eventData, eventId) {
|
||||
const timeWindow = Math.floor(Date.now() / 2000);
|
||||
const batchId = `${timeWindow}_${eventData.familyId}_${eventData.lastModifiedBy}`;
|
||||
@ -1126,85 +1063,16 @@ exports.renewGoogleCalendarWatch = functions.pubsub
|
||||
});
|
||||
});
|
||||
|
||||
exports.sendSyncNotification = functions.https.onRequest(async (req, res) => {
|
||||
const userId = req.query.userId;
|
||||
const calendarId = req.body.resourceId;
|
||||
const tokenRefreshInProgress = new Map();
|
||||
|
||||
console.log(`Received notification for user ${userId} with calendar ID ${calendarId}`);
|
||||
exports.cleanupTokenRefreshFlags = functions.pubsub
|
||||
.schedule('every 5 minutes')
|
||||
.onRun(() => {
|
||||
tokenRefreshInProgress.clear();
|
||||
console.log('[CLEANUP] Cleared all token refresh flags');
|
||||
return null;
|
||||
});
|
||||
|
||||
try {
|
||||
const userDoc = await db.collection("Profiles").doc(userId).get();
|
||||
const userData = userDoc.data();
|
||||
|
||||
const {googleAccounts} = userData;
|
||||
const email = Object.keys(googleAccounts || {})[0];
|
||||
const accountData = googleAccounts[email] || {};
|
||||
const token = accountData.accessToken;
|
||||
const refreshToken = accountData.refreshToken;
|
||||
const familyId = userData.familyId;
|
||||
|
||||
|
||||
if (userData.pushToken) {
|
||||
await sendNotifications(
|
||||
Array.isArray(userData.pushToken) ? userData.pushToken : [userData.pushToken],
|
||||
{
|
||||
title: "Calendar Sync",
|
||||
body: "Calendar sync in progress...",
|
||||
data: {type: 'sync_started'}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Starting calendar sync...");
|
||||
const eventCount = await calendarSync({userId, email, token, refreshToken, familyId});
|
||||
console.log("Calendar sync completed.");
|
||||
|
||||
|
||||
if (userData.pushToken) {
|
||||
const syncMessage = `Calendar sync completed: ${eventCount} ${eventCount === 1 ? 'event has' : 'events have'} been synced.`;
|
||||
|
||||
await Promise.all([
|
||||
|
||||
sendNotifications(
|
||||
Array.isArray(userData.pushToken) ? userData.pushToken : [userData.pushToken],
|
||||
{
|
||||
title: "Calendar Sync",
|
||||
body: syncMessage,
|
||||
data: {
|
||||
type: 'sync_completed',
|
||||
count: eventCount
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
storeNotification({
|
||||
type: 'sync',
|
||||
familyId,
|
||||
content: syncMessage,
|
||||
timestamp: admin.firestore.FieldValue.serverTimestamp(),
|
||||
creatorId: userId,
|
||||
date: new Date()
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
res.status(200).send("Sync completed successfully.");
|
||||
} catch (error) {
|
||||
console.error(`Error in sendSyncNotification for user ${userId}:`, error.message);
|
||||
|
||||
if (userData?.pushToken) {
|
||||
await sendNotifications(
|
||||
Array.isArray(userData.pushToken) ? userData.pushToken : [userData.pushToken],
|
||||
{
|
||||
title: "Calendar Sync Error",
|
||||
body: "There was an error syncing your calendar. Please try again later.",
|
||||
data: {type: 'sync_error'}
|
||||
}
|
||||
);
|
||||
}
|
||||
res.status(500).send("Failed to send sync notification.");
|
||||
}
|
||||
});
|
||||
|
||||
async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, creatorId}) {
|
||||
const baseDate = new Date();
|
||||
@ -1216,7 +1084,7 @@ async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, c
|
||||
const batchSize = 50;
|
||||
|
||||
try {
|
||||
console.log(`Fetching events for user: ${email}`);
|
||||
console.log(`[FETCH] Starting event fetch for user: ${email}`);
|
||||
|
||||
do {
|
||||
let events = [];
|
||||
@ -1227,19 +1095,32 @@ async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, c
|
||||
url.searchParams.set("maxResults", batchSize.toString());
|
||||
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
console.log(`[FETCH] Making request with token: ${token.substring(0, 10)}...`);
|
||||
|
||||
let response = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 401 && refreshToken) {
|
||||
console.log(`Token expired for user: ${email}, attempting to refresh`);
|
||||
const refreshedToken = await refreshGoogleToken(refreshToken);
|
||||
token = refreshedToken;
|
||||
console.log(`[TOKEN] Token expired during fetch, refreshing for ${email}`);
|
||||
const {refreshedGoogleToken} = await refreshGoogleToken(refreshToken);
|
||||
if (refreshedGoogleToken) {
|
||||
console.log(`[TOKEN] Token refreshed successfully during fetch`);
|
||||
token = refreshedGoogleToken;
|
||||
|
||||
if (token) {
|
||||
return fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, creatorId});
|
||||
// Update token in Firestore
|
||||
await db.collection("Profiles").doc(creatorId).update({
|
||||
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
|
||||
});
|
||||
|
||||
// Retry the request with new token
|
||||
response = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${refreshedGoogleToken}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1267,8 +1148,8 @@ async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, c
|
||||
events.push(googleEvent);
|
||||
});
|
||||
|
||||
|
||||
if (events.length > 0) {
|
||||
console.log(`[FETCH] Saving batch of ${events.length} events`);
|
||||
await saveEventsToFirestore(events);
|
||||
totalEvents += events.length;
|
||||
}
|
||||
@ -1276,9 +1157,10 @@ async function fetchAndSaveGoogleEvents({token, refreshToken, email, familyId, c
|
||||
pageToken = data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
console.log(`[FETCH] Completed with ${totalEvents} total events`);
|
||||
return totalEvents;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching Google Calendar events for ${email}:`, error);
|
||||
console.error(`[ERROR] Failed fetching events for ${email}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@ -1293,8 +1175,20 @@ async function saveEventsToFirestore(events) {
|
||||
}
|
||||
|
||||
async function calendarSync({userId, email, token, refreshToken, familyId}) {
|
||||
console.log(`Starting calendar sync for user ${userId} with email ${email}`);
|
||||
console.log(`[SYNC] Starting calendar sync for user ${userId} with email ${email}`);
|
||||
try {
|
||||
if (refreshToken) {
|
||||
console.log(`[TOKEN] Initial token refresh for ${email}`);
|
||||
const {refreshedGoogleToken} = await refreshGoogleToken(refreshToken);
|
||||
if (refreshedGoogleToken) {
|
||||
console.log(`[TOKEN] Token refreshed successfully for ${email}`);
|
||||
token = refreshedGoogleToken;
|
||||
await db.collection("Profiles").doc(userId).update({
|
||||
[`googleAccounts.${email}.accessToken`]: refreshedGoogleToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const eventCount = await fetchAndSaveGoogleEvents({
|
||||
token,
|
||||
refreshToken,
|
||||
@ -1302,10 +1196,11 @@ async function calendarSync({userId, email, token, refreshToken, familyId}) {
|
||||
familyId,
|
||||
creatorId: userId,
|
||||
});
|
||||
console.log("Calendar events synced successfully.");
|
||||
|
||||
console.log(`[SYNC] Calendar sync completed. Processed ${eventCount} events`);
|
||||
return eventCount;
|
||||
} catch (error) {
|
||||
console.error(`Error syncing calendar for user ${userId}:`, error);
|
||||
console.error(`[ERROR] Calendar sync failed for user ${userId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@ -1314,41 +1209,83 @@ exports.sendSyncNotification = functions.https.onRequest(async (req, res) => {
|
||||
const userId = req.query.userId;
|
||||
const calendarId = req.body.resourceId;
|
||||
|
||||
console.log(`Received notification for user ${userId} with calendar ID ${calendarId}`);
|
||||
console.log(`[SYNC START] Received notification for user ${userId} with calendar ID ${calendarId}`);
|
||||
console.log('Request headers:', req.headers);
|
||||
console.log('Request body:', req.body);
|
||||
|
||||
try {
|
||||
console.log(`[PROFILE] Fetching user profile for ${userId}`);
|
||||
const userDoc = await db.collection("Profiles").doc(userId).get();
|
||||
|
||||
if (!userDoc.exists) {
|
||||
console.error(`[ERROR] No profile found for user ${userId}`);
|
||||
return res.status(404).send("User profile not found");
|
||||
}
|
||||
|
||||
const userData = userDoc.data();
|
||||
|
||||
let pushTokens = [];
|
||||
if (userData && userData.pushToken) {
|
||||
pushTokens = Array.isArray(userData.pushToken) ? userData.pushToken : [userData.pushToken];
|
||||
}
|
||||
|
||||
if (pushTokens.length === 0) {
|
||||
console.log(`No push tokens found for user ${userId}`);
|
||||
res.status(200).send("No push tokens found for user.");
|
||||
return;
|
||||
}
|
||||
console.log(`[PROFILE] Found profile data for user ${userId}:`, {
|
||||
hasGoogleAccounts: !!userData.googleAccounts,
|
||||
familyId: userData.familyId
|
||||
});
|
||||
|
||||
const {googleAccounts} = userData;
|
||||
const email = Object.keys(googleAccounts || {})[0];
|
||||
|
||||
if (!email) {
|
||||
console.error(`[ERROR] No Google account found for user ${userId}`);
|
||||
return res.status(400).send("No Google account found");
|
||||
}
|
||||
|
||||
console.log(`[GOOGLE] Using Google account: ${email}`);
|
||||
|
||||
const accountData = googleAccounts[email] || {};
|
||||
const token = accountData.accessToken;
|
||||
const refreshToken = accountData.refreshToken;
|
||||
const familyId = userData.familyId;
|
||||
|
||||
console.log("Starting calendar sync...");
|
||||
await calendarSync({userId, email, token, refreshToken, familyId});
|
||||
console.log("Calendar sync completed.");
|
||||
if (!familyId) {
|
||||
console.error(`[ERROR] No family ID found for user ${userId}`);
|
||||
return res.status(400).send("No family ID found");
|
||||
}
|
||||
|
||||
res.status(200).send("Sync notification sent.");
|
||||
console.log(`[SYNC] Starting calendar sync for user ${userId} (family: ${familyId})`);
|
||||
const syncStartTime = Date.now();
|
||||
await calendarSync({userId, email, token, refreshToken, familyId});
|
||||
console.log(`[SYNC] Calendar sync completed in ${Date.now() - syncStartTime}ms`);
|
||||
|
||||
console.log(`[HOUSEHOLDS] Fetching households for family ${familyId}`);
|
||||
const querySnapshot = await db.collection('Households')
|
||||
.where("familyId", "==", familyId)
|
||||
.get();
|
||||
|
||||
console.log(`[HOUSEHOLDS] Found ${querySnapshot.size} households to update`);
|
||||
|
||||
const batch = db.batch();
|
||||
querySnapshot.docs.forEach((doc) => {
|
||||
console.log(`[HOUSEHOLDS] Adding household ${doc.id} to update batch`);
|
||||
batch.update(doc.ref, {
|
||||
lastSyncTimestamp: admin.firestore.FieldValue.serverTimestamp()
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`[HOUSEHOLDS] Committing batch update for ${querySnapshot.size} households`);
|
||||
const batchStartTime = Date.now();
|
||||
await batch.commit();
|
||||
console.log(`[HOUSEHOLDS] Batch update completed in ${Date.now() - batchStartTime}ms`);
|
||||
|
||||
console.log(`[SYNC COMPLETE] Successfully processed sync for user ${userId}`);
|
||||
res.status(200).send("Sync completed successfully.");
|
||||
} catch (error) {
|
||||
console.error(`Error in sendSyncNotification for user ${userId}:`, error.message);
|
||||
console.error(`[ERROR] Error in sendSyncNotification for user ${userId}:`, {
|
||||
errorMessage: error.message,
|
||||
errorStack: error.stack,
|
||||
errorCode: error.code
|
||||
});
|
||||
res.status(500).send("Failed to send sync notification.");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
async function refreshMicrosoftToken(refreshToken) {
|
||||
try {
|
||||
console.log("Refreshing Microsoft token...");
|
||||
@ -1556,4 +1493,257 @@ exports.microsoftCalendarWebhook = functions.https.onRequest(async (req, res) =>
|
||||
console.error(`Error processing Microsoft webhook for ${userId}:`, error);
|
||||
res.status(500).send();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
exports.triggerGoogleSync = functions.https.onCall(async (data, context) => {
|
||||
if (!context.auth) {
|
||||
throw new functions.https.HttpsError(
|
||||
'unauthenticated',
|
||||
'Authentication required'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const {email} = data;
|
||||
const userId = context.auth.uid;
|
||||
|
||||
const userDoc = await db.collection("Profiles").doc(userId).get();
|
||||
const userData = userDoc.data();
|
||||
|
||||
if (!userData?.googleAccounts?.[email]) {
|
||||
throw new functions.https.HttpsError(
|
||||
'failed-precondition',
|
||||
'No valid Google account found'
|
||||
);
|
||||
}
|
||||
|
||||
const accountData = userData.googleAccounts[email];
|
||||
const eventCount = await calendarSync({
|
||||
userId,
|
||||
email,
|
||||
token: accountData.accessToken,
|
||||
refreshToken: accountData.refreshToken,
|
||||
familyId: userData.familyId
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
eventCount,
|
||||
message: "Google calendar sync completed successfully"
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Google sync error:', error);
|
||||
throw new functions.https.HttpsError('internal', error.message);
|
||||
}
|
||||
});
|
||||
|
||||
exports.triggerMicrosoftSync = functions.https.onCall(async (data, context) => {
|
||||
if (!context.auth) {
|
||||
throw new functions.https.HttpsError(
|
||||
'unauthenticated',
|
||||
'Authentication required'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const {email} = data;
|
||||
if (!email) {
|
||||
throw new functions.https.HttpsError(
|
||||
'invalid-argument',
|
||||
'Email is required'
|
||||
);
|
||||
}
|
||||
|
||||
console.log('Starting Microsoft sync for:', {userId: context.auth.uid, email});
|
||||
|
||||
const userDoc = await db.collection("Profiles").doc(context.auth.uid).get();
|
||||
if (!userDoc.exists) {
|
||||
throw new functions.https.HttpsError(
|
||||
'not-found',
|
||||
'User profile not found'
|
||||
);
|
||||
}
|
||||
|
||||
const userData = userDoc.data();
|
||||
const accountData = userData.microsoftAccounts?.[email];
|
||||
|
||||
if (!accountData) {
|
||||
throw new functions.https.HttpsError(
|
||||
'failed-precondition',
|
||||
'Microsoft account not found'
|
||||
);
|
||||
}
|
||||
|
||||
let {accessToken, refreshToken} = accountData;
|
||||
|
||||
// Try to refresh token if it exists
|
||||
if (refreshToken) {
|
||||
try {
|
||||
const refreshedTokens = await refreshMicrosoftToken(refreshToken);
|
||||
accessToken = refreshedTokens.accessToken;
|
||||
refreshToken = refreshedTokens.refreshToken || refreshToken;
|
||||
|
||||
// Update the stored tokens
|
||||
await db.collection("Profiles").doc(context.auth.uid).update({
|
||||
[`microsoftAccounts.${email}`]: {
|
||||
...accountData,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
lastRefresh: admin.firestore.FieldValue.serverTimestamp()
|
||||
}
|
||||
});
|
||||
} catch (refreshError) {
|
||||
console.error('Token refresh failed:', refreshError);
|
||||
throw new functions.https.HttpsError(
|
||||
'failed-precondition',
|
||||
'Failed to refresh Microsoft token. Please reconnect your account.',
|
||||
{requiresReauth: true}
|
||||
);
|
||||
}
|
||||
} else if (!accessToken) {
|
||||
throw new functions.https.HttpsError(
|
||||
'failed-precondition',
|
||||
'Microsoft account requires authentication. Please reconnect your account.',
|
||||
{requiresReauth: true}
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Fetching Microsoft events with token');
|
||||
const eventCount = await fetchAndSaveMicrosoftEvents({
|
||||
token: accessToken,
|
||||
refreshToken,
|
||||
email,
|
||||
familyId: userData.familyId,
|
||||
creatorId: context.auth.uid
|
||||
});
|
||||
|
||||
console.log('Microsoft sync completed successfully:', {eventCount});
|
||||
return {
|
||||
success: true,
|
||||
eventCount,
|
||||
message: "Microsoft calendar sync completed successfully"
|
||||
};
|
||||
} catch (syncError) {
|
||||
// Check if the error is due to invalid token
|
||||
if (syncError.message?.includes('401') ||
|
||||
syncError.message?.includes('unauthorized') ||
|
||||
syncError.message?.includes('invalid_grant')) {
|
||||
throw new functions.https.HttpsError(
|
||||
'unauthenticated',
|
||||
'Microsoft authentication expired. Please reconnect your account.',
|
||||
{requiresReauth: true}
|
||||
);
|
||||
}
|
||||
throw new functions.https.HttpsError(
|
||||
'internal',
|
||||
syncError.message || 'Failed to sync Microsoft calendar',
|
||||
{originalError: syncError}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Microsoft sync function error:', error);
|
||||
if (error instanceof functions.https.HttpsError) {
|
||||
throw error;
|
||||
}
|
||||
throw new functions.https.HttpsError(
|
||||
'internal',
|
||||
error.message || 'Unknown error occurred',
|
||||
{originalError: error}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
exports.updateHouseholdTimestampOnEventCreate = functions.firestore
|
||||
.document('Events/{eventId}')
|
||||
.onCreate(async (snapshot, context) => {
|
||||
const eventData = snapshot.data();
|
||||
const familyId = eventData.familyId;
|
||||
const eventId = context.params.eventId;
|
||||
|
||||
console.log(`[HOUSEHOLD_UPDATE] Event created - Processing timestamp updates`, {
|
||||
eventId,
|
||||
familyId,
|
||||
eventTitle: eventData.title || 'Untitled'
|
||||
});
|
||||
|
||||
try {
|
||||
const householdsSnapshot = await db.collection('Households')
|
||||
.where('familyId', '==', familyId)
|
||||
.get();
|
||||
|
||||
console.log(`[HOUSEHOLD_UPDATE] Found ${householdsSnapshot.size} households to update for family ${familyId}`);
|
||||
|
||||
const batch = db.batch();
|
||||
householdsSnapshot.docs.forEach((doc) => {
|
||||
console.log(`[HOUSEHOLD_UPDATE] Adding household ${doc.id} to update batch`);
|
||||
batch.update(doc.ref, {
|
||||
lastUpdateTimestamp: admin.firestore.FieldValue.serverTimestamp()
|
||||
});
|
||||
});
|
||||
|
||||
const batchStartTime = Date.now();
|
||||
await batch.commit();
|
||||
console.log(`[HOUSEHOLD_UPDATE] Batch update completed in ${Date.now() - batchStartTime}ms`, {
|
||||
familyId,
|
||||
householdsUpdated: householdsSnapshot.size,
|
||||
eventId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[HOUSEHOLD_UPDATE] Error updating households for event creation`, {
|
||||
eventId,
|
||||
familyId,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
exports.updateHouseholdTimestampOnEventUpdate = functions.firestore
|
||||
.document('Events/{eventId}')
|
||||
.onUpdate(async (change, context) => {
|
||||
const eventData = change.after.data();
|
||||
const familyId = eventData.familyId;
|
||||
const eventId = context.params.eventId;
|
||||
|
||||
console.log(`[HOUSEHOLD_UPDATE] Event updated - Processing timestamp updates`, {
|
||||
eventId,
|
||||
familyId,
|
||||
eventTitle: eventData.title || 'Untitled'
|
||||
});
|
||||
|
||||
try {
|
||||
const householdsSnapshot = await db.collection('Households')
|
||||
.where('familyId', '==', familyId)
|
||||
.get();
|
||||
|
||||
console.log(`[HOUSEHOLD_UPDATE] Found ${householdsSnapshot.size} households to update for family ${familyId}`);
|
||||
|
||||
const batch = db.batch();
|
||||
householdsSnapshot.docs.forEach((doc) => {
|
||||
console.log(`[HOUSEHOLD_UPDATE] Adding household ${doc.id} to update batch`);
|
||||
batch.update(doc.ref, {
|
||||
lastUpdateTimestamp: admin.firestore.FieldValue.serverTimestamp()
|
||||
});
|
||||
});
|
||||
|
||||
const batchStartTime = Date.now();
|
||||
await batch.commit();
|
||||
console.log(`[HOUSEHOLD_UPDATE] Batch update completed in ${Date.now() - batchStartTime}ms`, {
|
||||
familyId,
|
||||
householdsUpdated: householdsSnapshot.size,
|
||||
eventId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[HOUSEHOLD_UPDATE] Error updating households for event update`, {
|
||||
eventId,
|
||||
familyId,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user