Files
cally/firebase/functions/index.js
2024-10-12 09:15:30 +02:00

227 lines
8.2 KiB
JavaScript

const {onRequest} = require("firebase-functions/v2/https");
const {getAuth} = require("firebase-admin/auth");
const {getFirestore} = require("firebase-admin/firestore");
const logger = require("firebase-functions/logger");
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {Expo} = require('expo-server-sdk');
admin.initializeApp();
const db = admin.firestore();
let expo = new Expo({accessToken: process.env.EXPO_ACCESS_TOKEN});
// Firestore trigger that listens for new events in the 'Events' collection
exports.sendNotificationOnEventCreation = functions.firestore
.document('Events/{eventId}')
.onCreate(async (snapshot, context) => {
const eventData = snapshot.data();
const { familyId, creatorId } = eventData;
if (!familyId || !creatorId) {
console.error('Missing familyId or creatorId in event data');
return;
}
const pushTokens = await getPushTokensForFamilyExcludingCreator(familyId, creatorId);
if (!pushTokens.length) {
console.log('No push tokens available for the event.');
return;
}
let messages = [];
for (let pushToken of pushTokens) {
if (!Expo.isExpoPushToken(pushToken)) {
console.error(`Push token ${pushToken} is not a valid Expo push token`);
continue;
}
messages.push({
to: pushToken,
sound: 'default',
title: 'New Event Added!',
body: `An event "${eventData.title}" has been added. Check it out!`,
data: { eventId: context.params.eventId },
});
}
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 && ticket.details.error) {
console.error('Error details:', ticket.details.error);
if (ticket.details.error === 'DeviceNotRegistered') {
console.log(`Removing invalid push token: ${ticket.to}`);
await removeInvalidPushToken(ticket.to);
}
}
}
}
} catch (error) {
console.error('Error sending notification:', error);
}
}
// Retrieve and handle notification receipts
let receiptIds = [];
for (let ticket of tickets) {
if (ticket.id) {
receiptIds.push(ticket.id);
}
}
let receiptIdChunks = expo.chunkPushNotificationReceiptIds(receiptIds);
for (let chunk of receiptIdChunks) {
try {
let receipts = await expo.getPushNotificationReceiptsAsync(chunk);
console.log('Receipts:', receipts);
for (let receiptId in receipts) {
let { status, message, details } = receipts[receiptId];
if (status === 'ok') {
console.log(`Notification with receipt ID ${receiptId} was delivered successfully`);
} else if (status === 'error') {
console.error(`Notification error: ${message}`);
if (details && details.error) {
console.error(`Error details: ${details.error}`);
}
}
}
} catch (error) {
console.error('Error retrieving receipts:', error);
}
}
return null;
});
exports.createSubUser = onRequest(async (request, response) => {
const authHeader = request.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
logger.warn("Missing or incorrect Authorization header", {authHeader});
response.status(401).json({error: 'Unauthorized'});
return;
}
try {
const token = authHeader.split('Bearer ')[1];
logger.info("Verifying ID token", {token});
let decodedToken;
try {
decodedToken = await getAuth().verifyIdToken(token);
logger.info("ID token verified successfully", {uid: decodedToken.uid});
} catch (verifyError) {
logger.error("ID token verification failed", {error: verifyError.message});
response.status(401).json({error: 'Unauthorized: Invalid token'});
return;
}
logger.info("Processing user creation", {requestBody: request.body.data});
const {userType, firstName, lastName, email, password, familyId} = request.body.data;
if (!email || !password || !firstName || !lastName || !userType || !familyId) {
logger.warn("Missing required fields in request body", {requestBody: request.body.data});
response.status(400).json({error: "Missing required fields"});
return;
}
let userRecord;
try {
userRecord = await getAuth().createUser({
email, password, displayName: `${firstName} ${lastName}`,
});
logger.info("User record created", {userId: userRecord.uid});
} catch (createUserError) {
logger.error("User creation failed", {error: createUserError.message});
response.status(500).json({error: "Failed to create user"});
return;
}
const userProfile = {
userType, firstName, lastName, familyId, email, uid: userRecord.uid
};
try {
await getFirestore().collection("Profiles").doc(userRecord.uid).set(userProfile);
logger.info("User profile saved to Firestore", {userId: userRecord.uid});
} catch (firestoreError) {
logger.error("Failed to save user profile to Firestore", {error: firestoreError.message});
response.status(500).json({error: "Failed to save user profile"});
return;
}
response.status(200).json({
data: {
message: "User created successfully", userId: userRecord.uid,
}
});
} catch (error) {
logger.error("Error in createSubUser function", {error: error.message});
response.status(500).json({data: {error: error.message}});
}
});
exports.generateCustomToken = onRequest(async (request, response) => {
try {
const {userId} = request.body.data;
console.log("Generating custom token for userId", {userId});
if (!userId) {
response.status(400).json({error: 'Missing userId'});
return;
}
const customToken = await getAuth().createCustomToken(userId);
response.status(200).json({data: {token: customToken}});
} catch (error) {
console.error("Error generating custom token", {error: error.message});
response.status(500).json({error: "Failed to generate custom token"});
}
});
async function getPushTokensForEvent() {
const usersRef = db.collection('Profiles');
const snapshot = await usersRef.get();
let pushTokens = [];
snapshot.forEach(doc => {
const data = doc.data();
if (data.pushToken) {
pushTokens.push(data.pushToken);
}
});
console.log('Push Tokens:', pushTokens);
return pushTokens;
}
async function getPushTokensForFamilyExcludingCreator(familyId, creatorId) {
const usersRef = db.collection('Profiles');
const snapshot = await usersRef.where('familyId', '==', familyId).get();
let pushTokens = [];
snapshot.forEach(doc => {
const data = doc.data();
// Exclude the creator
if (data.uid !== creatorId && data.pushToken) {
pushTokens.push(data.pushToken);
}
});
return pushTokens;
}