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.
I'm making a request to retrieve data from posts with the API, in my listTodo request I want to add sort the data to filter the data according to the date of their creation to put them in an order, but since it's the backend aws-amplify by default, I don't know how to do that.
export const listTodos = /* GraphQL */ `
query ListTodos(
$filter: ModelTodoFilterInput
$limit: Int
$nextToken: String
) {
listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
title
content
email
coverImage
images
page
language
dateAndLocation
createdAt
updatedAt
}
nextToken
}
}
`;
I already try:
add in setting sortDirection: ASC or sortDirection: DESC
add in setting queryField: "todosByDate", sortKeyFields: ["createdAt"]
lots of other try
Can anyone tell me how to sort the data in this query, sort by creation date ?
here is a few possibilities you can explore:
Global secondary index's:
One option is to create an index (global secondary index in dynamodb) on the part that you want to filter. To do that you have to update your graphql model. The model below will give you a query where you can retrieve all todos based on a language, and sort the data based on the createdAt field.
type Todo #model {
id: ID!)
title: String
content: String
email: String
coverImage: String
images: String
page: Int
language: String #index(name: "todosByLanguage", queryField: "todosByLanguage", sortKeyFields: ["createdAt"])
dateAndLocation: String
createdAt: AWSDateTime
updatedAt: AWSDateTime
}
You will then be able to do a query, called TodosByLanguage where you can pass inn the sort direction. This query will require an input for language.
Adding a sort key to your main index: There is also the possibility to give expand your original key (ID field) with a sort key.
type Todo #model {
id: ID! #primaryKey(sortKeyFields: ["createdAt"])
title: String
...
}
I haven't done this approach before so I am not sure exactly how it will work. But you might be able to sort the data on the createdAt field with this. But this changes the core behaviour of your dynamoDB table, for example you might night to pass inn the sort key (createdAt) when you will retrieve one single item, or update items. So make sure you do this in a dev environment before you try.
Elasticsearch / Opensearch
The last solution is to add #searchable to your model. This will stream all your data in the todo table into an elasticsearch (only new ones, existig data won't automatically be streamed over). But be aware, elasticsearch is run on rented servers and can be expensive (depending on your budget).
type Todo #searchable #model {
id: ID!
title: String
...
}
Opensearch is much more flexible in doing queries than dynamoDB. And you will here be able to search and sort almost everything in the way you want it to be queried.
Edit
Elasticsearch is now called Opensearch in aws
I have a schema that looks like this.
exports.typeDefs = gql`
type User {
userid: ID!
name: String
}
type Post {
post_id: ID!
post_category: String!
post_type: String!
post_hashtag: String
user: User
}
`;
Now post have the field named "post_hashtag".
I want to define another schema type and get that post_hashtag property for all the nodes in post type.
of post.
I tried the below type hashtag and put a cipher query on that.
type hashtag{
post_hashtag: String
#cypher[statement: "MATCH (n:Test_Temp) RETURN n.post_hashtag"]
}
But it returns only the one first found hashtag and save it on hashtag node. This is not what I want. I want all the hashtags that are available in any post_hashtag node.
Example: If I query
query{
hashtag{
post_hashtag
}
}
This should give all the hashtags that are available in any of the post node but instead it return only one hashtag.
I've been trying this since few days. going through different solutions but none worked.
Any suggestions Please.
I've figured out the problem. Actually, we just need to use the collect method of neo4j to return an array of hashtags and save it in the hashtag node. And it will save all the hashtags on the node and we can retrieve them later.
First, change the schema a little bit like this.
type hashtag{
post_hashtag: [String]
#cypher[statement: "MATCH (n:Test_Temp) RETURN
n.post_hashtag"]
}
Notice: I've changed the post_hashtag type and made it an array of strings.
Then just the cipher query will change by the one down below.
MATCH (n:Test_Temp) RETURN collect(n.post_hashtag)
And it is done.
What would be the best way to create a response for the user when he is trying to add a new item to an order that is not longer in stock?
Here is my simplified setup:
type Product {
id: ID!
unitsInStock: Int!
}
type OrderItem {
id: ID!
units: Int!
product: Product! #belongsTo
}
type Mutation {
createOrderItem(input: CreateOrderItemInput! #spread): OrderItem #create
}
input ProductBelongsTo {
connect: ID
create: CreateOrderItemInput
}
input CreateOrderItemInput {
units: Int!
product: ProductBelongsTo
}
Basically, before committing the createOrderItem mutation I want to check if the product selected for the order is present in stock, for example, if the user selects too many units of the product he would get a friendly message saying "only 10 items left remaining", or if stock is depleted "this item is no longer in stock, please update stock to continue".
How can I do that?
The best way is to check the count in your front-end section and simply don't allow the user to add more than in stock units, also you need to check in your back-end if the count is eligible, so I recommend using a complex function, sth like this:
createOrderItem(input: CreateOrderItemInput! #spread): OrderItem
#field(resolver: "App\\GraphQL\\OrderItemComplex#create")
App\GraphQL\OrderItemComplex looks like this:
public function create($rootValue, array $args)
{
// simply check the count before creating any record and
// throw an error with the appropriate message if sth is wrong
}
I've reached out on the AWS forums but am hoping to get some attention here with a broader audience. I'm looking for any guidance on the following question.
I'll post the question below:
Hello, thanks in advance for any help.
I'm new to Amplify/GraphQL and am struggling to get mutations working. Specifically, when I add a connection to a Model, they never appear in the mock api generator. If I write them out, they say "input doesn't exist". I've searched around and people seem to say "Create the sub item before the main item and then update the main item" but I don't want that. I have a large form that has several many-to-many relationships and they all need to be valid before I can save the main form. I don't see how I can create every sub item and then the main.
However, the items are listed in the available data for the response. In the example below, addresses, shareholders, boardofdirectors are all missing in the input.
None of the fields with '#connection' appear in the create api as inputs. I'll take any help/guidance I can get. I seem to not be understanding something core here.
Here's my Model:
type Company #model(queries: { get: "getEntity", list: "listEntities" }, subscriptions: null) {
id: ID!
name: String!
president: String
vicePresident: String
secretary: String
treasurer: String
shareholders: Shareholder #connection
boardOfDirectors: BoardMember #connection
addresses: [Address]! #connection
...
}
type Address #model{
id: ID!
line1: String!
line2: String
city: String!
postalCode: String!
state: State!
type: AddressType!
}
type BoardMember #model{
id: ID!
firstName: String!
lastName: String!
email: String!
}
type Shareholder #model {
id: ID!
firstName: String!
lastName: String!
numberOfShares: String!
user: User!
}
----A day later----
I have made some progress, but still lacking some understanding of what's going on.
I have updated the schema to be:
type Company #model(queries: { get: "getEntity", list: "listEntities" }, subscriptions: null) {
id: ID!
name: String!
president: String
vicePresident: String
secretary: String
treasurer: String
...
address: Address #connection
...
}
type Address #model{
id: ID!
line1: String!
line2: String
city: String!
postalCode: String!
state: State!
type: AddressType!
}
I removed the many-to-many relationship that I was attempting and now I'm limited to a company only having 1 address. I guess that's a future problem. However, now in the list of inputs a 'CompanyAddressId' is among the list of inputs. This would indicate that it expects me to save the address before the company. Address is just 1 part of the company and I don't want to save addresses if they aren't valid and some other part of the form fails and the user quits.
I don't get why I can't write out all the fields at once? Going along with the schema above, I'll also have shareholders, boardmembers, etc. So I have to create the list of boardmembers and shareholders before I can create the company? This seems backwards.
Again, any attempt to help me figure out what I'm missing would be appreciated.
Thanks
--Edit--
What I'm seeing in explorer
-- Edit 2--
Here is the newly generated operations based off your example. You'll see that Company takes an address Id now -- which we discussed prior. But it doesn't take anything about the shareholder. In order to write out a shareholder I have to use 'createShareholder' which needs a company Id, but the company hasn't been created yet. Thoroughly confused.
#engam I'm hoping you can help out the new questions. Thank you very much!
Here are some concepts that you can try out:
For the #model directive, try it out without renaming the queries. AWS Amplify gives great names for the automatically generated queries. For example to get a company it will be getCompany and for list it will be listCompanys. If you still want to give it new names, you may change this later.
For the #connection directive:
The #connection needs to be set on both tables of the connection. Also if you want many-to-many connections you need to add a third table that handles the connections. It is also usefull to give the connection a name, when you have many connections in your schema.
Only Scalar types that you have created in the schema, standard schalars like String, Int, Float and Boolean, and AWS specific schalars (like AWSDateTime) can be used as schalars in the schema. Check out this link:
https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html
Here is an example for some of what I think you want to achieve:
type Company #model {
id: ID!
name: String
president: String
vicePresident: String
secretary: String
treasurer: String
shareholders: [Shareholder] #connection(name: "CompanySharholderConnection")
address: Address #connection(name: "CompanyAdressConnection") #one to many example
# you may add more connections/attributes ...
}
# table handling many-to-many connections between users and companies, called Shareholder.
type Shareholder #model {
id: ID!
company: Company #connection(name: "CompanySharholderConnection")
user: User #connection(name: "UserShareholderConnection")
numberOfShares: Int #or String
}
type User #model {
id: ID!
firstname: String
lastname: String
company: [Shareholder] #connection(name: "UserShareholderConnection")
#... add more attributes / connections here
}
# address table, one address may have many companies
type Address #model {
id: ID!
street: String
city: String
code: String
country: String
companies: [Company] #connection(name: "CompanyAdressConnection") #many-to-one connection
}
Each of this type...#model generates a new dynamoDB table. This example will make it possible for u to create multiple companies and multiple users. To add users as shareholders to a company, you only need to create a new item in the Shareholder table, by creating a new item with the ID in of the user from the User table and the ID of the company in the Company table + adding how many shares.
Edit
Be aware that when you generate a connection between two tables, the amplify cli (which uses cloudformation to do backend changes), will generate a new global index to one or more of the dynamodb tables, so that appsync can efficient give you data.
Limitations in dynamodb, makes it only possible to generate one index (#connection) at a time, when you edit a table. I think you can do more at a time when you create a new table (#model). So when you edit one or more of your tables, only remove or add one connection at a time, between each amplify push / amplify publish. Or else cloudformation will fail when you push the changes. And that can be a mess to clean up. I have had to, multiple times, delete a whole environment because of this, luckily not in a production environment.
Update
(I also updated the Address table in the schema with som values);
To connect a new address when you are creating a new company, you will first have to create a new address item in the Address table in dynamoDb.
The mutation for this generated from appsync is probably named createAddress() and takes in a createAddressInput.
After you create the address you will recieve back the whole newly createdItem, including the automatically created ID (if you did not add one yourself).
Now you may save the new company that you are creating. One of the attributes the createCompany mutation takes is the id of the address that you created, probably named as companyAddressId. Store the address Id here. When you then retrieves your company with either getCompany or listCompanys you will get the address of your company.
Javascript example:
const createCompany = async (address, company) => {
// api is name of the service with the mutations and queries
try {
const newaddress = await this.api.createAddress({street: address.street, city: address.city, country: address.country});
const newcompany = await this.api.createCompany({
name: company.name,
president: company.president,
...
companyAddressId: newaddress.id
})
} catch(error) {
throw error
}
}
// and to retrieve the company including the address, you have to update your graphql statement for your query:
const statement = `query ListCompanys($filter: ModelPartiFilterInput, $limit: Int, $nextToken: String) {
listCompanys(filter: $filter, limit: $limit, nextToken: $nextToken) {
__typename
id
name
president
...
address {
__typename
id
street
city
code
country
}
}
}
`
AppSync will now retrive all your company (dependent on your filter and limit) and the addresses of those companies you have connected an address to.
Edit 2
Each type with #model is a referance to a dynamoDb table in aws. So when you are creating a one-to-many relationship between two tables, when both items are new you first have to create the the 'many' in the one-to-many realationships. In the dynamoDb Company tables when an address can have many companies, and one company only can have one address, you have to store the id (dynamoDB primary key) for the address on the company. You could of course generate the address id in frontend, and using that for the id of the address and the same for the addressCompanyId in for the company and use await Promise.all([createAddress(...),createCompany(...)) but then if one fails the other one will be created (but generally appsync api's are very stable, so if the data you send is correct it won't fail).
Another solution, if you generally don't wont to have to create/update multiple items in multiple tables, you could store the address directly in the company item.
type Company #model {
name: String
...
address: Address # or [Address] if you want more than one Address on the company
}
type Address {
street: String
postcode: String
city: string
}
Then the Address type will be part of the same item in the same table in dynamoDb. But you will loose the ability to do queries on addresses (or shareholders) to look up a address and see which companies are located there (or simulary look up a person and see which companies that person has a share in). Generally i don't like this method because it locks your application to one specific thing and it's harder to create new features later on.
As far as I'm aware of, it is not possible to create multiple items in multiple dynamoDb tables in one graphql (Amplify/AppSync) mutation. So async await with Promise.all() and you manually generate the id attributes frontendside before creating the items might be your best option.