Im trying to pull the email addresses for all users within my org.
My GraphQL query looks like this:
{
organization(login: "####") {
membersWithRole(first: 100) {
totalCount
edges {
node {
login
name
email
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
However, it only shows the email if the user has made it publicly shown. The reason I need this is to remove users from my org that have left/cancelled.
Thanks,
it only shows the email if the user has made it publicly shown.
Yes, which means that private emails are not to be queried or, as explained here:
some users don't have an e-mail address defined in github.com/settings/profile and that causes the email field in the API response to be blank.
As asked before, using email alone won't work, since private emails would not be part of that kind of queries.
That can give you a User node is null if email is private or unset.
An other approach would be needed in your case, like, for instance, Get last activity for a user in an organisation GitHub Ecosystem.
Related
I am relatively need to DDD tactical patterns and need help modelling an example app im putting together. I am creating an app that is supposed to help people manage shifts in their stores/organizations.
I am currently working on my first microservice: Membership service (domain). This service is responsible for creating new organizations & inviting users to be become members of the created organizations. The following are rules of the service I am trying to model.
Any user can create an organization
A user can create more than one organization
The user that creates the organization is the initial admin of the organization
An organization should at least have 1 admin
Only admins can invite people to join the organization. They can invite people who are not current users by sending an invitation via email
A user is added to the organization only if they accept the invitation (probably click on a link via email)
So far, I only have 2 aggregate root classes: Organization & User
Organization
class Organization extends Aggregate {
private _id!: OrgId;
private _members!: Member[];
private _name!: Name;
get members() {
return this._members;
}
get id() {
return this._id;
}
constructor(args: InstantiateConstructorArgs | CreateConstructorArgs) {
super();
if (args.type === ConstructorTypes.Instantiate) {
this.loadFromHistory(args.events);
} else {
const organizationCreatedEvent = OrganizationCreatedEvent.create(
args.id,
args.creatorId,
args.name
);
this.applyChange(organizationCreatedEvent);
}
}
private applyOrganizationCreatedEvent(e: OrganizationCreatedEvent) {
const initialMember = new Member(
e.orgId,
MemberRoles.Admin,
e.creatorUserId
);
this._id = e.orgId;
this._name = e.orgName;
this._members = [initialMember];
}
}
As you can see, the Organization aggregate root is responsible for maintaining a collection of its members. It is event sourced as well. The Member class is defined within the context of the as such
export enum OrganizationMemberRoles {
Admin,
Regular,
}
class OrganizationMember {
constructor(
public readonly organizationId: OrgId,
public readonly role: OrganizationMemberRoles,
public readonly userId: UserId
) {}
}
User
class User extends Aggregate {
constructor(public readonly id: UserId) {
super();
}
createOrganization(orgId: OrgId, name: Name) {
return OrganizationFactory.CreateOrganization(orgId, this.id, name);
}
}
My Modelling Struggle
I am not sure what is the best way to model the invitation process. Since only Admins can extend an invitation to the organization, would you recommend creating an Admin class and putting the extendInvitation method on that? If so, how would the request flow look like?
Should I model the invitation outside of the Organization aggregate? Or should it be engulfed in the aggregate? If outside, then should I use an eventual consistency model to add the member to the organization?
The fact that only admin can create it is a "authorization" concern, maybe tomorrow you want to extend it to a subset of "special" users that are not admin of that org, or you want the admin of the entire platform to be able to invite a user in a specific org.
A user is added to the organization only if they accept the invitation (probably click on a link via email)
It seems to me that the invitation is an entity itself, and I see the possibility to have an "Invite" command on the Organization that can be execute only by admin of the org itself, and will create a new Invite (the email being sent is a side effect of the Invite entity being created).
(I see a good approach having the Invite insite the org as it should already have all the info needed in order to check the invariants, it knows all the users that already joined it)
I'd like to get a mapping between GitHub logins and emails in my organization using the GitHub API (any version).
I can get the emails on organization members' accounts with this GraphQL query:
query {
organization(login:"myorg"){
members(first:100) {
nodes {
login
name
email
}
}
}
}
But this isn't the email I'm after. I really want the email on the "Linked SSO identity", which I get to from my organization page by clicking this link:
When I click this link, the desired email is listed in several places on https://github.com/orgs/myorg/people/danvk/sso.
Is it possible to access this SSO-linked email via any version of the GitHub API?
Organisation Level SAML
You can access this information for accounts provisioned via SCIM*.
query {
organization(login: "LOGIN") {
samlIdentityProvider {
ssoUrl
externalIdentities(first: 100) {
edges {
node {
guid
samlIdentity {
nameId
}
user {
login
}
}
}
}
}
}
}
[authored by a member of GitHub's support staff] and samples available here.
I haven't verified if accounts that have linked SAML accounts outside of SCIM would work using this query.
Enterprise Level SAML
If your IdP's configured at the enterprise level, run instead:
{
enterprise(slug: "MYENTERPRISENAME") {
ownerInfo {
samlIdentityProvider {
externalIdentities(after: null, first: 100) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
user {
login
}
samlIdentity {
nameId
}
}
}
}
}
}
}
}
Additional Info
These GraphQL queries can be run via the GitHub CLI (download here).
Permissions are provided by a personal access token (PAT). You can set this up at https://github.com/settings/tokens.
If querying the org, you'll need to assign your PAT the admin:org right. You'll also need to authorise it for each org against which you intend to use it (via the Configure SSO option next to the PAT.
If querying the enterprise, you'll need to assign your PAT the admin:enterprise right.
To authenticate create an environment variable, GH_TOKEN, and set its value to the token's value (if you didn't note this when creating the token, you'll have to drop and recreate the token to get a fresh value).
Examples of how to use the gh cli to run graphql (and other API) queries can be found here: https://cli.github.com/manual/gh_api
this worked for what i needed:
query {
user(login: "SOME-USER"){
organizationVerifiedDomainEmails(login: "SOME-ORG")
}
}
I'm sending emails using: https://github.com/sendgrid/sendgrid-nodejs/tree/master/packages/mail
I have not been able to find out HOW I can add the Unsubscribe equivalent. This is documented in here: https://sendgrid.com/docs/Classroom/Basics/Marketing_Campaigns/unsubscribe_groups.html#-Using-a-Custom-Unsubscribe-Link
On the website, you just use a shortcode [Unsubscribe], this does not work when sending emails via the sendgrid/mail package.
One tip that would have saved me an hour or two is that:
It's possible to send the following in the api json along with other stuff:
"asm":{
"group_id":123,
"groups_to_display": [123],
}
then the following variables become available to use within the template:
<%asm_group_unsubscribe_raw_url%>
<%asm_preferences_raw_url%>
If you want to keep things simple don't include the following variable as it fiddles with too many things (this wasn't obvious from the documentation so obviously I did so and wasted time :( ):
"tracking_settings": {
"subscription_tracking": {
"enable": true,
"substitution_tag": "[unsubscribe_url]"
}
}
Just use them in their raw format and you shall be fine.
Since you're sending using code, it's a "transactional" type of message. You'll want to either turn on the Subscription Tracking filter at the account level (via [UI](subscription tracking setting) or API), or turn it on as you send the message, as part of the mail/send API call, under tracking_settings.
It's important to note that you can't mix those. If you define anything in the mail/send API call, you'll need to define everything for Subscription Tracking in that call. SendGrid won't look at some settings at the mail level, and some at the account level.
Most users will just set it at the account level. There, you can customize the HTML & Text of the Unsubscribe footer, customize the HTML of the landing page, or redirect landing to a URL of your choosing, which will send the recipient there with ?email=test#domain.com in the URL string for your system to catch. You can also define the "replacement tag" like [%unsubscribe%], so that you can place the URL wherever you want within your HTML.
https://app.sendgrid.com/ > Suppressions > Unsubscribe Groups > Create New Group
Note down group_id/ids. e.g 123 (Only number !Not string)
Send email using node.js
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(SENDGRID_API_KEY);
const tags = { invitedBy : Alex }
const msg = {
to: email,
from: { "email": SENDER_EMAIL,
"name": SENDER_NAME
},
templateId: TEMPLATE_ID,
dynamic_template_data: {
Sender_Name: name,
...tags
},
asm: {
group_id: 123,
groups_to_display: [
123
],
},
};
await sgMail.send(msg);
The best approach is to use Group Unsubscribes.
First create a group in Sendgrid:
Groups > Unsubscribe Groups > Create a group
Next, insert a module into the Sendgrid template that creates specific tags in your email, which are populated when you make an API request
Go to your template
Insert an unsubscribe module in an HTML block
Save
Finally make an API request and specify the group created in step 1:
"asm":{
"group_id":544,
"groups_to_display": [544, 788],
}
These will be inserted into the module mentioned in step 2 when the email is sent.
Unfortunately Sendgrid unsubscribe links are not as straightforward as they could be. They are explained in more detail here
The easiest way is to do this via the SendGrid GUI.
Go to Settings -> Tracking -> Subscription Tracking
When an email is sent to a queue and there is a contact associated with the "From" email in CRM, upon promoting an email to an email activity the system automatically fills in the "From" field with the contact information. However, if a user with the same email exists in CRM, too, then the system always picks up the system user instead of the contact. I need to override this behaviour to ALWAYS pick up the contact if one with the email exists.
I created a post-operation plug-in (tried a pre-operation plug-in, too) for the event Create for email, trying to override the From field. The problem is, it does not work. When I debug the plug-in, it goes quietly past the assignment without any errors and then the same plug-in fires for the same email again. And again. And again.
When I try instead to create a new email and use the same ActivityList[] I was trying to use for the entity that triggered the event, it works. It seems that the problem is that CRM does not allow changing the From field from a plug-in, or am I doing something wrong? If it's a limitation enforced by CRM, is there a way around it?
My code is below:
var email = ((Entity)context.InputParameters["Target"]).ToEntity<Email>();
...
var oldFrom = ((EntityCollection)email.Attributes["from"]).Entities;
List<ActivityParty> newFrom = new List<ActivityParty>();
foreach (Entity party in oldFrom)
{
EntityReference entRef = (EntityReference)party.Attributes["partyid"];
if (entRef.LogicalName == SystemUser.EntityLogicalName)
user = userLogic.Get(new Guid(entRef.Id.ToString()));
if (user == null) return;
string emailAddress = user.InternalEMailAddress;
Contact contact = contactLogic.LookupPASIndividual("", emailAddress);
if (contact != null)
{ newFrom.Add(new ActivityParty() {PartyId = new EntityReference(Contact.EntityLogicalName, contact.ContactId.Value) });
}
else
return;
}
email.From = newFrom;
Update: So I registered the plug-in on Pre-validation now and it's not triggered when an email activity is created by a router, it IS triggered when a user creates an email in CRM though...
The problem is that you aren't changing the email which is processed at all.
var email = ((Entity)context.InputParameters["Target"]).ToEntity<Email>();
This line converts the record which is currently processed to an object of type email. You modify the record which is not in scope of the operation. You have to modify the From of the target (either directly or write it back).
For the processing stages: take a look at the Event Execution Pipeline. Pre-Validation is to early for your task. I'am not quite sure when the address resolution is done, but I would try to do your conversion Pre-Create.
I ended up using a workaround: created an async Post-Event that associates the email activity with the contact if a contact with the same email exists, leaving the user associated with the email in the "From" field.
How would you model an email app (like gmail) in MongoDB? Would you model a Conversation? Inbox / OutBox? or mail?
Thanks
Gmail use concept of labels (like tags on stackoverflow). That mean that inbox, send mail, starred, etc normal Email object, just marked with specified label. So, there are only Email and Labels.
You can see it using search in gmail like label:inbox or label:Starred.
I'd like to suggest a fairly simple design like this:
Email
{
_id
Title,
Body,
Status {read, unread},
Labels { name, type(system, custom) },
Replies {...},
..
}
Labels
{
_id,
name,
settings {
ShowInLabelsList (show, hide, showIfUnread),
ShowInMessageList (show, hide),
..
}
}
For sure i've missed something, but i guess it's okay to start from above schema and add more features in future if neeed.
Update:
For the 'Conversation View' i guess all replies show go to the nested collection Replies (i've update my schema). Logic is following:
Once you have received a new message you need check if email with same name already exists (for sure need to Remove 'Re', etc..) also need to check that user that has sent email in list of recipients. If above conditions is true then just add new email to nested collection of Replies otherwise add to collection of emails.