How do I delete data in gun DB when path has multiple objects - gun

How do I delete data when my path contains multiple objects? gun.path('saving_accounts').put(null) would delete all savings accounts.
Or, do you have a way to handle the null errors when iterating over data that has a 'deleted' object? I'm providing fully working examples to try to help in answering. Say I create gun data with this:
// localStorage.clear();
var gun = Gun();
////////////////////////////////////////////////////////////////// create record
var saving1 = gun.put({
name: "Bank of America",
accType: "Saving",
last4: "5555",
favorite: true,
status: true,
created: "some date created"
});
var saving2 = gun.put({
name: "Bank of America",
accType: "Saving",
last4: "4123",
favorite: true,
status: true,
created: "some date created"
});
var saving_accounts = gun.get('saving_accounts')
saving_accounts.set(saving1);
saving_accounts.set(saving2);
Then i can query all savings accounts with something like this:
const queryMultiple = (data_path) => {
console.log("Query for: " + data_path);
gun.get(data_path).map().val((name, ID) => {
// console.log(ID);
console.log(name.name, name.accType, ID);
});
};
queryMultiple('saving_accounts');
I tried to delete a record based on the gundb question here and wiki gun.path('object3').put(null) but I'm not sure how to change it for my application. On the savings account path, there are multiple savings accounts. So if i want to delete a specific savings account, i delete it by id but I think i'm doing it wrong. Say the id of the account i want to delete is FesxPaup8gzuNSsLFlWXMKaL:
// delete record
const deletebyID = (data_path, qID) => {
console.log("DELETE record");
gun.get(data_path).path(qID).put(null);
};
deletebyID('saving_accounts', 'FesxPaup8gzuNSsLFlWXMKaL');
But the .put(null) above will make the object FesxPaup8gzuNSsLFlWXMKaL point to null and when i list all savings accounts again with queryMultiple('saving_accounts'); I get a cannot read property name of null.
How do I delete data when my path contains multiple objects?
Side note: eventually i will nest multi transactions for a savings account under each savings account so I will have to do the same thing when deleting a account transaction that was made by mistake. Also hopefully when i delete a savings account, it automatically deletes/nulls all of that accounts transactions too but i haven't gotten past playing playing with data at this first layer.

#jtlindsey great question! You are correct though on how to delete data, even about how to delete an item inside a list/collection/table. But here is how to get the results you want:
Quick Solution:
Change your query to this:
const queryMultiple = (data_path) => {
console.log("Query for: " + data_path);
gun.get(data_path).map().val((name, ID) => {
if(!name){ return }
// console.log(ID);
console.log(name.name, name.accType, ID);
});
};
queryMultiple('saving_accounts');
And it will work. Why? Because it filters out any nulled account.
Why All the Nulls?
Deletes in GUN work like Mac OSX or Windows or Linux. The nulling tells every machine to "Put this data in the trash/recycle bin". The reason this is useful is because it lets you change your mind about deleting something, so you can recover it later if you want. (Recovering deleted content/files happens a LOT, but it something most people don't think about).
The null data also is useful for notifications! This is very applicable when you are designing frontend websites and you are rendering HTML. Let's go over a simple example:
Example
Imagine your user is checking the site on his phone, and realizes he needs to get clear up some issues that are a little bit more complicated so he logs on with his laptop. After checking the details on the laptop he decides to delete the account. Underneath his "click" action causes your run code to run:
// delete record
const deletebyID = (data_path, qID) => {
console.log("DELETE record");
gun.get(data_path).path(qID).put(null);
};
deletebyID('saving_accounts', 'FesxPaup8gzuNSsLFlWXMKaL');
Which is correct. However, if he then closes his laptop and picks his phone back up... he'll notice his account is still there!!! Which is not a good experience. But with GUN fixing this is easy because of the null notification:
gun.get(data_path).map().on((account, ID) => {
var UI = $("#account-" + ID);
if(!account){
UI.remove();
return;
}
updateUI(ID, account);
});
Now when they pick up their phone it will reflect the current state of their accounts! They'll see that it had been removed on all their devices because the null got synced to all devices.
Does that make sense? Does that your answer your question? Need help with anything else? As always, https://gitter.im/amark/gun and https://github.com/amark/gun/wiki/delete and http://gun.js.org/ .

Related

Managing 2 conferences with Voximplant scenario

I am trying to make conference with Voximplant, and when user makes a call to another user, while the call is still going on, it makes another call to another user making two calls and the callees is added to a video conferencing.
But it seems the caller is billed twice and the scenerio doesnt look optimised. What should i do to bill once and optimize it?
Scenario:
require(Modules.Conference);
var call, conf = null;
VoxEngine.addEventListener(AppEvents.Started, handleConferenceStarted);
function handleConferenceStarted(e) {
// Create 2 conferences right after session to manage audio in the right way
if( conf === null ){
conf = VoxEngine.createConference(); // create conference
}
conf.addEventListener(CallEvents.Connected,function(){
Logger.write('Conference started')
})
}
VoxEngine.addEventListener(AppEvents.CallAlerting, function(e) {
e.call.addEventListener(CallEvents.Connected, handleCallConnected);
let new_call = VoxEngine.callUser(e.destination,e.callerid,e.displayName,{},true)
new_call.addEventListener(CallEvents.Connected,handleCallConnected);
e.call.answer();
});
function handleCallConnected(e) {
Logger.write('caller connected');
conf.add({
call: e.call,
mode: "FORWARD",
direction: "BOTH", scheme: e.scheme
});
}
You need to end the conference when there are no participants. Refer to the following article in our documentation: https://voximplant.com/docs/guides/conferences/howto. You can find the full scenario code there.
Additionally, I recommend to add some handlers for the CallEvents.Disconnected and the CallEvent.Failed events right after
new_call.addEventListener(CallEvents.Connected,handleCallConnected);
because sometimes the callee may be offline or press a reject button. 🙂

How to read values from a view to insert in ratingValue and ratingCount in my web in order to be recognized by Google Structured data testing tool?

I was using an external service to get Aggregate Rating in my recipes blog, but dis service disappeared so I decided to build one myself. First of all, this is my first experience with cloud data and JavaScript programming so please, be paciente with me :-).
I'm doing my experiments in this duplicate of my blog: https://jleavalc.blogspot.com/
by now it works as I planned, letting one to vote and storing results in a oracle table, making it possible to retrieve results from a view of this table to get ratingCount and ratingValue values, as anyone can see in that link...
But at the end, despite you can see the stars, despite you can vote and get result stored, showing voting results, Structured data testing tool don't see tag values, so all work is useless.
I think I'm getting close to the problem, but not getting close to the solution. I have the impression that the cause of my problems is the asynchrony of the execution of the script that brings the data from the table, while the function is executed, the browser continues to render the page and it doesn't arrive in time to write those values ​​before the google tool can read them, so they appear empty to it.
I have tried everything including labels and variables in GTM with the same result. The latest version of the code, from this morning is installed right before the "/head" tag and it looks like this:
<script style='text/javascript'>
var myPostId = "<data:widgets.Blog.first.posts.first.id/>";
// <![CDATA[
var micuenta = 0;
var nota = 0;
getText("https://ge4e65cc87f573d-XXXXXXXXXXXX.adb.eu-amsterdam-1.oraclecloudapps.com/ords/admin/notas/?q={\"receta\":{\"$eq\":\"" + myPostId + "\"}}");
async function getText(file) {
let x = await fetch(file);
let y = await x.text();
let datos = JSON.parse(y);
nota = datos.items[0].media;
micuenta = datos.items[0].votos;
};
// This version gives the same result and is interchangeable with the previous one. I keep it commented so as not to forget it:
// var settings = {
// "url": "https://ge4e65cc87f573d-db20220526112405.adb.eu-amsterdam-1.oraclecloudapps.com/ords/admin/notas/?q={\"receta\":{\"$eq\":\"" + myPostId + "\"}}",
// "method": "GET",
// "timeout": 0,
// "async": false,
// };
// $.ajax(settings).done(function (response) {
// if (response.items.length != 0) {
// micuenta = response.items[0].votos;
// nota = response.items[0].media;
// }
// });
</script>
The key is, I think, getting this call to execute before Google's tool finishes rendering the Blogger post page.
The URL that I invoke to get the data calls an oracle view that returns a single row with the corresponding data from the recipe, placing this call:
recipe
In the browser the result is the following:
{"items":[{"receta":"5086941171011962392","media":4.5,"votos":12}],"hasMore":false,"limit":25,"offset":0,"count":1,"links":[{"rel":"self","href":"https://ge4e65cc87f573d-db20220526112405.adb.eu-amsterdam-1.oraclecloudapps.com/ords/admin/notas/?q=%7B%22receta%22:%7B%22%24eq%22:%225086941171011962392%22%7D%7D"},{"rel":"edit","href":"https://ge4e65cc87f573d-db20220526112405.adb.eu-amsterdam-1.oraclecloudapps.com/ords/admin/notas/?q=%7B%22receta%22:%7B%22%24eq%22:%225086941171011962392%22%7D%7D"},{"rel":"describedby","href":"https://ge4e65cc87f573d-db20220526112405.adb.eu-amsterdam-1.oraclecloudapps.com/ords/admin/metadata-catalog/notas/"},{"rel":"first","href":"https://ge4e65cc87f573d-db20220526112405.adb.eu-amsterdam-1.oraclecloudapps.com/ords/admin/notas/?q=%7B%22receta%22:%7B%22%24eq%22:%225086941171011962392%22%7D%7D"}]}
And I just need to take the median and votes values ​​to create the RatingCount and RatingValue labels
Can anyone offer me an idea that solves this little problem? :-)

lucene query filter not working

I am using this filter hook in my Auth0 Delegated Administration Extension.
function(ctx, callback) {
// Get the company from the current user's metadata.
var company = ctx.request.user.app_metadata && ctx.request.user.app_metadata.company;
if (!company || !company.length) {
return callback(new Error('The current user is not part of any company.'));
}
// The GREEN company can see all users.
if (company === 'GREEN') {
return callback();
}
// Return the lucene query.
return callback(null, 'app_metadata.company:"' + company + '"');
}
When user logged in whose company is GREEN can see all users. But when user logged in whose company is RED can't see any users whose company is RED.
I need to make this when user logged in, user should only be able to access users within his company. (except users from GREEN company).
But above code is not giving expected result. What could be the issue?
This might be related to a little warning note on the User Search documentation page
Basically they don't let you search for properties in the app_metadata field anymore. Unfortunately, this change was breaking and unannounced.
We had to make changes to our API so that we keep a copy of the app_metadatas in a separate database and convert lucene syntax to MongoDB queries, so that we can query by a chain of user_id:"<>" OR user_id:"<>" OR ....
One caveat though, you can't pass a query that's longer than 72 user_ids long. This number is so far undocumented and obtained empirically.
Also, you can't rely on Auth0's hooks to add new users to your database, as these don't fire for social logins, only for Username-Password-Authentication connections.
I hope this gave you some explanation as for why it wasn't working as well as a possible solution.
If I were you, I would look for an alternative for Auth0, which is what we are currently doing.
I finally ended up with this solution.
Used search functionality to filter users. I had to change below two files.
fetchUsers function in client\actions\user.js
changed
export function fetchUsers(search = '', reset = false, page = 0)
to
export function fetchUsers(search = '#red.com', reset = false,
page = 0)
AND
onReset function in client\containers\Users\Users.jsx
changed
onReset = () => { this.props.fetchUsers('', true); }
to
onReset = () => { this.props.fetchUsers('#red.com', true); }

Deny access for user/role with ACL

Is there any way of using ACL in Parse Server to deny access to items for a specific user or role?
Say I have a social networking app where users post updates. I have a role called all_users which all registered users are added to. All updates are readable by this role, EXCEPT users that the author has blocked.
I can grant read and write access to users/roles, but removing both read and write access through the Parse Dashboard removes the entry completely.
Tips would be greatly appreciated.
I wasn't 100% sure the answer, so I did what I usually do, make a unit test to figure it out.
As it happens, I am working on a PR to create an 'all user role' that will improve on your current solution for that, especially if your social network takes off and you have many many users.
See the issue: https://github.com/parse-community/parse-server/issues/4107
You can track the current state of my solution (which currently works, but isn't ready to merge just yet) here: https://github.com/parse-community/parse-server/pull/4111
But all I'm working on is the 'all user role' case, not the 'deny a user' case you need.
What I did to test your question was extend the unit test from my pr to address your particular (interesting) use case:
it('should respect for read.', function (done) {
let role, user;
const userP = new Parse.User()
.set('username', 'userA')
.set('password', 'password')
.save()
.then((user) => Parse.User.logIn(user.get('username'), 'password'));
const roleP = new Parse.Role('aRole', new Parse.ACL())
.save();
Parse.Promise.when(userP, roleP)
.then((newUser, newrole) => {
user = newUser;
role = newrole;
const acl = new Parse.ACL();
acl.setRoleReadAccess(role, true);
return new Parse.Object('Foo')
.setACL(acl)
.save();
})
.then(() => new Parse.Query('Foo').first())
.then((obj) => {
expect(obj).not.toBeDefined();
return new Parse.Query(Parse.Role)
.equalTo('name', '_All_Role')
.first()
})
.then((allRole) => {
expect(allRole).toBeDefined();
const roles = role.relation('roles');
roles.add(allRole);
return role.save(null, { useMasterKey: true });
})
.then(() => new Parse.Query('Foo').first())
.then((obj) => {
expect(obj).toBeDefined();
const acl = obj.getACL();
acl.setReadAccess(user.id, false); // <--- this is what you want!!!
console.log(acl);
const valid = obj.setACL(acl);
expect(valid).toBe(true);
return obj.save();
})
.then(() => new Parse.Query('Foo').first())
.then((obj) => {
expect(obj).not.toBeDefined(); // <--- but this fails :(....
done();
})
.catch(done.fail);
});
As I suspected, the test failed. I'm not an expert on parse permission (though learning) but my current understanding is that in there is no concept of precedence, so once you have added the permission through your everyone group, there is no way to 'deny'. In other words, the current permission model is that permissions are added, not explicitly denied.
Your use case is compelling though so finding a way to accommodate your use case would be interesting. As is often the case though, either you're going to need to figure out how to add it in such a way that it would be acceptable for the general use cases too or recruit someone who can (not volunteering, my dance card is full :).

Rally Kanban for Logged In Owner

I'm new to Rally's SDK. I'm trying to create a Kanban board that only shows the cards where the owner field = the person who's logged in (i.e. a My Kanban Board). What code should I add and where should I add it?
The following isn't my ideal answer to this issue, but I'd thought I'd post in case it helps someone else. I took the code from the Filter Epic post as suggested and modified it. It's not ideal for me because the filter occurs after the initial data pull, so it is only filtering the first 100 records the initial query pulled. Ideally, I want to change the initial pull of data to filter on username.
After this code in the Filtering Epic:
for (i=0;i<workproducts.length;i++) {
thisWorkProduct = workproducts[i];
Add:
//get the owner field value
var owner = "";
if (thisWorkProduct.Owner) {
if (thisWorkProduct.Owner.DisplayName) {
owner = thisWorkProduct.Owner.DisplayName;
}
else if (thisWorkProduct.Owner.UserName) {
owner = thisWorkProduct.Owner.UserName;
}
}
And then change:
if (thisWorkProduct.Children.length === 0) {
To:
if ((thisWorkProduct.Children.length === 0) && (owner === "__USER_NAME__")) {
And add in an if in the defects else (so it will now look like this):
else {
// If it's a Defect, it has no children so push it
if (owner === "__USER_NAME__") {
childlessWorkProducts.push(thisWorkProduct);
}
It's probably not the most efficient code because I'm new to javascript.
And if anyone has suggestions on how to do the username filter in the initial data pull, I'd love to hear them.
You can filter on the initial data pull by including a query in the cardboardConfig object:
var cardboardConfig = {
//... other properties
query: new rally.sdk.util.Query('Owner = /user/__USER_OID__')
};
Check out this answer:
Filtering epics from Kanban board
It would be pretty straightforward to adapt the filtering callback to filter by Owner instead of just child-less artifacts.