0
votes

I'm trying to implement a quote bookmarking service.

  • Given a quote collection, it has four info - quote, userId, authorId and sourceId.
  • Given an author collection, it has two info - name, userId
  • Given a source collection, it has three info - name (Star Wars), type (book, film), userId

When a user tries to save a quote, I would like to have a transaction where I'll check if the author exists (query via name. if yes, return the authorId. if no, create an author). Same goes for the source + type. Both author and source will return their respective ids. During saving the quote, the quote object will be created with the authorId and sourceId.

Is such a case possible? I've check that within a Firestore.firestore().transaction, there is only getDocument function and I can't do a query using whereField().

I've read somewhere that, we can enforce the create via the rules, and throw an error or some sort in a try/catch where the catch block will do a getDocument?

In the case where I can't do a query within a transaction and I can only rely on getDocument, does that mean the author/source collection's Id, has to be a composite key like "userId + hash(author/source's name)"?

What's the advice on performing such an action? Or Firestore can't handle such a use case?

In summary, I'm trying to do this (in pseudo code)...

Firestore.transaction {

  // Get or create author
  let author = Author.getOrCreate("Yoda", userId)

  // Get or create source
  let source = Source.getOrCreate("Star Wars", "film", userId)

  // Save quote
  let quote = Quote.create({
     quote: "Do or do not. There is no try", 
     authorId: author.id, 
     sourceId: source.id, 
     userId: userId
  })

}
1

1 Answers

1
votes

In a transaction, all reads should go first. You may use transaction.getAll() to get the author and the source, then create them if they do not exist:

const authorsRef = db.collection('authors')
const sourcesRef = db.collection('sources')
const quotesRef = db.collection('quotes')

db.runTransaction(transaction => transaction
  .getAll(
    authorsRef.where('name', '==', 'Yoda').get(),
    sourcesRef.where('name', '==', 'Star Wars').where('type', '==', 'film').get()
  )
  .then(([ authorDoc, sourceDoc ]) => {
    let author = authorDoc
    let source = sourceDoc


    if (!author.exists) {
      author = authorsRef.doc()
      transaction.set(author, { /* add author fields here */ })
    }

    if (!source.exists) {
      source = sourcesRef.doc()
      transaction.set(source, { /* add source fields here */ })
    }

    transaction.set(quotesRef.doc(), {
      // add other quote fields here
      authorId: author.id,
      sourceId: source.id
    })
  })
)