How to get Google UserId from active user session in App Maker? - google-app-maker

Is there a way to get "User Google Id" from the session in App Maker. In the documentation its only mentioned how to retrieve the email of the logged in user Session.getActiveUser().getEmail() but no where it says how to get the id. I need this because the user email might sometimes changes. So I need the user id to keep track of users and related permission tasks. Or is there something I'm missing out here in how this should be implemented.

Yet an easier way to find Google Id simply using the Directory model. Although its mentioned in documentation that there is a way to get current signed in user id ( which is Google Id), its not clearly stated how - maybe documentation could be improved here. Another problem is that in many occasions the email of current active user is referred to as the id for example in deprecated method Session.getActiveUser().getUserLoginId(). Anyways this is a proper way to get the id.
var query = app.models.Directory.newQuery();
query.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
var result = query.run();
var GoogleId = result[0]._key;
So with this GoogleId you can safely relate different models with each other and not worry that database integrity might break if an already referenced user email is changed.
Relating the different models could be done simply by creating a model that acts as a wrapper model around the Directory model and storing GoogleId in it. Then linking that model to other models where you want to track user related data because unfortunately we can not directly link The Directory Model to other models.

A team member has figured it out. This should be done using Apps Script - which works within App Maker environment using server side script.
var GoogleUser = (function (){
/**
*
* #param {string} email
*/
function getUserObjByEmail(email){
// Same as using AdminDirectory class.
var apiUrl = "https://www.googleapis.com/admin/directory/v1/users/"+email+"?fields=id";
var token = ScriptApp.getOAuthToken();
var header = {"Authorization":"Bearer " + token};
var options = {
"method": "GET",
"headers": header
};
var response = JSON.parse(UrlFetchApp.fetch(apiUrl, options));
return response;
}
/**
*
* #param {string} email - User email.
*/
function getIdByEmail(email){
return getUserObjByEmail(email)['id'];
}
var publicApi = {
getIdByEmail: getIdByEmail
};
return publicApi;
})();
Note that using var apiUrl = "https://www.googleapis.com/admin/directory/v1/users/"+email+"?fields=id"; is not going to be asynchronously called because its already happening in the server.

Is this a dup of this question?
I think this will solve your problem, even though it's a bit of a hack.

Related

User-Id for Push-Notification on Actions for Google

I try to make a push notification for my google assistant app.
I used the sendNotification Code form the google developer site: https://developers.google.com/actions/assistant/updates/notifications
I am coding Java.
Everything is working, expect getting the correct user id.
When I hardcode my user it works, but how do I get the user id in the code?
I tried following code:
Argument arg_userId = request.getArgument(ConstantsKt.ARG_UPDATES_USER_ID);
String userId = request.getUser().getUserId();
--> I get "java.lang.reflect.InvocationTargetException"
String userId = arg_userId.getRawText();
--> same Exception
There are two problems with the approach you're taking to get the notification ID:
The ID attached to the user object is deprecated and probably unavailable.
This wasn't the ID you wanted anyway.
In the response where the user finalizes the notification, that response includes an ID which you should get and store. Since you're using Java, the code might look something like this:
ResponseBuilder responseBuilder = getResponseBuilder(request);
Argument permission = request.getArgument(ConstantsKt.ARG_PERMISSION);
if (permission != null) {
Argument userId = request.getArgument(ConstantsKt.ARG_UPDATES_USER_ID);
// code to save intent and userID in your db
responseBuilder.add("Ok, I'll start alerting you.").endConversation();
} else {
responseBuilder.add("Ok, I won't alert you.");
}
return responseBuilder.build();

Adaptive User Management

I have built a review app based on Google's "people viewer" template that allows managers to create and edit reviews for their direct reports.
The app contains the directory model as well as three roles: Admins, HR, EndUsers.
The app contains a user settings model that allows to create and store user settings similar to the "people skills" template.
The app contains a review model that will contain the reviews for every employee. As one employee can have several reviews, this will be a one-to-many relation, either linked to directory model or user settings model.
The reviews should be readable by managers chain of manager. For this I have created a server script, assuming that the EmployeeEmail will be additionally stored in the review. But maybe there is a better alternative?
function getDirectReportsChainForUser_(query) {
var userQuery = app.models.Directory.newQuery();
userQuery.filters.PrimaryEmail._equals = query.parameters.PrimaryEmail;
userQuery.prefetch.DirectReports._add();
userQuery.prefetch.DirectReports.DirectReports._add();
var users = userQuery.run();
if (users.length === 0) {
return [];
}
var user = users[0];
var directs = user.DirectReports;
var records = [];
for (var i = 0; i <= directs.length; i++) {
records.push(directs[i].PrimaryEmail);
}
// The following lines are based on the asumption that the EmployeeEmail
// will be stored in the review in case that there is no better alternative.
//The question that then remains is how to recursively add the DirectReports
//of the DirectReports to the array???
var reviewQuery = app.models.Reviews.newQuery();
reviewQuery.filters.EmployeeEmail._in = records;
return reviewQuery.run();
}
The manager should be able to define whether one or more of his deputies can read the reviews for his unit, too. My idea was to solve this issue through a many-to-many relation between the directory and review model, but I am not sure how to implement it?
Furthermore, once a manager or his deputy departures, it should be possible for the Admin to dissolve the connection and to reconnect the reviews to a successor. Therefore I was thinking about integrating a multiselect in the admin page. Would this be feasible?
Here I see at least two distinct questions:
is there better way to associate directory model's record and ordinary data model than just adding primary email field to the data model
Nope, at this time it is not possible to establish relations between data (SQL/Drive Tables) and directory models.
how to recursively get all direct reports for a user
App Maker's Directory Model is a wrapper on top of G Suit Admin SDK's Directory API that exposes just a small subset of its powerful features. When you add Directory Model App Maker automatically plugs in correspondent Apps Script advance service:
Since we already have configured Directory API we can unleash its full power and easily fetch all manger's subordinates with a single call (or multiple if you have a need to support paging). In order to do that we will use Users.List API method with managerId query parameter (the only one that allows us to query all subordinates down the tree). Here are reference for the minimal set of search query parameters quoted from the full search documentation (without those parameters query would not work or wouldn't work in a way we need):
managerId: The ID of a user's manager either directly or up the management chain.
domain: The domain name. Use this field to get fields from only one domain. To return all domains for a customer account, use the customer query parameter instead. Either the customer or the domain parameter must be provided.
viewType: Whether to fetch the administrator-only or domain-wide public view of the user. For more information, see Retrieve a user as a non-administrator (admin_view is default value so we need to override it with domain_view).
query: Query string for searching user fields. For more information on constructing user queries, see Search for Users.
/**
* Fetches all reviews associated with all subordinate employees (both direct
* and indirect reports).
*/
function getAllReportsEmails(managerId) {
var emails = [];
var result = AdminDirectory.Users.list({
domain: 'ENTER HERE YOUR DOMAIN (exapmle.com)',
query: 'managerId=' + managerId,
viewType: 'domain_public',
maxResults: 100
});
if (result.users) {
emails = result.users.map(function (user) {
return user.primaryEmail;
});
}
return emails;
}
/**
* Fetches all reviews associated with all subordinate employees (both direct
* and indirect reports).
*/
function getAllReportsReviewsForManager_(query) {
var userQuery = app.models.Directory.newQuery();
// For better security I would recommend to use
// Session.getActiveUser().getEmail() instead of parameter
// passed from the client.
userQuery.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
var users = userQuery.run();
if (users.length === 0) {
return [];
}
var manager = users[0];
var managerId = manager._key;
var allReportsEmails = getAllReportsEmails(managerId);
var reviewQuery = app.models.Reviews.newQuery();
reviewQuery.filters.EmployeeEmail._in = allReportsEmails;
return reviewQuery.run();
}
Pavel, I tried to integrate the ideas you gave me into one server script that returns an array of the manager and his whole subordinate chains (direct reports + indirect reports), so that I can use it whenever needed. I turned into a recursive function to get the direct reports and indirect reports on the next lower level. Is there a way to get the whole chain?
function getSubordinatesChainForUser(query) {
var userQuery = app.models.Directory.newQuery();
userQuery.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
userQuery.prefetch.DirectReports._add();
userQuery.prefetch.DirectReports.DirectReports._add();
var users = userQuery.run();
if (users.length === 0) {
return [];
}
var userEmails = users.map(function(manager){
var employeeEmails = manager.DirectReports.map(function(employee){
return employee.PrimaryEmail;
});
return manager.PrimaryEmail + ',' + employeeEmails;
});
return userEmails;
}

Fetch data for record edit page

I have a page that lets you edit user data. I'm using FlowRouter for the routing and it can be found on the route /employees/:id.
I need to update the detail form when data changes on the server and leave the route if it was deleted by other client.
I decided to use Tracker.autorun which informs me whenever the data changes. The previous user info is stored on the template so it's easy to tell if the record was deleted.
Template.UpdateEmployee.onCreated(function () {
const self = this;
self.subscribe('user', FlowRouter.getParam('id'));
self.autorun(function () {
const _id = FlowRouter.getParam('id');
const user = Meteor.users.findOne({_id});
if(!user && self.user)
FlowRouter.go('/employees');
self.user = user;
if(!user)
return;
user.email = user.emails[0].address;
$('.ui.form').form('set values',user);
});
});
And lastly in the onRendered callback I'm checking if the data was set on template as I believe not doing so could lead to data being available before the template is rendered and hence values wouldn't get set properly. Is this correct?
Template.UpdateEmployee.onRendered(function () {
if(this.user){
user.email = user.emails[0].address;
$('.ui.form').form('set values',user);
}
});
Are there any pitfalls to this solution?
I can see a couple drawbacks inherently. The first one is doing a find query on the client. Typically you would want to return data from the server using Meteor publish and subscribe.
The second is you are passing the key to find the data over the URL. This can be spoofed by other users for them to find that users data.
Lastly if you are doing a find on the user object, I assume you might be storing data there. This is generally bad practice. If you need to store user data with their profile, it's best to create a new collection and publish/subscribe what you need.

meteor-shopify User Creation/ Login after Auth callback

Assuming I want to create users upon authorizing the app, how would I grab their email during the onAuth callback...? Looks like the callback assumes the user is already logged in. Am I thinking about it correctly?
I noticed when installing the Fishbowl Prizes app, after auth I can click on the accounts tab and see that all my account info is pre-populated from my shopify store account (name, email, address, etc).
I'm not sure if I should go by the title or the content of the post in terms of answering your question, so I'll provide a very simple example of how to get the info from the API and do something with it here.
I have provided a more in depth answer related specifically to grabbing the details from the API for user account creation here: https://github.com/froatsnook/meteor-shopify/issues/15#issuecomment-177413630
Looks like the callback assumes the user is already logged in.
The userId param is undefined if there is no user. If your onAuth operations don't need to do anything with the user, you can just leave it out of the params. In your case you'll just want to handle it conditionally using an if/else block:
if(!userId){
// do stuff
} else {
// do other stuff
}
On to the example of grabbing those details from the API:
All the prepopulated information you are seeing is available from the Shopify API in the shop object. You already have the access token when onAuth callbacks are fired, so you can just grab it from the API immediately after you have inserted the shop's Keyset.
For the sake of simplicity, in this example we'll assume the user already exists and is logged in. In your server-side onAuth callback (after you have inserted the keyset) you can do something like this to add those fields to the user's profile object:
Shopify.onAuth(function(access_token, authConfig, userId) {
var shopUUID = uuid.new(); // Not secure to name keyset same as the shop!
Shopify.addKeyset(shopUUID, {
access_token: access_token
});
var api = new Shopify.API({
shop: authConfig.shop,
keyset: shopUUID
});
// get the Shop object from the API
var shopObj = api.getShop();
var userInfo = {
'profile.name': shopObj.shop_owner,
'profile.email': shopObj.email,
'profile.phone': shopObj.phone,
'profile.shopName': shopObj.name
};
Meteor.users.update({_id: userId}, {$set: userInfo})
});
Then you can use them in templates like this:
{{currentUser.profile.name}} or {{currentUser.profile.email}}
Or in functions like so:
var realName = Meteor.user().profile.name
or
var userEmail = Meteor.user().profile.email etc
For a more about using this data for user creation, see my explanation here:
https://github.com/froatsnook/meteor-shopify/issues/15#issuecomment-177413630

Storing per-user data in Meteor

I want to store information for every logged in user in my meteor app, such as their profile picture, bio, ect. But if I try to do something like Meteor.user().picLink = "..."; it appears to get erased on every subsequent call to Meteor.user(). I assume this means I'm not supposed to store extra data directly on the user object like that.
The only response to that that I can think of is to have a separate collection with user data in it. But that seems like it would be hard to keep consistent with Meteor.users. Is there a better way?
All user accounts come with an automatically published profile field which you can update like so:
var userId = Meteor.userId();
var url = 'http://example.com/kittens.jpg';
Meteor.users.update(userId, {$set: {'profile.photo': url});
That will update the underlying database and persist across connections.
As I point out here you should be aware that the profile object is currently editable by default even when the insecure package has been removed. This means any user can open up the console and modify his/her profile.
A better approach is to deny the updates and to use a method instead:
client
var url = 'http://example.com/kittens.jpg';
Meteor.call('update.photo', url);
server
Meteor.users.deny({
update: function() {return true;}
});
Meteor.methods({
'update.photo': function(url) {
check(url, String);
Meteor.users.update(this.userId, {$set: {'profile.photo': url}});
}
});

Resources