I have an input that sends an api call on submit to the unsplash API. I am trying to convert this into a netlify function but am not sure how to pass the params from the input into the function. I am trying to keep the API key hidden. I've never worked with the qs package and looked up the docs but have not been able to quite figure it out.
script.js
const KEY = "" //secret
const URL = `https://api.unsplash.com/search/photos?page=1&per_page=50&client_id=${process.env.KEY}`;
const input = document.querySelector(".input");
const form = document.querySelector(".search-form");
const background = document.querySelector(".background");
const overlay = document.querySelector(".overlay");
const header = document.querySelector(".title");
let results = [];
search = (searchTerm) => {
let url = `${URL}&query=${searchTerm}`;//this should hit the netlify endpoint instead
return fetch(url)
.then((response) => response.json())
.then((result) => {
toggleStyles();
header.appendChild(form);
result.results.forEach((image) => {
const galleryItem = document.createElement("div");
galleryItem.className = "gallery-item";
const imageDiv = document.createElement("div");
imageDiv.className = "image-div";
document.querySelector(".results-page").appendChild(galleryItem);
galleryItem.appendChild(imageDiv);
imageDiv.innerHTML =
"<img class='image' src=" + image.urls.regular + ">";
form.classList.remove("toggle-show");
input.classList.add("header-expanded");
form.addEventListener("submit", (e) => {
e.preventDefault();
document.querySelector(".results-page").remove();
});
});
console.log(result.results);
return results;
});
};
toggleStyles = () => {
const resultsContainer = document.createElement("div");
resultsContainer.className = "results-page";
document.body.appendChild(resultsContainer);
};
input.addEventListener("focus", (e) => {
e.preventDefault();
input.style = "font-family: 'Raleway', sans-serif";
input.placeholder = "";
});
input.addEventListener("blur", (e) => {
e.preventDefault();
input.style = "font-family: FontAwesome";
input.value = "";
input.placeholder = "\uf002";
});
form.addEventListener("submit", (e) => {
e.preventDefault();
let searchTerm = input.value;
search(searchTerm);
});
token-hider.js
const axios = require("axios");
const qs = require("qs");
exports.handler = async function (event, context) {
// apply our function to the queryStringParameters and assign it to a variable
const API_PARAMS = qs.stringify(event.queryStringParameters);
console.log("API_PARAMS", API_PARAMS);
// Get env var values defined in our Netlify site UI
// TODO: customize your URL and API keys set in the Netlify Dashboard
// this is secret too, your frontend won't see this
const { KEY } = process.env;
const URL = `https://api.unsplash.com/search/photos?page=1&per_page=50&client_id=${KEY}`;
console.log("Constructed URL is ...", URL);
try {
const { data } = await axios.get(URL);
// refer to axios docs for other methods if you need them
// for example if you want to POST data:
// axios.post('/user', { firstName: 'Fred' })
return {
statusCode: 200,
body: JSON.stringify(data),
};
} catch (error) {
const { status, statusText, headers, data } = error.response;
return {
statusCode: error.response.status,
body: JSON.stringify({ status, statusText, headers, data }),
};
}
};
I added the KEY as an Environment variable in my netlify UI, and am able to hit the function's endpoint. Any help is greatly appreciated as I am new to serverless functions but want to learn JAMstack really badly.
I think you are asking two different things here - how to use secret keys in Netlify functions and how to pass parameters to it from your front end code.
You can define environment variables in your Netlify site settings. These variables can then be used in your Netlify functions via process.env. So if you called your variable SECRET_KEY, then your Netlify function code (not your front end code!) would be able to read it via process.env.SECRET_KEY.
Looking over your code, it looks like you understand that as you have it in your function, but you also try to use it in the client-side code. You can remove that.
Your code gets the query string parameters, and it looks like you just need to add them to the end of the URL you hit. Did you try that?
Related
const handleItinerary = (e, type) => {
var index = parseInt(e.target.name);
let arr = [...itinerary];
if (type === "imageUrl") {
const date = new Date().getTime();
const storageRef = ref(storage, `${date}`);
uploadBytes(storageRef, e.target.files[0]).then((snapshot) => {
getDownloadURL(storageRef).then((downloadURL) => {
arr[index]["imageUrl"] = downloadURL;
});
});
}
setitinerary(arr);
}
In the above code I am trying to upload an image in firebase storage using uploadBytes function and after uploading the image I get the downloadURL where image is stored, I want to put its value in arr[index]["imageUrl"], but the arr[index]["imageUrl"] is getting updated first before getting the downloadURL and I am getting error that downloadURL is undefined, so how to resolve this issue?
I am using react 18 and firebase version 9.
When using then() to run code in response to an asynchronous operation being completed, any code that needs to run upon completion has to be inside that then() callback.
So
const handleItinerary = (e, type) => {
var index = parseInt(e.target.name);
let arr = [...itinerary];
if (type === "imageUrl") {
const date = new Date().getTime();
const storageRef = ref(storage, `${date}`);
uploadBytes(storageRef, e.target.files[0]).then((snapshot) => {
getDownloadURL(storageRef).then((downloadURL) => {
arr[index]["imageUrl"] = downloadURL;
setitinerary(arr);
});
});
}
}
To make this a bit more familiar, you can mark the `` as async and use await inside it:
const handleItinerary = async (e, type) => {
var index = parseInt(e.target.name);
let arr = [...itinerary];
if (type === "imageUrl") {
const date = new Date().getTime();
const storageRef = ref(storage, `${date}`);
const snapshot = await uploadBytes(storageRef, e.target.files[0]);
const downloadURL = await getDownloadURL(storageRef);
arr[index]["imageUrl"] = downloadURL;
setitinerary(arr);
}
}
Note that this doesn't change anything about the actual behavior and all asynchronous calls are still executed asynchronously. It is merely a more familiar way to write the code.
If you have a list of images to upload, be sure to either use for of instead of forEach or Promise.all to detect when all asynchronous operations are done.
You can move the code that updates the arr[index]["imageUrl"] value inside the then block where you retrieve the downloadURL. This will ensure that the arr[index]["imageUrl"] value is only updated after the downloadURL has been retrieved.
const handleItinerary = (e, type) => {
var index = parseInt(e.target.name);
let arr = [...itinerary];
if (type === "imageUrl") {
const date = new Date().getTime();
const storageRef = ref(storage, `${date}`);
uploadBytes(storageRef, e.target.files[0]).then((snapshot) => {
getDownloadURL(storageRef).then((downloadURL) => {
arr[index]["imageUrl"] = downloadURL;
setitinerary(arr);
});
});
}
}
I would like to ask if is it possible to add data in a nested array.The result i want is this
But i get this when i add a new rating with the code i use
async function getAll(){
const userEmail= firebase.firestore().collection('users')
const snapshot=await userEmail.where('email','==',index.email).get()
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
userEmail.doc(doc.id).set({userRatings2:firebase.firestore.FieldValue.arrayUnion({parking:[markerName]})},{merge:true})
userEmail.doc(doc.id).set({userRatings2:firebase.firestore.FieldValue.arrayUnion({rating:[currentRating]})},{merge:true})
console.log(userEmail.doc(doc.id));
});
}
getAll()
Unfortunately in Firebase Firestore you can't even change a simple Array value at a specific index. That means also that you can't save nested array values. You can read more about it on this answer.
The only way to do it is to download the whole Array make your modifications and save the whole array again to the databse. There is not much context of your app so I can't tell if that is a good solution for you. It depends how much data you have in the array.
I've managed to do it by using forEach function to an array of image path
const [imageUri, setImageUri] = useState([]);
const [uploading, setUploading] = useState(false);
const UploadImage = () => {
setUploading(true);
imageUri.forEach(async (image, index) => {
// setTransferred(0);
const pathToFile = image;
let filename = pathToFile.substring(pathToFile.lastIndexOf('-') + 1);
const extension = filename.split('.').pop();
const name = filename.split('.').slice(0, -1).join('.');
filename = name + Date.now() + '.' + extension;
const reference = storage().ref().child(`/userprofile/${filename}`);
// path to existing file on filesystem
// uploads file
const task = reference.putFile(pathToFile);
try {
await task;
const url = await reference.getDownloadURL();
downloadableURI.push(url);
if (index == imageUri.length - 1) {
setUploading(false);
Alert.alert(
'Image uploaded!',
'Your image has been uploaded to the Firebase Cloud Storage Successfully!',
);
}
} catch (e) {
console.log(e);
}
});
};
whenever the function is called, then the array of images is uploaded to firebase storage,
I've been trying to find an answer to this for a few weeks, but couldn't quite do this, so I decided to ask.
I sometimes get this error:
ReferenceError: <SomeObject> is not defined
...whereas I know for sure that it is. The problem is that the object is located in a different file, so if I call the object from that file (or maybe even a third file), the code does work.
I believe this must have to do with how hoisting works, meaning I'm trying to call an object before it's declared.
But then how does it work exactly when you have different files?
Here's an example:
If I have the following code in one file and I run getID(), it works:
const SomeAPI = (function () {
const _auth = new WeakMap();
const _url = new WeakMap();
class SomeAPI {
constructor(url, user = DEFAULT_USER, pwd = DEFAULT_PWD) {
_url.set(this, url);
_auth.set(this, Utilities.base64Encode(`${user}:${pwd}`));
}
async fetch() {
const headers = {
Authorization: `Basic ${_auth.get(this)}`,
};
const options = {
headers,
};
const response = await UrlFetchApp.fetch(_url.get(this), options);
const data = JSON.parse(response);
return data;
}
}
return SomeAPI;
})();
const LIST_DATA = (async () => await getListData())();
async function getListData() {
const response = await new SomeAPI(ALL_SETTINGS['List API URL']).fetch();
return Array.isArray(response) ? response[0] : response;
}
const getID = async () => {
const listData = await LIST_DATA;
const listId = listData.list_id;
const id = {
sheetId: SpreadsheetApp.getActive().getId(),
listId
};
console.log(id);
return id;
};
However, if I move LIST_DATA, getListData() and getID() to a different file, I get:
ReferenceError: SomeAPI is not defined
Overall the project is composed of 17 different files.
All help is greatly appreciated!
Per the comment by #Cooper to your question, moving the file up works. I had the same problem with a class I created. I moved the file with the class to the top and it solved my problem.
I use one Cloud Function to resize images and the second on for uploading a new image URL to Cloud Firestore.
But something doesn't work, because the second function never runs.
I need the uid and postId where I can update the url.
How can I call the second function to update the img url in Firestore?
Code
const { functions, tmpdir, dirname, join, sharp, fse, gcs } = require('../../admin');
const runtimeOpts = {
timeoutSeconds: 120,
memory: '1GB',
};
exports.resizeImages = functions
.runWith(runtimeOpts)
.storage.object()
.onFinalize(async (object, context) => {
const bucket = gcs.bucket(object.bucket);
const filePath = object.name;
const fileName = filePath.split('/').pop();
const bucketDir = dirname(filePath);
const workingDir = join(tmpdir(), 'resize');
const tmpFilePath = join(workingDir, 'source.png');
if (fileName.includes('#s_') || !object.contentType.includes('image')) {
return false;
}
await fse.ensureDir(workingDir);
await bucket.file(filePath).download({ destination: tmpFilePath });
// creates 3 new images with these sizes..
const sizes = [1920, 720, 100];
var newUrl = null;
const uploadPromises = sizes.map(async size => {
const ext = fileName.split('.').pop();
const imgName = fileName.replace(`.${ext}`, '');
const newImgName = `${imgName}#s_${size}.${ext}`;
var imgPath = join(workingDir, newImgName);
newUrl = imgPath;
await sharp(tmpFilePath)
.resize({ width: size })
.toFile(imgPath);
return bucket.upload(imgPath, {
destination: join(bucketDir, newImgName),
});
});
await Promise.all(uploadPromises);
//second function
functions.firestore.document('users/{uid}/posts/{id}').onCreate(async (snap, context) => {
console.log(context.params);
const uid = context.params.uid;
const userPost = functions.firestore.doc('users/{uid}/posts}');
userPost.update({
url: newUrl,
});
});
return fse.remove(workingDir);
});
Your second function appears to be embedded in the first. This isn't going to work. All function definition must be at the top level so they can be detected by the Firebase CLI and deployed separately.
If you don't actually want two separate function definitions, just perform all the work in one function, and don't try to use the functions SDK to do any of that work. The Functions SDK is just for defining the functions for deployment.
I am just trying a simple get command with Firestore, using this code from Google it doesn't work because it's not waiting for the promise?
Earlier I had put only a snippet of code, this is the entirety of index.js -- I'm using Firestore with Dialogflow to build a Google Assistant app and trying to call a function from the welcome intent that gets a field from Firestore, then writes that field to a string (named question1), and then this string should be spoken by the assistant as part of the ssml response. I've been on this for at least 30 hours already, can't seem to comprehend promises in regards to intents, firestore, etc. I've tried about 10 different solutions, this one works, only it says "undefined" in other variations I have tried it would say undefined several times but after 2-3 passes the get command would be complete and then the variable would be read out. I'm just trying to figure out how to get the get command and variable set before moving onto the SSML response. Can anyone point me in the right direction?
'use strict';
const functions = require('firebase-functions'); //don't forget this one
// Import Admin SDK
var admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
var db = admin.firestore();
const collectionRef = db.collection('text');
const Firestore = require('#google-cloud/firestore');
var doc;
var question1;
const url = require('url');
const {
dialogflow,
Image,
Permission,
NewSurface,
} = require('actions-on-google');
const {ssml} = require('./util');
const config = functions.config();
const WELCOME_INTENT = 'Default Welcome Intent';
const app = dialogflow({debug: true});
async function dbaccess(rando) {
console.log("dbaseaccess started")
var currentquestion2 = 'question-' + rando.toString();
var cityRef
try { return cityRef = db.collection('text').doc(currentquestion2).get();
console.log("get command completed")
//do stuff
question1 = cityRef.data().n111
} catch(e) {
//error!
}
console.log("one line above return something");
return rando;
}
app.fallback((conv) => {
// intent contains the name of the intent
// you defined in the Intents area of Dialogflow
const intent = conv.intent;
switch (intent) {
case WELCOME_INTENT:
var rando = Math.floor(Math.random() * 3) + 1;
dbaccess(rando);
const ssml =
'<speak>' +
question1 +
'</speak>';
conv.ask(ssml);
break;
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
You have 2 options: you can use async/await or you can use Promise.then() depending on how you want the code to execute.
Async/await:
async function databasetest {
var cityRef;
try{
cityRef = await db.collection('cities').doc('SF');
// do stuff
} catch(e) {
// error!
}
Promise.then():
db.collection('cities').doc('SF').then((cityRef) => {
cityRef.get()
.then(doc => { /* do stuff */ })
.catch(err => { /* error! */ });
});
maybe a little of work around could help you, I'm not sure yet how you are trying to implement it.
function databasetest () {
var cityRef = db.collection('cities').doc('SF');
return cityRef.get()
}
// so you can implement it like
databasetest().then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
})
.catch(err => {
console.log('Error getting document', err);
});
More context would help to understand your use case better :)