Let say I have these 2 fields in my firebase database.
user: {
postCount: 2
posts: [
{title: hello, content: world},
{title: hello again, content: world}
]
}
I want the user to have permission to update his posts. but I don't want him to be able to update his post count. I want the post counts to always represent the number of posts and prevent the user from cheating it.
How can I do this in firebase? Is it possible with front end javascript only? If not what would be the option that requires the least server side code possible?
This is the code I'm using but it doesn't prevent users from cheating and just calling the increment function by themselves infinite times.
const push = (objectToInsert, firebasePath) => {
const key = firebase.database().ref().child(firebasePath).push().key
let updates = {}
updates[firebasePath + key] = objectToInsert
firebase.database().ref().update(updates)
}
const increment = (firebasePath) => {
const ref = firebase.database().ref(firebasePath)
ref.transaction( (value) => {
value++
return value
})
}
push(post, `/${user}/${posts}/`)
increment(`/${user}/${postCount}`)
Referring you to firebase rules:
https://firebase.google.com/docs/database/security/#section-authorization
Either you set the rules on each of the user properties based on your security setup and keep the structure as you mentioned, Or move the counts to another node and set the rules (ex. user_post_counts).
Related
I am not very efficient with my code which may be the reasons why this keeps failing. I am trying to remove and assign roles to "verified" users. The basic gist of the code is to loop through all "verified" users and assign them appropriate roles according to the data received from the API.
const fetch = require("node-fetch");
var i = 0;
function mainLoop(
guild,
redisClient,
users,
main_list,
Pilot,
Astronaut,
Cadet,
main_guild,
cadet_guild,
guest
) {
setTimeout(function () {
redisClient.GET(users[i], async function (err, reply) {
if (reply != null) {
var json = await JSON.parse(reply);
var uuid = Object.keys(json).shift();
if (Object.keys(main_list).includes(uuid)) {
var tag = users.shift();
var rank = main_list[uuid];
console.log(`${tag}: ${rank}`);
var role = guild.roles.cache.find(
(role) => role.name === `| ✧ | ${rank} | ✧ |`
);
await guild.members.cache.get(tag).roles.remove(guest);
await guild.members.cache.get(tag).roles.remove(Astronaut);
await guild.members.cache.get(tag).roles.remove(Cadet);
await guild.members.cache.get(tag).roles.remove(Pilot);
await guild.members.cache.get(tag).roles.remove(cadet_guild);
await guild.members.cache.get(tag).roles.add(main_guild);
await guild.members.cache.get(tag).roles.add(role);
} else {
var tag = users.shift();
console.log(`${tag}: Guest`);
await guild.members.cache.get(tag).roles.remove(Astronaut);
await guild.members.cache.get(tag).roles.remove(Cadet);
await guild.members.cache.get(tag).roles.remove(Pilot);
await guild.members.cache.get(tag).roles.remove(main_guild);
await guild.members.cache.get(tag).roles.remove(cadet_guild);
await guild.members.cache.get(tag).roles.add(guest);
}
}
i++;
if (i < users.length) {
mainLoop(
guild,
redisClient,
users,
main_list,
Pilot,
Astronaut,
Cadet,
main_guild,
cadet_guild,
guest
);
}
});
}, 5000);
}
The code will fetch api data, map the "verified" users and api data into an array. Then, when it starts looping through the users array, it will only log 3 times and not assign any roles. Any help would be appreciated.
I can provide extra explanation/code if needed.
One possible issue I see here is that you are both incrementing the index i and calling .shift() on the users array. This may be the cause of the problem you are experiencing, as this will entirely skip some of the users in the array. Array.shift() doesn't just return the first element of the array; it removes it from the array.
Consider, for example, that your users array looks like this:
var users = ["Ted", "Chris", "Ava", "Madison", "Jay"];
And your index starts at 0 like so:
var i = 0;
This is what is happening in your code:
Assign roles for users[i]; the index is currently 0, so get users[0] (Ted).
Get Ted's tag via users.shift(). users is now: ["Chris", "Ava", "Madison", "Jay"]
Increment the index with i++. i is now: 1.
Assign roles for users[i]; the index is currently 1, so get users[1] (now Ava, skips Chris entirely).
Get Ava's tag via users.shift() (actually gets Chris' tag). users is now: ["Ava", "Madison", "Jay"]
Increment the index with i++. i is now: 2.
Assign roles for users[i]; the index is currently 2, so get users[2] (now Jay, skips Madison entirely).
And so on, for the rest of the array; about half of the users in the users array will be skipped.
I don't know how many users are supposed to be in your users array, but this could be the reason why so few logs are occurring. Note, however, that this is just one cause of the problem you are experiencing; it is possible that there are more reasons why you are having that issue, such as rate limits.
My recommendation on how to fix this is to not use users.shift() to get the user's tag. Simply use users[i], which will return the proper tag value without messing with the length of the array. Another way to fix this would be to remove the index incrementation, and always use 0 as your index. Use one or the other, but not both.
I am pulling from a SQL table device, and displaying its content to a table by mapping from device. I am trying to add a column that pulls information from another SQL table, group, but I haven't figured out how to adjust the mapping in order to pull from both device and group. I am sure the issue is caused since group isn't declared in this scope but I cannot solve how it should be declared in this portion of the script.
Both tables shared a common column, group_id, and I have added useSelector for both:
const device = useSelector((state) => state.device);
const group = useSelector((state) => state.group);
<Table
tableHeaderColor="warning"
tableHead={['Device Name', 'Location', 'Group', 'Release Version']}
tableData={device.deviceData.map((device) => {
return [
device['device_name'],
device['location_name'],
group['group_name'],
device['release'],
];
})}
/>
An alternative fix I have tried is finding the group_name since both tables device and group share the group_id column, but it causes a group.find is not a function error. I am unsure if my syntax is incorrect, as I'm working from this site as a resource.
tableData={device.deviceData.map((device) => {
return [
device['device_name'],
device['location_name'],
group.find(group => group.group_id === device.group_id).group_name,
device['release'],
];
})}
Many thanks for any advice
UPDATE:
Thank you for the answers and comments so far. Here is some additional information:
The reducer does contain the initial state empty array for group (groupdata)
const initialState = {
groupData: [],
result: '',
};
Here are the screenshots of the SQL tables device and group. They do not have the same number of entries, as group lists the groups that a number of devices can be assigned to. Hence there are many more entries under device than group.
device SQL table
group SQL table
If you using group.find you need to make sure the reducer contains the initial state empty array for group
Doing this in a map in the actual JSX might get a bit confusing since you're pulling from two different datasources. Also, assuming that the array in
device.deviceData and the group array share the same number of entries and correlate to each other, searching in group on each loop through device.deviceData seems like an unnecessary performance hit. My preference might be to create my source data in a plain for loop outside of the return JSX, and then just plug it in directly:
let tableData = []
for (let i = 0; i < device.deviceData.length; i += 1) {
const currentDevice = device.deviceData[i];
const currentGroup = group[i];
const entry = [
currentDevice['device_name'],
currentDevice['location_name'],
currentGroup['group_name'],
currentDevice['release'],
];
tableData.push(entry);
}
Then I would simply pass tableData to the <Table /> tableData prop.
I think group is an object and has a structure similar to device.deviceData. By that assumption, group.groupData should be the array which will be useful to us.
What we can do is build a Map of group_id -> group_name which we use later for getting relevant device group_name in Table component.
Here is a code snippet which should give you an idea (this is a JS based answer. I think your use-case can make use of joins at sql level for less work here) :-
const device = {
deviceData: [{
group_id: 1
},
{
group_id: 2
}
]
}
const group = {
groupData: [{
group_id: 1,
group_name: 'Group-1'
},
{
group_id: 2,
group_name: 'Group-2'
}
]
}
const buildMap = () => {
const gMap = {};
for (const {
group_id,
group_name
} of group.groupData) {
gMap[group_id] = group_name;
}
return gMap;
}
const groupMap = buildMap();
device.deviceData.forEach(d => console.log(groupMap[d.group_id]));
I want to retrieve a list of products in relation to the user's position, for this I use Geofirestore and update my Flatlist
When I have my first 10 closest collections, I loop to have each of the sub-collections.
I manage to update my state well, but every time my collection is modified somewhere else, instead of updating my list, it duplicates me the object that has been modified and adds it (updated) at the end of my list and keep the old object in that list too.
For example:
const listListeningEvents = {
A: {Albert, Ducon}
B: {Mickael}
}
Another user modified 'A' and delete 'Ducon', I will get:
const listListeningEvents = {
A: {Albert, Ducon},
B: {Mickael},
A: {Albert}
}
And not:
const listListeningEvents = {
A: {Albert},
B: {Mickael},
}
That's my useEffect:
useEffect(() => {
let geoSubscriber;
let productsSubscriber;
// 1. getting user's location
getUserLocation()
// 2. then calling geoSubscriber to get the 10 nearest collections
.then((location) => geoSubscriber(location.coords))
.catch((e) => {
throw new Error(e.message);
});
//Here
geoSubscriber = async (coords) => {
let nearbyGeocollections = await geocollection
.limit(10)
.near({
center: new firestore.GeoPoint(coords.latitude, coords.longitude),
radius: 50,
})
.get();
// Empty array for loop
let nearbyUsers = [];
// 3. Getting Subcollections by looping onto the 10 collections queried by Geofirestore
productsSubscriber = await nearbyGeocollections.forEach((geo) => {
if (geo.id !== user.uid) {
firestore()
.collection("PRODUCTS")
.doc(geo.id)
.collection("USER_PRODUCTS")
.orderBy("createdDate", "desc")
.onSnapshot((product) => {
// 4. Pushing each result (and I guess the issue is here!)
nearbyUsers.push({
id: product.docs[0].id.toString(),
products: product.docs,
});
});
}
});
setLoading(false);
// 4. Setting my state which will be used within my Flatlist
setListOfProducts(nearbyUsers);
};
return () => {
if (geoSubscriber && productsSubscriber) {
geoSubscriber.remove();
productsSubscriber.remove();
}
};
}, []);
I've been struggling since ages to make this works properly and I'm going crazy.
So I'm dreaming about 2 things :
Be able to update my state without duplicating modified objects.
(Bonus) Find a way to get the 10 next nearest points when I scroll down onto my Flatlist.
In my opinion the problem is with type of nearbyUsers. It is initialized as Array =[] and when you push other object to it just add new item to at the end (array reference).
In this situation Array is not very convenient as to achieve the goal there is a need to check every existing item in the Array and find if you find one with proper id update it.
I think in this situation most convenient will be Map (Map reference). The Map indexes by the key so it is possible to just get particular value without searching it.
I will try to adjust it to presented code (not all lines, just changes):
Change type of object used to map where key is id and value is products:
let nearbyUsersMap = new Map();
Use set method instead of push to update products with particular key:
nearbyUsersMap.set(product.docs[0].id.toString(), product.docs);
Finally covert Map to Array to achieve the same object to use in further code (taken from here):
let nearbyUsers = Array.from(nearbyUsersMap, ([id, products]) => ({ id, products }));
setListOfProducts(nearbyUsers);
This should work, but I do not have any playground to test it. If you get any errors just try to resolve them. I am not very familiar with the geofirestore so I cannot help you more. For sure there are tones of other ways to achieve the goal, however this should work in the presented code and there are just few changes.
So I am trying to delete the field Notes[1], selectedNote has a value of the selected array I need to delete.
window.addEventListener("load", function load(event) {
document.getElementById("deleteNotes").onclick = function() {
console.log("you did click atleast");
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
let user = firebase.auth().currentUser;
let userInfo = db.collection("Users").doc(user.uid);
userInfo.get().then(function(doc) {
if (doc.exists) {
let selectedNote = document.getElementById("noteSelect")
.selectedIndex;
console.log(selectedNote);
var cityRef = db.collection("Users").doc(user.uid);
cityRef.update({
Notes: FieldValue.delete().arrayRemove(selectedNote)
});
}
});
}
});
};
});
So I am trying to use this
cityRef.update({
Notes: FieldValue.delete().arrayRemove(selectedNote)
});
to delete the selectedNote which is the array 1 for example inside of Notes. I don't want the entire Notes field deleted but just the selected array inside the Notes field. For some reason I am struggling to get it working. Any help would be appreciated <3
Firebase arrays doesn't use a indexes for their arrays, instead they require the value of the array entry. this does mean you can't get away with duplicated data.
you will have to fetch the array "Notes" first and return its value from its index
Although, I have heard that the arrays can come back out of order because they don't rely on an index.
change this line;
Notes: FieldValue.delete().arrayRemove(selectedNote)
to this;
Notes: FieldValue.delete().arrayRemove(doc.data().Notes[selectedNote])
I would recommend you pull the value of the array from a stored variable locally to ensure the values match pre and post edit.
I have a list of users, which contains a node called scannedCards, which contains an array of cardIds. Below is a screenshot of one user.
There are multiple users like above, I want to delete all the card Ids if they are equalTo(Some Card Id), I want to this with all the users at once.
Below is the query I a using:
const ref = database.ref('users');
const query = ref.orderByChild('cardId').equalTo(cardId);
But the result is always empty, Can someone please tell how I can achieve this?
The query you listed above doesn't seem to be returning anything because, based on the screenshot provided, cardId isn't a direct child property of users, but rather a property of each object under the scannedCards list that users may have.
If you only need to do this as part of a one-time operation, you may run something like this:
const ref = database.ref('users');
ref.once('value').then(snapshot => {
const users = snapshot.val();
for (var user_id in users) {
const scannedCards = users[user_id].scannedCards;
for (var card in scannedCards) {
if (scannedCards[card].card_id == "<some card id>") {
firebase.database().ref(`users/${user_id}/scannedCards/${card_id}`)
.set(null);
}
}
}
});
If you plan on letting logged-in users delete their scanned cards, it would be a little bit less complicated:
// user_id is for whoever is logged in
const ref = firebase.database().ref(`users/${user_id}/scannedCards`);
ref.once('value').then(snapshot => {
const scannedCards = snapshot.val();
for (var card in scannedCards) {
if (scannedCards[card].card_id == "<some card id>") {
firebase.database().ref(`users/${user_id}/scannedCards/${card_id}`)
.set(null)
}
}
});
Also worth pointing out, scannedCards is not an array of card ids, because the keys are object ids and the values are objects (which contain cardId and userId properties).