Идемпотентная функция cloud functions

Всем привет. Столкнулся с проблемой дубликатов при пользовании cloud functions в firebase. Ссылаясь на их доку https://cloud.google.com/functions/docs/concepts/exec#execution_guarantees это нормальная практика, ведь гуглы заявляют о своей фишке 'at-least-once-execution'. Такое явление порождает повторное создание документов в cloud firestore createContact с NEW_CONTACT_DATA срабатывает несколько раз с одним и тем же authorId

Тем самым порождая несколько ничем (кроме timestamp) неотличимых документов.

Вопрос: как можно реализовать однократное создание документа?

Сниппет при создании документа из исходного кода:

export async function addExperience(experience: IProfileAddExperienceRequest): Promise<IProfileAddExperienceResponse> {
  const uId = (firebaseUser() as any).uid;
  const req: any = { ...experience };

  const city: ISearchLocation = req.city as ISearchLocation;
  let region: any;
  let country: any;
  let images: string[] = [];
  if (experience.images) {
    for (let i = 0; i < experience.images.length; i++) {
      let url: string;
      url = await getImageURL(experience.images[i]);
      images.push(url);
    }
  }
  if (city && city.context) {
    region = city.context.find((item: any) => item.type === 'region');
    country = city.context.find((item: any) => item.type === 'country');
    delete city.context;
  }
  if (req.description === '') {
    delete req.description;
  }
  if (req.companyName === '') {
    delete req.companyName;
  }
  if (req.images) {
    req.images = images;
  }
  if (req.city) {
    req.selectedCity = {
      id: city.id,
      name: city.name,
      shortName: city.shortName,
    };
    delete req.city;
  }
  req.selectedRegion = region
    ? {
        name: region.name,
        id: region.id,
      }
    : city && city.type === 'region'
    ? city
    : undefined;
  req.selectedCountry = country
    ? {
        name: country.name,
        id: country.id,
      }
    : city && city.type === 'country'
    ? city
    : undefined;
  const ref = await CollectionService.workExperience().add({
    authorid: uId,
    active: true,
    deleted: false,
    createDate: firebase.firestore.Timestamp.now(),
    ...req,
  });

  return await ref.get();

Sourcе клауд функции:

const functions = require('firebase-functions');
const http_req = require('request');
const unirest = require('unirest');
const admin = require('firebase-admin');
admin.initializeApp();

exports.createContact = functions.firestore
    .document('contacts/{contactID}').onCreate(async (snap,context) => {
    snap.ref.update({
      deleted:false,active:true,rate:0,createDate:new Date()
    });
    
      const db = admin.firestore();

    const newContactData = snap.data();

    console.log('NEW_CONTACT_DATA', newContactData);

    const interestIconsDoc = await db.collection('system').doc('interest_icons').get();
    const interestIconsData = interestIconsDoc.data();

    const industryAvatar = interestIconsData[newContactData.selectedIndustry.id];

    const userDoc = await db.collection('users').doc(newContactData.authorid).get();

    const userData = userDoc.data();

    console.log('USER_DATA', userData);

    const activitiesData = await db
      .collection('contacts')
      .where('authorid', '==', newContactData.authorid)
      .where('active', '==', true)
      .where('deleted', '==', false)
      .where('type', '==', 'activity')
      .get();

    const connectionsData = await db
      .collection('contacts')
      .where('authorid', '==', newContactData.authorid)
      .where('active', '==', true)
      .where('deleted', '==', false)
      .where('type', '==', 'connection')
      .get();

    let friendsId = [];

    const friendsIdDocs = await db
      .collection('favorites')
      .where('contactUserId', '==', newContactData.authorid)
      .get();

    if (friendsIdDocs.docs.length !== 0) {
      friendsIdDocs.docs.forEach(doc => {
        const data = doc.data();
        if (!friendsId.includes(data.authorid)) {
          friendsId.push(data.authorid);
        }
      });
    }

    if (friendsId.length !== 0) {
      const myNewsDoc = await db
        .collection('news')
        .where('userData.authorid', '==', newContactData.authorid)
        .get();

      if (myNewsDoc.docs.length !== 0) {
        myNewsDoc.docs.forEach(async (doc) => {
          await db.collection('news').doc(doc.id).update({
            friendsId: friendsId,
          });
        });
      }
    }

    db.collection('news').add({
      type: 'newActivityContact',
      createDate: admin.firestore.FieldValue.serverTimestamp(),
      contactId: snap.id,
      friendsId: friendsId,
      userData: {
        authorid: newContactData.authorid,
        firstname: userData.firstName && userData.firstName,
        lastname: userData.lastName && userData.lastName,
        convertedName: userData.convertedName && userData.convertedName,
        isVip: !!userData.isVip,
        avatar: userData.picUrl && userData.picUrl,
        activitiesCount: activitiesData.docs && Number(activitiesData.docs.length) + newContactData.type === 'activity' ? 1 : 0, // плюсуем 1, чтобы данную карточку посчитать
        connectionsCount: connectionsData.docs && Number(connectionsData.docs.length) + newContactData.type === 'connection' ? 1 : 0,
      },
      competencies: {
        type: newContactData.type && newContactData.type,
        industryIcon: industryAvatar && industryAvatar,
        industry: newContactData.selectedIndustry !== undefined && newContactData.selectedIndustry.data,
        branch: newContactData.selectedBranch !== undefined && newContactData.selectedBranch.data,
        speciality: newContactData.selectedSpeciality !== undefined && newContactData.selectedSpeciality.data,
        city: newContactData.selectedCity !== undefined && newContactData.selectedCity.name,
        companyName: newContactData.companyName !== undefined && newContactData.companyName,
        images: newContactData.images && newContactData.images.length ? newContactData.images: null,
        description: newContactData.description ? newContactData.description : null,
      }
    });

    const increment = admin.firestore.FieldValue.increment(1);
    db.collection('system').doc('contacts').update({
      counter: increment
    })
});

Ответы (0 шт):