I need help with Google App Maker data model security. I want to set multiple owners for a single record. Like the current user + the assigned admin + super admin.
I need this because all records can have different owners and super-owners/admins.
I know that we can point google app maker to a field containing record owner's email and we can set that field to the current user at the time of the creation of the record.
record.Owner = Session.getActiveUser().getEmail();
I want to know if it is possible to have field owners or have multiple fields like owner1, owner2 and then assign access levels to owner1, owner2...
Or how can we programmatically control the access/security/permissions of records?
The solution I'd use for this one definitely involves a field on the record that contains a comma separated string of all the users who should have access to it. I've worked on the following example to explain better what I have in mind.
I created a model and is called documents and looks like this:
In a page, I have a table and a button to add new document records. The page looks like this:
When I click on the Add Document button, a dialog pops up and looks like this:
The logic on the SUBMIT button on the form above is the following:
widget.datasource.item.owners = app.user.email;
widget.datasource.createItem(function(){
app.closeDialog();
});
That will automatically assign the creator of the record the ownership. To add additional owners, I do it on an edit form. The edit form popus up when I click the edit button inside the record row. It looks like this:
As you can see, I'm using a list widget to control who the owners are. For that, it is necessary to use a <List>String custom property in the edit dialog and that will be the datasource of the list widget. In this case, I've called it owners. I've applied the following to the onClick event of the edit button:
var owners = widget.datasource.item.owners;
owners = owners ? owners.split(",") : [];
app.pageFragments.documentEdit.properties.owners = owners;
app.showDialog(app.pageFragments.documentEdit);
The add button above the list widget has the following logic for the onClick event handler:
widget.root.properties.owners.push("");
The TextBox widget inside the row of the list widget has the following logic for the onValueEdit event handler:
widget.root.properties.owners[widget.parent.childIndex] = newValue;
And the CLOSE button has the following logic for the onClick event handler:
var owners = widget.root.properties.owners || [];
if(owners && owners.length){
owners = owners.filter(function(owner){
return owner != false; //jshint ignore:line
});
}
widget.datasource.item.owners = owners.join();
app.closeDialog();
Since I want to create a logic that will load records only for authorized users, then I had to use a query script in the datasource that will serve that purpose. For that I created this function on a server script:
function getAuthorizedRecords(){
var authorized = [];
var userRoles = app.getActiveUserRoles();
var allRecs = app.models.documents.newQuery().run();
if(userRoles.indexOf(app.roles.Admins) > -1){
return allRecs;
} else {
for(var r=0; r<allRecs.length; r++){
var rec = allRecs[r];
if(rec.owners && rec.owners.indexOf(Session.getActiveUser().getEmail()) > -1){
authorized.push(rec);
}
}
return authorized;
}
}
And then on the documents datasource, I added the following to the query script:
return getAuthorizedRecords();
This solution will load all records for admin users, but for non-admin users, it will only load records where their email is located in the owners field of the record. This is the most elegant solution I could come up with and I hope it serves your purpose.
References:
https://developers.google.com/appmaker/models/datasources#query_script
https://developers.google.com/appmaker/ui/binding#custom_properties
https://developers.google.com/appmaker/ui/logic#events
https://developers-dot-devsite-v2-prod.appspot.com/appmaker/scripting/api/client#Record
Related
imagine this scenario:
You have a list with lets say 100 items, a user favorites 12 items from the list.
The user now wants these 12 items displayed in a new list with the up to date data (if data changes from the original list/node)
What would be the best way to accomplish this without having to denormalize all of the data for each item?
is there a way to orderByChild().equalTo(multiple items)
(the multiple items being the favorited items)
Currently, I am successfully displaying the favorites in a new list but I am pushing all the data to the users node with the favorites, problem is when i change data, their data from favorites wont change.
note - these changes are made manually in the db and cannot be changed by the user (meta data that can potentially change)
UPDATE
I'm trying to achieve this now with cloud functions. I am trying to update the item but I can't seem to get it working.
Here is my code:
exports.itemUpdate = functions.database
.ref('/items/{itemId}')
.onUpdate((change, context) => {
const before = change.before.val(); // DataSnapshot after the change
const after = change.after.val(); // DataSnapshot after the change
console.log(after);
if (before.effects === after.effects) {
console.log('effects didnt change')
return null;
}
const ref = admin.database().ref('users')
.orderByChild('likedItems')
.equalTo(before.title);
return ref.update(after);
});
I'm not to sure what I am doing wrong here.
Cheers!
There isn't a way to do multiple items in the orderByChild, denormalising in NoSQL is fine its just about keeping it in sync like you mention. I would recommend using a Cloud Function which will help you to keep things in sync.
Another option is to use Firestore instead of the Realtime Database as it has better querying capabilities where you could store a users id in the document and using an array contains filter you could get all the users posts.
The below is the start of a Cloud function for a realtime database trigger for an update of an item.
exports.itemUpdate = functions.database.ref('/items/{itemId}')
.onUpdate((snap, context) => {
// Query your users node for matching items and update them
});
We are new to DNN and we plan to add a product module that is in charge of adding, editing, deleting, listing, and showing the details of the products.
We have written a UserControl named ProductsList.ascx, which has AddProducts.ascx and ShowPrdoctDetail.ascx defined in it, using Host => Extensions => ProductsList => Module Definition => Add Module Control.
In admin mode,we have created a page and dragged the module in it, so that the admin of the site can add, edit, delete, and see the details of each product.
Also there is a slideshow in the homepage that shows the latest products.In addition, the products are shown in the menu.
Now, we want to redirect user to the product detail page (ShowPrdoctDetail.ascx in our case) whenever he/she clicked the product shown in slideshow or in menu.
We are aware of Globals.NavigateUrl() method, but it needs tabid and mid to redirect to a specific page and module and in DNN every added page by admin will get different tabid and mid.
Since in DNN, admin can create many pages and add this module to them, we have no idea that what tabid and mid we should pass to Globals.NavigateUrl() in order to navigate user to product details page (ShowPrdoctDetail.ascx) when user clicked on a specific product in menu or slideshow.
Any kind of help is highly appreciated.
Try save current tabid to DB when adding product detail module into page. And with ProductId, you can grab tabid of product detail, and use it to redirect to correct page.
The way I would tackle this is to create another Module Definition for the details module and give it a Friendly name like "Product Details" and add the ShowProductDetail.ascx module control as the default view of this new module definition.
Then you can drag that new module onto a page for your product details page.
In your main Product Admin module, you can create a setting view with a dropdown list that contains a list of all tabs (pages) that the "Product Details" module on.
You can use the following method to get the list of tabs in the portal that has an instance of the module:
private List<TabInfo> GetAllModuleTabsbyModuleName(string friendlyName)
{
List<TabInfo> results = new List<TabInfo>();
Dictionary<int, int> dups = new Dictionary<int, int>();
ModuleController mc = new ModuleController();
ArrayList oModules = mc.GetModulesByDefinition(base.PortalId, friendlyName);
TabController tc = new TabController();
TabCollection oTabs = tc.GetTabsByPortal(base.PortalId);
foreach (ModuleInfo oModule in oModules)
{
foreach (KeyValuePair<int, TabInfo> oTab in oTabs)
{
if (oTab.Key == oModule.TabID && !dups.ContainsKey(oModule.TabID))
{
results.Add(oTab.Value);
dups.Add(oModule.TabID, oModule.TabID);
}
}
}
return results;
}
You can bind that to the dropdown list options and an administrator could select the page that will be redirected when a product is clicked on the main module.
ddlProdDetailsTab.DataSource = GetAllModuleTabsbyModuleName("Product Details");
ddlProdDetailsTab.DataValueField = "TabID";
ddlProdDetailsTab.DataTextField = "TabName";
ddlProdDetailsTab.DataBind();
So from the settings, you know the TabId you want to redirect to, then you need the moduleId and you can create the redirect using NavigateUrl().
var pdTab = TabController.Instance.GetTab(Convert.ToInt32(Settings["ProductDetailTabId"]), PortalId);
var pdModule = pdTab.Modules.Cast<ModuleInfo>().FirstOrDefault(m => m.ModuleName == "Product Details");
var productLink = Globals.NavigateURL(pdTab.TabId, "", "mid=" + pdModule.ModuleId, "productId=" + productId);
I have a many-to-many structure mapped to entity framework. This is a sample of what it looks like:
User UserTag Tag
------- -------- -------
IdUser(PK) IdUserTag(PK) IdTag(PK)
Name IdUser(FK) TagName
Desc IdTag(FK) Active
Now, I needed to exclude from any request of any method the viewing of Tags that were Active=false.
First, I tried doing it manually in every method, like:
public User GetById(int id)
{
var item = UserRepository.GetById(id); //This is just a repository that calls the EF context
//EF automatically maps it to the *UserTags* property
foreach(var tag in item.UserTags)
{
if(tag.Tag.Active == false)
item.UserTags.Remove(tag);
}
}
But it throws the following exception:
The relationship could not be changed because one or more of the foreign-key properties is non-nullable
So, I wanted to know if there's a way to conditionaly filter every request made to a specific table, whether it is select or a join request.
Try this in your GetById method:
var user.UserTags = dbContext.Entry(user)
.Collection(u => u.UserTags)
.Query()
.Where(ut => ut.Active == true)
.ToList();
The supplied code fails because it is attempting to remove items from the data entities not the list. If you want to pass the data entity around instead of the data model, you need to not use Remove. Something like the below (untested should work).
tags = item.UserTags.Where((ut) => ut.Active).ToList();
This line will get you a list of data entities that are active. However, you should really map all of this into a data model (see AutoMapper) and then you would not be removing items from the database.
I have three entities : Event, Photo and User.
Three main relations :
An Event has 0 or more photos (blue relation, OneToMany)
An Event has been created by one photo, which I call the firstPhoto (red relation,
OneToOne)
A user can create 0 or more photos (violet relation,
OneToMany)
What I want is to map the relation between an Event and the User who created it, without adding or changing my database. It means the user that created the firstPhoto of the Event.
I'm not looking for a SQL query which I succed to do but really a mapping in my User.php Entity.
$user->getEvents() would give the events the user created.
I can't success to do so... any idea ? Am I obliged to add or change something in my database ?
I see 2 ways of doing that:
1) make a named native query http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html#named-native-query
2) write something like
public function getEvents()
{
$res = array();
$photos = $this->getPhotos();
foreach($photos as $photo) {
$res[] = $photo->getEvent();
}
return $res;
}
I have two profile2 profiles defined - main and customer_profile. Also, I have a node type called Customer.
When creating a new Customer node, I would like to load the custom_profile form. The idea is to create a node and a profile simultaneously.
I know it's definately a hook_form_alter solution but can someone tell me how to programmatically load a profile while creating or editing a Customer node.
You can load profile type and data by using these function
$types = profile2_get_types();
profile2_load_by_user($account, $type_name = NULL)
For Example :
$types = profile2_get_types();
if (!empty($types)) {
foreach ($types as $type) {
$profile = profile2_load_by_user($uid, $type->type);
}
}
Even if you are able to load the customer_profile form, you will need to handle the values separately as they are two different nodes.
I would suggest capturing those fields in the customer node form, and then create a customer_profile programmatically from the values.
If you want to get the profile2 form itself then you can use something like
module_load_include('inc', 'profile2_page', 'profile2_page');
$profile2 = profile2_by_uid_load($uid, 'seeker_profile');
$entity_form = entity_ui_get_form('profile2', $profile2, 'edit');
and then add that to the form you want to place it in.
You can load full profile data using profile2_load_by_user();
params like:-
profile2_load_by_user($account,$type_name)
$account: The user account to load profiles for, or its uid.
$type_name: To load a single profile, pass the type name of the profile to load
So code like bellow
$account->uid = $existingUser->uid;
$type_name = 'user_about';
$profile = profile2_load_by_user($account, $type_name);
//$profile variable have full data of profile fields
//changing data profile2 fields
if(isset($_POST['field_user_first_name'])&& !empty($_POST['field_user_first_name'])){
$profile->field_user_first_name['und'][0]['value'] = $_POST['field_user_first_name'];
}
profile2_save($profile);
Well When creating a new Profile , Profile2 fields are not visible until a manual save is done.
To Automatically create the profile2 object , We use rules Module
Step
1) Go to Drupal admin/config/workflow/rules
2) create new rule
3) Give a name and Select in react/event "After saving a new user account"
4) Action,>> Add Action >> Execute custom PHP code
5) insert php code
$profile = profile_create(array('type' => 'profile2 type machine name', 'uid' => $account->uid));
profile2_save($profile);
6)Save >> Save changes.
This will create profile2 field when a new user is Created.
I had a similar need for creating a custom tab at the user page and loading the user profile2 form in it.
Here is a snap code of how I managed to accomplish just that:
MYMODULE.module https://gist.github.com/4223234
MYMODULE_profile2_MYPROFILE2TYPE.inc https://gist.github.com/4223201
Hope it helps.