mirror of
https://github.com/urosran/cally.git
synced 2025-07-14 09:17:19 +00:00
Merge remote-tracking branch 'origin/main' into dev
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
const {onRequest} = require("firebase-functions/v2/https");
|
||||
const {getAuth} = require("firebase-admin/auth");
|
||||
const {getFirestore} = require("firebase-admin/firestore");
|
||||
const {getFirestore, Timestamp} = require("firebase-admin/firestore");
|
||||
const logger = require("firebase-functions/logger");
|
||||
const functions = require('firebase-functions');
|
||||
const admin = require('firebase-admin');
|
||||
@ -9,19 +9,23 @@ const {Expo} = require('expo-server-sdk');
|
||||
admin.initializeApp();
|
||||
const db = admin.firestore();
|
||||
|
||||
let expo = new Expo({ accessToken: process.env.EXPO_ACCESS_TOKEN });
|
||||
let expo = new Expo({accessToken: process.env.EXPO_ACCESS_TOKEN});
|
||||
let notificationTimeout = null;
|
||||
let eventCount = 0;
|
||||
let pushTokens = [];
|
||||
let notificationInProgress = false;
|
||||
|
||||
const GOOGLE_CALENDAR_ID = "primary";
|
||||
const CHANNEL_ID = "cally-family-calendar";
|
||||
const WEBHOOK_URL = "https://us-central1-cally-family-calendar.cloudfunctions.net/sendSyncNotification";
|
||||
|
||||
exports.sendNotificationOnEventCreation = functions.firestore
|
||||
.document('Events/{eventId}')
|
||||
.onCreate(async (snapshot, context) => {
|
||||
const eventData = snapshot.data();
|
||||
const { familyId, creatorId, email } = eventData;
|
||||
const { familyId, creatorId, email, title } = eventData;
|
||||
|
||||
if (email) {
|
||||
console.log('Event has an email field. Skipping notification.');
|
||||
if (!!eventData?.externalOrigin) {
|
||||
console.log('Externally synced event, ignoring.')
|
||||
return;
|
||||
}
|
||||
|
||||
@ -39,60 +43,78 @@ exports.sendNotificationOnEventCreation = functions.firestore
|
||||
|
||||
eventCount++;
|
||||
|
||||
if (notificationTimeout) {
|
||||
clearTimeout(notificationTimeout);
|
||||
}
|
||||
// Only set up the notification timeout if it's not already in progress
|
||||
if (!notificationInProgress) {
|
||||
notificationInProgress = true;
|
||||
|
||||
notificationTimeout = setTimeout(async () => {
|
||||
const eventMessage = eventCount === 1
|
||||
? `An event "${eventData.title}" has been added. Check it out!`
|
||||
: `${eventCount} new events have been added.`;
|
||||
notificationTimeout = setTimeout(async () => {
|
||||
const eventMessage = eventCount === 1
|
||||
? `An event "${title}" has been added. Check it out!`
|
||||
: `${eventCount} new events have been added.`;
|
||||
|
||||
let messages = pushTokens.map(pushToken => {
|
||||
if (!Expo.isExpoPushToken(pushToken)) {
|
||||
console.error(`Push token ${pushToken} is not a valid Expo push token`);
|
||||
return null;
|
||||
}
|
||||
let messages = pushTokens.map(pushToken => {
|
||||
if (!Expo.isExpoPushToken(pushToken)) {
|
||||
console.error(`Push token ${pushToken} is not a valid Expo push token`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
to: pushToken,
|
||||
sound: 'default',
|
||||
title: 'New Events Added!',
|
||||
body: eventMessage,
|
||||
data: { eventId: context.params.eventId },
|
||||
};
|
||||
}).filter(Boolean);
|
||||
return {
|
||||
to: pushToken,
|
||||
sound: 'default',
|
||||
title: 'New Events Added!',
|
||||
body: eventMessage,
|
||||
data: { eventId: context.params.eventId },
|
||||
};
|
||||
}).filter(Boolean);
|
||||
|
||||
let chunks = expo.chunkPushNotifications(messages);
|
||||
let tickets = [];
|
||||
let chunks = expo.chunkPushNotifications(messages);
|
||||
let tickets = [];
|
||||
|
||||
for (let chunk of chunks) {
|
||||
try {
|
||||
let ticketChunk = await expo.sendPushNotificationsAsync(chunk);
|
||||
tickets.push(...ticketChunk);
|
||||
for (let chunk of chunks) {
|
||||
try {
|
||||
let ticketChunk = await expo.sendPushNotificationsAsync(chunk);
|
||||
tickets.push(...ticketChunk);
|
||||
|
||||
for (let ticket of ticketChunk) {
|
||||
if (ticket.status === 'ok') {
|
||||
console.log('Notification successfully sent:', ticket.id);
|
||||
} else if (ticket.status === 'error') {
|
||||
console.error(`Notification error: ${ticket.message}`);
|
||||
if (ticket.details?.error === 'DeviceNotRegistered') {
|
||||
await removeInvalidPushToken(ticket.to);
|
||||
for (let ticket of ticketChunk) {
|
||||
if (ticket.status === 'ok') {
|
||||
console.log('Notification successfully sent:', ticket.id);
|
||||
} else if (ticket.status === 'error') {
|
||||
console.error(`Notification error: ${ticket.message}`);
|
||||
if (ticket.details?.error === 'DeviceNotRegistered') {
|
||||
await removeInvalidPushToken(ticket.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending notification:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending notification:', error);
|
||||
}
|
||||
}
|
||||
|
||||
eventCount = 0;
|
||||
pushTokens = [];
|
||||
// Save the notification in Firestore for record-keeping
|
||||
const notificationData = {
|
||||
creatorId,
|
||||
familyId,
|
||||
content: eventMessage,
|
||||
eventId: context.params.eventId,
|
||||
timestamp: Timestamp.now(),
|
||||
};
|
||||
|
||||
}, 5000);
|
||||
try {
|
||||
await db.collection("Notifications").add(notificationData);
|
||||
console.log("Notification stored in Firestore:", notificationData);
|
||||
} catch (error) {
|
||||
console.error("Error saving notification to Firestore:", error);
|
||||
}
|
||||
|
||||
// Reset state variables after notifications are sent
|
||||
eventCount = 0;
|
||||
pushTokens = [];
|
||||
notificationInProgress = false;
|
||||
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
exports.createSubUser = onRequest(async (request, response) => {
|
||||
const authHeader = request.get('Authorization');
|
||||
|
||||
@ -262,7 +284,7 @@ exports.generateCustomToken = onRequest(async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
exports.refreshTokens = functions.pubsub.schedule('every 1 hours').onRun(async (context) => {
|
||||
exports.refreshTokens = functions.pubsub.schedule('every 45 minutes').onRun(async (context) => {
|
||||
console.log('Running token refresh job...');
|
||||
|
||||
const profilesSnapshot = await db.collection('Profiles').get();
|
||||
@ -275,8 +297,11 @@ exports.refreshTokens = functions.pubsub.schedule('every 1 hours').onRun(async (
|
||||
for (const googleEmail of Object.keys(profileData?.googleAccounts)) {
|
||||
const googleToken = profileData?.googleAccounts?.[googleEmail]?.refreshToken;
|
||||
if (googleToken) {
|
||||
const refreshedGoogleToken = await refreshGoogleToken(googleToken);
|
||||
const updatedGoogleAccounts = {...profileData.googleAccounts, [googleEmail]: refreshedGoogleToken};
|
||||
const {refreshedGoogleToken, refreshedRefreshToken} = await refreshGoogleToken(googleToken);
|
||||
const updatedGoogleAccounts = {
|
||||
...profileData.googleAccounts,
|
||||
[googleEmail]: {accessToken: refreshedGoogleToken, refreshToken: refreshedRefreshToken}
|
||||
};
|
||||
await profileDoc.ref.update({googleAccounts: updatedGoogleAccounts});
|
||||
console.log(`Google token updated for user ${profileDoc.id}`);
|
||||
}
|
||||
@ -292,7 +317,10 @@ exports.refreshTokens = functions.pubsub.schedule('every 1 hours').onRun(async (
|
||||
const microsoftToken = profileData?.microsoftAccounts?.[microsoftEmail];
|
||||
if (microsoftToken) {
|
||||
const refreshedMicrosoftToken = await refreshMicrosoftToken(microsoftToken);
|
||||
const updatedMicrosoftAccounts = {...profileData.microsoftAccounts, [microsoftEmail]: refreshedMicrosoftToken};
|
||||
const updatedMicrosoftAccounts = {
|
||||
...profileData.microsoftAccounts,
|
||||
[microsoftEmail]: refreshedMicrosoftToken
|
||||
};
|
||||
await profileDoc.ref.update({microsoftAccounts: updatedMicrosoftAccounts});
|
||||
console.log(`Microsoft token updated for user ${profileDoc.id}`);
|
||||
}
|
||||
@ -320,21 +348,6 @@ exports.refreshTokens = functions.pubsub.schedule('every 1 hours').onRun(async (
|
||||
return null;
|
||||
});
|
||||
|
||||
async function refreshGoogleToken(refreshToken) {
|
||||
try {
|
||||
const response = await axios.post('https://oauth2.googleapis.com/token', {
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
client_id: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com", // Web client ID from googleConfig
|
||||
});
|
||||
|
||||
return response.data.access_token; // Return the new access token
|
||||
} catch (error) {
|
||||
console.error("Error refreshing Google token:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshMicrosoftToken(refreshToken) {
|
||||
try {
|
||||
const response = await axios.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
|
||||
@ -385,4 +398,409 @@ async function getPushTokensForFamilyExcludingCreator(familyId, creatorId) {
|
||||
|
||||
async function removeInvalidPushToken(pushToken) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
// Function to refresh Google Token with additional logging
|
||||
async function refreshGoogleToken(refreshToken) {
|
||||
try {
|
||||
console.log("Refreshing Google token...");
|
||||
const response = await fetch("https://oauth2.googleapis.com/token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
client_id: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error("Error refreshing Google token:", errorData);
|
||||
throw new Error(`Failed to refresh Google token: ${errorData.error || response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Google token refreshed successfully");
|
||||
|
||||
// Return both the access token and refresh token (if a new one is provided)
|
||||
return {
|
||||
refreshedGoogleToken: data.access_token,
|
||||
refreshedRefreshToken: data.refresh_token || refreshToken, // Return the existing refresh token if a new one is not provided
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error refreshing Google token:", error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get Google access tokens for all users and refresh them if needed with logging
|
||||
async function getGoogleAccessTokens() {
|
||||
console.log("Fetching Google access tokens for all users...");
|
||||
const tokens = {};
|
||||
const profilesSnapshot = await db.collection("Profiles").get();
|
||||
|
||||
await Promise.all(
|
||||
profilesSnapshot.docs.map(async (doc) => {
|
||||
const profileData = doc.data();
|
||||
const googleAccounts = profileData?.googleAccounts || {};
|
||||
|
||||
for (const googleEmail of Object.keys(googleAccounts)) {
|
||||
// Check if the googleAccount entry exists and has a refreshToken
|
||||
const accountInfo = googleAccounts[googleEmail];
|
||||
const refreshToken = accountInfo?.refreshToken;
|
||||
|
||||
if (refreshToken) {
|
||||
try {
|
||||
console.log(`Refreshing token for user ${doc.id} (email: ${googleEmail})`);
|
||||
const {refreshedGoogleToken} = await refreshGoogleToken(refreshToken);
|
||||
tokens[doc.id] = accessToken;
|
||||
console.log(`Token refreshed successfully for user ${doc.id}`);
|
||||
} catch (error) {
|
||||
tokens[doc.id] = accountInfo?.accessToken;
|
||||
console.error(`Failed to refresh token for user ${doc.id}:`, error.message);
|
||||
}
|
||||
} else {
|
||||
console.log(`No refresh token available for user ${doc.id} (email: ${googleEmail})`);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
console.log("Access tokens fetched and refreshed as needed");
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// Function to watch Google Calendar events with additional logging
|
||||
const watchCalendarEvents = async (userId, token) => {
|
||||
const url = `https://www.googleapis.com/calendar/v3/calendars/${GOOGLE_CALENDAR_ID}/events/watch`;
|
||||
|
||||
// Verify the token is valid
|
||||
console.log(`Attempting to watch calendar for user ${userId}`);
|
||||
console.log(`Token being used: ${token ? 'present' : 'missing'}`);
|
||||
console.log(`Calendar ID: ${GOOGLE_CALENDAR_ID}`);
|
||||
console.log(`Webhook URL: ${WEBHOOK_URL}?userId=${userId}`);
|
||||
|
||||
try {
|
||||
// Test the token first
|
||||
const testResponse = await fetch(
|
||||
`https://www.googleapis.com/calendar/v3/calendars/${GOOGLE_CALENDAR_ID}/events?maxResults=1`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!testResponse.ok) {
|
||||
console.error(`Token validation failed for user ${userId}:`, await testResponse.text());
|
||||
throw new Error('Token validation failed');
|
||||
}
|
||||
|
||||
console.log(`Token validated successfully for user ${userId}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: `${CHANNEL_ID}-${userId}`,
|
||||
type: "web_hook",
|
||||
address: `${WEBHOOK_URL}?userId=${userId}`,
|
||||
params: {
|
||||
ttl: "80000",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log(`Watch response for user ${userId}:`, responseText);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to watch calendar for user ${userId}:`, responseText);
|
||||
throw new Error(`Failed to watch calendar: ${responseText}`);
|
||||
}
|
||||
|
||||
const result = JSON.parse(responseText);
|
||||
console.log(`Successfully set up Google Calendar watch for user ${userId}`, result);
|
||||
|
||||
// Store the watch details in Firestore for monitoring
|
||||
await db.collection('CalendarWatches').doc(userId).set({
|
||||
watchId: result.id,
|
||||
resourceId: result.resourceId,
|
||||
expiration: result.expiration,
|
||||
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`Error in watchCalendarEvents for user ${userId}:`, error);
|
||||
// Store the error in Firestore for monitoring
|
||||
await db.collection('CalendarWatchErrors').add({
|
||||
userId,
|
||||
error: error.message,
|
||||
timestamp: admin.firestore.FieldValue.serverTimestamp(),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Add this to test webhook connectivity
|
||||
exports.testWebhook = functions.https.onRequest(async (req, res) => {
|
||||
console.log('Test webhook received');
|
||||
console.log('Headers:', req.headers);
|
||||
console.log('Body:', req.body);
|
||||
console.log('Query:', req.query);
|
||||
|
||||
res.status(200).send('Test webhook received successfully');
|
||||
});
|
||||
|
||||
// Schedule function to renew Google Calendar watch every 20 hours for each user with logging
|
||||
exports.renewGoogleCalendarWatch = functions.pubsub.schedule("every 10 minutes").onRun(async (context) => {
|
||||
console.log("Starting Google Calendar watch renewal process...");
|
||||
try {
|
||||
const tokens = await getGoogleAccessTokens();
|
||||
console.log("Tokens: ", tokens);
|
||||
|
||||
for (const [userId, token] of Object.entries(tokens)) {
|
||||
try {
|
||||
await watchCalendarEvents(userId, token);
|
||||
} catch (error) {
|
||||
console.error(`Error renewing Google Calendar watch for user ${userId}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Google Calendar watch renewal process completed");
|
||||
} catch (error) {
|
||||
console.error("Error in renewGoogleCalendarWatch function:", error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to handle notifications from Google Calendar with additional logging
|
||||
exports.sendSyncNotification = functions.https.onRequest(async (req, res) => {
|
||||
const userId = req.query.userId; // Extract userId from query params
|
||||
const calendarId = req.body.resourceId;
|
||||
|
||||
console.log(`Received notification for user ${userId} with calendar ID ${calendarId}`);
|
||||
|
||||
try {
|
||||
// Fetch user profile data for the specific user
|
||||
const userDoc = await db.collection("Profiles").doc(userId).get();
|
||||
const userData = userDoc.data();
|
||||
|
||||
// Ensure pushTokens is an array
|
||||
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;
|
||||
}
|
||||
|
||||
// Call calendarSync with necessary parameters
|
||||
const {googleAccounts} = userData;
|
||||
const email = Object.keys(googleAccounts || {})[0]; // Assuming the first account is the primary
|
||||
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.");
|
||||
|
||||
// Prepare and send push notifications after sync
|
||||
// const syncMessage = "New events have been synced.";
|
||||
//
|
||||
// let messages = pushTokens.map(pushToken => {
|
||||
// if (!Expo.isExpoPushToken(pushToken)) {
|
||||
// console.error(`Push token ${pushToken} is not a valid Expo push token`);
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// return {
|
||||
// to: pushToken,
|
||||
// sound: "default",
|
||||
// title: "Event Sync",
|
||||
// body: syncMessage,
|
||||
// data: { userId, calendarId },
|
||||
// };
|
||||
// }).filter(Boolean);
|
||||
//
|
||||
// let chunks = expo.chunkPushNotifications(messages);
|
||||
// let tickets = [];
|
||||
//
|
||||
// for (let chunk of chunks) {
|
||||
// try {
|
||||
// let ticketChunk = await expo.sendPushNotificationsAsync(chunk);
|
||||
// tickets.push(...ticketChunk);
|
||||
//
|
||||
// for (let ticket of ticketChunk) {
|
||||
// if (ticket.status === "ok") {
|
||||
// console.log("Notification successfully sent:", ticket.id);
|
||||
// } else if (ticket.status === "error") {
|
||||
// console.error(`Notification error: ${ticket.message}`);
|
||||
// if (ticket.details?.error === "DeviceNotRegistered") {
|
||||
// await removeInvalidPushToken(ticket.to);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error("Error sending notification:", error.message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// console.log(`Sync notification sent for user ${userId}`);
|
||||
res.status(200).send("Sync notification sent.");
|
||||
} catch (error) {
|
||||
console.error(`Error in sendSyncNotification for user ${userId}:`, error.message);
|
||||
res.status(500).send("Failed to send sync notification.");
|
||||
}
|
||||
});
|
||||
|
||||
async function fetchAndSaveGoogleEvents({ token, refreshToken, email, familyId, creatorId }) {
|
||||
const baseDate = new Date();
|
||||
const timeMin = new Date(baseDate.setMonth(baseDate.getMonth() - 1)).toISOString();
|
||||
const timeMax = new Date(baseDate.setMonth(baseDate.getMonth() + 2)).toISOString();
|
||||
|
||||
let events = [];
|
||||
let pageToken = null;
|
||||
|
||||
try {
|
||||
console.log(`Fetching events for user: ${email}`);
|
||||
|
||||
// Fetch all events from Google Calendar within the specified time range
|
||||
do {
|
||||
const url = new URL(`https://www.googleapis.com/calendar/v3/calendars/primary/events`);
|
||||
url.searchParams.set("singleEvents", "true");
|
||||
url.searchParams.set("timeMin", timeMin);
|
||||
url.searchParams.set("timeMax", timeMax);
|
||||
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.status === 401 && refreshToken) {
|
||||
console.log(`Token expired for user: ${email}, attempting to refresh`);
|
||||
const refreshedToken = await refreshGoogleToken(refreshToken);
|
||||
token = refreshedToken;
|
||||
|
||||
if (token) {
|
||||
return fetchAndSaveGoogleEvents({ token, refreshToken, email, familyId, creatorId });
|
||||
} else {
|
||||
console.error(`Failed to refresh token for user: ${email}`);
|
||||
await clearToken(email);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching events: ${data.error?.message || response.statusText}`);
|
||||
}
|
||||
|
||||
console.log(`Processing events for user: ${email}`);
|
||||
data.items?.forEach((item) => {
|
||||
const googleEvent = {
|
||||
id: item.id,
|
||||
title: item.summary || "",
|
||||
startDate: item.start?.dateTime ? new Date(item.start.dateTime) : new Date(item.start.date),
|
||||
endDate: item.end?.dateTime ? new Date(item.end.dateTime) : new Date(item.end.date),
|
||||
allDay: !item.start?.dateTime,
|
||||
familyId,
|
||||
email,
|
||||
creatorId,
|
||||
externalOrigin: "google",
|
||||
};
|
||||
events.push(googleEvent);
|
||||
console.log(`Processed event: ${JSON.stringify(googleEvent)}`);
|
||||
});
|
||||
|
||||
pageToken = data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
console.log(`Saving events to Firestore for user: ${email}`);
|
||||
await saveEventsToFirestore(events);
|
||||
} catch (error) {
|
||||
console.error(`Error fetching Google Calendar events for ${email}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveEventsToFirestore(events) {
|
||||
const batch = db.batch();
|
||||
events.forEach((event) => {
|
||||
const eventRef = db.collection("Events").doc(event.id);
|
||||
batch.set(eventRef, event, { merge: true });
|
||||
});
|
||||
await batch.commit();
|
||||
}
|
||||
|
||||
async function calendarSync({ userId, email, token, refreshToken, familyId }) {
|
||||
console.log(`Starting calendar sync for user ${userId} with email ${email}`);
|
||||
try {
|
||||
await fetchAndSaveGoogleEvents({
|
||||
token,
|
||||
refreshToken,
|
||||
email,
|
||||
familyId,
|
||||
creatorId: userId,
|
||||
});
|
||||
console.log("Calendar events synced successfully.");
|
||||
} catch (error) {
|
||||
console.error(`Error syncing calendar for user ${userId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
console.log(`Finished calendar sync for user ${userId}`);
|
||||
}
|
||||
|
||||
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}`);
|
||||
|
||||
try {
|
||||
const userDoc = await db.collection("Profiles").doc(userId).get();
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
console.log("Starting calendar sync...");
|
||||
await calendarSync({ userId, email, token, refreshToken, familyId });
|
||||
console.log("Calendar sync completed.");
|
||||
|
||||
res.status(200).send("Sync notification sent.");
|
||||
} catch (error) {
|
||||
console.error(`Error in sendSyncNotification for user ${userId}:`, error.message);
|
||||
res.status(500).send("Failed to send sync notification.");
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user