Using variables or constants in nextjs api - rest

I'm trying to use a constant in my API. This constant is an array where I can push or pop objects. I populate it through API calls. This is the code:
const washes = [];
export default async (req, res) => {
if(req.method === 'GET')
{
res.json({washes});
}
if(req.method === 'POST')
{
washes.push(req.body);
console.log(washes);
res.json({washes});
}
}
I make calls from the front-end to populate the array and to retrieve it. The problem is that, sometimes I get an empty array an sometimes I get the expected array. Or after a while I always get the empty array, like if it was restarted.
I deployed the application on Vercel.
Is there something I'm missing from the back-end functionality, or is it related to Vercel?. Last but not least is it a good practice to use variables or constants in back-end.?

You cant use an array as storage. You need to connect it to a database of sorts.
When you use this endpoint it will only retrieve the correct array sometimes, because it's stored in a temporary memory on the server.
Look into firebase if you easily want to setup up a project and store information in a database.
https://firebase.google.com/docs/web/setup
Another way is to use a .json file in the back-end
Read more here: https://jasonwatmore.com/post/2021/08/28/next-js-read-write-data-to-json-files-as-the-database
USER EXAMPLE
const fs = require('fs');
// users in JSON file for simplicity, store in a db for
production applications
let users = require('data/users.json');
export const usersRepo = {
getAll: () => users,
getById: id => users.find(x => x.id.toString() === id.toString()),
find: x => users.find(x),
create,
update,
delete: _delete
};
function create(user) {
// generate new user id
user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
// set date created and updated
user.dateCreated = new Date().toISOString();
user.dateUpdated = new Date().toISOString();
// add and save user
users.push(user);
saveData();
}
function update(id, params) {
const user = users.find(x => x.id.toString() === id.toString());
// set date updated
user.dateUpdated = new Date().toISOString();
// update and save
Object.assign(user, params);
saveData();
}
// prefixed with underscore '_' because 'delete' is a reserved word in javascript
function _delete(id) {
// filter out deleted user and save
users = users.filter(x => x.id.toString() !== id.toString());
saveData();
}
// private helper functions
function saveData() {
fs.writeFileSync('data/users.json', JSON.stringify(users, null, 4));
}

Related

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

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()
}
}

creating a sub user that inherit premium status from an admin?

I have this cloud function that I pass the id of an admin (sharedId)and id of sub user (subId), then make a quarry to get the admin's premiumUntill value of the doc, and copy it to the sub user. so if admin premium goes away.. a sub-user will become obsolete as well, the issue here is I am trying to find some safe guard the problem is anyone who can get an active premium admin id can use this function to make him self premium as well.... any idea how to go about this I don't want to use the method where each time a user logs in it checks the premium in the admin doc its very recourses consuming ?
my current solution is , I have the sub user id created already in the admin and stored in an array of sub users which is an immutable doc,
what I do is check if the incoming id in data.subid will be equal to snapshopt.subuss.id in that doc will this make sure that no body can mess with the data ? this will make the id a constant to verify the incoming data against. but I still it might have an issue.
export const createSubUser = functions.https.onCall(async (data, context) => {
const snapshot = await db.collection('AdminData').doc(data.sharedId).get();
// Safe Guard !!!!!1
// if(//some guard logic)
// Current solution
// getting the data from an immutable doc
const getSubuser = snapshot.data().subusers.filter((el: any) => el.subId === data.subId);
if (getSubuser[0].subId === data.subId) {
const payload = {
user: 'sub',
verifiedEmail: false,
subId: data.subId,
sharedId: data.sharedId,
createdAt: admin.firestore.Timestamp.now(),
premiumUntill: snapshot.data()?.premiumUntill,
};
return db
.collection('SubData')
.doc(context.auth?.uid!)
.set(payload)
.catch((err: any) => {
console.log(err);
});
});

Dialogflow to Firestore using Inline Fulfillment - Store all user data in one document

How do we store all user input data in one document per one chat session?
I tried this code:
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
process.env.DEBUG = 'dialogflow:debug';
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function getAge(agent) {
let age = agent.parameters.age;
db.collection("users").add({age: age});
}
function getLocation(agent) {
let location = agent.parameters.location;
db.collection("users").add({location: location});
}
function getCustomerExperience(agent) {
let customerExperience = agent.query;
db.collection("users").add({customerExperience: customerExperience});
}
let intentMap = new Map();
intentMap.set('age', age);
intentMap.set('location', getLocation);
intentMap.set('customer-experience', getCustomerExperience);
agent.handleRequest(intentMap);
});
but the data were stored in different document IDs:
What I'm trying to achieve is something like this:
If I'm not being clear, please let me know. I'm new to Dialogflow, Firebase, as well as the JS language. Cheers!
You're on the right track! The fundamental problem with your original code is that collection.add() will create a new document. But you want it to create a new document sometimes, and save it in a previous document other times.
This means that, during the entire Dialogflow session, you'll need some way to know what the document name is or should be. There are a few possible ways to do this.
Use a document based on the session
Dialogflow provides a session identifier that you can get as part of the agent.session property using the dialogflow-fulfillment library, or in the session property if you're parsing the JSON request body directly.
However, this string includes forward slash / characters, which should be avoided in document names. Fortunately, the format of this string is documented to be one of the two formats:
projects/Project ID/agent/sessions/Session ID
projects/Project ID/agent/environments/Environment ID/users/User ID/sessions/Session ID
In each case, the Session ID is the last portion of this path, so you can probably use code something like this to get the ID for the session, use it as your document name, and then save an attribute (for example, age) for it:
function documentRef( agent ){
const elements = agent.session.split('/');
const lastElement = elements[elements.length - 1];
return db.collection('users').doc(lastElement);
}
async function getCourier(agent) {
const ref = documentRef( agent );
const age = agent.parameters.age;
return await ref.update({age: age});
}
Note that I have also made getCourier() an async function, because the function calls that change the database (such as ref.update()) are async functions and Dialogflow requires you to either make it an async function or explicitly return a Promise. If you wish to return a Promise instead, this would be something more like this:
function getCourier(agent) {
const ref = documentRef( agent );
const age = agent.parameters.age;
return ref.update({age: age});
}
Use the document name generated by Firestore
With this method, you'll store a document name as a Context parameter. When you go to save a value, you'll check if this document name is set. If it is, you'll do an update() using this document name. If not, you'll do an add(), get the document name, and save it in the Context parameter.
It might look something like this (untested), again for the age:
async function getCourier( agent ){
const ref = db.collection('users');
const age = agent.parameters.age;
const docContext = agent.context.get('doc');
if( !docContext ){
// We don't previously have a document, so create it
const res = await ref.add({age:age});
// And set a long-lasting context with a "name" parameter
// containing the document id
agent.context.set( 'doc', 99, {
'name': ref.id
} );
} else {
// There is a context with the document name already set
// so get the name
const docName = docContext.parameters['name'];
const docRef = ref.doc(docName);
// And save the data at this location
await docRef.update({age: age});
}
}
Again, this uses an async function. If you'd rather use a Promise, it might be something more like this:
function getCourier( agent ){
const ref = db.collection('users');
const age = agent.parameters.age;
const docContext = agent.context.get('doc');
if( !docContext ){
// We don't previously have a document, so create it
return ref.add({age:age})
.then( ref => {
// And set a long-lasting context with a "name" parameter
// containing the document id
agent.context.set( 'doc', 99, {
'name': ref.id
} );
});
} else {
// There is a context with the document name already set
// so get the name
const docName = docContext.parameters['name'];
const docRef = ref.doc(docName);
// And save the data at this location
return docRef.update({age: age});
}
}
Use a document name you've generated and saved in the context
You don't need to use the session id from the first alternative. If you have some ID or name that makes sense on your own (a username or a timestamp, for example, or some combination), then you can save this in a Context parameter and use this each time as the document name. This is a combination of the first and second approaches above (but probably simpler than the second one, since you don't need to get the document name from creating the document the fist time).

Saving ag-grid filter model across page reloads

I have an ag-grid with infinite scroll and data retrieved from an IDatasource.
What I'm trying to do is to save the filter model to session storage when it changes, and then load it and apply it when the grid is reloaded, i.e. when the user leaves the page and then comes back.
I have an onFilterChanged event handler that does
onFilterChanged(params) {
sessionStorage["myFilters"] = JSON.stringify(this.gridApi.getFilterModel());
}
And what I'm trying to do is
onGridReady(params) {
this.gridApi = params.api;
setTimeout(() => {
if(sessionStorage["myFilters"] !== undefined) {
const filters = JSON.parse(sessionStorage["myFilters"]);
this.gridApi.setFilterModel(filters);
}
this.gridApi.setDatasource(this.myDataSource);
}, 0);
}
However, even if the JSON saved to session storage is correct, when getRows is invoked on my IDatasource, its filterModel param has empty values for the filters:
Does this have to do with the fact that my filter is a set filter and the values for the set are loaded dynamically from another API endpoint?
Is there a way to do this?
Turns out I had a bug in my set filter, which was not implementing setModel and getModel properly; the solution was to store the value of the filter in the filter component itself when calling setModel and to check against it when calling getModel:
getModel() {
return {
filter: this.items
.filter((item) => item.checked || item.name === this.selected)
.map((item) => item.name)
.join(),
};
}
setModel(model: any): void {
if (model && model.filter) {
this.selected = model.filter.name || model.filter;
}
}
This way the filter is able to compare the value retrieved from sessionStorage against the existing items, and it works as expected.

Intent not moving to next intent

first intent
second intent
As shown in the below code, the flow is not going from Number Intent to First Intent, it is been looped into the number loop. In dialog flow, with every intent corresponding context is also made. The flow is not moving as per context and is stuck in NumberIntent.
The flow should be like the google ask the user its survey id, the user says its id 612020 and google start asking its questions. The flow works fine until the type of question is rating i.e. user has to speak number. The error arises when the user is asked to answer in descriptive manner.
'use strict';
// Import the Dialogflow module from the Actions on Google client library.
const {dialogflow} = require('actions-on-google');
const functions = require('firebase-functions');
// Instantiate the Dialogflow client.
const app = dialogflow({debug: true});
const axios = require('axios').default;
global.ques = [];
global.i=0;
app.intent('Default Welcome Intent', (conv) => {
conv.add('Hello! What is your survey id?');
});
app.intent('NumberIntent', (conv,{number}) => {
return axios.get('https://www.openeyessurvey.com/api/get_open_survey_info/612020')
.then((result) => {
result.data.Item.QUESTIONS.map(questionobj => {
ques.push(questionobj.question);
})
conv.ask(ques[i]);
i+=1;
}).catch( err => {
console.log("error", JSON.stringify(err,null,2));
conv.close('This is not a valid survey ID');
});
});
app.intent('FirstIntent', (conv, {number}) => {
conv.ask(ques[i]);
i+=1;
});
app.intent('SecondIntent', (conv) => {
const des = conv.parameters.any;
if(des === 'ankit'){
conv.ask(ques[i]);
i+=1;
}
});
app.intent('ThirdIntent', (conv) => {
conv.ask(ques[i]);
i+=1;
});
app.intent('FourthIntent', (conv, {number}) => {
conv.ask(ques[i]);
i+=1;
});
app.intent('FifthIntent', (conv) => {
conv.ask(ques[i]);
i+=1;
conv.close('Goodbye!')
});
// Set the DialogflowApp object to handle the HTTPS POST request.
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
Output
Output2
INVALID INTENT NAME ERROR
I suspect that the issue is that i never actually gets updated.
You treat ques and i as global object, but since this is running under Firebase Cloud Functions, each call may be a new instance of the function. As a new instance, these would get reinitialized.
The flip side of this is that if you didn't get a new instance, it also has the problem that this would not work correctly if more than one person was using the Action at the same time since they would all be sharing the same value of i.
The solution to both is that, instead of storing i as a global variable, store it either in the Actions on Google session storage or in a Dialogflow context parameter.
Storing it as a session parameter, you would get the value, use it, increment it, and then save it again in the session parameter. It might look something like this:
const i = conv.data.i;
conv.ask(ques[i]);
conv.data.i = i+1;