I stumbled on GitHub GraphQL today and at first glance, it seemed to me I could do more with it than I already do with their regular RESTful API. I created some Adapters in my existing application and managed to authenticate Users based on a Personal Access Token without much trouble.
Going further, I tried then list Followers, Followees and Blocked Users:
query GetFollowers {
user(login: "username") {
followers(first: 100) {
nodes {
login
name
avatarUrl
}
}
}
}
Obviously, replacing username with a real GitHub username
However, I couldn't find a way to list the Blocked Users, which is something I can do with the RESTful API. Reading the (extensive) documentation, the closest I could build was:
query GetBlockedUsers {
node(id: "node_id") {
... on UserBlockedEvent {
subject {
avatarUrl
login
name
}
}
}
}
This not only didn't work — or, at least, didn't yield any results — but would also require an extra step when requesting the endpoint, as I would first need to retrieve the node_id of a given username:
Yes, there are some Users blocked on my list :Þ
query GetUserID {
user(login: "username") {
id
}
}
Is it possible to list/add/remove Users to the Blocked List with their GraphQL API? Preferably, but not required, in one single query, filtering by username instead of this node_id
Thank you for your time :D
Related
For example, if there are two types User and Item
type User {
items: [Item!]!
}
type Item {
id: ID!
name: String!
price: Int!
}
If one user has PARTNER role.
I want to prevent it from being called only in the form of the query below.
query Query1 {
user {
items {
name
}
}
}
If user call another query, I want to indicate that user doesn't have permission.
query Query2 {
user {
items {
id
name
}
}
}
In short. if (Query1 != Query2) throw new Error;
Your question is a bit hard to follow but a couple things:
A GraphQL server is stateless - you cannot (and really should not) have a query behave differently based on a previous query. (If there's a mutation in between sure but not two queries back to back)
access management is normally implemented in your resolvers. You can have the resolver for the item id check to see if the user making the query has the right to see that or not and return an error if they don't have access.
Note that it can be bad practice to hide the id of objects from queries as these are used as keys for caching on the client.
Imagine the condition that I have a query called "users" that returns all the users and these users can be associated with one or more companies, so I have a type UserCompanies (I need it because it saves some more information beyond the relation). I'm using Prisma and I need to force a filter that returns only users that are of the same company as the requester.
I get the information of the company from JWT and need to inject this to the query before sending it to Prisma.
So, query should be like that:
query allUsers {
users {
name
id
status
email
userCompanies{
id
role
}
}
}
and on server side, I should transform it to: (user where is ok, just changing args)
query allUsers {
users(where: {
userCompanies_some: {
companyId: "companyId-from-jwt"
}
}) {
name
id
status
email
userCompanies(where: {
companyId: "companyId-from-jwt"
}){
id
role
}
}
}
I'm seeing a few resolutions to this, but I don't know if it is the best way:
1 - Using addFragmentToInfo, does the job to put conditions on the query, but if the query has a usercompanies already set, it gives me a conflict. Otherwise, it works fine.
2 - I can use an alias for the query, but after DB result I will need to edit all the results in array to overwrite the result.
3 - don't use info on Prisma and filter in js.
4 - Edit info(4th parameter) of type GraphqlResolveInfo
I am specifically using the shopify graphql admin api to query orders.
I want to do a search for a nested related field.
Below is my query.
export const orderHistoryQuery = gql`
query Order($productsFirst: Int!, $productsAfter: String, $filterQuery: String) {
orders(first: $productsFirst, after: $productsAfter, reverse: true, query:$filterQuery) {
edges {
cursor
node {
id
name
customer {
id
metafields(first: 10) {
edges {
node {
id
key
value
namespace
}
cursor
}
}
}
totalPriceSet {
shopMoney {
amount
currencyCode
}
}
subtotalPriceSet {
shopMoney {
amount
currencyCode
}
}
totalRefundedSet {
shopMoney {
amount
currencyCode
}
}
currencyCode
email
phone
processedAt
totalShippingPriceSet {
shopMoney {
amount
currencyCode
}
}
totalTaxSet {
shopMoney {
amount
currencyCode
}
}
shippingAddress {
firstName
lastName
address1
address2
city
province
zip
country
}
billingAddress {
firstName
lastName
address1
address2
city
province
zip
country
}
customAttributes {
key
value
}
}
}
}
}
`;
I want to query metafields or ANYTHING really but it doesn't seem like it's supported. I am not sure if I just have the wrong query syntax or if it's not supported. The shopify search syntax documenation doesn't really help and this is where my knowledge of graphql falls apart.
Is it possible to do this in graphql? I also tried adding metafields(id: $whateverID) which is not supported by their setup.
Unfortunately, Shopify doesn't support query filters on metafields. The best way to figure this out is by using a graphql explorer like GraphiQL. Shopify dashboard has this built in if you go to Apps > Shopify GraphiQL App.
Using GraphiQL you can see that:
Customers query doesn't have metafields supported:
Orders query doesn't have customers or metafields supported:
And metafields on customers doesn't have a query param:
I think your options are to either query by what you can and filter after you get the results or use a customer tag and query by tag.
You would really help your cause out by simplifying things. My advice to you is to try a simple query. Can you get an order? Since an order has a customer (usually but not always), can you get a metafield associated with that customer?
You have so many obstacles in your attempt to show what you are trying to do, it is almost as if you want a migraine headache in trying to debug anything. GraphQL calls to endpoints are documented fairly well from the GraphQL website perspective, and Shopify is nothing but a vanilla implementation of that, with the caveat that they charge you for calls based on complexity, so you had best be monitoring your credits.
So ya, try simple calls. Get a product and it's Metafields. Get a customer record and it's Metafields. If you can do that, you are not challenging the documentation much, nor the concept of GraphQL queries. Once a basic all works, you can work in variables, cursors, paging, etc... but until a one-off call gives you what you want, debugging should be concentrated on the simplest of calls, not everything and the kitchen sink.
Also, when you screw up a call to the endpoint, Shopify usually returns a response with details about where you screwed up, providing you with a first place to look. We see nothing of your response, so there is little to go on to help you.
I am aware that it would be considered as an anti-pattern, but why exactly?
mutation {
createUser(name: "john doe") {
addToTeam(teamID: "123") {
name,
id
},
id
}
}
Wouldn't it be more convenient than two HTTP calls?
mutation {
createUser(name: "john doe") {
id, # we store the ID
}
}
mutation {
addToTeam(userID: id, teamID: "123") {
name,
id,
}
}
If you have a relation between Team and User, you could expose this API:
Create user, relate to existing team
mutation {
createUser(name: "john doe", teamId: "team-id") {
id
team {
id
}
}
}
Create new user and new team
mutation {
createUser(name: "john doe", team: {name: "New team"}) {
id
team {
id
}
}
}
This is exactly how the Graphcool API handles this as shown in this blog article. You can find another example in the documentation.
There are two reasons why this is an anti-pattern:
First, there are two atomic operations here, each may involve some extra logic related to authentication, validation, and yield different errors. So mixing them together could lead to some extra complexity.
For example, say a team can only have 10 people, and it has reached its max. Should the compose operation fail altogether? Shall we just add the user but not add it to the team? What the response will look like?
Second, lumping two operations in such way may potentially expose application logic. One can be tempted to use such mutations to perform 'When X happens Y should also happen as well'. For instance, when adding a new line to an invoice, the total should update. This should really happen with one mutation, addLineToInvoice, and have the logic reside on the server.
In a way, the command part of APIs is better being process (or action) centric, rather than data centric. If your API calls are focused on data manipulation, you are risking loading the client with business logic that should live in the server. You may also be losing on quite a few goodies like middleware (which is great for cross-cutting concerns, like permissions and logging).
I need to do what is essentially an upsert. What's the efficient way of checking for an existing user and doing one mutation vs another. In my case I need to do a signin if the user exists, otherwise I should call create before signing in. I am using the graph.cool graphql service. I can do it as 2 seperate calls, but can is there a way I can write it in graphql so that it's done in one call and wouldn't require a 2nd roundtrip? Note that I don't have control of the backend and can only use the functions that exist.
https://docs.graph.cool/reference/simple-api/user-authentication
mutation {
// Only create a user if they do not exist already
// is there a way to do a conditional statement in graphql here?
createUser(authProvider: { auth0: { idToken: "<idToken>" }}) {
id
}
// always try signing the user in with the token we already got from auth0
signinUser(input: { auth0: { idToken: "<idToken>" }}) {
id
token
}
}
I think your question boils down to "how can I return either a token or an id depending on what the back end decided to do?"
The answer is: a union type.
union authResult = id | token
mutation {
authenticateUser(authProvider: { auth0: { idToken: "<idToken>" }}) {
authResult
}
}
Now you have delated the decision of "signin or create?" to the back end, and you get to do it in one trip and find out the result.
Note that because you want to do it in one trip, you must provide all the necessary information in one go - IE you have to provide enough info for the createUser step even if the back end doesn't do the create because the user already exists.