What is the proper way to run fetch calls which use reactive components from a store? - fetch-api

I am getting two reactive variables I need from a store to use for my fetch calls. I need these fetch calls to rerun when the data in these store values change. I am able to make this work however when I reload the page it causes my app to crash because there are no values that are getting from the store. I am able to make it work if I disable ssr on the +page.js file.
I also believe it is relevant to mention that I am using a relative URL (/api) to make the fetch call because I have a proxy server to bypass CORS
What is the proper way to get this data by rerunning the fetch calls using a reactive component from a store without disabling ssr? Or is this the best/only solution?
+page.svelte
<script>
import { dateStore, shiftStore } from '../../../lib/store';
$: shift = $shiftStore
$: date = $dateStore
/**
* #type {any[]}
*/
export let comments = []
/**
* #type {any[]}
*/
let areas = []
//console.log(date)
async function getComments() {
const response = await fetch(`/api/${date.toISOString().split('T')[0]}/${shift}/1`)
comments = await response.json()
console.log(comments)
}
async function getAreas() {
const response = await fetch(`/api/api/TurnReportArea/1/${date.toISOString().split('T')[0]}/${shift}`)
areas = await response.json()
console.log(areas)
}
// both of these call function if date or shift value changes
$: date && shift && getAreas()
$: date , shift , getComments()
</script>
I tried to use the +page.js file for my fetch calls, however I cannot use the reactive values in the store in the +page.js file. Below the date variable is set as a 'Writble(Date)' When I try to add the $ in front of the value let dare = $dateStore, I get the error 'Cannot find name '$dateSrote'' If i put the $ in the fetch call I get the error 'Cannot find $date'. Even if I were able to make this work, I do not understand how my page would know to rerender if these fetch calls were ran so I do not think this is the solution. As I mentioned, the only solution I have found is to disable ssr on the +page.js, which I do not think is the best way to fix this issue.
import { dateStore, shiftStore } from "../../../lib/store"
export const load = async ({ }) => {
let shift = shiftStore
let date = dateStore
const getComments = async() => {
const commentRes = await fetch(`/api/${date.toISOString().split('T')[0]}/${shift}/1`)
const comments = await commentRes.json()
console.log(comments)
}
const getAreas = async () => {
const areasRes = await fetch(`/api/api/TurnReportArea/1/${date.toISOString().split('T')[0]}/${shift}`)
const areas = await areasRes.json()
console.log(areas)
}
return {
comments: getComments(),
areas: getAreas()
}
}

Related

Can't access Firestore docs data after getting the doc object

I'm trying to fetch a single field value from a doc in a collection (stored in Firestore).
The following function is called (by the triggered function) to perform this query and return the result.
Firestore data structure:
After I fetch the query result into helper_token object - I cannot access the DATA (fields) in it.
I tried many things, including:
helper_token[0].device_token;
helper_token.data().device_token;
JSON.stringify(helper_token);
Nothing works for me.
The log always shows results like these:
helper_token = {}
helper_token = undefined
What am I missing? how can I get the device_token based on user?
const admin = require('firebase-admin'); //required to access the FB RT DB
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
function getHelperToken(helperId) {
//Get token from Firestore
const tokensRef = db.collection('tokens');
const helper_token = tokensRef.where('user', '==', 'TM1EOV4lYlgEIly0cnGHVmCnybT2').get();
if (helper_token.empty) {
functions.logger.log('helper_token EMPTY');
}
functions.logger.log('helper_token=' + JSON.stringify(helper_token));
return helper_token.device_token;
};
For completeness, adding here the full calling function to the above function:
//DB triggered function - upon writing a child in the HElpersInvitations reference
exports.sendHelperInvitation = functions.database.ref('/HelpersInvitations/{helper_invitation_id}')
.onCreate((snapshot, context) => {
const helperId = snapshot.val().helperId;
const title = snapshot.val().title;
const body = snapshot.val().body;
//Get the helper token by Id
functions.logger.log('HelperID=' + helperId);
functions.logger.log('getHelperToken=' + getHelperToken(helperId));
const helper_token2 = getHelperToken(helperId);
//Notification payload
const payload = {
notification: {
title: title,
body: body,
icon: 'default',
click_action: 'com.skillblaster.app.helperinvitationnotification'
}
}
// //Send the notification
functions.logger.log('helper_token [BEFORE sendToDevice]=' + helper_token2);
return admin.messaging().sendToDevice(helper_token2, payload);
});
You need to consider that the get() call is asynchornous and also that you get a list of documents and not a single doc. Can you try it with this code:
const admin = require("firebase-admin"); //required to access the FB RT DB
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
async function getHelperToken(helperId) {
//Get token from Firestore
const tokensRef = db.collection("tokens");
const helperTokens = await tokensRef
.where("user", "==", "TM1EOV4lYlgEIly0cnGHVmCnybT2")
.get();
let helper_token = "";
helperTokens.forEach((token) => {
helper_token = token.data();
});
functions.logger.log("helper_token=" + JSON.stringify(helper_token));
return helper_token.device_token;
}
As the get() call in Firestore is asynchronous you need to use an asynchronous function. You can go through this article to know more about why Firebase APIs are asynchronous. Next when we query with the where clause in Firestore we get a list of documents even if there is only one document in the list. So we have to run a for loop to get the document inside the list of documents. Now as you are returning the value from an asynchronous function the return value will be a promise in pending state. So to get the value from the promise we need to use the then() block while calling the function.
Also I think the parameter helperId you are using in the function definition is not used anywhere in the function. Though it will not make a difference I would suggest you remove it if it is not required in the function.
So consider using the following code -
const admin = require(‘firebase-admin’);
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
async function getHelperToken() {
//Get token from Firestore
const tokensRef = db.collection(‘tokens’);
const helper_token = await tokensRef.where(‘user’, ‘==’, ‘TM1EOV4lYlgEIly0cnGHVmCnybT2’).get();
let helper_token_needed;
helper_token.forEach((token) => {
helper_token_needed = token.data();
});
console.log(helper_token_needed.device_token);
return helper_token_needed.device_token;
}
//when calling to the function use then() block to get the value as a promise is returned from asynchronous function
getHelperToken().then((value)=>{console.log(value)});

Can't update Mongo Collection in Meteor from within a function

I have a webhook set up that lives in imports/api in my application. This is a Stripe webhook and I’m trying update Campaign collection. I’m using a Meteor method to make this call however nothing happens. If I add the call back to it, I receive no error or results. However if I take this same Meteor Method an include it in main.js on the server in a Meteor startup function it runs fine. I’ve also tried just using the update function for a collection and it won’t work either. Main.js includes this file and I cannot figure out why this doesn’t work nor will push an error. The most important part of this line is the bottom where Paid == true, this is where the logic is. The console will spit out the information but the method won’t run. What could be the issue?
import bodyParser from 'body-parser';
import { Picker } from 'meteor/meteorhacks:picker';
import {Campaign} from '../api/campaigns';
let campaignID;
let chargeAmount;
let chargeID;
let hasPaid = false;
//This will run outside the updateCampaign function fine and works as expected
Campaign.update({_id: "BAxBhk4ae3AdHxxEQ"}, {$set: {chargeTrx: "chargeID", amount: "555"}});
function updateCampaign(){
//these consoles will print and prove the function runs
console.log('campaignID type:' + campaignID)
console.log('chargeIDtype:' + chargeID)
console.log('amount type:' + chargeAmount)
//doesn't work even with strings prefilled
Campaign.update({_id: "BAxBhk4ae3AdHxxEQ"}, {$set: {chargeTrx: "chargeID", amount: "333"}});
//This didn't run either but would run outside of this function
Meteor.call("updateCampaignForPayment", campaignID, chargeAmount, chargeID, (err, result) => {
if (err){
console.log('error')
}
});
}
// Middleware declaration
Picker.middleware(bodyParser.json({
limit: '10MB',
verify: function(req,res,buf) {
var url = req.originalUrl;
if (url.startsWith('/webhook')) {
req.rawBody = buf.toString();
let newResponse = req.rawBody;
//stripe returns to 2 objects, this makes it two arrays to parse it
let parsedResponse = JSON.parse('[' + newResponse.replace(/}{/g, '},{') + ']');
parsedResponse = parsedResponse[0].data.object;
//break down two further objects
if (parsedResponse.object == "charge"){
chargeID = parsedResponse.id;
hasPaid = parsedResponse.paid;
chargeAmount = parsedResponse.amount / 100;
}
else if (parsedResponse.object == "checkout.session"){
let campaignIDArray = parsedResponse.success_url.split('/');
campaignID = campaignIDArray[5];
}
// If user has paid, update campaign
if (hasPaid == true && chargeID && campaignID && chargeAmount){
console.log(hasPaid, chargeID, campaignID, chargeAmount)
//
updateCampaign();
}
}
}
}));
Any calls outside of the current Meteor environment (such as in callback functions or middleware handlers) to a construct, which requires Meteor environment, are to be bound using the Meteor environment.
In your case this seems to be updateCampaign, because it makes calls to the Meteor-Mongo Collection:
const updateCampaign = Meteor.bindEnvironment(function () {
const updated = Campaign.update({_id: "BAxBhk4ae3AdHxxEQ"}, {$set: {chargeTrx: "chargeID", amount: "333"}});
const callResult = Meteor.call("updateCampaignForPayment", campaignID, chargeAmount, chargeID);
})
Please also note, that the collection inside this function is running in async mode and only within Meteor environment code you can write code in a sync way and let it handle the async. You can always "wait" for the result there using async/await:
function async updateCampaign(){
const updated = await Campaign.update({_id: "BAxBhk4ae3AdHxxEQ"}, {$set: {chargeTrx: "chargeID", amount: "333"}});
//This didn't run either but would run outside of this function
const callResult = await Meteor.call("updateCampaignForPayment", campaignID, chargeAmount, chargeID);
}
or you use Promise.await, if you want to avoid async/await:
function updateCampaign(){
const updated = Promise.await(Campaign.update({_id: "BAxBhk4ae3AdHxxEQ"}, {$set: {chargeTrx: "chargeID", amount: "333"}}))
//This didn't run either but would run outside of this function
const callResult = Promise.await(Meteor.call("updateCampaignForPayment", campaignID, chargeAmount, chargeID))
}
Readings:
https://guide.meteor.com/using-npm-packages.html#bind-environment
https://docs.meteor.com/api/methods.html#Meteor-call
https://guide.meteor.com/using-npm-packages.html#promises
https://github.com/meteor/meteor/tree/devel/packages/promise

How to query firestore with the Dialogflow inline editor to get information

I am using the inline editor within Dialogflow with the aim of making queries to the database I have created within Firestore.
In short, the user requests a list of courses, I'd like the chatbot to then grab that information form the db and display that back to the user.
Below I have tried to create a function that will do this, I want to take the user input, say "Art Courses" and have my db return those results.
So far, I have created a function that is triggered when the intent is matched, like so;
function getCourses(agent){
let courseRequest = agent.parameters.courseRequest;
if (getCourses){
console.log('Here is the list you requested for ${getCourses}' + parameters.courseRequest);
return admin.firestore().collection('Course_Information').doc.where('CoureTypes').get();
}
}
Are there any notable things I need to add to my function to perform what I wish to achieve?
Thank you.
UPDATE
This code deploys fine, but when I communicate with my bot and trigger the CourseEnquiry intent, cloud Functions shows this error:
admin.collection is not a function
Whilst this seems self explanatory I can't make sure of what it means, I thought declaring const admin = require('firebase-admin');enables me to use admin.collection
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function getDate(agent){
var today = new Date();
}
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function test(agent){
agent.add("The test is successful");
}
function getCourses(agent){
// Get the database collection and document
const getCourseDoc = admin.collection('Course_Information').doc('Course_Types');
return getCourseDoc.get()
.then(doc => {
if (!doc.exists) {
agent.add('No data found in the database!');
} else {
agent.add(doc.data().entry);
}
return Promise.resolve('Here is the information you wanted');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
});
}
function getSubmissionDateSep(agent){
agent.add('Your next submission date is for coursework 1 is');
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Test_Test', test);
intentMap.set('CourseEnquiry', getCourses);
intentMap.set('Submission_Dates - sept', getSubmissionDateSep);
agent.handleRequest(intentMap);
});
UPDATE #2
Hey guys, still not got anywhere with this, I have tried adding:
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
According to this document but I get this error when deploying:
The deployment of your Cloud Function failed:
Function load error: Code in file index.js can't be loaded.
Is there a syntax error in your code?
Detailed stack trace: Error: Firebase config variables are not available. Please use the latest version of the Firebase CLI to deploy this function.
You don't show how you're responding to the user with your results, but you'll want to make sure you handle that as part of the then() clause in a Promise. Since the get() in the firestore collection returns a Promise, and you are returning it from your function, you need to make sure that the calling function treats it as a Promise, has a then() clause, and sends back the result as part of something inside this clause.

How do you get a syncfusion custom adapter to work with the feathers socket.io client

feathers-client 2.3.0
syncfusion-javascript 15.3.29
I have been trying for awhile to create a syncfusion custom adapter for the feathers socket.io version of it's client. I know I can use rest to get data but in order for me to do offline sync I need to use the feathers-offline-realtime plugin.
Also I am using this in an aurelia project so I am using es6 imports with babel.
Here is a code snippet I have tried, I can post the whole thing if needed.
I am also not sure if just using the Adapter vs UrlAdapter is correct as I need sorting and paging to hit the server and not just to do it locally. I think I can figure that part out if I can at least get some data back.
Note: Per Prince Oliver I am adding a clarification to the question I need to be able to call any methods of the adapter as well besides just proccessQuery such as onSort. When the datagrid calls the onSort method I need to be able to call my api using the feathers socket.io client since it handles socket.io in a special manner for offline capabilities.
import io from 'socket.io-client';
import * as feathers from 'feathers-client';
const baseUrl = 'http://localhost:3030';
const socket = io.connect(baseUrl);
const client = feathers.default()
.configure(feathers.hooks())
.configure(feathers.socketio(socket));
const customers = client.service('customers');
export class FeathersAdapter {
feathersAdapter = new ej.Adaptor().extend({
processQuery: function (ds, query) {
let results
makeMeLookSync(function* () {
results = yield customers.find();
console.log(results);
});
The result is undefined. I have tried several other ways but this one seems like it should work.
REVISED CODE:
I am now getting data but also strange error as noted in the picture when I call
let results = await customers.find();
The process then continues and I get data but when the result variable is returned there is still no data in the grid.
async processQuery(ds, query) {
let baseUrl = 'http://localhost:3030';
let socket = io.connect(baseUrl);
let client = feathers.default()
.configure(feathers.hooks())
.configure(feathers.socketio(socket));
let customers = client.service('customers');
let results = await customers.find();
var result = results, count = result.length, cntFlg = true, ret, key, agg = {};
for (var i = 0; i < query.queries.length; i++) {
key = query.queries[i];
ret = this[key.fn].call(this, result, key.e, query);
if (key.fn == "onAggregates")
agg[key.e.field + " - " + key.e.type] = ret;
else
result = ret !== undefined ? ret : result;
if (key.fn === "onPage" || key.fn === "onSkip" || key.fn === "onTake" || key.fn === "onRange") cntFlg = false;
if (cntFlg) count = result.length;
}
return result;
The processQuery method in the DataManager is used to process the parameter which are set in the ej.Query like skip, take, page before fetching the data. Then the data is fetched asynchronously based on these parameters and fetched data is processed in processResponse method to perform operations like filtering or modifying. The processQuery function operates synchronously and it does not wait for the asynchronous process to complete. Hence the returned data from the API did not get bound on the Grid and throws undefined error.
So, if you are using the socket.io to fetch the data from the API, then the data can be directly bound to the Grid control using the dataSource property. Once the dataSource is updated with the result, it will be reflected in Grid automatically through two-way binding.
[HTML]
<template>
<div>
<ej-grid e-data-source.bind="gridData" e-columns.bind="cols"> </ej-grid>
</div>
</template>
[JS]
let baseUrl = 'http://localhost:3030';
let socket = io.connect(baseUrl);
let client = feathers.default()
.configure(feathers.hooks())
.configure(feathers.socketio(socket));
let customers = client.service('customers');
let results = await customers.find();
this.gridData = results; // bind the data to Grid

Using Restangular, can I use a jsonResultsAdapterProvider when needing to override the id field?

I've got a mySql db with non-standard IDs and field names, so I was trying to use both jsonResultsAdapterProvider and setRestangularFields. Here's the code in my app.config file:
RestangularProvider.setBaseUrl(remoteServiceName);
RestangularProvider.setRestangularFields({id: 'personID'});
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (data.error) {
return data.error;
}
var extractedData = data.result;
return jsonResultsAdapterProvider.$get().camelizeKeys(extractedData);
});
RestangularProvider.addRequestInterceptor(function(elem, operation, what, url) {
return jsonResultsAdapterProvider.$get().decamelizeKeys(elem);
});
It's all good until I try to do a put/save. When I look at the request payload within the browser dev tools, it's: {"undefined":12842} (but the url is correct, so I know the id is set) If I don't use the ResultsAdapter and change the id field to Person_ID, payload looks good, so I know I'm making the right calls to Get and Save the Restangular objects. But for what it's worth, here's the code:
$scope.tests = Restangular.all('members').getList().$object;
vm.testEdit = function () {
$scope.test = Restangular.one('members', 12842).get().then(function(test) {
var copy = Restangular.copy(test);
copy.title = 'xxxx';
copy.put(); // payload was: undefined: 12842
});
}
// I also tried customPUT...
// copy.customPUT(copy, '', {}, {'Content-Type':'application/x-www-form-urlencoded'});
I tried "fixing" the id other ways too, too. like this:
Restangular.extendModel('members', function(model) {
model.id = model.personID;
return model;
});
but that messed up the urls, causing missing ids. And I tried getIdFromElem, but it only got called for my objects created with Restangular.one(), not with Restangular.all()
Restangular.configuration.getIdFromElem = function(elem) {
console.log('custom getIdFromElem called');
if (elem.route === 'members') { // this was never true
return elem[personID];
}
};
It seems like Restangular needs to substitute 'personID' most of the time, but maybe it needs 'Person_ID' at some point during the Save? Any ideas on what I could try to get the Save working?
I finally figured it out! The problem was in my config code and in the way I was decamelizing. Because of inconsistencies in my db field names (most use underscores, but some are already camelCase), I was storing the server's original elem names in an array within the jsonResultsAdapterProvider. But since I was calling jsonResultsAdapterProvider.$get().camelizeKeys(extractedData); within the interceptors, I was reinstantiating the array each time I made a new request. So, the undefined in the PUT request was coming from my decamelizeKeys() method.
My updated config code fixed the problem:
RestangularProvider.setBaseUrl(remoteServiceName);
RestangularProvider.setRestangularFields({id: 'personID'});
var jsonAdapter = jsonResultsAdapterProvider.$get();
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (data.error) {
return data.error;
}
var extractedData = data.result;
// return extractedData;
return jsonAdapter.camelizeKeys(extractedData);
});
RestangularProvider.addRequestInterceptor(function(elem, operation, what, url) {
return jsonAdapter.decamelizeKeys(elem);
});