2 |
3 | #
4 |
5 | [](https://github.com/open-source-labs/Swell/blob/master/LICENSE.txt)
6 | [](https://travis-ci.org/open-source-labs/Swell)
7 | 
8 | [](https://github.com/getswell/getswell/issues)
9 | [](https://twitter.com/intent/tweet?text=Swell-%20For%20all%20your%20streaming%20API%20testing%20needs&url=https://www.getswell.io&hashtags=SSE,WebSocket,HTTP,API,developers)
10 |
11 |
12 |
13 | # QLens
14 |
15 | Using GraphQL with MongoDB can cause data mismatch and schema duplication requiring developers to write similar code multiple times. This can significantly slow down development time. In addition, there aren't many libraries that tackle MongoDB conversion to GraphQL schemas. Most libraries tackling this issue are focused on relational databases. QLens solves that problem.
16 |
17 | QLens — Open source tool to extract metadata from your MongoDB database to generate GraphQL schemas, resolvers and server setup.
18 |
19 | Accelerated by [OS Labs](https://github.com/oslabs-beta/)
20 |
21 | ## Getting Started
22 | 1. Globally install extract mongo schema on your machine: npm -g install extract-mongo-schema
23 | 2. Download QLens onto your desktop by going to [www.qlensapp.com](http://qlensapp.com)
24 |
25 | ## Features
26 | * GraphQL schema (including resolvers, and mutations)
27 | * Visual interactive diagram of Hierarchy Display
28 | * Download Schemas
29 | * GraphQL's Playground GUI
30 |
31 | ## How does QLens work?
32 | 
33 |
34 | ## 1. Enter URI
35 | Simply enter your non-relational database (MongoDB) URI in the input form at the top of the screen and press enter. You will see a dropdown menu on the left hand side of the screen revealing all of your database schemas. Click the schemas you would like converted to GraphQL and then click the ‘Add Selected Schemas’ button.
36 |
37 | 
38 |
39 | Please see below gif to reset the URI:
40 |
41 | 
42 |
43 | ## 2. Select GraphQL Schemas
44 | On the right side of the screen is where your life just got a whole lot easier. You will find your GraphQL schema boilerplate already entirely formatted for your project complete with resolvers and mutations. At the top of the text editor, you will see a tab to toggle to see your MongoDB schemas in their own editor. Here is where you can either copy your GraphQL schemas and paste right into your code editor, or click the download button.
45 |
46 | ## 3. Download Schemas
47 | Click the Download Schemas button at the bottom of the code editor and the GraphQL schema code will download into a folder on your desktop. Drag the folder into your preferred code editor and voilà! Formatted GraphQL schemas right there in your code editor! It’s that easy!
48 |
49 | ## 4. Playground:
50 | Back in QLens, you can see your GraphQL schemas in action by clicking on the Playground button at the top right-hand side of the screen which will take you to GraphQL’s very own integrated development environment, GraphiQL where you can make queries and test your code.
51 |
52 | 
53 |
54 | ## How To Contribute
55 | We would love for you to test our application and submit any issues you encouter. Please feel free to fork your own repository to and submit your own pull requests.
56 |
57 | ## Built With
58 |
59 | - Electron
60 | - React
61 | - Codemirror
62 | - Jest
63 | - Node.js
64 | - Express
65 | - Graphiql
66 | - Graphql
67 | - Lodash
68 | - Babel
69 | - Webpack
70 | - Enzyme
71 | - React-Testing-Library
72 | - Spectron
73 |
74 | How you can contribute:
75 | * Bug fixes
76 | * Implementing features
77 | * Submitting or resolving GitHub issues
78 | * Help market our application
79 | ## Developers
80 | * [Steven LaBrie](https://github.com/stevenlabrie)
81 | * [Judy Tan](https://github.com/Judanator)
82 | * [Cho Yee Win Aung](https://github.com/choyeewinag)
83 | * [Jake Diorio](https://github.com/jdiorio2393)
84 |
85 | ## License
86 | This project is licensed under the MIT License
87 |
88 | ## Upcoming Features
89 | Currently QLens does not support playground functionality and has limited functionality for creating resolvers. Currently we are working on implementing the following features:
90 | * Playground functionality for testing queries and mutations
91 | * Generating mutations for updating
92 | * Generating resolvers for Mongo documents with "ref" or foreign keys
93 | * Allowing for conversion of SQL databases
94 |
--------------------------------------------------------------------------------
/__mocks__/MockCases.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {jest} from '@jest/globals';
3 |
4 | // set up mockState with real state name as prop, and state properties passed in
5 |
6 | export const schemaDataState = {
7 | schemaDataState: {},
8 | };
9 |
10 | export const schemaDataSetState = {
11 | generalState: {
12 | setSchemaData: jest.fn(),
13 | },
14 | };
15 |
16 | export const uriIdState = {
17 | uriIdState: {},
18 | };
19 |
20 | export const uriIdSetState = {
21 | uriIdState: {
22 | setUriId: jest.fn(),
23 | },
24 | };
25 |
26 | export const selectedSchemaDataState = {
27 | selectedSchemaDataState: {},
28 | };
29 |
30 | export const selectedSchemaDataSetState = {
31 | selectedSchemaDataState: {
32 | setSelectedSchemaData: jest.fn(),
33 | },
34 | };
35 |
36 | export const clickedState = {
37 | clicked: {},
38 | };
39 |
40 | export const clickedSetState = {
41 | clicked: {
42 | setClicked: jest.fn(),
43 | },
44 | };
45 |
46 |
47 | export const graphQLSchemaState = {
48 | graphQLSchema: {},
49 | };
50 |
51 | export const graphQLSchemaSetState = {
52 | graphQLSchema: {
53 | setGraphQLSchema: jest.fn(),
54 | },
55 | };
56 |
57 | // export const homeGenState = {
58 | // generalState: {
59 | // onHomePage: true,
60 | // URImodal: false,
61 | // helpModal: false,
62 | // },
63 | // };
64 |
65 | // export const appGenState = {
66 | // generalState: {
67 | // onHomePage: false,
68 | // URImodal: false,
69 | // helpModal: false,
70 | // generalDispatch: jest.fn(),
71 | // },
72 | // };
--------------------------------------------------------------------------------
/__mocks__/MockServer.js:
--------------------------------------------------------------------------------
1 | // tests RESTful requests. there's also a GraphQL one
2 | import { rest } from 'msw';
3 | import { setupServer } from 'msw/node';
4 | // import 'whatwg-fetch';
5 |
6 | const server = setupServer(
7 | rest.get('/', (req, res, ctx) => {
8 | return res(ctx.status(200), ctx.json({ greeting: 'hello world' }));
9 | })
10 | );
11 |
12 | // establish API mocking before all tests
13 | beforeAll(() => server.listen());
14 | // reset any request handlers that are declared as a part of our tests
15 | // (i.e. for testing one-time error scenarios)
16 | afterEach(() => server.resetHandlers());
17 | // clean up once the tests are done
18 | afterAll(() => server.close());
19 |
20 | async function onRoot() {
21 | const result = await fetch('/');
22 | const data = await result.json();
23 | return data;
24 | }
25 |
26 | export { server, rest };
--------------------------------------------------------------------------------
/__mocks__/SetupTests.js:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/extend-expect';
2 | import 'regenerator-runtime/runtime';
3 |
--------------------------------------------------------------------------------
/__mocks__/electronMock.js:
--------------------------------------------------------------------------------
1 | export const ipcRenderer = {
2 | on: jest.fn(),
3 | };
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = "test-file-stub"
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
3 |
--------------------------------------------------------------------------------
/__tests__/Enzyme.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Adapter from 'enzyme-adapter-react-16';
3 | import { shallow, configure } from 'enzyme';
4 |
5 | // import custom useContext
6 | import * as MockContexts from '../app/src/state/Contexts';
7 |
8 | // import mock cases
9 | import { schemaDataState, schemaDataSetState } from '../__mocks__/MockCases';
10 | import { uriIdState, uriIdSetState } from '../__mocks__/MockCases';
11 | import { selectedSchemaDataState, selectedSchemaDataSetState } from '../__mocks__/MockCases';
12 | import { clickedState, clickedSetState } from '../__mocks__/MockCases';
13 | import { graphQLSchema, graphQLSetSchema } from '../__mocks__/MockCases';
14 |
15 | // import React Components
16 | import CheckBox from '../app/src/Components/CheckBox';
17 | import DropDownMenu from '../app/src/Components/DropDownMenu';
18 | import MongoDBURI from '../app/src/Components/MongoDBURI';
19 | import MongoSchemaIDE from '../app/src/Components/MongoSchemaIDE';
20 | import PlaygroundButton from '../app/src/Components/PlaygroundButton';
21 | import TreeGraph from '../app/src/Components/TreeGraph';
22 | import App from '../app/src/App';
23 |
24 | configure({ adapter: new Adapter() });
25 |
26 | describe(' renders on electron app', () => {
27 | const wrapper = shallow();
28 |
29 | it('App contains Logo', () => {
30 | expect(wrapper.find('img')).toBeTruthy();
31 | });
32 | })
33 |
34 | describe('MongoDBURI Input Box Displays', () => {
35 | it('MongoDBURI Input Box renders initially', () => {
36 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx)
37 | // we pass in our mock state as context to the spy
38 | jest
39 | .spyOn(MockContexts, 'useGenContext')
40 | // .mockImplementation(() => uriIdState);
41 |
42 | const wrapper = shallow();
43 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer
44 | const confirm = wrapper.hasClass('formContainer');
45 | // expects confirm (boolean => true) to be true
46 | expect(confirm).toBe(true);
47 | });
48 | })
49 |
50 | describe('PlaygroundButton Displays', () => {
51 | it('PlaygroundButton renders initially', () => {
52 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx)
53 | // we pass in our mock state as context to the spy
54 | jest
55 | .spyOn(MockContexts, 'useGenContext')
56 | // .mockImplementation(() => schemaDataState);
57 |
58 | const wrapper = shallow();
59 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer
60 | const confirm = wrapper.hasClass('playgroundButton');
61 | // expects confirm (boolean => true) to be true
62 | expect(confirm).toBe(true);
63 | });
64 | })
65 |
66 | describe('DropDownMenu Displays', () => {
67 | it('DropDownMenu renders initially', () => {
68 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx)
69 | // we pass in our mock state as context to the spy
70 | jest
71 | .spyOn(MockContexts, 'useGenContext')
72 | // .mockImplementation(() => schemaDataState);
73 |
74 | const wrapper = shallow();
75 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer
76 | const confirm = wrapper.hasClass('dropDown');
77 | // expects confirm (boolean => true) to be true
78 | expect(confirm).toBe(true);
79 | });
80 | })
81 |
82 |
83 | describe('TreeGraph Displays', () => {
84 | it('TreeGraph renders initially', () => {
85 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx)
86 | // we pass in our mock state as context to the spy
87 | jest
88 | .spyOn(MockContexts, 'useGenContext')
89 | // .mockImplementation(() => schemaDataState);
90 |
91 | const wrapper = shallow();
92 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer
93 | const confirm = wrapper.hasClass('tree');
94 | // expects confirm (boolean => true) to be true
95 | expect(confirm).toBe(true);
96 | });
97 | })
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/app/electron.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Import parts of electron to use
3 | const { app, BrowserWindow } = require('electron');
4 | const path = require('path');
5 | const url = require('url');
6 | const { ipcMain } = require('electron');
7 | const process = require('process');
8 | const server = require('./server/schema');
9 | const fs = require('fs');
10 | const os = require('os');
11 | const fixPath = require('fix-path');
12 | // Connect to mongodb
13 | const MongoClient = require('mongodb').MongoClient;
14 | // Executing terminal commands using JS
15 | const { exec } = require('child_process');
16 | const { autoUpdater } = require('electron-updater');
17 |
18 | // Add React extension for development
19 | const { default: installExtension, REACT_DEVELOPER_TOOLS } = require('electron-devtools-installer');
20 |
21 | // Keep a global reference of the window object, if you don't, the window will
22 | // be closed automatically when the JavaScript object is garbage collected.
23 | let mainWindow;
24 |
25 | // Keep a reference for dev mode
26 | let dev = false;
27 |
28 | // Determine the mode (dev or production)
29 | if (process.defaultApp || /[\\/]electron-prebuilt[\\/]/.test(process.execPath) || /[\\/]electron[\\/]/.test(process.execPath)) {
30 | dev = true;
31 | }
32 |
33 | // Temporary fix for broken high-dpi scale factor on Windows (125% scaling)
34 | // info: https://github.com/electron/electron/issues/9691
35 | if (process.platform === 'win32') {
36 | app.commandLine.appendSwitch('high-dpi-support', 'true')
37 | app.commandLine.appendSwitch('force-device-scale-factor', '1')
38 | }
39 |
40 | function createWindow() {
41 | // Create the browser window.
42 | mainWindow = new BrowserWindow({
43 | width: 1350, // width of the window default 1024
44 | height: 768, // height of the window
45 | show: false, // don't show until window is ready
46 | webPreferences: {
47 | nodeIntegration: true
48 | }
49 | })
50 |
51 | // and load the index.html of the app.
52 | let indexPath;
53 |
54 | // Determine the correct index.html file
55 | // (created by webpack) to load in dev and production
56 | if (dev && process.argv.indexOf('--noDevServer') === -1) {
57 | indexPath = url.format({
58 | protocol: 'http:',
59 | host: 'localhost:8080',
60 | pathname: 'index.html',
61 | slashes: true
62 | })
63 | } else {
64 | indexPath = url.format({
65 | protocol: 'file:',
66 | pathname: path.join(__dirname, '../dist/index.html'),
67 | slashes: true
68 | })
69 | }
70 |
71 | // Load the index.html
72 | mainWindow.loadURL(indexPath)
73 |
74 | // Don't show the app window until it is ready and loaded
75 | mainWindow.once('ready-to-show', () => {
76 | mainWindow.show()
77 |
78 | // Open the DevTools automatically if developing
79 | if (dev) {
80 | installExtension(REACT_DEVELOPER_TOOLS)
81 | .catch(err => console.log('Error loading React DevTools: ', err))
82 | mainWindow.webContents.openDevTools()
83 | }
84 | })
85 |
86 | // Emitted when the window is closed.
87 | mainWindow.on('closed', function() {
88 | // Dereference the window object, usually you would store windows
89 | // in an array if your app supports multi windows, this is the time
90 | // when you should delete the corresponding element.
91 | mainWindow = null
92 | })
93 | mainWindow.once('ready-to-show', () => {
94 | autoUpdater.checkForUpdatesAndNotify();
95 | });
96 | }
97 |
98 | // This method will be called when Electron has finished
99 | // initialization and is ready to create browser windows.
100 | // Some APIs can only be used after this event occurs.
101 | app.on('ready', () => {
102 | createWindow()
103 | // otherWindow()
104 | })
105 |
106 | // Quit when all windows are closed.
107 | app.on('window-all-closed', () => {
108 | // On macOS it is common for applications and their menu bar
109 | // to stay active until the user quits explicitly with Cmd + Q
110 | if (process.platform !== 'darwin') {
111 | app.quit()
112 | }
113 | })
114 |
115 | app.on('activate', () => {
116 | // On macOS it's common to re-create a window in the app when the
117 | // dock icon is clicked and there are no other windows open.
118 | if (mainWindow === null) {
119 | createWindow()
120 | }
121 | })
122 |
123 | if (!fs.existsSync(path.join(process.resourcesPath, "/schemafiles/"))) {
124 | fs.mkdirSync(path.join(process.resourcesPath, "/schemafiles/"));
125 | }
126 | let testpath = path.join(process.resourcesPath, "/schemafiles/qlens.json")
127 |
128 | if (process.resourcesPath !== 'win32') fixPath();
129 |
130 | ipcMain.on('URI', (event, arg) => {
131 |
132 | // Connect to mongodb
133 | MongoClient.connect(arg).then(() => {
134 | const query = `extract-mongo-schema -d "${arg}" -o ${testpath}`;
135 | event.sender.send('console', process.platform)
136 | //Using exec to run extract-mongo-schema package in terminal
137 | exec(query, (error, stdout, stderr) => {
138 | event.sender.send('URI-reply', path.join(process.resourcesPath))
139 | })
140 | })
141 |
142 | let filepath = path.join(process.resourcesPath, "/schemafiles")
143 | let file;
144 | let extractedSchemas;
145 | // Watching for changes in root directory. (The adding of json file with schema)
146 | const watcher = fs.watch(filepath, (events, trigger) => {
147 | // console.log(`there was a ${event} at ${trigger}`);
148 | // Once change happens (file is added) read schema.json file
149 | file = fs.readFileSync(path.join(process.resourcesPath, "/schemafiles/qlens.json"));
150 | // console.log('SERVER.JS =========> ', Buffer.from(file).toString())
151 | extractedSchemas = Buffer.from(file).toString();
152 | if (extractedSchemas) {
153 | // send locals data to client, close this watch function
154 | watcher.close();
155 | if (fs.existsSync(path.join(process.resourcesPath, "/schemafiles/qlens.json"))) {
156 | fs.unlinkSync(path.join(process.resourcesPath, "/schemafiles/qlens.json"));
157 | console.log('file Deleted');
158 | }
159 | event.sender.send('URI-reply', extractedSchemas)
160 | }
161 | })
162 | });
163 |
164 | autoUpdater.on('update-available', () => {
165 | mainWindow.webContents.send('update_available');
166 | });
167 | autoUpdater.on('update-downloaded', () => {
168 | mainWindow.webContents.send('update_downloaded');
169 | });
170 |
171 | ipcMain.on('restart_app', () => {
172 | autoUpdater.quitAndInstall();
173 | });
174 |
--------------------------------------------------------------------------------
/app/server/schema.js:
--------------------------------------------------------------------------------
1 | const graphql = require('graphql');
2 | var cloneDeep = require('lodash.clonedeep');
3 | const MongoClient = require('mongodb').MongoClient;
4 | var pluralize = require('pluralize');
5 | const { ipcMain } = require('electron')
6 | const { graphqlHTTP } = require('express-graphql');
7 | const express = require('express');
8 | const app = express();
9 |
10 | const {
11 | GraphQLObjectType,
12 | GraphQLString,
13 | GraphQLSchema,
14 | GraphQLID,
15 | GraphQLInt,
16 | GraphQLList,
17 | GraphQLNonNull,
18 | } = graphql;
19 |
20 | const converter = {};
21 | let rootQueryObj = {};
22 |
23 | function addWhiteSpace(number) {
24 | let whiteSpace = ' ';
25 | for (let i = 0; i < number; i += 1) {
26 | whiteSpace += ' ';
27 | }
28 | return whiteSpace;
29 | }
30 |
31 | ipcMain.on('selectedSchemas', (event, arg) => {
32 | const url = arg.uriId;
33 | const data = arg.selectedSchemas;
34 | console.log('IS THIS IT????', data);
35 | // Function for capitalization
36 | const capitalize = (s) => {
37 | if (typeof s !== 'string') return '';
38 | return s.charAt(0).toUpperCase() + s.slice(1);
39 | };
40 | const getGraphQlType = (key, value) => {
41 | switch (true) {
42 | case key.includes('__v'):
43 | break;
44 | case key.includes('_id'):
45 | fieldsObj[key] = { type: GraphQLID };
46 | strFields += `${addWhiteSpace(4)}${key}: { type: GraphQLID },|`;
47 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace(
48 | 4
49 | )}${JSON.stringify(value)},|`;
50 | mutationToString += `${addWhiteSpace(8)}${key}: ${
51 | value.required === false
52 | ? '{ type: GraphQLID }'
53 | : '{ type: new GraphQLNonNull(GraphQLID)}'
54 | },|`;
55 | break;
56 | case value.type.includes('string'):
57 | fieldsObj[key] = { type: GraphQLString };
58 | mutationObj[key] = { type: new GraphQLNonNull(GraphQLString) };
59 | strFields += `${addWhiteSpace(4)}${key}:{ type: GraphQLString },|`;
60 | mutationToString += `${addWhiteSpace(8)}${key}: ${
61 | value.required === false
62 | ? '{ type: GraphQLString }'
63 | : '{ type: new GraphQLNonNull(GraphQLString)}'
64 | },|`;
65 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace(
66 | 4
67 | )}${JSON.stringify(value)},|`;
68 | break;
69 | case value.type.includes('Array'):
70 | fieldsObj[key] = { type: new GraphQLList(GraphQLString) };
71 | mutationObj[key] = {
72 | type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
73 | };
74 | strFields += `${addWhiteSpace(4)}${key}:{ type: GraphQLString },|`;
75 | mutationToString += `${addWhiteSpace(8)}${key}: ${
76 | value.required === false
77 | ? '{ type: new GraphQLList(GraphQLString) }'
78 | : '{ type: new GraphQLNonNull(new GraphQLList(GraphQLString)) }'
79 | },|`;
80 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace(
81 | 4
82 | )}${JSON.stringify(value)},|`;
83 | break;
84 | case value.type.includes('number'):
85 | fieldsObj[key] = { type: GraphQLInt };
86 | mutationObj[key] = { type: new GraphQLNonNull(GraphQLInt) };
87 | strFields += `${addWhiteSpace(4)}${key}:{ type: GraphQLInt },|`;
88 | mutationToString += `${addWhiteSpace(8)}${key}: ${
89 | value.required === false
90 | ? '{ type: GraphQLInt }'
91 | : '{ type: new GraphQLNonNull(GraphQLInt) }'
92 | },|`;
93 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace(
94 | 4
95 | )}${JSON.stringify(value)},|`;
96 | break;
97 | case value.type.includes('Object'):
98 | fieldsObj[key] = { type: GraphQLObjectType };
99 | mutationObj[key] = { type: new GraphQLNonNull(GraphQLObjectType) };
100 | strFields += `${addWhiteSpace(4)}${key}:{ type: GraphQLObjectType },|`;
101 | mutationToString += `${addWhiteSpace(8)}${key}: ${
102 | value.required === false
103 | ? '{ type: GraphQLObjectType }'
104 | : '{ type: new GraphQLNonNull(GraphQLObjectType) }'
105 | },|`;
106 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace(
107 | 4
108 | )}${JSON.stringify(value)},|`;
109 |
110 | break;
111 | default:
112 | console.log(value, 'Nothing Triggered-----');
113 | break;
114 | }
115 | };
116 |
117 | // Storing graphql object types
118 | let obj = {};
119 | // Storing properties of each mongo db schema
120 | let fieldsObj = {};
121 | let strFields = '';
122 | let stringObj = {};
123 | let rootQueryStr = '';
124 | let sendRootQueryObj = {};
125 | let mutationObj = {};
126 | let mutationObjStr = '';
127 | let mutationToString = '';
128 | let mutationSchema;
129 | let buildMongoStr = '';
130 | let mongoSchemaStr = '';
131 |
132 | for (const property in data) {
133 | for (const [key, value] of Object.entries(data[property])) {
134 | getGraphQlType(key, value);
135 | }
136 |
137 |
138 | // Building Selected schemas string to be sent to front end display (codemirror)
139 |
140 | buildMongoStr +=
141 | `const ${property} = new Schema({|` + `${mongoSchemaStr}` + `});||`;
142 |
143 | // Function will be added to rootQuery resolver. Returns documents
144 | async function run(id) {
145 | let dataArr = [];
146 | const client = new MongoClient(url, { useUnifiedTopology: true });
147 | const regex = /\/(\w+)\?/g;
148 | const databaseName = url.match(regex);
149 | const databaseString = databaseName
150 | .join('')
151 | .slice(1, databaseName.join('').length - 1);
152 | try {
153 | await client.connect();
154 | const database = client.db(databaseString);
155 | const collection = database.collection(property);
156 | if (id) {
157 | const query = { _id: id };
158 | const cursor = await collection.findOne(query);
159 | dataArr.push(cursor);
160 | } else {
161 | const cursor = collection.find({});
162 | // print a message if no documents were found
163 | if ((await cursor.count()) === 0) {
164 | console.log('No documents found!');
165 | }
166 | await cursor.forEach((data) => {
167 | dataArr.push(data);
168 | });
169 | }
170 | } finally {
171 | await client.close();
172 | }
173 | // return !id ? dataArr : dataArr.join('');
174 | return dataArr;
175 | }
176 | const deep = cloneDeep(fieldsObj);
177 |
178 | // Dynamically creating graphql object types
179 | obj[capitalize(`${property}Type`)] = new GraphQLObjectType({
180 | name: capitalize(property),
181 | fields: () => deep,
182 | });
183 |
184 | // formats the graphQL schema to send to the front end
185 | stringObj[property] =
186 | `const ${capitalize(`${property}Type`)} = new GraphQLObjectType({\n` +
187 | `${addWhiteSpace(2)}name: '${capitalize(property)}',\n` +
188 | `${addWhiteSpace(2)}fields: () => ({\n` +
189 | `${strFields}` +
190 | `${addWhiteSpace(2)}})${addWhiteSpace(1)}\n` +
191 | `});${addWhiteSpace(1)}\n`;
192 | // dynamically creating fields object in rootQueryType
193 | rootQueryObj[property] = {
194 | type: new GraphQLList(obj[capitalize(`${property}Type`)]),
195 | resolve: function resolve(parent, args) {
196 | // replace with Find() method Based on your file structure/imports
197 | return run();
198 | },
199 | };
200 |
201 | //======================= CREATING MUTATION =======================
202 |
203 | let listOfProperties = '';
204 | Object.keys(data[property])
205 | .filter((key) => key !== '_id' && key !== '__v')
206 | .forEach((el) => {
207 | listOfProperties += `${el}: args.${el},|${addWhiteSpace(10)}`;
208 | });
209 |
210 | mutationObjStr +=
211 | ` add${capitalize(property)} : {|` +
212 | ` type: ${capitalize(`${property}Type`)},|` +
213 | ` args: {|` +
214 | `${mutationToString}` +
215 | `${addWhiteSpace(6)}},|` +
216 | `${addWhiteSpace(6)}resolve(parent, args) {|` +
217 | `${addWhiteSpace(8)}let ${property} = new ${capitalize(property)} ({|` +
218 | `${addWhiteSpace(10)}${JSON.stringify(listOfProperties)}` +
219 | `});|` +
220 | `${addWhiteSpace(8)}return ${property}.save(); |` +
221 | ` } |` +
222 | ` }, |` +
223 | ` delete${pluralize.singular(capitalize(property))} : {|` +
224 | ` type: ${capitalize(`${property}Type`)},|` +
225 | ` args: {|` +
226 | `${mutationToString}` +
227 | `${addWhiteSpace(6)}},|` +
228 | `${addWhiteSpace(6)}resolve(parent, args) {|` +
229 | `${addWhiteSpace(8)} return ${pluralize.singular(
230 | capitalize(`${property}`)
231 | )}.findOneAndDelete({id: args.id})` +
232 | ` } |` +
233 | ` }, |`;
234 |
235 | mutationSchema =
236 | `const Mutation = new GraphQLObjectType({|` +
237 | `${addWhiteSpace(2)}name: 'Mutation', |` +
238 | `${addWhiteSpace(2)}fields: {|${mutationObjStr}|}|});`;
239 |
240 | //=====================================================================
241 |
242 | rootQueryStr +=
243 | ` ${pluralize.singular(property)}: {|` +
244 | `${addWhiteSpace(6)}type: ${capitalize(`${property}Type`)},|` +
245 | `${addWhiteSpace(6)}args: { id: { type: GraphQLID }},|` +
246 | `${addWhiteSpace(6)}resolve(parent, args) {|` +
247 | `${addWhiteSpace(8)}return ${capitalize(
248 | pluralize.singular(property)
249 | )}.findById(args.id);|` +
250 | `${addWhiteSpace(6)}} |` +
251 | `${addWhiteSpace(4)}},|`;
252 |
253 | rootQueryStr +=
254 | `${`${addWhiteSpace(4)}${property}`}: {|` +
255 | `${addWhiteSpace(6)}type: new GraphQLList(${capitalize(
256 | `${property}Type`
257 | )}),|` +
258 | `${addWhiteSpace(6)}resolve(parent, args) {|` +
259 | `${addWhiteSpace(8)}return ${capitalize(
260 | pluralize.singular(property)
261 | )}.find({});|` +
262 | `${addWhiteSpace(6)}}|` +
263 | `${addWhiteSpace(4)}},|`;
264 |
265 | // resetting the fieldsObject
266 | fieldsObj = {};
267 | strFields = '';
268 | mutationToString = '';
269 | mongoSchemaStr = '';
270 | }
271 |
272 | sendRootQueryObj.queries =
273 | `const RootQuery = new GraphQLObjectType({|` +
274 | ` name: "RootQueryType",|` +
275 | ` fields: { | ${rootQueryStr} }|});|`;
276 |
277 | const RootQuery = new GraphQLObjectType({
278 | name: 'RootQueryType',
279 | fields: rootQueryObj,
280 | });
281 |
282 | const Schema = new GraphQLSchema({
283 | query: RootQuery,
284 | })
285 |
286 | app.use(
287 | '/graphql',
288 | graphqlHTTP({ schema: Schema, graphiql: true })
289 | );
290 |
291 | event.sender.send("returnedSchemas", {types: stringObj, queries: sendRootQueryObj, mutations: mutationSchema, mongoSchema: buildMongoStr})
292 | })
293 |
294 | const RootQuery = new GraphQLObjectType({
295 | name: 'RootQueryType',
296 | fields: rootQueryObj,
297 | });
298 |
299 | app.listen(3000, () => console.log('listening on port 3000'));
300 |
301 | module.exports = {
302 | converter,
303 | schema: new GraphQLSchema({
304 | query: RootQuery,
305 | }),
306 | };
307 |
--------------------------------------------------------------------------------
/app/src/App.js:
--------------------------------------------------------------------------------
1 | // Import dependencies
2 | import React from "react";
3 | import Container from "./containers/Container";
4 | import AutoUpdate from "./Components/AutoUpdate";
5 | import "./public/index.css";
6 |
7 | // Create main App component
8 | const App = () => {
9 | return (
10 |