├── .gitignore ├── .graphqlconfig.yml ├── README.md ├── amplify ├── .config │ └── project-config.json └── backend │ ├── analytics │ └── reactnotes │ │ ├── parameters.json │ │ └── pinpoint-cloudformation-template.json │ ├── api │ └── reactnotes │ │ ├── build │ │ ├── resolvers │ │ │ ├── Mutation.createNote.request │ │ │ ├── Mutation.createNote.response │ │ │ ├── Mutation.deleteNote.request │ │ │ ├── Mutation.deleteNote.response │ │ │ ├── Mutation.updateNote.request │ │ │ ├── Mutation.updateNote.response │ │ │ ├── Query.getNote.request │ │ │ ├── Query.getNote.response │ │ │ ├── Query.listNotes.request │ │ │ └── Query.listNotes.response │ │ └── schema.graphql │ │ ├── cloudformation-template.json │ │ ├── parameters.json │ │ └── schema.graphql │ ├── auth │ └── reactnotescognitoauth │ │ ├── parameters.json │ │ └── reactnotescognitoauth-cloudformation-template.yml │ ├── awscloudformation │ └── nested-cloudformation-stack.yml │ └── backend-config.json ├── auth.jpg ├── hero.jpg ├── notesapp.jpg ├── package.json ├── preview.png ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── App.test.js ├── components │ ├── Form.js │ ├── Note.js │ └── Notes.js ├── graphql │ ├── mutations.js │ ├── queries.js │ ├── schema.json │ └── subscriptions.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | #amplify 26 | amplify/\#current-cloud-backend 27 | amplify/.config/local-* 28 | amplify/backend/amplify-meta.json 29 | aws-exports.js 30 | awsconfiguration.json -------------------------------------------------------------------------------- /.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | reactnotes: 3 | schemaPath: src/graphql/schema.json 4 | includes: 5 | - src/graphql/**/*.js 6 | excludes: 7 | - ./amplify/** 8 | extensions: 9 | amplify: 10 | codeGenTarget: javascript 11 | generatedFileName: '' 12 | docsFilePath: src/graphql 13 | graphQLApiId: v5w4eznaunb6pnuk7ksobutj3m 14 | endpoints: 15 | prod: >- 16 | https://xnxvltjqjzeajlmaq7qc7aas4i.appsync-api.eu-central-1.amazonaws.com/graphql 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building a ‘Notes’ app with Amplify Framework and React 2 | 3 | ### Overview 4 | In this tutorial, you will create a React 'Notes’ app that connects to a serverless backend via the Amplify Framework. The app will provide user authentication with Amazon Cognito, in-app analytics with Amazon Pinpoint, and it will be connected to a serverless GraphQL backend with AWS AppSync. 5 | 6 | ![](hero.jpg) 7 | 8 | The Amplify Framework enables frontend developers to build apps quickly with a simplified workflow. In this tutorial, you will learn how to create a cloud-enabled React app from scratch using the Amplify Framework. You can deploy this fullstack application to the Amplify Console by clicking the button below. 9 | 10 | [![amplifybutton](https://oneclick.amplifyapp.com/button.svg)](https://console.aws.amazon.com/amplify/home#/deploy?repo=https://github.com/username/repository) 11 | 12 | #### By completing this tutorial, you will be able to: 13 | 14 | - Bootstrap a React app with Create React App CLI 15 | - Install and configure the Amplify Framework in your app 16 | - Manage your app’s backend with the Amplify CLI 17 | - Add cloud-based authentication to your app 18 | - Create and use a GraphQL backend to store data 19 | 20 | ### Prerequisites 21 | You need to have a basic understanding of JavaScript, Node.js, and NPM to complete this tutorial. 22 | 23 | ### Source Code 24 | You can directly copy and paste code snippets from the tutorial as you are following the steps. Alternatively, if you would like to get right to the source code for the final version of the tutorial’s code, please visit the Github Repo. 25 | 26 | ## Content 27 | Here is the sequence of the tutorial: 28 | 29 | [Part 1: Creating the React App](https://github.com/dabit3/react-notes#part-1-create-a-react-app) 30 | [Part 2: Adding Cloud Features](https://github.com/dabit3/react-notes#part-2-adding-cloud-features) 31 | [Part 3: Enabling GraphQL Backend](https://github.com/dabit3/react-notes#part-3-enabling-graphql-backend) 32 | 33 | # Part 1: Create a React App 34 | 35 | This section introduces React basics. You will learn how to bootstrap a new React app with the Create React App CLI. In subsequent parts of the tutorial, you will gradually add new cloud functionality to your application. 36 | 37 | > If you want to integrate Amplify Framework into an existing React application, you can skip Part 1 and start directly to Part 2. 38 | 39 | ### Install the Create React App CLI and Create a New Project 40 | The easiest way to create an React application is with the Create React App Command Line Interface (CLI). To install the CLI, run the following command in your terminal: 41 | 42 | ```sh 43 | $ npm install -g create-react-app 44 | ``` 45 | 46 | After the installation, go to a location where you wish to start your new application project and execute the following command to create a new React app: 47 | 48 | ```sh 49 | $ create-react-app amplify-notes 50 | ``` 51 | 52 | ### Install Amplify Framework Libraries 53 | 54 | Now, you have a new React project called react-notes! You can switch to your project’s root folder and install required Amplify Framework libraries. 55 | 56 | ```sh 57 | $ cd react-notes 58 | ``` 59 | 60 | Amplify Framework provides npm packages for installation. aws-amplify is the core package and aws-amplify-react includes UI components and React modules that you will use when building you React app. 61 | 62 | Run the following commands to install required packages: 63 | 64 | ```sh 65 | $ npm install aws-amplify aws-amplify-react 66 | ``` 67 | 68 | You have installed the required Amplify packages in this step. In Part 2, you will use those packages to cloud-enable your React app. 69 | 70 | 71 | #### Testing your React App 72 | 73 | You can test your React app anytime in a browser by running: 74 | 75 | ```sh 76 | $ npm start 77 | ``` 78 | 79 | ## Creating Components 80 | 81 | In this section, we'll create the components that we will use in our React app. These components will at first use local state to manage data. Later on, we'll update our application to work with data from the cloud by adding an AppSync GraphQL API. 82 | 83 | ![](notesapp.jpg) 84 | 85 | The next thing we'll need to do is install a couple of additional dependencies (Glamor for styling & React Icons for Icons): 86 | 87 | ```sh 88 | npm install react-icons glamor 89 | ``` 90 | 91 | Next, we'll need to create a few new files to hold our components. 92 | 93 | In the `src` directory, create a `components` directory with three new components: __Form.js__, __Note.js__, & __Notes.js__: 94 | 95 | ```sh 96 | mkdir components 97 | touch components/Form.js components/Note.js components/Notes.js 98 | ``` 99 | 100 | Next, we'll update App.js. App.js will hold most of the logic for creating, updating & deleting items in our app. 101 | 102 | First, let's import everything we'll need for the component: 103 | 104 | ```js 105 | // src/App.js 106 | 107 | import { css } from 'glamor' 108 | 109 | import Form from './components/Form' 110 | import Notes from './components/Notes' 111 | ``` 112 | 113 | Next, in our class, we'll define the initial state: 114 | 115 | ```js 116 | state = { notes: [], filter: 'none' } 117 | ``` 118 | 119 | - The `notes` array will hold the notes we will be rendering in our app. 120 | - The `filter` value will hold the current filter for the type notes that we are viewing (all notes, completed notes, or pending notes). 121 | 122 | Next, we'll define the methods we'll be needing: 123 | 124 | ```js 125 | createNote = async note => { 126 | const notes = [note, ...this.state.notes] 127 | const newNotes = this.state.notes 128 | this.setState({ notes }) 129 | } 130 | 131 | updateNote = async note => { 132 | const updatedNote = { 133 | ...note, 134 | status: note.status === 'new' ? 'completed' : 'new' 135 | } 136 | const index = this.state.notes.findIndex(i => i.id === note.id) 137 | const notes = [...this.state.notes] 138 | notes[index] = updatedNote 139 | this.setState({ notes }) 140 | } 141 | 142 | deleteNote = async note => { 143 | const input = { id: note.id } 144 | const notes = this.state.notes.filter(n => n.id !== note.id) 145 | this.setState({ notes }) 146 | } 147 | 148 | updateFilter = filter => this.setState({ filter }) 149 | ``` 150 | 151 | - `createNote` - This method creates a new note in the state. 152 | 153 | - `updateNote` - This method updates the `status` of the note to be either __completed__ or __new__. 154 | 155 | - `deleteNote` - This method deletes the note from the state. 156 | 157 | - `updateFilter` - This method changes the filter type that we are currently viewing. 158 | 159 | Next, we'll update the render method to the following: 160 | 161 | ```js 162 | render() { 163 | let { notes, filter } = this.state 164 | if (filter === 'completed') { 165 | notes = notes.filter(n => n.status === 'completed') 166 | } 167 | if (filter === 'new') { 168 | notes = notes.filter(n => n.status === 'new') 169 | } 170 | return ( 171 |
172 |

Notes

173 |
176 | 181 |
182 |

this.updateFilter('none')} 184 | {...css([ styles.menuItem, getStyle('none', filter)])} 185 | >All

186 |

this.updateFilter('completed')} 188 | {...css([styles.menuItem, getStyle('completed', filter)])} 189 | >Completed

190 |

this.updateFilter('new')} 192 | {...css([styles.menuItem, getStyle('new', filter)])} 193 | >Pending

194 |
195 |
196 | ); 197 | } 198 | ``` 199 | 200 | - We first destructure the `notes` array & the `filter` value from the state. 201 | - Next, we apply the filter on the notes array if there is a filter that matches either `completed` or `new`. 202 | - We return the __Form__ & __Notes__ components as well as some UI to apply the filter. 203 | 204 | Finally, we have a few styles that we create to style our UI: 205 | 206 | ```js 207 | const styles = { 208 | container: { 209 | width: 360, 210 | margin: '0 auto', 211 | borderBottom: '1px solid #ededed', 212 | }, 213 | form: { 214 | display: 'flex', 215 | justifyContent: 'center', 216 | alignItems: 'center' 217 | }, 218 | input: { 219 | height: 35, 220 | width: '360px', 221 | border: 'none', 222 | outline: 'none', 223 | marginLeft: 10, 224 | fontSize: 20, 225 | padding: 8, 226 | } 227 | } 228 | ``` 229 | 230 | Now, let's take a look at the __Note__ component (components/Note.js): 231 | 232 | ```js 233 | // src/components/Note.js 234 | 235 | import React from 'react' 236 | import { css } from 'glamor' 237 | import { FaTimes, FaCircle } from 'react-icons/fa' 238 | import { MdCheckCircle } from 'react-icons/md'; 239 | 240 | class Note extends React.Component { 241 | render() { 242 | const { name, status } = this.props.note 243 | return ( 244 |
245 | { 246 | status === 'new' && ( 247 | this.props.updateNote(this.props.note)} 252 | /> 253 | ) 254 | } 255 | { 256 | status === 'completed' && ( 257 | this.props.updateNote(this.props.note)} 262 | /> 263 | ) 264 | } 265 |

{name}

266 |
267 | this.props.deleteNote(this.props.note)} 269 | color='red' 270 | size={22} 271 | {...css(styles.times)} 272 | /> 273 |
274 |
275 | ) 276 | } 277 | } 278 | 279 | const styles = { 280 | container: { 281 | borderBottom: '1px solid rgba(0, 0, 0, .15)', 282 | display: 'flex', 283 | alignItems: 'center' 284 | }, 285 | name: { 286 | textAlign: 'left', 287 | fontSize: 18 288 | }, 289 | iconContainer: { 290 | display: 'flex', 291 | flex: 1, 292 | justifyContent: 'flex-end', 293 | alignItems: 'center' 294 | }, 295 | new: { 296 | marginRight: 10, 297 | cursor: 'pointer', 298 | opacity: .3 299 | }, 300 | completed: { 301 | marginRight: 10, 302 | cursor: 'pointer' 303 | }, 304 | times: { 305 | cursor: 'pointer', 306 | opacity: 0.7 307 | } 308 | } 309 | 310 | export default Note 311 | ``` 312 | 313 | This component is pretty basic: we return the note `name` as well as some UI for updating & deleting the note. 314 | 315 | If the note is `new`, we show an empty circle next to the note. 316 | 317 | If the note is `completed`, we show a circle with a checkmark next to it. If the circle is clicked, we call the `updateNote` method passed down as props & toggle the `completed` state.. 318 | 319 | We also have a red __x__ that deletes the note when clicked by calling the `deleteNote` method passed down as props. 320 | 321 | Next, let's take a look at __Notes__ component (Notes.js): 322 | 323 | ```js 324 | // src/components/Notes.js 325 | 326 | import React from 'react' 327 | import { css } from 'glamor' 328 | 329 | import Note from './Note' 330 | 331 | class Notes extends React.Component { 332 | render() { 333 | return ( 334 |
335 | { 336 | this.props.notes.map((t, i) => ( 337 | 343 | )) 344 | } 345 |
346 | ) 347 | } 348 | } 349 | 350 | const styles = { 351 | container: { 352 | width: '360px', 353 | margin: '0 auto', 354 | '@media(max-width: 360px)': { 355 | width: 'calc(100% - 40px)' 356 | } 357 | } 358 | } 359 | 360 | export default Notes 361 | ``` 362 | 363 | In this component, we map over all of the notes (passed in as `this.props.notes`), & render a __Note__ component for each item in the array. 364 | 365 | We pass in the `deleteNote` & `updateNote` methods as props to each __Note__ component along with the note. 366 | 367 | Finally, let's take a look at the __Form__ component (components/Form.js): 368 | 369 | ```js 370 | // src/components/Form.js 371 | 372 | import React from 'react' 373 | import { css } from 'glamor' 374 | import { MdAdd } from 'react-icons/md' 375 | 376 | class Form extends React.Component { 377 | state = { name: '' } 378 | onChange = e => { 379 | this.setState({ name: e.target.value }) 380 | } 381 | handleKeyPress = (e) => { 382 | if (e.key === 'Enter' && this.state.name !== '') { 383 | const note = { 384 | ...this.state, status: 'new' 385 | } 386 | this.props.createNote(note) 387 | this.setState({ name: '' }) 388 | } 389 | } 390 | render() { 391 | return ( 392 |
393 |
394 | 395 | this.onChange(e)} 400 | value={this.state.name} 401 | /> 402 |
403 |
404 | ) 405 | } 406 | } 407 | 408 | const styles = { 409 | container: { 410 | width: 360, 411 | margin: '0 auto', 412 | borderBottom: '1px solid #ededed', 413 | }, 414 | form: { 415 | display: 'flex', 416 | justifyContent: 'center', 417 | alignItems: 'center' 418 | }, 419 | input: { 420 | height: 35, 421 | width: '360px', 422 | border: 'none', 423 | outline: 'none', 424 | marginLeft: 10, 425 | fontSize: 20, 426 | padding: 8, 427 | } 428 | } 429 | 430 | export default Form 431 | ``` 432 | 433 | This component renders a basic form. In the component we listen for an __enter__ `keyPress` event. If the key is the __Enter__ key, we call `this.props.createNote`, passing in the value in the text input. 434 | 435 | ## Testing Your Components 436 | 437 | Now, a working shell for our application if completed & should work with local state. Let's test it out: 438 | 439 | ```sh 440 | npm start 441 | ``` 442 | 443 | You should be able to create, update & delete todos in your app. You'll notice that when you refresh, the data goes away. We'll fix this in a later step by storing our data in an AppSync API & fetching it when the app loads. 444 | 445 | # Part 2: Adding Cloud Features 446 | 447 | In this section, you will cloud enable your React app using the Amplify CLI. 448 | 449 | ### Install Amplify CLI 450 | 451 | Amplify CLI is the command line tool that you will use to create and manage the backend for your React app. In the upcoming sections, you will use Amplify CLI to simplify various operations. The CLI enables you to create and configure your backend quickly, even without leaving the command line! 452 | 453 | ### Installing and Configuring the CLI 454 | 455 | To use Amplify CLI with your project, you need to install it to your local development machine and configure it with your AWS credentials. Configuration is a one-time effort; once you configure the CLI, you can use it on multiple projects on your local machine. Because the CLI creates backend resources for you, it needs to utilize an AWS account with appropriate IAM permissions. During the configuration step, a new IAM role will be automatically created on your AWS account. 456 | 457 | To install and configure the Amplify CLI, run the following commands: 458 | 459 | ```sh 460 | $ npm install -g @aws-amplify/cli 461 | 462 | $ npm amplify configure 463 | ``` 464 | 465 | > For a video walkthrough of how to configure the Amplify CLI, check out [this](https://www.youtube.com/watch?v=fWbM5DLh25U) video. 466 | 467 | ### Amplify CLI vs. AWS Console 468 | 469 | The backend resources that are created by the CLI is available to you through the AWS Console, e.g., you can access your Amazon Cognito User Pool on the AWS Console after you enable auth with Amplify CLI. 470 | 471 | > To learn about Amplify CLI, visit the CLI developer documentation. 472 | 473 | ### Initialize Your Backend 474 | 475 | To initialize your backend, run the following command in your project’s root folder: 476 | 477 | ```sh 478 | $ amplify init 479 | ``` 480 | 481 | The CLI guides you through the options for your project. Select `React` as your framework when prompted: 482 | 483 | ```sh 484 | Please tell us about your project 485 | ? What javascript framework are you using 486 | ❯ react 487 | react-native 488 | angular 489 | ionic 490 | vue 491 | none 492 | ``` 493 | 494 | When the CLI successfully configures your backend, the backend configuration files are saved to `/amplify` folder. You don’t need to manually edit the content of this folder as it is maintained by the CLI. 495 | 496 | ### Adding Analytics 497 | 498 | Let’s add our first backend feature to our app, Analytics. Adding Analytics won’t change the user experience though, but it will provide valuable metrics that you can track in Amazon Pinpoint dashboard. 499 | 500 | While enabling Analytics, you will also learn how to use Amplify CLI and configure your app to work with various backend services. 501 | 502 | ### How the Amplify CLI works? 503 | 504 | When you deploy your backend with Amplify CLI, here is what happens under the hood: 505 | 506 | 1. The CLI creates and provisions related resources on your account 507 | 2. The CLI updates your ‘/amplify’ folder, which has all the relevant information about your backend on AWS 508 | 3. The CLI updates the configuration file aws-exports.js with the latest resource credentials 509 | 510 | As a front-end developer, you need to import the auto generated aws-exports.js configuration file in your React app, and configure your app with Amplify.configure() method call. 511 | 512 | So, to enable analytics to your application, run the following commands: 513 | 514 | ```sh 515 | $ amplify add analytics 516 | $ amplify push 517 | ``` 518 | 519 | After successfully executing the push command, the CLI updates your configuration file _aws-exports.js_ in your ‘/src’ folder with the references to the newly created resources. 520 | 521 | ## Working with The Configuration File 522 | 523 | The next step is to import _aws-exports.js_ configuration file into your app. 524 | 525 | To configure your app, open __src/App.js__ file and make the following changes in code: 526 | 527 | ```js 528 | import Amplify, { Analytics } from 'aws-amplify'; 529 | import aws_exports from './aws-exports'; 530 | 531 | Amplify.configure(aws_exports); 532 | ``` 533 | 534 | ### Monitoring App Analytics 535 | 536 | Refresh your application a couple of times, and then you will automatically start receiving usage metrics in the Amazon Pinpoint console. 537 | 538 | To view the Amazon Pinpoint console, visit [https://console.aws.amazon.com/pinpoint/home/](https://console.aws.amazon.com/pinpoint/home/) & select the region where you created the service. 539 | 540 | Since your application doesn’t have much functionality at the moment, only ‘session start’ events are displayed in Pinpoint Console. As you add more cloud features to your app - like authentication - Amplify will automatically report related analytics events to Amazon Pinpoint. So, you will know how your users are interacting with your app. 541 | 542 | ### Adding Authentication 543 | 544 | Now that you know how to utilize Amplify CLI to enable backend services, you can continue to add new features to your React app easily. 545 | 546 | User authentication will be the next cloud feature you will enable. 547 | 548 | If you have been following the tutorial from the start and enabled Analytics in the previous step, auth is already enabled for your app (analytics needs secure credentials for reporting metrics). In this case, you just need to run update auth command to create a User Pool that will store your registered users: 549 | 550 | ```sh 551 | $ amplify update auth 552 | 553 | > Do you want to use the default authentication and security configuration? Yes, use the default configuration. 554 | 555 | $ amplify push 556 | ``` 557 | 558 | If you have not enabled Analytics earlier, you will be using auth features for the first time. Run the following command: 559 | 560 | ```sh 561 | $ amplify add auth 562 | $ amplify push 563 | ``` 564 | 565 | When prompted by the CLI, chose ‘default configuration’: 566 | 567 | ```sh 568 | Do you want to use the default authentication and security configuration? 569 | ❯ Yes, use the default configuration. 570 | ``` 571 | 572 | > AWS Amplify’s Authentication category works with Amazon Cognito, a cloud-based authentication and federation service that enables you to manage user access to your applications. 573 | 574 | ## Enabling the UI Components for Auth 575 | 576 | One of the most important benefits of Amplify Framework is that you don’t need to implement standard app features - like user authentication - from scratch. Amplify provides UI components that you can integrate into your app with just a few lines of code. 577 | 578 | ## Rendering the Auth UI 579 | 580 | Now, let’s put the auth UI components in our home page. We'll be using the `withAuthenticator` higher order component from the _aws-amplify-react_ library. 581 | 582 | The `withAuthenticator` component renders a pre-built sign-in and sign-up flow with full-fledged auth functionality like user registration, password reminders, and Multi-factor Authentication. 583 | 584 | Open __src/App.js__. Import the `withAuthenticator` component & replace the default export for the component: 585 | 586 | ```js 587 | import { withAuthenticator } from 'aws-amplify-react' 588 | 589 | export default withAuthenticator(App, { includeGreetings: true }) 590 | ``` 591 | 592 | ### Test Your Auth Flow 593 | 594 | Now, refresh your app. Once your application loads, you will see login/signup controls. 595 | 596 | ![](auth.jpg) 597 | 598 | ### Where is the user data stored? 599 | 600 | When a new user registers through the `withAuthenticator` component, the user data is stored in your Cognito User Pool. A user pool is a user directory in Amazon Cognito. With a user pool, your users can sign in to your app through Amazon Cognito. You can visit [Amazon Cognito console](https://console.aws.amazon.com/cognito/home), and see the list of registered users by selecting the User Pool that is created for your app. 601 | 602 | # Part 3: Enabling GraphQL Backend 603 | 604 | So far, your app is powered by Amazon Cognito User Pools, but we do not yet have any real yet as far as storing data. In this part, you will integrate your app with a GraphQL API (powered by AWS AppSync) that will store your notes in a NoSQL database (Amazon DynamoDB). 605 | 606 | ### What is GraphQL? 607 | 608 | GraphQL is a modern way of building APIs for your apps and interacting with your backend. It has many benefits over REST – such as using a single endpoint, powerful query syntax, data aggregation from multiple sources and a type system to describe the data – but the overall goal is to make your front-end development experience easy and more productive. 609 | 610 | The Amplify CLI will also help you when creating the GraphQL backend. 611 | 612 | ### Create a GraphQL API 613 | 614 | In the root folder of your app project, run the following command: 615 | 616 | ```sh 617 | amplify add api 618 | ``` 619 | 620 | Then, select GraphQL as the service type: 621 | 622 | ```sh 623 | ? Please select from one of the below mentioned services (Use arrow keys) 624 | ❯ GraphQL 625 | REST 626 | ``` 627 | 628 | > API category supports GraphQL and REST endpoints. In this tutorial, we will create our backend on GraphQL, which uses AWS AppSync. 629 | 630 | When you select GraphQL as the service type, the CLI offers you options to create a schema. A schema defines the data model for your GraphQL backend. 631 | 632 | Select Single object with fields when prompted What best describes your project?. This option will create a GraphQL backend data model which we can modify and use in our app: 633 | 634 | ```sh 635 | ? Provide API name: reactnotes 636 | ? Choose an authorization type for the API Amazon Cognito User Pool 637 | Use a Cognito user pool configured as a part of this project 638 | ? Do you have an annotated GraphQL schema? No 639 | ? Do you want a guided schema creation? true 640 | ? What best describes your project: 641 | ? What best describes your project: Single object with fields (e.g., “Todo” with ID, name, description) 642 | ? Do you want to edit the schema now? Yes 643 | ``` 644 | 645 | This should open a GraphQL schema in your text editor (located at amplify/backend/api/reactnotes/schema.graphql). 646 | 647 | Update the schema to the following & save the file: 648 | 649 | ```graphql 650 | type Note @model { 651 | id: ID! 652 | name: String! 653 | description: String 654 | status: String! 655 | } 656 | ``` 657 | 658 | Deploy your GraphQL backend: 659 | 660 | ```sh 661 | $ amplify push 662 | ``` 663 | 664 | When prompted, choose code generation language target as JavaScript: 665 | 666 | ```sh 667 | ? Do you want to generate code for your newly created GraphQL API: Yes 668 | ? Choose the code generation language target: javascript 669 | ? Enter the file name pattern of graphql queries, mutations and subscriptions: src/graphql/**/*.js 670 | ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions: Yes 671 | ``` 672 | 673 | ### Using GraphQL Queries and Mutations 674 | 675 | When working with a GraphQL API, you pass queries - or mutations - to the GraphQL endpoint. Queries are used for read operations, and mutations perform create, update & delete operations. 676 | 677 | A query/mutation has a simple, JSON-like format, but you don’t need to write it manually. Instead, the Amplify CLI can auto-generate queries and mutations for you. 678 | 679 | ## Auto-Generating Queries/Mutations 680 | 681 | When you updated your GraphQL schema, selecting Do you want to update code for your updated GraphQL API option generated queries and mutations that you need. The CLI also saved your generated queries and mutations under ‘src/graphql’ folder. 682 | 683 | > You can auto-generate queries and mutations anytime by running the `$ amplify codegen` command. 684 | 685 | When you check the `src/graphql` folder, you will see that the CLI has generated a list of queries, mutations, & subscriptions for common data operations. You will use them to perform CRUD (create-read-update-delete) operations on your data: 686 | 687 | | TYPE | OPERATIONS | 688 | | ------------- | ------------- | 689 | | query | getNote , listNotes | 690 | | mutation | createNote , updateNote , deleteNote | 691 | 692 | ## Running Queries/Mutations 693 | 694 | To run a query or mutation, you import it in your app, and use Amplify `API` category to perform the operation: 695 | 696 | ```js 697 | import Amplify, { API, graphqlOperation } from "aws-amplify"; 698 | import { listNotes } from './graphql/queries'; 699 | 700 | const allNotes = await API.graphql(graphqlOperation(listNotes)); 701 | console.log(allNotes); 702 | ``` 703 | 704 | Let's take a look at how to do this in our app. 705 | 706 | ## Connecting to the GraphQL Backend & Updating Your App 707 | 708 | Currently, your GraphQL API and related backend resources (an Amazon DynamoDB table that stores the data) have been deployed to the cloud. Now, we can update the functionality to our app by integrating the GraphQL backend. 709 | 710 | To do so, we'll open __App.js__ & do the following: 711 | 712 | 1. Import the components & queries we'll need to interact with the API: 713 | 714 | ```js 715 | // import API & graphqlOperation helpers from AWS Amplify 716 | import { API, graphqlOperation } from 'aws-amplify' 717 | 718 | // import the mutations & queries we'll need to interact with the API 719 | import { createNote, updateNote, deleteNote } from './graphql/mutations' 720 | import { listNotes } from './graphql/queries' 721 | ``` 722 | 723 | 2. Add a __ComponentDidMount__ lifecycle method to fetch the data from the AppSync API when the app loads & update the state: 724 | 725 | ```js 726 | async componentDidMount() { 727 | try { 728 | const { data: { listNotes: { items }}} = await API.graphql(graphqlOperation(listNotes)) 729 | this.setState({ notes: items }) 730 | } catch (err) { 731 | console.log('error fetching notes...', err) 732 | } 733 | } 734 | ``` 735 | 736 | Here, we call the API & fetch all of the notes. When the data is returned, we update the notes array to the data that was returned from the API. 737 | 738 | 3. Update the __createNote__ method to create the note in the AppSync API: 739 | 740 | ```js 741 | createNote = async note => { 742 | const notes = [note, ...this.state.notes] 743 | const newNotes = this.state.notes 744 | this.setState({ notes }) 745 | try { 746 | const data = await API.graphql(graphqlOperation(createNote, { input: note })) 747 | this.setState({ notes: [data.data.createNote, ...newNotes] }) 748 | } catch (err) { 749 | console.log('error creating note..', err) 750 | } 751 | } 752 | ``` 753 | 754 | This method calls the API & creates a new note. We still provide an optimistic response as to update the UI as soon as the user creates the note without waiting for the response from the API. 755 | 756 | 4. Update the __updateNote__ method to update the note in the AppSync API: 757 | 758 | ```js 759 | updateNote = async note => { 760 | const updatedNote = { 761 | ...note, 762 | status: note.status === 'new' ? 'completed' : 'new' 763 | } 764 | const index = this.state.notes.findIndex(i => i.id === note.id) 765 | const notes = [...this.state.notes] 766 | notes[index] = updatedNote 767 | this.setState({ notes }) 768 | 769 | try { 770 | await API.graphql(graphqlOperation(updateNote, { input: updatedNote })) 771 | } catch (err) { 772 | console.log('error updating note...', err) 773 | } 774 | } 775 | ``` 776 | 777 | This method updates the status of the note to be either completed or new. We also provide an optimistic response. 778 | 779 | 5. Update the __deleteNote__ method to delete the note in the AppSync API: 780 | 781 | ```js 782 | deleteNote = async note => { 783 | const input = { id: note.id } 784 | const notes = this.state.notes.filter(n => n.id !== note.id) 785 | this.setState({ notes }) 786 | try { 787 | await API.graphql(graphqlOperation(deleteNote, { input })) 788 | } catch (err) { 789 | console.log('error deleting note...', err) 790 | } 791 | } 792 | ``` 793 | 794 | This method deletes the note from the API. We also still provide an optimistic response. 795 | 796 | ### Testing it out 797 | 798 | You have just implemented GraphQL queries and mutations in your CRUD functions. Now, you can test your app and verify that the app data is persisted using your GraphQL backend! 799 | 800 | ```sh 801 | npm start 802 | ``` 803 | 804 | Inspecting a GraphQL Mutation 805 | 806 | Let’s see what is happening under the hood. Run your app, and add a new item while you monitor the network traffic in your browser’s developer tools. Here is what you will see for a mutation HTTP request: 807 | 808 | ![](preview.png) 809 | 810 | When you check the request header, you will notice that the Request Payload has the note item data in JSON format. 811 | 812 | __Congratulations! You now have a cloud-powered React app!__ 813 | 814 | You’ve persisted your app’s data using AWS AppSync and Amazon DynamoDB. 815 | 816 | ### What’s next? 817 | 818 | You have completed this tutorial. However, if you like to improve your app even further, here are some ideas you can consider. 819 | 820 | #### Deploy your app using the Amplify Console 821 | 822 | The AWS Amplify Console is a continuous deployment and hosting service for mobile web applications. The AWS Amplify Console makes it easy for you to rapidly release new features, helps you avoid downtime during application deployment, and handles the complexity of simultaneously updating the frontend and backend of your applications. To learn more about the Amplify console, check out [the documentation](https://docs.aws.amazon.com/amplify/latest/userguide/welcome.html). 823 | 824 | #### Work with User Data 825 | 826 | You may have noticed that our Note GraphQL schema doesn’t have a user id field. It means that the notes list is not personalized for your app users. To fix that, you can retrieve the user id after login, and use it when you work with data. When a user is logged in, you may also like to use the user profile information in your app, like displaying the username or profile picture. Learn more about User Attributes here. 827 | 828 | #### Use GraphQL Subscriptions 829 | 830 | In addition to queries and mutations, you can use GraphQL subscriptions with AWS AppSync and enable real-time data in your app. Think of a user experience which you share your notes with your friends and all of you create and edit items at the same time. Learn more about subscriptions here. 831 | 832 | #### Add Search 833 | 834 | You can add search functionality to your app. This will be very easy by adding a @searchable directive in your GraphQL schema. Learn more about here. 835 | 836 | #### Add Images 837 | 838 | You can add an image attachment feature for notes. This can be simply done by enabling complex object types in your GraphQL schema. Learn more about here. 839 | -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "react-notes", 3 | "version": "1.0", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "build", 10 | "BuildCommand": "npm run-script build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /amplify/backend/analytics/reactnotes/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "reactnotes", 3 | "roleName": "pinpointLambdaRoleda8c3f26", 4 | "cloudWatchPolicyName": "cloudWatchPolicyda8c3f26", 5 | "pinpointPolicyName": "pinpointPolicyda8c3f26", 6 | "authPolicyName": "pinpoint_amplify_da8c3f26", 7 | "unauthPolicyName": "pinpoint_amplify_da8c3f26", 8 | "authRoleName": { 9 | "Ref": "AuthRoleName" 10 | }, 11 | "unauthRoleName": { 12 | "Ref": "UnauthRoleName" 13 | }, 14 | "authRoleArn": { 15 | "Fn::GetAtt": [ 16 | "AuthRole", 17 | "Arn" 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /amplify/backend/analytics/reactnotes/pinpoint-cloudformation-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Pinpoint resource stack creation using Amplify CLI", 4 | "Parameters": { 5 | "appName": { 6 | "Type": "String" 7 | }, 8 | "appId": { 9 | "Type": "String", 10 | "Default": "NONE" 11 | }, 12 | "roleName": { 13 | "Type": "String" 14 | }, 15 | "cloudWatchPolicyName": { 16 | "Type": "String" 17 | }, 18 | "pinpointPolicyName": { 19 | "Type": "String" 20 | }, 21 | "authPolicyName": { 22 | "Type": "String" 23 | }, 24 | "unauthPolicyName": { 25 | "Type": "String" 26 | }, 27 | "authRoleName": { 28 | "Type": "String" 29 | }, 30 | "unauthRoleName": { 31 | "Type": "String" 32 | }, 33 | "authRoleArn": { 34 | "Type": "String" 35 | }, 36 | "env": { 37 | "Type": "String" 38 | } 39 | }, 40 | "Metadata": { 41 | "AWS::CloudFormation::Interface": { 42 | "ParameterGroups": [ 43 | { 44 | "Label": { 45 | "default": "Creating pinpoint app" 46 | }, 47 | "Parameters": [ 48 | "appName" 49 | ] 50 | } 51 | ] 52 | } 53 | }, 54 | "Conditions": { 55 | "ShouldCreatePinpointApp": { 56 | "Fn::Equals": [ 57 | { 58 | "Ref": "appId" 59 | }, 60 | "NONE" 61 | ] 62 | }, 63 | "ShouldNotCreateEnvResources": { 64 | "Fn::Equals": [ 65 | { 66 | "Ref": "env" 67 | }, 68 | "NONE" 69 | ] 70 | } 71 | }, 72 | "Resources": { 73 | "LambdaExecutionRole": { 74 | "Condition": "ShouldCreatePinpointApp", 75 | "Type": "AWS::IAM::Role", 76 | "Properties": { 77 | "RoleName": { 78 | "Fn::If": [ 79 | "ShouldNotCreateEnvResources", 80 | { 81 | "Ref": "roleName" 82 | }, 83 | { 84 | "Fn::Join": [ 85 | "", 86 | [ 87 | { 88 | "Ref": "roleName" 89 | }, 90 | "-", 91 | { 92 | "Ref": "env" 93 | } 94 | ] 95 | ] 96 | } 97 | ] 98 | }, 99 | "AssumeRolePolicyDocument": { 100 | "Version": "2012-10-17", 101 | "Statement": [ 102 | { 103 | "Effect": "Allow", 104 | "Principal": { 105 | "Service": [ 106 | "lambda.amazonaws.com" 107 | ] 108 | }, 109 | "Action": [ 110 | "sts:AssumeRole" 111 | ] 112 | } 113 | ] 114 | }, 115 | "Policies": [ 116 | { 117 | "PolicyName": { 118 | "Ref": "cloudWatchPolicyName" 119 | }, 120 | "PolicyDocument": { 121 | "Version": "2012-10-17", 122 | "Statement": [ 123 | { 124 | "Effect": "Allow", 125 | "Action": [ 126 | "logs:CreateLogGroup", 127 | "logs:CreateLogStream", 128 | "logs:PutLogEvents" 129 | ], 130 | "Resource": "arn:aws:logs:*:*:*" 131 | } 132 | ] 133 | } 134 | }, 135 | { 136 | "PolicyName": { 137 | "Ref": "pinpointPolicyName" 138 | }, 139 | "PolicyDocument": { 140 | "Version": "2012-10-17", 141 | "Statement": [ 142 | { 143 | "Effect": "Allow", 144 | "Action": [ 145 | "mobileanalytics:*", 146 | "mobiletargeting:*" 147 | ], 148 | "Resource": "*" 149 | } 150 | ] 151 | } 152 | } 153 | ] 154 | } 155 | }, 156 | "PinpointFunction": { 157 | "Type": "AWS::Lambda::Function", 158 | "Condition": "ShouldCreatePinpointApp", 159 | "Properties": { 160 | "Code": { 161 | "ZipFile": { 162 | "Fn::Join": [ 163 | "\n", 164 | [ 165 | "const response = require('cfn-response');", 166 | "const aws = require('aws-sdk');", 167 | "exports.handler = function(event, context) {", 168 | " if (event.RequestType == 'Delete') {", 169 | " response.send(event, context, response.SUCCESS);", 170 | " return;", 171 | " }", 172 | " if (event.RequestType == 'Update') {", 173 | " response.send(event, context, response.SUCCESS);", 174 | " return;", 175 | " }", 176 | " if (event.RequestType == 'Create') {", 177 | " const appName = event.ResourceProperties.appName;", 178 | " let responseData = {};", 179 | " const params = {", 180 | " CreateApplicationRequest: {", 181 | " Name: appName", 182 | " }", 183 | " };", 184 | " const pinpoint = new aws.Pinpoint({ apiVersion: '2016-12-01', region: event.ResourceProperties.region });", 185 | " return pinpoint.createApp(params).promise()", 186 | " .then((res) => {", 187 | " responseData = res.ApplicationResponse;", 188 | " response.send(event, context, response.SUCCESS, responseData);", 189 | " }).catch((err) => {", 190 | " console.log(err.stack);", 191 | " responseData = {Error: err};", 192 | " response.send(event, context, response.FAILED, responseData);", 193 | " throw err;", 194 | " });", 195 | " }", 196 | "};" 197 | ] 198 | ] 199 | } 200 | }, 201 | "Handler": "index.handler", 202 | "Runtime": "nodejs6.10", 203 | "Timeout": "300", 204 | "Role": { 205 | "Fn::GetAtt": [ 206 | "LambdaExecutionRole", 207 | "Arn" 208 | ] 209 | } 210 | } 211 | }, 212 | "PinpointFunctionOutputs": { 213 | "Type": "Custom::LambdaCallout", 214 | "Condition": "ShouldCreatePinpointApp", 215 | "Properties": { 216 | "ServiceToken": { 217 | "Fn::GetAtt": [ 218 | "PinpointFunction", 219 | "Arn" 220 | ] 221 | }, 222 | "region": { 223 | "Fn::FindInMap": [ 224 | "RegionMapping", 225 | { 226 | "Ref": "AWS::Region" 227 | }, 228 | "pinpointRegion" 229 | ] 230 | }, 231 | "appName": { 232 | "Fn::If": [ 233 | "ShouldNotCreateEnvResources", 234 | { 235 | "Ref": "appName" 236 | }, 237 | { 238 | "Fn::Join": [ 239 | "", 240 | [ 241 | { 242 | "Ref": "appName" 243 | }, 244 | "-", 245 | { 246 | "Ref": "env" 247 | } 248 | ] 249 | ] 250 | } 251 | ] 252 | } 253 | } 254 | }, 255 | "CognitoUnauthPolicy": { 256 | "Type": "AWS::IAM::Policy", 257 | "Condition": "ShouldCreatePinpointApp", 258 | "Properties": { 259 | "PolicyName": { 260 | "Ref": "unauthPolicyName" 261 | }, 262 | "Roles": [ 263 | { 264 | "Ref": "unauthRoleName" 265 | } 266 | ], 267 | "PolicyDocument": { 268 | "Version": "2012-10-17", 269 | "Statement": [ 270 | { 271 | "Effect": "Allow", 272 | "Action": [ 273 | "mobiletargeting:PutEvents", 274 | "mobiletargeting:UpdateEndpoint" 275 | ], 276 | "Resource": [ 277 | { 278 | "Fn::If": [ 279 | "ShouldCreatePinpointApp", 280 | { 281 | "Fn::Join": [ 282 | "", 283 | [ 284 | "arn:aws:mobiletargeting:*:", 285 | { 286 | "Fn::Select": [ 287 | "4", 288 | { 289 | "Fn::Split": [ 290 | ":", 291 | { 292 | "Ref": "authRoleArn" 293 | } 294 | ] 295 | } 296 | ] 297 | }, 298 | ":apps/", 299 | { 300 | "Fn::GetAtt": [ 301 | "PinpointFunctionOutputs", 302 | "Id" 303 | ] 304 | }, 305 | "*" 306 | ] 307 | ] 308 | }, 309 | { 310 | "Fn::Join": [ 311 | "", 312 | [ 313 | "arn:aws:mobiletargeting:*:", 314 | { 315 | "Fn::Select": [ 316 | "4", 317 | { 318 | "Fn::Split": [ 319 | ":", 320 | { 321 | "Ref": "authRoleArn" 322 | } 323 | ] 324 | } 325 | ] 326 | }, 327 | ":apps/", 328 | { 329 | "Ref": "appId" 330 | }, 331 | "*" 332 | ] 333 | ] 334 | } 335 | ] 336 | } 337 | ] 338 | } 339 | ] 340 | } 341 | } 342 | }, 343 | "CognitoAuthPolicy": { 344 | "Type": "AWS::IAM::Policy", 345 | "Condition": "ShouldCreatePinpointApp", 346 | "Properties": { 347 | "PolicyName": { 348 | "Ref": "authPolicyName" 349 | }, 350 | "Roles": [ 351 | { 352 | "Ref": "authRoleName" 353 | } 354 | ], 355 | "PolicyDocument": { 356 | "Version": "2012-10-17", 357 | "Statement": [ 358 | { 359 | "Effect": "Allow", 360 | "Action": [ 361 | "mobiletargeting:PutEvents", 362 | "mobiletargeting:UpdateEndpoint" 363 | ], 364 | "Resource": [ 365 | { 366 | "Fn::If": [ 367 | "ShouldCreatePinpointApp", 368 | { 369 | "Fn::Join": [ 370 | "", 371 | [ 372 | "arn:aws:mobiletargeting:*:", 373 | { 374 | "Fn::Select": [ 375 | "4", 376 | { 377 | "Fn::Split": [ 378 | ":", 379 | { 380 | "Ref": "authRoleArn" 381 | } 382 | ] 383 | } 384 | ] 385 | }, 386 | ":apps/", 387 | { 388 | "Fn::GetAtt": [ 389 | "PinpointFunctionOutputs", 390 | "Id" 391 | ] 392 | }, 393 | "*" 394 | ] 395 | ] 396 | }, 397 | { 398 | "Fn::Join": [ 399 | "", 400 | [ 401 | "arn:aws:mobiletargeting:*:", 402 | { 403 | "Fn::Select": [ 404 | "4", 405 | { 406 | "Fn::Split": [ 407 | ":", 408 | { 409 | "Ref": "authRoleArn" 410 | } 411 | ] 412 | } 413 | ] 414 | }, 415 | ":apps/", 416 | { 417 | "Ref": "appId" 418 | }, 419 | "*" 420 | ] 421 | ] 422 | } 423 | ] 424 | } 425 | ] 426 | } 427 | ] 428 | } 429 | } 430 | } 431 | }, 432 | "Outputs": { 433 | "Region": { 434 | "Value": { 435 | "Fn::FindInMap": [ 436 | "RegionMapping", 437 | { 438 | "Ref": "AWS::Region" 439 | }, 440 | "pinpointRegion" 441 | ] 442 | } 443 | }, 444 | "Id": { 445 | "Value": { 446 | "Fn::If": [ 447 | "ShouldCreatePinpointApp", 448 | { 449 | "Fn::GetAtt": [ 450 | "PinpointFunctionOutputs", 451 | "Id" 452 | ] 453 | }, 454 | { 455 | "Ref": "appId" 456 | } 457 | ] 458 | } 459 | }, 460 | "appName": { 461 | "Value": { 462 | "Fn::If": [ 463 | "ShouldCreatePinpointApp", 464 | { 465 | "Fn::GetAtt": [ 466 | "PinpointFunctionOutputs", 467 | "Name" 468 | ] 469 | }, 470 | { 471 | "Ref": "appName" 472 | } 473 | ] 474 | } 475 | } 476 | }, 477 | "Mappings": { 478 | "RegionMapping": { 479 | "us-east-1": { 480 | "pinpointRegion": "us-east-1" 481 | }, 482 | "us-east-2": { 483 | "pinpointRegion": "us-east-1" 484 | }, 485 | "sa-east-1": { 486 | "pinpointRegion": "us-east-1" 487 | }, 488 | "ca-central-1": { 489 | "pinpointRegion": "us-east-1" 490 | }, 491 | "us-west-1": { 492 | "pinpointRegion": "us-west-2" 493 | }, 494 | "us-west-2": { 495 | "pinpointRegion": "us-west-2" 496 | }, 497 | "cn-north-1": { 498 | "pinpointRegion": "us-west-2" 499 | }, 500 | "cn-northwest-1": { 501 | "pinpointRegion": "us-west-2" 502 | }, 503 | "ap-south-1": { 504 | "pinpointRegion": "us-west-2" 505 | }, 506 | "ap-northeast-3": { 507 | "pinpointRegion": "us-west-2" 508 | }, 509 | "ap-northeast-2": { 510 | "pinpointRegion": "us-west-2" 511 | }, 512 | "ap-southeast-1": { 513 | "pinpointRegion": "us-west-2" 514 | }, 515 | "ap-southeast-2": { 516 | "pinpointRegion": "us-west-2" 517 | }, 518 | "ap-northeast-1": { 519 | "pinpointRegion": "us-west-2" 520 | }, 521 | "eu-central-1": { 522 | "pinpointRegion": "eu-west-1" 523 | }, 524 | "eu-west-1": { 525 | "pinpointRegion": "eu-west-1" 526 | }, 527 | "eu-west-2": { 528 | "pinpointRegion": "eu-west-1" 529 | }, 530 | "eu-west-3": { 531 | "pinpointRegion": "eu-west-1" 532 | } 533 | } 534 | } 535 | } -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Mutation.createNote.request: -------------------------------------------------------------------------------- 1 | ## [Start] Prepare DynamoDB PutItem Request. ** 2 | $util.qr($context.args.input.put("createdAt", $util.time.nowISO8601())) 3 | $util.qr($context.args.input.put("updatedAt", $util.time.nowISO8601())) 4 | $util.qr($context.args.input.put("__typename", "Note")) 5 | { 6 | "version": "2017-02-28", 7 | "operation": "PutItem", 8 | "key": { 9 | "id": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.args.input.id, $util.autoId())) 10 | }, 11 | "attributeValues": $util.dynamodb.toMapValuesJson($context.args.input), 12 | "condition": { 13 | "expression": "attribute_not_exists(#id)", 14 | "expressionNames": { 15 | "#id": "id" 16 | } 17 | } 18 | } 19 | ## [End] Prepare DynamoDB PutItem Request. ** -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Mutation.createNote.response: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Mutation.deleteNote.request: -------------------------------------------------------------------------------- 1 | #if( $authCondition ) 2 | #set( $condition = $authCondition ) 3 | $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#id)")) 4 | $util.qr($condition.expressionNames.put("#id", "id")) 5 | #else 6 | #set( $condition = { 7 | "expression": "attribute_exists(#id)", 8 | "expressionNames": { 9 | "#id": "id" 10 | } 11 | } ) 12 | #end 13 | #if( $versionedCondition ) 14 | $util.qr($condition.put("expression", "($condition.expression) AND $versionedCondition.expression")) 15 | $util.qr($condition.expressionNames.putAll($versionedCondition.expressionNames)) 16 | #set( $expressionValues = $util.defaultIfNull($condition.expressionValues, {}) ) 17 | $util.qr($expressionValues.putAll($versionedCondition.expressionValues)) 18 | #set( $condition.expressionValues = $expressionValues ) 19 | #end 20 | { 21 | "version": "2017-02-28", 22 | "operation": "DeleteItem", 23 | "key": { 24 | "id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id) 25 | }, 26 | "condition": $util.toJson($condition) 27 | } -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Mutation.deleteNote.response: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Mutation.updateNote.request: -------------------------------------------------------------------------------- 1 | #if( $authCondition && $authCondition.expression != "" ) 2 | #set( $condition = $authCondition ) 3 | $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#id)")) 4 | $util.qr($condition.expressionNames.put("#id", "id")) 5 | #else 6 | #set( $condition = { 7 | "expression": "attribute_exists(#id)", 8 | "expressionNames": { 9 | "#id": "id" 10 | }, 11 | "expressionValues": {} 12 | } ) 13 | #end 14 | ## Automatically set the updatedAt timestamp. ** 15 | $util.qr($context.args.input.put("updatedAt", $util.time.nowISO8601())) 16 | $util.qr($context.args.input.put("__typename", "Note")) 17 | ## Update condition if type is @versioned ** 18 | #if( $versionedCondition ) 19 | $util.qr($condition.put("expression", "($condition.expression) AND $versionedCondition.expression")) 20 | $util.qr($condition.expressionNames.putAll($versionedCondition.expressionNames)) 21 | $util.qr($condition.expressionValues.putAll($versionedCondition.expressionValues)) 22 | #end 23 | #set( $expNames = {} ) 24 | #set( $expValues = {} ) 25 | #set( $expSet = {} ) 26 | #set( $expAdd = {} ) 27 | #set( $expRemove = [] ) 28 | #foreach( $entry in $util.map.copyAndRemoveAllKeys($context.args.input, ["id"]).entrySet() ) 29 | #if( $util.isNull($entry.value) ) 30 | #set( $discard = $expRemove.add("#$entry.key") ) 31 | $util.qr($expNames.put("#$entry.key", "$entry.key")) 32 | #else 33 | $util.qr($expSet.put("#$entry.key", ":$entry.key")) 34 | $util.qr($expNames.put("#$entry.key", "$entry.key")) 35 | $util.qr($expValues.put(":$entry.key", $util.dynamodb.toDynamoDB($entry.value))) 36 | #end 37 | #end 38 | #set( $expression = "" ) 39 | #if( !$expSet.isEmpty() ) 40 | #set( $expression = "SET" ) 41 | #foreach( $entry in $expSet.entrySet() ) 42 | #set( $expression = "$expression $entry.key = $entry.value" ) 43 | #if( $foreach.hasNext() ) 44 | #set( $expression = "$expression," ) 45 | #end 46 | #end 47 | #end 48 | #if( !$expAdd.isEmpty() ) 49 | #set( $expression = "$expression ADD" ) 50 | #foreach( $entry in $expAdd.entrySet() ) 51 | #set( $expression = "$expression $entry.key $entry.value" ) 52 | #if( $foreach.hasNext() ) 53 | #set( $expression = "$expression," ) 54 | #end 55 | #end 56 | #end 57 | #if( !$expRemove.isEmpty() ) 58 | #set( $expression = "$expression REMOVE" ) 59 | #foreach( $entry in $expRemove ) 60 | #set( $expression = "$expression $entry" ) 61 | #if( $foreach.hasNext() ) 62 | #set( $expression = "$expression," ) 63 | #end 64 | #end 65 | #end 66 | #set( $update = {} ) 67 | $util.qr($update.put("expression", "$expression")) 68 | #if( !$expNames.isEmpty() ) 69 | $util.qr($update.put("expressionNames", $expNames)) 70 | #end 71 | #if( !$expValues.isEmpty() ) 72 | $util.qr($update.put("expressionValues", $expValues)) 73 | #end 74 | { 75 | "version": "2017-02-28", 76 | "operation": "UpdateItem", 77 | "key": { 78 | "id": { 79 | "S": "$context.args.input.id" 80 | } 81 | }, 82 | "update": $util.toJson($update), 83 | "condition": $util.toJson($condition) 84 | } -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Mutation.updateNote.response: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Query.getNote.request: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2017-02-28", 3 | "operation": "GetItem", 4 | "key": { 5 | "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) 6 | } 7 | } -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Query.getNote.response: -------------------------------------------------------------------------------- 1 | $util.toJson($context.result) -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Query.listNotes.request: -------------------------------------------------------------------------------- 1 | #set( $limit = $util.defaultIfNull($context.args.limit, 10) ) 2 | { 3 | "version": "2017-02-28", 4 | "operation": "Scan", 5 | "filter": #if( $context.args.filter ) 6 | $util.transform.toDynamoDBFilterExpression($ctx.args.filter) 7 | #else 8 | null 9 | #end, 10 | "limit": $limit, 11 | "nextToken": #if( $context.args.nextToken ) 12 | "$context.args.nextToken" 13 | #else 14 | null 15 | #end 16 | } -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/resolvers/Query.listNotes.response: -------------------------------------------------------------------------------- 1 | $util.toJson($ctx.result) -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/build/schema.graphql: -------------------------------------------------------------------------------- 1 | type Note { 2 | id: ID! 3 | name: String! 4 | description: String 5 | status: String! 6 | } 7 | 8 | enum ModelSortDirection { 9 | ASC 10 | DESC 11 | } 12 | 13 | type ModelNoteConnection { 14 | items: [Note] 15 | nextToken: String 16 | } 17 | 18 | input ModelStringFilterInput { 19 | ne: String 20 | eq: String 21 | le: String 22 | lt: String 23 | ge: String 24 | gt: String 25 | contains: String 26 | notContains: String 27 | between: [String] 28 | beginsWith: String 29 | } 30 | 31 | input ModelIDFilterInput { 32 | ne: ID 33 | eq: ID 34 | le: ID 35 | lt: ID 36 | ge: ID 37 | gt: ID 38 | contains: ID 39 | notContains: ID 40 | between: [ID] 41 | beginsWith: ID 42 | } 43 | 44 | input ModelIntFilterInput { 45 | ne: Int 46 | eq: Int 47 | le: Int 48 | lt: Int 49 | ge: Int 50 | gt: Int 51 | contains: Int 52 | notContains: Int 53 | between: [Int] 54 | } 55 | 56 | input ModelFloatFilterInput { 57 | ne: Float 58 | eq: Float 59 | le: Float 60 | lt: Float 61 | ge: Float 62 | gt: Float 63 | contains: Float 64 | notContains: Float 65 | between: [Float] 66 | } 67 | 68 | input ModelBooleanFilterInput { 69 | ne: Boolean 70 | eq: Boolean 71 | } 72 | 73 | input ModelNoteFilterInput { 74 | id: ModelIDFilterInput 75 | name: ModelStringFilterInput 76 | description: ModelStringFilterInput 77 | status: ModelStringFilterInput 78 | and: [ModelNoteFilterInput] 79 | or: [ModelNoteFilterInput] 80 | not: ModelNoteFilterInput 81 | } 82 | 83 | type Query { 84 | getNote(id: ID!): Note 85 | listNotes(filter: ModelNoteFilterInput, limit: Int, nextToken: String): ModelNoteConnection 86 | } 87 | 88 | input CreateNoteInput { 89 | id: ID 90 | name: String! 91 | description: String 92 | status: String! 93 | } 94 | 95 | input UpdateNoteInput { 96 | id: ID! 97 | name: String 98 | description: String 99 | status: String 100 | } 101 | 102 | input DeleteNoteInput { 103 | id: ID 104 | } 105 | 106 | type Mutation { 107 | createNote(input: CreateNoteInput!): Note 108 | updateNote(input: UpdateNoteInput!): Note 109 | deleteNote(input: DeleteNoteInput!): Note 110 | } 111 | 112 | type Subscription { 113 | onCreateNote: Note @aws_subscribe(mutations: ["createNote"]) 114 | onUpdateNote: Note @aws_subscribe(mutations: ["updateNote"]) 115 | onDeleteNote: Note @aws_subscribe(mutations: ["deleteNote"]) 116 | } 117 | -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/cloudformation-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Metadata": {}, 4 | "Parameters": { 5 | "AppSyncApiName": { 6 | "Type": "String", 7 | "Description": "The name of the AppSync API", 8 | "Default": "AppSyncSimpleTransform" 9 | }, 10 | "DynamoDBModelTableReadIOPS": { 11 | "Type": "Number", 12 | "Description": "The number of read IOPS the table should support.", 13 | "Default": 5 14 | }, 15 | "DynamoDBModelTableWriteIOPS": { 16 | "Type": "Number", 17 | "Description": "The number of write IOPS the table should support.", 18 | "Default": 5 19 | }, 20 | "AuthCognitoUserPoolId": { 21 | "Type": "String", 22 | "Description": "The id of an existing User Pool to connect. If this is changed, a user pool will not be created for you.", 23 | "Default": "NONE" 24 | }, 25 | "AuthCognitoUserPoolName": { 26 | "Type": "String", 27 | "Description": "The name of the user pool.", 28 | "Default": "AppSyncUserPool" 29 | }, 30 | "AuthCognitoUserPoolMobileClientName": { 31 | "Type": "String", 32 | "Description": "The name of the native user pool client.", 33 | "Default": "CognitoNativeClient" 34 | }, 35 | "AuthCognitoUserPoolJSClientName": { 36 | "Type": "String", 37 | "Description": "The name of the web user pool client.", 38 | "Default": "CognitoJSClient" 39 | }, 40 | "AuthCognitoUserPoolRefreshTokenValidity": { 41 | "Type": "Number", 42 | "Description": "The time limit, in days, after which the refresh token is no longer valid.", 43 | "Default": 30 44 | }, 45 | "env": { 46 | "Type": "String", 47 | "Description": "The environment name. e.g. Dev, Test, or Production", 48 | "Default": "NONE" 49 | }, 50 | "ResolverBucket": { 51 | "Type": "String", 52 | "Description": "The name of the bucket containing the resolver templates" 53 | }, 54 | "ResolverRootKey": { 55 | "Type": "String", 56 | "Description": "The s3 key of the folder containing the resolver templates in format {Type}.{Field}.[response|request].{Timestamp}" 57 | }, 58 | "DeploymentTimestamp": { 59 | "Type": "String", 60 | "Description": "The timestamp used to identify thie most recent version of the resolver templates in s3." 61 | }, 62 | "schemaGraphql": { 63 | "Type": "String", 64 | "Description": "The S3 location for the Schema: schema.graphql" 65 | } 66 | }, 67 | "Resources": { 68 | "GraphQLSchema": { 69 | "Type": "AWS::AppSync::GraphQLSchema", 70 | "Properties": { 71 | "ApiId": { 72 | "Fn::GetAtt": [ 73 | "GraphQLAPI", 74 | "ApiId" 75 | ] 76 | }, 77 | "DefinitionS3Location": { 78 | "Ref": "schemaGraphql" 79 | } 80 | } 81 | }, 82 | "GraphQLAPI": { 83 | "Type": "AWS::AppSync::GraphQLApi", 84 | "Properties": { 85 | "Name": { 86 | "Ref": "AppSyncApiName" 87 | }, 88 | "AuthenticationType": "AMAZON_COGNITO_USER_POOLS", 89 | "UserPoolConfig": { 90 | "UserPoolId": { 91 | "Fn::If": [ 92 | "AuthShouldCreateUserPool", 93 | { 94 | "Ref": "AuthCognitoUserPool" 95 | }, 96 | { 97 | "Ref": "AuthCognitoUserPoolId" 98 | } 99 | ] 100 | }, 101 | "AwsRegion": { 102 | "Ref": "AWS::Region" 103 | }, 104 | "DefaultAction": "ALLOW" 105 | } 106 | } 107 | }, 108 | "GraphQLAPIKey": { 109 | "Type": "AWS::AppSync::ApiKey", 110 | "Properties": { 111 | "ApiId": { 112 | "Fn::GetAtt": [ 113 | "GraphQLAPI", 114 | "ApiId" 115 | ] 116 | } 117 | } 118 | }, 119 | "NoteTable": { 120 | "Type": "AWS::DynamoDB::Table", 121 | "Properties": { 122 | "TableName": { 123 | "Fn::Join": [ 124 | "-", 125 | [ 126 | "Note", 127 | { 128 | "Fn::GetAtt": [ 129 | "GraphQLAPI", 130 | "ApiId" 131 | ] 132 | } 133 | ] 134 | ] 135 | }, 136 | "KeySchema": [ 137 | { 138 | "AttributeName": "id", 139 | "KeyType": "HASH" 140 | } 141 | ], 142 | "AttributeDefinitions": [ 143 | { 144 | "AttributeName": "id", 145 | "AttributeType": "S" 146 | } 147 | ], 148 | "ProvisionedThroughput": { 149 | "ReadCapacityUnits": { 150 | "Ref": "DynamoDBModelTableReadIOPS" 151 | }, 152 | "WriteCapacityUnits": { 153 | "Ref": "DynamoDBModelTableWriteIOPS" 154 | } 155 | }, 156 | "StreamSpecification": { 157 | "StreamViewType": "NEW_AND_OLD_IMAGES" 158 | } 159 | } 160 | }, 161 | "NoteIAMRole": { 162 | "Type": "AWS::IAM::Role", 163 | "Properties": { 164 | "RoleName": { 165 | "Fn::Join": [ 166 | "-", 167 | [ 168 | "NoteTable", 169 | "role", 170 | { 171 | "Fn::GetAtt": [ 172 | "GraphQLAPI", 173 | "ApiId" 174 | ] 175 | } 176 | ] 177 | ] 178 | }, 179 | "AssumeRolePolicyDocument": { 180 | "Version": "2012-10-17", 181 | "Statement": [ 182 | { 183 | "Effect": "Allow", 184 | "Principal": { 185 | "Service": "appsync.amazonaws.com" 186 | }, 187 | "Action": "sts:AssumeRole" 188 | } 189 | ] 190 | }, 191 | "Policies": [ 192 | { 193 | "PolicyName": "DynamoDBAccess", 194 | "PolicyDocument": { 195 | "Version": "2012-10-17", 196 | "Statement": [ 197 | { 198 | "Effect": "Allow", 199 | "Action": [ 200 | "dynamodb:BatchGetItem", 201 | "dynamodb:BatchWriteItem", 202 | "dynamodb:PutItem", 203 | "dynamodb:DeleteItem", 204 | "dynamodb:GetItem", 205 | "dynamodb:Scan", 206 | "dynamodb:Query", 207 | "dynamodb:UpdateItem" 208 | ], 209 | "Resource": [ 210 | { 211 | "Fn::GetAtt": [ 212 | "NoteTable", 213 | "Arn" 214 | ] 215 | }, 216 | { 217 | "Fn::Join": [ 218 | "/", 219 | [ 220 | { 221 | "Fn::GetAtt": [ 222 | "NoteTable", 223 | "Arn" 224 | ] 225 | }, 226 | "*" 227 | ] 228 | ] 229 | } 230 | ] 231 | } 232 | ] 233 | } 234 | } 235 | ] 236 | } 237 | }, 238 | "NoteDataSource": { 239 | "Type": "AWS::AppSync::DataSource", 240 | "Properties": { 241 | "ApiId": { 242 | "Fn::GetAtt": [ 243 | "GraphQLAPI", 244 | "ApiId" 245 | ] 246 | }, 247 | "Name": "NoteTable", 248 | "Type": "AMAZON_DYNAMODB", 249 | "ServiceRoleArn": { 250 | "Fn::GetAtt": [ 251 | "NoteIAMRole", 252 | "Arn" 253 | ] 254 | }, 255 | "DynamoDBConfig": { 256 | "AwsRegion": { 257 | "Fn::Select": [ 258 | 3, 259 | { 260 | "Fn::Split": [ 261 | ":", 262 | { 263 | "Fn::GetAtt": [ 264 | "NoteTable", 265 | "Arn" 266 | ] 267 | } 268 | ] 269 | } 270 | ] 271 | }, 272 | "TableName": { 273 | "Ref": "NoteTable" 274 | } 275 | } 276 | }, 277 | "DependsOn": [ 278 | "NoteTable", 279 | "NoteIAMRole" 280 | ] 281 | }, 282 | "GetNoteResolver": { 283 | "Type": "AWS::AppSync::Resolver", 284 | "Properties": { 285 | "ApiId": { 286 | "Fn::GetAtt": [ 287 | "GraphQLAPI", 288 | "ApiId" 289 | ] 290 | }, 291 | "DataSourceName": { 292 | "Fn::GetAtt": [ 293 | "NoteDataSource", 294 | "Name" 295 | ] 296 | }, 297 | "FieldName": "getNote", 298 | "TypeName": "Query", 299 | "RequestMappingTemplateS3Location": { 300 | "Fn::Join": [ 301 | "", 302 | [ 303 | "s3://", 304 | { 305 | "Fn::Join": [ 306 | "/", 307 | [ 308 | { 309 | "Ref": "ResolverBucket" 310 | }, 311 | { 312 | "Ref": "ResolverRootKey" 313 | }, 314 | { 315 | "Fn::Join": [ 316 | ".", 317 | [ 318 | "Query", 319 | "getNote", 320 | "request", 321 | { 322 | "Ref": "DeploymentTimestamp" 323 | } 324 | ] 325 | ] 326 | } 327 | ] 328 | ] 329 | } 330 | ] 331 | ] 332 | }, 333 | "ResponseMappingTemplateS3Location": { 334 | "Fn::Join": [ 335 | "", 336 | [ 337 | "s3://", 338 | { 339 | "Fn::Join": [ 340 | "/", 341 | [ 342 | { 343 | "Ref": "ResolverBucket" 344 | }, 345 | { 346 | "Ref": "ResolverRootKey" 347 | }, 348 | { 349 | "Fn::Join": [ 350 | ".", 351 | [ 352 | "Query", 353 | "getNote", 354 | "response", 355 | { 356 | "Ref": "DeploymentTimestamp" 357 | } 358 | ] 359 | ] 360 | } 361 | ] 362 | ] 363 | } 364 | ] 365 | ] 366 | } 367 | }, 368 | "DependsOn": "GraphQLSchema" 369 | }, 370 | "ListNoteResolver": { 371 | "Type": "AWS::AppSync::Resolver", 372 | "Properties": { 373 | "ApiId": { 374 | "Fn::GetAtt": [ 375 | "GraphQLAPI", 376 | "ApiId" 377 | ] 378 | }, 379 | "DataSourceName": { 380 | "Fn::GetAtt": [ 381 | "NoteDataSource", 382 | "Name" 383 | ] 384 | }, 385 | "FieldName": "listNotes", 386 | "TypeName": "Query", 387 | "RequestMappingTemplateS3Location": { 388 | "Fn::Join": [ 389 | "", 390 | [ 391 | "s3://", 392 | { 393 | "Fn::Join": [ 394 | "/", 395 | [ 396 | { 397 | "Ref": "ResolverBucket" 398 | }, 399 | { 400 | "Ref": "ResolverRootKey" 401 | }, 402 | { 403 | "Fn::Join": [ 404 | ".", 405 | [ 406 | "Query", 407 | "listNotes", 408 | "request", 409 | { 410 | "Ref": "DeploymentTimestamp" 411 | } 412 | ] 413 | ] 414 | } 415 | ] 416 | ] 417 | } 418 | ] 419 | ] 420 | }, 421 | "ResponseMappingTemplateS3Location": { 422 | "Fn::Join": [ 423 | "", 424 | [ 425 | "s3://", 426 | { 427 | "Fn::Join": [ 428 | "/", 429 | [ 430 | { 431 | "Ref": "ResolverBucket" 432 | }, 433 | { 434 | "Ref": "ResolverRootKey" 435 | }, 436 | { 437 | "Fn::Join": [ 438 | ".", 439 | [ 440 | "Query", 441 | "listNotes", 442 | "response", 443 | { 444 | "Ref": "DeploymentTimestamp" 445 | } 446 | ] 447 | ] 448 | } 449 | ] 450 | ] 451 | } 452 | ] 453 | ] 454 | } 455 | }, 456 | "DependsOn": "GraphQLSchema" 457 | }, 458 | "CreateNoteResolver": { 459 | "Type": "AWS::AppSync::Resolver", 460 | "Properties": { 461 | "ApiId": { 462 | "Fn::GetAtt": [ 463 | "GraphQLAPI", 464 | "ApiId" 465 | ] 466 | }, 467 | "DataSourceName": { 468 | "Fn::GetAtt": [ 469 | "NoteDataSource", 470 | "Name" 471 | ] 472 | }, 473 | "FieldName": "createNote", 474 | "TypeName": "Mutation", 475 | "RequestMappingTemplateS3Location": { 476 | "Fn::Join": [ 477 | "", 478 | [ 479 | "s3://", 480 | { 481 | "Fn::Join": [ 482 | "/", 483 | [ 484 | { 485 | "Ref": "ResolverBucket" 486 | }, 487 | { 488 | "Ref": "ResolverRootKey" 489 | }, 490 | { 491 | "Fn::Join": [ 492 | ".", 493 | [ 494 | "Mutation", 495 | "createNote", 496 | "request", 497 | { 498 | "Ref": "DeploymentTimestamp" 499 | } 500 | ] 501 | ] 502 | } 503 | ] 504 | ] 505 | } 506 | ] 507 | ] 508 | }, 509 | "ResponseMappingTemplateS3Location": { 510 | "Fn::Join": [ 511 | "", 512 | [ 513 | "s3://", 514 | { 515 | "Fn::Join": [ 516 | "/", 517 | [ 518 | { 519 | "Ref": "ResolverBucket" 520 | }, 521 | { 522 | "Ref": "ResolverRootKey" 523 | }, 524 | { 525 | "Fn::Join": [ 526 | ".", 527 | [ 528 | "Mutation", 529 | "createNote", 530 | "response", 531 | { 532 | "Ref": "DeploymentTimestamp" 533 | } 534 | ] 535 | ] 536 | } 537 | ] 538 | ] 539 | } 540 | ] 541 | ] 542 | } 543 | }, 544 | "DependsOn": "GraphQLSchema" 545 | }, 546 | "UpdateNoteResolver": { 547 | "Type": "AWS::AppSync::Resolver", 548 | "Properties": { 549 | "ApiId": { 550 | "Fn::GetAtt": [ 551 | "GraphQLAPI", 552 | "ApiId" 553 | ] 554 | }, 555 | "DataSourceName": { 556 | "Fn::GetAtt": [ 557 | "NoteDataSource", 558 | "Name" 559 | ] 560 | }, 561 | "FieldName": "updateNote", 562 | "TypeName": "Mutation", 563 | "RequestMappingTemplateS3Location": { 564 | "Fn::Join": [ 565 | "", 566 | [ 567 | "s3://", 568 | { 569 | "Fn::Join": [ 570 | "/", 571 | [ 572 | { 573 | "Ref": "ResolverBucket" 574 | }, 575 | { 576 | "Ref": "ResolverRootKey" 577 | }, 578 | { 579 | "Fn::Join": [ 580 | ".", 581 | [ 582 | "Mutation", 583 | "updateNote", 584 | "request", 585 | { 586 | "Ref": "DeploymentTimestamp" 587 | } 588 | ] 589 | ] 590 | } 591 | ] 592 | ] 593 | } 594 | ] 595 | ] 596 | }, 597 | "ResponseMappingTemplateS3Location": { 598 | "Fn::Join": [ 599 | "", 600 | [ 601 | "s3://", 602 | { 603 | "Fn::Join": [ 604 | "/", 605 | [ 606 | { 607 | "Ref": "ResolverBucket" 608 | }, 609 | { 610 | "Ref": "ResolverRootKey" 611 | }, 612 | { 613 | "Fn::Join": [ 614 | ".", 615 | [ 616 | "Mutation", 617 | "updateNote", 618 | "response", 619 | { 620 | "Ref": "DeploymentTimestamp" 621 | } 622 | ] 623 | ] 624 | } 625 | ] 626 | ] 627 | } 628 | ] 629 | ] 630 | } 631 | }, 632 | "DependsOn": "GraphQLSchema" 633 | }, 634 | "DeleteNoteResolver": { 635 | "Type": "AWS::AppSync::Resolver", 636 | "Properties": { 637 | "ApiId": { 638 | "Fn::GetAtt": [ 639 | "GraphQLAPI", 640 | "ApiId" 641 | ] 642 | }, 643 | "DataSourceName": { 644 | "Fn::GetAtt": [ 645 | "NoteDataSource", 646 | "Name" 647 | ] 648 | }, 649 | "FieldName": "deleteNote", 650 | "TypeName": "Mutation", 651 | "RequestMappingTemplateS3Location": { 652 | "Fn::Join": [ 653 | "", 654 | [ 655 | "s3://", 656 | { 657 | "Fn::Join": [ 658 | "/", 659 | [ 660 | { 661 | "Ref": "ResolverBucket" 662 | }, 663 | { 664 | "Ref": "ResolverRootKey" 665 | }, 666 | { 667 | "Fn::Join": [ 668 | ".", 669 | [ 670 | "Mutation", 671 | "deleteNote", 672 | "request", 673 | { 674 | "Ref": "DeploymentTimestamp" 675 | } 676 | ] 677 | ] 678 | } 679 | ] 680 | ] 681 | } 682 | ] 683 | ] 684 | }, 685 | "ResponseMappingTemplateS3Location": { 686 | "Fn::Join": [ 687 | "", 688 | [ 689 | "s3://", 690 | { 691 | "Fn::Join": [ 692 | "/", 693 | [ 694 | { 695 | "Ref": "ResolverBucket" 696 | }, 697 | { 698 | "Ref": "ResolverRootKey" 699 | }, 700 | { 701 | "Fn::Join": [ 702 | ".", 703 | [ 704 | "Mutation", 705 | "deleteNote", 706 | "response", 707 | { 708 | "Ref": "DeploymentTimestamp" 709 | } 710 | ] 711 | ] 712 | } 713 | ] 714 | ] 715 | } 716 | ] 717 | ] 718 | } 719 | }, 720 | "DependsOn": "GraphQLSchema" 721 | }, 722 | "AuthCognitoUserPool": { 723 | "Type": "AWS::Cognito::UserPool", 724 | "Properties": { 725 | "UserPoolName": { 726 | "Ref": "AuthCognitoUserPoolName" 727 | }, 728 | "Policies": { 729 | "PasswordPolicy": { 730 | "MinimumLength": 8, 731 | "RequireLowercase": true, 732 | "RequireNumbers": true, 733 | "RequireSymbols": true, 734 | "RequireUppercase": true 735 | } 736 | }, 737 | "Schema": [ 738 | { 739 | "Name": "email", 740 | "Required": true, 741 | "Mutable": true 742 | } 743 | ], 744 | "AutoVerifiedAttributes": [ 745 | "email" 746 | ] 747 | }, 748 | "Condition": "AuthShouldCreateUserPool" 749 | }, 750 | "AuthCognitoUserPoolNativeClient": { 751 | "Type": "AWS::Cognito::UserPoolClient", 752 | "Properties": { 753 | "ClientName": { 754 | "Ref": "AuthCognitoUserPoolMobileClientName" 755 | }, 756 | "UserPoolId": { 757 | "Fn::If": [ 758 | "AuthShouldCreateUserPool", 759 | { 760 | "Ref": "AuthCognitoUserPool" 761 | }, 762 | { 763 | "Ref": "AuthCognitoUserPoolId" 764 | } 765 | ] 766 | }, 767 | "GenerateSecret": true, 768 | "RefreshTokenValidity": { 769 | "Ref": "AuthCognitoUserPoolRefreshTokenValidity" 770 | }, 771 | "ReadAttributes": [], 772 | "WriteAttributes": [] 773 | }, 774 | "Condition": "AuthShouldCreateUserPool" 775 | }, 776 | "AuthCognitoUserPoolJSClient": { 777 | "Type": "AWS::Cognito::UserPoolClient", 778 | "Properties": { 779 | "ClientName": { 780 | "Ref": "AuthCognitoUserPoolJSClientName" 781 | }, 782 | "UserPoolId": { 783 | "Fn::If": [ 784 | "AuthShouldCreateUserPool", 785 | { 786 | "Ref": "AuthCognitoUserPool" 787 | }, 788 | { 789 | "Ref": "AuthCognitoUserPoolId" 790 | } 791 | ] 792 | }, 793 | "GenerateSecret": false, 794 | "RefreshTokenValidity": { 795 | "Ref": "AuthCognitoUserPoolRefreshTokenValidity" 796 | }, 797 | "ReadAttributes": [], 798 | "WriteAttributes": [] 799 | }, 800 | "Condition": "AuthShouldCreateUserPool" 801 | } 802 | }, 803 | "Outputs": { 804 | "GraphQLAPIIdOutput": { 805 | "Description": "Your GraphQL API ID.", 806 | "Value": { 807 | "Fn::GetAtt": [ 808 | "GraphQLAPI", 809 | "ApiId" 810 | ] 811 | }, 812 | "Export": { 813 | "Name": { 814 | "Fn::Join": [ 815 | ":", 816 | [ 817 | { 818 | "Ref": "AWS::StackName" 819 | }, 820 | "GraphQLApiId" 821 | ] 822 | ] 823 | } 824 | } 825 | }, 826 | "GraphQLAPIEndpointOutput": { 827 | "Description": "Your GraphQL API endpoint.", 828 | "Value": { 829 | "Fn::GetAtt": [ 830 | "GraphQLAPI", 831 | "GraphQLUrl" 832 | ] 833 | }, 834 | "Export": { 835 | "Name": { 836 | "Fn::Join": [ 837 | ":", 838 | [ 839 | { 840 | "Ref": "AWS::StackName" 841 | }, 842 | "GraphQLApiEndpoint" 843 | ] 844 | ] 845 | } 846 | } 847 | }, 848 | "GraphQLAPIKeyOutput": { 849 | "Description": "Your GraphQL API key. Provide via 'x-api-key' header.", 850 | "Value": { 851 | "Fn::GetAtt": [ 852 | "GraphQLAPIKey", 853 | "ApiKey" 854 | ] 855 | }, 856 | "Export": { 857 | "Name": { 858 | "Fn::Join": [ 859 | ":", 860 | [ 861 | { 862 | "Ref": "AWS::StackName" 863 | }, 864 | "GraphQLApiKey" 865 | ] 866 | ] 867 | } 868 | } 869 | }, 870 | "AuthCognitoUserPoolNativeClientId": { 871 | "Description": "Amazon Cognito UserPools native client ID", 872 | "Value": { 873 | "Fn::If": [ 874 | "AuthShouldCreateUserPool", 875 | { 876 | "Ref": "AuthCognitoUserPoolNativeClient" 877 | }, 878 | { 879 | "Fn::Join": [ 880 | " ", 881 | [ 882 | "See UserPool:", 883 | { 884 | "Ref": "AuthCognitoUserPoolId" 885 | } 886 | ] 887 | ] 888 | } 889 | ] 890 | }, 891 | "Export": { 892 | "Name": { 893 | "Fn::Join": [ 894 | ":", 895 | [ 896 | { 897 | "Ref": "AWS::StackName" 898 | }, 899 | "CognitoNativeClient" 900 | ] 901 | ] 902 | } 903 | } 904 | }, 905 | "AuthCognitoUserPoolJSClientId": { 906 | "Description": "Amazon Cognito UserPools JS client ID", 907 | "Value": { 908 | "Fn::If": [ 909 | "AuthShouldCreateUserPool", 910 | { 911 | "Ref": "AuthCognitoUserPoolJSClient" 912 | }, 913 | { 914 | "Fn::Join": [ 915 | " ", 916 | [ 917 | "See UserPool:", 918 | { 919 | "Ref": "AuthCognitoUserPoolId" 920 | } 921 | ] 922 | ] 923 | } 924 | ] 925 | }, 926 | "Export": { 927 | "Name": { 928 | "Fn::Join": [ 929 | ":", 930 | [ 931 | { 932 | "Ref": "AWS::StackName" 933 | }, 934 | "CognitoJSClient" 935 | ] 936 | ] 937 | } 938 | } 939 | }, 940 | "AuthCognitoUserPoolIdOutput": { 941 | "Description": "Amazon Cognito UserPool id", 942 | "Value": { 943 | "Fn::If": [ 944 | "AuthShouldCreateUserPool", 945 | { 946 | "Ref": "AuthCognitoUserPool" 947 | }, 948 | { 949 | "Ref": "AuthCognitoUserPoolId" 950 | } 951 | ] 952 | }, 953 | "Export": { 954 | "Name": { 955 | "Fn::Join": [ 956 | ":", 957 | [ 958 | { 959 | "Ref": "AWS::StackName" 960 | }, 961 | "CognitoUserPoolId" 962 | ] 963 | ] 964 | } 965 | } 966 | } 967 | }, 968 | "Conditions": { 969 | "HasEnvironmentParameter": { 970 | "Fn::Not": [ 971 | { 972 | "Fn::Equals": [ 973 | { 974 | "Ref": "env" 975 | }, 976 | "NONE" 977 | ] 978 | } 979 | ] 980 | }, 981 | "AuthShouldCreateUserPool": { 982 | "Fn::Equals": [ 983 | { 984 | "Ref": "AuthCognitoUserPoolId" 985 | }, 986 | "NONE" 987 | ] 988 | } 989 | } 990 | } -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSyncApiName": "reactnotes", 3 | "AuthCognitoUserPoolId": { 4 | "Fn::GetAtt": [ 5 | "authreactnotescognitoauth", 6 | "Outputs.UserPoolId" 7 | ] 8 | }, 9 | "schemaGraphql": "s3://react-notes-20181228114606-deployment/amplify-appsync-files/schema.graphql.1546457354441", 10 | "ResolverBucket": "react-notes-20181228114606-deployment", 11 | "ResolverRootKey": "amplify-appsync-files", 12 | "DeploymentTimestamp": "1546457354441" 13 | } -------------------------------------------------------------------------------- /amplify/backend/api/reactnotes/schema.graphql: -------------------------------------------------------------------------------- 1 | type Note @model { 2 | id: ID! 3 | name: String! 4 | description: String 5 | status: String! 6 | } -------------------------------------------------------------------------------- /amplify/backend/auth/reactnotescognitoauth/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "identityPoolName": "react_notes_id_pool", 3 | "allowUnauthenticatedIdentities": true, 4 | "thirdPartyAuth": false, 5 | "lambdaLogPolicy": "reactnotescognitoauth_lambda_log_policy", 6 | "openIdLambdaRoleName": "reactnotescognitoauth_openid_lambda_role", 7 | "openIdRolePolicy": "reactnotescognitoauth_openid_pass_role_policy", 8 | "openIdLambdaIAMPolicy": "reactnotescognitoauth_openid_lambda_iam_policy", 9 | "openIdLogPolicy": "reactnotescognitoauth_openid_lambda_log_policy", 10 | "userPoolName": "react_notes_user_pool", 11 | "autoVerifiedAttributes": [ 12 | "email" 13 | ], 14 | "mfaConfiguration": "OFF", 15 | "mfaTypes": [ 16 | "SMS Text Message" 17 | ], 18 | "roleName": "reactnotescognitoauth_sns-role", 19 | "roleExternalId": "reactnotescognitoauth_role_external_id", 20 | "policyName": "reactnotescognitoauth-sns-policy", 21 | "smsAuthenticationMessage": "Your authentication code is {####}", 22 | "smsVerificationMessage": "Your verification code is {####}", 23 | "emailVerificationSubject": "Your verification code", 24 | "emailVerificationMessage": "Your verification code is {####}", 25 | "defaultPasswordPolicy": false, 26 | "passwordPolicyMinLength": 8, 27 | "passwordPolicyCharacters": [ 28 | "Requires Lowercase", 29 | "Requires Uppercase", 30 | "Requires Numbers", 31 | "Requires Symbols" 32 | ], 33 | "requiredAttributes": [ 34 | "email" 35 | ], 36 | "userpoolClientName": "reactnotescognitoauth_app_client", 37 | "userpoolClientGenerateSecret": true, 38 | "userpoolClientRefreshTokenValidity": 30, 39 | "userpoolClientReadAttributes": [ 40 | "email" 41 | ], 42 | "mfaLambdaRole": "reactnotescognitoauth_totp_lambda_role", 43 | "mfaLambdaLogPolicy": "reactnotescognitoauth_totp_lambda_log_policy", 44 | "mfaPassRolePolicy": "reactnotescognitoauth_totp_pass_role_policy", 45 | "mfaLambdaIAMPolicy": "reactnotescognitoauth_totp_lambda_iam_policy", 46 | "userpoolClientLambdaRole": "reactnotescognitoauth_userpoolclient_lambda_role", 47 | "userpoolClientLogPolicy": "reactnotescognitoauth_userpoolclient_lambda_log_policy", 48 | "userpoolClientLambdaPolicy": "reactnotescognitoauth_userpoolclient_lambda_iam_policy", 49 | "userpoolClientSetAttributes": false, 50 | "useDefault": "manual", 51 | "authSelections": "identityPoolAndUserPool", 52 | "resourceName": "reactnotescognitoauth", 53 | "authRoleName": { 54 | "Ref": "AuthRoleName" 55 | }, 56 | "unauthRoleName": { 57 | "Ref": "UnauthRoleName" 58 | }, 59 | "authRoleArn": { 60 | "Fn::GetAtt": [ 61 | "AuthRole", 62 | "Arn" 63 | ] 64 | }, 65 | "unauthRoleArn": { 66 | "Fn::GetAtt": [ 67 | "UnauthRole", 68 | "Arn" 69 | ] 70 | } 71 | } -------------------------------------------------------------------------------- /amplify/backend/auth/reactnotescognitoauth/reactnotescognitoauth-cloudformation-template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | 3 | Parameters: 4 | env: 5 | Type: String 6 | authRoleName: 7 | Type: String 8 | unauthRoleName: 9 | Type: String 10 | authRoleArn: 11 | Type: String 12 | unauthRoleArn: 13 | Type: String 14 | 15 | 16 | identityPoolName: 17 | Type: String 18 | 19 | allowUnauthenticatedIdentities: 20 | Type: String 21 | 22 | thirdPartyAuth: 23 | Type: String 24 | 25 | lambdaLogPolicy: 26 | Type: String 27 | 28 | openIdLambdaRoleName: 29 | Type: String 30 | 31 | openIdRolePolicy: 32 | Type: String 33 | 34 | openIdLambdaIAMPolicy: 35 | Type: String 36 | 37 | openIdLogPolicy: 38 | Type: String 39 | 40 | userPoolName: 41 | Type: String 42 | 43 | autoVerifiedAttributes: 44 | Type: CommaDelimitedList 45 | 46 | mfaConfiguration: 47 | Type: String 48 | 49 | mfaTypes: 50 | Type: CommaDelimitedList 51 | 52 | roleName: 53 | Type: String 54 | 55 | roleExternalId: 56 | Type: String 57 | 58 | policyName: 59 | Type: String 60 | 61 | smsAuthenticationMessage: 62 | Type: String 63 | 64 | smsVerificationMessage: 65 | Type: String 66 | 67 | emailVerificationSubject: 68 | Type: String 69 | 70 | emailVerificationMessage: 71 | Type: String 72 | 73 | defaultPasswordPolicy: 74 | Type: String 75 | 76 | passwordPolicyMinLength: 77 | Type: Number 78 | 79 | passwordPolicyCharacters: 80 | Type: CommaDelimitedList 81 | 82 | requiredAttributes: 83 | Type: CommaDelimitedList 84 | 85 | userpoolClientName: 86 | Type: String 87 | 88 | userpoolClientGenerateSecret: 89 | Type: String 90 | 91 | userpoolClientRefreshTokenValidity: 92 | Type: Number 93 | 94 | userpoolClientReadAttributes: 95 | Type: CommaDelimitedList 96 | 97 | mfaLambdaRole: 98 | Type: String 99 | 100 | mfaLambdaLogPolicy: 101 | Type: String 102 | 103 | mfaPassRolePolicy: 104 | Type: String 105 | 106 | mfaLambdaIAMPolicy: 107 | Type: String 108 | 109 | userpoolClientLambdaRole: 110 | Type: String 111 | 112 | userpoolClientLogPolicy: 113 | Type: String 114 | 115 | userpoolClientLambdaPolicy: 116 | Type: String 117 | 118 | userpoolClientSetAttributes: 119 | Type: String 120 | 121 | useDefault: 122 | Type: String 123 | 124 | authSelections: 125 | Type: String 126 | 127 | resourceName: 128 | Type: String 129 | 130 | Conditions: 131 | ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ] 132 | 133 | Resources: 134 | 135 | # BEGIN SNS ROLE RESOURCE 136 | SNSRole: 137 | # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process 138 | Type: AWS::IAM::Role 139 | Properties: 140 | RoleName: !If [ShouldNotCreateEnvResources, !Ref roleName, !Join ['',[!Ref roleName, '-', !Ref env]]] 141 | AssumeRolePolicyDocument: 142 | Version: "2012-10-17" 143 | Statement: 144 | - Sid: "" 145 | Effect: "Allow" 146 | Principal: 147 | Service: "cognito-idp.amazonaws.com" 148 | Action: 149 | - "sts:AssumeRole" 150 | Condition: 151 | StringEquals: 152 | sts:ExternalId: !Ref roleExternalId 153 | Policies: 154 | - 155 | PolicyName: !Ref policyName 156 | PolicyDocument: 157 | Version: "2012-10-17" 158 | Statement: 159 | - 160 | Effect: "Allow" 161 | Action: 162 | - "sns:Publish" 163 | Resource: "*" 164 | # BEGIN USER POOL RESOURCES 165 | UserPool: 166 | # Created upon user selection 167 | # Depends on SNS Role for Arn if MFA is enabled 168 | Type: AWS::Cognito::UserPool 169 | Properties: 170 | UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]] 171 | Schema: 172 | 173 | - 174 | Name: email 175 | Required: true 176 | Mutable: true 177 | 178 | 179 | AutoVerifiedAttributes: !Ref autoVerifiedAttributes 180 | 181 | 182 | EmailVerificationMessage: !Ref emailVerificationMessage 183 | EmailVerificationSubject: !Ref emailVerificationSubject 184 | 185 | Policies: 186 | PasswordPolicy: 187 | MinimumLength: !Ref passwordPolicyMinLength 188 | RequireLowercase: true 189 | RequireNumbers: true 190 | RequireSymbols: true 191 | RequireUppercase: true 192 | MfaConfiguration: !Ref mfaConfiguration 193 | SmsVerificationMessage: !Ref smsVerificationMessage 194 | SmsConfiguration: 195 | SnsCallerArn: !GetAtt SNSRole.Arn 196 | ExternalId: !Ref roleExternalId 197 | 198 | UserPoolClientWeb: 199 | # Created provide application access to user pool 200 | # Depends on UserPool for ID reference 201 | Type: "AWS::Cognito::UserPoolClient" 202 | Properties: 203 | ClientName: reactnotescognitoauth_app_clientWeb 204 | 205 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 206 | UserPoolId: !Ref UserPool 207 | DependsOn: UserPool 208 | UserPoolClient: 209 | # Created provide application access to user pool 210 | # Depends on UserPool for ID reference 211 | Type: "AWS::Cognito::UserPoolClient" 212 | Properties: 213 | ClientName: !Ref userpoolClientName 214 | 215 | GenerateSecret: !Ref userpoolClientGenerateSecret 216 | RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity 217 | UserPoolId: !Ref UserPool 218 | DependsOn: UserPool 219 | # BEGIN USER POOL LAMBDA RESOURCES 220 | UserPoolClientRole: 221 | # Created to execute Lambda which gets userpool app client config values 222 | Type: 'AWS::IAM::Role' 223 | Properties: 224 | RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 225 | AssumeRolePolicyDocument: 226 | Version: '2012-10-17' 227 | Statement: 228 | - Effect: Allow 229 | Principal: 230 | Service: 231 | - lambda.amazonaws.com 232 | Action: 233 | - 'sts:AssumeRole' 234 | DependsOn: UserPoolClient 235 | UserPoolClientLambda: 236 | # Lambda which gets userpool app client config values 237 | # Depends on UserPool for id 238 | # Depends on UserPoolClientRole for role ARN 239 | Type: 'AWS::Lambda::Function' 240 | Properties: 241 | Code: 242 | ZipFile: !Join 243 | - |+ 244 | - - 'const response = require(''cfn-response'');' 245 | - 'const aws = require(''aws-sdk'');' 246 | - 'const identity = new aws.CognitoIdentityServiceProvider();' 247 | - 'exports.handler = (event, context, callback) => {' 248 | - ' if (event.RequestType == ''Delete'') { ' 249 | - ' response.send(event, context, response.SUCCESS, {})' 250 | - ' }' 251 | - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {' 252 | - ' const params = {' 253 | - ' ClientId: event.ResourceProperties.clientId,' 254 | - ' UserPoolId: event.ResourceProperties.userpoolId' 255 | - ' };' 256 | - ' identity.describeUserPoolClient(params).promise()' 257 | - ' .then((res) => {' 258 | - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});' 259 | - ' })' 260 | - ' .catch((err) => {' 261 | - ' response.send(event, context, response.FAILURE, {err});' 262 | - ' });' 263 | - ' }' 264 | - '};' 265 | Handler: index.handler 266 | Runtime: nodejs8.10 267 | Timeout: '300' 268 | Role: !GetAtt 269 | - UserPoolClientRole 270 | - Arn 271 | DependsOn: UserPoolClientRole 272 | UserPoolClientLambdaPolicy: 273 | # Sets userpool policy for the role that executes the Userpool Client Lambda 274 | # Depends on UserPool for Arn 275 | # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing 276 | Type: 'AWS::IAM::Policy' 277 | Properties: 278 | PolicyName: !Ref userpoolClientLambdaPolicy 279 | Roles: 280 | - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 281 | PolicyDocument: 282 | Version: '2012-10-17' 283 | Statement: 284 | - Effect: Allow 285 | Action: 286 | - 'cognito-idp:DescribeUserPoolClient' 287 | Resource: !GetAtt UserPool.Arn 288 | DependsOn: UserPoolClientLambda 289 | UserPoolClientLogPolicy: 290 | # Sets log policy for the role that executes the Userpool Client Lambda 291 | # Depends on UserPool for Arn 292 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 293 | Type: 'AWS::IAM::Policy' 294 | Properties: 295 | PolicyName: !Ref userpoolClientLogPolicy 296 | Roles: 297 | - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] 298 | PolicyDocument: 299 | Version: 2012-10-17 300 | Statement: 301 | - Effect: Allow 302 | Action: 303 | - 'logs:CreateLogGroup' 304 | - 'logs:CreateLogStream' 305 | - 'logs:PutLogEvents' 306 | Resource: !Sub 307 | - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:* 308 | - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda} 309 | DependsOn: UserPoolClientLambdaPolicy 310 | UserPoolClientInputs: 311 | # Values passed to Userpool client Lambda 312 | # Depends on UserPool for Id 313 | # Depends on UserPoolClient for Id 314 | # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing 315 | Type: 'Custom::LambdaCallout' 316 | Properties: 317 | ServiceToken: !GetAtt UserPoolClientLambda.Arn 318 | clientId: !Ref UserPoolClient 319 | userpoolId: !Ref UserPool 320 | DependsOn: UserPoolClientLogPolicy 321 | 322 | 323 | # BEGIN IDENTITY POOL RESOURCES 324 | 325 | 326 | IdentityPool: 327 | # Always created 328 | Type: AWS::Cognito::IdentityPool 329 | Properties: 330 | IdentityPoolName: !If [ShouldNotCreateEnvResources, 'react_notes_id_pool', !Join ['',['react_notes_id_pool', '__', !Ref env]]] 331 | 332 | CognitoIdentityProviders: 333 | - ClientId: !Ref UserPoolClient 334 | ProviderName: !Sub 335 | - cognito-idp.${region}.amazonaws.com/${client} 336 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 337 | - ClientId: !Ref UserPoolClientWeb 338 | ProviderName: !Sub 339 | - cognito-idp.${region}.amazonaws.com/${client} 340 | - { region: !Ref "AWS::Region", client: !Ref UserPool} 341 | 342 | AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities 343 | 344 | 345 | DependsOn: UserPoolClientInputs 346 | 347 | 348 | IdentityPoolRoleMap: 349 | # Created to map Auth and Unauth roles to the identity pool 350 | # Depends on Identity Pool for ID ref 351 | Type: AWS::Cognito::IdentityPoolRoleAttachment 352 | Properties: 353 | IdentityPoolId: !Ref IdentityPool 354 | Roles: 355 | unauthenticated: !Ref unauthRoleArn 356 | authenticated: !Ref authRoleArn 357 | DependsOn: IdentityPool 358 | 359 | 360 | Outputs : 361 | 362 | IdentityPoolId: 363 | Value: !Ref 'IdentityPool' 364 | Description: Id for the identity pool 365 | IdentityPoolName: 366 | Value: !GetAtt IdentityPool.Name 367 | 368 | 369 | UserPoolId: 370 | Value: !Ref 'UserPool' 371 | Description: Id for the user pool 372 | UserPoolName: 373 | Value: !Ref userPoolName 374 | AppClientIDWeb: 375 | Value: !Ref 'UserPoolClientWeb' 376 | Description: The user pool app client id for web 377 | AppClientID: 378 | Value: !Ref 'UserPoolClient' 379 | Description: The user pool app client id 380 | AppClientSecret: 381 | Value: !GetAtt UserPoolClientInputs.appSecret 382 | 383 | 384 | 385 | 386 | 387 | 388 | -------------------------------------------------------------------------------- /amplify/backend/awscloudformation/nested-cloudformation-stack.yml: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Root stack for the Amplify AWS CloudFormation provider", 4 | "Parameters": { 5 | "DeploymentBucketName": { 6 | "Description": "Name of the common deployment bucket provided by the parent stack", 7 | "Type": "String", 8 | "Default": "DeploymentBucket" 9 | }, 10 | "AuthRoleName": { 11 | "Type": "String", 12 | "Default": "AuthRoleName" 13 | }, 14 | "UnauthRoleName": { 15 | "Type": "String", 16 | "Default": "UnauthRoleName" 17 | } 18 | }, 19 | "Resources": { 20 | "DeploymentBucket": { 21 | "Type": "AWS::S3::Bucket", 22 | "DeletionPolicy": "Retain", 23 | "Properties": { 24 | "BucketName": { 25 | "Ref": "DeploymentBucketName" 26 | } 27 | } 28 | }, 29 | "AuthRole": { 30 | "Type": "AWS::IAM::Role", 31 | "Properties": { 32 | "RoleName": { 33 | "Ref": "AuthRoleName" 34 | }, 35 | "AssumeRolePolicyDocument": { 36 | "Version": "2012-10-17", 37 | "Statement": [ 38 | { 39 | "Sid": "", 40 | "Effect": "Allow", 41 | "Principal": { 42 | "Federated": "cognito-identity.amazonaws.com" 43 | }, 44 | "Action": "sts:AssumeRoleWithWebIdentity", 45 | "Condition": { 46 | "ForAnyValue:StringLike": { 47 | "cognito-identity.amazonaws.com:amr": "authenticated" 48 | } 49 | } 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | "UnauthRole": { 56 | "Type": "AWS::IAM::Role", 57 | "Properties": { 58 | "RoleName": { 59 | "Ref": "UnauthRoleName" 60 | }, 61 | "AssumeRolePolicyDocument": { 62 | "Version": "2012-10-17", 63 | "Statement": [ 64 | { 65 | "Sid": "", 66 | "Effect": "Allow", 67 | "Principal": { 68 | "Federated": "cognito-identity.amazonaws.com" 69 | }, 70 | "Action": "sts:AssumeRoleWithWebIdentity", 71 | "Condition": { 72 | "ForAnyValue:StringLike": { 73 | "cognito-identity.amazonaws.com:amr": "unauthenticated" 74 | } 75 | } 76 | } 77 | ] 78 | } 79 | } 80 | }, 81 | "authreactnotescognitoauth": { 82 | "Type": "AWS::CloudFormation::Stack", 83 | "Properties": { 84 | "TemplateURL": "https://s3.amazonaws.com/react-notes-20181228114606-deployment/amplify-cfn-templates/auth/reactnotescognitoauth-cloudformation-template.yml", 85 | "Parameters": { 86 | "identityPoolName": "react_notes_id_pool", 87 | "allowUnauthenticatedIdentities": true, 88 | "thirdPartyAuth": false, 89 | "lambdaLogPolicy": "reactnotescognitoauth_lambda_log_policy", 90 | "openIdLambdaRoleName": "reactnotescognitoauth_openid_lambda_role", 91 | "openIdRolePolicy": "reactnotescognitoauth_openid_pass_role_policy", 92 | "openIdLambdaIAMPolicy": "reactnotescognitoauth_openid_lambda_iam_policy", 93 | "openIdLogPolicy": "reactnotescognitoauth_openid_lambda_log_policy", 94 | "userPoolName": "react_notes_user_pool", 95 | "autoVerifiedAttributes": "email", 96 | "mfaConfiguration": "OFF", 97 | "mfaTypes": "SMS Text Message", 98 | "roleName": "reactnotescognitoauth_sns-role", 99 | "roleExternalId": "reactnotescognitoauth_role_external_id", 100 | "policyName": "reactnotescognitoauth-sns-policy", 101 | "smsAuthenticationMessage": "Your authentication code is {####}", 102 | "smsVerificationMessage": "Your verification code is {####}", 103 | "emailVerificationSubject": "Your verification code", 104 | "emailVerificationMessage": "Your verification code is {####}", 105 | "defaultPasswordPolicy": false, 106 | "passwordPolicyMinLength": 8, 107 | "passwordPolicyCharacters": "Requires Lowercase,Requires Uppercase,Requires Numbers,Requires Symbols", 108 | "requiredAttributes": "email", 109 | "userpoolClientName": "reactnotescognitoauth_app_client", 110 | "userpoolClientGenerateSecret": true, 111 | "userpoolClientRefreshTokenValidity": 30, 112 | "userpoolClientReadAttributes": "email", 113 | "mfaLambdaRole": "reactnotescognitoauth_totp_lambda_role", 114 | "mfaLambdaLogPolicy": "reactnotescognitoauth_totp_lambda_log_policy", 115 | "mfaPassRolePolicy": "reactnotescognitoauth_totp_pass_role_policy", 116 | "mfaLambdaIAMPolicy": "reactnotescognitoauth_totp_lambda_iam_policy", 117 | "userpoolClientLambdaRole": "reactnotescognitoauth_userpoolclient_lambda_role", 118 | "userpoolClientLogPolicy": "reactnotescognitoauth_userpoolclient_lambda_log_policy", 119 | "userpoolClientLambdaPolicy": "reactnotescognitoauth_userpoolclient_lambda_iam_policy", 120 | "userpoolClientSetAttributes": false, 121 | "useDefault": "manual", 122 | "authSelections": "identityPoolAndUserPool", 123 | "resourceName": "reactnotescognitoauth", 124 | "authRoleName": { 125 | "Ref": "AuthRoleName" 126 | }, 127 | "unauthRoleName": { 128 | "Ref": "UnauthRoleName" 129 | }, 130 | "authRoleArn": { 131 | "Fn::GetAtt": [ 132 | "AuthRole", 133 | "Arn" 134 | ] 135 | }, 136 | "unauthRoleArn": { 137 | "Fn::GetAtt": [ 138 | "UnauthRole", 139 | "Arn" 140 | ] 141 | }, 142 | "env": "dev" 143 | } 144 | } 145 | }, 146 | "apireactnotes": { 147 | "Type": "AWS::CloudFormation::Stack", 148 | "Properties": { 149 | "TemplateURL": "https://s3.amazonaws.com/react-notes-20181228114606-deployment/amplify-cfn-templates/api/cloudformation-template.json", 150 | "Parameters": { 151 | "AppSyncApiName": "reactnotes", 152 | "AuthCognitoUserPoolId": { 153 | "Fn::GetAtt": [ 154 | "authreactnotescognitoauth", 155 | "Outputs.UserPoolId" 156 | ] 157 | }, 158 | "schemaGraphql": "s3://react-notes-20181228114606-deployment/amplify-appsync-files/schema.graphql.1546457354441", 159 | "ResolverBucket": "react-notes-20181228114606-deployment", 160 | "ResolverRootKey": "amplify-appsync-files", 161 | "DeploymentTimestamp": "1546457354441", 162 | "env": "dev" 163 | } 164 | } 165 | }, 166 | "analyticsreactnotes": { 167 | "Type": "AWS::CloudFormation::Stack", 168 | "Properties": { 169 | "TemplateURL": "https://s3.amazonaws.com/react-notes-20181228114606-deployment/amplify-cfn-templates/analytics/pinpoint-cloudformation-template.json", 170 | "Parameters": { 171 | "appName": "reactnotes", 172 | "roleName": "pinpointLambdaRoleda8c3f26", 173 | "cloudWatchPolicyName": "cloudWatchPolicyda8c3f26", 174 | "pinpointPolicyName": "pinpointPolicyda8c3f26", 175 | "authPolicyName": "pinpoint_amplify_da8c3f26", 176 | "unauthPolicyName": "pinpoint_amplify_da8c3f26", 177 | "authRoleName": { 178 | "Ref": "AuthRoleName" 179 | }, 180 | "unauthRoleName": { 181 | "Ref": "UnauthRoleName" 182 | }, 183 | "authRoleArn": { 184 | "Fn::GetAtt": [ 185 | "AuthRole", 186 | "Arn" 187 | ] 188 | }, 189 | "env": "dev" 190 | } 191 | } 192 | } 193 | }, 194 | "Outputs": { 195 | "Region": { 196 | "Description": "CloudFormation provider root stack Region", 197 | "Value": { 198 | "Ref": "AWS::Region" 199 | }, 200 | "Export": { 201 | "Name": { 202 | "Fn::Sub": "${AWS::StackName}-Region" 203 | } 204 | } 205 | }, 206 | "StackName": { 207 | "Description": "CloudFormation provider root stack ID", 208 | "Value": { 209 | "Ref": "AWS::StackName" 210 | }, 211 | "Export": { 212 | "Name": { 213 | "Fn::Sub": "${AWS::StackName}-StackName" 214 | } 215 | } 216 | }, 217 | "StackId": { 218 | "Description": "CloudFormation provider root stack name", 219 | "Value": { 220 | "Ref": "AWS::StackId" 221 | }, 222 | "Export": { 223 | "Name": { 224 | "Fn::Sub": "${AWS::StackName}-StackId" 225 | } 226 | } 227 | }, 228 | "DeploymentBucketName": { 229 | "Description": "CloudFormation provider root stack deployment bucket name", 230 | "Value": { 231 | "Ref": "DeploymentBucketName" 232 | }, 233 | "Export": { 234 | "Name": { 235 | "Fn::Sub": "${AWS::StackName}-DeploymentBucketName" 236 | } 237 | } 238 | }, 239 | "AuthRoleArn": { 240 | "Value": { 241 | "Fn::GetAtt": [ 242 | "AuthRole", 243 | "Arn" 244 | ] 245 | } 246 | }, 247 | "UnauthRoleArn": { 248 | "Value": { 249 | "Fn::GetAtt": [ 250 | "UnauthRole", 251 | "Arn" 252 | ] 253 | } 254 | }, 255 | "AuthRoleName": { 256 | "Value": { 257 | "Ref": "AuthRole" 258 | } 259 | }, 260 | "UnauthRoleName": { 261 | "Value": { 262 | "Ref": "UnauthRole" 263 | } 264 | } 265 | } 266 | } -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "reactnotescognitoauth": { 4 | "service": "Cognito", 5 | "providerPlugin": "awscloudformation" 6 | } 7 | }, 8 | "api": { 9 | "reactnotes": { 10 | "service": "AppSync", 11 | "providerPlugin": "awscloudformation", 12 | "output": { 13 | "securityType": "AMAZON_COGNITO_USER_POOLS" 14 | } 15 | } 16 | }, 17 | "analytics": { 18 | "reactnotes": { 19 | "service": "Pinpoint", 20 | "providerPlugin": "awscloudformation" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /auth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabit3/react-notes/2dca8e54f768dfb431cd90f66e7d3e6c3c9c0a7a/auth.jpg -------------------------------------------------------------------------------- /hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabit3/react-notes/2dca8e54f768dfb431cd90f66e7d3e6c3c9c0a7a/hero.jpg -------------------------------------------------------------------------------- /notesapp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabit3/react-notes/2dca8e54f768dfb431cd90f66e7d3e6c3c9c0a7a/notesapp.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-notes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.18", 7 | "aws-amplify-react": "^2.2.5", 8 | "glamor": "^2.20.40", 9 | "react": "^16.7.0", 10 | "react-dom": "^16.7.0", 11 | "react-icons": "^3.2.2", 12 | "react-scripts": "2.1.2" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabit3/react-notes/2dca8e54f768dfb431cd90f66e7d3e6c3c9c0a7a/preview.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabit3/react-notes/2dca8e54f768dfb431cd90f66e7d3e6c3c9c0a7a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-link { 22 | color: #61dafb; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { API, graphqlOperation } from 'aws-amplify' 3 | import { withAuthenticator } from 'aws-amplify-react' 4 | import { css } from 'glamor' 5 | 6 | import Form from './components/Form' 7 | import Notes from './components/Notes' 8 | import { createNote, updateNote, deleteNote } from './graphql/mutations' 9 | import { listNotes } from './graphql/queries' 10 | 11 | class App extends Component { 12 | state = { notes: [], filter: 'none' } 13 | async componentDidMount() { 14 | try { 15 | const { data: { listNotes: { items }}} = await API.graphql(graphqlOperation(listNotes)) 16 | this.setState({ notes: items }) 17 | } catch (err) { 18 | console.log('error fetching notes...', err) 19 | } 20 | } 21 | createNote = async note => { 22 | const notes = [note, ...this.state.notes] 23 | const newNotes = this.state.notes 24 | this.setState({ notes }) 25 | try { 26 | const data = await API.graphql(graphqlOperation(createNote, { input: note })) 27 | this.setState({ notes: [data.data.createNote, ...newNotes] }) 28 | } catch (err) { 29 | console.log('error creating note..', err) 30 | } 31 | } 32 | updateNote = async note => { 33 | const updatedNote = { 34 | ...note, 35 | status: note.status === 'new' ? 'completed' : 'new' 36 | } 37 | const index = this.state.notes.findIndex(i => i.id === note.id) 38 | const notes = [...this.state.notes] 39 | notes[index] = updatedNote 40 | this.setState({ notes }) 41 | 42 | try { 43 | await API.graphql(graphqlOperation(updateNote, { input: updatedNote })) 44 | } catch (err) { 45 | console.log('error updating note...', err) 46 | } 47 | } 48 | deleteNote = async note => { 49 | const input = { id: note.id } 50 | const notes = this.state.notes.filter(n => n.id !== note.id) 51 | this.setState({ notes }) 52 | try { 53 | await API.graphql(graphqlOperation(deleteNote, { input })) 54 | } catch (err) { 55 | console.log('error deleting note...', err) 56 | } 57 | } 58 | updateFilter = filter => this.setState({ filter }) 59 | render() { 60 | let { notes, filter } = this.state 61 | if (filter === 'completed') { 62 | notes = notes.filter(n => n.status === 'completed') 63 | } 64 | if (filter === 'new') { 65 | notes = notes.filter(n => n.status === 'new') 66 | } 67 | return ( 68 |
69 |

Notes

70 | 73 | 78 |
79 |

this.updateFilter('none')} 81 | {...css([ styles.menuItem, getStyle('none', filter)])} 82 | >All

83 |

this.updateFilter('completed')} 85 | {...css([styles.menuItem, getStyle('completed', filter)])} 86 | >Completed

87 |

this.updateFilter('new')} 89 | {...css([styles.menuItem, getStyle('new', filter)])} 90 | >Pending

91 |
92 |
93 | ); 94 | } 95 | } 96 | 97 | function getStyle(type, filter) { 98 | if (type === filter) { 99 | return { 100 | fontWeight: 'bold' 101 | } 102 | } 103 | } 104 | 105 | const styles = { 106 | bottomMenu: { 107 | display: 'flex', 108 | marginTop: 10, 109 | justifyContent: 'center' 110 | }, 111 | menuItem: { 112 | cursor: 'pointer', 113 | marginRight: 20 114 | }, 115 | title: { 116 | fontSize: 44, 117 | margin: '10px 0px' 118 | }, 119 | container: { 120 | textAlign: 'center' 121 | } 122 | } 123 | 124 | export default withAuthenticator(App, { includeGreetings: true }) 125 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/Form.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { css } from 'glamor' 3 | import { MdAdd } from 'react-icons/md' 4 | 5 | class Form extends React.Component { 6 | state = { name: '' } 7 | onChange = e => { 8 | this.setState({ name: e.target.value }) 9 | } 10 | handleKeyPress = (e) => { 11 | if (e.key === 'Enter' && this.state.name !== '') { 12 | const note = { 13 | ...this.state, status: 'new' 14 | } 15 | this.props.createNote(note) 16 | this.setState({ name: '' }) 17 | } 18 | } 19 | render() { 20 | return ( 21 |
22 |
23 | 24 | this.onChange(e)} 29 | value={this.state.name} 30 | /> 31 |
32 |
33 | ) 34 | } 35 | } 36 | 37 | const styles = { 38 | container: { 39 | width: 360, 40 | margin: '0 auto', 41 | borderBottom: '1px solid #ededed', 42 | }, 43 | form: { 44 | display: 'flex', 45 | justifyContent: 'center', 46 | alignItems: 'center' 47 | }, 48 | input: { 49 | height: 35, 50 | width: '360px', 51 | border: 'none', 52 | outline: 'none', 53 | marginLeft: 10, 54 | fontSize: 20, 55 | padding: 8, 56 | } 57 | } 58 | 59 | export default Form -------------------------------------------------------------------------------- /src/components/Note.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { css } from 'glamor' 3 | import { FaTimes, FaCircle } from 'react-icons/fa' 4 | import { MdCheckCircle } from 'react-icons/md'; 5 | 6 | class Note extends React.Component { 7 | render() { 8 | const { name, status } = this.props.note 9 | return ( 10 |
11 | { 12 | status === 'new' && ( 13 | this.props.updateNote(this.props.note)} 18 | /> 19 | ) 20 | } 21 | { 22 | status === 'completed' && ( 23 | this.props.updateNote(this.props.note)} 28 | /> 29 | ) 30 | } 31 |

{name}

32 |
33 | this.props.deleteNote(this.props.note)} 35 | color='red' 36 | size={22} 37 | {...css(styles.times)} 38 | /> 39 |
40 |
41 | ) 42 | } 43 | } 44 | 45 | const styles = { 46 | container: { 47 | borderBottom: '1px solid rgba(0, 0, 0, .15)', 48 | display: 'flex', 49 | alignItems: 'center' 50 | }, 51 | name: { 52 | textAlign: 'left', 53 | fontSize: 18 54 | }, 55 | iconContainer: { 56 | display: 'flex', 57 | flex: 1, 58 | justifyContent: 'flex-end', 59 | alignItems: 'center' 60 | }, 61 | new: { 62 | marginRight: 10, 63 | cursor: 'pointer', 64 | opacity: .3 65 | }, 66 | completed: { 67 | marginRight: 10, 68 | cursor: 'pointer' 69 | }, 70 | times: { 71 | cursor: 'pointer', 72 | opacity: 0.7 73 | } 74 | } 75 | 76 | export default Note -------------------------------------------------------------------------------- /src/components/Notes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { css } from 'glamor' 3 | 4 | import Note from './Note' 5 | 6 | class Notes extends React.Component { 7 | render() { 8 | return ( 9 |
10 | { 11 | this.props.notes.map((t, i) => ( 12 | 18 | )) 19 | } 20 |
21 | ) 22 | } 23 | } 24 | 25 | const styles = { 26 | container: { 27 | width: '360px', 28 | margin: '0 auto', 29 | '@media(max-width: 360px)': { 30 | width: 'calc(100% - 40px)' 31 | } 32 | } 33 | } 34 | 35 | export default Notes -------------------------------------------------------------------------------- /src/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | // eslint-disable 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const createNote = `mutation CreateNote($input: CreateNoteInput!) { 5 | createNote(input: $input) { 6 | id 7 | name 8 | description 9 | status 10 | } 11 | } 12 | `; 13 | export const updateNote = `mutation UpdateNote($input: UpdateNoteInput!) { 14 | updateNote(input: $input) { 15 | id 16 | name 17 | description 18 | status 19 | } 20 | } 21 | `; 22 | export const deleteNote = `mutation DeleteNote($input: DeleteNoteInput!) { 23 | deleteNote(input: $input) { 24 | id 25 | name 26 | description 27 | status 28 | } 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /src/graphql/queries.js: -------------------------------------------------------------------------------- 1 | // eslint-disable 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const getNote = `query GetNote($id: ID!) { 5 | getNote(id: $id) { 6 | id 7 | name 8 | description 9 | status 10 | } 11 | } 12 | `; 13 | export const listNotes = `query ListNotes( 14 | $filter: ModelNoteFilterInput 15 | $limit: Int 16 | $nextToken: String 17 | ) { 18 | listNotes(filter: $filter, limit: $limit, nextToken: $nextToken) { 19 | items { 20 | id 21 | name 22 | description 23 | status 24 | } 25 | nextToken 26 | } 27 | } 28 | `; 29 | -------------------------------------------------------------------------------- /src/graphql/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "__schema" : { 4 | "queryType" : { 5 | "name" : "Query" 6 | }, 7 | "mutationType" : { 8 | "name" : "Mutation" 9 | }, 10 | "subscriptionType" : { 11 | "name" : "Subscription" 12 | }, 13 | "types" : [ { 14 | "kind" : "OBJECT", 15 | "name" : "Query", 16 | "description" : null, 17 | "fields" : [ { 18 | "name" : "getNote", 19 | "description" : null, 20 | "args" : [ { 21 | "name" : "id", 22 | "description" : null, 23 | "type" : { 24 | "kind" : "NON_NULL", 25 | "name" : null, 26 | "ofType" : { 27 | "kind" : "SCALAR", 28 | "name" : "ID", 29 | "ofType" : null 30 | } 31 | }, 32 | "defaultValue" : null 33 | } ], 34 | "type" : { 35 | "kind" : "OBJECT", 36 | "name" : "Note", 37 | "ofType" : null 38 | }, 39 | "isDeprecated" : false, 40 | "deprecationReason" : null 41 | }, { 42 | "name" : "listNotes", 43 | "description" : null, 44 | "args" : [ { 45 | "name" : "filter", 46 | "description" : null, 47 | "type" : { 48 | "kind" : "INPUT_OBJECT", 49 | "name" : "ModelNoteFilterInput", 50 | "ofType" : null 51 | }, 52 | "defaultValue" : null 53 | }, { 54 | "name" : "limit", 55 | "description" : null, 56 | "type" : { 57 | "kind" : "SCALAR", 58 | "name" : "Int", 59 | "ofType" : null 60 | }, 61 | "defaultValue" : null 62 | }, { 63 | "name" : "nextToken", 64 | "description" : null, 65 | "type" : { 66 | "kind" : "SCALAR", 67 | "name" : "String", 68 | "ofType" : null 69 | }, 70 | "defaultValue" : null 71 | } ], 72 | "type" : { 73 | "kind" : "OBJECT", 74 | "name" : "ModelNoteConnection", 75 | "ofType" : null 76 | }, 77 | "isDeprecated" : false, 78 | "deprecationReason" : null 79 | } ], 80 | "inputFields" : null, 81 | "interfaces" : [ ], 82 | "enumValues" : null, 83 | "possibleTypes" : null 84 | }, { 85 | "kind" : "OBJECT", 86 | "name" : "Note", 87 | "description" : null, 88 | "fields" : [ { 89 | "name" : "id", 90 | "description" : null, 91 | "args" : [ ], 92 | "type" : { 93 | "kind" : "NON_NULL", 94 | "name" : null, 95 | "ofType" : { 96 | "kind" : "SCALAR", 97 | "name" : "ID", 98 | "ofType" : null 99 | } 100 | }, 101 | "isDeprecated" : false, 102 | "deprecationReason" : null 103 | }, { 104 | "name" : "name", 105 | "description" : null, 106 | "args" : [ ], 107 | "type" : { 108 | "kind" : "NON_NULL", 109 | "name" : null, 110 | "ofType" : { 111 | "kind" : "SCALAR", 112 | "name" : "String", 113 | "ofType" : null 114 | } 115 | }, 116 | "isDeprecated" : false, 117 | "deprecationReason" : null 118 | }, { 119 | "name" : "description", 120 | "description" : null, 121 | "args" : [ ], 122 | "type" : { 123 | "kind" : "SCALAR", 124 | "name" : "String", 125 | "ofType" : null 126 | }, 127 | "isDeprecated" : false, 128 | "deprecationReason" : null 129 | }, { 130 | "name" : "status", 131 | "description" : null, 132 | "args" : [ ], 133 | "type" : { 134 | "kind" : "NON_NULL", 135 | "name" : null, 136 | "ofType" : { 137 | "kind" : "SCALAR", 138 | "name" : "String", 139 | "ofType" : null 140 | } 141 | }, 142 | "isDeprecated" : false, 143 | "deprecationReason" : null 144 | } ], 145 | "inputFields" : null, 146 | "interfaces" : [ ], 147 | "enumValues" : null, 148 | "possibleTypes" : null 149 | }, { 150 | "kind" : "SCALAR", 151 | "name" : "ID", 152 | "description" : "Built-in ID", 153 | "fields" : null, 154 | "inputFields" : null, 155 | "interfaces" : null, 156 | "enumValues" : null, 157 | "possibleTypes" : null 158 | }, { 159 | "kind" : "SCALAR", 160 | "name" : "String", 161 | "description" : "Built-in String", 162 | "fields" : null, 163 | "inputFields" : null, 164 | "interfaces" : null, 165 | "enumValues" : null, 166 | "possibleTypes" : null 167 | }, { 168 | "kind" : "OBJECT", 169 | "name" : "ModelNoteConnection", 170 | "description" : null, 171 | "fields" : [ { 172 | "name" : "items", 173 | "description" : null, 174 | "args" : [ ], 175 | "type" : { 176 | "kind" : "LIST", 177 | "name" : null, 178 | "ofType" : { 179 | "kind" : "OBJECT", 180 | "name" : "Note", 181 | "ofType" : null 182 | } 183 | }, 184 | "isDeprecated" : false, 185 | "deprecationReason" : null 186 | }, { 187 | "name" : "nextToken", 188 | "description" : null, 189 | "args" : [ ], 190 | "type" : { 191 | "kind" : "SCALAR", 192 | "name" : "String", 193 | "ofType" : null 194 | }, 195 | "isDeprecated" : false, 196 | "deprecationReason" : null 197 | } ], 198 | "inputFields" : null, 199 | "interfaces" : [ ], 200 | "enumValues" : null, 201 | "possibleTypes" : null 202 | }, { 203 | "kind" : "INPUT_OBJECT", 204 | "name" : "ModelNoteFilterInput", 205 | "description" : null, 206 | "fields" : null, 207 | "inputFields" : [ { 208 | "name" : "id", 209 | "description" : null, 210 | "type" : { 211 | "kind" : "INPUT_OBJECT", 212 | "name" : "ModelIDFilterInput", 213 | "ofType" : null 214 | }, 215 | "defaultValue" : null 216 | }, { 217 | "name" : "name", 218 | "description" : null, 219 | "type" : { 220 | "kind" : "INPUT_OBJECT", 221 | "name" : "ModelStringFilterInput", 222 | "ofType" : null 223 | }, 224 | "defaultValue" : null 225 | }, { 226 | "name" : "description", 227 | "description" : null, 228 | "type" : { 229 | "kind" : "INPUT_OBJECT", 230 | "name" : "ModelStringFilterInput", 231 | "ofType" : null 232 | }, 233 | "defaultValue" : null 234 | }, { 235 | "name" : "status", 236 | "description" : null, 237 | "type" : { 238 | "kind" : "INPUT_OBJECT", 239 | "name" : "ModelStringFilterInput", 240 | "ofType" : null 241 | }, 242 | "defaultValue" : null 243 | }, { 244 | "name" : "and", 245 | "description" : null, 246 | "type" : { 247 | "kind" : "LIST", 248 | "name" : null, 249 | "ofType" : { 250 | "kind" : "INPUT_OBJECT", 251 | "name" : "ModelNoteFilterInput", 252 | "ofType" : null 253 | } 254 | }, 255 | "defaultValue" : null 256 | }, { 257 | "name" : "or", 258 | "description" : null, 259 | "type" : { 260 | "kind" : "LIST", 261 | "name" : null, 262 | "ofType" : { 263 | "kind" : "INPUT_OBJECT", 264 | "name" : "ModelNoteFilterInput", 265 | "ofType" : null 266 | } 267 | }, 268 | "defaultValue" : null 269 | }, { 270 | "name" : "not", 271 | "description" : null, 272 | "type" : { 273 | "kind" : "INPUT_OBJECT", 274 | "name" : "ModelNoteFilterInput", 275 | "ofType" : null 276 | }, 277 | "defaultValue" : null 278 | } ], 279 | "interfaces" : null, 280 | "enumValues" : null, 281 | "possibleTypes" : null 282 | }, { 283 | "kind" : "INPUT_OBJECT", 284 | "name" : "ModelIDFilterInput", 285 | "description" : null, 286 | "fields" : null, 287 | "inputFields" : [ { 288 | "name" : "ne", 289 | "description" : null, 290 | "type" : { 291 | "kind" : "SCALAR", 292 | "name" : "ID", 293 | "ofType" : null 294 | }, 295 | "defaultValue" : null 296 | }, { 297 | "name" : "eq", 298 | "description" : null, 299 | "type" : { 300 | "kind" : "SCALAR", 301 | "name" : "ID", 302 | "ofType" : null 303 | }, 304 | "defaultValue" : null 305 | }, { 306 | "name" : "le", 307 | "description" : null, 308 | "type" : { 309 | "kind" : "SCALAR", 310 | "name" : "ID", 311 | "ofType" : null 312 | }, 313 | "defaultValue" : null 314 | }, { 315 | "name" : "lt", 316 | "description" : null, 317 | "type" : { 318 | "kind" : "SCALAR", 319 | "name" : "ID", 320 | "ofType" : null 321 | }, 322 | "defaultValue" : null 323 | }, { 324 | "name" : "ge", 325 | "description" : null, 326 | "type" : { 327 | "kind" : "SCALAR", 328 | "name" : "ID", 329 | "ofType" : null 330 | }, 331 | "defaultValue" : null 332 | }, { 333 | "name" : "gt", 334 | "description" : null, 335 | "type" : { 336 | "kind" : "SCALAR", 337 | "name" : "ID", 338 | "ofType" : null 339 | }, 340 | "defaultValue" : null 341 | }, { 342 | "name" : "contains", 343 | "description" : null, 344 | "type" : { 345 | "kind" : "SCALAR", 346 | "name" : "ID", 347 | "ofType" : null 348 | }, 349 | "defaultValue" : null 350 | }, { 351 | "name" : "notContains", 352 | "description" : null, 353 | "type" : { 354 | "kind" : "SCALAR", 355 | "name" : "ID", 356 | "ofType" : null 357 | }, 358 | "defaultValue" : null 359 | }, { 360 | "name" : "between", 361 | "description" : null, 362 | "type" : { 363 | "kind" : "LIST", 364 | "name" : null, 365 | "ofType" : { 366 | "kind" : "SCALAR", 367 | "name" : "ID", 368 | "ofType" : null 369 | } 370 | }, 371 | "defaultValue" : null 372 | }, { 373 | "name" : "beginsWith", 374 | "description" : null, 375 | "type" : { 376 | "kind" : "SCALAR", 377 | "name" : "ID", 378 | "ofType" : null 379 | }, 380 | "defaultValue" : null 381 | } ], 382 | "interfaces" : null, 383 | "enumValues" : null, 384 | "possibleTypes" : null 385 | }, { 386 | "kind" : "INPUT_OBJECT", 387 | "name" : "ModelStringFilterInput", 388 | "description" : null, 389 | "fields" : null, 390 | "inputFields" : [ { 391 | "name" : "ne", 392 | "description" : null, 393 | "type" : { 394 | "kind" : "SCALAR", 395 | "name" : "String", 396 | "ofType" : null 397 | }, 398 | "defaultValue" : null 399 | }, { 400 | "name" : "eq", 401 | "description" : null, 402 | "type" : { 403 | "kind" : "SCALAR", 404 | "name" : "String", 405 | "ofType" : null 406 | }, 407 | "defaultValue" : null 408 | }, { 409 | "name" : "le", 410 | "description" : null, 411 | "type" : { 412 | "kind" : "SCALAR", 413 | "name" : "String", 414 | "ofType" : null 415 | }, 416 | "defaultValue" : null 417 | }, { 418 | "name" : "lt", 419 | "description" : null, 420 | "type" : { 421 | "kind" : "SCALAR", 422 | "name" : "String", 423 | "ofType" : null 424 | }, 425 | "defaultValue" : null 426 | }, { 427 | "name" : "ge", 428 | "description" : null, 429 | "type" : { 430 | "kind" : "SCALAR", 431 | "name" : "String", 432 | "ofType" : null 433 | }, 434 | "defaultValue" : null 435 | }, { 436 | "name" : "gt", 437 | "description" : null, 438 | "type" : { 439 | "kind" : "SCALAR", 440 | "name" : "String", 441 | "ofType" : null 442 | }, 443 | "defaultValue" : null 444 | }, { 445 | "name" : "contains", 446 | "description" : null, 447 | "type" : { 448 | "kind" : "SCALAR", 449 | "name" : "String", 450 | "ofType" : null 451 | }, 452 | "defaultValue" : null 453 | }, { 454 | "name" : "notContains", 455 | "description" : null, 456 | "type" : { 457 | "kind" : "SCALAR", 458 | "name" : "String", 459 | "ofType" : null 460 | }, 461 | "defaultValue" : null 462 | }, { 463 | "name" : "between", 464 | "description" : null, 465 | "type" : { 466 | "kind" : "LIST", 467 | "name" : null, 468 | "ofType" : { 469 | "kind" : "SCALAR", 470 | "name" : "String", 471 | "ofType" : null 472 | } 473 | }, 474 | "defaultValue" : null 475 | }, { 476 | "name" : "beginsWith", 477 | "description" : null, 478 | "type" : { 479 | "kind" : "SCALAR", 480 | "name" : "String", 481 | "ofType" : null 482 | }, 483 | "defaultValue" : null 484 | } ], 485 | "interfaces" : null, 486 | "enumValues" : null, 487 | "possibleTypes" : null 488 | }, { 489 | "kind" : "SCALAR", 490 | "name" : "Int", 491 | "description" : "Built-in Int", 492 | "fields" : null, 493 | "inputFields" : null, 494 | "interfaces" : null, 495 | "enumValues" : null, 496 | "possibleTypes" : null 497 | }, { 498 | "kind" : "OBJECT", 499 | "name" : "Mutation", 500 | "description" : null, 501 | "fields" : [ { 502 | "name" : "createNote", 503 | "description" : null, 504 | "args" : [ { 505 | "name" : "input", 506 | "description" : null, 507 | "type" : { 508 | "kind" : "NON_NULL", 509 | "name" : null, 510 | "ofType" : { 511 | "kind" : "INPUT_OBJECT", 512 | "name" : "CreateNoteInput", 513 | "ofType" : null 514 | } 515 | }, 516 | "defaultValue" : null 517 | } ], 518 | "type" : { 519 | "kind" : "OBJECT", 520 | "name" : "Note", 521 | "ofType" : null 522 | }, 523 | "isDeprecated" : false, 524 | "deprecationReason" : null 525 | }, { 526 | "name" : "updateNote", 527 | "description" : null, 528 | "args" : [ { 529 | "name" : "input", 530 | "description" : null, 531 | "type" : { 532 | "kind" : "NON_NULL", 533 | "name" : null, 534 | "ofType" : { 535 | "kind" : "INPUT_OBJECT", 536 | "name" : "UpdateNoteInput", 537 | "ofType" : null 538 | } 539 | }, 540 | "defaultValue" : null 541 | } ], 542 | "type" : { 543 | "kind" : "OBJECT", 544 | "name" : "Note", 545 | "ofType" : null 546 | }, 547 | "isDeprecated" : false, 548 | "deprecationReason" : null 549 | }, { 550 | "name" : "deleteNote", 551 | "description" : null, 552 | "args" : [ { 553 | "name" : "input", 554 | "description" : null, 555 | "type" : { 556 | "kind" : "NON_NULL", 557 | "name" : null, 558 | "ofType" : { 559 | "kind" : "INPUT_OBJECT", 560 | "name" : "DeleteNoteInput", 561 | "ofType" : null 562 | } 563 | }, 564 | "defaultValue" : null 565 | } ], 566 | "type" : { 567 | "kind" : "OBJECT", 568 | "name" : "Note", 569 | "ofType" : null 570 | }, 571 | "isDeprecated" : false, 572 | "deprecationReason" : null 573 | } ], 574 | "inputFields" : null, 575 | "interfaces" : [ ], 576 | "enumValues" : null, 577 | "possibleTypes" : null 578 | }, { 579 | "kind" : "INPUT_OBJECT", 580 | "name" : "CreateNoteInput", 581 | "description" : null, 582 | "fields" : null, 583 | "inputFields" : [ { 584 | "name" : "id", 585 | "description" : null, 586 | "type" : { 587 | "kind" : "SCALAR", 588 | "name" : "ID", 589 | "ofType" : null 590 | }, 591 | "defaultValue" : null 592 | }, { 593 | "name" : "name", 594 | "description" : null, 595 | "type" : { 596 | "kind" : "NON_NULL", 597 | "name" : null, 598 | "ofType" : { 599 | "kind" : "SCALAR", 600 | "name" : "String", 601 | "ofType" : null 602 | } 603 | }, 604 | "defaultValue" : null 605 | }, { 606 | "name" : "description", 607 | "description" : null, 608 | "type" : { 609 | "kind" : "SCALAR", 610 | "name" : "String", 611 | "ofType" : null 612 | }, 613 | "defaultValue" : null 614 | }, { 615 | "name" : "status", 616 | "description" : null, 617 | "type" : { 618 | "kind" : "NON_NULL", 619 | "name" : null, 620 | "ofType" : { 621 | "kind" : "SCALAR", 622 | "name" : "String", 623 | "ofType" : null 624 | } 625 | }, 626 | "defaultValue" : null 627 | } ], 628 | "interfaces" : null, 629 | "enumValues" : null, 630 | "possibleTypes" : null 631 | }, { 632 | "kind" : "INPUT_OBJECT", 633 | "name" : "UpdateNoteInput", 634 | "description" : null, 635 | "fields" : null, 636 | "inputFields" : [ { 637 | "name" : "id", 638 | "description" : null, 639 | "type" : { 640 | "kind" : "NON_NULL", 641 | "name" : null, 642 | "ofType" : { 643 | "kind" : "SCALAR", 644 | "name" : "ID", 645 | "ofType" : null 646 | } 647 | }, 648 | "defaultValue" : null 649 | }, { 650 | "name" : "name", 651 | "description" : null, 652 | "type" : { 653 | "kind" : "SCALAR", 654 | "name" : "String", 655 | "ofType" : null 656 | }, 657 | "defaultValue" : null 658 | }, { 659 | "name" : "description", 660 | "description" : null, 661 | "type" : { 662 | "kind" : "SCALAR", 663 | "name" : "String", 664 | "ofType" : null 665 | }, 666 | "defaultValue" : null 667 | }, { 668 | "name" : "status", 669 | "description" : null, 670 | "type" : { 671 | "kind" : "SCALAR", 672 | "name" : "String", 673 | "ofType" : null 674 | }, 675 | "defaultValue" : null 676 | } ], 677 | "interfaces" : null, 678 | "enumValues" : null, 679 | "possibleTypes" : null 680 | }, { 681 | "kind" : "INPUT_OBJECT", 682 | "name" : "DeleteNoteInput", 683 | "description" : null, 684 | "fields" : null, 685 | "inputFields" : [ { 686 | "name" : "id", 687 | "description" : null, 688 | "type" : { 689 | "kind" : "SCALAR", 690 | "name" : "ID", 691 | "ofType" : null 692 | }, 693 | "defaultValue" : null 694 | } ], 695 | "interfaces" : null, 696 | "enumValues" : null, 697 | "possibleTypes" : null 698 | }, { 699 | "kind" : "OBJECT", 700 | "name" : "Subscription", 701 | "description" : null, 702 | "fields" : [ { 703 | "name" : "onCreateNote", 704 | "description" : null, 705 | "args" : [ ], 706 | "type" : { 707 | "kind" : "OBJECT", 708 | "name" : "Note", 709 | "ofType" : null 710 | }, 711 | "isDeprecated" : false, 712 | "deprecationReason" : null 713 | }, { 714 | "name" : "onUpdateNote", 715 | "description" : null, 716 | "args" : [ ], 717 | "type" : { 718 | "kind" : "OBJECT", 719 | "name" : "Note", 720 | "ofType" : null 721 | }, 722 | "isDeprecated" : false, 723 | "deprecationReason" : null 724 | }, { 725 | "name" : "onDeleteNote", 726 | "description" : null, 727 | "args" : [ ], 728 | "type" : { 729 | "kind" : "OBJECT", 730 | "name" : "Note", 731 | "ofType" : null 732 | }, 733 | "isDeprecated" : false, 734 | "deprecationReason" : null 735 | } ], 736 | "inputFields" : null, 737 | "interfaces" : [ ], 738 | "enumValues" : null, 739 | "possibleTypes" : null 740 | }, { 741 | "kind" : "INPUT_OBJECT", 742 | "name" : "ModelIntFilterInput", 743 | "description" : null, 744 | "fields" : null, 745 | "inputFields" : [ { 746 | "name" : "ne", 747 | "description" : null, 748 | "type" : { 749 | "kind" : "SCALAR", 750 | "name" : "Int", 751 | "ofType" : null 752 | }, 753 | "defaultValue" : null 754 | }, { 755 | "name" : "eq", 756 | "description" : null, 757 | "type" : { 758 | "kind" : "SCALAR", 759 | "name" : "Int", 760 | "ofType" : null 761 | }, 762 | "defaultValue" : null 763 | }, { 764 | "name" : "le", 765 | "description" : null, 766 | "type" : { 767 | "kind" : "SCALAR", 768 | "name" : "Int", 769 | "ofType" : null 770 | }, 771 | "defaultValue" : null 772 | }, { 773 | "name" : "lt", 774 | "description" : null, 775 | "type" : { 776 | "kind" : "SCALAR", 777 | "name" : "Int", 778 | "ofType" : null 779 | }, 780 | "defaultValue" : null 781 | }, { 782 | "name" : "ge", 783 | "description" : null, 784 | "type" : { 785 | "kind" : "SCALAR", 786 | "name" : "Int", 787 | "ofType" : null 788 | }, 789 | "defaultValue" : null 790 | }, { 791 | "name" : "gt", 792 | "description" : null, 793 | "type" : { 794 | "kind" : "SCALAR", 795 | "name" : "Int", 796 | "ofType" : null 797 | }, 798 | "defaultValue" : null 799 | }, { 800 | "name" : "contains", 801 | "description" : null, 802 | "type" : { 803 | "kind" : "SCALAR", 804 | "name" : "Int", 805 | "ofType" : null 806 | }, 807 | "defaultValue" : null 808 | }, { 809 | "name" : "notContains", 810 | "description" : null, 811 | "type" : { 812 | "kind" : "SCALAR", 813 | "name" : "Int", 814 | "ofType" : null 815 | }, 816 | "defaultValue" : null 817 | }, { 818 | "name" : "between", 819 | "description" : null, 820 | "type" : { 821 | "kind" : "LIST", 822 | "name" : null, 823 | "ofType" : { 824 | "kind" : "SCALAR", 825 | "name" : "Int", 826 | "ofType" : null 827 | } 828 | }, 829 | "defaultValue" : null 830 | } ], 831 | "interfaces" : null, 832 | "enumValues" : null, 833 | "possibleTypes" : null 834 | }, { 835 | "kind" : "INPUT_OBJECT", 836 | "name" : "ModelFloatFilterInput", 837 | "description" : null, 838 | "fields" : null, 839 | "inputFields" : [ { 840 | "name" : "ne", 841 | "description" : null, 842 | "type" : { 843 | "kind" : "SCALAR", 844 | "name" : "Float", 845 | "ofType" : null 846 | }, 847 | "defaultValue" : null 848 | }, { 849 | "name" : "eq", 850 | "description" : null, 851 | "type" : { 852 | "kind" : "SCALAR", 853 | "name" : "Float", 854 | "ofType" : null 855 | }, 856 | "defaultValue" : null 857 | }, { 858 | "name" : "le", 859 | "description" : null, 860 | "type" : { 861 | "kind" : "SCALAR", 862 | "name" : "Float", 863 | "ofType" : null 864 | }, 865 | "defaultValue" : null 866 | }, { 867 | "name" : "lt", 868 | "description" : null, 869 | "type" : { 870 | "kind" : "SCALAR", 871 | "name" : "Float", 872 | "ofType" : null 873 | }, 874 | "defaultValue" : null 875 | }, { 876 | "name" : "ge", 877 | "description" : null, 878 | "type" : { 879 | "kind" : "SCALAR", 880 | "name" : "Float", 881 | "ofType" : null 882 | }, 883 | "defaultValue" : null 884 | }, { 885 | "name" : "gt", 886 | "description" : null, 887 | "type" : { 888 | "kind" : "SCALAR", 889 | "name" : "Float", 890 | "ofType" : null 891 | }, 892 | "defaultValue" : null 893 | }, { 894 | "name" : "contains", 895 | "description" : null, 896 | "type" : { 897 | "kind" : "SCALAR", 898 | "name" : "Float", 899 | "ofType" : null 900 | }, 901 | "defaultValue" : null 902 | }, { 903 | "name" : "notContains", 904 | "description" : null, 905 | "type" : { 906 | "kind" : "SCALAR", 907 | "name" : "Float", 908 | "ofType" : null 909 | }, 910 | "defaultValue" : null 911 | }, { 912 | "name" : "between", 913 | "description" : null, 914 | "type" : { 915 | "kind" : "LIST", 916 | "name" : null, 917 | "ofType" : { 918 | "kind" : "SCALAR", 919 | "name" : "Float", 920 | "ofType" : null 921 | } 922 | }, 923 | "defaultValue" : null 924 | } ], 925 | "interfaces" : null, 926 | "enumValues" : null, 927 | "possibleTypes" : null 928 | }, { 929 | "kind" : "SCALAR", 930 | "name" : "Float", 931 | "description" : "Built-in Float", 932 | "fields" : null, 933 | "inputFields" : null, 934 | "interfaces" : null, 935 | "enumValues" : null, 936 | "possibleTypes" : null 937 | }, { 938 | "kind" : "ENUM", 939 | "name" : "ModelSortDirection", 940 | "description" : null, 941 | "fields" : null, 942 | "inputFields" : null, 943 | "interfaces" : null, 944 | "enumValues" : [ { 945 | "name" : "ASC", 946 | "description" : null, 947 | "isDeprecated" : false, 948 | "deprecationReason" : null 949 | }, { 950 | "name" : "DESC", 951 | "description" : null, 952 | "isDeprecated" : false, 953 | "deprecationReason" : null 954 | } ], 955 | "possibleTypes" : null 956 | }, { 957 | "kind" : "INPUT_OBJECT", 958 | "name" : "ModelBooleanFilterInput", 959 | "description" : null, 960 | "fields" : null, 961 | "inputFields" : [ { 962 | "name" : "ne", 963 | "description" : null, 964 | "type" : { 965 | "kind" : "SCALAR", 966 | "name" : "Boolean", 967 | "ofType" : null 968 | }, 969 | "defaultValue" : null 970 | }, { 971 | "name" : "eq", 972 | "description" : null, 973 | "type" : { 974 | "kind" : "SCALAR", 975 | "name" : "Boolean", 976 | "ofType" : null 977 | }, 978 | "defaultValue" : null 979 | } ], 980 | "interfaces" : null, 981 | "enumValues" : null, 982 | "possibleTypes" : null 983 | }, { 984 | "kind" : "SCALAR", 985 | "name" : "Boolean", 986 | "description" : "Built-in Boolean", 987 | "fields" : null, 988 | "inputFields" : null, 989 | "interfaces" : null, 990 | "enumValues" : null, 991 | "possibleTypes" : null 992 | }, { 993 | "kind" : "OBJECT", 994 | "name" : "__Schema", 995 | "description" : "A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, the entry points for query, mutation, and subscription operations.", 996 | "fields" : [ { 997 | "name" : "types", 998 | "description" : "A list of all types supported by this server.", 999 | "args" : [ ], 1000 | "type" : { 1001 | "kind" : "NON_NULL", 1002 | "name" : null, 1003 | "ofType" : { 1004 | "kind" : "LIST", 1005 | "name" : null, 1006 | "ofType" : { 1007 | "kind" : "NON_NULL", 1008 | "name" : null, 1009 | "ofType" : { 1010 | "kind" : "OBJECT", 1011 | "name" : "__Type", 1012 | "ofType" : null 1013 | } 1014 | } 1015 | } 1016 | }, 1017 | "isDeprecated" : false, 1018 | "deprecationReason" : null 1019 | }, { 1020 | "name" : "queryType", 1021 | "description" : "The type that query operations will be rooted at.", 1022 | "args" : [ ], 1023 | "type" : { 1024 | "kind" : "NON_NULL", 1025 | "name" : null, 1026 | "ofType" : { 1027 | "kind" : "OBJECT", 1028 | "name" : "__Type", 1029 | "ofType" : null 1030 | } 1031 | }, 1032 | "isDeprecated" : false, 1033 | "deprecationReason" : null 1034 | }, { 1035 | "name" : "mutationType", 1036 | "description" : "If this server supports mutation, the type that mutation operations will be rooted at.", 1037 | "args" : [ ], 1038 | "type" : { 1039 | "kind" : "OBJECT", 1040 | "name" : "__Type", 1041 | "ofType" : null 1042 | }, 1043 | "isDeprecated" : false, 1044 | "deprecationReason" : null 1045 | }, { 1046 | "name" : "directives", 1047 | "description" : "'A list of all directives supported by this server.", 1048 | "args" : [ ], 1049 | "type" : { 1050 | "kind" : "NON_NULL", 1051 | "name" : null, 1052 | "ofType" : { 1053 | "kind" : "LIST", 1054 | "name" : null, 1055 | "ofType" : { 1056 | "kind" : "NON_NULL", 1057 | "name" : null, 1058 | "ofType" : { 1059 | "kind" : "OBJECT", 1060 | "name" : "__Directive", 1061 | "ofType" : null 1062 | } 1063 | } 1064 | } 1065 | }, 1066 | "isDeprecated" : false, 1067 | "deprecationReason" : null 1068 | }, { 1069 | "name" : "subscriptionType", 1070 | "description" : "'If this server support subscription, the type that subscription operations will be rooted at.", 1071 | "args" : [ ], 1072 | "type" : { 1073 | "kind" : "OBJECT", 1074 | "name" : "__Type", 1075 | "ofType" : null 1076 | }, 1077 | "isDeprecated" : false, 1078 | "deprecationReason" : null 1079 | } ], 1080 | "inputFields" : null, 1081 | "interfaces" : [ ], 1082 | "enumValues" : null, 1083 | "possibleTypes" : null 1084 | }, { 1085 | "kind" : "OBJECT", 1086 | "name" : "__Type", 1087 | "description" : null, 1088 | "fields" : [ { 1089 | "name" : "kind", 1090 | "description" : null, 1091 | "args" : [ ], 1092 | "type" : { 1093 | "kind" : "NON_NULL", 1094 | "name" : null, 1095 | "ofType" : { 1096 | "kind" : "ENUM", 1097 | "name" : "__TypeKind", 1098 | "ofType" : null 1099 | } 1100 | }, 1101 | "isDeprecated" : false, 1102 | "deprecationReason" : null 1103 | }, { 1104 | "name" : "name", 1105 | "description" : null, 1106 | "args" : [ ], 1107 | "type" : { 1108 | "kind" : "SCALAR", 1109 | "name" : "String", 1110 | "ofType" : null 1111 | }, 1112 | "isDeprecated" : false, 1113 | "deprecationReason" : null 1114 | }, { 1115 | "name" : "description", 1116 | "description" : null, 1117 | "args" : [ ], 1118 | "type" : { 1119 | "kind" : "SCALAR", 1120 | "name" : "String", 1121 | "ofType" : null 1122 | }, 1123 | "isDeprecated" : false, 1124 | "deprecationReason" : null 1125 | }, { 1126 | "name" : "fields", 1127 | "description" : null, 1128 | "args" : [ { 1129 | "name" : "includeDeprecated", 1130 | "description" : null, 1131 | "type" : { 1132 | "kind" : "SCALAR", 1133 | "name" : "Boolean", 1134 | "ofType" : null 1135 | }, 1136 | "defaultValue" : "false" 1137 | } ], 1138 | "type" : { 1139 | "kind" : "LIST", 1140 | "name" : null, 1141 | "ofType" : { 1142 | "kind" : "NON_NULL", 1143 | "name" : null, 1144 | "ofType" : { 1145 | "kind" : "OBJECT", 1146 | "name" : "__Field", 1147 | "ofType" : null 1148 | } 1149 | } 1150 | }, 1151 | "isDeprecated" : false, 1152 | "deprecationReason" : null 1153 | }, { 1154 | "name" : "interfaces", 1155 | "description" : null, 1156 | "args" : [ ], 1157 | "type" : { 1158 | "kind" : "LIST", 1159 | "name" : null, 1160 | "ofType" : { 1161 | "kind" : "NON_NULL", 1162 | "name" : null, 1163 | "ofType" : { 1164 | "kind" : "OBJECT", 1165 | "name" : "__Type", 1166 | "ofType" : null 1167 | } 1168 | } 1169 | }, 1170 | "isDeprecated" : false, 1171 | "deprecationReason" : null 1172 | }, { 1173 | "name" : "possibleTypes", 1174 | "description" : null, 1175 | "args" : [ ], 1176 | "type" : { 1177 | "kind" : "LIST", 1178 | "name" : null, 1179 | "ofType" : { 1180 | "kind" : "NON_NULL", 1181 | "name" : null, 1182 | "ofType" : { 1183 | "kind" : "OBJECT", 1184 | "name" : "__Type", 1185 | "ofType" : null 1186 | } 1187 | } 1188 | }, 1189 | "isDeprecated" : false, 1190 | "deprecationReason" : null 1191 | }, { 1192 | "name" : "enumValues", 1193 | "description" : null, 1194 | "args" : [ { 1195 | "name" : "includeDeprecated", 1196 | "description" : null, 1197 | "type" : { 1198 | "kind" : "SCALAR", 1199 | "name" : "Boolean", 1200 | "ofType" : null 1201 | }, 1202 | "defaultValue" : "false" 1203 | } ], 1204 | "type" : { 1205 | "kind" : "LIST", 1206 | "name" : null, 1207 | "ofType" : { 1208 | "kind" : "NON_NULL", 1209 | "name" : null, 1210 | "ofType" : { 1211 | "kind" : "OBJECT", 1212 | "name" : "__EnumValue", 1213 | "ofType" : null 1214 | } 1215 | } 1216 | }, 1217 | "isDeprecated" : false, 1218 | "deprecationReason" : null 1219 | }, { 1220 | "name" : "inputFields", 1221 | "description" : null, 1222 | "args" : [ ], 1223 | "type" : { 1224 | "kind" : "LIST", 1225 | "name" : null, 1226 | "ofType" : { 1227 | "kind" : "NON_NULL", 1228 | "name" : null, 1229 | "ofType" : { 1230 | "kind" : "OBJECT", 1231 | "name" : "__InputValue", 1232 | "ofType" : null 1233 | } 1234 | } 1235 | }, 1236 | "isDeprecated" : false, 1237 | "deprecationReason" : null 1238 | }, { 1239 | "name" : "ofType", 1240 | "description" : null, 1241 | "args" : [ ], 1242 | "type" : { 1243 | "kind" : "OBJECT", 1244 | "name" : "__Type", 1245 | "ofType" : null 1246 | }, 1247 | "isDeprecated" : false, 1248 | "deprecationReason" : null 1249 | } ], 1250 | "inputFields" : null, 1251 | "interfaces" : [ ], 1252 | "enumValues" : null, 1253 | "possibleTypes" : null 1254 | }, { 1255 | "kind" : "ENUM", 1256 | "name" : "__TypeKind", 1257 | "description" : "An enum describing what kind of type a given __Type is", 1258 | "fields" : null, 1259 | "inputFields" : null, 1260 | "interfaces" : null, 1261 | "enumValues" : [ { 1262 | "name" : "SCALAR", 1263 | "description" : "Indicates this type is a scalar.", 1264 | "isDeprecated" : false, 1265 | "deprecationReason" : null 1266 | }, { 1267 | "name" : "OBJECT", 1268 | "description" : "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 1269 | "isDeprecated" : false, 1270 | "deprecationReason" : null 1271 | }, { 1272 | "name" : "INTERFACE", 1273 | "description" : "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 1274 | "isDeprecated" : false, 1275 | "deprecationReason" : null 1276 | }, { 1277 | "name" : "UNION", 1278 | "description" : "Indicates this type is a union. `possibleTypes` is a valid field.", 1279 | "isDeprecated" : false, 1280 | "deprecationReason" : null 1281 | }, { 1282 | "name" : "ENUM", 1283 | "description" : "Indicates this type is an enum. `enumValues` is a valid field.", 1284 | "isDeprecated" : false, 1285 | "deprecationReason" : null 1286 | }, { 1287 | "name" : "INPUT_OBJECT", 1288 | "description" : "Indicates this type is an input object. `inputFields` is a valid field.", 1289 | "isDeprecated" : false, 1290 | "deprecationReason" : null 1291 | }, { 1292 | "name" : "LIST", 1293 | "description" : "Indicates this type is a list. `ofType` is a valid field.", 1294 | "isDeprecated" : false, 1295 | "deprecationReason" : null 1296 | }, { 1297 | "name" : "NON_NULL", 1298 | "description" : "Indicates this type is a non-null. `ofType` is a valid field.", 1299 | "isDeprecated" : false, 1300 | "deprecationReason" : null 1301 | } ], 1302 | "possibleTypes" : null 1303 | }, { 1304 | "kind" : "OBJECT", 1305 | "name" : "__Field", 1306 | "description" : null, 1307 | "fields" : [ { 1308 | "name" : "name", 1309 | "description" : null, 1310 | "args" : [ ], 1311 | "type" : { 1312 | "kind" : "NON_NULL", 1313 | "name" : null, 1314 | "ofType" : { 1315 | "kind" : "SCALAR", 1316 | "name" : "String", 1317 | "ofType" : null 1318 | } 1319 | }, 1320 | "isDeprecated" : false, 1321 | "deprecationReason" : null 1322 | }, { 1323 | "name" : "description", 1324 | "description" : null, 1325 | "args" : [ ], 1326 | "type" : { 1327 | "kind" : "SCALAR", 1328 | "name" : "String", 1329 | "ofType" : null 1330 | }, 1331 | "isDeprecated" : false, 1332 | "deprecationReason" : null 1333 | }, { 1334 | "name" : "args", 1335 | "description" : null, 1336 | "args" : [ ], 1337 | "type" : { 1338 | "kind" : "NON_NULL", 1339 | "name" : null, 1340 | "ofType" : { 1341 | "kind" : "LIST", 1342 | "name" : null, 1343 | "ofType" : { 1344 | "kind" : "NON_NULL", 1345 | "name" : null, 1346 | "ofType" : { 1347 | "kind" : "OBJECT", 1348 | "name" : "__InputValue", 1349 | "ofType" : null 1350 | } 1351 | } 1352 | } 1353 | }, 1354 | "isDeprecated" : false, 1355 | "deprecationReason" : null 1356 | }, { 1357 | "name" : "type", 1358 | "description" : null, 1359 | "args" : [ ], 1360 | "type" : { 1361 | "kind" : "NON_NULL", 1362 | "name" : null, 1363 | "ofType" : { 1364 | "kind" : "OBJECT", 1365 | "name" : "__Type", 1366 | "ofType" : null 1367 | } 1368 | }, 1369 | "isDeprecated" : false, 1370 | "deprecationReason" : null 1371 | }, { 1372 | "name" : "isDeprecated", 1373 | "description" : null, 1374 | "args" : [ ], 1375 | "type" : { 1376 | "kind" : "NON_NULL", 1377 | "name" : null, 1378 | "ofType" : { 1379 | "kind" : "SCALAR", 1380 | "name" : "Boolean", 1381 | "ofType" : null 1382 | } 1383 | }, 1384 | "isDeprecated" : false, 1385 | "deprecationReason" : null 1386 | }, { 1387 | "name" : "deprecationReason", 1388 | "description" : null, 1389 | "args" : [ ], 1390 | "type" : { 1391 | "kind" : "SCALAR", 1392 | "name" : "String", 1393 | "ofType" : null 1394 | }, 1395 | "isDeprecated" : false, 1396 | "deprecationReason" : null 1397 | } ], 1398 | "inputFields" : null, 1399 | "interfaces" : [ ], 1400 | "enumValues" : null, 1401 | "possibleTypes" : null 1402 | }, { 1403 | "kind" : "OBJECT", 1404 | "name" : "__InputValue", 1405 | "description" : null, 1406 | "fields" : [ { 1407 | "name" : "name", 1408 | "description" : null, 1409 | "args" : [ ], 1410 | "type" : { 1411 | "kind" : "NON_NULL", 1412 | "name" : null, 1413 | "ofType" : { 1414 | "kind" : "SCALAR", 1415 | "name" : "String", 1416 | "ofType" : null 1417 | } 1418 | }, 1419 | "isDeprecated" : false, 1420 | "deprecationReason" : null 1421 | }, { 1422 | "name" : "description", 1423 | "description" : null, 1424 | "args" : [ ], 1425 | "type" : { 1426 | "kind" : "SCALAR", 1427 | "name" : "String", 1428 | "ofType" : null 1429 | }, 1430 | "isDeprecated" : false, 1431 | "deprecationReason" : null 1432 | }, { 1433 | "name" : "type", 1434 | "description" : null, 1435 | "args" : [ ], 1436 | "type" : { 1437 | "kind" : "NON_NULL", 1438 | "name" : null, 1439 | "ofType" : { 1440 | "kind" : "OBJECT", 1441 | "name" : "__Type", 1442 | "ofType" : null 1443 | } 1444 | }, 1445 | "isDeprecated" : false, 1446 | "deprecationReason" : null 1447 | }, { 1448 | "name" : "defaultValue", 1449 | "description" : null, 1450 | "args" : [ ], 1451 | "type" : { 1452 | "kind" : "SCALAR", 1453 | "name" : "String", 1454 | "ofType" : null 1455 | }, 1456 | "isDeprecated" : false, 1457 | "deprecationReason" : null 1458 | } ], 1459 | "inputFields" : null, 1460 | "interfaces" : [ ], 1461 | "enumValues" : null, 1462 | "possibleTypes" : null 1463 | }, { 1464 | "kind" : "OBJECT", 1465 | "name" : "__EnumValue", 1466 | "description" : null, 1467 | "fields" : [ { 1468 | "name" : "name", 1469 | "description" : null, 1470 | "args" : [ ], 1471 | "type" : { 1472 | "kind" : "NON_NULL", 1473 | "name" : null, 1474 | "ofType" : { 1475 | "kind" : "SCALAR", 1476 | "name" : "String", 1477 | "ofType" : null 1478 | } 1479 | }, 1480 | "isDeprecated" : false, 1481 | "deprecationReason" : null 1482 | }, { 1483 | "name" : "description", 1484 | "description" : null, 1485 | "args" : [ ], 1486 | "type" : { 1487 | "kind" : "SCALAR", 1488 | "name" : "String", 1489 | "ofType" : null 1490 | }, 1491 | "isDeprecated" : false, 1492 | "deprecationReason" : null 1493 | }, { 1494 | "name" : "isDeprecated", 1495 | "description" : null, 1496 | "args" : [ ], 1497 | "type" : { 1498 | "kind" : "NON_NULL", 1499 | "name" : null, 1500 | "ofType" : { 1501 | "kind" : "SCALAR", 1502 | "name" : "Boolean", 1503 | "ofType" : null 1504 | } 1505 | }, 1506 | "isDeprecated" : false, 1507 | "deprecationReason" : null 1508 | }, { 1509 | "name" : "deprecationReason", 1510 | "description" : null, 1511 | "args" : [ ], 1512 | "type" : { 1513 | "kind" : "SCALAR", 1514 | "name" : "String", 1515 | "ofType" : null 1516 | }, 1517 | "isDeprecated" : false, 1518 | "deprecationReason" : null 1519 | } ], 1520 | "inputFields" : null, 1521 | "interfaces" : [ ], 1522 | "enumValues" : null, 1523 | "possibleTypes" : null 1524 | }, { 1525 | "kind" : "OBJECT", 1526 | "name" : "__Directive", 1527 | "description" : null, 1528 | "fields" : [ { 1529 | "name" : "name", 1530 | "description" : null, 1531 | "args" : [ ], 1532 | "type" : { 1533 | "kind" : "SCALAR", 1534 | "name" : "String", 1535 | "ofType" : null 1536 | }, 1537 | "isDeprecated" : false, 1538 | "deprecationReason" : null 1539 | }, { 1540 | "name" : "description", 1541 | "description" : null, 1542 | "args" : [ ], 1543 | "type" : { 1544 | "kind" : "SCALAR", 1545 | "name" : "String", 1546 | "ofType" : null 1547 | }, 1548 | "isDeprecated" : false, 1549 | "deprecationReason" : null 1550 | }, { 1551 | "name" : "locations", 1552 | "description" : null, 1553 | "args" : [ ], 1554 | "type" : { 1555 | "kind" : "LIST", 1556 | "name" : null, 1557 | "ofType" : { 1558 | "kind" : "NON_NULL", 1559 | "name" : null, 1560 | "ofType" : { 1561 | "kind" : "ENUM", 1562 | "name" : "__DirectiveLocation", 1563 | "ofType" : null 1564 | } 1565 | } 1566 | }, 1567 | "isDeprecated" : false, 1568 | "deprecationReason" : null 1569 | }, { 1570 | "name" : "args", 1571 | "description" : null, 1572 | "args" : [ ], 1573 | "type" : { 1574 | "kind" : "NON_NULL", 1575 | "name" : null, 1576 | "ofType" : { 1577 | "kind" : "LIST", 1578 | "name" : null, 1579 | "ofType" : { 1580 | "kind" : "NON_NULL", 1581 | "name" : null, 1582 | "ofType" : { 1583 | "kind" : "OBJECT", 1584 | "name" : "__InputValue", 1585 | "ofType" : null 1586 | } 1587 | } 1588 | } 1589 | }, 1590 | "isDeprecated" : false, 1591 | "deprecationReason" : null 1592 | }, { 1593 | "name" : "onOperation", 1594 | "description" : null, 1595 | "args" : [ ], 1596 | "type" : { 1597 | "kind" : "SCALAR", 1598 | "name" : "Boolean", 1599 | "ofType" : null 1600 | }, 1601 | "isDeprecated" : true, 1602 | "deprecationReason" : "Use `locations`." 1603 | }, { 1604 | "name" : "onFragment", 1605 | "description" : null, 1606 | "args" : [ ], 1607 | "type" : { 1608 | "kind" : "SCALAR", 1609 | "name" : "Boolean", 1610 | "ofType" : null 1611 | }, 1612 | "isDeprecated" : true, 1613 | "deprecationReason" : "Use `locations`." 1614 | }, { 1615 | "name" : "onField", 1616 | "description" : null, 1617 | "args" : [ ], 1618 | "type" : { 1619 | "kind" : "SCALAR", 1620 | "name" : "Boolean", 1621 | "ofType" : null 1622 | }, 1623 | "isDeprecated" : true, 1624 | "deprecationReason" : "Use `locations`." 1625 | } ], 1626 | "inputFields" : null, 1627 | "interfaces" : [ ], 1628 | "enumValues" : null, 1629 | "possibleTypes" : null 1630 | }, { 1631 | "kind" : "ENUM", 1632 | "name" : "__DirectiveLocation", 1633 | "description" : "An enum describing valid locations where a directive can be placed", 1634 | "fields" : null, 1635 | "inputFields" : null, 1636 | "interfaces" : null, 1637 | "enumValues" : [ { 1638 | "name" : "QUERY", 1639 | "description" : "Indicates the directive is valid on queries.", 1640 | "isDeprecated" : false, 1641 | "deprecationReason" : null 1642 | }, { 1643 | "name" : "MUTATION", 1644 | "description" : "Indicates the directive is valid on mutations.", 1645 | "isDeprecated" : false, 1646 | "deprecationReason" : null 1647 | }, { 1648 | "name" : "FIELD", 1649 | "description" : "Indicates the directive is valid on fields.", 1650 | "isDeprecated" : false, 1651 | "deprecationReason" : null 1652 | }, { 1653 | "name" : "FRAGMENT_DEFINITION", 1654 | "description" : "Indicates the directive is valid on fragment definitions.", 1655 | "isDeprecated" : false, 1656 | "deprecationReason" : null 1657 | }, { 1658 | "name" : "FRAGMENT_SPREAD", 1659 | "description" : "Indicates the directive is valid on fragment spreads.", 1660 | "isDeprecated" : false, 1661 | "deprecationReason" : null 1662 | }, { 1663 | "name" : "INLINE_FRAGMENT", 1664 | "description" : "Indicates the directive is valid on inline fragments.", 1665 | "isDeprecated" : false, 1666 | "deprecationReason" : null 1667 | }, { 1668 | "name" : "SCHEMA", 1669 | "description" : "Indicates the directive is valid on a schema SDL definition.", 1670 | "isDeprecated" : false, 1671 | "deprecationReason" : null 1672 | }, { 1673 | "name" : "SCALAR", 1674 | "description" : "Indicates the directive is valid on a scalar SDL definition.", 1675 | "isDeprecated" : false, 1676 | "deprecationReason" : null 1677 | }, { 1678 | "name" : "OBJECT", 1679 | "description" : "Indicates the directive is valid on an object SDL definition.", 1680 | "isDeprecated" : false, 1681 | "deprecationReason" : null 1682 | }, { 1683 | "name" : "FIELD_DEFINITION", 1684 | "description" : "Indicates the directive is valid on a field SDL definition.", 1685 | "isDeprecated" : false, 1686 | "deprecationReason" : null 1687 | }, { 1688 | "name" : "ARGUMENT_DEFINITION", 1689 | "description" : "Indicates the directive is valid on a field argument SDL definition.", 1690 | "isDeprecated" : false, 1691 | "deprecationReason" : null 1692 | }, { 1693 | "name" : "INTERFACE", 1694 | "description" : "Indicates the directive is valid on an interface SDL definition.", 1695 | "isDeprecated" : false, 1696 | "deprecationReason" : null 1697 | }, { 1698 | "name" : "UNION", 1699 | "description" : "Indicates the directive is valid on an union SDL definition.", 1700 | "isDeprecated" : false, 1701 | "deprecationReason" : null 1702 | }, { 1703 | "name" : "ENUM", 1704 | "description" : "Indicates the directive is valid on an enum SDL definition.", 1705 | "isDeprecated" : false, 1706 | "deprecationReason" : null 1707 | }, { 1708 | "name" : "ENUM_VALUE", 1709 | "description" : "Indicates the directive is valid on an enum value SDL definition.", 1710 | "isDeprecated" : false, 1711 | "deprecationReason" : null 1712 | }, { 1713 | "name" : "INPUT_OBJECT", 1714 | "description" : "Indicates the directive is valid on an input object SDL definition.", 1715 | "isDeprecated" : false, 1716 | "deprecationReason" : null 1717 | }, { 1718 | "name" : "INPUT_FIELD_DEFINITION", 1719 | "description" : "Indicates the directive is valid on an input object field SDL definition.", 1720 | "isDeprecated" : false, 1721 | "deprecationReason" : null 1722 | } ], 1723 | "possibleTypes" : null 1724 | } ], 1725 | "directives" : [ { 1726 | "name" : "include", 1727 | "description" : "Directs the executor to include this field or fragment only when the `if` argument is true", 1728 | "locations" : [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], 1729 | "args" : [ { 1730 | "name" : "if", 1731 | "description" : "Included when true.", 1732 | "type" : { 1733 | "kind" : "NON_NULL", 1734 | "name" : null, 1735 | "ofType" : { 1736 | "kind" : "SCALAR", 1737 | "name" : "Boolean", 1738 | "ofType" : null 1739 | } 1740 | }, 1741 | "defaultValue" : null 1742 | } ], 1743 | "onOperation" : false, 1744 | "onFragment" : true, 1745 | "onField" : true 1746 | }, { 1747 | "name" : "skip", 1748 | "description" : "Directs the executor to skip this field or fragment when the `if`'argument is true.", 1749 | "locations" : [ "FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT" ], 1750 | "args" : [ { 1751 | "name" : "if", 1752 | "description" : "Skipped when true.", 1753 | "type" : { 1754 | "kind" : "NON_NULL", 1755 | "name" : null, 1756 | "ofType" : { 1757 | "kind" : "SCALAR", 1758 | "name" : "Boolean", 1759 | "ofType" : null 1760 | } 1761 | }, 1762 | "defaultValue" : null 1763 | } ], 1764 | "onOperation" : false, 1765 | "onFragment" : true, 1766 | "onField" : true 1767 | }, { 1768 | "name" : "defer", 1769 | "description" : "This directive allows results to be deferred during execution", 1770 | "locations" : [ "FIELD" ], 1771 | "args" : [ ], 1772 | "onOperation" : false, 1773 | "onFragment" : false, 1774 | "onField" : true 1775 | }, { 1776 | "name" : "aws_subscribe", 1777 | "description" : "Tells the service which mutation triggers this subscription.", 1778 | "locations" : [ "FIELD_DEFINITION" ], 1779 | "args" : [ { 1780 | "name" : "mutations", 1781 | "description" : "List of mutations which will trigger this subscription when they are called.", 1782 | "type" : { 1783 | "kind" : "LIST", 1784 | "name" : null, 1785 | "ofType" : { 1786 | "kind" : "SCALAR", 1787 | "name" : "String", 1788 | "ofType" : null 1789 | } 1790 | }, 1791 | "defaultValue" : null 1792 | } ], 1793 | "onOperation" : false, 1794 | "onFragment" : false, 1795 | "onField" : false 1796 | }, { 1797 | "name" : "aws_auth", 1798 | "description" : "Directs the schema to enforce authorization on a field", 1799 | "locations" : [ "FIELD_DEFINITION" ], 1800 | "args" : [ { 1801 | "name" : "cognito_groups", 1802 | "description" : "List of cognito user pool groups which have access on this field", 1803 | "type" : { 1804 | "kind" : "LIST", 1805 | "name" : null, 1806 | "ofType" : { 1807 | "kind" : "SCALAR", 1808 | "name" : "String", 1809 | "ofType" : null 1810 | } 1811 | }, 1812 | "defaultValue" : null 1813 | } ], 1814 | "onOperation" : false, 1815 | "onFragment" : false, 1816 | "onField" : false 1817 | }, { 1818 | "name" : "aws_publish", 1819 | "description" : "Tells the service which subscriptions will be published to when this mutation is called. This directive is deprecated use @aws_susbscribe directive instead.", 1820 | "locations" : [ "FIELD_DEFINITION" ], 1821 | "args" : [ { 1822 | "name" : "subscriptions", 1823 | "description" : "List of subscriptions which will be published to when this mutation is called.", 1824 | "type" : { 1825 | "kind" : "LIST", 1826 | "name" : null, 1827 | "ofType" : { 1828 | "kind" : "SCALAR", 1829 | "name" : "String", 1830 | "ofType" : null 1831 | } 1832 | }, 1833 | "defaultValue" : null 1834 | } ], 1835 | "onOperation" : false, 1836 | "onFragment" : false, 1837 | "onField" : false 1838 | }, { 1839 | "name" : "deprecated", 1840 | "description" : null, 1841 | "locations" : [ "FIELD_DEFINITION", "ENUM_VALUE" ], 1842 | "args" : [ { 1843 | "name" : "reason", 1844 | "description" : null, 1845 | "type" : { 1846 | "kind" : "SCALAR", 1847 | "name" : "String", 1848 | "ofType" : null 1849 | }, 1850 | "defaultValue" : "\"No longer supported\"" 1851 | } ], 1852 | "onOperation" : false, 1853 | "onFragment" : false, 1854 | "onField" : false 1855 | } ] 1856 | } 1857 | } 1858 | } -------------------------------------------------------------------------------- /src/graphql/subscriptions.js: -------------------------------------------------------------------------------- 1 | // eslint-disable 2 | // this is an auto generated file. This will be overwritten 3 | 4 | export const onCreateNote = `subscription OnCreateNote { 5 | onCreateNote { 6 | id 7 | name 8 | description 9 | status 10 | } 11 | } 12 | `; 13 | export const onUpdateNote = `subscription OnUpdateNote { 14 | onUpdateNote { 15 | id 16 | name 17 | description 18 | status 19 | } 20 | } 21 | `; 22 | export const onDeleteNote = `subscription OnDeleteNote { 23 | onDeleteNote { 24 | id 25 | name 26 | description 27 | status 28 | } 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | import Amplify from 'aws-amplify' 8 | import config from './aws-exports' 9 | Amplify.configure(config) 10 | 11 | ReactDOM.render(, document.getElementById('root')); 12 | 13 | // If you want your app to work offline and load faster, you can change 14 | // unregister() to register() below. Note this comes with some pitfalls. 15 | // Learn more about service workers: http://bit.ly/CRA-PWA 16 | serviceWorker.unregister(); 17 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------