json2graphql: clean up, update readme, add examples (#1375)

This commit is contained in:
Rishichandra Wawhal 2019-01-15 17:10:29 +05:30 committed by Shahidh K Muhammed
parent 66b67cfe22
commit 92b071afdf
17 changed files with 2212 additions and 318 deletions

View File

@ -1 +1,2 @@
./test/db.js
test/db.js
example-datasets

View File

@ -1,88 +1,146 @@
# JSON database to GraphQL
[Hasura GraphQL Engine](https://hasura.io) gives instant GraphQL APIs over Postgres.
This is A CLI tool to import a schema and data to Postgres using JSON data. You can then leverage all the features of Hasura GraphQL Engine to query the Postgres data over GraphQL.
# json2graphql: From a JSON file to postgres-backed GraphQL
[![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io)
[![Version](https://img.shields.io/npm/v/json2graphql.svg)](https://npmjs.org/package/json2graphql)
## Quick start
`json2graphql` tool to that imports [a JSON file](#json-structure) to initialise schema and data in Postgres and then allows you to start querying it with GraphQL.
1. Quickly get the GraphQL Engine running by clicking this button:
![json2graphql - From JSON to GraphQL on Postgres](https://graphql-engine-cdn.hasura.io/assets/json2graphql/json2postgres-graphql.png)
[Hasura](https://hasura.io) is used to expose a realtime GraphQL API on Postgres. Once your schema and data is imported, you can instantly start running powerful queries with filters, pagination, sorting, fetching relations, insert/update/delete mutations and subscriptions too.
**Use-cases**:
- **Bootstrapping a GraphQL backend**: Try out this example of initialising a GraphQL chat backend using a messages/groups/users chat JSON file. [Try it out](#quickly-bootstrap-a-graphql-backend)
- **Play with a mongo dataset in Postgres & GraphQL**: Export a mongo JSON dump, import it to Postgres and start querying it with GraphQL. [Try it out](#play-with-graphql-on-your-mongodb-data)
- **Query existing JSON datasets over GraphQL**: Pick up a JSON dataset, import to your new or existing Hasura/Postgres instance and start querying it. Try using [jdorfman/awesome-json-datasets](https://github.com/jdorfman/awesome-json-datasets).
------------------------------------------
## Demo
![demo-gif](https://graphql-engine-cdn.hasura.io/assets/json2graphql/j2g.gif)
In the GIF above, we are importing a schema and data from a JSON database. The Hasura GraphQL Engine is running at `https://j2gtest.herokuapp.com`
------------------------------------------
- [Quickstart](#quickstart)
- [Installation](#installation)
- [CLI Usage](#cli-usage)
- [JSON Structure](#json-structure)
- [Use Cases](#use-cases)
- [Credits and related projects](#credits-and-related-projects)
## Quickstart
1. **Create a JSON file** Create a JSON file, say, `db.json` as:
```json
{
"post": [
{ "id": 1, "title": "Lorem Ipsum", "views": 254, "user_id": 123 },
{ "id": 2, "title": "Sic Dolor amet", "views": 65, "user_id": 456 }
],
"user": [
{ "id": 123, "name": "John Doe" },
{ "id": 456, "name": "Alison Craus" }
],
"comment": [
{ "id": 987, "post_id": 1, "body": "Consectetur adipiscing elit", "user_id": 123 },
{ "id": 995, "post_id": 2, "body": "Nam molestie pellentesque dui", "user_id": 456 },
{ "id": 999, "post_id": 1, "body": "quid agis", "user_id": 456 }
]
}
```
2. **Run Hasura + Postgres**: Run the Hasura GraphQL Engine and Postgres on Heroku's free tier by clicking this button:
[![Deploy to heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku)
Note the URL. It will be of the form: `https://<app-name>.herokuapp.com`
Note the URL. It will be of the form: `https://<app-name>.herokuapp.com`. Let's say it's `j2gtest.herokuapp.com`.
For instructions on how to deploy Hasura in other environments, head to the [docs](https://docs.hasura.io/1.0/graphql/manual/getting-started/index.html).
2. Create a db.js file. Your data file should export an object where the keys are the entity types. The values should be lists of entities, i.e. arrays of value objects with at least an id key. For instance:
```js
module.exports = {
user: [
{ id: 123, name: "John Doe" },
{ id: 456, name: "Jane Doe" }
],
city: [
{ id: 987, name: "Stockholm", country: "Sweden" },
{ id: 995, name: "Sydney", country: "Australia" }
]
}
3. **json2graphql**: We import schema, data and create Hasura configuration in one command:
```bash
npm install -g json2graphql
json2graphql https://<app-name>.herokuapp.com --db=./path/to/db.json
```
3. Use the CLI to import the data:
4. **Run GraphQL queries**: You can query the data in Postgres tables over GraphQL using Hasura GraphQL Engine. You can make complicated queries like:
```
$ npm install -g json2graphql
$ json2graphql https://<app-name>.herokuapp.com --db=./path/to/db.js
```
```graphql
query {
user {
postsByUserId {
id
title
commentsByPostId {
body
id
}
}
id
}
}
```
4. That's it. You can go to your HGE URL `https://<app-name>.herokuapp.com` and start querying this data over GraphQL:
5. **Behind the scenes**: The following schema is created in Postgres::
```graphql
query {
user {
id
name
}
city {
id
name
country
}
}
```
```sql
Check [this section](#foreign-keys-and-relationships) for knowing about foreign keys and relationships.
user (
id integer not null primary key,
name text
)
post (
id integer not null primary key,
title text,
views integer,
user_id integer foreign key references user(id)
)
comment (
id integer not null primary key,
body text,
post_id integer foreign key references post(id),
user_id integer foreign key references user(id)
)
```
## Installation
### CLI
```bash
## Install globally
npm install -g json2graphql
## Or run as a one-off command
npx json2graphql <hasura-url> -d ./path/to/db.json
```
## Usage
### CLI
#### Without access key
```
$ json2graphql https://hge.herokuapp.com -d ./path/to/db.js
```
#### With access key
```
$ json2graphql https://hge.herokuapp.com -k <access-key> -d ./path/to/db.js
```
### Command
## CLI Usage
```bash
$ gq URL [flags]
# Running against a hasura without an access key
json2graphql https://j2gtest.herokuapp.com -d ./path/to/db.json
# Running against a hasura with an access key
json2graphql https://j2gtest.herokuapp.com -k <access-key> -d ./path/to/db.json
# Reset configuration, schema & data and import
# Useful for updating schema structure or working against an existing Hasura setup
# WARNING: This will remove all existing schema/data before applying
json2graphql https://j2gtest.herokuapp.com --overwrite -d ./path/to/db.json
```
#### Command
```bash
json2graphql URL [flags]
```
#### Args
@ -92,106 +150,192 @@ $ gq URL [flags]
#### Options
- `-d --db`: path to the JS file that exports your sample JSON database
- `-o --overwrite`: Overwrite tables if they already exist in database
- `-o --overwrite`: DANGER: Overwrite tables if they already exist in database
- `-v --version`: show CLI version
- `-h, --help`: show CLI help
## More features
## JSON structure
### Foreign keys and relationships
You can also define foreign keys and relationships in your JSON sample data. The CLI infers foreign keys and relationships from column names and table names.
For example, in the following data set, the `posts` table has a field called `users_id` which is a foreign key to the `id` column of table `users`. Also, the `comments` table has a field called `posts_id` which is a foreign key to the `id` column of table `posts`.
```js
module.exports = {
post: [
{ id: 1, title: "Lorem Ipsum", views: 254, user_id: 123 },
{ id: 2, title: "Sic Dolor amet", views: 65, user_id: 456 },
],
user: [
{ id: 123, name: "John Doe" },
{ id: 456, name: "Jane Doe" }
],
comment: [
{ id: 987, post_id: 1, body: "Consectetur adipiscing elit" },
{ id: 995, post_id: 1, body: "Nam molestie pellentesque dui" }
]
};
```
Import the database:
```
$ json2graphql https://<app-name>.herokuapp.com --db=./path/to/db.js
```
Now you can make complicated queries like:
```graphql
query {
post {
id
title
views
userByUserId {
id
name
}
commentsByPostId {
id
body
}
}
}
```
The response would be:
The top level of your JSON database should be a JSON object with keys being the name of entities and values being list of entities. For example:
```json
{
"data": {
"post": [
{
"userByUserId": {
"name": "John Doe",
"id": 123
},
"views": 254,
"id": 1,
"title": "Lorem Ipsum",
"commentsByPostId": [
{
"body": "Consectetur adipiscing elit",
"id": 987
},
{
"body": "Nam molestie pellentesque dui",
"id": 995
}
]
},
{
"userByUserId": {
"name": "Jane Doe",
"id": 456
},
"views": 65,
"id": 2,
"title": "Sic Dolor amet",
"commentsByPostId": []
}
"user": [
{ "id": 123, "name": "John Doe" },
{ "id": 456, "name": "Jane Doe" }
],
"city": [
{ "id": 987, "name": "Stockholm", "country": "Sweden" },
{ "id": 995, "name": "Sydney", "country": "Australia" }
]
}
```
1. The JSON structure is a "normalised" set of objects
2. Top level objects are mapped to tables in postgres and root fields in the GraphQL schema
3. Keys in the objects are mapped to columns of the tables in postgres, and as fields in the GraphQL schema
4. Keys in the object with the column name of the form `<ENTITY_NAME>_id`, are considered to indicate foreign-key constraints on postgres, and connections in the GraphQL schema
5. The types of the columns/fields are inferred from the data in the columns
json2graphql treats top-level objects as tables, and their keys as columns. If it encounters a column name of the form `<ENTITY_NAME>_id`, json2graphql will consider it a foreign key to the entity with name `<ENTITY_NAME>`.
| JavaScript type (constructor.name) | Postgres column type | GraphQL field type | Example data |
| ---------------------------------- | ---------------------------- | ------------------ | ------------ |
| Number | numeric | numeric | `12.34` or `1223` |
| String | text | String | `Hello world` |
| Boolean | bool | Boolean | true |
| Date | timestamptz | timestamptz | `new Date("Jan 24, 2010 00:00:00")` |
| Object or Array | jsonb | jsonb | { ... } |
### Generating data - importing with `.js` files
You can also use Javascript `.js` files. This allows you to:
- Write some generation logic for sample data
- Use `date` types
```js
module.exports = {
user: [1,2,3,4,5].map(i => ({
id: i,
name: `user-${i}`,
created: new Date()
}))
};
```
## Use cases
### Play with GraphQL on your MongoDB data
**Note:** This assumes that you've already run through the [quickstart](#quickstart)!
You can migrate your data from MongoDB and explore Realtime GraphQL over it.
1. Tweak the MongoDB doc to fit the required [JSON structure](#json-structure).
2. Use json2graphql to import the data from the JSON
3. Make realtime GraphQL queries
Consider [this MongoDB doc](https://github.com/ozlerhakan/mongodb-json-files/blob/master/datasets/country.json):
1. Tweak the doc to fit the required JSON structure.
The doc originally looks something like this:
```js
{"_id":{"$oid":"55a0f42f20a4d760b5fc305e"},"altSpellings":["AI"],"area":91, ... }
{"_id":{"$oid":"55a0f42f20a4d760b5fc305e"},"altSpellings":["AI"],"area":91, ... }
{"_id":{"$oid":"55a0f42f20a4d760b5fc305e"},"altSpellings":["AI"],"area":91, ... }
.
.
.
```
You should wrap it in an array and make the array a value of a top level key of your choice, say, `country`. You should also field name `_id` to `id` because the CLI expects an `id` field. It should look something like this:
```js
{
"country": [
{"id":{"$oid":"55a0f42f20a4d760b5fc305e"},"altSpellings":["AI"],"area":91, ... }
{"id":{"$oid":"55a0f42f20a4d760b5fc305e"},"altSpellings":["AI"],"area":91, ... }
{"id":{"$oid":"55a0f42f20a4d760b5fc305e"},"altSpellings":["AI"],"area":91, ... }
.
.
.
]
}
```
2. Use json2graphql to import the data from the JSON to Postgres using Hasura GraphQL Engine:
```
json2graphql https://j2gtest.herokuapp.com -d ./db.js
```
3. Try realtime GraphQL. Go to your GraphQL Engine console and try making GraphQL queries like so:
```gql
query {
country (
order_by: { name: asc }
limit: 10
where: { capital: { _is_null: false }}
){
id
name
area
currency
callingCode
capital
}
}
```
### Quickly bootstrap a GraphQL Backend
**Note:** This assumes that you've already run through the [quickstart](#quickstart)!
You can write your schema and data in JSON format to quickly get a Realtime GraphQL API.
For example, to start with a group chat backend:
```
{
"user": [
{ "id": 1, "name": "John Doe", "username": "johndoe", "last_seen": new Date() },
{ "id": 2, "name": "Alice Wan", "username": "alisson", "last_seen": new Date() },
{ "id": 3, "name": "Natalie Jackson", "username": "nats", "last_seen": new Date() },
{ "id": 4, "name": "George Walsh", "username": "georgee", "last_seen": new Date() }
],
"group": [
{ "id": 1, "name": "Engineering", is_active: true },
{ "id": 2, "name": "Marketting", is_active: false }
],
"message": [
{ "id": 1, group_id: 1, "body": "Message 1", "sent_at": new Date(), "user_id": 1 },
{ "id": 2, group_id: 1, "body": "Message 2", "sent_at": new Date(), "user_id": 2 },
{ "id": 3, group_id: 2, "body": "Message 3", "sent_at": new Date(), "user_id": 3 },
{ "id": 4, group_id: 2, "body": "Message 4", "sent_at": new Date(), "user_id": 4 }
]
}
```
You can import the above JSON dataset and make queries like:
```gql
# fetch all the active groups
query fetch_groups {
group (
where: {is_active: { _eq: true }}
order_by: { name: asc }
){
id
is_active
name
}
}
# fetch all messages from a group
query fetch_messeges_from_a_group {
message(
where: { group_id: { _eq: 1 }}
order_by: { sent_at: asc }
) {
id
body
sent_at
sent_by: userByUserId {
id
username
}
}
}
```
### Overwrite
## Examples
If your Postgres already contains tables that you are trying to import using `json2graphql`, the command will fail.
For more examples, check out the [./example-datasets](./example-datasets) directory.
If you want to overwrite the existing tables in the database with the new tables from your sample JSON database, you must provide a flag `-o` or `--overwrite`
## Credits and related projects
---
Maintained with :heart: by <a href="https://hasura.io">Hasura</a>
- [Blowson](https://www.blowson.com/docs/) and its creator [Fredi Bach](https://fredibach.ch)
- [firebase2graphql](https://firebase2graphql.com/): A tool to import data from firebase to a realtime GraphQL API on Postgres
- [json-graphql-server](https://github.com/marmelab/json-graphql-server)

View File

@ -0,0 +1,12 @@
{
"user": [
{ "id": 456, "name": "Sita K", "address": {"country": "India"}},
{ "id": 123, "name": "John Doe", "address":{"street": "Sesame street", "city":"Timbuktoo", "country": "Mali" }}
],
"post": [
{ "id": 1, "title": "My first article - user 123", "views": 254, "user_id": 123, "is_published": true },
{ "id": 2, "title": "My first article - user 456", "views": 54, "user_id": 456, "is_published": true},
{ "id": 3, "title": "My second article - user 123", "views": 65, "user_id": 123, "is_published": false},
{ "id": 4, "title": "My second article - user 456", "views": 565, "user_id": 456, "is_published": false}
]
}

View File

@ -0,0 +1,20 @@
const db = {
"user": [
{ "id": 1, "name": "John Doe", "username": "johndoe", "last_seen": new Date() },
{ "id": 2, "name": "Alice Wan", "username": "alisson", "last_seen": new Date() },
{ "id": 3, "name": "Natalie Jackson", "username": "nats", "last_seen": new Date() },
{ "id": 4, "name": "George Walsh", "username": "georgee", "last_seen": new Date() }
],
"group": [
{ "id": 1, "name": "Engineering", is_active: true },
{ "id": 2, "name": "Marketting", is_active: false }
],
"message": [
{ "id": 1, group_id: 1, "body": "Message 1", "sent_at": new Date(), "user_id": 1 },
{ "id": 2, group_id: 1, "body": "Message 2", "sent_at": new Date(), "user_id": 2 },
{ "id": 3, group_id: 2, "body": "Message 3", "sent_at": new Date(), "user_id": 3 },
{ "id": 4, group_id: 2, "body": "Message 4", "sent_at": new Date(), "user_id": 4 }
]
}
module.exports = db;

View File

@ -0,0 +1,114 @@
const db = {
users: [
{id: 1, name: 'Fredi Bach', country: 'CH', birthday: '1975-09-03', sex: 'm', email: 'osxcode@gmail.com', userStatus_id: 2, date: new Date(), object: {hey: 'there', whats: 'up'}},
{id: 2, name: 'Samuel Patzen', country: 'CH', birthday: '1978-02-01', sex: 'm', email: 'patzen@bluewin.ch', userStatus_id: 2, date: new Date()},
{id: 3, name: 'Hans Muster', country: 'CH', birthday: '1978-02-01', sex: 'm', email: 'hans.muster@domain.ch', userStatus_id: 1, date: new Date()},
],
userStatus: [
{id: 1, key: 'inactive'},
{id: 2, key: 'active'},
{id: 3, key: 'blocked'},
],
userConfigs: [
{id: 1, users_id: 1},
],
leagues: [
{id: 1, name: 'Switzerland', yearly: true, description: 'Waypoint are all placed in Switzerland by local instructors and top pilots.', created: '2018-05-01', seasonStart: '10-01', seasonEnd: '09-31'},
{id: 2, name: 'Austria', yearly: true, description: 'Waypoint are all placed in Austria by local instructors and top pilots.', created: '2018-05-02', seasonStart: '10-01', seasonEnd: '09-31'},
{id: 3, name: 'Vol Liber Grischun Clubmeisterschaft', yearly: false, created: '2018-05-02', seasonStart: '2018-10-01', seasonEnd: '2048-10-01'},
],
userLeagues: [
{id: 1, users_id: 1, leagues_id: 1, isAdmin: true},
{id: 2, users_id: 1, leagues_id: 2, isAdmin: true},
{id: 3, users_id: 2, leagues_id: 1},
{id: 4, users_id: 1, leagues_id: 3},
{id: 5, users_id: 2, leagues_id: 3, isAdmin: true},
],
files: [
{id: 1, mimetypes_id: 1, width: 250, height: 250, url: 'https://imgplaceholder.com/250x250/cccccc/757575/ion-happy-outline'},
{id: 2, mimetypes_id: 1, width: 800, height: 400, url: 'https://imgplaceholder.com/800x400/cccccc/757575/fa-image'},
{id: 3, mimetypes_id: 1, width: 300, height: 200, url: 'https://imgplaceholder.com/300x200/cccccc/757575/fa-map-marker'},
{id: 4, mimetypes_id: 3, url: 'https://mycdn.com/fredi-bach/2018-07-02-001.igc'},
{id: 5, mimetypes_id: 3, url: 'https://mycdn.com/fredi-bach/2018-07-03-001.igc'},
],
mimetypes: [
{id: 1, mime: 'image/png', description: 'Portable Network Graphics'},
{id: 2, mime: 'image/jpeg', description: 'JPEG images'},
{id: 3, mime: 'application/vnd.fai.igc', description: 'Flight track file'},
],
types: [
{id: 1, name: 'Challenge', description: 'A challenging waypoint, only for the best', points: 200},
{id: 2, name: 'Altitude', description: 'A big mountain, that needs altitude to reach', points: 150},
{id: 3, name: 'Beauty', description: 'Just a nice view', points: 100},
{id: 4, name: 'Takeoff', description: 'Official takoeff', points: 10},
{id: 5, name: 'Landing', description: 'Official landing', points: 10},
],
waypoints: [
{id: 1, leagues_id: 1, types_id: 1, lat: 3.789, lng: 41.987, radius: 400, points: 100, minAltitude: 3500, name: 'Oberalp Pass', description: 'From Andermatt to Disentis', files_id: 3},
{id: 2, leagues_id: 1, types_id: 2, lat: 3.589, lng: 41.787, radius: 400, points: 100, minAltitude: 3500, name: 'Furka Pass', description: 'From the Goms to Andermatt', files_id: 3},
{id: 3, leagues_id: 1, types_id: 4, lat: 3.889, lng: 40.787, radius: 400, points: 10, name: 'Fiesch'},
],
waypointNotes: [
{id: 1, waypoints_id: 1, noteTypes_id: 1, title: 'Föhn', text: 'Bei Föhn sehr gefährlich!'},
{id: 2, waypoints_id: 1, noteTypes_id: 2, title: 'Basis', text: 'Braucht mindestens 3000 Meter Basis, besser mehr.'},
],
waypointPhotos: [
{id: 1, users_id: 1, official: true, waypoints_id: 1, mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-1.jpeg'},
{id: 2, users_id: 1, official: true, waypoints_id: 1, mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-2.jpeg'},
{id: 3, users_id: 2, official: false, waypoints_id: 1, mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-3.jpeg'},
],
waypointSuggestions: [
{id: 1, users_id: 2, leagues_id: 1, types_id: 1, lat: 11.789, lng: 33.987, radius: 800, points: 100, minAltitude: 3500, name: 'Limmeren Stausee', description: 'Auf dem Weg von der Surselva ins Glaernerland', files_id: 3},
],
noteTypes: [
{id: 1, name: 'Wind', icon: 'wind', class: 'waypoint-note-wind'},
{id: 2, name: 'Altitude', icon: 'altitude', class: 'waypoint-note-altitude'},
],
sponsors: [
{id: 1, waypoints_id: 1, users_id: 1, name: 'Flugschule Appenzell', url: 'http://www.gleitschirm.ch', slogan: 'Die Flugschule im Alpstein.'},
{id: 2, waypoints_id: 2, name: 'Ozone', url: 'http://www.flyozone.ch', slogan: 'Real world performance.'},
],
waypointChats: [
{id: 1, waypoints_id: 1, users_id: 1, message: 'Can be quite hard with low base!', datetime: '2018-07-02 12:48:45'},
{id: 2, waypoints_id: 1, users_id: 2, message: 'Oh yes, it can!', datetime: '2018-07-02 12:52:11'},
],
wings: [
{id: 1, model: 'Zeno', brand: 'Ozone', certification: 'D'},
{id: 2, model: 'Mentor 3', brand: 'Nova', certification: 'B'},
],
flights: [
{id: 1, users_id: 1, leagues_id: 1, wings_id: 1, date: '2018-07-02', score: 200, files_id: 4, comment: 'Bockig!'},
{id: 2, users_id: 2, leagues_id: 1, wings_id: 2, date: '2018-07-03', score: 100, files_id: 5},
],
favoriteFlights: [
{id: 1, users_id: 1, flights_id: 2, datetime: '2018-07-02 12:48:45'},
],
flightWaypoints: [
{id: 1, flights_id: 1, waypoints_id: 1, datetime: '2018-07-02 12:48:45', score: 100},
{id: 2, flights_id: 1, waypoints_id: 2, datetime: '2018-07-02 13:11:59', score: 100},
{id: 3, flights_id: 2, waypoints_id: 2, datetime: '2018-08-02 14:06:11', score: 100},
],
flightComments: [
{id: 1, flights_id: 1, users_id: 2, datetime: '2018-08-02 14:06:11', text: 'Ok, that was nice!'},
{id: 2, flights_id: 1, users_id: 1, datetime: '2018-08-02 14:09:11', text: 'Thanks'},
],
leagueSeasonUserScores: [
{id: 1, users_id: 1, leagues_id: 1, season: '2018', score: 200, flightCount: 1},
{id: 2, users_id: 1, leagues_id: 2, season: '2018', score: 0, flightCount: 0},
{id: 3, users_id: 2, leagues_id: 1, season: '2018', score: 100, flightCount: 1},
],
routes: [
{id: 1, users_id: 1, leagues_id: 1, name: 'Wallis Sightseeing', description: 'A great route for a low wind high cloudbase day.'},
{id: 2, users_id: 1, leagues_id: 1, name: 'Surselva Adventure'},
],
routeWaypoints: [
{id: 1, routes_id: 1, waypoints_id: 1},
{id: 2, routes_id: 1, waypoints_id: 2, routeWaypoints_id: 1},
{id: 3, routes_id: 1, waypoints_id: 3, routeWaypoints_id: 2},
],
favoriteRoutes: [
{id: 1, users_id: 1, routes_id: 1, datetime: '2018-07-01 15:48:45'},
],
};
module.exports = db;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
{
"post": [
{ "id": 1, "title": "Lorem Ipsum", "views": 254, "user_id": 123 },
{ "id": 2, "title": "Sic Dolor amet", "views": 65, "user_id": 456 }
],
"user": [
{ "id": 123, "name": "John Doe" },
{ "id": 456, "name": "Alison Craus" }
],
"comment": [
{ "id": 987, "post_id": 1, "body": "Consectetur adipiscing elit", "user_id": 123 },
{ "id": 995, "post_id": 2, "body": "Nam molestie pellentesque dui", "user_id": 456 },
{ "id": 999, "post_id": 1, "body": "quid agis", "user_id": 456 }
]
}

View File

@ -0,0 +1,18 @@
const db = {
user: [
{ id: 1, name: "Jon Doe", date_of_birth: new Date('Jan 8, 1992 00:00:00')},
{ id: 2, name: "Jon Doe", date_of_birth: new Date('May 8, 1982 00:00:00')},
{ id: 3, name: "Jon Doe", date_of_birth: new Date('Dec 8, 1972 00:00:00')}
],
product: [
{ id: 1, name: "Samsung TV", price: "4000", available: 6},
{ id: 2, name: "Apple TV", price: "8000", available: 2},
{ id: 3, name: "Playstation", price: "800", available: 20}
],
order: [
{ id: 1, user_id: 1, product_id: 2, placed_at: new Date('Jan 22, 2019 00:12:10')},
{ id: 2, user_id: 3, product_id: 1, placed_at: new Date('Jan 26, 2019 00:12:10')}
]
}
module.exports = db;

View File

@ -1,6 +1,6 @@
{
"name": "json2graphql",
"version": "0.1.1",
"version": "0.1.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -18,32 +18,32 @@
}
},
"@oclif/command": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@oclif/command/-/command-1.5.0.tgz",
"integrity": "sha512-ZKvLLLCtIVNgsUXIECOz7WhTOw5j+eyG5Ly7AncJrCN2ENfFfp5BobX6Vvg3P2eL01RxZhnSXIiyJX79QLiD6Q==",
"version": "1.4.35",
"resolved": "https://registry.npmjs.org/@oclif/command/-/command-1.4.35.tgz",
"integrity": "sha512-Qpx4JMLBaHya1qdNQXx8zp/4avBumtZGMXBQr2dEsuKADJrcx4gBS9qNX8dlCECRd8DMcZTQLzZ3t/JzL5TU7A==",
"requires": {
"@oclif/errors": "^1.1.2",
"@oclif/parser": "^3.6.0",
"@oclif/parser": "^3.5.2",
"debug": "^3.1.0",
"semver": "^5.5.0"
}
},
"@oclif/config": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@oclif/config/-/config-1.7.4.tgz",
"integrity": "sha512-vkK/9PXNsOEEdjsb7D/slclLJNvwTax7e2ApjOpnh8sYL34LcpVC9RA05ypCeoDvQiihN8ZdbMut0/AwskKAaQ==",
"version": "1.6.33",
"resolved": "https://registry.npmjs.org/@oclif/config/-/config-1.6.33.tgz",
"integrity": "sha512-6Tgf8mpUQQ/FYDP4EWxuAmNHftt2lWNN1dwEj4I+V3d+tRAIgcAprRUTjc7ZIl9/9e84N5HE4tgCHCqR1UEh8Q==",
"requires": {
"debug": "^3.1.0",
"tslib": "^1.9.3"
"tslib": "^1.9.2"
}
},
"@oclif/errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@oclif/errors/-/errors-1.2.0.tgz",
"integrity": "sha512-DIcWydwfESHVMwrZt3lj5qLAiX296vdfA6K7utCa2/6nmT1JgBc102iFcjpmNxUzDKBU67NKCVBPSMCBDD/1wg==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@oclif/errors/-/errors-1.1.2.tgz",
"integrity": "sha512-7JGmfL8ob+R+Ut+MQO6qOdj8bUKfbc2HiwR8EeQL6ceK0NA0ch/2tKVkmGYtWZsvc1aqJumWEnCO0FcPMsoQAA==",
"requires": {
"clean-stack": "^1.3.0",
"fs-extra": "^7.0.0",
"fs-extra": "^6.0.1",
"indent-string": "^3.2.0",
"strip-ansi": "^4.0.0",
"wrap-ansi": "^3.0.1"
@ -55,9 +55,9 @@
"integrity": "sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw=="
},
"@oclif/parser": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/@oclif/parser/-/parser-3.6.1.tgz",
"integrity": "sha512-H5gyGM3GaDFr1SHt7gsHfMEmt0/Q5SQYOqmtBlpofYaqiof8wdHZQAj4KY2oJpcy4tnsRJrFM3fN3GNEARBgtg==",
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/@oclif/parser/-/parser-3.7.2.tgz",
"integrity": "sha512-ssYXztaf9TuOGCJQOYMg62L1Q4y2lB4wZORWng+Iy0ckP2A6IUnQy97V8YjAJkkohYZOu3Mga8LGfQcf+xdIIw==",
"requires": {
"@oclif/linewrap": "^1.0.0",
"chalk": "^2.4.1",
@ -65,35 +65,23 @@
}
},
"@oclif/plugin-help": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-2.1.1.tgz",
"integrity": "sha512-eP1Z1yQNgQX3dpvCVQkr3iYVVVGKnWmI1pWxxqPIoUHZ6rmMZtYiawmPPpO/VSouV0ml0eoJ4HBPQfZfhiF8nw==",
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-2.0.5.tgz",
"integrity": "sha512-4i7dGI+Jfrmo7g3Ab9B/cE9SfrSibI1ZZtNTh7Dj9sK412pUcS+LmYx2zpfHSy6ZTa8rUnpO7yMmc+a03QKKPA==",
"requires": {
"@oclif/command": "^1.5.0",
"@oclif/command": "^1.4.30",
"chalk": "^2.4.1",
"indent-string": "^3.2.0",
"lodash.template": "^4.4.0",
"string-width": "^2.1.1",
"widest-line": "^2.0.0",
"wrap-ansi": "^4.0.0"
},
"dependencies": {
"wrap-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz",
"integrity": "sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==",
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^2.1.1",
"strip-ansi": "^4.0.0"
}
}
"wrap-ansi": "^3.0.1"
}
},
"@oclif/screen": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-1.0.2.tgz",
"integrity": "sha512-9k2C/Oyk6OwcvyBrKbSGDfH0baI986Dn8ZDxl8viIg8shl40TSPVx+TqXExUeA0Pj02xSdXEt5YXgDFP5Opc5g=="
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-1.0.4.tgz",
"integrity": "sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw=="
},
"@types/async": {
"version": "2.0.49",
@ -493,11 +481,10 @@
}
},
"cli-ux": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-4.8.1.tgz",
"integrity": "sha512-ehGXI54J7A4WJOa+fe0GDxcl6xmYLQmXDHptTtsWQDqWNXFOJQJzTHaJaFVOSo7e1f/kXtfvS1sPttQqTw44BA==",
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-4.7.3.tgz",
"integrity": "sha512-JtnkcrM7IDlNcj4EHjkgcEu8UUCwK1VfWfiX6t7N+ICTfIzmbNszwHe6nJCz61mmPQr5bmSEZHHj+sN6+IyEfA==",
"requires": {
"@oclif/errors": "^1.2.0",
"@oclif/linewrap": "^1.0.0",
"@oclif/screen": "^1.0.2",
"ansi-styles": "^3.2.1",
@ -505,15 +492,15 @@
"chalk": "^2.4.1",
"clean-stack": "^1.3.0",
"extract-stack": "^1.0.0",
"fs-extra": "^7.0.0",
"fs-extra": "^6.0.1",
"hyperlinker": "^1.0.0",
"indent-string": "^3.2.0",
"is-wsl": "^1.1.0",
"lodash": "^4.17.10",
"password-prompt": "^1.0.7",
"semver": "^5.5.1",
"password-prompt": "^1.0.6",
"semver": "^5.5.0",
"strip-ansi": "^4.0.0",
"supports-color": "^5.5.0",
"supports-color": "^5.4.0",
"supports-hyperlinks": "^1.0.1"
}
},
@ -1054,9 +1041,9 @@
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"fs-extra": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz",
"integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz",
"integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
@ -2332,9 +2319,9 @@
}
},
"widest-line": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz",
"integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
"integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
"requires": {
"string-width": "^2.1.1"
}

View File

@ -1,7 +1,7 @@
{
"name": "json2graphql",
"description": "A CLI tool to import JSON data in Hasura GraphQL Engine",
"version": "0.1.3",
"version": "0.1.4",
"author": "Hasura",
"bin": {
"json2graphql": "./bin/run",
@ -9,18 +9,18 @@
},
"bugs": "https://github.com/hasura/graphql-engine/issues?q=is%3Aissue+is%3Aopen+label%3Ac%2Fjson2graphql",
"dependencies": {
"@oclif/command": "^1.4.35",
"@oclif/config": "^1.6.33",
"@oclif/errors": "^1.1.2",
"@oclif/plugin-help": "^2.0.5",
"cli-ux": "^4.7.3",
"graphqurl": "^0.3.2",
"moment": "^2.22.2",
"node-fetch": "^2.2.0"
"@oclif/command": "1.4.35",
"@oclif/config": "1.6.33",
"@oclif/errors": "1.1.2",
"@oclif/plugin-help": "2.0.5",
"cli-ux": "4.7.3",
"graphqurl": "0.3.2",
"moment": "2.22.2",
"node-fetch": "2.2.0"
},
"devDependencies": {
"eslint": "^4.19.1",
"eslint-config-oclif": "^1.5.1"
"eslint": "4.19.1",
"eslint-config-oclif": "1.5.1"
},
"engines": {
"node": ">=8.0.0"

View File

@ -15,28 +15,22 @@ const createTables = async (tables, url, headers, overwrite, runSql, sql) => {
method: 'POST',
headers,
body: JSON.stringify({
type: 'select',
type: 'run_sql',
args: {
table: {
name: 'hdb_table',
schema: 'hdb_catalog',
},
columns: ['*.*'],
where: {
table_schema: 'public',
},
sql: "select * from information_schema.tables where table_schema = 'public';",
},
}),
}
);
const dbTables = await resp.json();
const tableIndex = dbTables.result[0].indexOf('table_name');
let found = false;
tables.forEach(table => {
if (dbTables.find(dbTable => dbTable.table_name === table.name)) {
for (let i = dbTables.result.length - 1; i > 0; i--) {
if (tables.find(t => t.name === dbTables.result[i][tableIndex])) {
found = true;
throwError('Message: Your JSON database contains tables that already exist in Postgres. Please use the flag "--overwrite" to overwrite them.');
throwError('Message: Your JSON database contains tables that already exist in Postgres public schema. Please use the flag "--overwrite" to overwrite them.');
}
});
}
if (!found) {
cli.action.stop('Done!');
cli.action.start('Creating tables');

View File

@ -2,7 +2,7 @@ const throwError = require('./error');
const getDataType = (data, column) => {
if (typeof data === 'number') {
return (data === parseInt(data, 10)) ? 'int' : 'numeric';
return 'numeric';
}
if (typeof data === 'string' || data === null) {
return 'text';
@ -13,8 +13,8 @@ const getDataType = (data, column) => {
if (data.constructor.name === 'Date') {
return 'timestamptz';
}
if (data.constructor.name === 'Object') {
return 'json';
if (data.constructor.name === 'Object' || data.constructor.name === 'Array') {
return 'jsonb';
}
throwError(`message: invalid data type given for column ${column}: ${typeof data}`);
};
@ -33,17 +33,13 @@ const isForeign = (name, db) => {
};
const getColumnData = (dataArray, db) => {
const refRow = {
numOfCols: 0,
index: 0,
};
dataArray.forEach((row, i) => {
if (Object.keys(row).length > refRow.numOfCols) {
refRow.numOfCols = Object.keys(row).length;
refRow.index = i;
}
let refColumns = {};
dataArray.forEach(row => {
refColumns = {
...refColumns,
...row,
};
});
const refColumns = dataArray[refRow.index];
const columnData = [];
Object.keys(refColumns).forEach(column => {
const columnMetadata = {};
@ -92,7 +88,7 @@ const generate = db => {
Object.keys(db).forEach(rootField => {
const tableMetadata = {};
if (!hasPrimaryKey(db[rootField], rootField)) {
throwError(`message: a unique column with name "id" and type integer must present in table "${rootField}"`);
throwError(`message: a unique column with name "id" must present in table "${rootField}"`);
}
tableMetadata.name = rootField;
tableMetadata.columns = getColumnData(db[rootField], db);

View File

@ -44,7 +44,7 @@ const transformData = (data, tables) => {
if (column.type === 'timestamptz' && row[column.name]) {
newRow[column.name] = moment(row[column.name]).format();
}
if (column.type === 'json' && row[column.name]) {
if (column.type === 'jsonb' && row[column.name]) {
newRow[column.name] = JSON.stringify(row[column.name]);
}
});

View File

@ -33,7 +33,7 @@ const generateCreateTableSql = metadata => {
let columnSql = '(';
table.columns.forEach((column, i) => {
if (column.name === 'id') {
columnSql += '"id" int not null primary key';
columnSql += `"id" ${column.type} not null primary key`;
} else {
columnSql += `"${column.name}" ${column.type}`;
}

View File

@ -1,113 +1,113 @@
const db = {
users: [
{id: 1, name: 'Fredi Bach', country: 'CH', birthday: '1975-09-03', sex: 'm', email: 'osxcode@gmail.com', userStatus_id: 2, date: new Date(), object: {hey: 'there', whats: 'up'}},
{id: 2, name: 'Samuel Patzen', country: 'CH', birthday: '1978-02-01', sex: 'm', email: 'patzen@bluewin.ch', userStatus_id: 2, date: new Date()},
{id: 3, name: 'Hans Muster', country: 'CH', birthday: '1978-02-01', sex: 'm', email: 'hans.muster@domain.ch', userStatus_id: 1, date: new Date()},
j2g_test_users: [
{id: 1, name: 'Fredi Bach', country: 'CH', birthday: '1975-09-03', sex: 'm', email: 'osxcode@gmail.com', j2g_test_userStatus_id: 2, date: new Date(), object: {hey: 'there', whats: 'up'}},
{id: 2, name: 'Samuel Patzen', country: 'CH', birthday: '1978-02-01', sex: 'm', email: 'patzen@bluewin.ch', j2g_test_userStatus_id: 2, date: new Date()},
{id: 3, name: 'Hans Muster', country: 'CH', birthday: '1978-02-01', sex: 'm', email: 'hans.muster@domain.ch', j2g_test_userStatus_id: 1, date: new Date()},
],
userStatus: [
j2g_test_userStatus: [
{id: 1, key: 'inactive'},
{id: 2, key: 'active'},
{id: 3, key: 'blocked'},
],
userConfigs: [
{id: 1, users_id: 1},
j2g_test_userConfigs: [
{id: 1, j2g_test_users_id: 1},
],
leagues: [
j2g_test_leagues: [
{id: 1, name: 'Switzerland', yearly: true, description: 'Waypoint are all placed in Switzerland by local instructors and top pilots.', created: '2018-05-01', seasonStart: '10-01', seasonEnd: '09-31'},
{id: 2, name: 'Austria', yearly: true, description: 'Waypoint are all placed in Austria by local instructors and top pilots.', created: '2018-05-02', seasonStart: '10-01', seasonEnd: '09-31'},
{id: 3, name: 'Vol Liber Grischun Clubmeisterschaft', yearly: false, created: '2018-05-02', seasonStart: '2018-10-01', seasonEnd: '2048-10-01'},
],
userLeagues: [
{id: 1, users_id: 1, leagues_id: 1, isAdmin: true},
{id: 2, users_id: 1, leagues_id: 2, isAdmin: true},
{id: 3, users_id: 2, leagues_id: 1},
{id: 4, users_id: 1, leagues_id: 3},
{id: 5, users_id: 2, leagues_id: 3, isAdmin: true},
j2g_test_userLeagues: [
{id: 1, j2g_test_users_id: 1, j2g_test_leagues_id: 1, isAdmin: true},
{id: 2, j2g_test_users_id: 1, j2g_test_leagues_id: 2, isAdmin: true},
{id: 3, j2g_test_users_id: 2, j2g_test_leagues_id: 1},
{id: 4, j2g_test_users_id: 1, j2g_test_leagues_id: 3},
{id: 5, j2g_test_users_id: 2, j2g_test_leagues_id: 3, isAdmin: true},
],
files: [
{id: 1, mimetypes_id: 1, width: 250, height: 250, url: 'https://imgplaceholder.com/250x250/cccccc/757575/ion-happy-outline'},
{id: 2, mimetypes_id: 1, width: 800, height: 400, url: 'https://imgplaceholder.com/800x400/cccccc/757575/fa-image'},
{id: 3, mimetypes_id: 1, width: 300, height: 200, url: 'https://imgplaceholder.com/300x200/cccccc/757575/fa-map-marker'},
{id: 4, mimetypes_id: 3, url: 'https://mycdn.com/fredi-bach/2018-07-02-001.igc'},
{id: 5, mimetypes_id: 3, url: 'https://mycdn.com/fredi-bach/2018-07-03-001.igc'},
j2g_test_files: [
{id: 1, j2g_test_mimetypes_id: 1, width: 250, height: 250, url: 'https://imgplaceholder.com/250x250/cccccc/757575/ion-happy-outline'},
{id: 2, j2g_test_mimetypes_id: 1, width: 800, height: 400, url: 'https://imgplaceholder.com/800x400/cccccc/757575/fa-image'},
{id: 3, j2g_test_mimetypes_id: 1, width: 300, height: 200, url: 'https://imgplaceholder.com/300x200/cccccc/757575/fa-map-marker'},
{id: 4, j2g_test_mimetypes_id: 3, url: 'https://mycdn.com/fredi-bach/2018-07-02-001.igc'},
{id: 5, j2g_test_mimetypes_id: 3, url: 'https://mycdn.com/fredi-bach/2018-07-03-001.igc'},
],
mimetypes: [
j2g_test_mimetypes: [
{id: 1, mime: 'image/png', description: 'Portable Network Graphics'},
{id: 2, mime: 'image/jpeg', description: 'JPEG images'},
{id: 3, mime: 'application/vnd.fai.igc', description: 'Flight track file'},
],
types: [
j2g_test_types: [
{id: 1, name: 'Challenge', description: 'A challenging waypoint, only for the best', points: 200},
{id: 2, name: 'Altitude', description: 'A big mountain, that needs altitude to reach', points: 150},
{id: 3, name: 'Beauty', description: 'Just a nice view', points: 100},
{id: 4, name: 'Takeoff', description: 'Official takoeff', points: 10},
{id: 5, name: 'Landing', description: 'Official landing', points: 10},
],
waypoints: [
{id: 1, leagues_id: 1, types_id: 1, lat: 3.789, lng: 41.987, radius: 400, points: 100, minAltitude: 3500, name: 'Oberalp Pass', description: 'From Andermatt to Disentis', files_id: 3},
{id: 2, leagues_id: 1, types_id: 2, lat: 3.589, lng: 41.787, radius: 400, points: 100, minAltitude: 3500, name: 'Furka Pass', description: 'From the Goms to Andermatt', files_id: 3},
{id: 3, leagues_id: 1, types_id: 4, lat: 3.889, lng: 40.787, radius: 400, points: 10, name: 'Fiesch'},
j2g_test_waypoints: [
{id: 1, j2g_test_leagues_id: 1, j2g_test_types_id: 1, lat: 3.789, lng: 41.987, radius: 400, points: 100, minAltitude: 3500, name: 'Oberalp Pass', description: 'From Andermatt to Disentis', j2g_test_files_id: 3},
{id: 2, j2g_test_leagues_id: 1, j2g_test_types_id: 2, lat: 3.589, lng: 41.787, radius: 400, points: 100, minAltitude: 3500, name: 'Furka Pass', description: 'From the Goms to Andermatt', j2g_test_files_id: 3},
{id: 3, j2g_test_leagues_id: 1, j2g_test_types_id: 4, lat: 3.889, lng: 40.787, radius: 400, points: 10, name: 'Fiesch'},
],
waypointNotes: [
{id: 1, waypoints_id: 1, noteTypes_id: 1, title: 'Föhn', text: 'Bei Föhn sehr gefährlich!'},
{id: 2, waypoints_id: 1, noteTypes_id: 2, title: 'Basis', text: 'Braucht mindestens 3000 Meter Basis, besser mehr.'},
j2g_test_waypointNotes: [
{id: 1, j2g_test_waypoints_id: 1, j2g_test_noteTypes_id: 1, title: 'Föhn', text: 'Bei Föhn sehr gefährlich!'},
{id: 2, j2g_test_waypoints_id: 1, j2g_test_noteTypes_id: 2, title: 'Basis', text: 'Braucht mindestens 3000 Meter Basis, besser mehr.'},
],
waypointPhotos: [
{id: 1, users_id: 1, official: true, waypoints_id: 1, mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-1.jpeg'},
{id: 2, users_id: 1, official: true, waypoints_id: 1, mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-2.jpeg'},
{id: 3, users_id: 2, official: false, waypoints_id: 1, mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-3.jpeg'},
j2g_test_waypointPhotos: [
{id: 1, j2g_test_users_id: 1, official: true, j2g_test_waypoints_id: 1, j2g_test_mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-1.jpeg'},
{id: 2, j2g_test_users_id: 1, official: true, j2g_test_waypoints_id: 1, j2g_test_mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-2.jpeg'},
{id: 3, j2g_test_users_id: 2, official: false, j2g_test_waypoints_id: 1, j2g_test_mimetypes_id: 2, width: 1080, height: 960, url: 'https://mycdn.com/fredi-bach/oberalp-2018-3.jpeg'},
],
waypointSuggestions: [
{id: 1, users_id: 2, leagues_id: 1, types_id: 1, lat: 11.789, lng: 33.987, radius: 800, points: 100, minAltitude: 3500, name: 'Limmeren Stausee', description: 'Auf dem Weg von der Surselva ins Glaernerland', files_id: 3},
j2g_test_waypointSuggestions: [
{id: 1, j2g_test_users_id: 2, j2g_test_leagues_id: 1, j2g_test_types_id: 1, lat: 11.789, lng: 33.987, radius: 800, points: 100, minAltitude: 3500, name: 'Limmeren Stausee', description: 'Auf dem Weg von der Surselva ins Glaernerland', files_id: 3},
],
noteTypes: [
j2g_test_noteTypes: [
{id: 1, name: 'Wind', icon: 'wind', class: 'waypoint-note-wind'},
{id: 2, name: 'Altitude', icon: 'altitude', class: 'waypoint-note-altitude'},
],
sponsors: [
{id: 1, waypoints_id: 1, users_id: 1, name: 'Flugschule Appenzell', url: 'http://www.gleitschirm.ch', slogan: 'Die Flugschule im Alpstein.'},
j2g_test_sponsors: [
{id: 1, waypoints_id: 1, j2g_test_users_id: 1, name: 'Flugschule Appenzell', url: 'http://www.gleitschirm.ch', slogan: 'Die Flugschule im Alpstein.'},
{id: 2, waypoints_id: 2, name: 'Ozone', url: 'http://www.flyozone.ch', slogan: 'Real world performance.'},
],
waypointChats: [
{id: 1, waypoints_id: 1, users_id: 1, message: 'Can be quite hard with low base!', datetime: '2018-07-02 12:48:45'},
{id: 2, waypoints_id: 1, users_id: 2, message: 'Oh yes, it can!', datetime: '2018-07-02 12:52:11'},
j2g_test_waypointChats: [
{id: 1, j2g_test_waypoints_id: 1, j2g_test_users_id: 1, message: 'Can be quite hard with low base!', datetime: '2018-07-02 12:48:45'},
{id: 2, j2g_test_waypoints_id: 1, j2g_test_users_id: 2, message: 'Oh yes, it can!', datetime: '2018-07-02 12:52:11'},
],
wings: [
j2g_test_wings: [
{id: 1, model: 'Zeno', brand: 'Ozone', certification: 'D'},
{id: 2, model: 'Mentor 3', brand: 'Nova', certification: 'B'},
],
flights: [
{id: 1, users_id: 1, leagues_id: 1, wings_id: 1, date: '2018-07-02', score: 200, files_id: 4, comment: 'Bockig!'},
{id: 2, users_id: 2, leagues_id: 1, wings_id: 2, date: '2018-07-03', score: 100, files_id: 5},
j2g_test_flights: [
{id: 1, j2g_test_users_id: 1, j2g_test_leagues_id: 1, j2g_test_wings_id: 1, date: '2018-07-02', score: 200, j2g_test_files_id: 4, comment: 'Bockig!'},
{id: 2, j2g_test_users_id: 2, j2g_test_leagues_id: 1, j2g_test_wings_id: 2, date: '2018-07-03', score: 100, j2g_test_files_id: 5},
],
favoriteFlights: [
{id: 1, users_id: 1, flights_id: 2, datetime: '2018-07-02 12:48:45'},
j2g_test_favoriteFlights: [
{id: 1, j2g_test_users_id: 1, j2g_test_flights_id: 2, datetime: '2018-07-02 12:48:45'},
],
flightWaypoints: [
{id: 1, flights_id: 1, waypoints_id: 1, datetime: '2018-07-02 12:48:45', score: 100},
{id: 2, flights_id: 1, waypoints_id: 2, datetime: '2018-07-02 13:11:59', score: 100},
{id: 3, flights_id: 2, waypoints_id: 2, datetime: '2018-08-02 14:06:11', score: 100},
j2g_test_flightWaypoints: [
{id: 1, j2g_test_flights_id: 1, j2g_test_waypoints_id: 1, datetime: '2018-07-02 12:48:45', score: 100},
{id: 2, j2g_test_flights_id: 1, j2g_test_waypoints_id: 2, datetime: '2018-07-02 13:11:59', score: 100},
{id: 3, j2g_test_flights_id: 2, j2g_test_waypoints_id: 2, datetime: '2018-08-02 14:06:11', score: 100},
],
flightComments: [
{id: 1, flights_id: 1, users_id: 2, datetime: '2018-08-02 14:06:11', text: 'Ok, that was nice!'},
{id: 2, flights_id: 1, users_id: 1, datetime: '2018-08-02 14:09:11', text: 'Thanks'},
j2g_test_flightComments: [
{id: 1, j2g_test_flights_id: 1, j2g_test_users_id: 2, datetime: '2018-08-02 14:06:11', text: 'Ok, that was nice!'},
{id: 2, j2g_test_flights_id: 1, j2g_test_users_id: 1, datetime: '2018-08-02 14:09:11', text: 'Thanks'},
],
leagueSeasonUserScores: [
{id: 1, users_id: 1, leagues_id: 1, season: '2018', score: 200, flightCount: 1},
{id: 2, users_id: 1, leagues_id: 2, season: '2018', score: 0, flightCount: 0},
{id: 3, users_id: 2, leagues_id: 1, season: '2018', score: 100, flightCount: 1},
j2g_test_leagueSeasonUserScores: [
{id: 1, j2g_test_users_id: 1, j2g_test_leagues_id: 1, season: '2018', score: 200, flightCount: 1},
{id: 2, j2g_test_users_id: 1, j2g_test_leagues_id: 2, season: '2018', score: 0, flightCount: 0},
{id: 3, j2g_test_users_id: 2, j2g_test_leagues_id: 1, season: '2018', score: 100, flightCount: 1},
],
routes: [
{id: 1, users_id: 1, leagues_id: 1, name: 'Wallis Sightseeing', description: 'A great route for a low wind high cloudbase day.'},
{id: 2, users_id: 1, leagues_id: 1, name: 'Surselva Adventure'},
j2g_test_routes: [
{id: 1, j2g_test_users_id: 1, j2g_test_leagues_id: 1, name: 'Wallis Sightseeing', description: 'A great route for a low wind high cloudbase day.'},
{id: 2, j2g_test_users_id: 1, j2g_test_leagues_id: 1, name: 'Surselva Adventure'},
],
routeWaypoints: [
{id: 1, routes_id: 1, waypoints_id: 1},
{id: 2, routes_id: 1, waypoints_id: 2, routeWaypoints_id: 1},
{id: 3, routes_id: 1, waypoints_id: 3, routeWaypoints_id: 2},
j2g_test_routeWaypoints: [
{id: 1, j2g_test_routes_id: 1, j2g_test_waypoints_id: 1},
{id: 2, j2g_test_routes_id: 1, j2g_test_waypoints_id: 2, j2g_test_routeWaypoints_id: 1},
{id: 3, j2g_test_routes_id: 1, j2g_test_waypoints_id: 3, j2g_test_routeWaypoints_id: 2},
],
favoriteRoutes: [
{id: 1, users_id: 1, routes_id: 1, datetime: '2018-07-01 15:48:45'},
j2g_test_favoriteRoutes: [
{id: 1, j2g_test_users_id: 1, j2g_test_routes_id: 1, datetime: '2018-07-01 15:48:45'},
],
};

View File

@ -1,14 +1,19 @@
const {query} = require('graphqurl');
const fetch = require('node-fetch');
const complexQuery = `
query {
favoriteRoutes {
routesByRoutesId {
leaguesByLeaguesId {
flightssByLeaguesId {
flightCommentssByFlightsId (order_by: {users_id:asc}){
users_id
usersByUsersId {
j2g_test_favoriteRoutes {
j2g_test_routesByJ2g_test_routesId {
j2g_test_leaguesByJ2g_test_leaguesId {
j2g_test_flightssByJ2g_test_leaguesId (
order_by: {
id:asc
}
){
j2g_test_flightCommentssByJ2g_test_flightsId(order_by: {j2g_test_users_id:asc}) {
j2g_test_users_id
j2g_test_usersByJ2g_test_usersId {
email
}
}
@ -19,20 +24,78 @@ query {
}
`;
const testTables = [
'j2g_test_favoriteFlights',
'j2g_test_favoriteRoutes',
'j2g_test_files',
'j2g_test_flightComments',
'j2g_test_flightWaypoints',
'j2g_test_flights',
'j2g_test_leagueSeasonUserScores',
'j2g_test_leagues',
'j2g_test_mimetypes',
'j2g_test_noteTypes',
'j2g_test_routeWaypoints',
'j2g_test_routes',
'j2g_test_sponsors',
'j2g_test_types',
'j2g_test_userConfigs',
'j2g_test_userLeagues',
'j2g_test_userStatus',
'j2g_test_users',
'j2g_test_waypointChats',
'j2g_test_waypointNotes',
'j2g_test_waypointPhotos',
'j2g_test_waypointSuggestions',
'j2g_test_waypoints',
'j2g_test_wings',
];
const deleteTables = () => {
const deleteQuery = {
type: 'bulk',
args: testTables.map(tname => ({
type: 'run_sql',
args: {
sql: `drop table if exists public."${tname}" cascade;`,
cascade: true,
},
})),
};
fetch(
`${process.env.TEST_HGE_URL}/v1/query`,
{
method: 'POST',
headers: {'x-hasura-access-key': process.env.TEST_X_HASURA_ACCESS_KEY},
body: JSON.stringify(deleteQuery),
}
).then(() => {
console.log('Test tables deleted!');
}).catch(() => console.log('Failed deleting test tables'));
};
const verifyDataImport = () => {
query({
let resp = null;
return query({
query: complexQuery,
endpoint: `${process.env.TEST_HGE_URL}/v1alpha1/graphql`,
headers: {'x-hasura-access-key': process.env.TEST_X_HASURA_ACCESS_KEY},
}).then(response => {
if (response.data.favoriteRoutes[0].routesByRoutesId.leaguesByLeaguesId.flightssByLeaguesId[0].flightCommentssByFlightsId[0].usersByUsersId.email === 'osxcode@gmail.com') {
resp = response;
if (response.data.j2g_test_favoriteRoutes[0]
.j2g_test_routesByJ2g_test_routesId
.j2g_test_leaguesByJ2g_test_leaguesId
.j2g_test_flightssByJ2g_test_leaguesId[0]
.j2g_test_flightCommentssByJ2g_test_flightsId[0]
.j2g_test_usersByJ2g_test_usersId.email === 'osxcode@gmail.com') {
console.log('✔︎ Test passed');
process.exit();
} else {
console.log('✖ Test failed. Unexpected response.');
console.log(response.data);
}
}).catch(() => {
console.log('✖ Test failed. Unexpected response.');
console.log(JSON.stringify(resp, null, 2));
});
};
verifyDataImport();
verifyDataImport().then(() => deleteTables()).catch(() => deleteTables());