When fetching data in a firebase cloud function from firestore we get is not assignable errors when using a converter - firebase

I am writing a cloud function that needs to retrieve a document from firestore using get(). I then need to use this data, so I am trying to use a converter so I can use the object later on down the line. The converter uses FamilyRequest to specify fields. Here is the relevant code:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import * as firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
admin.initializeApp();
const db = admin.firestore();
const settings = {timestampsInSnapshots: true};
db.settings(settings);
class FamilyRequest {
recipient;
sender;
familyId;
role;
senderIsModGuard;
constructor (recipient: string, sender: string, familyId: string, role: string, senderIsModGuard: boolean) {
this.recipient = recipient;
this.sender = sender;
this.familyId = familyId;
this.role = role;
this.senderIsModGuard = senderIsModGuard;
}
toString() {
return this.recipient + ', ' + this.sender + ', ' + this.familyId + ', ' + this.role + ', ' + this.senderIsModGuard;
}
}
const familyRequestConverter = {
toFirestore(famrequest: FamilyRequest): firebase.firestore.DocumentData {
return {
recipient: famrequest.recipient,
sender: famrequest.sender,
familyId: famrequest.familyId,
role: famrequest.role,
senderIsModGuard: famrequest.senderIsModGuard
};
},
fromFirestore(
snapshot: firebase.firestore.QueryDocumentSnapshot,
options: firebase.firestore.SnapshotOptions
): FamilyRequest {
const data = snapshot.data(options)!;
return new FamilyRequest(data.recipient, data.sender, data.familyId, data.role, data.senderIsModGuard);
}
};
export const acceptFamilyRequestFromFamilyModerator = functions.https.onCall(
async (data, context) => {
// Message text passed from the client.
if (data && context && context.auth && context.auth.uid) {
const groupId = data.text.groupId;
// Authentication info is automatically added to the request.
const uid = context.auth.uid;
const requid = data.text.uid;
const famrequestrecipient = data.text.famrequestrecipient;
const famrequestsender = data.text.famrequestsender; //this is the original sender of the famrequest
//Do sender/recipient checks here
const ref = await db.collection("famrequests")
.doc(uid)
.collection('myfamrequests')
.doc(famrequestrecipient)
.withConverter(familyRequestConverter)
.get()
.then(doc => { //This is simplified for now
if (doc.exists) {
return doc;
} else {
return null;
}
});
}
});
When entering this code into the IDE, the following errors come up:
No overload matches this call.
Overload 1 of 2, '(converter: null): DocumentReference', gave the following error.
Argument of type '{ toFirestore(famrequest: FamilyRequest): DocumentData; fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions): FamilyRequest; }' is not assignable to parameter of type 'null'.
Overload 2 of 2, '(converter: FirestoreDataConverter): DocumentReference', gave the following error.
Argument of type '{ toFirestore(famrequest: FamilyRequest): DocumentData; fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions): FamilyRequest; }' is not assignable to parameter of type 'FirestoreDataConverter'.
Types of property 'fromFirestore' are incompatible.
Type '(snapshot: QueryDocumentSnapshot, options: SnapshotOptions) => FamilyRequest' is not assignable to type '(snapshot: QueryDocumentSnapshot) => FamilyRequest'.ts(2769)
const familyRequestConverter: {
toFirestore(famrequest: FamilyRequest): firebase.firestore.DocumentData;
fromFirestore(snapshot: firebase.firestore.QueryDocumentSnapshot, options: firebase.firestore.SnapshotOptions): FamilyRequest;
}
I am not entirely sure what I am doing wrong as I am following the example code for converters here:
https://firebase.google.com/docs/firestore/query-data/get-data#web-version-8_3
very closely.
Any help is much appreciated.

I made the following change to familyRequestConverter that worked:
const familyRequestConverter = {
toFirestore: function(famrequest: FamilyRequest): firebase.firestore.DocumentData {
return {
recipient: famrequest.recipient,
sender: famrequest.sender,
familyId: famrequest.familyId,
role: famrequest.role,
senderIsModGuard: famrequest.senderIsModGuard,
};
},
fromFirestore: function(
snapshot: firebase.firestore.DocumentData
) {
const data = snapshot.data()!;
return new FamilyRequest(data.recipient, data.sender, data.familyId, data.role, data.senderIsModGuard);
}
};

This overloading error can be cause if you are missing either toFirestore or fromFirestore functions in the converter object which is passed to withConverter function.

Related

post request to an https triggered GCF response: "only absolute paths are supported"

data to be posted to the cloud function:
const body: object = {data: [{link: "https://links.someapp.app/JfzxSyzj1rAoMxQi8"}]}
the api call to invoke the cloud function:
const response: any = fetch("https://us-central1-someProject.cloudfunctions.net/getInfo", {method: 'POST', body: JSON.stringify(body), headers: {'Content-Type' : 'application/json'}}).then((data) => {console.log(data.url); return data.url});
The code of the function that when invoked throws the TypeError: Only absolute urls are supported:
export const shareIdFromShortLink = functions.https.onCall(async (data: any, context: CallableContext): Promise<string> => {
let shareId: string = "";
console.log("XXXXXX");
try {
console.log("YYYYYY");
console.log("data", data)
const res: any = await fetch(data.url);
const url: string = res.url;
const splitUrl = url.split('=');
shareId = splitUrl[1];
}catch(err:any){
Sentry.captureException(err);
throw new Error(
`ERROR: Failed to get user shareId from dynamic shortlink at shareIdFromShortLink in pushNotifications.ts\nMESSAGE:${err} ${err.stack}`)
}
return shareId;
});
Oddly for some IoT there must have been a glitch when I initially deployed?After deleting the GCF and redeploying my function works and I have completed my function code:
* Callable cloud function takes in a short link and returns the user's shareId
* #param data
* #param context
* #returns {string} shareId
*/
export const shareIdFromShortLink = functions.https.onCall(async (data: object, context: CallableContext): Promise<string> => {
let shareId: string = "";
try {
const link: object[] = Object.values(data).map((val: any) => {
return Object.values(val).map((v: any) => v)
});
const url: string = link[0][0];
const res: string = await fetch(url).then((x: any) => {
return x.url;
});
const resArray: string[] = res.split('=');;
shareId = resArray[1];
} catch (err: any) {
Sentry.captureException(err);
throw new Error(
`ERROR: Failed to get user shareId from dynamic shortlink at shareIdFromShortLink in pushNotifications.ts\nERROR MESSAGE:${err} ${err.stack}`)
}
return shareId;
});```

Firestore withConverter Returning Error for fromFirestore

I am working on a project using Firebase Firestore and using the custom object code provided by Firebase. I am using a converter in a could trigger function and receiving an error that states fromFirestore is not a function. I am do not understand why. I've added the City class file, the cloud trigger function, and the error message found in the logs. Any help would be greatly appreciated.
City Class
class City {
constructor(name, state, country) {
this.name = name;
this.state = state;
this.country = country;
}
toString() {
return this.name + ', ' + this.state + ', ' + this.country;
}
}
// Firestore data converter
var cityConverter = {
toFirestore: function (city) {
return {
name: city.name,
state: city.state,
country: city.country,
};
},
fromFirestore: function (snapshot, options) {
const data = snapshot.data(options);
return new City(data.name, data.state, data.country);
},
};
// Exports
module.exports = {
City: City,
};
module.exports.cityConverter = cityConverter;
Cloud Trigger Function
exports.cityUpdate = functions.firestore
.document('cities/{cityId}')
.onUpdate(async (change, context) => {
const data = change.after.data();
const doc = change.after;
const cityDoc = await db
.collection('cities')
.doc('LV')
.withConverter(cityConverter)
.get();
if (cityDoc.exists) {
const city = cityDoc.data();
console.log(city);
console.log(city.name);
} else {
console.log('No listing exists');
}
});
Error Log
TypeError: this.ref._converter.fromFirestore is not a function
at QueryDocumentSnapshot.data (/workspace/node_modules/#google-cloud/firestore/build/src/document.js:311:40)
at QueryDocumentSnapshot.data (/workspace/node_modules/#google-cloud/firestore/build/src/document.js:488:28)
at exports.cityUpdate.functions.firestore.document.onUpdate (/workspace/functions/cities.js:28:28)
at process._tickCallback (internal/process/next_tick.js:68:7)
The problem is that the Firebase Admin SDK does not allow the use of withConverter and the toFirebase and fromFirebase functions. These are reserved for the JavaScript SDK. Hopefully, these functions are added to the Admin SDK.

App working locally but not on production: TypeError: Cannot read property 'titulo_categoria' of undefined

I'm trying to deploy an app using Prismic as CMS and everything works perfectly locally, but once I deploy to vercel I get the error:
19:09:51.850 | TypeError: Cannot read property 'titulo_categoria' of undefined
There seems to be something wrong when it tries to get the data from Prismic.
My code is the following:
import {getAllCategorias, getCategory2} from '../../lib/api';
export default function Index({cat}) {
return <>{cat.titulo_categoria[0].text}</>;
}
export async function getStaticProps({params}) {
const data = await getCategory2(params.slug);
return {
props: {
cat: data?.categorias ?? null,
},
};
}
export async function getStaticPaths() {
const allPosts = await getAllCategorias();
return {
paths: allPosts?.map(({node}) => `/test/${node._meta.uid}`) || [],
fallback: true,
};
}
And the API code that gets data from Prismic is:
import Prismic from 'prismic-javascript';
const REPOSITORY = process.env.PRISMIC_REPOSITORY_NAME;
const REF_API_URL = `https://${REPOSITORY}.prismic.io/api/v2`;
const GRAPHQL_API_URL = `https://${REPOSITORY}.prismic.io/graphql`;
// export const API_URL = 'https://your-repo-name.cdn.prismic.io/api/v2'
export const API_TOKEN = process.env.PRISMIC_API_TOKEN;
export const API_LOCALE = process.env.PRISMIC_REPOSITORY_LOCALE;
export const PrismicClient = Prismic.client(REF_API_URL, {
accessToken: API_TOKEN,
});
async function fetchAPI(query, {previewData, variables} = {}) {
const prismicAPI = await PrismicClient.getApi();
const res = await fetch(
`${GRAPHQL_API_URL}?query=${query}&variables=${JSON.stringify(variables)}`,
{
headers: {
'Prismic-Ref': previewData?.ref || prismicAPI.masterRef.ref,
'Content-Type': 'application/json',
'Accept-Language': API_LOCALE,
Authorization: `Token ${API_TOKEN}`,
},
}
);
if (res.status !== 200) {
console.log(await res.text());
throw new Error('Failed to fetch API');
}
const json = await res.json();
if (json.errors) {
console.error(json.errors);
throw new Error('Failed to fetch API');
}
return json.data;
}
export async function getCategory2(slug) {
const data = await fetchAPI(
`
query CategoryBySlug($slug: String!, $lang: String!) {
categorias(uid: $slug, lang: $lang) {
titulo_categoria
_meta {
uid
}
}
}
`,
{
variables: {
slug,
lang: API_LOCALE,
},
}
);
return data;
}
Any idea what's wrong with this? I been trying to figure it out for the whole day without any luck
Perhaps you already checked that, but since you mentioned everything works locally and not on Vercel are you sure your environment variables are set there? Especially PRISMIC_API_TOKEN since it appears you're relying on it to query the API?
Also I'm a bit worried about that part of the code:
props: {
cat: data?.categorias ?? null,
}
...where you might be sending a null value to your Index component resulting in your error, I'd try that instead:
props: {
cat: data?.categorias ?? {},
}
...plus using the safe navigation operator (?.) on the Index component?
Let me know how it goes!

Convert the callback to async/await for nexmo.message.sendSms?

My nodejs app calls nexmo API to send SMS message. Here is the API:
nexmo.message.sendSms(sender, recipient, message, options, callback);
In the app, it is
const nexmo = new Nexmo({
apiKey: "nexmoApiKey",
apiSecret: "nexmoSecret"
}, { debug: true });
nexmo.message.sendSms(nexmo_sender_number, cell_country + to, message, {type: 'unicode'}, async (err, result) => {....});
Is there a way I can convert it to async/await structure like below:
const {err, result} = nexmo.message.sendSms(nexmo_sender_number, cell_country + to, vcode, {type: 'unicode'});
if (err) {.....};
//then process result...
I would like to return the message to parent function after the message was successfully sent out.
The nexmo-node library only supports callbacks for now. You'll need to use something like promisify or bluebird to convert the sendSms function to promises, and the use async/await with it. Here is an example using Node's promisify
const util = require('util');
const Nexmo = require('nexmo');
const nexmo = new Nexmo({
apiKey: "nexmoApiKey",
apiSecret: "nexmoSecret"
}, { debug: true });
const sendSms = util.promisify(nexmo.message.sendSms);
async function sendingSms() {
const {err, result} = await sendSms(nexmo_sender_number, cell_country + to, message, {type: 'unicode'});
if (err) {...} else {
// do something with result
}
}
Though Alex's solution is elegant. It breaks TypeScript and util does some 'hidden logic' on the promises; when it errors stack traces are not clear.
This also allows you to stay true to the API and get auto-fill on the properties.
So instead you can do this (TypeScript)
/**
* Return the verification id needed.
*/
export const sendSMSCode = async (phoneNumber: string): Promise<string> => {
const result = await new Promise(async (resolve: (value: RequestResponse) => void, reject: (value: VerifyError) => void) => {
await nexmo.verify.request({
number: phoneNumber,
brand: `${Config.productName}`,
code_length: 4
}, (err, result) => {
console.log(err ? err : result)
if (err) {
reject(err)
}
resolve(result)
});
})
return result.request_id
}

Firebase Functions get files from storage

I have to send a file to an API, therefor I have to use fs.readFileSync(). After uploading the picture to the storage, I am calling my function to execute the API call. But I cannot get the file from the storage. This is a section of the code, which always gets null in the result. I tried also to .getFiles() without a parameter and then I got all files but I dont want to filter them by iteration.
exports.stripe_uploadIDs = functions.https //.region("europe-west1")
.onCall((data, context) => {
const authID = context.auth.uid;
console.log("request is authentificated? :" + authID);
if (!authID) {
throw new functions.https.HttpsError("not authorized", "not authorized");
}
let accountID;
let result_fileUpload;
let tempFile = path.join(os.tmpdir(), "id_front.jpg");
const options_id_front_jpeg = {
prefix: "/user/" + authID + "/id_front.jpg"
};
const storageRef = admin
.storage()
.bucket()
.getFiles(options_id_front)
.then(results => {
console.log("JPG" + JSON.stringify(results));
// need to write this file to tempFile
return results;
});
const paymentRef = storageRef.then(() => {
return admin
.database()
.ref("Payment/" + authID)
.child("accountID")
.once("value");
});
const setAccountID = paymentRef.then(snap => {
accountID = snap.val();
return accountID;
});
const fileUpload = setAccountID.then(() => {
return Stripe.fileUploads.create(
{
purpose: "identity_document",
file: {
data: tempFile, // Documentation says I should use fs.readFileSync("filepath")
name: "id_front.jpg",
type: "application/octet-stream"
}
},
{ stripe_account: accountID }
);
});
const fileResult = fileUpload.then(result => {
result_fileUpload = result;
console.log(JSON.stringify(result_fileUpload));
return result_fileUpload;
});
return fileResult;
});
Result is:
JPG[[]]
You need to download your file from a bucket to your local function context env.
After your Firebase function start executing you can call the below:
More or less the below should work, just tweak to your needs. Call this within you .onCall context, you get the idea
import admin from 'firebase-admin';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
admin.initializeApp();
const { log } = console;
async function tempFile(fileBucket: string, filePath: string) {
const bucket = admin.storage().bucket(fileBucket);
const fileName = 'MyFile.ext';
const tempFilePath = path.join(os.tmpdir(), fileName);
const metadata = {
contentType: 'DONT_FORGET_CONTEN_TYPE'
};
// Donwload the file to a local temp file
// Do whatever you need with it
await bucket.file(filePath).download({ destination: tempFilePath });
log('File downloaded to', tempFilePath);
// After you done and if you need to upload the modified file back to your
// bucket then uploaded
// This is optional
await bucket.upload(tempFilePath, {
destination: filePath,
metadata: metadata
});
//free up disk space by realseasing the file.
// Otherwise you might be charged extra for keeping memory space
return fs.unlinkSync(tempFilePath);
}

Resources