├── example ├── hasura │ ├── metadata │ │ ├── network.yaml │ │ ├── allow_list.yaml │ │ ├── api_limits.yaml │ │ ├── cron_triggers.yaml │ │ ├── inherited_roles.yaml │ │ ├── query_collections.yaml │ │ ├── remote_schemas.yaml │ │ ├── rest_endpoints.yaml │ │ ├── version.yaml │ │ ├── graphql_schema_introspection.yaml │ │ ├── databases │ │ │ ├── databases.yaml │ │ │ └── default │ │ │ │ └── tables │ │ │ │ ├── tables.yaml │ │ │ │ ├── public_site_admin.yaml │ │ │ │ ├── public_order_status.yaml │ │ │ │ ├── public_product_category_enum.yaml │ │ │ │ ├── public_order_product.yaml │ │ │ │ ├── public_user.yaml │ │ │ │ ├── public_product.yaml │ │ │ │ ├── public_order.yaml │ │ │ │ ├── public_address.yaml │ │ │ │ └── public_product_review.yaml │ │ ├── actions.graphql │ │ └── actions.yaml │ ├── config.yaml │ ├── seeds │ │ └── default │ │ │ ├── 04_default_user_login_seeds.sql │ │ │ ├── 01_B_user_address_seeds.sql │ │ │ └── 01_A_user_seeds.sql │ └── migrations │ │ └── default │ │ └── 1646834482402_init │ │ └── up.sql ├── src │ ├── react-app-env.d.ts │ ├── setupTests.ts │ ├── index.css │ ├── reportWebVitals.ts │ ├── index.tsx │ ├── order_product │ │ └── index.tsx │ ├── App.css │ ├── user │ │ └── index.tsx │ ├── address │ │ └── index.tsx │ ├── product │ │ └── index.tsx │ ├── order │ │ └── index.tsx │ ├── App.tsx │ └── logo.svg ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json └── docker-compose.yaml ├── .gitignore ├── .npmignore ├── src ├── typings.d.ts ├── helpers │ ├── isList.ts │ ├── isRequired.ts │ ├── getFinalType.ts │ ├── getArgType.ts │ ├── isList.test.ts │ ├── isRequired.test.ts │ ├── getFinalType.test.ts │ └── fetchActions.ts ├── getResponseParser │ ├── sanitizeResource.test.ts │ ├── sanitizeResource.ts │ └── index.ts ├── buildVariables │ ├── buildCreateVariables.ts │ ├── makeNestedTarget.ts │ ├── typeAwareKeyValueReducer.ts │ ├── buildUpdateVariables.ts │ ├── index.ts │ └── buildGetListVariables.ts ├── buildGqlQuery │ ├── buildFields.ts │ ├── buildArgs.ts │ └── index.ts ├── types.ts ├── index.ts ├── buildQuery │ └── index.ts └── customDataProvider │ └── index.ts ├── .husky └── pre-commit ├── .prettierrc ├── jest.config.js ├── .github └── workflows │ └── test.yml ├── LICENSE ├── tsconfig.json ├── package.json ├── CHANGELOG.md └── README.md /example/hasura/metadata/network.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/allow_list.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/api_limits.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/cron_triggers.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/inherited_roles.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/query_collections.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/remote_schemas.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/rest_endpoints.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/version.yaml: -------------------------------------------------------------------------------- 1 | version: 3 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.org 3 | lib 4 | dist 5 | .vscode 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | webpack.config.js 3 | .prettierrc 4 | *.org 5 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'graphql-ast-types-browser'; 2 | -------------------------------------------------------------------------------- /example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example/hasura/metadata/graphql_schema_introspection.yaml: -------------------------------------------------------------------------------- 1 | disabled_for_roles: [] 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stats/ra-data-hasura/master/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stats/ra-data-hasura/master/example/public/logo192.png -------------------------------------------------------------------------------- /example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stats/ra-data-hasura/master/example/public/logo512.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /example/hasura/config.yaml: -------------------------------------------------------------------------------- 1 | version: 3 2 | endpoint: http://localhost:8080 3 | admin_secret: my-secret 4 | metadata_directory: metadata 5 | actions: 6 | kind: synchronous 7 | handler_webhook_baseurl: http://localhost:3000 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.tsx?$': 'ts-jest', 4 | }, 5 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', 6 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 7 | }; 8 | -------------------------------------------------------------------------------- /example/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/databases.yaml: -------------------------------------------------------------------------------- 1 | - name: default 2 | kind: postgres 3 | configuration: 4 | connection_info: 5 | use_prepared_statements: false 6 | database_url: 7 | from_env: PG_DATABASE_URL 8 | isolation_level: read-committed 9 | tables: '!include default/tables/tables.yaml' 10 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Install dependencies 13 | run: npm install 14 | - name: Run tests 15 | run: npm run test 16 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/tables.yaml: -------------------------------------------------------------------------------- 1 | - '!include public_address.yaml' 2 | - '!include public_order.yaml' 3 | - '!include public_order_product.yaml' 4 | - '!include public_order_status.yaml' 5 | - '!include public_product.yaml' 6 | - '!include public_product_category_enum.yaml' 7 | - '!include public_product_review.yaml' 8 | - '!include public_site_admin.yaml' 9 | - '!include public_user.yaml' 10 | -------------------------------------------------------------------------------- /example/.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 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/helpers/isList.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IntrospectionType, 3 | IntrospectionTypeRef, 4 | IntrospectionNonNullTypeRef, 5 | TypeKind, 6 | } from 'graphql'; 7 | 8 | const isList = ( 9 | type: IntrospectionType | IntrospectionNonNullTypeRef | IntrospectionTypeRef 10 | ): boolean => { 11 | if (type.kind === TypeKind.NON_NULL) { 12 | return isList(type.ofType); 13 | } 14 | 15 | return type.kind === TypeKind.LIST; 16 | }; 17 | 18 | export default isList; 19 | -------------------------------------------------------------------------------- /src/helpers/isRequired.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IntrospectionType, 3 | IntrospectionListTypeRef, 4 | IntrospectionTypeRef, 5 | TypeKind, 6 | } from 'graphql'; 7 | 8 | const isRequired = ( 9 | type: IntrospectionType | IntrospectionListTypeRef | IntrospectionTypeRef 10 | ): boolean => { 11 | if (type.kind === TypeKind.LIST) { 12 | return isRequired(type.ofType); 13 | } 14 | 15 | return type.kind === TypeKind.NON_NULL; 16 | }; 17 | 18 | export default isRequired; 19 | -------------------------------------------------------------------------------- /example/hasura/seeds/default/04_default_user_login_seeds.sql: -------------------------------------------------------------------------------- 1 | -- "password" bcrypted = $2y$10$4fLjiqiJ.Rh0F/qYfIfCeOy7a9nLJN49YzUEJbYnj2ZsiwVhGsOxK 2 | INSERT INTO public.user 3 | (name, email, password) 4 | VALUES 5 | ('Person', 'user@site.com', '$2y$10$4fLjiqiJ.Rh0F/qYfIfCeOy7a9nLJN49YzUEJbYnj2ZsiwVhGsOxK'); 6 | 7 | INSERT INTO public.site_admin 8 | (name, email, password) 9 | VALUES 10 | ('Admin', 'admin@site.com', '$2y$10$4fLjiqiJ.Rh0F/qYfIfCeOy7a9nLJN49YzUEJbYnj2ZsiwVhGsOxK'); 11 | -------------------------------------------------------------------------------- /src/getResponseParser/sanitizeResource.test.ts: -------------------------------------------------------------------------------- 1 | import { sanitizeResource } from './sanitizeResource'; 2 | 3 | describe('sanitizeResource', () => { 4 | it('It keeps null values', () => { 5 | expect( 6 | sanitizeResource([ 7 | { name: 'vendor', value: 'Test Vendor' }, 8 | { name: 'brand', value: null }, 9 | ]) 10 | ).toEqual({ 11 | '0': { name: 'vendor', value: 'Test Vendor' }, 12 | '1': { name: 'brand', value: null }, 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /example/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_site_admin.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: site_admin 4 | insert_permissions: 5 | - role: site-admin 6 | permission: 7 | check: {} 8 | columns: '*' 9 | select_permissions: 10 | - role: site-admin 11 | permission: 12 | columns: '*' 13 | filter: {} 14 | update_permissions: 15 | - role: site-admin 16 | permission: 17 | columns: '*' 18 | filter: {} 19 | check: null 20 | delete_permissions: 21 | - role: site-admin 22 | permission: 23 | filter: {} 24 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /example/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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # ra-data-hasura Example 2 | 3 | A sample React Admin app using ra-data-hasura and the [Hasura Super App schema](https://github.com/hasura/hasura-ecommerce). 4 | 5 | In the future, login functionality will be added. Meanwhile, a JWT representing a site-admin role has been pre-generated. 6 | 7 | You should be able to view, edit, and delete different schema items as well as see their relationships. 8 | 9 | To run: 10 | 11 | ```bash 12 | docker compose up -d 13 | 14 | hasura seed apply --database-name default --project hasura 15 | 16 | npm i 17 | 18 | npm run start 19 | ``` 20 | 21 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 22 | -------------------------------------------------------------------------------- /example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const container = document.getElementById('root'); 8 | const root = createRoot(container!); // createRoot(container!) if you use TypeScript 9 | root.render( 10 | 11 | 12 | 13 | ); 14 | 15 | // If you want to start measuring performance in your app, pass a function 16 | // to log results (for example: reportWebVitals(console.log)) 17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 18 | reportWebVitals(); 19 | -------------------------------------------------------------------------------- /src/helpers/getFinalType.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IntrospectionType, 3 | IntrospectionTypeRef, 4 | IntrospectionNonNullTypeRef, 5 | TypeKind, 6 | } from 'graphql'; 7 | 8 | type GraphQLTypes = 9 | | IntrospectionType 10 | | IntrospectionNonNullTypeRef 11 | | IntrospectionTypeRef; 12 | 13 | /** 14 | * Ensure we get the real type even if the root type is NON_NULL or LIST 15 | * @param {GraphQLType} type 16 | */ 17 | const getFinalType = (type: GraphQLTypes): IntrospectionType => { 18 | if (type.kind === TypeKind.NON_NULL || type.kind === TypeKind.LIST) { 19 | return getFinalType(type.ofType); 20 | } 21 | 22 | return type as IntrospectionType; 23 | }; 24 | 25 | export default getFinalType; 26 | -------------------------------------------------------------------------------- /example/src/order_product/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Datagrid, 3 | DateField, 4 | List, 5 | NumberField, 6 | ReferenceField, 7 | TextField, 8 | } from 'react-admin'; 9 | 10 | export const OrderProductList = () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /example/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/buildVariables/buildCreateVariables.ts: -------------------------------------------------------------------------------- 1 | import { typeAwareKeyValueReducer } from './typeAwareKeyValueReducer'; 2 | import { FetchType, IntrospectedResource, IntrospectionResult } from '../types'; 3 | 4 | type BuildCreateVariables = ( 5 | introspectionResults: IntrospectionResult 6 | ) => ( 7 | resource: IntrospectedResource, 8 | aorFetchType: FetchType, 9 | params: any, 10 | queryType: any 11 | ) => any; 12 | 13 | export const buildCreateVariables: BuildCreateVariables = 14 | (introspectionResults) => (resource, _, params, __) => { 15 | const reducer = typeAwareKeyValueReducer( 16 | introspectionResults, 17 | resource, 18 | params 19 | ); 20 | return Object.keys(params.data).reduce(reducer, {}); 21 | }; 22 | -------------------------------------------------------------------------------- /src/buildGqlQuery/buildFields.ts: -------------------------------------------------------------------------------- 1 | import { TypeKind, IntrospectionObjectType, FieldNode } from 'graphql'; 2 | import * as gqlTypes from 'graphql-ast-types-browser'; 3 | import getFinalType from '../helpers/getFinalType'; 4 | import { FetchType } from '../types'; 5 | 6 | export type BuildFields = ( 7 | type: IntrospectionObjectType, 8 | aorFetchType?: FetchType 9 | ) => FieldNode[]; 10 | 11 | export const buildFields: BuildFields = (type) => 12 | type.fields.reduce((acc, field) => { 13 | const type = getFinalType(field.type); 14 | 15 | if (type.kind !== TypeKind.OBJECT && type.kind !== TypeKind.INTERFACE) { 16 | return [...acc, gqlTypes.field(gqlTypes.name(field.name))]; 17 | } 18 | 19 | return acc; 20 | }, [] as FieldNode[]); 21 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IntrospectionObjectType, 3 | IntrospectionSchema, 4 | IntrospectionType, 5 | IntrospectionField, 6 | } from 'graphql'; 7 | 8 | export enum FetchType { 9 | GET_LIST = 'GET_LIST', 10 | GET_ONE = 'GET_ONE', 11 | GET_MANY = 'GET_MANY', 12 | GET_MANY_REFERENCE = 'GET_MANY_REFERENCE', 13 | CREATE = 'CREATE', 14 | UPDATE = 'UPDATE', 15 | UPDATE_MANY = 'UPDATE_MANY', 16 | DELETE = 'DELETE', 17 | DELETE_MANY = 'DELETE_MANY', 18 | } 19 | 20 | export type IntrospectedResource = { 21 | type: IntrospectionObjectType; 22 | } & { 23 | [fetchType in FetchType]: IntrospectionField; 24 | }; 25 | 26 | export type IntrospectionResult = { 27 | types: IntrospectionType[]; 28 | queries: IntrospectionObjectType[]; 29 | resources: IntrospectedResource[]; 30 | schema: IntrospectionSchema; 31 | }; 32 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_order_status.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: order_status 4 | is_enum: true 5 | array_relationships: 6 | - name: orders 7 | using: 8 | foreign_key_constraint_on: 9 | column: status 10 | table: 11 | schema: public 12 | name: order 13 | insert_permissions: 14 | - role: site-admin 15 | permission: 16 | check: {} 17 | columns: '*' 18 | select_permissions: 19 | - role: site-admin 20 | permission: 21 | columns: '*' 22 | filter: {} 23 | - role: user 24 | permission: 25 | columns: '*' 26 | filter: {} 27 | update_permissions: 28 | - role: site-admin 29 | permission: 30 | columns: '*' 31 | filter: {} 32 | check: null 33 | delete_permissions: 34 | - role: site-admin 35 | permission: 36 | filter: {} 37 | -------------------------------------------------------------------------------- /src/helpers/getArgType.ts: -------------------------------------------------------------------------------- 1 | import * as gqlTypes from 'graphql-ast-types-browser'; 2 | import getFinalType from './getFinalType'; 3 | import isRequired from './isRequired'; 4 | import isList from './isList'; 5 | 6 | const getArgType = (arg: any) => { 7 | const type = getFinalType(arg.type); 8 | const required = isRequired(arg.type); 9 | const list = isList(arg.type); 10 | 11 | if (required) { 12 | if (list) { 13 | return gqlTypes.nonNullType( 14 | gqlTypes.listType( 15 | gqlTypes.nonNullType(gqlTypes.namedType(gqlTypes.name(type.name))) 16 | ) 17 | ); 18 | } 19 | 20 | return gqlTypes.nonNullType(gqlTypes.namedType(gqlTypes.name(type.name))); 21 | } 22 | 23 | if (list) { 24 | return gqlTypes.listType(gqlTypes.namedType(gqlTypes.name(type.name))); 25 | } 26 | 27 | return gqlTypes.namedType(gqlTypes.name(type.name)); 28 | }; 29 | 30 | export default getArgType; 31 | -------------------------------------------------------------------------------- /example/src/user/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Datagrid, 3 | DateField, 4 | DateInput, 5 | Edit, 6 | EmailField, 7 | List, 8 | SimpleForm, 9 | TextField, 10 | TextInput, 11 | } from 'react-admin'; 12 | 13 | export const UserList = () => ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | 26 | export const UserEdit = () => ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_product_category_enum.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: product_category_enum 4 | is_enum: true 5 | array_relationships: 6 | - name: products 7 | using: 8 | foreign_key_constraint_on: 9 | column: category_display_name 10 | table: 11 | schema: public 12 | name: product 13 | insert_permissions: 14 | - role: site-admin 15 | permission: 16 | check: {} 17 | columns: '*' 18 | select_permissions: 19 | - role: anonymous 20 | permission: 21 | columns: '*' 22 | filter: {} 23 | - role: site-admin 24 | permission: 25 | columns: '*' 26 | filter: {} 27 | - role: user 28 | permission: 29 | columns: '*' 30 | filter: {} 31 | update_permissions: 32 | - role: site-admin 33 | permission: 34 | columns: '*' 35 | filter: {} 36 | check: null 37 | delete_permissions: 38 | - role: site-admin 39 | permission: 40 | filter: {} 41 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { buildFields } from './buildGqlQuery/buildFields'; 2 | export type { BuildFields } from './buildGqlQuery/buildFields'; 3 | 4 | export { 5 | buildArgs, 6 | buildMetaArgs, 7 | buildApolloArgs, 8 | } from './buildGqlQuery/buildArgs'; 9 | export type { 10 | BuildArgs, 11 | BuildMetaArgs, 12 | BuildApolloArgs, 13 | } from './buildGqlQuery/buildArgs'; 14 | 15 | export { buildGqlQuery } from './buildGqlQuery'; 16 | export type { BuildGqlQuery } from './buildGqlQuery'; 17 | 18 | export { getResponseParser } from './getResponseParser'; 19 | export type { GetResponseParser } from './getResponseParser'; 20 | 21 | import buildQuery from './buildQuery'; 22 | export { buildQuery }; 23 | export type { BuildQuery, BuildQueryFactory } from './buildQuery'; 24 | 25 | export { buildVariables, BuildVariables } from './buildVariables'; 26 | 27 | export { buildCustomDataProvider as default } from './customDataProvider'; 28 | export type { BuildCustomDataProvider } from './customDataProvider'; 29 | 30 | export { FetchType } from './types'; 31 | -------------------------------------------------------------------------------- /src/buildVariables/makeNestedTarget.ts: -------------------------------------------------------------------------------- 1 | import set from 'lodash/set'; 2 | 3 | type TargetEquals = { 4 | _eq: any; 5 | }; 6 | 7 | type NestedTarget = 8 | | K 9 | | { 10 | [key: string]: K | NestedTarget; 11 | }; 12 | 13 | export const makeNestedTarget = ( 14 | target: string, 15 | id: string | number 16 | ): NestedTarget => { 17 | // This simple example should make clear what this function does 18 | // makeNestedTarget("a.b", 42) 19 | // makeNestedTarget("a#b", 42) 20 | // => { a: { b: { _eq: 42 } } } 21 | // makeNestedTarget("a#b@_contains@c#d", id) 22 | // => { a: { b: { _contains: { c: { d: 42 } } } } } 23 | // . -> # to make nested filtering support the same separator/standard 24 | 25 | let [path, operation = '_eq', oppath] = target.split('@'); 26 | let value = oppath ? set({}, oppath 27 | .split(".").join("#") // nested filtering support the same standard 28 | .split('#'), id) : id; 29 | 30 | return set({}, path.split('.').join("#").split("#"), { 31 | [operation]: value, 32 | }) as NestedTarget; 33 | }; 34 | -------------------------------------------------------------------------------- /src/helpers/isList.test.ts: -------------------------------------------------------------------------------- 1 | import { TypeKind } from 'graphql'; 2 | import isList from './isList'; 3 | 4 | describe('isList', () => { 5 | it('returns the correct type for SCALAR types', () => { 6 | expect(isList({ name: 'foo', kind: TypeKind.SCALAR })).toEqual(false); 7 | }); 8 | it('returns the correct type for NON_NULL types', () => { 9 | expect( 10 | isList({ 11 | kind: TypeKind.NON_NULL, 12 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 13 | }) 14 | ).toEqual(false); 15 | }); 16 | it('returns the correct type for LIST types', () => { 17 | expect( 18 | isList({ 19 | kind: TypeKind.LIST, 20 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 21 | }) 22 | ).toEqual(true); 23 | }); 24 | it('returns the correct type for NON_NULL LIST types', () => { 25 | expect( 26 | isList({ 27 | kind: TypeKind.NON_NULL, 28 | ofType: { 29 | kind: TypeKind.LIST, 30 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 31 | }, 32 | }) 33 | ).toEqual(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/helpers/isRequired.test.ts: -------------------------------------------------------------------------------- 1 | import { TypeKind } from 'graphql'; 2 | import isRequired from './isRequired'; 3 | 4 | describe('isRequired', () => { 5 | it('returns the correct type for SCALAR types', () => { 6 | expect(isRequired({ name: 'foo', kind: TypeKind.SCALAR })).toEqual(false); 7 | }); 8 | it('returns the correct type for NON_NULL types', () => { 9 | expect( 10 | isRequired({ 11 | kind: TypeKind.NON_NULL, 12 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 13 | }) 14 | ).toEqual(true); 15 | }); 16 | it('returns the correct type for LIST types', () => { 17 | expect( 18 | isRequired({ 19 | kind: TypeKind.LIST, 20 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 21 | }) 22 | ).toEqual(false); 23 | }); 24 | it('returns the correct type for NON_NULL LIST types', () => { 25 | expect( 26 | isRequired({ 27 | kind: TypeKind.NON_NULL, 28 | ofType: { 29 | kind: TypeKind.LIST, 30 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 31 | }, 32 | }) 33 | ).toEqual(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_order_product.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: order_product 4 | object_relationships: 5 | - name: order 6 | using: 7 | foreign_key_constraint_on: order_id 8 | - name: product 9 | using: 10 | foreign_key_constraint_on: product_id 11 | insert_permissions: 12 | - role: site-admin 13 | permission: 14 | check: {} 15 | columns: '*' 16 | - role: user 17 | permission: 18 | check: 19 | order: 20 | user_id: 21 | _eq: X-Hasura-User-Id 22 | columns: '*' 23 | select_permissions: 24 | - role: site-admin 25 | permission: 26 | columns: '*' 27 | filter: {} 28 | allow_aggregations: true 29 | - role: user 30 | permission: 31 | columns: '*' 32 | filter: 33 | order: 34 | user_id: 35 | _eq: X-Hasura-User-Id 36 | update_permissions: 37 | - role: site-admin 38 | permission: 39 | columns: '*' 40 | filter: {} 41 | check: null 42 | delete_permissions: 43 | - role: site-admin 44 | permission: 45 | filter: {} 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Radcliffe Robinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^14.2.0", 9 | "@types/jest": "^28.1.1", 10 | "@types/node": "^17.0.40", 11 | "@types/react": "^18.0.11", 12 | "@types/react-dom": "^18.0.5", 13 | "graphql": "^15.8.0", 14 | "ra-data-hasura": "^0.5.0", 15 | "react": "^18.1.0", 16 | "react-admin": "^4.1.2", 17 | "react-dom": "^18.1.0", 18 | "react-scripts": "5.0.1", 19 | "typescript": "^4.7.3", 20 | "web-vitals": "^2.1.4" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_user.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: user 4 | array_relationships: 5 | - name: addresses 6 | using: 7 | foreign_key_constraint_on: 8 | column: user_id 9 | table: 10 | schema: public 11 | name: address 12 | - name: orders 13 | using: 14 | foreign_key_constraint_on: 15 | column: user_id 16 | table: 17 | schema: public 18 | name: order 19 | - name: product_reviews 20 | using: 21 | foreign_key_constraint_on: 22 | column: user_id 23 | table: 24 | schema: public 25 | name: product_review 26 | insert_permissions: 27 | - role: site-admin 28 | permission: 29 | check: {} 30 | columns: '*' 31 | select_permissions: 32 | - role: site-admin 33 | permission: 34 | columns: '*' 35 | filter: {} 36 | allow_aggregations: true 37 | - role: user 38 | permission: 39 | columns: '*' 40 | filter: 41 | id: 42 | _eq: X-Hasura-User-Id 43 | update_permissions: 44 | - role: site-admin 45 | permission: 46 | columns: '*' 47 | filter: {} 48 | check: null 49 | delete_permissions: 50 | - role: site-admin 51 | permission: 52 | filter: {} 53 | -------------------------------------------------------------------------------- /example/hasura/metadata/actions.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | adminLogin(params: AdminLoginInput!): JWT 3 | } 4 | 5 | type Mutation { 6 | adminSignup(params: AdminSignupInput!): JWT 7 | } 8 | 9 | type Mutation { 10 | createPaymentIntent( 11 | params: CreatePaymentIntentInput! 12 | ): PaymentIntentClientSecret 13 | } 14 | 15 | type Mutation { 16 | login(params: LoginInput!): JWT 17 | } 18 | 19 | type Query { 20 | refreshToken(params: RefreshTokenInput!): RefreshTokenJWT 21 | } 22 | 23 | type Mutation { 24 | signup(params: SignupInput!): JWT 25 | } 26 | 27 | input SignupInput { 28 | name: String! 29 | email: String! 30 | password: String! 31 | } 32 | 33 | input LoginInput { 34 | email: String! 35 | password: String! 36 | } 37 | 38 | input AdminLoginInput { 39 | email: String! 40 | password: String! 41 | } 42 | 43 | input AdminSignupInput { 44 | name: String! 45 | email: String! 46 | password: String! 47 | } 48 | 49 | input CreatePaymentIntentInput { 50 | paymentAmount: Float! 51 | } 52 | 53 | input RefreshTokenInput { 54 | refreshToken: String! 55 | } 56 | 57 | type PaymentIntentClientSecret { 58 | clientSecret: String! 59 | } 60 | 61 | type JWT { 62 | name: String! 63 | email: String! 64 | token: String! 65 | refreshToken: String! 66 | } 67 | 68 | type RefreshTokenJWT { 69 | token: String! 70 | } 71 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_product.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: product 4 | object_relationships: 5 | - name: category 6 | using: 7 | foreign_key_constraint_on: category_display_name 8 | array_relationships: 9 | - name: orders 10 | using: 11 | foreign_key_constraint_on: 12 | column: product_id 13 | table: 14 | schema: public 15 | name: order_product 16 | - name: product_reviews 17 | using: 18 | foreign_key_constraint_on: 19 | column: product_id 20 | table: 21 | schema: public 22 | name: product_review 23 | insert_permissions: 24 | - role: site-admin 25 | permission: 26 | check: {} 27 | columns: '*' 28 | select_permissions: 29 | - role: anonymous 30 | permission: 31 | columns: '*' 32 | filter: {} 33 | allow_aggregations: true 34 | - role: site-admin 35 | permission: 36 | columns: '*' 37 | filter: {} 38 | allow_aggregations: true 39 | - role: user 40 | permission: 41 | columns: '*' 42 | filter: {} 43 | update_permissions: 44 | - role: site-admin 45 | permission: 46 | columns: '*' 47 | filter: {} 48 | check: null 49 | delete_permissions: 50 | - role: site-admin 51 | permission: 52 | filter: {} 53 | -------------------------------------------------------------------------------- /src/getResponseParser/sanitizeResource.ts: -------------------------------------------------------------------------------- 1 | export const sanitizeResource = (data: any = {}): object => { 2 | const result = Object.keys(data).reduce((acc, key) => { 3 | if (key.startsWith('_')) { 4 | return acc; 5 | } 6 | 7 | const dataKey = data[key]; 8 | 9 | if (Array.isArray(dataKey)) { 10 | if (dataKey[0] && typeof dataKey[0] === 'object') { 11 | // if var is an array of reference objects with id properties 12 | if (dataKey[0].id != null) { 13 | return { 14 | ...acc, 15 | [key]: dataKey.map(sanitizeResource), 16 | [`${key}Ids`]: dataKey.map((d) => d.id), 17 | }; 18 | } else { 19 | return { 20 | ...acc, 21 | [key]: dataKey.map(sanitizeResource), 22 | }; 23 | } 24 | } else { 25 | return { ...acc, [key]: dataKey }; 26 | } 27 | } 28 | 29 | if ( 30 | typeof dataKey === 'object' && 31 | dataKey !== null && 32 | dataKey !== undefined 33 | ) { 34 | return { 35 | ...acc, 36 | ...(dataKey && 37 | dataKey.id && { 38 | [`${key}.id`]: dataKey.id, 39 | }), 40 | [key]: sanitizeResource(dataKey), 41 | }; 42 | } 43 | 44 | return { ...acc, [key]: dataKey }; 45 | }, {}); 46 | 47 | return result; 48 | }; 49 | -------------------------------------------------------------------------------- /src/helpers/getFinalType.test.ts: -------------------------------------------------------------------------------- 1 | import { TypeKind } from 'graphql'; 2 | import getFinalType from './getFinalType'; 3 | 4 | describe('getFinalType', () => { 5 | it('returns the correct type for SCALAR types', () => { 6 | expect(getFinalType({ name: 'foo', kind: TypeKind.SCALAR })).toEqual({ 7 | name: 'foo', 8 | kind: TypeKind.SCALAR, 9 | }); 10 | }); 11 | it('returns the correct type for NON_NULL types', () => { 12 | expect( 13 | getFinalType({ 14 | kind: TypeKind.NON_NULL, 15 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 16 | }) 17 | ).toEqual({ 18 | name: 'foo', 19 | kind: TypeKind.SCALAR, 20 | }); 21 | }); 22 | it('returns the correct type for LIST types', () => { 23 | expect( 24 | getFinalType({ 25 | kind: TypeKind.LIST, 26 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 27 | }) 28 | ).toEqual({ 29 | name: 'foo', 30 | kind: TypeKind.SCALAR, 31 | }); 32 | }); 33 | it('returns the correct type for NON_NULL LIST types', () => { 34 | expect( 35 | getFinalType({ 36 | kind: TypeKind.NON_NULL, 37 | ofType: { 38 | kind: TypeKind.LIST, 39 | ofType: { name: 'foo', kind: TypeKind.SCALAR }, 40 | }, 41 | }) 42 | ).toEqual({ name: 'foo', kind: TypeKind.SCALAR }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | "target": "es2016" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 7 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 8 | "allowJs": true /* Allow javascript files to be compiled. */, 9 | "declaration": true /* Generates corresponding '.d.ts' file. */, 10 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 11 | "outDir": "./dist" /* Redirect output structure to the directory. */, 12 | 13 | /* Strict Type-Checking Options */ 14 | "strict": true /* Enable all strict type-checking options. */, 15 | 16 | /* Module Resolution Options */ 17 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 18 | 19 | /* Advanced Options */ 20 | "skipLibCheck": true /* Skip type checking of declaration files. */, 21 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 22 | }, 23 | "include": ["src/**/*"], 24 | "exclude": ["node_modules", "**/*.test.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_order.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: order 4 | object_relationships: 5 | - name: billing_address 6 | using: 7 | foreign_key_constraint_on: billing_address_id 8 | - name: order_status 9 | using: 10 | foreign_key_constraint_on: status 11 | - name: shipping_address 12 | using: 13 | foreign_key_constraint_on: shipping_address_id 14 | - name: user 15 | using: 16 | foreign_key_constraint_on: user_id 17 | array_relationships: 18 | - name: products 19 | using: 20 | foreign_key_constraint_on: 21 | column: order_id 22 | table: 23 | schema: public 24 | name: order_product 25 | insert_permissions: 26 | - role: site-admin 27 | permission: 28 | check: {} 29 | columns: '*' 30 | - role: user 31 | permission: 32 | check: 33 | user_id: 34 | _eq: X-Hasura-User-Id 35 | columns: '*' 36 | select_permissions: 37 | - role: site-admin 38 | permission: 39 | columns: '*' 40 | filter: {} 41 | allow_aggregations: true 42 | - role: user 43 | permission: 44 | columns: '*' 45 | filter: 46 | user_id: 47 | _eq: X-Hasura-User-Id 48 | update_permissions: 49 | - role: site-admin 50 | permission: 51 | columns: '*' 52 | filter: {} 53 | check: null 54 | delete_permissions: 55 | - role: site-admin 56 | permission: 57 | filter: {} 58 | -------------------------------------------------------------------------------- /src/buildVariables/typeAwareKeyValueReducer.ts: -------------------------------------------------------------------------------- 1 | import type { IntrospectionField } from 'graphql'; 2 | import { IntrospectionResult, IntrospectedResource } from '../types'; 3 | 4 | /** 5 | * Returns a reducer that converts the react-admin key-values to hasura-acceptable values 6 | * 7 | * Currently that means that dates should never be an empty string, but in the future that can be extended 8 | * See https://github.com/marmelab/react-admin/pull/6199 9 | * 10 | */ 11 | 12 | type TypeAwareKeyValueReducer = ( 13 | introspectionResults: IntrospectionResult, 14 | resource: IntrospectedResource, 15 | params: any 16 | ) => (acc: any, key: any) => any; 17 | 18 | export const typeAwareKeyValueReducer: TypeAwareKeyValueReducer = 19 | (introspectionResults, resource, params) => (acc, key) => { 20 | const type = introspectionResults.types.find( 21 | (t) => t.name === resource.type.name 22 | ); 23 | 24 | let value = params.data[key]; 25 | if (type) { 26 | const field = (type as any)?.fields?.find( 27 | (t: IntrospectionField) => t.name === key 28 | ); 29 | if (field?.type?.name === 'date' && params.data[key] === '') { 30 | value = null; 31 | } 32 | if (field?.type?.name === 'Int' && params.data[key] === '') { 33 | value = null; 34 | } 35 | } 36 | return resource.type.fields.some((f) => f.name === key) 37 | ? { 38 | ...acc, 39 | [key]: value, 40 | } 41 | : acc; 42 | }; 43 | -------------------------------------------------------------------------------- /example/src/address/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Datagrid, 3 | DateField, 4 | DateInput, 5 | Edit, 6 | List, 7 | ReferenceField, 8 | ReferenceInput, 9 | SelectInput, 10 | SimpleForm, 11 | TextField, 12 | TextInput, 13 | } from 'react-admin'; 14 | 15 | export const AddressList = () => ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | 34 | export const AddressEdit = () => ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | -------------------------------------------------------------------------------- /example/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: postgres:14 4 | restart: always 5 | volumes: 6 | - db_data:/var/lib/postgresql/data 7 | ports: 8 | # Expose the port for tooling (SQL language server in IDE, connecting with GUI's etc) 9 | - 5432:5432 10 | environment: 11 | POSTGRES_PASSWORD: postgrespassword 12 | 13 | graphql-engine: 14 | image: hasura/graphql-engine:v2.7.0.cli-migrations-v3 15 | volumes: 16 | - ./hasura/migrations:/hasura-migrations 17 | - ./hasura/metadata:/hasura-metadata 18 | ports: 19 | - 8080:8080 20 | depends_on: 21 | - 'postgres' 22 | restart: always 23 | environment: 24 | NEXTJS_SERVER_URL: http://nextjs:3000 25 | HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres 26 | PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres 27 | ## enable the console served by server 28 | HASURA_GRAPHQL_ENABLE_CONSOLE: 'true' # set to "false" to disable console 29 | ## enable debugging mode. It is recommended to disable this in production 30 | HASURA_GRAPHQL_DEV_MODE: 'true' 31 | HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log 32 | HASURA_GRAPHQL_ADMIN_SECRET: my-secret 33 | HASURA_GRAPHQL_JWT_SECRET: '{ "type": "HS256", "key": "this-is-a-generic-HS256-secret-key-and-you-should-really-change-it" }' 34 | HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymous 35 | 36 | volumes: 37 | db_data: 38 | -------------------------------------------------------------------------------- /src/helpers/fetchActions.ts: -------------------------------------------------------------------------------- 1 | import { FetchType } from '../types'; 2 | 3 | export const GET_LIST = 'GET_LIST'; 4 | export const GET_ONE = 'GET_ONE'; 5 | export const GET_MANY = 'GET_MANY'; 6 | export const GET_MANY_REFERENCE = 'GET_MANY_REFERENCE'; 7 | export const CREATE = 'CREATE'; 8 | export const UPDATE = 'UPDATE'; 9 | export const UPDATE_MANY = 'UPDATE_MANY'; 10 | export const DELETE = 'DELETE'; 11 | export const DELETE_MANY = 'DELETE_MANY'; 12 | 13 | export const fetchActionsWithRecordResponse = [GET_ONE, CREATE, UPDATE]; 14 | export const fetchActionsWithArrayOfIdentifiedRecordsResponse = [ 15 | GET_LIST, 16 | GET_MANY, 17 | GET_MANY_REFERENCE, 18 | ]; 19 | export const fetchActionsWithArrayOfRecordsResponse = [ 20 | ...fetchActionsWithArrayOfIdentifiedRecordsResponse, 21 | UPDATE_MANY, 22 | DELETE_MANY, 23 | ]; 24 | export const fetchActionsWithTotalResponse = [GET_LIST, GET_MANY_REFERENCE]; 25 | 26 | export const sanitizeFetchType = (fetchType: FetchType) => { 27 | switch (fetchType) { 28 | case GET_LIST: 29 | return 'getList'; 30 | case GET_ONE: 31 | return 'getOne'; 32 | case GET_MANY: 33 | return 'getMany'; 34 | case GET_MANY_REFERENCE: 35 | return 'getManyReference'; 36 | case CREATE: 37 | return 'create'; 38 | case UPDATE: 39 | return 'update'; 40 | case UPDATE_MANY: 41 | return 'updateMany'; 42 | case DELETE: 43 | return 'delete'; 44 | case DELETE_MANY: 45 | return 'deleteMany'; 46 | default: 47 | return fetchType; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /example/src/product/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BooleanInput, 3 | Datagrid, 4 | DateField, 5 | DateInput, 6 | Edit, 7 | List, 8 | NumberField, 9 | ReferenceInput, 10 | SelectInput, 11 | SimpleForm, 12 | TextField, 13 | TextInput, 14 | } from 'react-admin'; 15 | 16 | export const ProductList = () => ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | 32 | export const ProductEdit = () => ( 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | -------------------------------------------------------------------------------- /src/getResponseParser/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GET_LIST, 3 | GET_MANY, 4 | GET_MANY_REFERENCE, 5 | GET_ONE, 6 | CREATE, 7 | UPDATE, 8 | DELETE, 9 | UPDATE_MANY, 10 | DELETE_MANY, 11 | } from '../helpers/fetchActions'; 12 | import { IntrospectionResult, IntrospectedResource, FetchType } from '../types'; 13 | import { sanitizeResource } from './sanitizeResource'; 14 | 15 | export type GetResponseParser = (introspectionResults: IntrospectionResult) => ( 16 | aorFetchType: FetchType, 17 | resource?: IntrospectedResource 18 | ) => (res: { data: any }) => { 19 | data: any; 20 | total?: number; 21 | }; 22 | 23 | export const getResponseParser: GetResponseParser = 24 | () => (aorFetchType) => (res) => { 25 | const response = res.data; 26 | 27 | switch (aorFetchType) { 28 | case GET_MANY_REFERENCE: 29 | case GET_LIST: 30 | return { 31 | data: response.items.map(sanitizeResource), 32 | total: response.total.aggregate.count, 33 | }; 34 | 35 | case GET_MANY: 36 | return { data: response.items.map(sanitizeResource) }; 37 | 38 | case GET_ONE: 39 | return { data: sanitizeResource(response.returning[0]) }; 40 | 41 | case CREATE: 42 | case UPDATE: 43 | case DELETE: 44 | return { data: sanitizeResource(response.data.returning[0]) }; 45 | 46 | case UPDATE_MANY: 47 | case DELETE_MANY: 48 | return { data: response.data.returning.map((x: { id: any }) => x.id) }; 49 | 50 | default: 51 | throw Error(`Expected a proper fetchType, got: ${aorFetchType}`); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /example/hasura/metadata/actions.yaml: -------------------------------------------------------------------------------- 1 | actions: 2 | - name: adminLogin 3 | definition: 4 | kind: '' 5 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/admin-login' 6 | permissions: 7 | - role: anonymous 8 | - name: adminSignup 9 | definition: 10 | kind: synchronous 11 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/admin-signup' 12 | permissions: 13 | - role: site-admin 14 | - name: createPaymentIntent 15 | definition: 16 | kind: synchronous 17 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/create-payment-intent' 18 | permissions: 19 | - role: anonymous 20 | - role: site-admin 21 | - role: user 22 | - name: login 23 | definition: 24 | kind: synchronous 25 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/login' 26 | permissions: 27 | - role: anonymous 28 | - name: refreshToken 29 | definition: 30 | kind: '' 31 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/refresh-token' 32 | permissions: 33 | - role: anonymous 34 | - role: site-admin 35 | - role: user 36 | - name: signup 37 | definition: 38 | kind: synchronous 39 | handler: '{{NEXTJS_SERVER_URL}}/api/actions/signup' 40 | permissions: 41 | - role: anonymous 42 | custom_types: 43 | enums: [] 44 | input_objects: 45 | - name: SignupInput 46 | - name: LoginInput 47 | - name: AdminLoginInput 48 | - name: AdminSignupInput 49 | - name: CreatePaymentIntentInput 50 | - name: RefreshTokenInput 51 | objects: 52 | - name: PaymentIntentClientSecret 53 | - name: JWT 54 | - name: RefreshTokenJWT 55 | scalars: [] 56 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_address.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: address 4 | object_relationships: 5 | - name: user 6 | using: 7 | foreign_key_constraint_on: user_id 8 | array_relationships: 9 | - name: orders_with_billing_address 10 | using: 11 | foreign_key_constraint_on: 12 | column: billing_address_id 13 | table: 14 | schema: public 15 | name: order 16 | - name: orders_with_shipping_address 17 | using: 18 | foreign_key_constraint_on: 19 | column: shipping_address_id 20 | table: 21 | schema: public 22 | name: order 23 | insert_permissions: 24 | - role: site-admin 25 | permission: 26 | check: {} 27 | columns: '*' 28 | - role: user 29 | permission: 30 | check: 31 | user_id: 32 | _eq: X-Hasura-User-Id 33 | columns: '*' 34 | select_permissions: 35 | - role: site-admin 36 | permission: 37 | columns: '*' 38 | filter: {} 39 | allow_aggregations: true 40 | - role: user 41 | permission: 42 | columns: '*' 43 | filter: 44 | user_id: 45 | _eq: X-Hasura-User-Id 46 | update_permissions: 47 | - role: site-admin 48 | permission: 49 | columns: '*' 50 | filter: {} 51 | check: null 52 | - role: user 53 | permission: 54 | columns: '*' 55 | filter: 56 | user_id: 57 | _eq: X-Hasura-User-Id 58 | check: null 59 | delete_permissions: 60 | - role: site-admin 61 | permission: 62 | filter: {} 63 | - role: user 64 | permission: 65 | filter: 66 | user_id: 67 | _eq: X-Hasura-User-Id 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ra-data-hasura", 3 | "version": "0.5.1", 4 | "description": "A data provider for connecting react-admin to a Hasura endpoint", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "sideEffects": false, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/hasura/ra-data-hasura.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/hasura/ra-data-hasura/issues" 14 | }, 15 | "homepage": "https://github.com/hasura/ra-data-hasura#readme", 16 | "authors": [ 17 | "Praveen Durairaju", 18 | "Radcliffe Robinson" 19 | ], 20 | "keywords": [ 21 | "reactjs", 22 | "react", 23 | "react-admin", 24 | "admin-on-rest", 25 | "rest", 26 | "graphql", 27 | "hasura" 28 | ], 29 | "license": "MIT", 30 | "scripts": { 31 | "build": "rimraf ./dist && tsc", 32 | "prepublishOnly": "npm run test && npm run build", 33 | "test": "jest", 34 | "prettier": "prettier --config ./.prettierrc --write '**/*.{js,jsx,md}'", 35 | "prepare": "husky install" 36 | }, 37 | "dependencies": { 38 | "graphql-ast-types-browser": "~1.0.2", 39 | "lodash": "~4.17.21", 40 | "ra-data-graphql": "^4.1.3", 41 | "graphql": "^15.8.0" 42 | }, 43 | "peerDependencies": { 44 | "ra-core": "^4.1.3" 45 | }, 46 | "devDependencies": { 47 | "@types/jest": "^28.1.1", 48 | "@types/lodash": "^4.14.182", 49 | "husky": "^8.0.1", 50 | "jest": "^28.1.1", 51 | "prettier": "^2.7.0", 52 | "pretty-quick": "^3.1.3", 53 | "rimraf": "^3.0.2", 54 | "ts-jest": "^28.0.5", 55 | "typescript": "^4.7.3" 56 | }, 57 | "overrides": { 58 | "graphql-ast-types-browser": { 59 | "graphql": "$graphql" 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example/src/order/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BooleanField, 3 | BooleanInput, 4 | Datagrid, 5 | DateField, 6 | DateInput, 7 | Edit, 8 | List, 9 | ReferenceField, 10 | ReferenceInput, 11 | SelectInput, 12 | SimpleForm, 13 | TextField, 14 | TextInput, 15 | } from 'react-admin'; 16 | 17 | export const OrderList = () => ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | 38 | export const OrderEdit = () => ( 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/hasura/metadata/databases/default/tables/public_product_review.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | schema: public 3 | name: product_review 4 | object_relationships: 5 | - name: product 6 | using: 7 | foreign_key_constraint_on: product_id 8 | - name: user 9 | using: 10 | foreign_key_constraint_on: user_id 11 | insert_permissions: 12 | - role: site-admin 13 | permission: 14 | check: {} 15 | columns: '*' 16 | - role: user 17 | permission: 18 | check: 19 | user: 20 | id: 21 | _eq: X-Hasura-User-Id 22 | orders: 23 | products: 24 | id: 25 | _ceq: product_id 26 | set: 27 | user_id: X-Hasura-User-Id 28 | columns: 29 | - product_id 30 | - rating 31 | - comment 32 | select_permissions: 33 | - role: anonymous 34 | permission: 35 | columns: '*' 36 | filter: {} 37 | - role: site-admin 38 | permission: 39 | columns: '*' 40 | filter: {} 41 | allow_aggregations: true 42 | - role: user 43 | permission: 44 | columns: '*' 45 | filter: {} 46 | update_permissions: 47 | - role: site-admin 48 | permission: 49 | columns: '*' 50 | filter: {} 51 | check: null 52 | - role: user 53 | permission: 54 | columns: 55 | - product_id 56 | - rating 57 | - comment 58 | filter: 59 | user: 60 | id: 61 | _eq: X-Hasura-User-Id 62 | orders: 63 | products: 64 | id: 65 | _ceq: product_id 66 | check: null 67 | set: 68 | user_id: X-Hasura-User-Id 69 | delete_permissions: 70 | - role: site-admin 71 | permission: 72 | filter: {} 73 | - role: user 74 | permission: 75 | filter: 76 | user: 77 | id: 78 | _eq: X-Hasura-User-Id 79 | orders: 80 | products: 81 | id: 82 | _ceq: product_id 83 | -------------------------------------------------------------------------------- /src/buildVariables/buildUpdateVariables.ts: -------------------------------------------------------------------------------- 1 | import isEqual from 'lodash/isEqual'; 2 | import { IntrospectionInputObjectType } from 'graphql'; 3 | import { FetchType, IntrospectedResource, IntrospectionResult } from '../types'; 4 | import { typeAwareKeyValueReducer } from './typeAwareKeyValueReducer'; 5 | 6 | type BuildUpdateVariables = ( 7 | introspectionResults: IntrospectionResult 8 | ) => ( 9 | resource: IntrospectedResource, 10 | aorFetchType: FetchType, 11 | params: any, 12 | queryType: any 13 | ) => any; 14 | 15 | export const buildUpdateVariables: BuildUpdateVariables = 16 | (introspectionResults) => (resource, _, params, __) => { 17 | const reducer = typeAwareKeyValueReducer( 18 | introspectionResults, 19 | resource, 20 | params 21 | ); 22 | let permitted_fields: any = null; 23 | const resource_name = resource.type.name; 24 | if (resource_name) { 25 | let inputType = introspectionResults.types.find( 26 | (obj) => obj.name === `${resource_name}_set_input` 27 | ); 28 | if (inputType) { 29 | let inputTypeFields = (inputType as IntrospectionInputObjectType) 30 | .inputFields; 31 | if (inputTypeFields) { 32 | permitted_fields = inputTypeFields.map((obj) => obj.name); 33 | } 34 | } 35 | } 36 | return Object.keys(params.data).reduce((acc, key) => { 37 | // If hasura permissions do not allow a field to be updated like (id), 38 | // we are not allowed to put it inside the variables 39 | // RA passes the whole previous Object here 40 | // https://github.com/marmelab/react-admin/issues/2414#issuecomment-428945402 41 | 42 | // Fetch permitted fields from *_set_input INPUT_OBJECT and filter out any key 43 | // not present inside it 44 | if (permitted_fields && !permitted_fields.includes(key)) return acc; 45 | 46 | if ( 47 | params.previousData && 48 | isEqual(params.data[key], params.previousData[key]) 49 | ) { 50 | return acc; 51 | } 52 | return reducer(acc, key); 53 | }, {}); 54 | }; 55 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Admin, Resource, DataProvider } from 'react-admin'; 3 | import buildHasuraProvider from 'ra-data-hasura'; 4 | import { ProductEdit, ProductList } from './product'; 5 | import { createHttpLink } from '@apollo/client'; 6 | import { setContext } from '@apollo/client/link/context'; 7 | import { OrderEdit, OrderList } from './order'; 8 | import { OrderProductList } from './order_product'; 9 | import { UserEdit, UserList } from './user'; 10 | import { AddressEdit, AddressList } from './address'; 11 | 12 | const httpLink = createHttpLink({ 13 | uri: 'http://localhost:8080/v1/graphql', 14 | }); 15 | 16 | const authLink = setContext((_, { headers }) => { 17 | // return the headers to the context so httpLink can read them 18 | return { 19 | headers: { 20 | ...headers, 21 | authorization: 22 | // To generate your own JWT see Hasura Super App https://github.com/hasura/hasura-ecommerce 23 | 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsic2l0ZS1hZG1pbiIsInVzZXIiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoic2l0ZS1hZG1pbiIsIlgtSGFzdXJhLVNpdGUtQWRtaW4tSWQiOiIxIn0sImlhdCI6MTY1MTc4MTg1NH0.DsyJSVZYt7Ah8PIWFvnYbAvnpSDdMbhAkmjuLRE8Gas', 24 | }, 25 | }; 26 | }); 27 | 28 | const App = () => { 29 | const [dataProvider, setDataProvider] = React.useState( 30 | null 31 | ); 32 | 33 | React.useEffect(() => { 34 | const buildDataProvider = async () => { 35 | const dataProviderHasura = await buildHasuraProvider({ 36 | clientOptions: { 37 | link: authLink.concat(httpLink) as any, 38 | }, 39 | }); 40 | setDataProvider(() => dataProviderHasura); 41 | }; 42 | buildDataProvider(); 43 | }, []); 44 | 45 | if (!dataProvider) return

Loading...

; 46 | 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ); 56 | }; 57 | 58 | export default App; 59 | -------------------------------------------------------------------------------- /example/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/buildQuery/index.ts: -------------------------------------------------------------------------------- 1 | import { buildVariables } from '../buildVariables'; 2 | import buildGqlQuery, { BuildGqlQueryFactory } from '../buildGqlQuery'; 3 | import { getResponseParser, GetResponseParser } from '../getResponseParser'; 4 | import type { FetchType, IntrospectionResult } from '../types'; 5 | 6 | export type BuildQuery = (introspectionResults: IntrospectionResult) => ( 7 | aorFetchType: FetchType, 8 | resourceName: string, 9 | params: any 10 | ) => { 11 | query: any; 12 | variables: any; 13 | parseResponse: ({ data }: any) => { data: any; total?: number }; 14 | }; 15 | 16 | export type BuildQueryFactory = ( 17 | buildVariablesImpl: any, 18 | buildGqlQueryImpl: BuildGqlQueryFactory, 19 | getResponseParserImpl: GetResponseParser 20 | ) => BuildQuery; 21 | 22 | export const buildQueryFactory: BuildQueryFactory = 23 | (buildVariablesImpl, buildGqlQueryImpl, getResponseParserImpl) => 24 | (introspectionResults) => { 25 | const knownResources = introspectionResults.resources.map( 26 | (r) => r.type.name 27 | ); 28 | 29 | return (aorFetchType, resourceName, params) => { 30 | const resource = introspectionResults.resources.find( 31 | (r) => r.type.name === resourceName 32 | ); 33 | 34 | if (!resource) { 35 | if (knownResources.length) { 36 | throw new Error( 37 | `Unknown resource ${resourceName}. Make sure it has been declared on your server side schema. Known resources are ${knownResources.join( 38 | ', ' 39 | )}` 40 | ); 41 | } else { 42 | throw new Error( 43 | `Unknown resource ${resourceName}. No resources were found. Make sure it has been declared on your server side schema and check if your Authorization header is properly set up.` 44 | ); 45 | } 46 | } 47 | 48 | const queryType = resource[aorFetchType]; 49 | 50 | if (!queryType) { 51 | throw new Error( 52 | `No query or mutation matching fetch type ${aorFetchType} could be found for resource ${resource.type.name}` 53 | ); 54 | } 55 | 56 | const variables = buildVariablesImpl(introspectionResults)( 57 | resource, 58 | aorFetchType, 59 | params, 60 | queryType 61 | ); 62 | const query = buildGqlQueryImpl(introspectionResults)( 63 | resource, 64 | aorFetchType, 65 | queryType, 66 | variables 67 | ); 68 | const parseResponse = getResponseParserImpl(introspectionResults)( 69 | aorFetchType, 70 | resource 71 | ); 72 | 73 | return { 74 | query, 75 | variables, 76 | parseResponse, 77 | }; 78 | }; 79 | }; 80 | 81 | export default buildQueryFactory( 82 | buildVariables, 83 | buildGqlQuery, 84 | getResponseParser 85 | ); 86 | -------------------------------------------------------------------------------- /src/buildGqlQuery/buildArgs.ts: -------------------------------------------------------------------------------- 1 | import * as gqlTypes from 'graphql-ast-types-browser'; 2 | import { 3 | IntrospectionField, 4 | ArgumentNode, 5 | VariableDefinitionNode, 6 | } from 'graphql'; 7 | import { 8 | GET_LIST, 9 | GET_MANY, 10 | GET_MANY_REFERENCE, 11 | } from '../helpers/fetchActions'; 12 | import getArgType from '../helpers/getArgType'; 13 | import { FetchType } from '../types'; 14 | 15 | export type BuildArgs = ( 16 | query: IntrospectionField, 17 | variables: any 18 | ) => ArgumentNode[]; 19 | 20 | export type BuildMetaArgs = ( 21 | query: IntrospectionField, 22 | variables: any, 23 | aorFetchType: FetchType 24 | ) => ArgumentNode[]; 25 | 26 | export type BuildApolloArgs = ( 27 | query: IntrospectionField, 28 | variables: any 29 | ) => VariableDefinitionNode[]; 30 | 31 | export const buildArgs: BuildArgs = (query, variables) => { 32 | if (query.args.length === 0) { 33 | return []; 34 | } 35 | 36 | const validVariables = Object.keys(variables).filter( 37 | (k) => typeof variables[k] !== 'undefined' 38 | ); 39 | 40 | return query.args 41 | .filter((a) => validVariables.includes(a.name)) 42 | .reduce( 43 | (acc, arg) => [ 44 | ...acc, 45 | gqlTypes.argument( 46 | gqlTypes.name(arg.name), 47 | gqlTypes.variable(gqlTypes.name(arg.name)) 48 | ), 49 | ], 50 | [] as ArgumentNode[] 51 | ); 52 | }; 53 | 54 | export const buildMetaArgs: BuildMetaArgs = ( 55 | query, 56 | variables, 57 | aorFetchType 58 | ) => { 59 | if (query.args.length === 0) { 60 | return []; 61 | } 62 | 63 | const validVariables = Object.keys(variables).filter((k) => { 64 | if ( 65 | aorFetchType === GET_LIST || 66 | aorFetchType === GET_MANY || 67 | aorFetchType === GET_MANY_REFERENCE 68 | ) { 69 | return ( 70 | typeof variables[k] !== 'undefined' && k !== 'limit' && k !== 'offset' 71 | ); 72 | } 73 | 74 | return typeof variables[k] !== 'undefined'; 75 | }); 76 | 77 | return query.args 78 | .filter((a) => validVariables.includes(a.name)) 79 | .reduce( 80 | (acc, arg) => [ 81 | ...acc, 82 | gqlTypes.argument( 83 | gqlTypes.name(arg.name), 84 | gqlTypes.variable(gqlTypes.name(arg.name)) 85 | ), 86 | ], 87 | [] as ArgumentNode[] 88 | ); 89 | }; 90 | 91 | export const buildApolloArgs: BuildApolloArgs = (query, variables) => { 92 | if (query.args.length === 0) { 93 | return []; 94 | } 95 | 96 | const validVariables = Object.keys(variables).filter( 97 | (k) => typeof variables[k] !== 'undefined' 98 | ); 99 | 100 | return query.args 101 | .filter((a) => validVariables.includes(a.name)) 102 | .reduce((acc, arg) => { 103 | return [ 104 | ...acc, 105 | gqlTypes.variableDefinition( 106 | gqlTypes.variable(gqlTypes.name(arg.name)), 107 | getArgType(arg) 108 | ), 109 | ]; 110 | }, [] as VariableDefinitionNode[]); 111 | }; 112 | -------------------------------------------------------------------------------- /src/buildVariables/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GET_ONE, 3 | GET_LIST, 4 | GET_MANY, 5 | GET_MANY_REFERENCE, 6 | DELETE, 7 | CREATE, 8 | UPDATE, 9 | UPDATE_MANY, 10 | DELETE_MANY, 11 | } from '../helpers/fetchActions'; 12 | import { buildGetListVariables } from './buildGetListVariables'; 13 | import { buildUpdateVariables } from './buildUpdateVariables'; 14 | import { buildCreateVariables } from './buildCreateVariables'; 15 | import { makeNestedTarget } from './makeNestedTarget'; 16 | import { FetchType, IntrospectedResource, IntrospectionResult } from '../types'; 17 | 18 | export type BuildVariables = ( 19 | introspectionResults: IntrospectionResult 20 | ) => ( 21 | resource: IntrospectedResource, 22 | aorFetchType: FetchType, 23 | params: any, 24 | queryType: any 25 | ) => any; 26 | 27 | export const buildVariables: BuildVariables = 28 | (introspectionResults) => (resource, aorFetchType, params, queryType) => { 29 | switch (aorFetchType) { 30 | case GET_LIST: 31 | return buildGetListVariables(introspectionResults)( 32 | resource, 33 | aorFetchType, 34 | params 35 | ); 36 | case GET_MANY_REFERENCE: { 37 | var built = buildGetListVariables(introspectionResults)( 38 | resource, 39 | aorFetchType, 40 | params 41 | ); 42 | if (params.filter) { 43 | return { 44 | ...built, 45 | where: { 46 | _and: [ 47 | ...built['where']['_and'], 48 | makeNestedTarget(params.target, params.id), 49 | ], 50 | }, 51 | }; 52 | } 53 | return { 54 | ...built, 55 | where: makeNestedTarget(params.target, params.id), 56 | }; 57 | } 58 | case GET_MANY: 59 | case DELETE_MANY: 60 | return { 61 | where: { id: { _in: params.ids } }, 62 | }; 63 | 64 | case GET_ONE: 65 | return { 66 | where: { id: { _eq: params.id } }, 67 | limit: 1, 68 | }; 69 | 70 | case DELETE: 71 | return { 72 | where: { id: { _eq: params.id } }, 73 | }; 74 | case CREATE: 75 | return { 76 | objects: buildCreateVariables(introspectionResults)( 77 | resource, 78 | aorFetchType, 79 | params, 80 | queryType 81 | ), 82 | }; 83 | 84 | case UPDATE: 85 | return { 86 | _set: buildUpdateVariables(introspectionResults)( 87 | resource, 88 | aorFetchType, 89 | params, 90 | queryType 91 | ), 92 | where: { id: { _eq: params.id } }, 93 | }; 94 | 95 | case UPDATE_MANY: 96 | return { 97 | _set: buildUpdateVariables(introspectionResults)( 98 | resource, 99 | aorFetchType, 100 | params, 101 | queryType 102 | ), 103 | where: { id: { _in: params.ids } }, 104 | }; 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.5.1 (June 14, 2022) 4 | 5 | - Fix dependency issue 6 | 7 | ## 0.5.0 (June 6, 2022) 8 | 9 | - Upgrade library and sample to React Admin v4, thanks to @LucaColonnello 10 | 11 | ## 0.4.2 (May 6, 2022) 12 | 13 | - Example: Add Example v3 App 14 | - Feature: \_nin operator (#89), thanks to @fkowal 15 | - Feature: Enable support for \_contains operator and nested path in jsonb joins, thanks to @fkowal 16 | - Feature: Support nested fields when sorting by multiple columns, thanks to @daa 17 | - Bug Fix: buildFields types (#91), thanks to @cpv123 18 | - Bug Fix: Keep null values when sanitizing resources (#97), thanks to @nselikoff 19 | 20 | ## 0.4.1 (April 7, 2022) 21 | 22 | - Bug Fix: Variables for mutations are not being populated, thanks to @nselikoff 23 | 24 | ## 0.4.0 (March 2, 2022) 25 | 26 | - Full Typescript rewrite thanks to Chris Vibert @cpv123 27 | 28 | ## 0.3.0 (March 2, 2022) 29 | 30 | - Bug Fix: Update only includes edited fields 31 | - Feature: Support sorting by multiple fields 32 | - Bug Fix: Return dataProvider object, not function 33 | - Bug fix: nested keys with array values 34 | 35 | ## 0.2.0 (June 30, 2021) 36 | 37 | - Feature: Update only permitted fields. 38 | - Feature: Add option for custom aggregate field names. 39 | - Feature: Reference a nested object in a reference field. 40 | - Bug Fix: Issue in sanitizing a null value in an array. 41 | - Feature: Add support for nested field filtering 42 | - Bug Fix: Fix issue with null / dates 43 | - Bug Fix: Fix error with react-admin 1.13.0 for date inputs 44 | 45 | ## 0.1.0 (January 19, 2021) 46 | 47 | - **Breaking change**: This release is a complete rewrite of the library, replacing the API with code from `ra-data-hasura-graphql` library. The `steams/ra-data-hasura-graphql` will henceforth be archived. Refer `README.md` for usage instructions. 48 | 49 | ## 0.0.8 (March 18, 2020) 50 | 51 | - Bug Fix: Translate id to primary key for custom primary keys. 52 | - Bug Fix: Respect primary key on order by. 53 | - Bug Fix: Fix typo in GET_MANY_REFERENCE. 54 | - Bug Fix: Set `asc` as default sorting order in GET_LIST. 55 | 56 | ## 0.0.7 (September 17, 2019) 57 | 58 | - Bug Fix: Re-build library to fix discrepancies. Pass `where` arguments to `count` query. 59 | - Feature: Add support for httpClient to pass in dynamic headers. Backwards compatibility maintained for static headers. 60 | - Update package dependencies. 61 | 62 | ## 0.0.6 (June 14, 2019) 63 | 64 | - Bug Fix: Fix sort order, fix primary key when response not an array and add filters to GET\_\* operations. 65 | 66 | ## 0.0.5 (May 16, 2019) 67 | 68 | - Feature: Support specifying primary keys other than id for tables using a config object 69 | Example: `const config = { 'primaryKey': {'author': 'name'} }` 70 | 71 | ## 0.0.4 (April 25, 2019) 72 | 73 | - Feature: Support multiple schemas using "." separator. 74 | Example: `` 75 | 76 | ## 0.0.3 (January 24, 2019) 77 | 78 | - Bug Fix: Fix count query to support UUID 79 | 80 | ## 0.0.2 (January 20, 2019) 81 | 82 | - Bug Fix: GET_MANY_REFERENCE definition 83 | 84 | ## 0.0.1 (January 19, 2019) 85 | 86 | - Add support for hasura data provider 87 | -------------------------------------------------------------------------------- /src/customDataProvider/index.ts: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | import buildDataProvider, { Options } from 'ra-data-graphql'; 3 | import { 4 | GET_ONE, 5 | GET_LIST, 6 | GET_MANY, 7 | GET_MANY_REFERENCE, 8 | DELETE, 9 | CREATE, 10 | UPDATE, 11 | UPDATE_MANY, 12 | DELETE_MANY, 13 | } from '../helpers/fetchActions'; 14 | import { 15 | buildVariables as defaultBuildVariables, 16 | BuildVariables, 17 | } from '../buildVariables'; 18 | import { 19 | getResponseParser as defaultGetResponseParser, 20 | GetResponseParser, 21 | } from '../getResponseParser'; 22 | import { buildGqlQuery } from '../buildGqlQuery'; 23 | import { 24 | buildMetaArgs, 25 | buildArgs, 26 | buildApolloArgs, 27 | BuildMetaArgs, 28 | BuildArgs, 29 | BuildApolloArgs, 30 | } from '../buildGqlQuery/buildArgs'; 31 | import { buildFields, BuildFields } from '../buildGqlQuery/buildFields'; 32 | import { buildQueryFactory } from '../buildQuery'; 33 | import type { IntrospectionResult } from '../types'; 34 | 35 | const defaultOptions: Partial = { 36 | introspection: { 37 | operationNames: { 38 | [GET_LIST]: (resource) => `${resource.name}`, 39 | [GET_ONE]: (resource) => `${resource.name}`, 40 | [GET_MANY]: (resource) => `${resource.name}`, 41 | [GET_MANY_REFERENCE]: (resource) => `${resource.name}`, 42 | [CREATE]: (resource) => `insert_${resource.name}`, 43 | [UPDATE]: (resource) => `update_${resource.name}`, 44 | [UPDATE_MANY]: (resource) => `update_${resource.name}`, 45 | [DELETE]: (resource) => `delete_${resource.name}`, 46 | [DELETE_MANY]: (resource) => `delete_${resource.name}`, 47 | }, 48 | }, 49 | }; 50 | 51 | const buildGqlQueryDefaults = { 52 | buildFields, 53 | buildMetaArgs, 54 | buildArgs, 55 | buildApolloArgs, 56 | aggregateFieldName: (resourceName: string) => `${resourceName}_aggregate`, 57 | }; 58 | 59 | export type BuildCustomDataProvider = ( 60 | options: Partial, 61 | buildGqlQueryOverrides?: { 62 | buildFields?: BuildFields; 63 | buildMetaArgs?: BuildMetaArgs; 64 | buildArgs?: BuildArgs; 65 | buildApolloArgs?: BuildApolloArgs; 66 | aggregateFieldName?: (resourceName: string) => string; 67 | }, 68 | customBuildVariables?: BuildVariables, 69 | customGetResponseParser?: GetResponseParser 70 | ) => ReturnType; 71 | 72 | export const buildCustomDataProvider: BuildCustomDataProvider = ( 73 | options = {}, 74 | buildGqlQueryOverrides = {}, 75 | customBuildVariables = defaultBuildVariables, 76 | customGetResponseParser = defaultGetResponseParser 77 | ) => { 78 | const buildGqlQueryOptions = { 79 | ...buildGqlQueryDefaults, 80 | ...buildGqlQueryOverrides, 81 | }; 82 | 83 | const customBuildGqlQuery = (introspectionResults: IntrospectionResult) => 84 | buildGqlQuery( 85 | introspectionResults, 86 | buildGqlQueryOptions.buildFields, 87 | buildGqlQueryOptions.buildMetaArgs, 88 | buildGqlQueryOptions.buildArgs, 89 | buildGqlQueryOptions.buildApolloArgs, 90 | buildGqlQueryOptions.aggregateFieldName 91 | ); 92 | 93 | const buildQuery = buildQueryFactory( 94 | customBuildVariables, 95 | customBuildGqlQuery, 96 | customGetResponseParser 97 | ); 98 | 99 | return buildDataProvider(merge({}, defaultOptions, { buildQuery }, options)); 100 | }; 101 | -------------------------------------------------------------------------------- /src/buildGqlQuery/index.ts: -------------------------------------------------------------------------------- 1 | import * as gqlTypes from 'graphql-ast-types-browser'; 2 | import { IntrospectionField } from 'graphql'; 3 | import { 4 | GET_LIST, 5 | GET_MANY, 6 | GET_MANY_REFERENCE, 7 | DELETE, 8 | CREATE, 9 | UPDATE, 10 | UPDATE_MANY, 11 | DELETE_MANY, 12 | } from '../helpers/fetchActions'; 13 | import { buildFields, BuildFields } from './buildFields'; 14 | import { 15 | buildArgs, 16 | buildApolloArgs, 17 | buildMetaArgs, 18 | BuildArgs, 19 | BuildMetaArgs, 20 | BuildApolloArgs, 21 | } from './buildArgs'; 22 | import { FetchType, IntrospectionResult } from '../types'; 23 | 24 | export type BuildGqlQuery = ( 25 | introspectionResults: IntrospectionResult, 26 | buildFields: BuildFields, 27 | buildMetaArgs: BuildMetaArgs, 28 | buildArgs: BuildArgs, 29 | buildApolloArgs: BuildApolloArgs, 30 | aggregateFieldName: (resourceName: string) => string 31 | ) => ( 32 | resource: any, 33 | aorFetchType: FetchType, 34 | queryType: IntrospectionField, 35 | variables: any 36 | ) => any; 37 | 38 | export type BuildGqlQueryFactory = ( 39 | introspectionResults: IntrospectionResult 40 | ) => ReturnType; 41 | 42 | export const buildGqlQuery: BuildGqlQuery = 43 | ( 44 | _, 45 | buildFields, 46 | buildMetaArgs, 47 | buildArgs, 48 | buildApolloArgs, 49 | aggregateFieldName 50 | ) => 51 | (resource, aorFetchType, queryType, variables) => { 52 | const { sortField, sortOrder, ...metaVariables } = variables; 53 | const apolloArgs = buildApolloArgs(queryType, variables); 54 | const args = buildArgs(queryType, variables); 55 | const metaArgs = buildMetaArgs(queryType, metaVariables, aorFetchType); 56 | const fields = buildFields(resource.type, aorFetchType); 57 | if ( 58 | aorFetchType === GET_LIST || 59 | aorFetchType === GET_MANY || 60 | aorFetchType === GET_MANY_REFERENCE 61 | ) { 62 | return gqlTypes.document([ 63 | gqlTypes.operationDefinition( 64 | 'query', 65 | gqlTypes.selectionSet([ 66 | gqlTypes.field( 67 | gqlTypes.name(queryType.name), 68 | gqlTypes.name('items'), 69 | args, 70 | null, 71 | gqlTypes.selectionSet(fields) 72 | ), 73 | gqlTypes.field( 74 | gqlTypes.name(aggregateFieldName(queryType.name)), 75 | gqlTypes.name('total'), 76 | metaArgs, 77 | null, 78 | gqlTypes.selectionSet([ 79 | gqlTypes.field( 80 | gqlTypes.name('aggregate'), 81 | null, 82 | null, 83 | null, 84 | gqlTypes.selectionSet([ 85 | gqlTypes.field(gqlTypes.name('count')), 86 | ]) 87 | ), 88 | ]) 89 | ), 90 | ]), 91 | gqlTypes.name(queryType.name), 92 | apolloArgs 93 | ), 94 | ]); 95 | } 96 | 97 | if ( 98 | aorFetchType === CREATE || 99 | aorFetchType === UPDATE || 100 | aorFetchType === UPDATE_MANY || 101 | aorFetchType === DELETE || 102 | aorFetchType === DELETE_MANY 103 | ) { 104 | return gqlTypes.document([ 105 | gqlTypes.operationDefinition( 106 | 'mutation', 107 | gqlTypes.selectionSet([ 108 | gqlTypes.field( 109 | gqlTypes.name(queryType.name), 110 | gqlTypes.name('data'), 111 | args, 112 | null, 113 | gqlTypes.selectionSet([ 114 | gqlTypes.field( 115 | gqlTypes.name('returning'), 116 | null, 117 | null, 118 | null, 119 | gqlTypes.selectionSet(fields) 120 | ), 121 | ]) 122 | ), 123 | ]), 124 | gqlTypes.name(queryType.name), 125 | apolloArgs 126 | ), 127 | ]); 128 | } 129 | 130 | return gqlTypes.document([ 131 | gqlTypes.operationDefinition( 132 | 'query', 133 | gqlTypes.selectionSet([ 134 | gqlTypes.field( 135 | gqlTypes.name(queryType.name), 136 | gqlTypes.name('returning'), 137 | args, 138 | null, 139 | gqlTypes.selectionSet(fields) 140 | ), 141 | ]), 142 | gqlTypes.name(queryType.name), 143 | apolloArgs 144 | ), 145 | ]); 146 | }; 147 | 148 | const buildGqlQueryFactory: BuildGqlQueryFactory = (introspectionResults) => 149 | buildGqlQuery( 150 | introspectionResults, 151 | buildFields, 152 | buildMetaArgs, 153 | buildArgs, 154 | buildApolloArgs, 155 | (resourceName) => `${resourceName}_aggregate` 156 | ); 157 | 158 | export default buildGqlQueryFactory; 159 | -------------------------------------------------------------------------------- /src/buildVariables/buildGetListVariables.ts: -------------------------------------------------------------------------------- 1 | import set from 'lodash/set'; 2 | import omit from 'lodash/omit'; 3 | import getFinalType from '../helpers/getFinalType'; 4 | import type { 5 | FetchType, 6 | IntrospectionResult, 7 | IntrospectedResource, 8 | } from '../types'; 9 | 10 | type BuildGetListVariables = ( 11 | introspectionResults: IntrospectionResult 12 | ) => ( 13 | resource: IntrospectedResource, 14 | aorFetchType: FetchType, 15 | params: any 16 | ) => any; 17 | 18 | const SPLIT_TOKEN = '#'; 19 | const MULTI_SORT_TOKEN = ','; 20 | const SPLIT_OPERATION = '@'; 21 | 22 | export const buildGetListVariables: BuildGetListVariables = 23 | () => (resource, _, params) => { 24 | const result: any = {}; 25 | let { filter: filterObj = {} } = params; 26 | const { customFilters = [] } = params; 27 | 28 | /** 29 | * Nested entities are parsed by CRA, which returns a nested object 30 | * { 'level1': {'level2': 'test'}} 31 | * instead of { 'level1.level2': 'test'} 32 | * That's why we use a HASH for properties, when we declared nested stuff at CRA: 33 | * level1#level2@_ilike 34 | */ 35 | 36 | /** 37 | keys with comma separated values 38 | { 39 | 'title@ilike,body@like,authors@similar': 'test', 40 | 'col1@like,col2@like': 'val' 41 | } 42 | */ 43 | const orFilterKeys = Object.keys(filterObj).filter((e) => e.includes(',')); 44 | 45 | /** 46 | format filters 47 | { 48 | 'title@ilike': 'test', 49 | 'body@like': 'test', 50 | 'authors@similar': 'test', 51 | 'col1@like': 'val', 52 | 'col2@like': 'val' 53 | } 54 | */ 55 | const orFilterObj = orFilterKeys.reduce((acc, commaSeparatedKey) => { 56 | const keys = commaSeparatedKey.split(','); 57 | return { 58 | ...acc, 59 | ...keys.reduce((acc2, key) => { 60 | return { 61 | ...acc2, 62 | [key]: filterObj[commaSeparatedKey], 63 | }; 64 | }, {}), 65 | }; 66 | }, {}); 67 | filterObj = omit(filterObj, orFilterKeys); 68 | 69 | const makeNestedFilter = (obj: any, operation: string): any => { 70 | if (Object.keys(obj).length === 1) { 71 | const [key] = Object.keys(obj); 72 | return { [key]: makeNestedFilter(obj[key], operation) }; 73 | } else { 74 | return { [operation]: obj }; 75 | } 76 | }; 77 | 78 | const filterReducer = (obj: any) => (acc: any, key: any) => { 79 | let filter; 80 | if (key === 'ids') { 81 | filter = { id: { _in: obj['ids'] } }; 82 | } else if (Array.isArray(obj[key])) { 83 | let [keyName, operation = '_in', opPath] = key.split(SPLIT_OPERATION); 84 | let value = opPath 85 | ? set({}, opPath.split(SPLIT_TOKEN), obj[key]) 86 | : obj[key]; 87 | filter = set({}, keyName.split(SPLIT_TOKEN), { [operation]: value }); 88 | } else if (obj[key] && obj[key].format === 'hasura-raw-query') { 89 | filter = { [key]: obj[key].value || {} }; 90 | } else { 91 | let [keyName, operation = ''] = key.split(SPLIT_OPERATION); 92 | let operator; 93 | const field = resource.type.fields.find((f) => f.name === keyName); 94 | if (field) { 95 | switch (getFinalType(field.type).name) { 96 | case 'String': 97 | operation = operation || '_ilike'; 98 | operator = { 99 | [operation]: operation.includes('like') 100 | ? `%${obj[key]}%` 101 | : obj[key], 102 | }; 103 | filter = set({}, keyName.split(SPLIT_TOKEN), operator); 104 | break; 105 | default: 106 | operator = { 107 | [operation]: operation.includes('like') 108 | ? `%${obj[key]}%` 109 | : obj[key], 110 | }; 111 | filter = set({}, keyName.split(SPLIT_TOKEN), { 112 | [operation || '_eq']: obj[key], 113 | }); 114 | } 115 | } else { 116 | // Else block runs when the field is not found in Graphql schema. 117 | // Most likely it's nested. If it's not, it's better to let 118 | // Hasura fail with a message than silently fail/ignore it 119 | operator = { 120 | [operation || '_eq']: operation.includes('like') 121 | ? `%${obj[key]}%` 122 | : obj[key], 123 | }; 124 | filter = set({}, keyName.split(SPLIT_TOKEN), operator); 125 | } 126 | } 127 | return [...acc, filter]; 128 | }; 129 | const andFilters = Object.keys(filterObj) 130 | .reduce(filterReducer(filterObj), customFilters) 131 | .filter(Boolean); 132 | const orFilters = Object.keys(orFilterObj) 133 | .reduce(filterReducer(orFilterObj), []) 134 | .filter(Boolean); 135 | 136 | result['where'] = { 137 | _and: andFilters, 138 | ...(orFilters.length && { _or: orFilters }), 139 | }; 140 | 141 | if (params.pagination) { 142 | result['limit'] = parseInt(params.pagination.perPage, 10); 143 | result['offset'] = 144 | (params.pagination.page - 1) * params.pagination.perPage; 145 | } 146 | 147 | if (params.sort) { 148 | const { field, order } = params.sort; 149 | const hasMultiSort = 150 | field.includes(MULTI_SORT_TOKEN) || order.includes(MULTI_SORT_TOKEN); 151 | if (hasMultiSort) { 152 | const fields = field.split(MULTI_SORT_TOKEN); 153 | const orders = order 154 | .split(MULTI_SORT_TOKEN) 155 | .map((order: string) => order.toLowerCase()); 156 | 157 | if (fields.length !== orders.length) { 158 | throw new Error( 159 | `The ${ 160 | resource.type.name 161 | } list must have an order value for each sort field. Sort fields are "${fields.join( 162 | ',' 163 | )}" but sort orders are "${orders.join(',')}"` 164 | ); 165 | } 166 | 167 | const multiSort = fields.map((field: any, index: number) => 168 | set({}, field, orders[index]) 169 | ); 170 | result['order_by'] = multiSort; 171 | } else { 172 | result['order_by'] = set({}, field, order.toLowerCase()); 173 | } 174 | } 175 | 176 | return result; 177 | }; 178 | -------------------------------------------------------------------------------- /example/hasura/migrations/default/1646834482402_init/up.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION public.set_current_timestamp_updated_at () 2 | RETURNS TRIGGER 3 | LANGUAGE plpgsql 4 | AS $$ 5 | DECLARE 6 | _new record; 7 | BEGIN 8 | _new := NEW; 9 | _new. "updated_at" = NOW(); 10 | RETURN _new; 11 | END; 12 | $$; 13 | 14 | -- Create an "url" Postgres type that is an alias for "text" 15 | -- Which validates the input is an URL 16 | CREATE DOMAIN url AS text CHECK (VALUE ~ 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#()?&//=]*)'); 17 | 18 | COMMENT ON DOMAIN url IS 'Match URLs (http or https)'; 19 | 20 | 21 | CREATE TABLE "address" ( 22 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 23 | name text, 24 | created_at timestamptz DEFAULT now() NOT NULL, 25 | updated_at timestamptz DEFAULT now() NOT NULL, 26 | user_id int NOT NULL, 27 | city text NOT NULL, 28 | state text NOT NULL, 29 | -- Use text for zipcode to handle ZIP+4 extended zipcodes 30 | zipcode text NOT NULL, 31 | address_line_one text NOT NULL, 32 | address_line_two text 33 | ); 34 | 35 | COMMENT ON TABLE address IS 'A physical billing/shipping address, attached to a user account'; 36 | 37 | 38 | CREATE TABLE "site_admin" ( 39 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 40 | created_at timestamptz DEFAULT now() NOT NULL, 41 | updated_at timestamptz DEFAULT now() NOT NULL, 42 | name text NOT NULL, 43 | email text NOT NULL UNIQUE, 44 | password text NOT NULL, 45 | refresh_token text UNIQUE 46 | ); 47 | 48 | COMMENT ON TABLE "site_admin" IS 'Someone administrative capabilities on the site'; 49 | 50 | COMMENT ON COLUMN "site_admin"."password" IS 'A bcrypt-hashed version of the admin password, compared against securely in the JWT Auth API handler for sign-in'; 51 | 52 | 53 | CREATE TABLE "order" ( 54 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 55 | created_at timestamptz DEFAULT now() NOT NULL, 56 | updated_at timestamptz DEFAULT now() NOT NULL, 57 | user_id int NOT NULL, 58 | billing_address_id int NOT NULL, 59 | shipping_address_id int NOT NULL, 60 | is_shipped boolean DEFAULT FALSE NOT NULL, 61 | order_total numeric, 62 | status text DEFAULT 'CREATED' NOT NULL 63 | ); 64 | 65 | COMMENT ON TABLE "order" IS 'An order from a customer, containing one or more products and quantities'; 66 | 67 | 68 | CREATE TABLE "order_product" ( 69 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 70 | created_at timestamptz DEFAULT now() NOT NULL, 71 | updated_at timestamptz DEFAULT now() NOT NULL, 72 | order_id int NOT NULL, 73 | product_id int NOT NULL, 74 | quantity int NOT NULL 75 | ); 76 | 77 | COMMENT ON TABLE "order_product" IS 'A product belonging to a customer order, along with a quantity'; 78 | 79 | 80 | CREATE TABLE order_status ( 81 | status text PRIMARY KEY 82 | ); 83 | 84 | INSERT INTO order_status ( 85 | status) 86 | VALUES ( 87 | 'CREATED'), 88 | ( 89 | 'PAID'), 90 | ( 91 | 'SHIPPED'), 92 | ( 93 | 'DELIVERED'), 94 | ( 95 | 'CANCELLED'), 96 | ( 97 | 'REFUNDED'); 98 | CREATE TABLE product ( 99 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 100 | created_at timestamptz DEFAULT now() NOT NULL, 101 | updated_at timestamptz DEFAULT now() NOT NULL, 102 | name text NOT NULL, 103 | -- Do we want description to be mandatory? 104 | description text, 105 | category_display_name text NOT NULL, 106 | brand text, 107 | price numeric NOT NULL, 108 | -- TODO: Make "product_image_url" table and one-to-many relationship from product -> product_image_url 109 | -- The Kaggle data just came with it in this format so we can hackily (ab)use JSONB for shitty relationships 110 | image_urls jsonb 111 | ); 112 | 113 | 114 | -- Hasura Enum table containing product categories 115 | -- Arguably, making categories an Enum might not be a best practice 116 | -- 117 | -- For a few reasons: 118 | -- 119 | -- Primarily that, because each change to the Enum table (insert/update/delete) modifies the GQL schema types 120 | -- any sort of Client SDK containing the schema types has to be regenerated to stay up-to-date after any changes 121 | -- 122 | -- And also because the metadata needs to be reloaded after modifications to the enum, since this can't be detected and reflected automatically yet 123 | -- 124 | -- But, this is a great way to demo this feature and a semi-realistic usecase 125 | CREATE TABLE product_category_enum ( 126 | name text PRIMARY KEY, 127 | display_name text NOT NULL UNIQUE 128 | ); 129 | 130 | INSERT INTO product_category_enum ( 131 | display_name, 132 | name) 133 | VALUES ( 134 | 'Home Furnishing', 135 | 'home_furnishing'), 136 | ( 137 | 'Computers', 138 | 'computers'), 139 | ( 140 | 'Baby Care', 141 | 'baby_care'), 142 | ( 143 | 'Wearable Smart Devices', 144 | 'wearable_smart_devices'), 145 | ( 146 | 'Furniture', 147 | 'furniture'), 148 | ( 149 | 'Home Entertainment', 150 | 'home_entertainment'), 151 | ( 152 | 'Home & Kitchen', 153 | 'home_kitchen'), 154 | ( 155 | 'Clothing', 156 | 'clothing'), 157 | ( 158 | 'Beauty and Personal Care', 159 | 'beauty_and_personal_care'), 160 | ( 161 | 'Sunglasses', 162 | 'sunglasses'), 163 | ( 164 | 'Tools & Hardware', 165 | 'tools_hardware'), 166 | ( 167 | 'Household Supplies', 168 | 'household_supplies'), 169 | ( 170 | 'Home Improvement', 171 | 'home_improvement'), 172 | ( 173 | 'Footwear', 174 | 'footwear'), 175 | ( 176 | 'Gaming', 177 | 'gaming'), 178 | ( 179 | 'Mobiles & Accessories', 180 | 'mobiles_accessories'), 181 | ( 182 | 'Sports & Fitness', 183 | 'sports_fitness'), 184 | ( 185 | 'Health & Personal Care Appliances', 186 | 'health_personal_care_appliances'), 187 | ( 188 | 'Home Decor & Festive Needs', 189 | 'home_decor_festive_needs'), 190 | ( 191 | 'Pens & Stationery', 192 | 'pens_stationery'), 193 | ( 194 | 'Watches', 195 | 'watches'), 196 | ( 197 | 'Food & Nutrition', 198 | 'food_nutrition'), 199 | ( 200 | 'Kitchen & Dining', 201 | 'kitchen_dining'), 202 | ( 203 | 'Pet Supplies', 204 | 'pet_supplies'), 205 | ( 206 | 'Jewellery', 207 | 'jewellery'), 208 | ( 209 | 'Cameras & Accessories', 210 | 'cameras_accessories'), 211 | ( 212 | 'Automotive', 213 | 'automotive'), 214 | ( 215 | 'eBooks', 216 | 'e_books'), 217 | ( 218 | 'Toys & School Supplies', 219 | 'toys_school_supplies'), 220 | ( 221 | 'Eyewear', 222 | 'eyewear'), 223 | ( 224 | 'Automation & Robotics', 225 | 'automation_robotics'), 226 | ( 227 | 'Bags, Wallets & Belts', 228 | 'bags_wallets_belts'); 229 | 230 | 231 | CREATE TABLE "product_review" ( 232 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 233 | created_at timestamptz DEFAULT now() NOT NULL, 234 | updated_at timestamptz DEFAULT now() NOT NULL, 235 | user_id int NOT NULL, 236 | product_id int NOT NULL, 237 | rating int NOT NULL CHECK (rating >= 1 AND rating <= 5), 238 | -- 5,000 characters = 800 words or 1.5 pages single-spaced (based on 6-char word avg) 239 | comment text NOT NULL CHECK (char_length(comment) <= 5000), 240 | -- Only allow each person to leave one review per product they've purchased 241 | CONSTRAINT one_review_per_person_and_product UNIQUE (user_id, product_id) 242 | ); 243 | 244 | COMMENT ON TABLE "product_review" IS 'A review for a product which a customer has purchased before'; 245 | 246 | 247 | CREATE TABLE "user" ( 248 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 249 | created_at timestamptz DEFAULT now() NOT NULL, 250 | updated_at timestamptz DEFAULT now() NOT NULL, 251 | name text NOT NULL, 252 | email text NOT NULL UNIQUE, 253 | password text NOT NULL, 254 | -- refresh_token really should be NOT NULL but the seed data doesn't have them 255 | refresh_token text UNIQUE 256 | ); 257 | 258 | COMMENT ON TABLE "user" IS 'Someone with an account on the site, who uses it to make purchases'; 259 | 260 | COMMENT ON COLUMN "user"."password" IS 'A bcrypt-hashed version of the user password, compared against securely in the JWT Auth API handler for sign-in'; 261 | 262 | 263 | ALTER TABLE ONLY public.address 264 | ADD CONSTRAINT address_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.user (id); 265 | 266 | 267 | ALTER TABLE ONLY public.order 268 | ADD CONSTRAINT order_billing_address_id_fkey FOREIGN KEY 269 | (billing_address_id) REFERENCES public.address (id); 270 | 271 | ALTER TABLE ONLY public.order 272 | ADD CONSTRAINT order_shipping_address_id_fkey FOREIGN KEY 273 | (shipping_address_id) REFERENCES public.address (id); 274 | 275 | ALTER TABLE ONLY public.order 276 | ADD CONSTRAINT order_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.user (id); 277 | 278 | ALTER TABLE ONLY public.order 279 | ADD CONSTRAINT order_status_fkey FOREIGN KEY (status) REFERENCES 280 | public.order_status (status); 281 | 282 | 283 | /*Functions*/ 284 | CREATE OR REPLACE FUNCTION public.gen_order_total () 285 | RETURNS TRIGGER 286 | LANGUAGE plpgsql 287 | STABLE 288 | AS $function$ 289 | DECLARE 290 | sumtotal numeric; 291 | BEGIN 292 | SELECT 293 | TRUNC(SUM(p.price), 2) INTO STRICT sumtotal 294 | FROM 295 | public.order o 296 | INNER JOIN public.order_product op ON (o.id = op.order_id) 297 | INNER JOIN public.product p ON (op.product_id = p.id) 298 | WHERE 299 | o.id = OLD.id 300 | GROUP BY 301 | o.id; 302 | NEW.order_total := sumtotal; 303 | RETURN NEW; 304 | EXCEPTION 305 | WHEN no_data_found THEN 306 | RAISE NOTICE 'No products found for %', OLD.id; 307 | RETURN NEW; 308 | END; 309 | 310 | $function$; 311 | 312 | 313 | ALTER TABLE ONLY public.order_product 314 | ADD CONSTRAINT order_id_fkey FOREIGN KEY (order_id) REFERENCES public.order (id); 315 | 316 | ALTER TABLE ONLY public.order_product 317 | ADD CONSTRAINT product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product (id); 318 | 319 | 320 | ALTER TABLE ONLY public.product 321 | ADD CONSTRAINT category_display_name_fk FOREIGN KEY (category_display_name) REFERENCES public.product_category_enum (display_name); 322 | 323 | 324 | ALTER TABLE ONLY public.product_review 325 | ADD CONSTRAINT user_id_fkey FOREIGN KEY (user_id) REFERENCES public.user (id); 326 | 327 | ALTER TABLE ONLY public.product_review 328 | ADD CONSTRAINT product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product (id); 329 | 330 | 331 | CREATE TRIGGER set_address_updated_at 332 | BEFORE UPDATE ON public.address 333 | FOR EACH ROW 334 | EXECUTE FUNCTION public.set_current_timestamp_updated_at (); 335 | CREATE TRIGGER set_site_admin_updated_at 336 | BEFORE UPDATE ON public.site_admin 337 | FOR EACH ROW 338 | EXECUTE FUNCTION public.set_current_timestamp_updated_at (); 339 | CREATE TRIGGER set_order_updated_at 340 | BEFORE UPDATE ON public.order 341 | FOR EACH ROW 342 | EXECUTE FUNCTION public.set_current_timestamp_updated_at (); 343 | 344 | CREATE TRIGGER sum_order 345 | BEFORE INSERT OR UPDATE ON public.order 346 | FOR EACH ROW 347 | EXECUTE PROCEDURE public.gen_order_total (); 348 | CREATE TRIGGER set_order_product_updated_at 349 | BEFORE UPDATE ON public.order_product 350 | FOR EACH ROW 351 | EXECUTE FUNCTION public.set_current_timestamp_updated_at (); 352 | CREATE TRIGGER set_product_updated_at 353 | BEFORE UPDATE ON public.product 354 | FOR EACH ROW 355 | EXECUTE FUNCTION public.set_current_timestamp_updated_at (); 356 | 357 | CREATE TRIGGER set_product_review_updated_at 358 | BEFORE UPDATE ON public.product_review 359 | FOR EACH ROW 360 | EXECUTE FUNCTION public.set_current_timestamp_updated_at (); 361 | CREATE TRIGGER set_user_updated_at 362 | BEFORE UPDATE ON public.user 363 | FOR EACH ROW 364 | EXECUTE FUNCTION public.set_current_timestamp_updated_at (); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ra-data-hasura 2 | 3 | A GraphQL data provider for [react-admin v4](https://marmelab.com/react-admin) tailored to target [Hasura](https://hasura.io/) GraphQL endpoints. For React Admin v3 use v0.4.2 of this library. 4 | 5 | - [ra-data-hasura](#ra-data-hasura) 6 | - [Benefits and Motivation](#benefits-and-motivation) 7 | - [Installation](#installation) 8 | - [Usage](#usage) 9 | - [How It Works](#how-it-works) 10 | - [Options](#options) 11 | - [Customize the Apollo client](#customize-the-apollo-client) 12 | - [Adding Authentication Headers](#adding-authentication-headers) 13 | - [Customize the introspection](#customize-the-introspection) 14 | - [Customize the Data Return](#customize-the-data-return) 15 | - [Customizing queries](#customizing-queries) 16 | - [Example: extending a query to include related entities](#example-extending-a-query-to-include-related-entities) 17 | - [Example: write a completely custom query](#example-write-a-completely-custom-query) 18 | - [Special Filter Feature](#special-filter-feature) 19 | - [Nested filtering](#nested-filtering) 20 | - [Jsonb filtering](#jsonb-filtering) 21 | - [Sorting lists by multiple columns](#sorting-lists-by-multiple-columns) 22 | - [Contributing](#contributing) 23 | - [Credits](#credits) 24 | 25 | Example applications demonstrating usage: 26 | 27 | - [react-admin-low-code](https://github.com/cpursley/react-admin-low-code) (basic usage) 28 | - [react-admin-hasura-queries](https://github.com/cpv123/react-admin-hasura-queries) (usage with custom queries) 29 | 30 | ## Benefits and Motivation 31 | 32 | This utility is built on top of [ra-data-graphql](https://github.com/vladimiregorov/react-admin/blob/master/packages/ra-data-graphql/README.md) and is a custom data provider for the current Hasura GraphQL API format. 33 | 34 | The existing ra-data-graphql-simple provider, requires that your GraphQL endpoint implement a specific grammar for the objects and methods exposed, which is different with Hasura because the exposed objects and methods are generated differently. 35 | 36 | This utility auto generates valid GraphQL queries based on the properties exposed by the Hasura API such as `object_bool_exp` and `object_set_input`. 37 | 38 | ## Installation 39 | 40 | Install with: 41 | 42 | ```sh 43 | npm install --save graphql ra-data-hasura 44 | ``` 45 | 46 | ## Usage 47 | 48 | The `ra-data-hasura` package exposes a single function with the following signature: 49 | 50 | ```js 51 | buildHasuraProvider( 52 | options?: Object, 53 | buildGqlQueryOverrides?: Object, 54 | customBuildVariables?: Function, 55 | customGetResponseParser?: Function, 56 | ) => Function 57 | ``` 58 | 59 | See the [Options](#options) and [Customizing queries](#customizing-queries) sections below for more details on these arguments. 60 | 61 | This function acts as a constructor for a `dataProvider` based on a Hasura GraphQL endpoint. When executed, this function calls the endpoint, running an [introspection](http://graphql.org/learn/introspection/) query to learn about the specific data models exposed by your Hasura endpoint. It uses the result of this query (the GraphQL schema) to automatically configure the `dataProvider` accordingly. 62 | 63 | ```jsx 64 | // Initialize the dataProvider before rendering react-admin resources. 65 | import React, { useState, useEffect } from 'react'; 66 | import buildHasuraProvider from 'ra-data-hasura'; 67 | import { Admin, Resource } from 'react-admin'; 68 | 69 | import { PostCreate, PostEdit, PostList } from './posts'; 70 | 71 | const App = () => { 72 | const [dataProvider, setDataProvider] = useState(null); 73 | 74 | useEffect(() => { 75 | const buildDataProvider = async () => { 76 | const dataProvider = await buildHasuraProvider({ 77 | clientOptions: { uri: 'http://localhost:8080/v1/graphql' }, 78 | }); 79 | setDataProvider(() => dataProvider); 80 | }; 81 | buildDataProvider(); 82 | }, []); 83 | 84 | if (!dataProvider) return

Loading...

; 85 | 86 | return ( 87 | 88 | 94 | 95 | ); 96 | }; 97 | 98 | export default App; 99 | ``` 100 | 101 | ## How It Works 102 | 103 | The data provider converts React Admin queries into the form expected by Hasura's GraphQL API. For example, a React Admin `GET_LIST` request for a person resource with the parameters : 104 | 105 | ```json 106 | { 107 | "pagination": { "page": 1, "perPage": 5 }, 108 | "sort": { "field": "name", "order": "DESC" }, 109 | "filter": { 110 | "ids": [101, 102] 111 | } 112 | } 113 | ``` 114 | 115 | will generate the following GraphQL request for Hasura : 116 | 117 | ``` 118 | query person($limit: Int, $offset: Int, $order_by: [person_order_by!]!, $where: person_bool_exp) { 119 | items: person(limit: $limit, offset: $offset, order_by: $order_by, where: $where) { 120 | id 121 | name 122 | address_id 123 | } 124 | total: person_aggregate(limit: $limit, offset: $offset, order_by: $order_by, where: $where) { 125 | aggregate { 126 | count 127 | } 128 | } 129 | } 130 | ``` 131 | 132 | With the following variables to be passed alongside the query: 133 | 134 | ``` 135 | { 136 | limit: 5, 137 | offset: 0, 138 | order_by: { name: 'desc' }, 139 | where: { 140 | _and: [ 141 | { 142 | id: { 143 | _in: [101, 102] 144 | } 145 | } 146 | ] 147 | } 148 | } 149 | 150 | ``` 151 | 152 | React Admin sort and filter objects will be converted appropriately, for example sorting with dot notation: 153 | 154 | ```jsx 155 | export const PostList = (props) => ( 156 | 157 | ... 158 | 159 | ); 160 | ``` 161 | 162 | will generate the following GraphQL query variables: 163 | 164 | ```js 165 | { 166 | limit: 25, 167 | offset: 0, 168 | order_by: { user: { email: 'desc' } } 169 | } 170 | ``` 171 | 172 | ## Options 173 | 174 | ### Customize the Apollo client 175 | 176 | You can either supply just the client options: 177 | 178 | ```js 179 | buildGraphQLProvider({ 180 | clientOptions: { 181 | uri: 'http://localhost:8080/v1/graphql', 182 | ...otherApolloOptions, 183 | }, 184 | }); 185 | ``` 186 | 187 | or supply the client instance directly: 188 | 189 | ```js 190 | buildGraphQLProvider({ client: myClient }); 191 | ``` 192 | 193 | ### Adding Authentication Headers 194 | 195 | To send authentication headers, you'll need to supply the client instance directly with headers defined: 196 | 197 | ```js 198 | import { ApolloClient, InMemoryCache } from '@apollo/client'; 199 | 200 | const myClientWithAuth = new ApolloClient({ 201 | uri: 'http://localhost:8080/v1/graphql', 202 | cache: new InMemoryCache(), 203 | headers: { 204 | 'x-hasura-admin-secret': 'hasuraAdminSecret', 205 | // 'Authorization': `Bearer xxxx`, 206 | }, 207 | }); 208 | 209 | buildHasuraProvider({ client: myClientWithAuth }); 210 | ``` 211 | 212 |
213 | 214 | Adding headers using just client options 215 | 216 | You can also add headers using only client options rather than the client itself: 217 | 218 | ```js 219 | import { createHttpLink } from '@apollo/client'; 220 | import { setContext } from '@apollo/client/link/context'; 221 | 222 | const authLink = setContext((_, { headers }) => ({ 223 | headers: { 224 | ...headers, 225 | 'x-hasura-admin-secret': 'hasuraAdminSecret', 226 | // 'Authorization': `Bearer xxxx`, 227 | }, 228 | })); 229 | 230 | const httpLink = createHttpLink({ 231 | uri: 'http://localhost:8080/v1/graphql', 232 | }); 233 | 234 | const clientOptionsWithAuth = { 235 | link: authLink.concat(httpLink), 236 | }; 237 | 238 | buildHasuraProvider({ client: clientOptionsWithAuth }); 239 | ``` 240 | 241 |
242 | 243 | ### Customize the introspection 244 | 245 | These are the default options for introspection: 246 | 247 | ```js 248 | const introspectionOptions = { 249 | include: [], // Either an array of types to include or a function which will be called for every type discovered through introspection 250 | exclude: [], // Either an array of types to exclude or a function which will be called for every type discovered through introspection 251 | }; 252 | 253 | // Including types 254 | const introspectionOptions = { 255 | include: ['Post', 'Comment'], 256 | }; 257 | 258 | // Excluding types 259 | const introspectionOptions = { 260 | exclude: ['CommandItem'], 261 | }; 262 | 263 | // Including types with a function 264 | const introspectionOptions = { 265 | include: (type) => ['Post', 'Comment'].includes(type.name), 266 | }; 267 | 268 | // Including types with a function 269 | const introspectionOptions = { 270 | exclude: (type) => !['Post', 'Comment'].includes(type.name), 271 | }; 272 | ``` 273 | 274 | **Note**: `exclude` and `include` are mutually exclusives and `include` will take precendance. 275 | 276 | **Note**: When using functions, the `type` argument will be a type returned by the introspection query. Refer to the [introspection](http://graphql.org/learn/introspection/) documentation for more information. 277 | 278 | Pass the introspection options to the `buildApolloProvider` function: 279 | 280 | ```js 281 | buildApolloProvider({ introspection: introspectionOptions }); 282 | ``` 283 | 284 | ### Customize the Data Return 285 | 286 | Once the data is returned back from the provider, you can customize it by implementing the `DataProvider` interface. [An example is changing the ID key](https://marmelab.com/react-admin/FAQ.html#can-i-have-custom-identifiersprimary-keys-for-my-resources). 287 | 288 | ```typescript 289 | const [dataProvider, setDataProvider] = React.useState( 290 | null 291 | ); 292 | 293 | React.useEffect(() => { 294 | const buildDataProvider = async () => { 295 | const dataProviderHasura = await buildHasuraProvider({ 296 | clientOptions: { 297 | uri: 'http://localhost:8080/v1/graphql', 298 | }, 299 | }); 300 | const modifiedProvider: DataProvider = { 301 | getList: async (resource, params) => { 302 | let { data, ...metadata } = await dataProviderHasura.getList( 303 | resource, 304 | params 305 | ); 306 | 307 | if (resource === 'example_resource_name') { 308 | data = data.map( 309 | (val): Record => ({ 310 | ...val, 311 | id: val.region_id, 312 | }) 313 | ); 314 | } 315 | 316 | return { 317 | data: data as any[], 318 | ...metadata, 319 | }; 320 | }, 321 | getOne: (resource, params) => dataProviderHasura.getOne(resource, params), 322 | getMany: (resource, params) => 323 | dataProviderHasura.getMany(resource, params), 324 | getManyReference: (resource, params) => 325 | dataProviderHasura.getManyReference(resource, params), 326 | update: (resource, params) => dataProviderHasura.update(resource, params), 327 | updateMany: (resource, params) => 328 | dataProviderHasura.updateMany(resource, params), 329 | create: (resource, params) => dataProviderHasura.create(resource, params), 330 | delete: (resource, params) => dataProviderHasura.delete(resource, params), 331 | deleteMany: (resource, params) => 332 | dataProviderHasura.deleteMany(resource, params), 333 | }; 334 | setDataProvider(() => modifiedProvider); 335 | }; 336 | buildDataProvider(); 337 | }, []); 338 | ``` 339 | 340 | ## Customizing queries 341 | 342 | Queries built by this data provider are made up of 3 parts: 343 | 344 | 1. The set of fields requested 345 | 2. The variables defining the query constraints like `where, order_by, limit, offset` 346 | 3. The response format e.g. `{ data: {...}, total: 100 }` 347 | 348 | Each of these can be customized - functions overriding numbers 2 and 3 can be passed to directly to `buildDataProvider` as shown in [Usage](#usage), whilst number 1 can be customized in parts using the `buildGqlQueryOverrides` object argument: 349 | 350 | ```js 351 | { 352 | buildFields?: Function, 353 | buildMetaArgs?: Function, 354 | buildArgs?: Function, 355 | buildApolloArgs?: Function, 356 | } 357 | ``` 358 | 359 | A likely scenario is that you want to override only the `buildFields` part so that you can customize your GraphQL queries - requesting fewer fields, more fields, nested fields etc. 360 | 361 | This can be easily done, and importantly can be done using `gql` template literal tags, as shown in the examples below. Take a look at this [demo application](https://github.com/cpv123/react-admin-hasura-queries) to see it in action. 362 | 363 | ### Example: extending a query to include related entities 364 | 365 | By default, the data provider will generate queries that include all fields on a resource, but without any relationships to nested entities. If you would like to keep these base fields but extend the query to also include related entities, then you can write a custom `buildFields` like this: 366 | 367 | ```ts 368 | import buildDataProvider, { buildFields } from 'ra-data-hasura'; 369 | import type { BuildFields } from 'ra-data-hasura'; 370 | import gql from 'graphql-tag'; 371 | 372 | /** 373 | * Extracts just the fields from a GraphQL AST. 374 | * @param {GraphQL AST} queryAst 375 | */ 376 | const extractFieldsFromQuery = (queryAst) => { 377 | return queryAst.definitions[0].selectionSet.selections; 378 | }; 379 | 380 | // Define the additional fields that we want. 381 | const EXTENDED_GET_ONE_USER = gql` 382 | { 383 | todos_aggregate { 384 | aggregate { 385 | count 386 | } 387 | } 388 | } 389 | `; 390 | 391 | const customBuildFields: BuildFields = (type, fetchType) => { 392 | const resourceName = type.name; 393 | 394 | // First take the default fields (all, but no related or nested). 395 | const defaultFields = buildFields(type, fetchType); 396 | 397 | if (resourceName === 'users' && fetchType === 'GET_ONE') { 398 | const relatedEntities = extractFieldsFromQuery(EXTENDED_GET_ONE_USER); 399 | defaultFields.push(...relatedEntities); 400 | } 401 | 402 | // Extend other queries for other resources/fetchTypes here... 403 | 404 | return defaultFields; 405 | }; 406 | 407 | buildDataProvider(options, { buildFields: customBuildFields }); 408 | ``` 409 | 410 | ### Example: write a completely custom query 411 | 412 | If you want full control over the GraphQL query, then you can define the entire set of fields like this: 413 | 414 | ```ts 415 | import gql from 'graphql-tag'; 416 | import buildDataProvider, { buildFields } from 'ra-data-hasura'; 417 | import type { BuildFields } from 'ra-data-hasura'; 418 | 419 | /** 420 | * Extracts just the fields from a GraphQL AST. 421 | * @param {GraphQL AST} queryAst 422 | */ 423 | const extractFieldsFromQuery = (queryAst) => { 424 | return queryAst.definitions[0].selectionSet.selections; 425 | }; 426 | 427 | const GET_ONE_USER = gql` 428 | { 429 | id 430 | name 431 | todos( 432 | where: { is_completed: { _eq: false } } 433 | order_by: { created_at: asc } 434 | ) { 435 | title 436 | } 437 | todos_aggregate { 438 | aggregate { 439 | count 440 | } 441 | } 442 | } 443 | `; 444 | 445 | const customBuildFields: BuildFields = (type, fetchType) => { 446 | const resourceName = type.name; 447 | 448 | if (resourceName === 'users' && fetchType === 'GET_ONE') { 449 | return extractFieldsFromQuery(GET_ONE_USER); 450 | } 451 | 452 | // No custom query defined, so use the default query fields (all, but no related/nested). 453 | return buildFields(type, fetchType); 454 | }; 455 | 456 | buildDataProvider(options, { buildFields: customBuildFields }); 457 | ``` 458 | 459 | Note that when using this approach in particular, it is possible that you will come across [this issue](https://github.com/cpv123/react-admin-hasura-queries#troubleshooting). 460 | 461 | ## Special Filter Feature 462 | 463 | This adapter allows filtering several columns at a time with using specific comparators, e.g. `ilike`, `like`, `eq`, etc. 464 | 465 | ```tsx 466 | 467 | 472 | 473 | ``` 474 | 475 | It will generate the following filter payload 476 | 477 | ```json 478 | { 479 | "variables": { 480 | "where": { 481 | "_and": [], 482 | "_or": [ 483 | { 484 | "email": { 485 | "_ilike": "%edu%" 486 | } 487 | }, 488 | { 489 | "first_name": { 490 | "_eq": "edu" 491 | } 492 | }, 493 | { 494 | "last_name": { 495 | "_like": "%edu%" 496 | } 497 | } 498 | ] 499 | }, 500 | "limit": 10, 501 | "offset": 0, 502 | "order_by": { 503 | "id": "asc" 504 | } 505 | } 506 | } 507 | ``` 508 | 509 | The adapter assigns default comparator depends on the data type if it is not provided. 510 | For string data types, it assumes as text search and uses `ilike` otherwise it uses `eq`. 511 | For string data types that uses `like` or `ilike` it automatically transform the filter `value` as `%value%`. 512 | 513 | ### Nested filtering 514 | 515 | Nested filtering is supported using # as a field separator. 516 | 517 | ```tsx 518 | 523 | ``` 524 | 525 | Will produce the following payload: 526 | 527 | ```json 528 | { 529 | "where": { 530 | "_and": [], 531 | "_or": [ 532 | { 533 | "indication": { 534 | "name": { 535 | "_ilike": "%TEXT%" 536 | } 537 | } 538 | }, 539 | { 540 | "drug": { 541 | "name": { 542 | "_ilike": "%TEXT%" 543 | } 544 | } 545 | }, 546 | { 547 | "sponsor": { 548 | "name": { 549 | "_ilike": "%TEXT%" 550 | } 551 | } 552 | } 553 | ] 554 | }, 555 | "limit": 10, 556 | "offset": 0, 557 | "order_by": { 558 | "id": "asc" 559 | } 560 | } 561 | ``` 562 | 563 | ## Jsonb filtering 564 | 565 | ```jsx 566 | 567 | ``` 568 | 569 | Will produce payload: 570 | 571 | ```json 572 | { 573 | "where": { 574 | "_and": [ 575 | { 576 | "users": { 577 | "preferences": { 578 | "_contains": { 579 | "ux": { 580 | "theme": "%TEXT" 581 | } 582 | } 583 | } 584 | } 585 | } 586 | ] 587 | }, 588 | "limit": 10, 589 | "offset": 0, 590 | "order_by": { 591 | "id": "asc" 592 | } 593 | } 594 | ``` 595 | 596 | Fetch data matching a jsonb `_contains` operation 597 | 598 | ```jsx 599 | 605 | 606 | ... 607 | 608 | 609 | } /> 610 | ``` 611 | 612 | Will produce payload: 613 | 614 | ```json 615 | { 616 | "where": { 617 | "_and": [ 618 | { 619 | "payments": { 620 | "details": { 621 | "_contains": { 622 | "processor": { 623 | "%{rec.processor}_id": "%{rec.id}" 624 | } 625 | } 626 | } 627 | } 628 | } 629 | ] 630 | } 631 | } 632 | ``` 633 | 634 | ## Sorting lists by multiple columns 635 | 636 | Hasura support [sorting by multiple fields](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/sorting.html#sorting-by-multiple-fields) but React Admin itself doesn't allow the `List` component to receive an array as the `sort` prop. So to achieve sorting by multiple fields, separate the field and order values using a comma. 637 | 638 | For example, a list like 639 | 640 | ```jsx 641 | const TodoList = (props) => ( 642 | 643 | ... 644 | 645 | ); 646 | ``` 647 | 648 | will generate a query with an `order_by` variable like 649 | 650 | ``` 651 | order_by: [{ title: "asc" }, { is_completed: "desc" }] 652 | ``` 653 | 654 | Fields may contain dots to specify sorting by nested object properties similarly to React Admin `source` property. 655 | 656 | ## Contributing 657 | 658 | To modify, extend and test this package locally, 659 | 660 | ``` 661 | $ cd ra-data-hasura 662 | $ npm link 663 | ``` 664 | 665 | Now use this local package in your react app for testing 666 | 667 | ``` 668 | $ cd my-react-app 669 | $ npm link ra-data-hasura 670 | ``` 671 | 672 | Build the library by running `npm run build` and it will generate the transpiled version of the library under `lib` folder. 673 | 674 | ## Credits 675 | 676 | We would like to thank [Steams](https://github.com/Steams) and all the contributors to this library for porting this adapter to support GraphQL spec, since all the releases till v0.0.8 were based off the REST API spec. 677 | -------------------------------------------------------------------------------- /example/hasura/seeds/default/01_B_user_address_seeds.sql: -------------------------------------------------------------------------------- 1 | BEGIN TRANSACTION; 2 | 3 | insert into public.address (user_id, city, state, zipcode, address_line_one) values 4 | (4, 'Springfield', 'Massachusetts', '01129', '119 Victoria Trail'), 5 | (5, 'Indianapolis', 'Indiana', '46216', '06 Holy Cross Lane'), 6 | (6, 'Athens', 'Georgia', '30610', '1287 Clyde Gallagher Terrace'), 7 | (7, 'Orlando', 'Florida', '32813', '02572 Forest Way'), 8 | (8, 'Reading', 'Pennsylvania', '19605', '83625 Dawn Park'), 9 | (9, 'Daytona Beach', 'Florida', '32128', '9202 Carpenter Park'), 10 | (10, 'El Paso', 'Texas', '88579', '509 Grayhawk Pass'), 11 | (11, 'Tacoma', 'Washington', '98411', '5 Troy Hill'), 12 | (12, 'New Castle', 'Pennsylvania', '16107', '7 Bay Center'), 13 | (13, 'El Paso', 'Texas', '79955', '5763 Mendota Point'), 14 | (14, 'Anaheim', 'California', '92825', '7 Mayer Road'), 15 | (15, 'Dallas', 'Texas', '75372', '31319 Maple Wood Terrace'), 16 | (16, 'Anchorage', 'Alaska', '99599', '111 Bashford Pass'), 17 | (17, 'Tulsa', 'Oklahoma', '74184', '77416 Birchwood Junction'), 18 | (18, 'Oklahoma City', 'Oklahoma', '73109', '74037 Comanche Park'), 19 | (19, 'Fullerton', 'California', '92640', '36599 Pennsylvania Circle'), 20 | (20, 'San Jose', 'California', '95113', '1 Hoffman Center'), 21 | (21, 'Albuquerque', 'New Mexico', '87110', '950 Thompson Crossing'), 22 | (22, 'Oakland', 'California', '94611', '2933 Bellgrove Parkway'), 23 | (23, 'San Diego', 'California', '92153', '8962 Katie Lane'), 24 | (24, 'Amarillo', 'Texas', '79105', '2 Gina Circle'), 25 | (25, 'Lansing', 'Michigan', '48912', '60 Jenna Junction'), 26 | (26, 'Sacramento', 'California', '95823', '2196 Corscot Court'), 27 | (27, 'Elizabeth', 'New Jersey', '07208', '1 Portage Parkway'), 28 | (28, 'Norfolk', 'Virginia', '23514', '9 Knutson Hill'), 29 | (29, 'Indianapolis', 'Indiana', '46266', '2394 Talisman Lane'), 30 | (30, 'Tucson', 'Arizona', '85732', '139 Sherman Point'), 31 | (31, 'El Paso', 'Texas', '79989', '7 Grover Avenue'), 32 | (32, 'Los Angeles', 'California', '90005', '1620 Ilene Place'), 33 | (33, 'Huntington', 'West Virginia', '25770', '93 Macpherson Avenue'), 34 | (34, 'Kansas City', 'Kansas', '66160', '02 Green Ridge Road'), 35 | (35, 'Cincinnati', 'Ohio', '45243', '6 Cascade Road'), 36 | (36, 'Fresno', 'California', '93721', '55 New Castle Trail'), 37 | (37, 'Tulsa', 'Oklahoma', '74156', '2483 Upham Pass'), 38 | (38, 'Miami', 'Florida', '33261', '2 Jackson Drive'), 39 | (39, 'Houston', 'Texas', '77075', '89 Service Parkway'), 40 | (40, 'Lexington', 'Kentucky', '40586', '61159 Onsgard Crossing'), 41 | (41, 'Saint Augustine', 'Florida', '32092', '69 Magdeline Parkway'), 42 | (42, 'San Angelo', 'Texas', '76905', '1043 Novick Court'), 43 | (43, 'Norfolk', 'Virginia', '23520', '62190 Jana Trail'), 44 | (44, 'Canton', 'Ohio', '44710', '0 Eggendart Circle'), 45 | (45, 'Helena', 'Montana', '59623', '2 Thackeray Junction'), 46 | (46, 'Montgomery', 'Alabama', '36177', '012 Dixon Lane'), 47 | (47, 'Troy', 'Michigan', '48098', '4 Bashford Trail'), 48 | (48, 'Amarillo', 'Texas', '79105', '4176 Clyde Gallagher Drive'), 49 | (49, 'Stamford', 'Connecticut', '06922', '5975 Armistice Place'), 50 | (50, 'Rochester', 'New York', '14652', '811 Tony Lane'), 51 | (51, 'Houston', 'Texas', '77095', '737 Steensland Avenue'), 52 | (52, 'Alexandria', 'Virginia', '22333', '4777 Lerdahl Plaza'), 53 | (53, 'Knoxville', 'Tennessee', '37914', '01294 Warbler Crossing'), 54 | (54, 'Valdosta', 'Georgia', '31605', '1 Ramsey Parkway'), 55 | (55, 'Wilmington', 'North Carolina', '28410', '48 Main Alley'), 56 | (56, 'Nashville', 'Tennessee', '37228', '076 Buhler Point'), 57 | (57, 'Pittsburgh', 'Pennsylvania', '15279', '5291 Haas Junction'), 58 | (58, 'Birmingham', 'Alabama', '35205', '94012 Bellgrove Crossing'), 59 | (59, 'San Jose', 'California', '95160', '86685 Rieder Circle'), 60 | (60, 'Dayton', 'Ohio', '45440', '527 Hintze Point'), 61 | (61, 'Saint Louis', 'Missouri', '63196', '1 Kensington Parkway'), 62 | (62, 'Boston', 'Massachusetts', '02208', '45321 Thackeray Way'), 63 | (63, 'Denver', 'Colorado', '80279', '54 Sloan Way'), 64 | (64, 'Albuquerque', 'New Mexico', '87190', '1 Thompson Crossing'), 65 | (65, 'Clearwater', 'Florida', '34629', '00356 Sugar Center'), 66 | (66, 'Knoxville', 'Tennessee', '37939', '69 Ryan Point'), 67 | (67, 'Shreveport', 'Louisiana', '71166', '10473 Brown Circle'), 68 | (68, 'Houston', 'Texas', '77260', '44 Warrior Way'), 69 | (69, 'Albuquerque', 'New Mexico', '87195', '55415 Waywood Crossing'), 70 | (70, 'Lansing', 'Michigan', '48930', '9830 Sycamore Parkway'), 71 | (71, 'Waltham', 'Massachusetts', '02453', '24648 Lakewood Crossing'), 72 | (72, 'Lexington', 'Kentucky', '40505', '30550 Macpherson Parkway'), 73 | (73, 'Phoenix', 'Arizona', '85053', '36184 Eggendart Court'), 74 | (74, 'Gastonia', 'North Carolina', '28055', '5839 Dapin Pass'), 75 | (75, 'Baton Rouge', 'Louisiana', '70883', '1646 Merry Way'), 76 | (76, 'Newark', 'Delaware', '19725', '26795 Swallow Lane'), 77 | (77, 'Ogden', 'Utah', '84409', '3329 Jana Crossing'), 78 | (78, 'Raleigh', 'North Carolina', '27610', '01950 Graedel Park'), 79 | (79, 'San Antonio', 'Texas', '78260', '08 Elmside Alley'), 80 | (80, 'New York City', 'New York', '10170', '0 Swallow Court'), 81 | (81, 'San Antonio', 'Texas', '78240', '4991 Menomonie Junction'), 82 | (82, 'Austin', 'Texas', '78721', '854 Waywood Point'), 83 | (83, 'San Angelo', 'Texas', '76905', '7 Lindbergh Way'), 84 | (84, 'Los Angeles', 'California', '90071', '99 Welch Pass'), 85 | (85, 'Richmond', 'Virginia', '23228', '7 Commercial Road'), 86 | (86, 'Oklahoma City', 'Oklahoma', '73157', '1090 Hoepker Court'), 87 | (87, 'Columbia', 'South Carolina', '29208', '894 Hoard Way'), 88 | (88, 'Roanoke', 'Virginia', '24048', '1584 Rowland Plaza'), 89 | (89, 'Lexington', 'Kentucky', '40510', '0983 Elmside Way'), 90 | (90, 'Portland', 'Oregon', '97232', '264 Eggendart Hill'), 91 | (91, 'Houston', 'Texas', '77070', '54 Debs Terrace'), 92 | (92, 'Corpus Christi', 'Texas', '78410', '65 5th Road'), 93 | (93, 'Arlington', 'Virginia', '22244', '7 Arrowood Trail'), 94 | (94, 'Anchorage', 'Alaska', '99599', '2 Pearson Place'), 95 | (95, 'Canton', 'Ohio', '44760', '5 Lawn Court'), 96 | (96, 'Arlington', 'Virginia', '22234', '0607 Rockefeller Point'), 97 | (97, 'Saginaw', 'Michigan', '48604', '68566 Mallard Crossing'), 98 | (98, 'Detroit', 'Michigan', '48242', '7513 Prairieview Trail'), 99 | (99, 'Tampa', 'Florida', '33686', '7928 Golf Lane'), 100 | (100, 'San Francisco', 'California', '94154', '69 Burrows Way'), 101 | (101, 'Saginaw', 'Michigan', '48604', '8 Mesta Parkway'), 102 | (102, 'Akron', 'Ohio', '44305', '0316 Nova Street'), 103 | (103, 'Fresno', 'California', '93715', '8 Onsgard Drive'), 104 | (104, 'Boston', 'Massachusetts', '02298', '7134 Lawn Alley'), 105 | (105, 'Frederick', 'Maryland', '21705', '6471 Muir Lane'), 106 | (106, 'Salt Lake City', 'Utah', '84115', '3384 Carberry Alley'), 107 | (107, 'Washington', 'District of Columbia', '20057', '013 Thompson Place'), 108 | (108, 'Cleveland', 'Ohio', '44191', '17 Bowman Parkway'), 109 | (109, 'Saint Cloud', 'Minnesota', '56372', '1 Del Sol Way'), 110 | (110, 'El Paso', 'Texas', '79994', '1919 Ludington Pass'), 111 | (111, 'Allentown', 'Pennsylvania', '18105', '17647 Shoshone Road'), 112 | (112, 'Bronx', 'New York', '10459', '0 Namekagon Lane'), 113 | (113, 'Corona', 'California', '92878', '11 Monterey Pass'), 114 | (114, 'Pittsburgh', 'Pennsylvania', '15286', '206 Novick Parkway'), 115 | (115, 'Washington', 'District of Columbia', '20099', '73 Anzinger Trail'), 116 | (116, 'Jamaica', 'New York', '11436', '66 Meadow Vale Point'), 117 | (117, 'Washington', 'District of Columbia', '20456', '4675 Menomonie Hill'), 118 | (118, 'Cincinnati', 'Ohio', '45203', '9 La Follette Crossing'), 119 | (119, 'Jacksonville', 'Florida', '32215', '996 South Parkway'), 120 | (120, 'Greenville', 'South Carolina', '29605', '0106 Myrtle Hill'), 121 | (121, 'Shawnee Mission', 'Kansas', '66225', '0083 Gerald Park'), 122 | (122, 'Washington', 'District of Columbia', '20041', '72954 Loftsgordon Trail'), 123 | (123, 'San Antonio', 'Texas', '78260', '45 Elmside Terrace'), 124 | (124, 'Bronx', 'New York', '10454', '8867 Namekagon Hill'), 125 | (125, 'Corona', 'California', '92878', '17 Vidon Avenue'), 126 | (126, 'Moreno Valley', 'California', '92555', '933 Clyde Gallagher Parkway'), 127 | (127, 'Memphis', 'Tennessee', '38114', '3660 Colorado Lane'), 128 | (128, 'Bakersfield', 'California', '93381', '1557 Granby Alley'), 129 | (129, 'Gulfport', 'Mississippi', '39505', '357 Spaight Way'), 130 | (130, 'Evansville', 'Indiana', '47737', '366 Manley Court'), 131 | (131, 'Amarillo', 'Texas', '79159', '565 Michigan Road'), 132 | (132, 'Richmond', 'Virginia', '23220', '5 Hovde Road'), 133 | (133, 'Las Cruces', 'New Mexico', '88006', '680 Graceland Parkway'), 134 | (134, 'Albuquerque', 'New Mexico', '87110', '26 Shelley Avenue'), 135 | (135, 'Henderson', 'Nevada', '89012', '4 Sunnyside Place'), 136 | (136, 'Saint Paul', 'Minnesota', '55123', '01 3rd Way'), 137 | (137, 'Huntsville', 'Texas', '77343', '04 Trailsway Crossing'), 138 | (138, 'Saint Paul', 'Minnesota', '55172', '454 Bayside Crossing'), 139 | (139, 'El Paso', 'Texas', '79940', '91257 Comanche Hill'), 140 | (140, 'Corpus Christi', 'Texas', '78465', '8884 Lukken Crossing'), 141 | (141, 'Kalamazoo', 'Michigan', '49006', '05191 Orin Pass'), 142 | (142, 'Kansas City', 'Missouri', '64125', '68977 Porter Crossing'), 143 | (143, 'Kansas City', 'Missouri', '64114', '5367 Fieldstone Crossing'), 144 | (144, 'Chicago', 'Illinois', '60663', '6997 Loomis Trail'), 145 | (145, 'Tacoma', 'Washington', '98481', '8688 Dryden Crossing'), 146 | (146, 'Fort Lauderdale', 'Florida', '33320', '7 Warrior Place'), 147 | (147, 'San Diego', 'California', '92115', '53367 Darwin Plaza'), 148 | (148, 'Washington', 'District of Columbia', '20414', '1 Fairfield Plaza'), 149 | (149, 'Metairie', 'Louisiana', '70005', '1 Maryland Parkway'), 150 | (150, 'New York City', 'New York', '10155', '53 Johnson Place'), 151 | (151, 'Midland', 'Michigan', '48670', '945 Gateway Point'), 152 | (152, 'Lakeland', 'Florida', '33811', '3554 Pennsylvania Alley'), 153 | (153, 'Fort Lauderdale', 'Florida', '33345', '2458 Forest Place'), 154 | (154, 'Raleigh', 'North Carolina', '27635', '26 Huxley Way'), 155 | (155, 'Washington', 'District of Columbia', '20310', '03 Sommers Lane'), 156 | (156, 'Richmond', 'Virginia', '23277', '24775 Mallard Place'), 157 | (157, 'Littleton', 'Colorado', '80126', '09132 Myrtle Trail'), 158 | (158, 'Los Angeles', 'California', '90071', '625 Warbler Street'), 159 | (159, 'Washington', 'District of Columbia', '20503', '8 Green Plaza'), 160 | (160, 'Palatine', 'Illinois', '60078', '349 5th Street'), 161 | (161, 'Philadelphia', 'Pennsylvania', '19184', '01905 Amoth Circle'), 162 | (162, 'Bradenton', 'Florida', '34205', '5200 John Wall Crossing'), 163 | (163, 'Carson City', 'Nevada', '89714', '2323 Florence Court'), 164 | (164, 'Irving', 'Texas', '75037', '466 Charing Cross Avenue'), 165 | (165, 'Waterbury', 'Connecticut', '06726', '993 Anniversary Junction'), 166 | (166, 'Richmond', 'Virginia', '23272', '90896 Stone Corner Lane'), 167 | (167, 'Atlanta', 'Georgia', '30375', '2 Armistice Lane'), 168 | (168, 'Mesquite', 'Texas', '75185', '0 Weeping Birch Court'), 169 | (169, 'Los Angeles', 'California', '90060', '916 Butterfield Terrace'), 170 | (170, 'Seattle', 'Washington', '98104', '82201 Clarendon Avenue'), 171 | (171, 'Detroit', 'Michigan', '48275', '62567 Walton Circle'), 172 | (172, 'Arlington', 'Texas', '76004', '897 Anniversary Place'), 173 | (173, 'Austin', 'Texas', '78737', '309 Fisk Road'), 174 | (174, 'Austin', 'Texas', '78744', '1 Burning Wood Circle'), 175 | (175, 'Miami', 'Florida', '33233', '30120 Truax Point'), 176 | (176, 'Sacramento', 'California', '94263', '47485 Sauthoff Junction'), 177 | (177, 'Denver', 'Colorado', '80209', '704 Morrow Park'), 178 | (178, 'Cumming', 'Georgia', '30130', '61 Homewood Plaza'), 179 | (179, 'Watertown', 'Massachusetts', '02472', '443 Bonner Park'), 180 | (180, 'Houston', 'Texas', '77035', '454 Sycamore Drive'), 181 | (181, 'Monticello', 'Minnesota', '55590', '3 Carpenter Crossing'), 182 | (182, 'Colorado Springs', 'Colorado', '80945', '8 Red Cloud Lane'), 183 | (183, 'Los Angeles', 'California', '90101', '750 Loftsgordon Crossing'), 184 | (184, 'Escondido', 'California', '92030', '93634 Farwell Center'), 185 | (185, 'Racine', 'Wisconsin', '53405', '45 Golf View Point'), 186 | (186, 'Oakland', 'California', '94616', '29 Northview Avenue'), 187 | (187, 'Washington', 'District of Columbia', '20205', '2 Sommers Place'), 188 | (188, 'Las Vegas', 'Nevada', '89120', '31088 Warbler Terrace'), 189 | (189, 'Kansas City', 'Missouri', '64149', '413 Nevada Street'), 190 | (190, 'Fort Worth', 'Texas', '76115', '20 Colorado Place'), 191 | (191, 'Fort Worth', 'Texas', '76178', '35 Duke Crossing'), 192 | (192, 'Southfield', 'Michigan', '48076', '694 Monica Alley'), 193 | (193, 'Tampa', 'Florida', '33686', '5 Katie Park'), 194 | (194, 'Santa Fe', 'New Mexico', '87505', '3 Porter Lane'), 195 | (195, 'Saint Petersburg', 'Florida', '33705', '4 Autumn Leaf Court'), 196 | (196, 'Fort Smith', 'Arkansas', '72916', '6489 Loomis Center'), 197 | (197, 'Galveston', 'Texas', '77554', '80 Hanson Alley'), 198 | (198, 'Oklahoma City', 'Oklahoma', '73135', '9 Mayer Junction'), 199 | (199, 'San Antonio', 'Texas', '78220', '7367 Merry Junction'), 200 | (200, 'Elmira', 'New York', '14905', '066 Anzinger Point'), 201 | (201, 'West Hartford', 'Connecticut', '06127', '8386 Dawn Parkway'), 202 | (202, 'Davenport', 'Iowa', '52804', '8 Melrose Parkway'), 203 | (203, 'Washington', 'District of Columbia', '20530', '336 Rockefeller Park'), 204 | (204, 'Garland', 'Texas', '75044', '52 Talmadge Center'), 205 | (205, 'Springfield', 'Illinois', '62756', '9785 Birchwood Crossing'), 206 | (206, 'Tucson', 'Arizona', '85754', '61256 Meadow Valley Junction'), 207 | (207, 'San Jose', 'California', '95113', '1 Magdeline Park'), 208 | (208, 'El Paso', 'Texas', '79950', '8 Gateway Court'), 209 | (209, 'Dallas', 'Texas', '75387', '125 Muir Parkway'), 210 | (210, 'Clearwater', 'Florida', '34620', '1 Thompson Junction'), 211 | (211, 'Staten Island', 'New York', '10310', '44953 Clemons Way'), 212 | (212, 'Simi Valley', 'California', '93094', '941 Sage Trail'), 213 | (213, 'Atlanta', 'Georgia', '30358', '831 Ohio Way'), 214 | (214, 'Washington', 'District of Columbia', '20310', '49614 Sherman Court'), 215 | (215, 'Jackson', 'Mississippi', '39204', '22747 Fremont Street'), 216 | (216, 'Birmingham', 'Alabama', '35210', '871 Russell Point'), 217 | (217, 'Irving', 'Texas', '75062', '6 Melvin Way'), 218 | (218, 'Charleston', 'West Virginia', '25313', '6836 Clyde Gallagher Plaza'), 219 | (219, 'Metairie', 'Louisiana', '70033', '6 Eagle Crest Center'), 220 | (220, 'Fort Lauderdale', 'Florida', '33345', '07240 Commercial Center'), 221 | (221, 'Lexington', 'Kentucky', '40586', '4 Mcguire Center'), 222 | (222, 'Portland', 'Oregon', '97216', '8 Amoth Place'), 223 | (223, 'Bridgeport', 'Connecticut', '06606', '44 Kedzie Terrace'), 224 | (224, 'New York City', 'New York', '10079', '16 Novick Alley'), 225 | (225, 'Orlando', 'Florida', '32819', '920 Alpine Point'), 226 | (226, 'Bozeman', 'Montana', '59771', '1470 Vermont Court'), 227 | (227, 'New Orleans', 'Louisiana', '70124', '97640 Ruskin Crossing'), 228 | (228, 'Boston', 'Massachusetts', '02283', '5092 Union Point'), 229 | (229, 'Colorado Springs', 'Colorado', '80940', '563 Dunning Trail'), 230 | (230, 'Fort Wayne', 'Indiana', '46852', '82 Elka Plaza'), 231 | (231, 'Baltimore', 'Maryland', '21290', '8018 Ludington Way'), 232 | (232, 'Birmingham', 'Alabama', '35236', '8 Grim Junction'), 233 | (233, 'New York City', 'New York', '10105', '1856 Anderson Way'), 234 | (234, 'Seattle', 'Washington', '98158', '89 Merry Circle'), 235 | (235, 'Chicago', 'Illinois', '60636', '5539 Starling Junction'), 236 | (236, 'Peoria', 'Illinois', '61656', '4373 Arapahoe Way'), 237 | (237, 'Visalia', 'California', '93291', '295 Stephen Trail'), 238 | (238, 'Pensacola', 'Florida', '32575', '15 Quincy Trail'), 239 | (239, 'El Paso', 'Texas', '79989', '83 Southridge Court'), 240 | (240, 'Detroit', 'Michigan', '48211', '729 Lakewood Gardens Road'), 241 | (241, 'El Paso', 'Texas', '79968', '97598 Continental Parkway'), 242 | (242, 'Silver Spring', 'Maryland', '20910', '66055 4th Point'), 243 | (243, 'Boston', 'Massachusetts', '02109', '7 Lunder Circle'), 244 | (244, 'Fort Worth', 'Texas', '76162', '14 Bunker Hill Way'), 245 | (245, 'Atlanta', 'Georgia', '30323', '06215 Hoepker Alley'), 246 | (246, 'Shreveport', 'Louisiana', '71166', '660 Logan Crossing'), 247 | (247, 'Birmingham', 'Alabama', '35220', '66 Browning Road'), 248 | (248, 'Springfield', 'Missouri', '65898', '2 Marquette Circle'), 249 | (249, 'El Paso', 'Texas', '88519', '2 Jay Circle'), 250 | (250, 'Wilmington', 'North Carolina', '28410', '5263 Hoepker Lane'), 251 | (251, 'Pensacola', 'Florida', '32505', '44544 Browning Drive'), 252 | (252, 'Escondido', 'California', '92030', '86933 Loomis Junction'), 253 | (253, 'Nashville', 'Tennessee', '37235', '8539 Caliangt Crossing'), 254 | (254, 'El Paso', 'Texas', '79955', '8 Luster Trail'), 255 | (255, 'Jamaica', 'New York', '11431', '627 Stephen Hill'), 256 | (256, 'New Orleans', 'Louisiana', '70142', '7 Hauk Lane'), 257 | (257, 'Bronx', 'New York', '10454', '4 Ramsey Place'), 258 | (258, 'Lincoln', 'Nebraska', '68505', '6 Morrow Hill'), 259 | (259, 'Tucson', 'Arizona', '85710', '6790 Shopko Pass'), 260 | (260, 'Washington', 'District of Columbia', '20067', '5879 Bartillon Park'), 261 | (261, 'Charleston', 'West Virginia', '25331', '8897 Blaine Crossing'), 262 | (262, 'Fresno', 'California', '93715', '04 6th Trail'), 263 | (263, 'Elmira', 'New York', '14905', '383 Roth Crossing'), 264 | (264, 'Saint Louis', 'Missouri', '63167', '12642 David Parkway'), 265 | (265, 'Las Vegas', 'Nevada', '89110', '9371 Reinke Center'), 266 | (266, 'Madison', 'Wisconsin', '53710', '801 Northview Circle'), 267 | (267, 'Los Angeles', 'California', '90025', '1 Macpherson Hill'), 268 | (268, 'Houston', 'Texas', '77281', '48 Prentice Road'), 269 | (269, 'Columbia', 'South Carolina', '29208', '789 Goodland Pass'), 270 | (270, 'Santa Barbara', 'California', '93111', '062 Lerdahl Way'), 271 | (271, 'Greensboro', 'North Carolina', '27499', '61 Beilfuss Terrace'), 272 | (272, 'Fullerton', 'California', '92835', '3 Forest Run Lane'), 273 | (273, 'Albany', 'New York', '12262', '317 Forest Run Crossing'), 274 | (274, 'Anchorage', 'Alaska', '99522', '5 Esch Street'), 275 | (275, 'Salt Lake City', 'Utah', '84199', '59 Glacier Hill Circle'), 276 | (276, 'Jackson', 'Mississippi', '39282', '2 Golf View Avenue'), 277 | (277, 'Austin', 'Texas', '78710', '16780 Rockefeller Point'), 278 | (278, 'Gainesville', 'Florida', '32605', '16 Homewood Pass'), 279 | (279, 'Washington', 'District of Columbia', '20425', '81271 Karstens Parkway'), 280 | (280, 'Saint Louis', 'Missouri', '63110', '99256 Bobwhite Parkway'), 281 | (281, 'Boston', 'Massachusetts', '02216', '6 Fairfield Place'), 282 | (282, 'Minneapolis', 'Minnesota', '55446', '2156 Truax Court'), 283 | (283, 'Indianapolis', 'Indiana', '46231', '6034 Thompson Avenue'), 284 | (284, 'Tucson', 'Arizona', '85710', '3 Sachs Terrace'), 285 | (285, 'Providence', 'Rhode Island', '02905', '47009 Maryland Court'), 286 | (286, 'Omaha', 'Nebraska', '68134', '1 Nova Alley'), 287 | (287, 'Visalia', 'California', '93291', '706 Moulton Drive'), 288 | (288, 'Danbury', 'Connecticut', '06816', '0806 Park Meadow Place'), 289 | (289, 'Minneapolis', 'Minnesota', '55428', '48 Kennedy Hill'), 290 | (290, 'Lexington', 'Kentucky', '40515', '431 Browning Hill'), 291 | (291, 'Lincoln', 'Nebraska', '68531', '898 Longview Hill'), 292 | (292, 'Mobile', 'Alabama', '36616', '800 Hazelcrest Center'), 293 | (293, 'Chicago', 'Illinois', '60609', '0 Crownhardt Road'), 294 | (294, 'New York City', 'New York', '10105', '0 Rieder Junction'), 295 | (295, 'Orlando', 'Florida', '32835', '1 Mallard Way'), 296 | (296, 'San Francisco', 'California', '94147', '251 Acker Circle'), 297 | (297, 'Erie', 'Pennsylvania', '16565', '985 Dryden Crossing'), 298 | (298, 'Cleveland', 'Ohio', '44125', '74 Fuller Parkway'), 299 | (299, 'Oakland', 'California', '94605', '450 Shelley Place'), 300 | (300, 'Phoenix', 'Arizona', '85015', '89 Barnett Crossing'), 301 | (301, 'Macon', 'Georgia', '31296', '0 Bayside Point'), 302 | (302, 'Pinellas Park', 'Florida', '34665', '53 Duke Avenue'), 303 | (303, 'Jacksonville', 'Florida', '32204', '9666 Riverside Hill'), 304 | (304, 'Fredericksburg', 'Virginia', '22405', '301 Cody Center'), 305 | (305, 'El Paso', 'Texas', '79940', '2218 Washington Parkway'), 306 | (306, 'New York City', 'New York', '10150', '974 Aberg Pass'), 307 | (307, 'San Jose', 'California', '95160', '08109 Mayer Alley'), 308 | (308, 'Arlington', 'Virginia', '22212', '0985 Clarendon Trail'), 309 | (309, 'Baton Rouge', 'Louisiana', '70836', '8 Buell Street'), 310 | (310, 'South Bend', 'Indiana', '46614', '965 Sullivan Avenue'), 311 | (311, 'Austin', 'Texas', '78769', '48226 Straubel Junction'), 312 | (312, 'Kansas City', 'Missouri', '64190', '71300 Oak Valley Point'), 313 | (313, 'Greensboro', 'North Carolina', '27404', '19687 Maywood Drive'), 314 | (314, 'Jacksonville', 'Florida', '32204', '9 Rusk Parkway'), 315 | (315, 'Evansville', 'Indiana', '47719', '29 Donald Street'), 316 | (316, 'Lincoln', 'Nebraska', '68510', '88 Talisman Crossing'), 317 | (317, 'Los Angeles', 'California', '90035', '1036 Chive Street'), 318 | (318, 'Temple', 'Texas', '76505', '8730 Aberg Terrace'), 319 | (319, 'Waterbury', 'Connecticut', '06726', '3 2nd Trail'), 320 | (320, 'Atlanta', 'Georgia', '30392', '5 Namekagon Plaza'), 321 | (321, 'Terre Haute', 'Indiana', '47812', '31 Ilene Parkway'), 322 | (322, 'Pensacola', 'Florida', '32505', '74910 Bowman Avenue'), 323 | (323, 'Los Angeles', 'California', '90087', '527 Beilfuss Terrace'), 324 | (324, 'San Francisco', 'California', '94121', '19 Debs Terrace'), 325 | (325, 'Saint Louis', 'Missouri', '63116', '2 Mallard Court'), 326 | (326, 'Aurora', 'Colorado', '80015', '80010 Homewood Street'), 327 | (327, 'Seattle', 'Washington', '98140', '30 Troy Center'), 328 | (328, 'Kansas City', 'Kansas', '66160', '725 Brown Way'), 329 | (329, 'San Diego', 'California', '92153', '32 Green Ridge Hill'), 330 | (330, 'Springfield', 'Illinois', '62711', '53161 Tony Alley'), 331 | (331, 'Colorado Springs', 'Colorado', '80925', '14636 Forster Lane'), 332 | (332, 'Atlanta', 'Georgia', '30386', '90746 Lindbergh Park'), 333 | (333, 'Riverside', 'California', '92519', '58 Prairieview Pass'), 334 | (334, 'Des Moines', 'Iowa', '50981', '24 Sugar Way'), 335 | (335, 'Durham', 'North Carolina', '27717', '670 American Ash Street'), 336 | (336, 'Topeka', 'Kansas', '66617', '4407 Hollow Ridge Court'), 337 | (337, 'Cincinnati', 'Ohio', '45238', '60 Fisk Circle'), 338 | (338, 'Evansville', 'Indiana', '47747', '10893 Cascade Center'), 339 | (339, 'Los Angeles', 'California', '90076', '013 Gerald Crossing'), 340 | (340, 'Houston', 'Texas', '77288', '131 Pennsylvania Plaza'), 341 | (341, 'Jamaica', 'New York', '11407', '084 Kensington Circle'), 342 | (342, 'Seattle', 'Washington', '98121', '6117 Center Drive'), 343 | (343, 'West Hartford', 'Connecticut', '06127', '4 Red Cloud Alley'), 344 | (344, 'Sioux Falls', 'South Dakota', '57105', '5 Mcbride Crossing'), 345 | (345, 'Buffalo', 'New York', '14210', '148 Mayer Crossing'), 346 | (346, 'Springfield', 'Missouri', '65805', '98 Westend Drive'), 347 | (347, 'New York City', 'New York', '10270', '9 Spohn Park'), 348 | (348, 'Arlington', 'Virginia', '22244', '3786 Farragut Circle'), 349 | (349, 'New Orleans', 'Louisiana', '70174', '8674 Kinsman Park'), 350 | (350, 'Buffalo', 'New York', '14210', '951 Washington Way'), 351 | (351, 'Baton Rouge', 'Louisiana', '70820', '07 Manufacturers Court'), 352 | (352, 'Nashville', 'Tennessee', '37210', '518 Butternut Point'), 353 | (353, 'Seattle', 'Washington', '98121', '47 Graedel Street'), 354 | (354, 'Nashville', 'Tennessee', '37235', '6 Dunning Avenue'), 355 | (355, 'Orlando', 'Florida', '32813', '310 Sutherland Drive'), 356 | (356, 'Cleveland', 'Ohio', '44105', '5 Badeau Parkway'), 357 | (357, 'Monroe', 'Louisiana', '71208', '2833 Manley Trail'), 358 | (358, 'Des Moines', 'Iowa', '50315', '132 Dovetail Plaza'), 359 | (359, 'Bethesda', 'Maryland', '20816', '10859 Mccormick Drive'), 360 | (360, 'New Haven', 'Connecticut', '06520', '593 Schurz Place'), 361 | (361, 'Terre Haute', 'Indiana', '47805', '459 Quincy Alley'), 362 | (362, 'Lakeland', 'Florida', '33811', '52 Goodland Way'), 363 | (363, 'Roanoke', 'Virginia', '24014', '518 Armistice Center'), 364 | (364, 'Tampa', 'Florida', '33673', '3722 Larry Plaza'), 365 | (365, 'Fresno', 'California', '93762', '36096 Shopko Road'), 366 | (366, 'Winston Salem', 'North Carolina', '27110', '98 Spenser Pass'), 367 | (367, 'Chicago', 'Illinois', '60657', '29 Lawn Hill'), 368 | (368, 'Kansas City', 'Missouri', '64199', '72339 Boyd Center'), 369 | (369, 'Canton', 'Ohio', '44720', '9 Butternut Drive'), 370 | (370, 'Lawrenceville', 'Georgia', '30245', '350 Commercial Trail'), 371 | (371, 'Humble', 'Texas', '77346', '64 Rutledge Point'), 372 | (372, 'Newton', 'Massachusetts', '02162', '44549 1st Street'), 373 | (373, 'El Paso', 'Texas', '88530', '28836 Northwestern Park'), 374 | (374, 'Inglewood', 'California', '90305', '0 Melby Park'), 375 | (375, 'El Paso', 'Texas', '88574', '161 Schurz Place'), 376 | (376, 'Salem', 'Oregon', '97312', '4370 Dixon Circle'), 377 | (377, 'Philadelphia', 'Pennsylvania', '19104', '5652 Katie Avenue'), 378 | (378, 'Johnson City', 'Tennessee', '37605', '01234 Goodland Trail'), 379 | (379, 'Iowa City', 'Iowa', '52245', '75 Nevada Pass'), 380 | (380, 'San Diego', 'California', '92145', '6662 Daystar Drive'), 381 | (381, 'Saint Paul', 'Minnesota', '55166', '423 Almo Center'), 382 | (382, 'Anchorage', 'Alaska', '99512', '5 Stang Street'), 383 | (383, 'Seattle', 'Washington', '98140', '4200 Shoshone Terrace'), 384 | (384, 'Austin', 'Texas', '78778', '3030 Jackson Park'), 385 | (385, 'Salt Lake City', 'Utah', '84170', '4 Vermont Alley'), 386 | (386, 'Lynchburg', 'Virginia', '24515', '671 Warbler Avenue'), 387 | (387, 'Reading', 'Pennsylvania', '19610', '8 Fairview Place'), 388 | (388, 'Omaha', 'Nebraska', '68117', '5361 Dottie Way'), 389 | (389, 'Washington', 'District of Columbia', '20057', '569 Vera Court'), 390 | (390, 'Houston', 'Texas', '77015', '12745 Veith Lane'), 391 | (391, 'Minneapolis', 'Minnesota', '55487', '08545 Kim Pass'), 392 | (392, 'Philadelphia', 'Pennsylvania', '19120', '579 Carioca Center'), 393 | (393, 'Longview', 'Texas', '75605', '4 Schlimgen Crossing'), 394 | (394, 'Las Vegas', 'Nevada', '89193', '963 Lunder Pass'), 395 | (395, 'Philadelphia', 'Pennsylvania', '19146', '56808 Bellgrove Parkway'), 396 | (396, 'Corpus Christi', 'Texas', '78475', '20111 Green Place'), 397 | (397, 'Worcester', 'Massachusetts', '01654', '61 Ridgeview Crossing'), 398 | (398, 'Tallahassee', 'Florida', '32309', '7 Scoville Terrace'), 399 | (399, 'Bonita Springs', 'Florida', '34135', '8294 Nova Avenue'), 400 | (400, 'Charlotte', 'North Carolina', '28225', '409 Anthes Court'), 401 | (401, 'Colorado Springs', 'Colorado', '80925', '1 Vernon Plaza'), 402 | (402, 'Chicago', 'Illinois', '60669', '38741 Iowa Alley'), 403 | (403, 'Colorado Springs', 'Colorado', '80910', '9 Kensington Place'), 404 | (404, 'York', 'Pennsylvania', '17405', '2 Meadow Vale Junction'), 405 | (405, 'Philadelphia', 'Pennsylvania', '19120', '88364 Transport Center'), 406 | (406, 'Port Washington', 'New York', '11054', '84 Jackson Road'), 407 | (407, 'Houston', 'Texas', '77065', '35541 Chive Court'), 408 | (408, 'Port Charlotte', 'Florida', '33954', '4377 Union Drive'), 409 | (409, 'Terre Haute', 'Indiana', '47812', '281 Redwing Lane'), 410 | (410, 'Cape Coral', 'Florida', '33915', '2439 Doe Crossing Parkway'), 411 | (411, 'Phoenix', 'Arizona', '85083', '5 Oak Valley Avenue'), 412 | (412, 'Harrisburg', 'Pennsylvania', '17105', '566 Eagle Crest Point'), 413 | (413, 'Topeka', 'Kansas', '66642', '002 Harbort Street'), 414 | (414, 'Harrisburg', 'Pennsylvania', '17105', '60129 Bultman Pass'), 415 | (415, 'Norfolk', 'Virginia', '23504', '73880 Kings Parkway'), 416 | (416, 'North Little Rock', 'Arkansas', '72118', '654 Prairieview Lane'), 417 | (417, 'Washington', 'District of Columbia', '20456', '78871 Hallows Court'), 418 | (418, 'San Jose', 'California', '95150', '00 Lillian Trail'), 419 | (419, 'Savannah', 'Georgia', '31410', '69881 Doe Crossing Place'), 420 | (420, 'Dearborn', 'Michigan', '48126', '8162 Sutherland Place'), 421 | (421, 'Akron', 'Ohio', '44315', '1 Summerview Trail'), 422 | (422, 'Sacramento', 'California', '94297', '85 Namekagon Road'), 423 | (423, 'Omaha', 'Nebraska', '68105', '1 Hagan Hill'), 424 | (424, 'Des Moines', 'Iowa', '50315', '41077 Muir Way'), 425 | (425, 'New York City', 'New York', '10275', '374 Warrior Crossing'), 426 | (426, 'Delray Beach', 'Florida', '33448', '5 Lotheville Center'), 427 | (427, 'Columbia', 'South Carolina', '29220', '27 Dottie Drive'), 428 | (428, 'Phoenix', 'Arizona', '85072', '877 Rigney Plaza'), 429 | (429, 'Wilmington', 'Delaware', '19805', '2 Crescent Oaks Avenue'), 430 | (430, 'Garland', 'Texas', '75044', '4434 Vernon Alley'), 431 | (431, 'Longview', 'Texas', '75605', '07509 Lunder Park'), 432 | (432, 'Richmond', 'Virginia', '23260', '35 Pennsylvania Court'), 433 | (433, 'El Paso', 'Texas', '79934', '31 Dawn Pass'), 434 | (434, 'Washington', 'District of Columbia', '20599', '334 Rowland Center'), 435 | (435, 'San Jose', 'California', '95160', '4133 Brown Hill'), 436 | (436, 'Philadelphia', 'Pennsylvania', '19172', '862 Shelley Junction'), 437 | (437, 'Plano', 'Texas', '75074', '858 Marquette Avenue'), 438 | (438, 'Greensboro', 'North Carolina', '27455', '1 Buell Trail'), 439 | (439, 'Peoria', 'Illinois', '61614', '06 Maryland Center'), 440 | (440, 'Richmond', 'Virginia', '23208', '135 Sunnyside Street'), 441 | (441, 'San Francisco', 'California', '94132', '44443 Texas Park'), 442 | (442, 'Houston', 'Texas', '77218', '5547 Jenifer Hill'), 443 | (443, 'Athens', 'Georgia', '30605', '057 Farmco Alley'), 444 | (444, 'Washington', 'District of Columbia', '20580', '39838 Londonderry Trail'), 445 | (445, 'New York City', 'New York', '10060', '767 Westerfield Street'), 446 | (446, 'Santa Fe', 'New Mexico', '87592', '97565 Nelson Court'), 447 | (447, 'Brockton', 'Massachusetts', '02405', '418 Bonner Park'), 448 | (448, 'Newark', 'New Jersey', '07112', '7868 Muir Hill'), 449 | (449, 'Columbia', 'Missouri', '65211', '61 Karstens Place'), 450 | (450, 'Washington', 'District of Columbia', '20299', '39431 Sloan Park'), 451 | (451, 'Orange', 'California', '92668', '17 Laurel Trail'), 452 | (452, 'Denver', 'Colorado', '80243', '15 Columbus Center'), 453 | (453, 'Seattle', 'Washington', '98166', '097 Brentwood Terrace'), 454 | (454, 'Saginaw', 'Michigan', '48604', '48315 Graedel Center'), 455 | (455, 'Houston', 'Texas', '77040', '273 Surrey Road'), 456 | (456, 'Whittier', 'California', '90605', '0698 Jenna Trail'), 457 | (457, 'Fairbanks', 'Alaska', '99790', '91 Anderson Pass'), 458 | (458, 'Springfield', 'Missouri', '65810', '20 Namekagon Road'), 459 | (459, 'Grand Forks', 'North Dakota', '58207', '3563 Karstens Park'), 460 | (460, 'Baltimore', 'Maryland', '21281', '71 Bartelt Way'), 461 | (461, 'Chicago', 'Illinois', '60663', '40 Mesta Plaza'), 462 | (462, 'Lincoln', 'Nebraska', '68517', '9 Hagan Point'), 463 | (463, 'Jacksonville', 'Florida', '32204', '8335 Novick Crossing'), 464 | (464, 'Durham', 'North Carolina', '27710', '09344 Stephen Pass'), 465 | (465, 'Orlando', 'Florida', '32868', '4 Susan Street'), 466 | (466, 'Houston', 'Texas', '77212', '173 Pearson Parkway'), 467 | (467, 'Midland', 'Texas', '79710', '7105 Lakewood Circle'), 468 | (468, 'Cleveland', 'Ohio', '44130', '82 High Crossing Center'), 469 | (469, 'Salt Lake City', 'Utah', '84120', '5973 1st Junction'), 470 | (470, 'Santa Clara', 'California', '95054', '6470 Sundown Crossing'), 471 | (471, 'Roanoke', 'Virginia', '24014', '87 Hoard Center'), 472 | (472, 'Rochester', 'Minnesota', '55905', '02920 Ronald Regan Place'), 473 | (473, 'Erie', 'Pennsylvania', '16565', '93 Cody Point'), 474 | (474, 'Tampa', 'Florida', '33686', '673 Kipling Street'), 475 | (475, 'Lancaster', 'Pennsylvania', '17622', '38290 Forest Run Lane'), 476 | (476, 'Saint Louis', 'Missouri', '63121', '6 Fallview Avenue'), 477 | (477, 'San Francisco', 'California', '94105', '2 Lighthouse Bay Point'), 478 | (478, 'Saint Louis', 'Missouri', '63158', '4 Waywood Court'), 479 | (479, 'San Diego', 'California', '92196', '5 Hooker Terrace'), 480 | (480, 'Jefferson City', 'Missouri', '65105', '52337 Nobel Plaza'), 481 | (481, 'Carson City', 'Nevada', '89706', '08232 Fairfield Circle'), 482 | (482, 'El Paso', 'Texas', '79994', '3602 Arapahoe Street'), 483 | (483, 'Norman', 'Oklahoma', '73071', '7303 Bonner Parkway'), 484 | (484, 'Seattle', 'Washington', '98166', '47914 Schurz Hill'), 485 | (485, 'Miami', 'Florida', '33147', '4864 Waxwing Alley'), 486 | (486, 'Pasadena', 'California', '91186', '7637 Northland Park'), 487 | (487, 'Salem', 'Oregon', '97312', '9637 Brown Circle'), 488 | (488, 'Columbia', 'Missouri', '65211', '3646 Bluejay Lane'), 489 | (489, 'Dallas', 'Texas', '75323', '52 Warrior Park'), 490 | (490, 'Buffalo', 'New York', '14276', '265 John Wall Court'), 491 | (491, 'San Jose', 'California', '95138', '6184 Russell Crossing'), 492 | (492, 'Laredo', 'Texas', '78044', '6900 Main Place'), 493 | (493, 'New York City', 'New York', '10270', '361 Springs Pass'), 494 | (494, 'Prescott', 'Arizona', '86305', '829 Schlimgen Junction'), 495 | (495, 'Sandy', 'Utah', '84093', '09898 Dexter Place'), 496 | (496, 'Washington', 'District of Columbia', '20005', '19346 Mallard Hill'), 497 | (497, 'Baltimore', 'Maryland', '21281', '5 Starling Drive'), 498 | (498, 'Colorado Springs', 'Colorado', '80940', '49563 Sutherland Junction'), 499 | (499, 'Midland', 'Texas', '79710', '487 Monument Place'), 500 | (500, 'Sacramento', 'California', '95828', '0 Coolidge Trail'); 501 | 502 | COMMIT; 503 | 504 | SELECT setval(pg_get_serial_sequence('address', 'id'), 505 | (select max(id) from "address")); -------------------------------------------------------------------------------- /example/hasura/seeds/default/01_A_user_seeds.sql: -------------------------------------------------------------------------------- 1 | BEGIN TRANSACTION; 2 | 3 | insert into public.user (id, name, email, password, created_at) values 4 | (4, 'Dickie', 'dmayell2@yellowbook.com', 'mFZNmZC897P', '2020-06-16T06:36:45Z'), 5 | (5, 'Korella', 'kfriedlos3@comsenz.com', 'jnnCnLRMW2lw', '2020-04-01T23:03:28Z'), 6 | (6, 'Lazare', 'lstclair4@scientificamerican.com', '8ZXPtb', '2020-11-16T00:06:37Z'), 7 | (7, 'Miof mela', 'mpedican5@ycombinator.com', 'DCnhOovj7P2', '2020-06-09T10:51:52Z'), 8 | (8, 'Bent', 'bziemens6@drupal.org', 'JnaSi4FX3X', '2020-02-17T10:16:13Z'), 9 | (9, 'Shem', 'sroubay7@cyberchimps.com', '8RHC6q', '2020-06-08T00:15:14Z'), 10 | (10, 'Elbertine', 'eivanikhin8@google.fr', '9cLjaF3', '2020-06-20T18:47:28Z'), 11 | (11, 'Rogers', 'rdyde9@woothemes.com', 'oheWkZfkN', '2020-06-13T08:32:12Z'), 12 | (12, 'Nona', 'narpina@infoseek.co.jp', '6vbXFbRWHO', '2020-03-21T14:20:17Z'), 13 | (13, 'Cristin', 'ctilzeyb@soup.io', 'lt87BrX', '2020-10-23T14:01:55Z'), 14 | (14, 'Sharia', 'snorthcotec@zdnet.com', 'qrDtLLwWP', '2020-04-29T11:37:45Z'), 15 | (15, 'Hannie', 'hsoulsbyd@omniture.com', 'akjo2H', '2021-01-17T04:40:51Z'), 16 | (16, 'Emmalee', 'eblasgene@cdc.gov', 'WqgsMK6QsbUD', '2020-05-01T15:52:36Z'), 17 | (17, 'Leta', 'lhacksbyf@marriott.com', 'dhtcEC', '2020-10-08T12:04:36Z'), 18 | (18, 'Barris', 'biacovuzzig@yellowbook.com', 'KfCaphSVrQV', '2021-01-23T01:46:48Z'), 19 | (19, 'Culley', 'cgrollh@phpbb.com', 'yT7mhDIo', '2020-09-04T00:43:38Z'), 20 | (20, 'Skylar', 'stollmachei@1688.com', 'SG1VcDsb', '2020-02-18T05:00:10Z'), 21 | (21, 'Elenore', 'ebillisonj@dedecms.com', 'zyKPF544F', '2020-11-19T01:36:21Z'), 22 | (22, 'Elias', 'ewaistallk@nsw.gov.au', 'tdqHGPzFg', '2020-03-21T01:57:40Z'), 23 | (23, 'Caroline', 'cfollinl@europa.eu', 'HakQZQnHvid', '2020-09-26T03:51:12Z'), 24 | (24, 'Tadio', 'tblenkinsoppm@geocities.com', 'ldDkpJ', '2020-07-03T01:44:46Z'), 25 | (25, 'Cornall', 'chaesliern@ebay.co.uk', '8Zo2H3QKxZu', '2020-02-20T22:37:17Z'), 26 | (26, 'Andeee', 'agonzalezo@live.com', 'rXSHuA8SRb', '2021-01-03T17:47:21Z'), 27 | (27, 'Marilee', 'mpenwardenp@adobe.com', 'p2PYO1ULyhL', '2020-07-30T15:49:37Z'), 28 | (28, 'Lusa', 'lleckeyq@imageshack.us', 'CdbJZRB1uVm', '2020-07-30T14:56:27Z'), 29 | (29, 'Waylan', 'wdaleyr@gnu.org', 'u3d7C7fuXj', '2020-08-06T14:49:14Z'), 30 | (30, 'Addie', 'apowtons@cnn.com', 'd2TYM5EwO', '2021-02-13T12:26:30Z'), 31 | (31, 'Kirsti', 'kstroddert@google.it', 'br7pvJwry1Q', '2020-12-03T18:18:50Z'), 32 | (32, 'Carmon', 'cpoelu@mozilla.com', 'dayRoet5', '2020-12-02T13:12:59Z'), 33 | (33, 'Moll', 'mbeetv@joomla.org', 'V2LD7Qox193c', '2020-09-19T00:33:16Z'), 34 | (34, 'Bar', 'bmiskinw@fotki.com', '15qY3zzwbKWz', '2020-10-22T15:00:23Z'), 35 | (35, 'Tades', 'tsperrettx@geocities.jp', 'Hj03ho2GL0', '2020-10-15T02:49:34Z'), 36 | (36, 'Iggy', 'iadeyy@trellian.com', 'CRgtVk', '2020-11-04T03:36:30Z'), 37 | (37, 'Veradis', 'vjestyz@uiuc.edu', 'WNLV0aHZP', '2020-08-05T04:35:38Z'), 38 | (38, 'Hanan', 'hollive10@eventbrite.com', 'OPIbiWh', '2020-02-21T08:36:00Z'), 39 | (39, 'Briggs', 'bbolderoe11@geocities.jp', 'wKFVYDeJ', '2020-09-04T20:19:54Z'), 40 | (40, 'Marta', 'mcawston12@opensource.org', 'VmFHPnXst6Tu', '2020-02-17T02:12:35Z'), 41 | (41, 'Veriee', 'vhatchette13@japanpost.jp', 'sZ2acdib', '2021-01-10T16:08:02Z'), 42 | (42, 'Adlai', 'aryland14@gravatar.com', 'rWfr42Zmh4md', '2020-10-12T13:39:45Z'), 43 | (43, 'Riva', 'rroach15@yolasite.com', 'OVqXMQFe9Q', '2020-12-09T04:48:14Z'), 44 | (44, 'Suellen', 'swarricker16@hatena.ne.jp', 'z0rGEKREAIM', '2020-02-21T16:27:12Z'), 45 | (45, 'Raynor', 'rspriggin17@paypal.com', 'tKRnH08', '2021-01-24T18:40:08Z'), 46 | (46, 'Romain', 'rwestover18@hostgator.com', '518319K', '2020-08-28T11:40:46Z'), 47 | (47, 'Daria', 'ddemattei19@blogs.com', '4762CzX', '2020-03-07T20:45:20Z'), 48 | (48, 'Gun', 'gallridge1a@intel.com', 'u8lDAyLoY', '2020-09-04T06:58:56Z'), 49 | (49, 'Dominique', 'dkittle1b@mysql.com', 'WLQD449X8', '2020-12-22T11:37:53Z'), 50 | (50, 'Dionysus', 'dgerin1c@vinaora.com', 'L3APBZ', '2020-06-12T19:51:59Z'), 51 | (51, 'Ronnie', 'rtuttle1d@ow.ly', '7E1jE9OfzYPV', '2020-03-23T12:55:29Z'), 52 | (52, 'Alberik', 'ayeskin1e@hao123.com', 'guYZq9YTJUc', '2020-12-02T23:08:16Z'), 53 | (53, 'Robena', 'rcarloni1f@senate.gov', '0nTKKZcctl', '2020-02-29T10:52:58Z'), 54 | (54, 'Jefferson', 'jletessier1g@stanford.edu', 'R2Rgfu', '2021-02-08T07:26:28Z'), 55 | (55, 'Vanya', 'vdabels1h@time.com', 'fB4XXhZ', '2020-07-03T09:02:51Z'), 56 | (56, 'Fitzgerald', 'fschustl1i@mapy.cz', 'TLfW12Cx', '2020-09-07T10:40:19Z'), 57 | (57, 'Thia', 'tpeegrem1j@flavors.me', 'o9mf7exh', '2020-02-24T15:14:42Z'), 58 | (58, 'Rosalind', 'rlehrmann1k@hostgator.com', 'AHVkfG', '2020-03-18T22:48:36Z'), 59 | (59, 'Daria', 'dhardiman1l@dion.ne.jp', 'YNNTwaGwAH', '2020-06-27T13:11:21Z'), 60 | (60, 'Dugald', 'dhenbury1m@eventbrite.com', 'mTHlNrxXr', '2020-03-24T17:28:23Z'), 61 | (61, 'Caty', 'cfurmston1n@dedecms.com', 'S2hV3qY9', '2020-07-30T06:00:26Z'), 62 | (62, 'Lem', 'lledbetter1o@disqus.com', 'se9CxhEVDl', '2020-07-07T13:12:37Z'), 63 | (63, 'Nat', 'ncroy1p@mlb.com', 'KZ9eTxCQ', '2020-11-01T08:38:10Z'), 64 | (64, 'Elset', 'evarcoe1q@yandex.ru', 'xc12bUWUn7', '2020-08-26T19:50:54Z'), 65 | (65, 'Phyllys', 'pdeetlefs1r@scribd.com', 'MwaMaNG8P', '2020-07-14T12:40:52Z'), 66 | (66, 'Timoteo', 'tgosby1s@latimes.com', 'Be1qYFx2P', '2020-12-23T01:39:57Z'), 67 | (67, 'Zorine', 'zgroombridge1t@goo.gl', 'h2NAT8QWh', '2020-06-03T18:15:13Z'), 68 | (68, 'Hildagard', 'hgurling1u@yahoo.co.jp', 'RAPV9S', '2020-07-11T14:50:59Z'), 69 | (69, 'Rutledge', 'rekless1v@oracle.com', 'NDc66uRb5mc', '2020-04-30T17:23:28Z'), 70 | (70, 'Perrine', 'pcurror1w@mozilla.org', 'h88HRF1', '2020-04-28T21:30:09Z'), 71 | (71, 'Charla', 'clandre1x@exblog.jp', 'CuylIC0g', '2020-02-18T11:39:11Z'), 72 | (72, 'Lesley', 'lshedden1y@patch.com', 'wrUmCPSeaz', '2020-05-09T06:38:57Z'), 73 | (73, 'Darill', 'dskate1z@buzzfeed.com', '8EaxCVUY', '2021-01-26T05:27:35Z'), 74 | (74, 'Johann', 'jackerley20@sakura.ne.jp', 'nyaWn79s', '2020-11-29T06:23:15Z'), 75 | (75, 'Nerta', 'nbiss21@redcross.org', 'mAMhD0Fp', '2020-04-07T05:57:18Z'), 76 | (76, 'Bridget', 'blonglands22@bloglines.com', 'qG02NjhGZ', '2020-07-31T13:07:57Z'), 77 | (77, 'Dave', 'dnudde23@opensource.org', 'ixEjnCior', '2020-07-30T07:30:54Z'), 78 | (78, 'Charla', 'csteffens24@nsw.gov.au', 'u8oXvF', '2021-01-13T21:14:25Z'), 79 | (79, 'Hart', 'hwoofinden25@fotki.com', 'zIoMbe8jb6', '2020-02-24T09:22:40Z'), 80 | (80, 'Garner', 'gmyton26@phoca.cz', 'xoSVyMkkL', '2020-11-04T13:40:27Z'), 81 | (81, 'Aloysius', 'aolivia27@cnbc.com', 'EJAgzHg2p', '2020-12-08T19:19:03Z'), 82 | (82, 'Bartram', 'btreadger28@edublogs.org', 'dmHbJJ9W', '2020-04-27T19:09:11Z'), 83 | (83, 'Marcelia', 'mchamberlaine29@wisc.edu', 'hJB51H', '2021-01-20T09:16:23Z'), 84 | (84, 'Michal', 'mbothen2a@nba.com', 'h7pV5Km21', '2020-03-28T12:29:54Z'), 85 | (85, 'Brenden', 'bhaylor2b@vk.com', 'nBeAZ0Z', '2020-12-13T13:59:50Z'), 86 | (86, 'Ianthe', 'ithowless2c@oakley.com', 'SbvMvRCOdrE', '2021-02-06T14:03:16Z'), 87 | (87, 'Herminia', 'hwolfenden2d@1und1.de', 'JT3cutK', '2020-04-02T23:14:07Z'), 88 | (88, 'Nelson', 'nvasichev2e@prnewswire.com', 'PBd5BHzpQU', '2020-09-23T04:00:02Z'), 89 | (89, 'Ilsa', 'imunroe2f@prlog.org', 'ZRC8J27MpRL', '2020-05-09T09:41:24Z'), 90 | (90, 'Engracia', 'eandrysek2g@vinaora.com', 'G0GJil', '2020-12-03T12:48:36Z'), 91 | (91, 'Chandra', 'cleaton2h@reverbnation.com', 'Js6h8y', '2020-03-28T18:57:55Z'), 92 | (92, 'Antonino', 'acarnegie2i@ebay.com', 'lOU4Ir', '2020-10-02T20:44:33Z'), 93 | (93, 'Austina', 'awestmore2j@netscape.com', '4zuVTs2od', '2020-06-09T00:52:29Z'), 94 | (94, 'Pat', 'pshowering2k@cbc.ca', 'DLmoDMj9Z2', '2020-04-27T01:15:40Z'), 95 | (95, 'Paulette', 'ptomblett2l@w3.org', '56KdiOlR', '2020-09-28T03:15:26Z'), 96 | (96, 'Shaylynn', 'smckenzie2m@loc.gov', 'ZtGdlVK', '2020-11-15T23:57:44Z'), 97 | (97, 'Maura', 'mmaccrosson2n@unc.edu', 'etsPL7QAyj', '2020-03-07T17:08:45Z'), 98 | (98, 'Frederica', 'frudgerd2o@marriott.com', 'mCEHDRtYg', '2020-10-20T14:12:49Z'), 99 | (99, 'Niel', 'nstinson2p@studiopress.com', 'yo9JZmxL', '2021-01-14T15:45:16Z'), 100 | (100, 'Dari', 'dleuty2q@ft.com', 'Yr5gjs3R', '2020-12-08T04:54:02Z'), 101 | (101, 'Betti', 'bcaulier2r@ifeng.com', '07zdZhUZbpz', '2020-08-01T23:27:52Z'), 102 | (102, 'Jessie', 'jmahaffey2s@exblog.jp', 'gfrRLN', '2020-11-18T11:28:47Z'), 103 | (103, 'Aloin', 'akieff2t@gmpg.org', 'WcZdbC', '2020-09-19T17:22:33Z'), 104 | (104, 'Kial', 'kwimmers2u@etsy.com', 'ZHR24hysR9bp', '2020-08-02T19:07:59Z'), 105 | (105, 'Jeni', 'jelizabeth2v@youku.com', 'DrmTWQMg', '2020-04-05T06:02:14Z'), 106 | (106, 'Ericka', 'epavek2w@gmpg.org', 'rEM23TkiQ', '2020-07-17T19:28:03Z'), 107 | (107, 'Theadora', 'tmccullough2x@163.com', 'jbE7MN', '2021-01-06T13:14:20Z'), 108 | (108, 'Benedict', 'bsteanson2y@ask.com', '00gaTRtfTk', '2020-07-06T15:59:44Z'), 109 | (109, 'Shae', 'sdureden2z@shutterfly.com', 'ULqLby3JLK6', '2020-05-27T11:49:56Z'), 110 | (110, 'Pierre', 'psimmen30@samsung.com', 'y4DII2C', '2020-03-14T17:34:52Z'), 111 | (111, 'Lorna', 'lrevie31@ifeng.com', 'AVheuR', '2020-07-27T13:01:38Z'), 112 | (112, 'Dwight', 'dorridge32@wordpress.org', 'te0O439gGZ5g', '2020-06-05T19:58:51Z'), 113 | (113, 'Susan', 'sred33@purevolume.com', 'XO8RCPRChAsF', '2020-12-03T21:10:33Z'), 114 | (114, 'Siana', 'slorryman34@networksolutions.com', 'zxm4HtK', '2020-06-02T00:06:57Z'), 115 | (115, 'Sella', 'smeehan35@exblog.jp', 'oVqy3j4E', '2020-09-05T09:54:59Z'), 116 | (116, 'Clara', 'cjaumet36@dagondesign.com', 'nIVozrVE', '2021-02-01T17:41:31Z'), 117 | (117, 'Buiron', 'bgouny37@businessinsider.com', 'KSjTJyyf95D', '2020-06-13T20:08:19Z'), 118 | (118, 'Dew', 'dcastello38@ca.gov', 't3FzSRN2qMxu', '2020-03-02T01:31:51Z'), 119 | (119, 'Shaun', 'sthurlby39@twitpic.com', 'R7x6ly', '2020-05-06T08:16:13Z'), 120 | (120, 'Darrick', 'ddawson3a@indiegogo.com', '8GdDtOQGO', '2020-11-23T18:01:21Z'), 121 | (121, 'Hillard', 'hburchard3b@umich.edu', 'd0oBgd6P', '2020-07-26T10:21:51Z'), 122 | (122, 'Roxanne', 'rrickertsen3c@eepurl.com', '6PDrnDjrf3p1', '2020-06-26T16:15:54Z'), 123 | (123, 'Goddard', 'gscrowston3d@wix.com', 'kAgBlcPQaT', '2020-07-18T23:40:27Z'), 124 | (124, 'Raven', 'rcrosher3e@yelp.com', '8Ux5fLi', '2020-09-18T02:53:37Z'), 125 | (125, 'Annice', 'agrieger3f@archive.org', '8PfqtrylnviM', '2021-02-12T11:25:46Z'), 126 | (126, 'Ainslie', 'ahadgraft3g@mapy.cz', 'XqiyvJ6', '2020-03-26T03:18:46Z'), 127 | (127, 'Cristen', 'cscarse3h@cafepress.com', 'ph57XGl3', '2020-05-27T13:57:05Z'), 128 | (128, 'Araldo', 'adome3i@hud.gov', 'DEtJyMFx62j', '2020-06-25T23:25:40Z'), 129 | (129, 'Kristin', 'kducarme3j@loc.gov', 'WCdKyx', '2020-03-30T15:18:10Z'), 130 | (130, 'Dorthea', 'dshiell3k@youtu.be', 'oNdEqU', '2020-07-10T16:58:29Z'), 131 | (131, 'Corena', 'crainer3l@sohu.com', '9Euh4Q', '2020-06-22T07:17:23Z'), 132 | (132, 'Lillian', 'lcrandon3m@parallels.com', 'SMMYZC0WY', '2020-02-18T20:43:29Z'), 133 | (133, 'Riki', 'rtullot3n@census.gov', '8KAL27TR7', '2020-04-10T15:08:27Z'), 134 | (134, 'Gaile', 'gbasset3o@freewebs.com', '995mTUUc6ooG', '2020-08-31T11:35:51Z'), 135 | (135, 'Dolph', 'dmarklund3p@wikipedia.org', 'ZpxrFrLcIiAd', '2021-01-03T00:51:50Z'), 136 | (136, 'Matthew', 'mspadelli3q@tripod.com', '2FOuxpuask0H', '2020-06-19T06:28:05Z'), 137 | (137, 'Lezley', 'ldomini3r@ihg.com', 'dkIF4E', '2021-01-27T18:18:54Z'), 138 | (138, 'Phaedra', 'pstrass3s@php.net', 'FmPmIncjgXR', '2020-08-05T04:40:30Z'), 139 | (139, 'Steffane', 'scrossthwaite3t@whitehouse.gov', '1R2BD4dCc', '2020-05-05T22:11:32Z'), 140 | (140, 'Thaxter', 'tbannon3u@nps.gov', '7OQxGKm', '2020-08-27T10:02:25Z'), 141 | (141, 'Charlton', 'classelle3v@icq.com', '2QflgXskB3w', '2020-12-15T04:58:47Z'), 142 | (142, 'Ernestus', 'ebazoge3w@nsw.gov.au', 'r6tlgtnBpt', '2020-03-26T22:46:04Z'), 143 | (143, 'Anastassia', 'ajonczyk3x@google.fr', 'yBhCs3M', '2020-09-28T19:02:32Z'), 144 | (144, 'Guillema', 'gclaxton3y@indiegogo.com', 'qKBy4NvpYC4X', '2020-04-02T00:15:22Z'), 145 | (145, 'Kevan', 'kagney3z@amazon.de', 'R9XZ2iMyuv', '2020-03-02T13:10:10Z'), 146 | (146, 'Chico', 'cmochan40@noaa.gov', 'D6cgZl5NO56', '2020-08-04T11:22:52Z'), 147 | (147, 'Tremaine', 'tleverich41@youtube.com', 'bAK4T08uX0', '2020-06-16T07:27:06Z'), 148 | (148, 'Dione', 'driping42@twitter.com', 'xJUNU6HJ', '2020-09-02T08:40:21Z'), 149 | (149, 'Killian', 'kattkins43@imageshack.us', 'xvG2nr1', '2020-07-20T22:16:00Z'), 150 | (150, 'Bamby', 'bwyles44@oracle.com', 'MBXFDmjL', '2020-08-07T15:05:29Z'), 151 | (151, 'Gretta', 'gemmett45@google.cn', '4ZxaNQA7DD', '2021-02-02T23:57:14Z'), 152 | (152, 'Lynn', 'lburkinshaw46@tmall.com', 'PDBVkAjr', '2020-06-08T16:53:27Z'), 153 | (153, 'Anderea', 'abrydon47@usnews.com', 'VtDtkVy44D4', '2020-12-04T00:29:58Z'), 154 | (154, 'Giselle', 'gjesson48@fc2.com', 'BfRMQV', '2020-09-24T09:22:31Z'), 155 | (155, 'Abra', 'alassey49@vkontakte.ru', 'POvbc0pvGU', '2020-02-22T03:38:44Z'), 156 | (156, 'Caria', 'cbrind4a@pen.io', '0IM530', '2021-02-07T01:57:54Z'), 157 | (157, 'Charil', 'cpeploe4b@unesco.org', '1yGMK68d3', '2021-01-20T12:45:11Z'), 158 | (158, 'Danya', 'dbeckmann4c@sitemeter.com', 'XzPcfVoCv', '2020-11-07T00:51:33Z'), 159 | (159, 'Ortensia', 'ocromie4d@skype.com', '8kuSEJmODoOm', '2020-03-09T03:26:50Z'), 160 | (160, 'Nerte', 'nbraundt4e@icq.com', 'MYa23N8javnN', '2020-10-08T20:52:08Z'), 161 | (161, 'Toinette', 'tpentercost4f@surveymonkey.com', 'GLmh5CiPmWo', '2020-12-19T06:40:09Z'), 162 | (162, 'Jinny', 'jgiraudy4g@slashdot.org', '59WyosUz0oiE', '2020-05-20T12:06:51Z'), 163 | (163, 'Peggi', 'pbalnave4h@list-manage.com', 'hXIl0lP', '2020-10-23T06:08:08Z'), 164 | (164, 'Morissa', 'mbrandli4i@over-blog.com', '9o58Ca', '2020-10-14T05:16:53Z'), 165 | (165, 'Dav', 'drobertacci4j@moonfruit.com', 'CgpxY9K8', '2020-03-15T06:15:31Z'), 166 | (166, 'Audra', 'abiggadyke4k@fda.gov', '2FviYmyb', '2020-04-04T15:43:28Z'), 167 | (167, 'Vikky', 'vflegg4l@jiathis.com', 'yeVjQr', '2020-12-03T15:45:14Z'), 168 | (168, 'Guthry', 'gstorrier4m@va.gov', 'QjL86FEI5zAM', '2020-07-31T11:13:01Z'), 169 | (169, 'Gabbi', 'gknowlton4n@ocn.ne.jp', 'ir1V9ZdpF', '2020-03-16T21:12:12Z'), 170 | (170, 'Toni', 'tcampaigne4o@netvibes.com', '9njt6hqT03', '2020-09-01T01:18:47Z'), 171 | (171, 'Harriot', 'hrawling4p@nps.gov', '09C8yFyCuJ', '2020-11-17T19:49:00Z'), 172 | (172, 'Atalanta', 'aweedall4q@weibo.com', 'BXXPyZNOocR9', '2021-01-02T23:47:48Z'), 173 | (173, 'Bjorn', 'bstorre4r@amazon.co.uk', '2wje8dv', '2020-02-21T04:26:53Z'), 174 | (174, 'Andra', 'aodell4s@boston.com', '8Ky6pSSM6o', '2020-03-24T01:20:41Z'), 175 | (175, 'Leisha', 'lhostan4t@wikia.com', '3nGLrZM5', '2020-05-29T23:18:43Z'), 176 | (176, 'Sara-ann', 'shelliker4u@buzzfeed.com', 'pZ2tlM9', '2020-07-12T04:15:26Z'), 177 | (177, 'Gilberta', 'godams4v@mit.edu', 'GjcU1L2Yi8Bx', '2020-02-19T12:49:15Z'), 178 | (178, 'Pippa', 'pshortland4w@java.com', 'ue4xhv', '2020-04-13T15:28:50Z'), 179 | (179, 'Germana', 'gsanter4x@youtube.com', 'zWTLIVq58U', '2020-03-23T23:12:48Z'), 180 | (180, 'Modesty', 'mclinton4y@fotki.com', '1JZGit484AG', '2020-06-20T05:48:40Z'), 181 | (181, 'Fionnula', 'fheaker4z@t.co', 'WcI4xq', '2020-08-15T17:54:43Z'), 182 | (182, 'Corinne', 'chead50@java.com', 'HeAV6DQmHLO', '2020-10-23T04:18:28Z'), 183 | (183, 'Ches', 'cdillimore51@berkeley.edu', 'WHmS3elHf', '2020-03-23T17:58:25Z'), 184 | (184, 'Mellie', 'mfaulconer52@accuweather.com', 'Pl9Gqtypzo', '2020-07-11T02:47:17Z'), 185 | (185, 'Shelton', 'sbydaway53@dedecms.com', 'empUmiPVh', '2020-08-13T00:42:00Z'), 186 | (186, 'Zebedee', 'zgait54@slashdot.org', 'npiRsQS', '2020-06-16T17:41:41Z'), 187 | (187, 'Crystal', 'cstitfall55@economist.com', '955qRs', '2020-07-10T21:14:13Z'), 188 | (188, 'Heindrick', 'hmeegan56@columbia.edu', 'r5ffzVHff', '2020-03-30T11:13:57Z'), 189 | (189, 'Hakim', 'hcrayker57@youtu.be', 'ON8IcxwuH9q6', '2020-08-31T06:35:07Z'), 190 | (190, 'Jerome', 'jkretschmer58@youtu.be', '4t1RgHpz8GoP', '2020-03-01T00:27:50Z'), 191 | (191, 'Yalonda', 'yellacombe59@w3.org', 'mb4U3SgQa', '2020-03-11T11:51:54Z'), 192 | (192, 'Emelita', 'ehalloway5a@lycos.com', '3hOIUyAUg', '2020-04-03T11:32:35Z'), 193 | (193, 'Roxi', 'rpeel5b@wiley.com', 'SF2Ohjj', '2020-04-04T16:08:06Z'), 194 | (194, 'Olivia', 'orogerot5c@gov.uk', 'xaLVaG', '2020-03-15T16:37:48Z'), 195 | (195, 'Reginauld', 'rstangroom5d@behance.net', '7DOIdD', '2020-08-29T22:51:59Z'), 196 | (196, 'Candida', 'chadkins5e@canalblog.com', 'VYRBtLe', '2020-10-27T09:31:37Z'), 197 | (197, 'Edith', 'eocurrine5f@ed.gov', 'aQMLjOpkNzmy', '2020-05-24T04:32:49Z'), 198 | (198, 'Cherin', 'cmorriarty5g@oracle.com', 'Qo1Z2y', '2021-01-30T05:05:41Z'), 199 | (199, 'Yvon', 'yhundal5h@buzzfeed.com', 'zWMVjh', '2020-09-17T04:32:21Z'), 200 | (200, 'Karlis', 'kdorning5i@gravatar.com', 'J2e44LwXs', '2020-09-25T06:02:14Z'), 201 | (201, 'Galvan', 'ggladeche5j@house.gov', 'YHkxv5bwMj', '2020-03-07T13:32:32Z'), 202 | (202, 'Cary', 'calliband5k@networksolutions.com', 'gZflOr2J', '2020-10-21T20:59:22Z'), 203 | (203, 'Melony', 'mhoppner5l@berkeley.edu', 'gX0Kf9', '2020-06-18T13:55:33Z'), 204 | (204, 'Kimbell', 'kgarz5m@businesswire.com', 'MPAjQsvVF', '2020-03-08T14:39:14Z'), 205 | (205, 'Jacob', 'jhillyatt5n@hud.gov', 'UdT0q73QB', '2020-09-26T01:50:28Z'), 206 | (206, 'Carmina', 'cwithey5o@wsj.com', 'XNnfcXoSi', '2020-04-22T21:56:20Z'), 207 | (207, 'Dinah', 'dcharlton5p@dagondesign.com', 'CBFlrf', '2020-11-22T02:34:17Z'), 208 | (208, 'Wake', 'wpallent5q@jalbum.net', 'PdyU8RgJ8Ca', '2021-02-01T11:00:11Z'), 209 | (209, 'Peta', 'pgoble5r@rediff.com', 'UOu6q8yb', '2020-03-20T15:22:11Z'), 210 | (210, 'June', 'jmcnamara5s@yale.edu', 'VWSza9dw', '2020-03-14T00:07:06Z'), 211 | (211, 'Grenville', 'gelt5t@amazon.de', 'eex1YIE', '2020-10-20T10:46:56Z'), 212 | (212, 'Marga', 'msimkin5u@phoca.cz', 'boLhsTi', '2020-08-20T13:15:38Z'), 213 | (213, 'Charlie', 'cdomico5v@ucoz.com', '3IWn0wehh4iW', '2020-06-14T13:35:23Z'), 214 | (214, 'Ulises', 'umortimer5w@pen.io', 'XSrobW', '2020-08-26T19:16:35Z'), 215 | (215, 'Robbie', 'rscottini5x@intel.com', 'qnfRLhKOZ', '2020-09-28T11:43:54Z'), 216 | (216, 'Flin', 'fvial5y@auda.org.au', 'TaDD13QSU', '2020-04-19T17:37:23Z'), 217 | (217, 'Marcy', 'mcordingly5z@biblegateway.com', 'OTHChCaCWc5', '2020-02-22T13:31:29Z'), 218 | (218, 'Teresina', 'tconlon60@lycos.com', 'zzqoAzuxu', '2020-12-16T03:00:41Z'), 219 | (219, 'Brynn', 'bbern61@ifeng.com', '2ILBZx', '2020-10-23T10:39:52Z'), 220 | (220, 'Hadrian', 'hvlasyev62@mlb.com', 'PCA0Y1YvNL', '2020-03-01T15:25:46Z'), 221 | (221, 'Price', 'ppeacey63@flavors.me', 't7bJIT', '2020-05-09T16:50:28Z'), 222 | (222, 'William', 'winstone64@issuu.com', '3145ATR', '2021-01-13T16:34:32Z'), 223 | (223, 'Tracy', 'tgidley65@domainmarket.com', 'liNFSG', '2020-05-08T08:45:58Z'), 224 | (224, 'Redford', 'rgoodboddy66@google.fr', 'vhJvmmMe6Y', '2020-10-12T01:12:26Z'), 225 | (225, 'Leland', 'lstubbeley67@usda.gov', 'RKSm8M', '2020-08-28T03:57:45Z'), 226 | (226, 'Layney', 'lknevet68@vinaora.com', '74h0t53', '2020-11-11T21:16:00Z'), 227 | (227, 'Maxy', 'mburhouse69@chronoengine.com', 'srHn57WWWsM', '2020-03-05T14:44:10Z'), 228 | (228, 'Allx', 'amalim6a@so-net.ne.jp', 'KvnbCEF', '2020-08-27T13:41:50Z'), 229 | (229, 'Joell', 'jthor6b@oakley.com', 'QonpCgCX', '2021-01-04T10:18:03Z'), 230 | (230, 'Nefen', 'nainslee6c@facebook.com', 'cQdzyn', '2020-04-22T07:37:27Z'), 231 | (231, 'Kamilah', 'kfollitt6d@themeforest.net', 'IJ1Xaiq', '2020-05-15T02:48:54Z'), 232 | (232, 'Hyacinthe', 'htruelove6e@abc.net.au', 'EqIZlHz8OV', '2020-06-25T00:30:41Z'), 233 | (233, 'Barnabe', 'blutas6f@usatoday.com', 'G5wOBHoz7Re', '2020-07-27T19:34:24Z'), 234 | (234, 'Grantley', 'gjouanny6g@hugedomains.com', 'FI3m9gVf', '2020-08-07T04:35:19Z'), 235 | (235, 'Gracia', 'ghanscom6h@sourceforge.net', 'RKn9Ugwe', '2021-01-21T21:50:07Z'), 236 | (236, 'Agnese', 'awater6i@cnbc.com', 'OtTpkWXTiIr', '2020-05-15T06:23:55Z'), 237 | (237, 'Glennie', 'garonov6j@usda.gov', '0cC6Sq0NIMe', '2020-05-21T21:40:10Z'), 238 | (238, 'Ephrem', 'emagnus6k@mapy.cz', 'KBjJQLn', '2021-01-13T10:28:14Z'), 239 | (239, 'Jojo', 'jarmatage6l@sohu.com', 'oMu6PzexmcG5', '2021-01-13T05:55:40Z'), 240 | (240, 'Ruby', 'rpassfield6m@amazon.com', 'XTSmzfk4ni0b', '2020-11-24T08:07:20Z'), 241 | (241, 'Gerome', 'gscarisbrick6n@aol.com', '84u5wKrZFD', '2021-01-07T06:34:35Z'), 242 | (242, 'Alley', 'aslingsby6o@google.pl', 'UyiC23olkc', '2020-04-28T16:00:55Z'), 243 | (243, 'Saleem', 'sberntsson6p@etsy.com', 'WGXAu7ka672v', '2020-11-22T17:58:45Z'), 244 | (244, 'Joseito', 'jpedrielli6q@ebay.co.uk', 'vfxcNLRu', '2020-12-07T11:54:25Z'), 245 | (245, 'Salem', 'sgaineofengland6r@unc.edu', 'ql9l9olGb', '2020-10-22T18:13:00Z'), 246 | (246, 'Patrice', 'poneal6s@studiopress.com', 'ZN88Bci6fqEQ', '2020-03-03T16:34:54Z'), 247 | (247, 'Rawley', 'rangove6t@ucla.edu', 'jBKxE4B4zr', '2020-03-22T06:10:29Z'), 248 | (248, 'Irvine', 'isutter6u@vinaora.com', 'mUt5As', '2020-04-08T20:57:41Z'), 249 | (249, 'Niall', 'npaulou6v@springer.com', 'aU1XdT', '2021-01-20T11:12:35Z'), 250 | (250, 'Liz', 'lcorradi6w@cpanel.net', 'antkNn9RDq', '2020-05-02T20:01:31Z'), 251 | (251, 'Liliane', 'lvonwelldun6x@fotki.com', 'cIU0NvYm2a', '2020-08-16T13:02:34Z'), 252 | (252, 'Kirsti', 'klocks6y@twitter.com', '7jmpwWXgDl', '2020-07-05T00:02:40Z'), 253 | (253, 'Abe', 'atamblyn6z@friendfeed.com', 'wIY8oPT', '2020-08-24T03:36:11Z'), 254 | (254, 'Filberte', 'fspong70@list-manage.com', 'E5RwuPN', '2020-11-12T01:40:18Z'), 255 | (255, 'Clemmie', 'csinkings71@geocities.com', 'y9T9LeXGEgB', '2020-08-08T09:52:10Z'), 256 | (256, 'Aldis', 'avear72@usatoday.com', 'meLycqO1UKvP', '2020-10-07T21:26:48Z'), 257 | (257, 'Arthur', 'aworling73@cnn.com', 'qjOTLP', '2020-07-01T17:15:35Z'), 258 | (258, 'Alexandro', 'alynde74@alexa.com', 'wFP181HjA', '2020-10-23T23:28:21Z'), 259 | (259, 'Olwen', 'omandy75@disqus.com', 'nqYi4KsP1ZH', '2020-09-23T16:15:37Z'), 260 | (260, 'Jasmine', 'jphilippson76@hexun.com', 'LYPm3c4H', '2020-04-30T21:08:21Z'), 261 | (261, 'Xenos', 'xmacdermid77@networkadvertising.org', 'xfhxgpTk0', '2021-01-18T18:33:02Z'), 262 | (262, 'Janeen', 'jtwell78@utexas.edu', 'rKPCdvS', '2020-05-26T03:38:29Z'), 263 | (263, 'Jeanine', 'jchoffin79@dion.ne.jp', 'oggem2qZ', '2020-04-14T16:50:01Z'), 264 | (264, 'Sonnie', 'skruger7a@ning.com', 'gWq5D9A', '2020-09-05T23:53:38Z'), 265 | (265, 'Annabel', 'afoyston7b@list-manage.com', 'eFvcTmHz0', '2020-09-29T07:52:25Z'), 266 | (266, 'Betteann', 'baldred7c@msu.edu', 'fVlCUPlR', '2020-09-08T07:03:59Z'), 267 | (267, 'Kellsie', 'kstorry7d@e-recht24.de', '75kncEgvDLRS', '2020-04-26T03:09:15Z'), 268 | (268, 'Joye', 'jascraft7e@hibu.com', '5g8MpMAs0jTz', '2020-11-04T01:53:12Z'), 269 | (269, 'Suki', 'schstney7f@vkontakte.ru', 'PRn69guJ3', '2020-11-16T17:47:47Z'), 270 | (270, 'Blakelee', 'bstanney7g@xrea.com', 'W10UZGa', '2020-09-29T20:59:05Z'), 271 | (271, 'Hephzibah', 'hingraham7h@usa.gov', 'z20OE068gL', '2021-01-14T10:37:55Z'), 272 | (272, 'Arlina', 'aeagland7i@blog.com', 'hbRgaiM5', '2020-06-09T10:48:50Z'), 273 | (273, 'Marie', 'mspaldin7j@qq.com', 'jeZpL1MvCX', '2021-01-21T14:54:53Z'), 274 | (274, 'Mikael', 'mmoorman7k@fda.gov', '5l66kj', '2020-11-23T12:15:56Z'), 275 | (275, 'Thea', 'trobillard7l@accuweather.com', 'HOeoUU', '2020-11-22T06:05:15Z'), 276 | (276, 'Tawnya', 'ttunder7m@yolasite.com', 'OmaI0Zs', '2020-03-24T03:33:47Z'), 277 | (277, 'Guido', 'gharries7n@privacy.gov.au', '18jzOKck', '2020-10-09T02:26:07Z'), 278 | (278, 'Adler', 'aegleofgermany7o@e-recht24.de', '8Gb7DKEig', '2020-03-11T12:32:49Z'), 279 | (279, 'Lynnett', 'lblackstock7p@csmonitor.com', 'vLESHi42UrtW', '2020-08-04T08:02:28Z'), 280 | (280, 'Nolie', 'nlafee7q@qq.com', 'lQCMAOG9HS4T', '2020-06-07T06:55:14Z'), 281 | (281, 'Beau', 'brecords7r@tiny.cc', '3ynW50vU7y', '2020-06-30T14:38:49Z'), 282 | (282, 'Amos', 'aburgin7s@bluehost.com', 'NMsQsq', '2021-02-08T08:12:42Z'), 283 | (283, 'Larisa', 'lugoletti7t@flavors.me', 'nYnEmG', '2020-07-02T17:21:07Z'), 284 | (284, 'Avis', 'amcginley7u@purevolume.com', 'OnZExEmXC', '2020-02-18T12:01:49Z'), 285 | (285, 'Leo', 'lscupham7v@themeforest.net', '6aLbHSdW', '2020-07-16T07:56:26Z'), 286 | (286, 'Melamie', 'mmedmore7w@edublogs.org', '4pQuzpxkj', '2020-11-04T07:50:47Z'), 287 | (287, 'Tod', 'tzanni7x@intel.com', 'HfmCVWXR6vw', '2020-09-19T00:34:58Z'), 288 | (288, 'Vidovik', 'vmounfield7y@linkedin.com', 'W4ck0ZVBs', '2021-01-29T09:55:26Z'), 289 | (289, 'Lowell', 'lpisculli7z@360.cn', 'Eec9I3rX', '2020-11-12T13:13:42Z'), 290 | (290, 'Claudell', 'cclubbe80@mysql.com', 'lXHqfF', '2021-01-18T19:33:34Z'), 291 | (291, 'Kalina', 'ksherwell81@nymag.com', 'xY28RTkE8', '2020-05-14T01:08:31Z'), 292 | (292, 'Marjory', 'mlanning82@miitbeian.gov.cn', 'yAqNcvRZ', '2020-03-07T07:59:26Z'), 293 | (293, 'Alexio', 'aperkins83@oaic.gov.au', '1W6sX0cIZl', '2020-04-15T13:54:02Z'), 294 | (294, 'Shaine', 'sessery84@angelfire.com', 'MWmmBPckKS', '2020-04-19T15:37:03Z'), 295 | (295, 'Fabe', 'fhurdwell85@domainmarket.com', '3ZzozxM', '2021-01-22T21:39:26Z'), 296 | (296, 'Ive', 'ibotting86@simplemachines.org', 'WWbAmibIed', '2020-08-25T09:23:27Z'), 297 | (297, 'Teador', 'tbantick87@google.pl', 'J6JOg4RS4', '2020-07-25T08:56:32Z'), 298 | (298, 'Langston', 'lwhinray88@ocn.ne.jp', 'CDfRrnhl', '2020-10-14T04:11:47Z'), 299 | (299, 'Britni', 'broz89@deliciousdays.com', 'uVGtPy', '2020-04-07T16:01:52Z'), 300 | (300, 'Gonzales', 'gshekle8a@springer.com', '4y6x8cjW', '2021-01-07T14:14:37Z'), 301 | (301, 'Bondon', 'bkearley8b@redcross.org', '0zKjaSS33Z', '2020-03-01T21:53:23Z'), 302 | (302, 'Correna', 'cbeevers8c@youtube.com', 'y4VD3M', '2020-11-23T22:42:53Z'), 303 | (303, 'Nickolaus', 'nkippen8d@last.fm', '3ZEBNjAsI7Yc', '2020-10-05T07:51:20Z'), 304 | (304, 'Ermina', 'ecuthbert8e@surveymonkey.com', 'Cqn7rI2LbXo', '2020-10-29T18:40:44Z'), 305 | (305, 'Jourdain', 'jswiggs8f@xing.com', 'z8RwfRJvhjlo', '2020-10-23T03:23:42Z'), 306 | (306, 'Janet', 'jbarnsdale8g@51.la', 'if0aTO', '2020-03-29T20:38:41Z'), 307 | (307, 'Aluin', 'aellwell8h@flickr.com', 'toiyvO5f', '2021-01-02T20:19:00Z'), 308 | (308, 'Adeline', 'alum8i@google.co.uk', '7k3mvEywpWa', '2021-01-15T00:23:17Z'), 309 | (309, 'Harlen', 'hchatten8j@networkadvertising.org', 'bc1hpYjE0mTN', '2020-06-06T15:42:58Z'), 310 | (310, 'Tonia', 'tgeistmann8k@theatlantic.com', 'XWTI3VW', '2020-07-24T20:11:54Z'), 311 | (311, 'Dynah', 'dhailston8l@vimeo.com', 'rrXC94j', '2020-11-10T03:34:02Z'), 312 | (312, 'Timmie', 'tpetracchi8m@uol.com.br', 'wFd2lJrlp', '2020-07-07T10:54:32Z'), 313 | (313, 'Tobias', 'tfairey8n@technorati.com', 'gPx4P8swUdEY', '2020-08-26T22:53:38Z'), 314 | (314, 'Phylys', 'ptortis8o@cnn.com', 'rtDCMPKBv', '2020-09-03T22:22:25Z'), 315 | (315, 'Smitty', 'sronchetti8p@shinystat.com', 'kAdRiWhSF5', '2020-02-22T22:41:25Z'), 316 | (316, 'Aleda', 'abiddleston8q@mysql.com', 'LJHi96', '2020-04-08T17:51:46Z'), 317 | (317, 'Ahmad', 'adrissell8r@examiner.com', 'k1fks7Wo0', '2020-06-04T20:08:02Z'), 318 | (318, 'Juliette', 'jmacaless8s@google.fr', 'uQ2q0c', '2021-02-07T06:26:00Z'), 319 | (319, 'Konstantin', 'ktorrijos8t@patch.com', 'hgEPJ6A', '2021-02-02T07:45:29Z'), 320 | (320, 'Sal', 'sgreenham8u@themeforest.net', 'rZBKwsE', '2020-05-06T09:09:21Z'), 321 | (321, 'Hillyer', 'hpaule8v@twitpic.com', '6B8lakv1', '2020-12-24T12:35:41Z'), 322 | (322, 'Laurence', 'ldring8w@seattletimes.com', 'hUb3fv6IB', '2020-03-31T09:36:40Z'), 323 | (323, 'Dyanna', 'ddannatt8x@discuz.net', '9I72rxcHd', '2020-11-06T12:54:54Z'), 324 | (324, 'Vincenty', 'vknight8y@washingtonpost.com', 's4zE5Bsdd8ij', '2020-08-29T08:05:55Z'), 325 | (325, 'Brook', 'bemney8z@statcounter.com', 'e6Nh3yetQ', '2020-02-23T12:41:41Z'), 326 | (326, 'Leonie', 'lcurless90@java.com', 'oTTGQWFtAU', '2020-07-15T15:16:11Z'), 327 | (327, 'Tallie', 'tgroven91@apple.com', '25iMvYKh', '2021-01-21T12:17:03Z'), 328 | (328, 'Sib', 'swhooley92@nih.gov', 'sleyGycV', '2021-01-03T21:08:47Z'), 329 | (329, 'Townie', 'tmcginnis93@twitter.com', 'lylOc3j', '2020-03-15T01:01:20Z'), 330 | (330, 'Fionnula', 'fhalt94@cloudflare.com', 'DtLuelcCZV', '2020-03-30T04:03:14Z'), 331 | (331, 'Robbi', 'rskedgell95@amazonaws.com', 'PP4JWc9umM6N', '2021-02-09T21:15:39Z'), 332 | (332, 'Hana', 'hguildford96@illinois.edu', 'VhoRllL7X', '2020-06-10T16:39:36Z'), 333 | (333, 'Gayelord', 'gglencrosche97@issuu.com', '00E26JFGesz', '2020-03-09T16:44:18Z'), 334 | (334, 'Rubie', 'rserchwell98@yahoo.com', 'rpayeE', '2020-07-30T23:03:02Z'), 335 | (335, 'Helenelizabeth', 'hlarder99@prlog.org', 'tbOJjzARXS', '2020-11-05T05:59:03Z'), 336 | (336, 'Allyson', 'afells9a@cpanel.net', 'wYFIsTm57xk', '2020-06-24T09:20:24Z'), 337 | (337, 'Ealasaid', 'edelisle9b@guardian.co.uk', 'qcxzO5', '2020-08-12T11:18:27Z'), 338 | (338, 'Carmela', 'cdigle9c@toplist.cz', 'AhfTULzM', '2020-05-28T14:03:03Z'), 339 | (339, 'Adelbert', 'akellart9d@mapy.cz', 'DmuIDKHy3SL', '2020-05-28T16:24:05Z'), 340 | (340, 'Amargo', 'awalster9e@infoseek.co.jp', 'hAWR8D', '2020-08-22T20:13:59Z'), 341 | (341, 'Shani', 'smacavaddy9f@answers.com', '5DcnpN', '2020-10-04T12:53:42Z'), 342 | (342, 'Drusilla', 'dfewlass9g@indiatimes.com', 'juTyKzh', '2020-08-27T22:10:00Z'), 343 | (343, 'Hieronymus', 'hclaybourn9h@cdc.gov', '09S8hk', '2020-03-10T09:53:42Z'), 344 | (344, 'Adair', 'ajaquet9i@rakuten.co.jp', 'wQYcJA', '2021-02-05T18:53:04Z'), 345 | (345, 'Fianna', 'fnollet9j@a8.net', 'quuMNjI8I', '2020-07-05T12:34:31Z'), 346 | (346, 'Maxy', 'mrissen9k@bloglines.com', 'FUP3v2XBQDO', '2020-05-26T07:09:51Z'), 347 | (347, 'Cacilia', 'cgregg9l@histats.com', 'fNZvc7C3B3', '2020-08-28T02:35:56Z'), 348 | (348, 'Faustine', 'fhannigane9m@reddit.com', 'MGZwbHV', '2021-01-02T01:36:15Z'), 349 | (349, 'Bunny', 'bthunnerclef9n@dailymotion.com', '4xz9MntrH', '2020-08-24T09:42:30Z'), 350 | (350, 'Marigold', 'mklimko9o@w3.org', 'WJoiAq5U', '2020-05-02T11:51:45Z'), 351 | (351, 'Ida', 'irodie9p@ask.com', 'dWMguH6PcC1u', '2020-12-31T01:44:34Z'), 352 | (352, 'Mary', 'mnansom9q@amazon.co.jp', 'dbrDRrv9h2', '2020-08-18T17:05:56Z'), 353 | (353, 'Marcelline', 'mbeurich9r@quantcast.com', 'pbt3uAMdcT', '2020-05-31T01:36:15Z'), 354 | (354, 'Livvy', 'lodoherty9s@cmu.edu', '0LMqoFnRl', '2020-09-17T17:51:41Z'), 355 | (355, 'Prentice', 'poconnell9t@fc2.com', 'if0PoC8', '2020-06-07T22:06:06Z'), 356 | (356, 'Rea', 'rfrancom9u@phoca.cz', '0OAJkYu0j', '2020-07-27T13:02:48Z'), 357 | (357, 'Christen', 'ccastilljo9v@psu.edu', 'kMrmwMgyGJfV', '2020-10-04T22:12:46Z'), 358 | (358, 'Edee', 'efausch9w@4shared.com', 'ksyvY8VsAa', '2020-09-03T17:24:37Z'), 359 | (359, 'Felix', 'fduffit9x@nbcnews.com', 'PoRBdjB3X', '2020-05-08T08:16:16Z'), 360 | (360, 'Neddy', 'nbelfrage9y@amazon.co.uk', 'YQoP7oJvQ', '2020-08-27T12:47:50Z'), 361 | (361, 'Hadrian', 'hedlestone9z@mozilla.com', 'rtjH5k5SuFYL', '2020-08-12T22:22:59Z'), 362 | (362, 'Papagena', 'pscrubya0@hostgator.com', 'qjq9dbvpGlr7', '2020-10-19T12:59:30Z'), 363 | (363, 'Jordan', 'jbroggettia1@blog.com', 'ybX94wbn8', '2020-05-05T15:30:26Z'), 364 | (364, 'Cyrill', 'cdenzeya2@timesonline.co.uk', 'Qq6ifwur6Qh', '2020-09-30T18:50:49Z'), 365 | (365, 'Marlon', 'mshyrea3@w3.org', 'obIXL2Dy8YCy', '2020-09-20T00:38:24Z'), 366 | (366, 'Shelagh', 'scockhilla4@hugedomains.com', 'EB7w7GyREGro', '2021-01-14T23:06:48Z'), 367 | (367, 'Twyla', 'tpenketha5@china.com.cn', 'Xmwwnipd2yi', '2020-03-29T10:16:59Z'), 368 | (368, 'Massimo', 'mtryea6@si.edu', '5VjyhebYhJN1', '2020-08-04T09:52:06Z'), 369 | (369, 'Dorine', 'dsmallridgea7@umich.edu', '1oVLzBwy0iJ7', '2020-10-07T09:51:02Z'), 370 | (370, 'Shirlee', 'sdewberrya8@posterous.com', 'rRzfClfNBYo', '2021-01-21T11:38:24Z'), 371 | (371, 'Uri', 'uhebditcha9@weibo.com', '8dKoXQ5', '2020-10-24T21:31:26Z'), 372 | (372, 'Dar', 'dfallancheaa@liveinternet.ru', 'NX8iBLMfW', '2020-11-29T19:29:28Z'), 373 | (373, 'Bridie', 'bmcartanab@hp.com', 'ABjnXk', '2021-01-04T21:26:27Z'), 374 | (374, 'Karel', 'klidgertonac@hostgator.com', '8WAw1qp', '2020-06-09T17:22:06Z'), 375 | (375, 'Harmonie', 'hroughsedgead@noaa.gov', 'NPophb7KI8', '2020-09-29T16:53:54Z'), 376 | (376, 'Dulcine', 'dpockae@seattletimes.com', 'KQyscooPU', '2020-11-24T16:56:52Z'), 377 | (377, 'Vere', 'vpurnellaf@princeton.edu', 'x4yZjv', '2020-06-20T10:14:54Z'), 378 | (378, 'Zachary', 'zdaubneyag@php.net', 'XsKBnD', '2020-11-07T01:34:30Z'), 379 | (379, 'Raymund', 'rcutbirthah@goo.ne.jp', 'TrPYVLL9', '2021-01-05T04:09:07Z'), 380 | (380, 'Coral', 'cbampkinai@google.com.hk', 'Mk1XSk7JFL', '2020-11-09T18:17:10Z'), 381 | (381, 'Maighdiln', 'melyaj@yelp.com', '8WbhzLJCA', '2020-04-10T03:20:52Z'), 382 | (382, 'Davis', 'dlamswoodak@umich.edu', 'O95I8XiG', '2020-03-15T14:17:37Z'), 383 | (383, 'Rhea', 'rwesternal@tamu.edu', 'scCXv3No5y', '2020-09-26T14:05:18Z'), 384 | (384, 'Nicolina', 'nmcglaughnam@tinyurl.com', 'JozYLxABXdj', '2020-11-21T19:01:23Z'), 385 | (385, 'Stearn', 'sscapensan@google.com', 'isRDoVb7zo49', '2020-03-13T09:46:58Z'), 386 | (386, 'Sianna', 'sjurgensenao@instagram.com', 'efPSDkos1Wt', '2021-02-06T11:45:13Z'), 387 | (387, 'Jerrie', 'jnewportap@chronoengine.com', 'Pc0mJs3', '2020-12-26T14:42:38Z'), 388 | (388, 'Salvidor', 'scutmereaq@mlb.com', 'Dn90zSvjXj', '2021-01-04T18:14:28Z'), 389 | (389, 'Nolie', 'nastillar@facebook.com', 'tO9CqaAr', '2020-10-23T05:03:57Z'), 390 | (390, 'Andra', 'alongegaas@twitter.com', 'uQ1QrxaJCugs', '2021-01-30T11:58:18Z'), 391 | (391, 'Lanie', 'ldicarloat@clickbank.net', 'TcBnmiMf', '2020-04-20T23:31:22Z'), 392 | (392, 'Palm', 'pwhewayau@google.com.br', 'WB0cofoh28b6', '2020-06-20T04:10:45Z'), 393 | (393, 'Caria', 'cmilchav@opera.com', 'oajDNQbES4', '2020-11-10T21:33:59Z'), 394 | (394, 'Toby', 'tmackieaw@altervista.org', 'z6RS0zxC', '2020-10-26T15:56:59Z'), 395 | (395, 'Bettye', 'bvenningax@cbsnews.com', 'tlkY06NMv', '2020-02-16T09:35:34Z'), 396 | (396, 'Shirline', 'sgillmoray@ycombinator.com', 'qrFPpIZPI', '2020-09-06T15:49:13Z'), 397 | (397, 'Kristan', 'kswiggeraz@ezinearticles.com', 'Rs4DWYVKi', '2020-05-30T08:19:25Z'), 398 | (398, 'Humphrey', 'hbielfeldb0@amazonaws.com', '8RSZ657n', '2020-06-21T20:55:37Z'), 399 | (399, 'Bev', 'bmatleyb1@bluehost.com', 'M2sscQLi', '2020-12-16T03:38:51Z'), 400 | (400, 'Gaynor', 'gbrunigesb2@angelfire.com', 'qB1fmXy3F1', '2020-05-28T20:56:53Z'), 401 | (401, 'Dani', 'dsabatheb3@biblegateway.com', 'WEimiM0', '2020-12-25T17:15:17Z'), 402 | (402, 'Layla', 'lhuygenb4@princeton.edu', 'EMVn3MxJvXm', '2020-09-10T09:32:58Z'), 403 | (403, 'Willabella', 'wchestertonb5@theglobeandmail.com', 'BPN4OkA', '2020-04-30T04:47:05Z'), 404 | (404, 'Forest', 'fmcaleesb6@redcross.org', '06mkYEaz', '2020-09-25T20:26:41Z'), 405 | (405, 'Carree', 'ckorfb7@toplist.cz', 'fC18MNhf', '2020-08-30T08:33:56Z'), 406 | (406, 'Dolf', 'dmccaughanb8@wikimedia.org', 'vGb2AxWl4CJ', '2020-09-10T11:19:57Z'), 407 | (407, 'Keefe', 'kmcquirterb9@eepurl.com', 'F6qqinF0', '2021-02-11T08:16:12Z'), 408 | (408, 'Alecia', 'aosbanba@sina.com.cn', 'zrZlVYAi', '2020-07-10T01:32:44Z'), 409 | (409, 'Anatole', 'ahollingsheadbb@xinhuanet.com', '76L7iGp41', '2020-06-13T13:12:04Z'), 410 | (410, 'Filide', 'flammerdingbc@state.gov', 'rHptRjN', '2020-08-05T23:28:13Z'), 411 | (411, 'Evelyn', 'eoxtibybd@harvard.edu', 'Tl7utTJm61lk', '2020-05-22T07:18:56Z'), 412 | (412, 'Ferdinand', 'fbuttrickbe@cdbaby.com', 'vlw5ZY', '2020-03-03T04:07:29Z'), 413 | (413, 'Haley', 'hjowittbf@census.gov', 'cSiF8Rfpp', '2021-01-10T17:04:43Z'), 414 | (414, 'Finley', 'frubertisbg@salon.com', '5isNgWaSOi', '2020-05-23T17:14:19Z'), 415 | (415, 'Cathie', 'cmerietbh@nymag.com', 'vFzIMTtF1', '2020-05-28T07:21:54Z'), 416 | (416, 'Schuyler', 'saistonbi@shutterfly.com', 'yLQhbV2', '2021-01-08T07:20:44Z'), 417 | (417, 'Quinlan', 'qmetrickbj@ucoz.ru', 'u4yTHnXwO1', '2020-12-11T23:20:37Z'), 418 | (418, 'Boyd', 'bgoodlattbk@desdev.cn', 'BZqcfOqa', '2020-09-02T10:56:02Z'), 419 | (419, 'Tiffany', 'tsolleybl@cloudflare.com', '8aJ97V46N', '2020-04-05T23:17:37Z'), 420 | (420, 'Staci', 'sgosdenbm@businessinsider.com', 'GsRmNZRl', '2020-09-15T07:23:52Z'), 421 | (421, 'Starla', 'slomanseybn@prnewswire.com', 'o9xJl11ZDCfd', '2020-04-25T22:43:52Z'), 422 | (422, 'Josefina', 'jgowlettbo@house.gov', 't8WT6YOKVB8', '2020-11-08T21:21:27Z'), 423 | (423, 'Ruby', 'rbaudinetbp@patch.com', '2bJb8ST', '2020-11-05T04:47:25Z'), 424 | (424, 'Ruttger', 'rforsdikebq@shinystat.com', 'z2OtzyDG', '2020-12-06T00:11:36Z'), 425 | (425, 'Porter', 'pshadwickbr@bandcamp.com', 'jOoWVQwI7Z', '2020-07-10T13:37:51Z'), 426 | (426, 'Christal', 'cgotmannbs@biglobe.ne.jp', 'wAF8IwaklHll', '2020-04-03T19:55:16Z'), 427 | (427, 'Aimee', 'amcpakebt@nhs.uk', 'LiCXVWC', '2020-09-03T17:34:36Z'), 428 | (428, 'Bobbette', 'bklossbu@ihg.com', 'rExVaak2e', '2020-08-01T01:07:15Z'), 429 | (429, 'Amara', 'acubbonbv@technorati.com', 'tD4kj0', '2020-06-16T03:45:51Z'), 430 | (430, 'Carlotta', 'crawdalesbw@samsung.com', '7TiNtC', '2020-07-31T01:37:47Z'), 431 | (431, 'Cordie', 'cshakeladebx@telegraph.co.uk', 'x6OZE1ULde', '2021-01-06T17:06:25Z'), 432 | (432, 'Phillipp', 'pgaudonby@nytimes.com', 'R0gmjrpEQ', '2020-03-05T01:29:58Z'), 433 | (433, 'Doro', 'dfewbz@opensource.org', 'WhkiUxR4Yeo', '2020-08-24T04:27:28Z'), 434 | (434, 'Gonzales', 'gcazereauc0@csmonitor.com', '7lGWvuDY', '2020-03-26T04:52:47Z'), 435 | (435, 'Barn', 'bleighc1@berkeley.edu', 'wBjBmgcrEg', '2020-04-08T15:22:18Z'), 436 | (436, 'Brunhilda', 'burvoyc2@baidu.com', 'j9vtAg', '2020-08-30T16:55:16Z'), 437 | (437, 'Elisha', 'ewinshipc3@elegantthemes.com', '5XvfrG1a3BQJ', '2020-09-08T04:46:34Z'), 438 | (438, 'Jany', 'jwalchc4@cam.ac.uk', 'MzlpSHTZ', '2020-12-10T20:01:46Z'), 439 | (439, 'Katie', 'kmclaughlinc5@moonfruit.com', 'AlI4tZ2SzBDg', '2021-01-02T07:10:38Z'), 440 | (440, 'Barn', 'bspraggsc6@google.ru', '5IQorYSe', '2020-09-22T20:54:14Z'), 441 | (441, 'Adelaide', 'apietsmac7@gravatar.com', 'AmBSp7FmDE', '2020-02-22T07:58:31Z'), 442 | (442, 'Sindee', 'slofthousec8@privacy.gov.au', 'dOdUhRJvyo5', '2020-03-28T04:55:26Z'), 443 | (443, 'Kennie', 'kharvattc9@com.com', 'ZAeyzz', '2020-12-06T09:09:52Z'), 444 | (444, 'Christie', 'ckitneyca@ustream.tv', '2h3KXPtPR8Ty', '2021-01-09T01:21:18Z'), 445 | (445, 'Dalila', 'dandrzejewskicb@oaic.gov.au', 'UncIoKdY8X2', '2020-10-31T04:00:51Z'), 446 | (446, 'Malinde', 'mastlecc@ebay.co.uk', 'sqYwWiU', '2020-12-10T10:21:46Z'), 447 | (447, 'Grier', 'gocheltreecd@shinystat.com', 'DTfN6Lv4eTV', '2020-06-26T05:01:43Z'), 448 | (448, 'Windy', 'wreemece@indiegogo.com', 'iYckkQM', '2020-10-13T01:03:41Z'), 449 | (449, 'Chadwick', 'cfandrichcf@weibo.com', 'vjaSAs', '2020-12-09T22:59:33Z'), 450 | (450, 'Eldon', 'egarratcg@bbb.org', 't99Jy4FZi', '2020-12-09T17:43:10Z'), 451 | (451, 'Kittie', 'kzealeych@engadget.com', 'ceQkQH', '2021-01-20T12:03:19Z'), 452 | (452, 'Maryl', 'mveazeyci@hibu.com', '22D6juHg', '2020-04-05T01:40:42Z'), 453 | (453, 'Morgan', 'mmewcj@vistaprint.com', 'vKYrE6', '2020-07-12T17:18:48Z'), 454 | (454, 'Rosaline', 'rmcveighck@sciencedirect.com', 'ycBVMj', '2020-09-07T01:38:40Z'), 455 | (455, 'Meggie', 'mlemmcl@skyrock.com', '4z4rcWqG7Ox', '2020-09-28T17:47:34Z'), 456 | (456, 'Esmeralda', 'eflecknoecm@harvard.edu', 'Q7XF7kZAlnUQ', '2021-01-26T20:15:40Z'), 457 | (457, 'Corette', 'cgoskercn@jalbum.net', 'oS7uqPEZak', '2021-01-08T13:57:03Z'), 458 | (458, 'Lottie', 'lcoursco@parallels.com', 'mYnql0no', '2020-02-19T21:57:24Z'), 459 | (459, 'Shanta', 'sregiscp@umn.edu', 'zoueL23g', '2021-01-24T19:05:49Z'), 460 | (460, 'Thacher', 'tkoptacq@chron.com', 'F3KP1c6owR', '2020-08-04T19:14:20Z'), 461 | (461, 'Rasla', 'rcrownecr@google.cn', 'J1lIx8', '2020-06-10T11:50:34Z'), 462 | (462, 'Arny', 'atuminics@techcrunch.com', 'Z3MYpT6yp', '2020-09-26T15:25:49Z'), 463 | (463, 'Thomasine', 'tguinnanect@storify.com', 'O51spXb3Gx', '2020-10-04T17:57:51Z'), 464 | (464, 'Aubine', 'acrimminscu@ning.com', 'OQWteFtjxA', '2020-02-19T08:23:28Z'), 465 | (465, 'Currie', 'craitcv@freewebs.com', '7PXprz', '2021-02-02T08:41:53Z'), 466 | (466, 'Baxter', 'bcastiglionicw@sun.com', '8ldBY8pSGa', '2020-04-13T19:35:04Z'), 467 | (467, 'Artie', 'apawseycx@dedecms.com', 'UYYywCWu', '2020-05-15T20:18:45Z'), 468 | (468, 'Khalil', 'ktomaszcy@nationalgeographic.com', 'x9p84Yy', '2020-04-30T15:34:09Z'), 469 | (469, 'Liane', 'lneilsoncz@mozilla.com', 'mBkgqPyCLOum', '2021-02-08T11:03:59Z'), 470 | (470, 'Garvey', 'ggolsworthyd0@vimeo.com', 'UlV4l0iH', '2020-04-22T04:47:42Z'), 471 | (471, 'Truman', 'tfoldsd1@shop-pro.jp', 'NrWSYhB', '2020-10-27T01:31:30Z'), 472 | (472, 'Konstantine', 'kdunphied2@digg.com', 'xioI2AST', '2020-06-17T16:26:57Z'), 473 | (473, 'Hart', 'hhandsd3@businessinsider.com', '6MDalfy', '2020-05-23T07:16:08Z'), 474 | (474, 'Ezequiel', 'edupreyd4@ifeng.com', '1EHF1MClvO', '2020-09-21T13:56:47Z'), 475 | (475, 'Arie', 'awakenshawd5@fastcompany.com', 'NPU5c5Y2C', '2020-11-16T08:21:32Z'), 476 | (476, 'Balduin', 'btrusslerd6@blog.com', 'zARDFu8zM', '2020-06-25T21:09:45Z'), 477 | (477, 'Orsa', 'ocoulsend7@china.com.cn', 'hKaIpKwQgT', '2020-06-05T10:06:50Z'), 478 | (478, 'Gena', 'gforwardd8@barnesandnoble.com', 'e9lAhH8Ak', '2020-06-10T02:16:47Z'), 479 | (479, 'Weidar', 'wadaminid9@mtv.com', 'oP1rHk1Mp', '2020-04-25T18:53:22Z'), 480 | (480, 'Carolina', 'cgareyda@aol.com', 'Ibngqgbu', '2020-06-06T23:54:48Z'), 481 | (481, 'Morganne', 'mrowbottomdb@sitemeter.com', 'UqSbMu', '2020-07-29T11:50:00Z'), 482 | (482, 'Kevin', 'kkingsforddc@fotki.com', 'p7uybEc6K', '2020-11-23T13:54:50Z'), 483 | (483, 'Conn', 'cpaynterdd@webnode.com', 'ljeJnr7n', '2020-03-03T03:51:38Z'), 484 | (484, 'Jolie', 'jflescherde@godaddy.com', 'AGOkbRdtG', '2020-05-13T05:57:16Z'), 485 | (485, 'Essie', 'edurraddf@wunderground.com', 'BsXN7d', '2020-04-06T15:35:44Z'), 486 | (486, 'Zachary', 'zpestrickedg@deviantart.com', 'ANuYw2b23z', '2021-01-21T05:53:45Z'), 487 | (487, 'Gussie', 'gnortondh@is.gd', 'DPUHVrr6fDX', '2020-04-16T04:21:43Z'), 488 | (488, 'Clari', 'cpostgatedi@eventbrite.com', 'tINj8C6dE9C9', '2020-07-21T03:36:55Z'), 489 | (489, 'Leonardo', 'layreedj@symantec.com', 'Kx8JVduNBC', '2020-05-14T17:49:11Z'), 490 | (490, 'Pepita', 'pweerdenburgdk@slideshare.net', 'C82rin97MAhD', '2020-04-23T07:16:02Z'), 491 | (491, 'Carie', 'cwinyarddl@studiopress.com', 'imkpyiDj', '2020-05-09T01:50:54Z'), 492 | (492, 'Rosaline', 'rnockdm@hubpages.com', 'gW9LQbMte', '2020-10-30T16:30:06Z'), 493 | (493, 'Orelie', 'ocornelleaudn@histats.com', 'gYX6eyMthpl', '2020-07-27T23:20:33Z'), 494 | (494, 'Louie', 'lewertdo@msu.edu', 'VornaMZo1DyN', '2020-05-20T05:17:10Z'), 495 | (495, 'Anallese', 'apeddersendp@constantcontact.com', 'Vx2VpoyK7Sdq', '2020-08-04T01:43:55Z'), 496 | (496, 'Heinrick', 'hquogandq@jalbum.net', 'c7Auo3ri', '2020-05-17T03:01:00Z'), 497 | (497, 'Chen', 'ciskowdr@guardian.co.uk', 'kd7kmidr7', '2020-12-06T19:08:54Z'), 498 | (498, 'Myriam', 'mzamudiods@about.com', '4zdv2GIvxRD', '2020-11-18T19:21:15Z'), 499 | (499, 'Windham', 'wparysiakdt@google.ca', 'AdDu3hotJq', '2020-11-04T00:17:03Z'), 500 | (500, 'Kellyann', 'kbalmforthdu@furl.net', 'cMD0GE0S', '2020-06-03T19:18:35Z'), 501 | (501, 'Eveline', 'esamwayesdv@bing.com', 'T3Y6pmvTrB9Z', '2020-12-29T20:59:22Z'); 502 | 503 | COMMIT; 504 | 505 | SELECT setval(pg_get_serial_sequence('user', 'id'), 506 | (select max(id) from "user")); --------------------------------------------------------------------------------