I'm learning firestore recently.
I want to implement below data structure.
- user (document)
- schedules(field, array)
- 0(map)
- startTime(tiemstamp)
- endTime(timestamp)
- 1(map)
- startTime(tiemstamp)
- endTime(timestamp)
I wanted to operate adding a testdata in firestore's GUI. so I created a schedules(array) and 0(map)'s data(include startTime and endTime). after that I added 1(map)'s data(include startTime and endTime). At that moment, 0(map)'sdata was automatically changed to below data structure.
- 0(map)
- startTime(object)
- nanoseconds(number) (changed to 0)
- seconds(number) (changed to 1550502000)
- endTime(object)
- nanoseconds(number) (changed to 0)
- seconds(number) (changed to 1550504000)
- 1(map)
- startTime(tiemstamp)
- endTime(timestamp)
why timestamp was automatically changed to object?
Please teach me.
I got to reproduce the problem, it seems to be a bug with the Firebase console, not with Firestore itself, because it only happens on the console.
You should report it to Google: https://firebase.google.com/support/contact/
In the meantime, though, what you can do is add the test data through code, using any language that supports the Admin SDK.
Example in Node:
import admin from 'firebase-admin';
admin.initializeApp();
// ...
await admin.firestore().collection('test-date').add({
schedule: [
{
startDate: Timestamp.fromDate(new Date()),
endDate: Timestamp.fromDate(new Date()),
},
{
startDate: Timestamp.fromDate(new Date()),
endDate: Timestamp.fromDate(new Date()),
},
]
});
Related
My question is, how can I delete a users analytics data from firebase using "Google User Deletion API" and its method: userdeletionRequests:upsert? This is important for me to fully fulfill GDPR.
I tried searching for this, but didn't a solution for using it in combination with "NodeJS" and "firebase-cloud-functions".
My biggest problem is, how I get the access, token, this is what I have for now:
const accessToken = (await admin.credential.applicationDefault().getAccessToken()).access_token;
return ky.post(constants.googleUserDeletionURL, {
body: JSON.stringify({
kind: "analytics#userDeletionRequest",
id: {
type: "USER_ID",
userId: uid,
},
propertyId: constants.googleUserDeletionPropertyId,
}),
headers: {
"Authorization": `Bearer ${accessToken}`,
},
}).catch((err) => {
functions.logger.log(`An Error happened trying to delete user-anayltics ${(err as Error).message}`);
});
But I always get An Error happened trying to delete user-anayltics Request failed with status code 403 Forbidden
Okay, after some painful and long days (literally took me like >20 hours), I've figured out how to achieve this request. Here is a step for step guide:
Step 1 - Needed Dependencies
To send a post-request to google, we need a http-client-library. I've choosen "ky", so we need to install it first with:
npm install ky
Furthermore, we need to create or OWN oAuth2 token, otherwise the post request will be denied with "error 403". To create our own oAuth token, we need another dependency:
npm install #googleapis/docs
Step 2 - Needed Google Property ID
With your request, Google needs to know which property-id / project you are targeting (where it should later delete the user-analytics-data). To get this property-id, we need to log in into GCP via Firebase (not the "real" GCP, the one via Firebase).
For this, go into your "console.firebase.google.com" → Select your project → Analytics Dashboard → "View more in Google Analytics (button at the right corner)"
Write "property-id" into the search field and then save it somewhere (we need it later)
Step 3 - Creating Client-Secret
The third step is to create a service-account, which we will later add into our functions-folder, in order to create our oAuthClient (don't worry, you will see what I mean to a later point)
To create your new service.json, log in to google cloud platform via "https://cloud.google.com/" and then follow the pictures:
FIRST:
SECOND:
THIRD:
FOURTH:
FIFTH
Step 4 - Download JSON
After we created our "oAuth-Deleter service-account", we need to manually download the needed JSON, so we can paste it into our functions-folder.
For this, select "oauth-deleter#your-domain.iam.gserviceaccount.com" under "Service Account"
Then click on "Keys" and "Add key", which will automagically download you a service-json (SELECT Key type → JSON → Create).
Step 5 - Paste JSON file into your functions-folder
To loosen up the mood a bit, here is an easy step. Paste the downloaded JSON-File into your functions-folder.
Step 6 - Grant Access to our new created oAuth-Delelter-Account
Creating the service-account and giving it access in the normal GCP is not enough for Google, so we also have to give it access in our Firebase project. For this, go back into your "GCP via Firebase (see Step 2)" → Click on Setting → "User Access for Account" → Click on the "plus"
Then click on "Add user" and write the email we copied before into the email field (the email from Step 3, Picture FOURTH "Service-Account ID). In our case, it is "oAuth-Deleter#your-domain.iam.gserviceaccount.com". Furthermore, it needs admin-access:
Step 6 - The code
Now, after these million unnecessary but necessary steps, we get to the final part. THE DAMN CODE. I've written this in typescript with "compilerOptions" → "module": "esnext", "target": "esnext". But I am sure that you are smart enough to change the code after completing this many steps :)
import admin from "firebase-admin";
import functions from "firebase-functions";
import ky from "ky";
import docs from "#googleapis/docs";
import { UserRecord } from "firebase-functions/v1/auth";
export const dbUserOnDeleted = functions.
.auth
.user()
.onDelete((user) => doOnDeletedUser(user))
----------------------------
export asnc function doOnDeletedUser/user: UserRecord) {
try {
const googleDeletionURL = "https://www.googleapis.com/analytics/v3/userDeletion/userDeletionRequests:upsert"
// Step 1: Paste propertyID: (See Step 2)
const copiedPropertyID = "12345678"
// Step 2: Create oAuthClient
const oAuthClient = new docs.auth.GoogleAuth({
keyFile: "NAME-OF-THE-FILE-YOU-COPIED-INTO-YOUR-FUNCTIONS-FOLDER",
scopes: ["https://www.googleapis.com/auth/analytics.user.deletion"]
});
// Step 3: Get user uid you want to delete from user-analytics
const uid = user.uid
// Step 4: Generate access token
// (this is the reason why we needed the 5 steps before this)
// yup, a single line of code
const accessToken = await oAuthClient.getAccessToken() as string;
// Step 5: Make the request to google and delete the user
return ky.post(googleDeletionURL, {
body: JSON.stringify({
kind: "analytics#userDeletionRequest",
id: {
type: "USER_ID",
userid: uid
},
propertyId: copiedPropertyID
}),
headers: {
"Authorization": "Bearer " + accessToken,
}
});
} catch (err) {
functions.logger.error(`Something bad happened, ${(err as Error).message)`
}
}
Afterthoughts
This was and probably will be my longest post at stack overflow forever. I have to say that it was a pain in the a** to get this thing to working. The amount of work and setup that is needed to simply delete a data from one endpoint is just ridiculous. Google, please fix.
Trying to find out the new way to show a timestamp in collections, usually it just used to be
timestamp: firebase.firestore.FieldValue.serverTimestamp()
im using the timestamp key aswell if that changes anything, I cannot find it anywhere, apparently they updated it as there was an issue?
db.collection('posts').add({
message: input,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
profilePic: user.photoURL,
username: user.displayName,
image: image,
})
Thanks
I'm currently using Jovo for cross platform developing Alexa and Google Assistant's skills/actions.
I currently hit a roadblock in which I'm trying to get the previous intent by doing either:
this.user().context.prev[0].request.intent or
this.user().getPrevIntent(0).
But it hasn't worked. I get context is undefined and getPrevIntent doesn't exist. According to the Docs, I need to set up a table with DynamoDB (I did, and verified that it's working since Jovo is able to store the user object), and passed in the default configuration to App. But still can't seem to get it work. Any ideas?
const config = {
logging: false,
// Log incoming JSON requests.
// requestLogging: true,
/**
* You don't want AMAZON.YesIntent on Dialogflow, right?
* This will map it for you!
*/
intentMap: {
'AMAZON.YesIntent': 'YesIntent',
'AMAZON.NoIntent': 'NoIntent',
'AMAZON.HelpIntent': 'HelpIntent',
'AMAZON.RepeatIntent': 'RepeatIntent',
'AMAZON.NextIntent': 'NextIntent',
'AMAZON.StartOverIntent': 'StartOverIntent',
'AMAZON.ResumeIntent': 'ContinueIntent',
'AMAZON.CancelIntent': 'CancelIntent',
},
// Configures DynamoDB to persist data
db: {
awsConfig,
type: 'dynamodb',
tableName: 'user-data',
},
userContext: {
prev: {
size: 1,
request: {
intent: true,
state: true,
inputs: true,
timestamp: true,
},
response: {
speech: true,
reprompt: true,
state: true,
},
},
},
};
const app = new App(config);
Thanks 😊
To make use of the User Context Object of the Jovo Framework, you need to have at least v1.2.0 of the jovo-framework.
You can update the package to the latest version like this: npm install jovo-framework --save
(This used to be a comment. Just adding this as an answer so other people see it as well)
Edit: See solution at the end
My guess was to put the model (in my case 'user') inside => type, but then it'll say "Assertion failed, you need to pass a model ..."
I do have a user.js in app/models
here's an excerpt from the router (after login function)
self.store.push({
data: {
id: data.currentUser.uid,
type: 'user',
attributes: {
displayName: data.currentUser.displayName,
email: data.currentUser.email,
photoURL: data.currentUser.photoURL,
firebaseUID: data.currentUser.uid,
rank: "scorer",
status: "active",
loginCount: 0,
provider: provider,
timestamp: new Date().getTime()
}
}
});
and here's my model (user.js in app/models)
import DS from 'ember-data';
export default DS.Model.extend({
displayName: DS.attr('string'),
email: DS.attr('string'),
photoURL: DS.attr('string'),
firebaseUID: DS.attr('string'),
rank: DS.attr('string'),
status: DS.attr('string'),
loginCount: DS.attr('string'),
provider: DS.attr('string'),
timestamp: DS.attr('number')
});
Please help :( thanks everyone in advance!
Edit => Solution that worked: If you do a createRecord and match the "id:" attribute, it will update the record with the same id (will work if you specified your own id). However, I'm not sure yet how to update a record if you let the system generate an ID for you. I assume that you would have to extract the ID first. But I haven't tested that idea yet. If someone would be so kind to test it, that'll be awesome.
Instead of pushing the raw data, create a model and run its save method.
var user = this.store.createRecord('user', {
displayName: data.currentUser.displayName,
// set more properties here
})
user.save()
This way, Emberfire and Ember Data can do their thing and ensure the data is formatted correctly. Also see
https://github.com/firebase/emberfire/blob/master/docs/quickstart.md#5-save-data
https://guides.emberjs.com/v3.0.0/models/creating-updating-and-deleting-records/
https://guides.emberjs.com/v3.0.0/models/pushing-records-into-the-store/
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
export default functions.auth.user().onCreate(saveUserToDatabase);
async function saveUserToDatabase(event) {
const Users = admin.database().ref('Users');
const authUser = event.data;
let user = await Users.child(authUser.uid).set({
createdAt: authUser.metadata.createdAt.toString(),
email: authUser.email,
facebookId: authUser.providerData[0].uid.replace('http://facebook.com/', ''),
lastSignedInAt: authUser.metadata.lastSignedInAt.toString(),
name: authUser.displayName,
photoUrl: authUser.photoURL,
});
console.log('saveUserToDatabase()');
console.log(authUser);
}
In the above code I save a user to the firebase database when they register. Now unless I use the toString method on the dates they do not save. I looked into this a little further and found that the dates don't come in as strings and that is causing some issues. (I did this with console.log and checking the firebase dashboard)
{ displayName: 'Test User',
email: 'test#example.com',
metadata:
{ createdAt: 2017-05-02T05:18:45.000Z,
lastSignedInAt: 2017-05-02T05:18:45.000Z }
}
When I use the toString method though it converts the output to:
"Tue May 02 2017 05:24:31 GMT+0000 (UTC)"
Dates are also saved in some instances in this format:
"1492213242000"
Why is there so many timestamp formats?
What is the preferred timestamp format for firebase I should be using?
How do I convert the dates into the preferred timestamp using cloud functions?
The object you're receiving in event.data is an admin.auth.UserRecord. Its interface specifies that the createdAt and lastSignedInAt timestamps should be JavaScript Date objects.
The Date type has enough issues that we try to avoid it in newer interfaces. I suspect you may be hitting some of those pain points in the inconsistent toString() serialization.
I agree with Doug that for storage, milliseconds since the epoch should be the preferred format. You should be able to get that from createdAt.value, for example.