Adding photos from Instragram API to synchronized array in AngularFire - firebase

I built a simple Instagram app using the Instagram API and Angular.
Now I would like to permanently store the users search (aka response from Instagram server for each hashtag entered) to Firebase.
I got the Instagram API call working and saved it as $scope.instadata but how do I add $scope.instadata to Firebase using $add()?
I'm getting err:
angular.js:11500 Error: Firebase.set failed: First argument contains a function..
var app = angular.module('InstagramApp', ['firebase']);
app.controller('MainController', function ($scope, $http, $firebaseArray) {
// search posts based on hashtag
function getHash() {
var base = 'https://api.instagram.com/v1/tags/'
var service = document.getElementById('hashtag').value
var apiKey = '####'
var callback = 'JSON_CALLBACK'
var url = base + service + '/media/recent?access_token=' + apiKey + '&callback=' + callback
// use jsonp method to prevent CORS error
$http.jsonp(url).then(function(data) {
$scope.instadata = data
console.log("returned json object: ")
console.log($scope.instadata)
// set up firebase reference
var ref = new Firebase('https://myapp.firebaseio.com/fbdata')
// create a synchronized array
$scope.fbdata = $firebaseArray(ref)
// add new items to the array RETURNS ERROR
$scope.fbdata.$add($scope.instadata).then(
function(p){
console.log(p.key())
console.log($scope.instadata)
},
function(err){
console.log("The expected Firebase action failed to occur this was your error: " + err)
})
}
 })
}
document.getElementById('hashtag').addEventListener('click', getHash, false)
})
It's my first time asking a question here, working with API's, Angular and Firebase! I hope that's ok!

Related

How do I make a firestore query to be used with both get() and valueChanges()?

I am using Angular 8 and have a form where a user can choose what he wants to query the database for and then click either of two buttons - one to view data in realtime on the website, and the other to download the data.
I thought I could make use of one function to make a query and then call different functions depending on what button the user clicked, using get() for the download and valueChanges() for the realtime data view. But when I try this, I get the following errors in the browser console. (This is with query as type any - if I specify the type as AngularFirestoreCollection I get errors regarding my type for the get() part in VSCode)
ERROR Error: "Uncaught (in promise): TypeError: this.query.get is not
a function
I can add that I previously had two completely separate (working) functions for downloading and viewing in realtime. And for downloading I used the below query. I gather this is actually a Firestore Query, whereas the "query" I'm trying to use in my updated code is an AngularFirestoreCollection. But is there a way I can make some kind of Query/Collection that will work for both get() and valueChanges()?
Old (working) query:
var query = this.afs.collection(collection).ref.where('module', 'in', array_part);
Trying a common function makeQuery():
onSubmit(value, buttonType): void {
if (buttonType=='realtime') {
this.getRealTimeData(value);
}
if (buttonType=='download') {
this.downloadCsv(value);
}
}
async downloadCsv(value) {
this.query = this.makeQuery(value);
this.dataForDownload = await this.getDataForDownload();
this.dataForDownload = JSON.stringify(this.dataForDownload['data']);
console.log('Data: ', this.dataForDownload);
var date = new Date();
var date_str = this.datePipe.transform(date, 'yyyy-MM-ddTHH-mm');
this.makeFileService.downloadFile(this.dataForDownload, 'OPdata-' + date_str);
}
getDataForDownload() {
return this.query.get()
.then(function (querySnapshot) {
var jsonStr = '{"data":[]}';
var dataObj = JSON.parse(jsonStr); //making object we can push to
querySnapshot.forEach(function (doc) {
JSON.stringify(doc.data()), ', id: ', doc.id);
dataObj['data'].push(doc.data());
});
return dataObj;
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
}
async getRealTimeData(value) {
this.query = await this.makeQuery(value);
this.data = this.query.valueChanges();
}
async makeQuery(value) {
var collection: string;
return this.query = this.afs.collection<DataItem>('CollectionName', ref => ref.where('datetime', '>=', '2020-01-15T09:51:00.000Z').orderBy('datetime', 'desc').limit(100));
}
The valueChanges() is a method used in angularfire to retrieve data from firestore, while the get() method is used to retrieve from firestore but using the vanilla javascript.
Mixing both methods will return an error as you have seen in your code. Therefore, since angularfire was created above the javascript firebase code, then you should be able to use valueChanges() to view data in realtime on the website, and to download the data.

How to get public download link within a firebase storage trigger function: "onFinalize"?

I am writing a firebase cloud function that records the download link of a recentally uploaded file to real-time database:
exports.recordImage = functions.storage.object().onFinalize((object) => {
});
"object" gives me access to two variables "selfLink" and "mediaLink" but both of them when entered in a browser they return the following:
Anonymous caller does not have storage.objects.get access to ... {filename}
So, they are not public links. How can I get the public download link within this trigger function?
You have to use the asynchronous getSignedUrl() method, see the doc of the Cloud Storage Node.js library: https://cloud.google.com/nodejs/docs/reference/storage/2.0.x/File#getSignedUrl.
So the following code should do the trick:
.....
const defaultStorage = admin.storage();
.....
exports.recordImage = functions.storage.object().onFinalize(object => {
const bucket = defaultStorage.bucket();
const file = bucket.file(object.name);
const options = {
action: 'read',
expires: '03-17-2025'
};
// Get a signed URL for the file
return file
.getSignedUrl(options)
.then(results => {
const url = results[0];
console.log(`The signed url for ${filename} is ${url}.`);
return true;
})
});
Note that, in order to use the getSignedUrl() method, you need to initialize the Admin SDK with the credentials for a dedicated service account, see this SO Question & Answer firebase function get download url after successfully save image to firebase cloud storage.
*use this function:
function mediaLinkToDownloadableUrl(object) {
var firstPartUrl = object.mediaLink.split("?")[0] // 'https://storage.googleapis.com/download/storage/v1/b/abcbucket.appspot.com/o/songs%2Fsong1.mp3.mp3'
var secondPartUrl = object.mediaLink.split("?")[1] // 'generation=123445678912345&alt=media'
firstPartUrl = firstPartUrl.replace("https://storage.googleapis.com/download/storage", "https://firebasestorage.googleapis.com")
firstPartUrl = firstPartUrl.replace("v1", "v0")
firstPartUrl += "?" + secondPartUrl.split("&")[1]; // 'alt=media'
firstPartUrl += "&token=" + object.metadata.firebaseStorageDownloadTokens
return firstPartUrl
}
this is how your code might look like:
export const onAddSong = functions.storage.object().onFinalize((object) => {
console.log("object: ", object);
var url = mediaLinkToDownloadableUrl(object);
//do anything with url, like send via email or save it in your database in playlist table
//in my case I'm saving it in mongodb database
return new playlistModel({
name: storyName,
mp3Url: url,
ownerEmail: ownerEmail
})
.save() // I'm doing nothing on save complete
.catch(e => {
console.log(e) // log if error occur in database write
})
})
*I have tested this method on mp3 files, I'm sure it will work on all type of files but incase if it doesnt work for you simply go to firebase storage dashboard open any file and copy download url, and try to generate the same url in your code, and edit this answer too if possible

Google cloud function returning 204 status when accessing realtime database

I have a website for testing purposes hosted via firebase, storing client information on a realtime database which needs to be accessed later. When I do this via a single html document with a script that accesses my reatime database I am able to find information successfuly, but when I copied and pasted that same logic into a cloud function it did not work. I have tried everything I can think of and now when I run the function it executes twice (I am not sure why). The first execution finishes with a http 204 status (no content found). The second execution returns http 500 internal service error. When I checked the logs on firebase it says the error was because "accounts.getValue() is not a function". I think what is happening is on the first execution the function is unable to locate accounts and it executes again without trying to find the accounts, which might be why it can't run accounts.getValue()
I guess my main question is why is my function unable to locate accounts?
geturl is the function I am having trouble with
The structure of my realtime database is
database name
-accounts
-some data
-more data
-more account data
-ActiveQRs
-some data...
My index.js file for cloud functions is
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors')({origin: true});
var firebase = require("firebase");
var admin = require("firebase-admin");
require("firebase/auth");
require("firebase/database");
//require("firebase/firestore");
//require("firebase/messaging");
require("firebase/functions");
var serviceAccount = require("./serviceKey.json");
// Initialize the app with a service account, granting admin
//privileges
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://databaseName.firebaseio.com"
});
const displayqr = express();
const geturl = express();
displayqr.get('/displayqr', (request, response) => {
console.log("response sent");
response.send("testio/qrdisplay.html");
});
exports.displayqr = functions.https.onRequest(displayqr);
exports.geturl = functions.https.onCall((email) => {
const mail = email.toString();
var result = "";
result = result + mail;
var accounts =
admin.database().ref("livsuiteform/accounts");
result = (accounts.getValue());
accounts.orderByKey().on("value", function(snapshot) {
snapshot.forEach(function(data) {
if (data.child("Email").val() == mail) {
var firstName = data.child("FirstName").val();
var lastName = data.child("LastName").val();
result = firstname;
result = "if loop entered";
} // end if
// return "name not found";
}); // end for each
}); // end order by
return result;
});
TLDR; follow this tutorial on how to build and deploy callable functions for your mobile app.
There are multiple reasons for why your functions aren't working as you expect.
You are including the client-side version of Firebase (var firebase = require("firebase");). You shouldn't use or even require the client-side version. Instead just use Firebase Admin (docs) to access any data. If you need certain user permissions when accessing the DB from the Admin SDK, here is a good example of how to achieve that (Scroll down to "You can still perform user-authorized changes...").
You have mixed different Admin SDK references. getValue() is part of the Admin SDK for Java. You should use the JavaScript equivalent val(). Also, in your code, accounts is a Reference and not a DataSnapshot.
You aren't returning your Promise's. This can be a source of inconsistency in your function execution later SO Question.
You aren't returning anything from your initial function. If you don't return anything, then nothing will get returned to your app. The solution is the same as 3's solution: return your Promise.
You shouldn't use on in Firebase Functions. You should use once. The difference is that on doesn't return a Promise while once does. It returns a function that is used to detach the listener.
I know this is a lot of bullet points and pointing out problems in your code, but I just didn't want give a shallow answer which resulted in you asking another question and waiting another ~2 hours (at the time of writing) for an answer.
I hope this helps!
Code
exports.geturl = functions.https.onCall((email) => {
const mail = email.toString();
var result = "";
result = result + mail;
var accounts = admin.database().ref("livsuiteform/accounts");
return accounts.orderByKey().once("value")
.then(function (snapshot) {
snapshot.forEach(function (data) {
if (data.child("Email").val() == mail) {
var firstName = data.child("FirstName").val();
var lastName = data.child("LastName").val();
result = firstName;
result = "if loop entered";
} // end if
// return "name not found";
}); // end for each
return result;
}); // end order by
});

Is Firebase Database searchable using objects instead of references, with AngularFire $firebaseArray $keyAt(recordOrIndex)?

After a user logs in using $firebaseAuth, Google sends the user's displayName, email, and photoURL. I then want to look up the user's account in my Firebase database. I can't use $getRecord(key) because Google doesn't tell me the user's key. It appears that I should use $keyAt(recordOrIndex), and then use $getRecord(key). $keyAt(recordOrIndex) works fine with an index. $keyAt(recordOrIndex) works fine with a record that I retrieved with $getRecord(key). I can't get $keyAt(recordOrIndex) to work with an object that I made from the user data that Google returned using $firebaseAuth.
I tried both the complete object (displayName, email, photoURL) and an object consisting of only the email address. The latter is what I would prefer to use. Neither worked.
app.controller('LoginModalInstanceCtrl', ['$scope', '$location', '$uibModalInstance', '$firebaseArray', '$firebaseObject', '$firebaseAuth', function($scope, $location, $uibModalInstance, $firebaseArray, $firebaseObject, $firebaseAuth) {
// Create Firebase3 reference
var ref = firebase.database().ref();
// Set up Firebase Auth
$scope.authObj = $firebaseAuth();
var authData = $scope.authObj.$getAuth();
$scope.authData = authData;
// Google OAuth login handler
$scope.loginGoogle = function() {
$scope.authData = null;
$scope.error = null;
$scope.authObj.$signInWithPopup("google")
.then(function(authData) {
$scope.authData = authData;
console.log(authData);
console.log("Your displayName is:", authData.user.displayName);
console.log("Your email is:", authData.user.email);
console.log("Your photoURL is:", authData.user.photoURL);
var record = {
displayName: authData.user.displayName,
email: authData.user.email,
photoURL: authData.user.photoURL
};
var emailObject = {
email: authData.user.email
};
// look up account
var users = $firebaseArray(ref.child('users'));
users.$loaded()
.then(function() {
console.log("Array loaded!");
var key1 = users.$keyAt(1);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var record = users.$getRecord(key1);
console.log(record); // Object with displayName, email, and photoURL
var key1 = users.$keyAt(record);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var objectKey = users.$keyAt(object);
console.log(objectKey); // null
var emailKey = users.$keyAt(emailObject);
console.log(emailKey); // null
});
$uibModalInstance.close(); // close modal window
$location.path('/languagetwo/'); // return to the homepage
}).catch(function(error) {
console.error("Authentication failed:", error);
});
};
Should I use $firebaseObject instead of $firebaseArray:
var user = $firebaseObject(ref.child('users').child( SOMETHING HERE? ));
The answer appears to be no, you can't search Firebase Database using AngularFire. (Maybe AngularFire 2 has search, I didn't look.) What I did instead was to use "plain vanilla" Firebase:
var users = firebase.database().ref('users');
users.orderByChild('email').equalTo(authData.user.email).once('value').then(function(snapshot) {
console.log(snapshot.val());
});
The first line sets up the Firebase ref and is the similar to as before, except that I'm going straight to the users array, instead of using $FirebaseArray to get to the users array.
The second line is a completely different syntax. First, you have to specify the order that you want the returned object to be in. Yes, it returns an object, not an array. I tried snapshot.val().length() and found that it's not an array. What orderByChild('email') does is to access the 'email' property of the objects in the 'users' array.
Next we do the query. equalTo(authData.user.email) returns only the objects in which the email address from $FirebaseAuth equals the email address in our 'users' array.
Next, once('value') creates a promise and waits for the async data. I tried using on() but couldn't get it to work, too many arguments or something. once() requires an argument, which can be value, child_added, child_changed, child_removed, or child_moved. The value argument is for getting data from a location without changing the child nodes.
We can then set up our then promise fulfillment. You can call the returned data anything. Here it's called snapshot.
Lastly snapshot.val() provides the data from the database, looking just like it does in the Firebase Console.

Angularfire .5 can't bind to data after login authentication

So here is the next step ( which has failed ) in trying to use v0.5. Once you have logged in succesfully with a JWT, any attempt to bind will result in an undefined value. Binding to a public database works in my experiments but not with a private database where the user has logged in AND has the proper permissions to write to that datastore ( Keep in mind, this is a port of a working app from .4 ). Here is the code that will not bind ( $rootScope.profile == undefined ):
myApp.app.run(['$rootScope','$firebase','$firebaseAuth',
function($rootScope, $firebase,$firebaseAuth ) {
var ref = new Firebase("mydatabase.firebaseio.com");
$rootScope.fbAuth = $firebaseAuth(ref);
console.log($rootScope.fbAuth.user);
var jwt="eyJhbGciOiAiSFMyN.... // A VALID JAVA WEB TOKEN...trust me.
$rootScope.fbAuth.$login(jwt);
$rootScope.$on("$firebaseAuth:login", function(e, user) {
$rootScope.user = user;
var refUsers = new Firebase('mydatabase.firebaseio.com'+'/users/'+user.id);
$rootScope.fbUser = $firebase(refUsers);
$rootScope.fbUser.$on("loaded", function() {
$rootScope.fbUser.$bind($rootScope, "profile").then (function(unbind){
alert(unbind);
console.log($rootScope.profile);
});
});
});
}]);
Clarification:
The documentation shows the example:
$scope.items.$bind($scope, "remoteItems");
$scope.remoteItems.bar = "foo"; // new Firebase(URL + "/bar") is now "foo".
alert($scope.remoteItems);
This will show undefined
if you have intiated $scope.remoteItems={};
then this will update firebase and erase the data bound to $scope.items ( undesirable IMHO)
// If you were to try:
$scope.items.$bind($scope,'remoteItems') // Fails with undefined and no binding.
// If you were to try:
$scope.remoteItems = {}; // or similar init
$scope.items.$bind($scope,'remoteItems') // wipes out data stored at refItems on firebase.
I have not found a way to get the bind to work on my database and it is definitely not a security issue.

Resources