export const meta = {
  title: `Overview`,
  description: `Add authorization rules to your GraphQL schema to control access to your data.`,
};

<MigrationAlert url={"/cli-legacy/graphql-transformer/examples"}/>

## Todo

```graphql
type Todo @model @auth(rules: [{allow: owner}]) {
  id: ID!
  name: String!
  description: String
}
```

## Blog
```graphql
type Blog @model @auth(rules: [
  { allow: groups, groups: ["Admins"] },
  { allow: public, operations: [read] }
  ]) {
  id: ID!
  name: String!
  posts: [Post] @hasMany
}
type Post @model @auth(rules: [
  { allow: groups, groups: ["Admins"] },
  { allow: public, operations: [read] }
  ]) {
  id: ID!
  title: String!
  blog: Blog @belongsTo
  comments: [Comment] @hasMany
}
type Comment @model @auth(rules: [
  { allow: groups, groups: ["Admins"], operations: [delete] },
  { allow: owner },
  { allow: public, operations: [read] }
  ]) {
  id: ID!
  content: String
  post: Post @belongsTo
}
```

## Warehouse Management System
In this "Warehouse management system" example, you will learn how to configure common access patterns for your app. This example has the following types:

- Warehouse
- Product
- Inventory
- Employee
- AccountRepresentative
- Customer

These types have the following common access patterns:

1. [Look up employee details by employee ID](#1-look-up-employee-details-by-employee-id)
2. [Query employee details by employee name](#2-query-employee-details-by-employee-name)
3. [Find an employee's phone number(s)](#3-find-an-employees-phone-number)
4. [Find a customer's phone number(s)](#4-find-a-customers-phone-number)
5. [Get orders for a given customer within a given date range](#5-get-orders-for-a-given-customer-within-a-given-date-range)
6. [Show all open orders within a given date range across all customers](#6-show-all-open-orders-within-a-given-date-range-across-all-customers)
7. [See all employees recently hired](#7-see-all-employees-hired-recently)
8. [Find all employees working in a given warehouse](#8-find-all-employees-working-in-a-given-warehouse)
9. [Get all items on order for a given product](#9-get-all-items-on-order-for-a-given-product)
10. [Get current inventories for a given product at all warehouses](#10-get-current-inventories-for-a-product-at-all-warehouses)
11. [Get customers by account representative](#11-get-customers-by-account-representative)
12. [Get orders by account representative and date](#12-get-orders-by-account-representative-and-date)
13. [Get all items on order for a given product](#13-get-all-items-on-order-for-a-given-product)
14. [Get all employees with a given job title](#14-get-all-employees-with-a-given-job-title)
15. [Get inventory by product and warehouse](#15-get-inventory-by-product-by-warehouse)
16. [Get total product inventory](#16-get-total-product-inventory)
17. [Get account representatives ranked by order total and sales period](#17-get-sales-representatives-ranked-by-order-total-and-sales-period)

The following schema introduces the required indexes and relationships so that you can support these access patterns:

```graphql
# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/auth
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!

type Order @model {
  id: ID!
  customerID: ID! @index(name: "byCustomerByStatusByDate", sortKeyFields: ["status", "date"]) @index(name: "byCustomerByDate", sortKeyFields: ["date"])
  accountRepresentativeID: ID! @index(name: "byRepresentativebyDate", sortKeyFields: ["date"])
  productID: ID! @index(name: "byProduct", sortKeyFields: ["id"])
  status: String!
  amount: Int!
  date: String!
}

type Customer @model {
  id: ID!
  name: String!
  phoneNumber: String
  accountRepresentativeID: ID! @index(name: "byRepresentative", sortKeyFields: ["id"])
  ordersByDate: [Order] @hasMany(indexName: "byCustomerByDate", fields: ["id"])
  ordersByStatusDate: [Order] @hasMany(indexName: "byCustomerByStatusByDate", fields: ["id"])
}

type Employee @model {
  id: ID!
  name: String! @index(name: "byName", queryField: "employeeByName", sortKeyFields: ["id"])
  startDate: String!
  phoneNumber: String!
  warehouseID: ID! @index(name: "byWarehouse", sortKeyFields: ["id"])
  jobTitle: String! @index(name: "byTitle", queryField: "employeesByJobTitle", sortKeyFields: ["id"])
  newHire: String! @index(name: "newHire", queryField: "employeesNewHire", sortKeyFields: ["id"]) @index(name: "newHireByStartDate", queryField: "employeesNewHireByStartDate", sortKeyFields: ["startDate"])
}

type Warehouse @model {
  id: ID!
  employees: [Employee] @hasMany(indexName: "byWarehouse", fields: ["id"])
}

type AccountRepresentative @model {
  id: ID!
  customers: [Customer] @hasMany(indexName: "byRepresentative", fields: ["id"])
  orders: [Order] @hasMany(indexName: "byRepresentativebyDate", fields: ["id"])
  orderTotal: Int
  salesPeriod: String @index(name: "bySalesPeriodByOrderTotal", queryField: "repsByPeriodAndTotal", sortKeyFields: ["orderTotal"])
}

type Inventory @model {
  productID: ID! @primaryKey(sortKeyFields: ["warehouseID"])
  warehouseID: ID! @index(name: "byWarehouseID", queryField: "itemsByWarehouseID")
  inventoryAmount: Int!
}

type Product @model {
  id: ID!
  name: String!
  orders: [Order] @hasMany(indexName: "byProduct", fields: ["id"])
  inventories: [Inventory] @hasMany(fields: ["id"])
}
```

Now that you have the schema created, let's create the items in the database that you will be operating against:

```graphql
# first
mutation createWarehouse {
  createWarehouse(input: {id: "1"}) {
    id
  }
}

# second
mutation createEmployee {
  createEmployee(input: {
    id: "amanda"
    name: "Amanda",
    startDate: "2018-05-22",
    phoneNumber: "6015555555",
    warehouseID: "1",
    jobTitle: "Manager",
    newHire: "true"}
  ) {
    id
    jobTitle
    name
    newHire
    phoneNumber
    startDate
    warehouseID
  }
}

# third
mutation createAccountRepresentative {
  createAccountRepresentative(input: {
    id: "dabit"
    orderTotal: 400000
    salesPeriod: "January 2019"
  }) {
    id
    orderTotal
    salesPeriod
  }
}

# fourth
mutation createCustomer {
  createCustomer(input: {
    id: "jennifer_thomas"
    accountRepresentativeID: "dabit"
    name: "Jennifer Thomas"
    phoneNumber: "+16015555555"
  }) {
    id
    name
    accountRepresentativeID
    phoneNumber
  }
}

# fifth
mutation createProduct {
  createProduct(input: {
    id: "yeezyboost"
    name: "Yeezy Boost"
  }) {
    id
    name
  }
}

# sixth
mutation createInventory {
  createInventory(input: {
    productID: "yeezyboost"
    warehouseID: "1"
    inventoryAmount: 300
  }) {
    productID
    inventoryAmount
    warehouseID
  }
}

# seventh
mutation createOrder {
  createOrder(input: {
    amount: 300
    date: "2018-07-12"
    status: "pending"
    accountRepresentativeID: "dabit"
    customerID: "jennifer_thomas"
    productID: "yeezyboost"
  }) {
    id
    customerID
    accountRepresentativeID
    amount
    date
    customerID
    productID
  }
}
```

### 1. Look up employee details by employee ID

This can simply be done by querying the employee model with an employee ID, no `@primaryKey` or `@index` need to be explicitly specified to make this work.

```graphql
query getEmployee($id: ID!) {
  getEmployee(id: $id) {
    id
    name
    phoneNumber
    startDate
    jobTitle
  }
}
```

### 2. Query employee details by employee name

The `@index` `byName` on the `Employee` type makes this access-pattern feasible because under the hood an index is created and a query is used to match against the name field. You can use this query:

```graphql
query employeeByName($name: String!) {
  employeeByName(name: $name) {
    items {
      id
      name
      phoneNumber
      startDate
      jobTitle
    }
  }
}
```

### 3. Find an Employee’s phone number

Either one of the previous queries would work to find an employee’s phone number as long as one has their ID or name.

### 4. Find a customer’s phone number

A similar query to those given above but on the Customer model would give you a customer’s phone number.

```graphql
query getCustomer($customerID: ID!) {
  getCustomer(id: $customerID) {
    phoneNumber
  }
}
```

### 5. Get orders for a given customer within a given date range

There is a one-to-many relation that lets all the orders of a customer be queried.

This relationship is created by having the `@index` name `byCustomerByDate` on the Order model that is queried by the `@hasMany` relationship on the orders field of the Customer model.

A sort key with the date is used. What this means is that the GraphQL resolver can use predicates like `Between` to efficiently search the date range rather than scanning all records in the database and then filtering them out.

The query one would need to get the orders to a customer within a date range would be:

```graphql
query getCustomerWithOrdersByDate($customerID: ID!) {
  getCustomer(id: $customerID) {
    ordersByDate(date: {
      between: [ "2018-01-22", "2020-10-11" ]
    }) {
      items {
        id
        amount
        productID
      }
    }
  }
}
```

### 6. Show all open orders within a given date range across all customers

The `@index` `byCustomerByStatusByDate` enables you to run a query that would work for this access pattern.

In this example, a composite sort key (combination of two or more keys) with the `status` and `date` is used. What this means is that the unique identifier of a record in the database is created by concatenating these two fields (status and date) together, and then the GraphQL resolver can use predicates like `between` or `contains` to efficiently search the unique identifier for matches rather than scanning all records in the database and then filtering them out.

```graphql
query listCustomersWithOrdersByStatusDate {
  listCustomers {
    items {
      ordersByStatusDate(statusDate: {
        between: [
          { status: "pending", date: "2018-01-22" },
          { status: "pending", date: "2020-10-11" }
        ]}) {
        items {
          id
          amount
          date
        }
      }
    }
  }
}
```

### 7. See all employees hired recently

Having `@index(name: "newHire", fields: ["newHire", "id"])` on the `Employee` model allows one to query by whether an employee has been hired recently.

```graphql
query employeesNewHire {
  employeesNewHire(newHire: "true") {
    items {
      id
      name
      phoneNumber
      startDate
      jobTitle
    }
  }
}
```

You can also query and have the results returned by start date by using the `employeesNewHireByStartDate` query:

```graphql
query employeesNewHireByDate {
  employeesNewHireByStartDate(newHire: "true") {
    items {
      id
      name
      phoneNumber
      startDate
      jobTitle
    }
  }
}
```

### 8. Find all employees working in a given warehouse

This needs a one to many relationship from warehouses to employees. As can be seen from the `@hasMany` relationship in the `Warehouse` model, this relationship uses the `byWarehouse` index on the `Employee` model. The relevant query would look like this:

```graphql
query getWarehouse($warehouseID: ID!) {
  getWarehouse(id: $warehouseID) {
    id
    employees{
      items {
        id
        name
        startDate
        phoneNumber
        jobTitle
      }
    }
  }
}
```

### 9. Get all items on order for a given product

This access-pattern would use a one-to-many relation from products to orders. With this query you can get all orders of a given product:

```graphql
query getProductOrders($productID: ID!) {
  getProduct(id: $productID) {
    id
    orders {
      items {
        id
        status
        amount
        date
      }
    }
  }
}
```

### 10. Get current inventories for a product at all warehouses

The query needed to get the inventories of a product in all warehouses would be:

```graphql
query getProductInventoryInfo($productID: ID!) {
  getProduct(id: $productID) {
    id
    inventories {
      items {
        warehouseID
        inventoryAmount
      }
    }
  }
}
```

### 11. Get customers by account representative

This uses a has-many relationship between account representatives and customers:

The query needed would look like this:

```graphql
query getCustomersForAccountRepresentative($representativeId: ID!) {
  getAccountRepresentative(id: $representativeId) {
    customers {
      items {
        id
        name
        phoneNumber
      }
    }
  }
}
```

### 12. Get orders by account representative and date

As can be seen in the AccountRepresentative model this relationship uses the `byRepresentativebyDate` field on the `Order` model to create the connection needed. The query needed would look like this:

```graphql
query getOrdersForAccountRepresentative($representativeId: ID!) {
  getAccountRepresentative(id: $representativeId) {
    id
    orders(date: {
      between: [
         "2010-01-22", "2020-10-11"
      ]
    }) {
        items {
          id
          status
          amount
          date
        }
    }
  }
}
```

### 13. Get all items on order for a given product

This is the same as number 9.

### 14. Get all employees with a given job title

Using the `byTitle` `@index` makes this access pattern quite easy.

```graphql
query employeesByJobTitle {
  employeesByJobTitle(jobTitle: "Manager") {
    items {
      id
      name
      phoneNumber
      jobTitle
    }
  }
}
```

### 15. Get inventory by product by warehouse

Here having the inventories be held in a separate model is particularly useful since this model can have its own partition key and sort key such that the inventories themselves can be queried as is needed for this access-pattern.

A query on this model would look like this:

```graphql
query inventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) {
  getInventory(productID: $productID, warehouseID: $warehouseID) {
    productID
    warehouseID
    inventoryAmount
  }
}

```

You can also get all inventory from an individual warehouse by using the `itemsByWarehouseID` query created by the `byWarehouseID` key:

```graphql
query byWarehouseId($warehouseID: ID!) {
  itemsByWarehouseID(warehouseID: $warehouseID) {
    items {
      inventoryAmount
      productID
    }
  }
}
```

### 16. Get total product inventory

How this would be done depends on the use case. If one just wants a list of all inventories in all warehouses, one could just run a list inventories on the Inventory model:

```graphql
query listInventorys {
  listInventorys {
    items {
      productID
      warehouseID
      inventoryAmount
    }
  }
}
```

### 17. Get sales representatives ranked by order total and sales period

The sales period is either a date range or maybe even a month or week. Therefore you can set the sales period as a string and query using the combination of `salesPeriod` and `orderTotal`. You can also set the `sortDirection` in order to get the return values from largest to smallest:

```graphql
query repsByPeriodAndTotal {
  repsByPeriodAndTotal(
    sortDirection: DESC,
    salesPeriod: "January 2019",
    orderTotal: {
      ge: 1000
    }) {
    items {
      id
      orderTotal
    }
  }
}
```

## Advanced

### Batch Put Custom Resolver

Sometimes you need to create objects in bulk, rather than creating individual objects sequentially and waiting for all the requests to complete.

1. Define your schema with a custom mutation. The custom mutation should not be deployed to AppSync beforehand if following these steps, the CLI will attach its own resolver preventing you from attaching a custom resource this way.
```graphql
type Todo @model {
  id: ID!
  name: String!
  description: String
}

type Mutation {
  batchCreateTodo(todos: [BatchCreateTodo]): [Todo]
}

input BatchCreateTodo {
  id: ID
  name: String!
  description: String
}
```

2. [Create a custom resource for your resolver](/cli/graphql/custom-business-logic/#vtl-resolver) and use the following code snippets as a guide to get started

2. Follow the steps for creating a custom resolver:
```bash
amplify add custom
```
```console
? How do you want to define this custom resource?
❯ AWS CDK
? Provide a name for your custom resource
❯ MyCustomResolvers
```

Next, install the AppSync dependencies for your custom resource:
```bash
cd amplify/backend/custom/MyCustomResolvers
npm i @aws-cdk/aws-appsync@~1.124.0
```

Use the following template as a starting point for your custom CDK stack, the resolvers must be templated with environment references

```ts
import * as cdk from '@aws-cdk/core';
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
import * as appsync from '@aws-cdk/aws-appsync';
import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref';

export class cdkStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) {
    super(scope, id, props);
    /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
    new cdk.CfnParameter(this, 'env', {
      type: 'String',
      description: 'Current Amplify CLI env name',
    });
    
    // Access other Amplify Resources 
    const retVal:AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this, 
      amplifyResourceProps.category, 
      amplifyResourceProps.resourceName, 
      [{
        category: "api",
        resourceName: "<YOUR-API-NAME>"
      }]
    );

    const requestVTL = `
      ## [Start] Initialization default values. **
      $util.qr($ctx.stash.put("defaultValues", $util.defaultIfNull($ctx.stash.defaultValues, {})))
      #set( $createdAt = $util.time.nowISO8601() )
      #set($todosArray = [])
      #foreach($item in \${ctx.args.todos})
        $util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId())))
        $util.qr($item.put("createdAt", $util.defaultIfNull($item.createdAt, $createdAt)))
        $util.qr($item.put("updatedAt", $util.defaultIfNull($item.updatedAt, $createdAt)))
        $util.qr($item.put("__typename", "Todo"))
        $util.qr($todosArray.add($util.dynamodb.toMapValues($item)))
      #end
      ## [End] Initialization default values. **
      $util.toJson( {
        "version": "2018-05-29",
        "operation": "BatchPutItem",
        "tables": {
          "<TYPE-NAME-HERE>-${apiIdRef}-${envRef}": $todosArray
        }
      } )
    `
    const responseVTL = `
      ## [Start] ResponseTemplate. **
      #if( $ctx.error )
        $util.error($ctx.error.message, $ctx.error.type)
      #else
        $util.toJson($ctx.result.data.<TYPE-NAME-HERE>-${apiIdRef}-${envRef})
      #end
      ## [End] ResponseTemplate. **
    `;


    const resolver = new appsync.CfnResolver(this, "custom-resolver", {
      // apiId: retVal.api.new.GraphQLAPIIdOutput,
      // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887
      // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack.
      // Previously the ApiId is the variable Name which is wrong , it should be variable value as below
      apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput),
      fieldName: "querySomething", 
      typeName: "Query", // Query | Mutation | Subscription
      requestMappingTemplate: requestVTL,
      responseMappingTemplate: responseVTL,
      dataSourceName: "TodoTable" // DataSource name
    })
  }
}
```

By using CloudFormation parameters, you contextualize your custom resolvers to the environment you're working with.

3. Run `amplify push` and deploy your API

The full documentation for custom resolvers [is available here](https://docs.amplify.aws/cli/graphql/custom-business-logic/#vtl-resolver)
