Vue로 PWA 개발

57. mylog 구독자 알림 전송

그랜파 개발자 2024. 11. 2. 16:07

Vue로 PWA 개발 - 그랜파 개발자

 

마이로그를 쓰면 구독자에게 알림을 전송하는 기능을 구현합니다. 잎에서 구현한 FCM 푸시 메시지 전송 기능을 그대로 두고 새로운 함수로 기능을 구현하고자 합니다.
Cloud Firestore 함수 트리거의 onDocumentCreated 이벤트는 문서를 처음으로 기록할 때 트리거됩니다. 이 이벤트를 받아 구독자에게 알림을 전송하는 기능을 구현합니다.

1. 구독자 알림 전송

2. funcstions/index.js

// funcstions/index.js
const { onRequest } = require("firebase-functions/v2/https");
const { onDocumentCreated } = require('firebase-functions/v2/firestore');
const logger = require("firebase-functions/logger");

//const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({ origin: true }); // Allow all origins

// Initialize Firebase Admin
admin.initializeApp();

// Reference to Firestore
const db = admin.firestore();

// Cloud Function to send push notifications
exports.sendPushNotification = onRequest(async (req, res) => {
  cors(req, res, async () => {
    try {
      // Retrieve the FCM token(s) from Firestore or request body
      const {userId, title, body } = req.body;  //.userId;
      const tokenSnapshot = await admin.firestore().collection('fcmTokens').doc(userId).get();

      if (!tokenSnapshot.exists) {
        return res.status(404).send('User not found');
      }
      // 한 회원이 여러 토큰을 가진다. PC, 모바일 등
      const tokens = tokenSnapshot.data().tokens;
      console.log('tokens: ', tokens);

      // Token을 배열에 넣는다.
      const fcmTokens = []; 
      tokens.forEach((token) => {
        // doc.data() is never undefined for query doc snapshots
        fcmTokens.push(token.token);
      });

      try {
        const response = await admin.messaging().sendEachForMulticast({ 
          tokens: fcmTokens,
          notification: {
              title: title,
              body: body,
          } 
        });

        // Check the results of the notifications
        response.responses.forEach((response, idx) => {
          if (response.success) {
            console.log(`Message sent successfully to token: ${fcmTokens[idx]}`);
          } else {
            console.error(`Failed to send message to token: ${fcmTokens[idx]}`, response.error);
          }
        });
        return res.status(200).send('Notification sent successfully');
      } catch (error) {
        console.error('Error sending multicast notifications:', error);
      }
    } catch (error) {
      console.error('Error sending notification:', error);
      return res.status(500).send('Failed to send notification');
    }    
  })
});

// 마이로그를 쓰면 구독자에게 알림을 전송한다.
exports.sendMylogNotification = onDocumentCreated('/mylogs/{mylogId}', async (event) => {

  const snapshot = event.data;
  if (!snapshot) {
      console.log("No data associated with the event");
      return;
  }

  const data = snapshot.data();

  // access a particular field as you would any JS property
  const authorId = data.userId;
  const title = data.title;
  const content = data.content;

  try {
    // userId는 저자이다. 저자의 독자들을 가져와야 한다.
    // subscriptions 컬렉션의 구조: userId: userId, authorId: authorId, createdAt: new Date(),
    const readersSnapshot = await db.collection('subscriptions')
      .where('authorId', '==', authorId)  // Query for posts where 'authorId' matches
      .get();

    const readerIds = [];

    readersSnapshot.forEach((doc) => {
      readerIds.push(doc.data().userId);
    });

    // readerIds 에 userId가 있는 모든 독자에게 알림 전송
    for (const userId of readerIds) {
      //  각 회원의 토큰을 가져온다.
      const tokenSnapshot = await admin.firestore().collection('fcmTokens').doc(userId).get();
      // 토근이 있으면 알림을 전송한다.
      if (tokenSnapshot.exists) {

        // 한 회원이 여러 토큰을 가진다. PC, 모바일 등
        const tokens = tokenSnapshot.data().tokens;
        console.log('tokens: ', tokens);

        // Token을 배열에 넣는다.
        const fcmTokens = []; 
        tokens.forEach((token) => {
          // doc.data() is never undefined for query doc snapshots
          fcmTokens.push(token.token);
        });

        try {
          const response = await admin.messaging().sendEachForMulticast({ 
            tokens: fcmTokens,
            notification: {
                title: title,
                body: content,
            } 
          });

          // Check the results of the notifications
          response.responses.forEach((response, idx) => {
            if (response.success) {
              console.log(`Message sent successfully to token: ${fcmTokens[idx]}`);
            } else {
              console.error(`Failed to send message to token: ${fcmTokens[idx]}`, response.error);
            }
          });
          //return res.status(200).send('Notification sent successfully');
        } catch (error) {
          console.error('Error sending multicast notifications:', error);
        }
      }
    }
  } catch (error) {
    console.error('Error getting readers:', error);
    //res.status(500).send('Error getting readers');
  }
});

3. Cloud Firestore 함수 트리거

마이로그가 저장될 때 이벤트를 받아 마이로그의 저자로 구독자를 찾아 구독자의 토큰을사용하여 FCM 알림을 전송합니다.

onDocumentCreated

// funcstions/index.js
const { onRequest } = require("firebase-functions/v2/https");
const { onDocumentCreated } = require('firebase-functions/v2/firestore');
const logger = require("firebase-functions/logger");

//const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({ origin: true }); // Allow all origins

// Initialize Firebase Admin
admin.initializeApp();

// Reference to Firestore
const db = admin.firestore();

. . .

// 마이로그를 쓰면 구독자에게 알림을 전송한다.
exports.sendMylogNotification = onDocumentCreated('/mylogs/{mylogId}', async (event) => {

  const snapshot = event.data;
  if (!snapshot) {
      console.log("No data associated with the event");
      return;
  }

  const data = snapshot.data();

  // access a particular field as you would any JS property
  const authorId = data.userId;
  const title = data.title;
  const content = data.content;

  try {
    // userId는 저자이다. 저자의 독자들을 가져와야 한다.
    // subscriptions 컬렉션의 구조: userId: userId, authorId: authorId, createdAt: new Date(),
    const readersSnapshot = await db.collection('subscriptions')
      .where('authorId', '==', authorId)  // Query for posts where 'authorId' matches
      .get();

    const readerIds = [];

    readersSnapshot.forEach((doc) => {
      readerIds.push(doc.data().userId);
    });

    // readerIds 에 userId가 있는 모든 독자에게 알림 전송
    for (const userId of readerIds) {
      //  각 회원의 토큰을 가져온다.
      const tokenSnapshot = await admin.firestore().collection('fcmTokens').doc(userId).get();
      // 토근이 있으면 알림을 전송한다.
      if (tokenSnapshot.exists) {

        // 한 회원이 여러 토큰을 가진다. PC, 모바일 등
        const tokens = tokenSnapshot.data().tokens;
        console.log('tokens: ', tokens);

        // Token을 배열에 넣는다.
        const fcmTokens = []; 
        tokens.forEach((token) => {
          // doc.data() is never undefined for query doc snapshots
          fcmTokens.push(token.token);
        });

        try {
          const response = await admin.messaging().sendEachForMulticast({ 
            tokens: fcmTokens,
            notification: {
                title: title,
                body: content,
            } 
          });

          // Check the results of the notifications
          response.responses.forEach((response, idx) => {
            if (response.success) {
              console.log(`Message sent successfully to token: ${fcmTokens[idx]}`);
            } else {
              console.error(`Failed to send message to token: ${fcmTokens[idx]}`, response.error);
            }
          });
          //return res.status(200).send('Notification sent successfully');
        } catch (error) {
          console.error('Error sending multicast notifications:', error);
        }
      }
    }
  } catch (error) {
    console.error('Error getting readers:', error);
    //res.status(500).send('Error getting readers');
  }
});