Firebase NoSQL Data modeling - Like counter - firebase

I'm currently learning Firebase/NoSQL database modeling. I just ended watching the Firebase for SQL developers, but I still have few doubts.
Let's say I'm creating Instagram-styled app where users could share their photos and each user could like each photo.
So I would like to achieve two things:
1. Know which user has liked which photo. (So only one like per user for photo)
2. How many likes each photo has.
My current database looks like this:
{
"images": {
"100": {
"imageUrl": "../../image.png",
},
"101": {
"imageUrl": "../../image.png",
}
},
"users": {
"200": {
"name": "user1"
},
"201": {
"name": "user2"
}
},
"likes": {
"100": 1,
"101": 2
},
"likesPerUser": {
"200": {
"100": "true"
},
"201": {
"100": "true",
"101": "true"
}
}
"imagesPerUser": {
"200": {
"101": "true"
},
"201": {
"100": "true"
}
}
My question is related to the counter, that counts how many likes each photo has. Would the best practice be that I have them as their own "root"-object (current model) OR to create key-value pair for "likes" under the photo (and maybe do the same for authorID)?

This Firebase Sample recommends having the counter(likes_count) under each post. And also having a node(likes) with a list/lookup . Like this:
"images": {
"100": {
"imageUrl": "../../image.png",
"likes_count":2,
"likes":{
"200":true,
"201":true
}
},
"101": {
"imageUrl": "../../image.png",
"likes_count":1,
"likes":{
"201":true
}
}
}
This way you'll ensure only one like per user, because keys must be unique under a Firebase Realtime Database node, and the user ids are used as keys under the likes_count node. You can also know which users liked the photo because their uids are there. And obviously, you can see how many likes a photo has by accessing the counter.

Related

Get list of related values Angularfire 4 Ionic 3

I am working to calculate the difference between two times in ionic.
I am using AngularFire and my tree looks like this:
{
"users": {
"name": {
"17": {
"10": {
"2017": {
"-Kwfm1k9_A74PzlmijUJ": {
"date": "17/10/2017",
"hora": "17:20:58",
"status": "In"
},
"-Kwfm8wEJ8Oob4YFvNNu": {
"date": "17/10/2017",
"hora": "17:21:27",
"status": "Out"
},
"-KwfoKkPJMt2g8AQNmxq": {
"date": "17/10/2017",
"hora": "17:31:00",
"status": "In"
},
"-Kwfp0BOAGnM-2_MfziP": {
"date": "17/10/2017",
"hora": "17:33:58",
"status": "Out"
},
"-KwfqW5XKpUNedda4rZz": {
"date": "17/10/2017",
"hora": "17:40:31",
"status": "In"
},
"-Kwg0pQDlI3FMV3BPNaa": {
"date": "17/10/2017",
"hora": "18:29:58",
"status": "Out"
}
}
}
}
}
}
}
I would like to get a difference between the first and second, third and fourth, fifth and sixth, remembering that they are related by the tag "Status".
First In - Out = difference
Second In - Out = difference
Third In - Out = difference
Always doing the difference between the In's and Out's.
In's are for when a person come in the room. Out's are for when a person come out of the room. I want to record the time a person spend inside of a room.
So the results would be:
"17:20:58" - "17:21:27" = 00:01:29
"17:31:00" - "17:33:58" = 00:02:58
"17:40:31" - "18:29:58" = 00:49:27
Do you have any tip on refactoring this code so it works fine?
Remembering I'm using Ionic 3 with AngularFire4
Appreciate the help.
To achieve your goal I recommend you to restructure your data. For now, there is no real relation between the IN's and OUT's.
A possible approach would be to create some node which wraps the checkIn and the checkOut. Let's call it session. Each time a user checks in a new session get's created and each time a user checks out a session get's closed.
Your structure could look similar to this:
"sessions": {
"uid": { // or name or whatever
"17-10-2017": { // not sure if you need the date in the structure, but if you need it make a single node like this
"-Kwfm1k9_A74PzlmijUJ": { // this is a session create it on each check in
"checkin": 1508354574, // timestamp
"checkout": 1508354584
},
}
}
}
Here's a code example (just to give you an idea how it could look like):
var ref = firebase.database().ref("sessions/uid"); // uid must be dynamic
// checkin
ref.push({
checkin: firebase.database.ServerValue.TIMESTAMP // make sure to use the servers timestamp
}); // creates a new session with a pushkey as id
// checkout
ref.child('sessionId').update({
checkout: firebase.database.ServerValue.TIMESTAMP
});
// stats
ref.child('sessionId').once('value', function(snap) {
var session = snap.val();
var difference = session.checkout - session.checkin;
// now you have the difference in milliseconds or seconds depending on your timestamp format. this can be formatted whatever unit you want e.g. minutes, hours...
});
Hope this gives you an idea, how it could be done.

Firebase Database Design

I have a database containing a structure similar to the one shown below. Since I am not allowed to make it public I am using the following keys:
i = {0,1,2,3....}
All a(i) represent single key/value pairings e.g. userName: "awesome"
With the below structure, every time a User wants to create new Stuff these are the steps I currently take
Store images to FIRStorage and retrieve their respective downloadURL
Then I add the Stuff to FIRDatabase. At this point I add all of the info related to a(i) because they're all under a single child; Stuff.UUID, hence I send one huge dictionary consisting of the data.
The issue arises in adding data to the mini-dictionaries. Because they are in different paths, I have to individually make requests to all of them as shown below
I then add location info
Followed by the respective child of timeStamp
The images information is next to be updated
Subscribe the User to respective Stuff.UUID
Lastly add the User.UUID to the members portion of Stuff
Is it possible to reduce steps 3-7?
As a follow up, is it possible to add values into different paths with one call?
PS: Link to what the code of the above demo might look like. Due to confidentiality stuff I am not allowed to post the actual code.
{
"Users":
{
"JglJnGDXcqLq6m844pZ":
{
a(0),
a(1),
a(2),
a(3),
a(4),
a(5),
"Stuff":
{
"fcWpzduhpPn8XR6Zqca": true,
"gfntTr6TkDwZ439jkW8": true
}
}
},
"Stuff":
{
"fcWpzduhpPn8XR6Zqca":
{
a(0),
a(1),
a(2),
a(3),
a(4),
a(5),
a(6),
a(7),
"location":
{
"latitude":"-17.41439",
"longitude":"-5.85170"
},
"timestamp":
{
"created":
{
a(0),
a(1),
a(2),
a(3),
a(4),
a(5),
},
"lastModified":
{
a(0),
a(1),
a(2),
a(3),
a(4),
a(5),
}
},
"images":
{
"B4FaR6wfJAeXqJ29T33":
{
"imageURL": "https://google.com"
}
},
"members":
{
"JglJnGDXcqLq6m844pZ": true,
"DpHAfrqL4eqbR8QNgHg": true
}
}
}
}

Firebase database modeling

I'm starting out using firebase and even though I've spent 2 days on firebase db and nosql I'm still not sure if I'm on the right track.
I'm working on a fairly simple usecase/dataset:
I have content-items (title, coordinates, ...) which can be attributed to subcategories. Subcategories are attributed to categories.
Sound simple? Ok, I created the following:
{
"contentitems": {
"item1": {
"title": "i am a content item",
"coordinates": ""
},
"item2": { ... },
"item3": { ... }
},
"subcategories": {
"sc1": {
"title": "i am a subcategory",
"contentitems": {
"item1" : true,
"item2" : true
}
},
"sc2": {
"title": "i am another subcategory",
"contentitems": {
"item1" : true,
"item134" : true
}
},
"sc3": { ... }
},
"categories": {
"c1": {
"title" : "i am a cateogry,
"subcategories": {
"sc1" : true,
"sc2" : true
},
},
"c2": { ... },
"c3": { ... }
}
}
As far as I can tell this should be fine for the following usecase (in an app):
I click on a category from the categories list and open a list with all related subcategories. When I click on a subcategory I open a list with all related content-items.
Is this correct? Are there any pitfalls I can't see at the moment? Am I completly off?
2n part of my question:
How can I achieve a geolocation search with a bounding box? Do I have to create another 'table' called geocoordinates like this:
"geocoordinates": {
"coords" : {
lat: "45",
lon: "44"
}
}
How can I get all data within the bounding box?

How can I store a user's words using Amazon Alexa?

I'm writing Alexa skills and want to write a skill to store the speaker's words.
For example, if I say, 'Alexa, save {whatever I say}', it should save the words in some string.
Now from what I understand, the intent schema something should be like
{
intents:[
"intent" : "SaveIntent"
]
}
and utterances like
SaveIntent save
SaveIntent store
In this case, how do I store '{whatever I say}'?
To capture free-form speech input (rather than a defined list of possible values), you'll need to use the AMAZON.LITERAL slot type. The Amazon documentation for the Literal slot type describes a use case similar to yours, where a skill is created to take any phrase and post it to a Social Media site. This is done by creating a StatusUpdate intent:
{
"intents": [
{
"intent": "StatusUpdate",
"slots": [
{
"name": "UpdateText",
"type": "AMAZON.LITERAL"
}
]
}
]
}
Since it uses the AMAZON.LITERAL slot type, this intent will be able to capture any arbitrary phrase. However, to ensure that the speech engine will do a decent job of capturing real-world phrases, you need to provide a variety of example utterances that resemble the sorts of things you expect the user to say.
Given that in your described scenario, you're trying to capture very dynamic phrases, there's a couple things in the documentation you'll want to give extra consideration to:
If you are using the AMAZON.LITERAL type to collect free-form text
with wide variations in the number of words that might be in the slot,
note the following:
Covering this full range (minimum, maximum, and all in between) will
require a very large set of samples. Try to provide several hundred
samples or more to address all the variations in slot value words as
noted above.
Keep the phrases within slots short enough that users can
say the entire phrase without needing to pause.
Lengthy spoken input can lead to lower accuracy experiences, so avoid
designing a spoken language interface that requires more than a few
words for a slot value. A phrase that a user cannot speak without
pausing is too long for a slot value.
That said, here's the example Sample Utterances from the documentation, again:
StatusUpdate post the update {arrived|UpdateText}
StatusUpdate post the update {dinner time|UpdateText}
StatusUpdate post the update {out at lunch|UpdateText}
...(more samples showing phrases with 4-10 words)
StatusUpdate post the update {going to stop by the grocery store this evening|UpdateText}
If you provide enough examples of different lengths to give an accurate picture of the range of expected user utterances, then your intent will be able to accurately capture dynamic phrases in real uses cases, which you can access in the UpdateText slot. Based on this, you should be able to implement an intent specific to your needs.
Important: AMAZON.LITERAL is deprecated as of October 22, 2018. Older skills built with AMAZON.LITERAL do continue to work, but you must migrate away from AMAZON.LITERAL when you update those older skills, and for all new skills.
Instead of using AMAZON.LITERAL, you can use a custom slot to trick alexa into passing the free flow text into the backend.
You can use this configuration to do it:
{
"interactionModel": {
"languageModel": {
"invocationName": "siri",
"intents": [
{
"name": "SaveIntent",
"slots": [
{
"name": "text",
"type": "catchAll"
}
],
"samples": [
"{text}"
]
}
],
"types": [
{
"name": "catchAll",
"values": [
{
"name": {
"value": "allonymous isoelectrically salubrity apositia phantomize Sangraal externomedian phylloidal"
}
},
{
"name": {
"value": "imbreviate Bertie arithmetical undramatically braccianite eightling imagerially leadoff"
}
},
{
"name": {
"value": "mistakenness preinspire tourbillion caraguata chloremia unsupportedness squatarole licitation"
}
},
{
"name": {
"value": "Cimbric sigillarid deconsecrate acceptableness balsamine anostosis disjunctively chafflike"
}
},
{
"name": {
"value": "earsplitting mesoblastema outglow predeclare theriomorphism prereligious unarousing"
}
},
{
"name": {
"value": "ravinement pentameter proboscidate unexigent ringbone unnormal Entomophila perfectibilism"
}
},
{
"name": {
"value": "defyingly amoralist toadship psoatic boyology unpartizan merlin nonskid"
}
},
{
"name": {
"value": "broadax lifeboat progenitive betel ashkoko cleronomy unpresaging pneumonectomy"
}
},
{
"name": {
"value": "overharshness filtrability visual predonate colisepsis unoccurring turbanlike flyboy"
}
},
{
"name": {
"value": "kilp Callicarpa unforsaken undergarment maxim cosenator archmugwump fitted"
}
},
{
"name": {
"value": "ungutted pontificially Oudenodon fossiled chess Unitarian bicone justice"
}
},
{
"name": {
"value": "compartmentalize prenotice achromat suitability molt stethograph Ricciaceae ultrafidianism"
}
},
{
"name": {
"value": "slotter archae contrastimulant sopper Serranus remarry pterygial atactic"
}
},
{
"name": {
"value": "superstrata shucking Umbrian hepatophlebotomy undreaded introspect doxographer tractility"
}
},
{
"name": {
"value": "obstructionist undethroned unlockable Lincolniana haggaday vindicatively tithebook"
}
},
{
"name": {
"value": "unsole relatively Atrebates Paramecium vestryish stockfish subpreceptor"
}
},
{
"name": {
"value": "babied vagueness elabrate graphophonic kalidium oligocholia floccus strang"
}
},
{
"name": {
"value": "undersight monotriglyphic uneffete trachycarpous albeit pardonableness Wade"
}
},
{
"name": {
"value": "minacious peroratory filibeg Kabirpanthi cyphella cattalo chaffy savanilla"
}
},
{
"name": {
"value": "Polyborinae Shakerlike checkerwork pentadecylic shopgirl herbary disanagrammatize shoad"
}
}
]
}
]
}
}
}
You can try using the slot type AMAZON.SearchQuery. So you intent would be something like this
{
"intents": [
{
"intent": "SaveIntent",
"slots": [
{
"name": "UpdateText",
"type": "AMAZON.SearchQuery"
}
]
}
]
}
as of end of 2018 I am using SearchQuery to get whatever the user says.
It does work, and I have it on production systems.
But you have to ask the user something and fill the slot.
For example:
Define a slot type of SearchQuery named query (choose whatever name you want)
Add sample utterances in the slot prompts like I want to watch {query} or {query} or I want {query}
Make a question to the user for slot filling
const message = 'What movie do you want to watch?'
handlerInput
.responseBuilder
.speak(message)
.reprompt(message)
.addElicitSlotDirective('query')
.getResponse();
Updated: This answer isn't true. mentioned in the comments there is the Amazon.Literal Slot type that should allow this.
Alexa doesn't currently support access to the users raw speech input. It may be possible in the future, or you can look at some other voice to text API's such as Google's.
The only way to do this currently with Alexa would be to have a set list of words that the user could say that it would save.
To do that you can follow one of Amazon's examples of using a custom slot type. Then put all of the possible words that the user would say into that category.
(8/5/17) Unfortunately this feature was removed from Amazon with the elimination of AMAZON.LITERALS.
However, depending on how interested you are in capturing free form inputs you may be satisfied with an input MODE that captures one word, name, city, number, letter, symbol, etc. at a time and strings them together into a single variable with no message in between.
I've worked on a password input mode that can be modified to collect and concatenate user inputs. While your input would be slower, if you optimize your lambda function you may be able to achieve a fast user experience for entering a few sentences. The structure is what's important. The code could easily be adapted.
How to give input to Amazon Alexa Skills Kit (ASK) mixed string with numbers?
https://stackoverflow.com/a/45515598/8408056
Here is the better possible way to achieve what you were looking for. After trying several methods, I have got the complete words of the statement asked Alexa.
You need to make the following setup in your Alexa skill (name of intent, slot name, and slot type you can choose as per your need)
Setting up Intent
Setting up custom slot type
After setting up your Alexa skill, you can invoke your skill, keep some response for launch request and say anything you want, and you can catch the entire words or text as shown here.
"intent": {
"name": "sample",
"confirmationStatus": "NONE",
"slots": {
"sentence": {
"name": "sentence",
"value": "hello, how are you?",
"resolutions": {
"resolutionsPerAuthority": [
{
"authority": "xxxxxxx",
"status": {
"code": "xxxxxxx"
}
}
]
},
"confirmationStatus": "NONE",
"source": "USER"
}
}
}
Note*: In this method, you will need to handle utterances properly if there are more than one intent.

Difficulty setting up validation rules for Firebase datastructure

I'm working on setting up validaton rules for a Firebase data structure, created using the Bolt compiler.
I'm currently having the Bolt statement below:
path /sharedEvents/{share} is Boolean[] {
read() { isMailOfCurrentUser( share ) }
create() { isOwnerOfEvent( ...) } //NOT YET CORRECT!
delete() { isOwnerOfEvent( prior(...) } //NOT YET CORRECT!
}
With this, I'm trying to achieve that:
Only users having a mail corresponding to the key of 'share' are allowed to read the data (they use this date to retrieve the key of events shared with them.
Only the owner of an event is able to add/remove the key for his event to the list of shared events.
This second point is where I'm running into trouble -I'm not able to create the create/delete rules- since I have no idea how to reference the keys of the boolean values in the validation rule...
Example data in Firebase for the above bolt statement:
sharedEvents
ZW5kc3dhc0BldmVyeW1hMWwuYml6
-BDKBEvy-hssDhKqVF5w: true
-FDKBEvy-hsDsgsdsf5w: true
-ADBEvy-hfsdsdKqVF5w: true
aXQnc251bWJlcnNAbWExbDJ1LnVz
-KBEvy-hsDhH6OKqVF5w: true
To clarify the needs on this example:
Only user with mail 'ZW5kc3dhc0BldmVyeW1hMWwuYml6' is able to read the three nested childs.
Only the owner of event '-BDKBEvy-hssDhKqVF5w' should be able to create/delete this value. (the same for the other event key/boolean pairs).
My question: is this setup going to work (and how to setup the create/delete rules)? Or is this not going to work and should I rethink/structure the data?
Any help is appreciated!
-----------------OUTPUT JSON FILE------------------------------------------
The question above has been answered, this section is showing the resulting json
"sharedEvents": {
"$share": {
".read": "<removed for readability>",
"$event": {
".validate": "newData.isBoolean()",
".write": "<removed for readability>"
}
}
},
Thanks again for your quick support!
You'll need a nested path statement to handle the restriction on the events (the nodes under /sharedEvents/$mail/$eventid). I quickly prototyped with this JSON structure:
{
"events": {
"-ADBEvy-hfsdsdKqVF5w": {
"name": "Event 1",
"ownerMail": "aXQnc251bWJlcnNAbWExbDJ1LnVz"
},
"-BDKBEvy-hssDhKqVF5w": {
"name": "Event 2",
"ownerMail": "aXQnc251bWJlcnNAbWExbDJ1LnVz"
},
"-FDKBEvy-hsDsgsdsf5w": {
"name": "Event 3",
"ownerMail": "aXQnc251bWJlcnNAbWExbDJ1LnVz"
},
"-KBEvy-hsDhH6OKqVF5w": {
"name": "Event 3",
"ownerMail": "ZW5kc3dhc0BldmVyeW1hMWwuYml6"
}
},
"sharedEvents": {
"ZW5kc3dhc0BldmVyeW1hMWwuYml6": {
"-ADBEvy-hfsdsdKqVF5w": true,
"-BDKBEvy-hssDhKqVF5w": true,
"-FDKBEvy-hsDsgsdsf5w": true
},
"aXQnc251bWJlcnNAbWExbDJ1LnVz": {
"-KBEvy-hsDhH6OKqVF5w": true
}
},
"userMails": {
"peter": "aXQnc251bWJlcnNAbWExbDJ1LnVz",
"puf": "ZW5kc3dhc0BldmVyeW1hMWwuYml6"
}
}
And came up with these rules:
path /sharedEvents/{share} {
read() { isMailOfCurrentUser(share) }
}
path /sharedEvents/{share}/{event} is Boolean {
create() { isOwnerOfEvent(event) }
delete() { isOwnerOfEvent(prior(event)) }
}
isMailOfCurrentUser(share) { true }
getMailOfCurrentUser(uid) { root.ownerMails.uid }
getEventOwnerMail(event) { root.events.event.ownerMail }
isOwnerOfEvent(event) { getMailOfCurrentUser(auth.uid) == getEventOwnerMail(event) }
Ignoring any mistakes on my end, this should be the basics of the authorization structure you're looking for.

Resources