├── 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"));
--------------------------------------------------------------------------------