Ir para o console

Pesquisa de texto completo

A maioria dos apps permite aos usuários pesquisar conteúdo neles. Por exemplo, você pode buscar postagens que contêm uma determinada palavra ou notas que você escreveu sobre um tópico específico.

O Cloud Firestore não é compatível com indexação nativa ou pesquisa de campos de texto em documentos. Além disso, não é nada prático fazer o download de uma coleção inteira para procurar campos do cliente.

Solução: Algolia

Para permitir a pesquisa de texto completo nos seus dados do Cloud Firestore, use um serviço de pesquisa de terceiros como o Algolia. Use um app de notas em que cada nota representa um documento:

// /notes/${ID}
{
  owner: "{UID}", // Firebase Authentication's User ID of note owner
  text: "This is my first note!"
}

Você pode usar o Algolia com o Cloud Functions para preencher um índice com o conteúdo de cada nota e fazer pesquisas. Primeiro, configure um cliente do Algolia usando o ID do app e a chave de API:

Node.js

// Initialize Algolia, requires installing Algolia dependencies:
// https://www.algolia.com/doc/api-client/javascript/getting-started/#install
//
// App ID and API Key are stored in functions config variables
const ALGOLIA_ID = functions.config().algolia.app_id;
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
const ALGOLIA_SEARCH_KEY = functions.config().algolia.search_key;

const ALGOLIA_INDEX_NAME = 'notes';
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);

Depois, adicione uma função que atualize o índice sempre que uma nota for escrita:

Node.js

// Update the search index every time a blog post is written.
exports.onNoteCreated = functions.firestore.document('notes/{noteId}').onCreate((snap, context) => {
  // Get the note document
  const note = snap.data();

  // Add an 'objectID' field which Algolia requires
  note.objectID = context.params.noteId;

  // Write to the algolia index
  const index = client.initIndex(ALGOLIA_INDEX_NAME);
  return index.saveObject(note);
});

Quando seus dados forem indexados, você pode usar quaisquer integrações do Algolia para iOS, Android ou Web para fazer pesquisas.

Web

var client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_KEY);
var index = client.initIndex('notes');

// Perform an Algolia search:
// https://www.algolia.com/doc/api-reference/api-methods/search/
index
  .seach({
    query
  })
  .then(function(responses) {
    // Response from Algolia:
    // https://www.algolia.com/doc/api-reference/api-methods/search/#response-format
    console.log(responses.hits);
  });

Como reforçar a segurança

A solução original funciona bem se todas as notas puderem ser pesquisadas por todos os usuários. No entanto, muitos casos de uso exigem resultados limitados por medida de segurança. Para esses casos de uso, você pode adicionar uma função de nuvem HTTP que gera uma chave de API segura. Ela adicionará filtros específicos a todas as consultas realizadas por um usuário.

Node.js

// This complex HTTP function will be created as an ExpressJS app:
// https://expressjs.com/en/4x/api.html
const app = require('express')();

// We'll enable CORS support to allow the function to be invoked
// from our app client-side.
app.use(require('cors')({origin: true}));

// Then we'll also use a special 'getFirebaseUser' middleware which
// verifies the Authorization header and adds a `user` field to the
// incoming request:
// https://gist.github.com/abehaskins/832d6f8665454d0cd99ef08c229afb42
app.use(getFirebaseUser);

// Add a route handler to the app to generate the secured key
app.get('/', (req, res) => {
  // Create the params object as described in the Algolia documentation:
  // https://www.algolia.com/doc/guides/security/api-keys/#generating-api-keys
  const params = {
    // This filter ensures that only documents where author == user_id will be readable
    filters: `author:${req.user.user_id}`,
    // We also proxy the user_id as a unique token for this key.
    userToken: req.user.user_id,
  };

  // Call the Algolia API to generate a unique key based on our search key
  const key = client.generateSecuredApiKey(ALGOLIA_SEARCH_KEY, params);

  // Then return this key as {key: '...key'}
  res.json({key});
});

// Finally, pass our ExpressJS app to Cloud Functions as a function
// called 'getSearchKey';
exports.getSearchKey = functions.https.onRequest(app);

Se você usar essa função para fornecer a chave de pesquisa do Algolia, o usuário somente poderá pesquisar notas em que o campo author for exatamente igual ao código do usuário.

Web

// Use Firebase Authentication to request the underlying token
return firebase.auth().currentUser.getIdToken()
  .then(function(token) {
    // The token is then passed to our getSearchKey Cloud Function
    return fetch('https://us-central1-' + PROJECT_ID + '.cloudfunctions.net/getSearchKey/', {
        headers: { Authorization: 'Bearer ' + token }
    });
  })
  .then(function(response) {
    // The Fetch API returns a stream, which we convert into a JSON object.
    return response.json();
  })
  .then(function(data) {
    // Data will contain the restricted key in the `key` field.
    client = algoliasearch(ALGOLIA_APP_ID, data.key);
    index = client.initIndex('notes');

    // Perform the search as usual.
    return index.search({query});
  })
  .then(function(responses) {
    // Finally, use the search 'hits' returned from Algolia.
    return responses.hits;
  });

Não é necessário buscar uma chave de pesquisa para cada consulta. Basta armazenar a chave recuperada ou o cliente Algolia em cache para acelerar a pesquisa.

Limitações

A solução mostrada acima é uma maneira simples de adicionar pesquisa de texto completo aos dados do Cloud Firestore. Observe que o Algolia não é o único provedor de pesquisa de terceiros. Pense em alternativas como o ElasticSearch antes de implantar uma solução no ambiente de produção.