This commit is contained in:
Stefan Stefanov 2018-06-26 14:51:44 +03:00 коммит произвёл GitHub
Родитель 5a5cb59806
Коммит 1fe50f1e61
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 3178 добавлений и 0 удалений

2444
client/README.md Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

32
client/package.json Normal file
Просмотреть файл

@ -0,0 +1,32 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@progress/kendo-data-query": "^1.3.1",
"@progress/kendo-drawing": "^1.5.6",
"@progress/kendo-react-dateinputs": "^1.1.0",
"@progress/kendo-react-dropdowns": "^1.1.0",
"@progress/kendo-react-grid": "^1.1.0",
"@progress/kendo-react-inputs": "^1.1.0",
"@progress/kendo-react-intl": "^1.1.0",
"@progress/kendo-react-pdf": "^1.1.0",
"@progress/kendo-theme-default": "^2.54.0",
"apollo-boost": "^0.1.10",
"bootstrap": "^4.1.1",
"cldr-data": "^32.0.1",
"graphql": "^0.13.2",
"jquery": "^3.3.1",
"popper.js": "^1.14.3",
"react": "^16.4.1",
"react-apollo": "^2.1.6",
"react-dom": "^16.4.1",
"react-scripts": "1.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

Двоичные данные
client/public/favicon.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.8 KiB

42
client/public/index.html Normal file
Просмотреть файл

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Просмотреть файл

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

27
client/src/App.css Normal file
Просмотреть файл

@ -0,0 +1,27 @@
.header {
position: relative;
}
.header h5 {
color: #888888;
display: inline-block;
position: relative;
margin: 0 0 10px;
padding: 0 10px 0 0;
background: #fff;
text-transform: uppercase;
}
.header:before {
content: "";
display: block;
position: absolute;
left: 20px;
right: 20px;
bottom: 18px;
border-top: 1px solid #e8ecef;
}
.k-grid {
margin-bottom: 20px;
}
.k-form fieldset {
border-top-width: 0;
}

51
client/src/App.js Normal file
Просмотреть файл

@ -0,0 +1,51 @@
import React, { Component } from 'react';
import '@progress/kendo-theme-default/dist/all.css';
import 'bootstrap';
import './App.css';
import GridContainer from './components/GridContainer'
import ProductsForm from './components/ProductForm'
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
// apollo client setup
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql'
})
class App extends Component {
constructor(props){
super(props)
this.state = {
selectedItem: {},
inEdit: false
}
}
changeRowSelection = (row) =>{
this.setState({
inEdit: true,
selectedItem: row
})
}
addItem = () =>{
this.setState({
inEdit: false
})
}
render() {
return (
<ApolloProvider client={client}>
<div className="App row m-2">
<ProductsForm selectedItem={this.state.selectedItem} inEdit={this.state.inEdit} addItem={this.addItem}/>
<GridContainer changeRowSelection={this.changeRowSelection} addItem={this.addItem} inEdit={this.state.inEdit}/>
</div>
</ApolloProvider>
);
}
}
export default App;

9
client/src/App.test.js Normal file
Просмотреть файл

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

Просмотреть файл

@ -0,0 +1,66 @@
import React, {Component} from 'react';
import { Grid, GridColumn, GridToolbar } from '@progress/kendo-react-grid';
import MyCommandCell from './MyCommandCell'
import { graphql, compose } from 'react-apollo';
import { getProductsQuery, deleteProductMutation } from '../queries/queries';
class GridContainer extends Component {
constructor(props){
super(props)
this.remove = this.remove.bind(this);
this.CommandCell = MyCommandCell(this.remove);
}
remove(dataItem) {
this.props.deleteProductMutation({
variables: {
ProductID: dataItem.ProductID,
},
refetchQueries: [{ query: getProductsQuery }]
});
}
handleRowClick = (event) => {
this.props.changeRowSelection(event.dataItem)
}
handleAddItem = () => {
this.props.addItem()
}
render() {
return (
<div className="grid-container col-md-8 col-sm-12 col-xs-12">
<div className="header">
<h5>Data</h5>
</div>
<Grid data={this.props.getProductsQuery.loading === true ? [] : this.props.getProductsQuery.products}
onRowClick={this.handleRowClick}
style={{ maxHeight: "600px" }}
>
<GridToolbar>
<button
title="Add new"
className="k-button k-primary"
onClick={this.handleAddItem}
disabled={!this.props.inEdit}
>Add new
</button>
</GridToolbar>
<GridColumn field="ProductID" title="ID" width="300"/>
<GridColumn field="ProductName" title="Product Name"/>
<GridColumn field="UnitPrice" title="Unit Price" width="150px"/>
<GridColumn field="UnitsInStock" title="Units in Stock" width="150px"/>
<GridColumn cell={this.CommandCell} width="120px" />
</Grid>
</div>
);
}
}
export default compose(
graphql(getProductsQuery, { name: "getProductsQuery" }),
graphql(deleteProductMutation, { name: "deleteProductMutation" })
)(GridContainer);

Просмотреть файл

@ -0,0 +1,18 @@
import React from 'react';
import { GridCell } from '@progress/kendo-react-grid';
export default function MyCommandCell(remove) {
return class extends GridCell {
render() {
return (
<td>
<button
className="k-button k-grid-remove-command"
onClick={(e) => window.confirm('Confirm deleting: ' + this.props.dataItem.ProductName) && remove(this.props.dataItem)}>
Remove
</button>
</td>
);
}
}
};

Просмотреть файл

@ -0,0 +1,128 @@
import React, {Component} from 'react';
import { NumericTextBox } from '@progress/kendo-react-inputs';
import { graphql, compose } from 'react-apollo';
import { addProductMutation, getProductsQuery, updateProductMutation} from '../queries/queries';
class ProductsForm extends Component {
constructor(props) {
super(props)
this.state = {
ProductName: "",
UnitPrice: 0,
UnitsInStock: 0,
internalUpdate: false
}
this.handleSubmit = this.handleSubmit.bind(this);
}
static getDerivedStateFromProps(nextProps, state) {
if(state.internalUpdate){
state.internalUpdate = false
return state
}
if(nextProps.inEdit){
return {
ProductName:nextProps.selectedItem.ProductName,
UnitPrice:nextProps.selectedItem.UnitPrice,
UnitsInStock:nextProps.selectedItem.UnitsInStock,
}
}
else{
return {
ProductName:"",
UnitPrice:0,
UnitsInStock:0,
}
}
}
handleSubmit = (e) => {
e.preventDefault()
if(this.props.inEdit){
this.props.updateProductMutation({
variables: {
ProductID: this.props.selectedItem.ProductID,
ProductName: this.state.ProductName,
UnitPrice: this.state.UnitPrice,
UnitsInStock: this.state.UnitsInStock
},
refetchQueries: [{ query: getProductsQuery }]
});
}
else{
this.props.addProductMutation({
variables: {
ProductName: this.state.ProductName,
UnitPrice: this.state.UnitPrice,
UnitsInStock: this.state.UnitsInStock
},
refetchQueries: [{ query: getProductsQuery }]
});
}
this.props.addItem()
this.setState({
ProductName: "",
UnitPrice: 0,
UnitsInStock: 0,
internalUpdate: true
})
}
render() {
return (
<div className="col-md-4 col-sm-12 col-xs-12">
<div className="header">
<h5>Product</h5>
</div>
<form className="k-form" onSubmit={this.handleSubmit}>
<fieldset>
<label className="k-form-field">
<span>Product Name</span>
<input
required
className="k-textbox"
placeholder="Product Name"
value={this.state.ProductName}
onChange={(e) => {
this.setState({ProductName: e.target.value, internalUpdate: true})
}}/>
</label>
<label className="k-form-field">
<span>Unit Price</span>
<NumericTextBox
style={{width: "100%"}}
value={this.state.UnitPrice}
onChange={(e) => {
this.setState({UnitPrice: e.value, internalUpdate: true})
}}/>
</label>
<label className="k-form-field">
<span>Units in Stock</span>
<NumericTextBox
value={this.state.UnitsInStock}
style={{width: "100%"}}
onChange={(e) => {
this.setState({UnitsInStock: e.value, internalUpdate: true})
}}/>
</label>
</fieldset>
<div className="text-right">
<button type="button" className="k-button k-primary" type="submit">{!this.props.inEdit
? "Add new product"
: "Update"
}</button>
</div>
</form>
</div>
)
}
}
export default compose(
graphql(addProductMutation, {name: "addProductMutation"}),
graphql(getProductsQuery, { name: "getProductsQuery" }),
graphql(updateProductMutation, { name: "updateProductMutation" })
)(ProductsForm);

5
client/src/index.css Normal file
Просмотреть файл

@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

6
client/src/index.js Normal file
Просмотреть файл

@ -0,0 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

7
client/src/logo.svg Normal file
Просмотреть файл

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 2.6 KiB

Просмотреть файл

@ -0,0 +1,39 @@
import { gql } from 'apollo-boost';
const getProductsQuery = gql `
{
products {
ProductID
ProductName
UnitPrice
UnitsInStock
}
}
`;
const addProductMutation = gql`
mutation AddProduct($ProductName: String!, $UnitPrice: Float!, $UnitsInStock: Float!){
AddProduct(ProductName: $ProductName, UnitPrice: $UnitPrice, UnitsInStock: $UnitsInStock){
ProductName
ProductID
}
}
`;
const updateProductMutation = gql`
mutation UpdateProduct($ProductID: ID!, $ProductName: String! ,$UnitPrice: Float!, $UnitsInStock: Float!){
UpdateProduct(ProductID: $ProductID, ProductName: $ProductName, UnitPrice: $UnitPrice, UnitsInStock: $UnitsInStock){
ProductID
}
}
`;
const deleteProductMutation = gql`
mutation DeleteProduct($ProductID: ID!){
DeleteProduct(ProductID: $ProductID){
ProductID
}
}
`;
export { getProductsQuery, addProductMutation, deleteProductMutation, updateProductMutation };

19
server/app.js Normal file
Просмотреть файл

@ -0,0 +1,19 @@
const express = require('express');
const graphqlHTTP = require('express-graphql');
const schema = require('./schema/schema');
const cors = require('cors')
const app = express();
// allow cross-origin requests
app.use(cors());
// bind express with graphql
app.use('/graphql', graphqlHTTP({
schema
}));
app.listen(4000, () => {
console.log('now listening for requests on port 4000');
});

18
server/package.json Normal file
Просмотреть файл

@ -0,0 +1,18 @@
{
"name": "kendo-graphql",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.4",
"express": "^4.16.3",
"express-graphql": "^0.6.12",
"graphql": "^0.13.2",
"uuid": "^3.2.1"
}
}

252
server/schema/schema.js Normal file
Просмотреть файл

@ -0,0 +1,252 @@
const graphql = require('graphql');
const uuidv1 = require('uuid/v1');
const { GraphQLObjectType,
GraphQLString,
GraphQLSchema,
GraphQLID,
GraphQLFloat,
GraphQLList,
GraphQLNonNull } = graphql;
// products data
var products = [{
'ProductID' : 1,
'ProductName' : 'Chai',
'SupplierID' : 1,
'CategoryID' : 1,
'QuantityPerUnit' : '10 boxes x 20 bags',
'UnitPrice' : 18.0000,
'UnitsInStock' : 39,
'UnitsOnOrder' : 0,
'ReorderLevel' : 10,
'Discontinued' : false,
'Category' : {
'CategoryID' : 1,
'CategoryName' : 'Beverages',
'Description' : 'Soft drinks, coffees, teas, beers, and ales'
}
}, {
'ProductID' : 2,
'ProductName' : 'Chang',
'SupplierID' : 1,
'CategoryID' : 1,
'QuantityPerUnit' : '24 - 12 oz bottles',
'UnitPrice' : 19.0000,
'UnitsInStock' : 17,
'UnitsOnOrder' : 40,
'ReorderLevel' : 25,
'Discontinued' : false,
'Category' : {
'CategoryID' : 1,
'CategoryName' : 'Beverages',
'Description' : 'Soft drinks, coffees, teas, beers, and ales'
}
}, {
'ProductID' : 3,
'ProductName' : 'Aniseed Syrup',
'SupplierID' : 1,
'CategoryID' : 2,
'QuantityPerUnit' : '12 - 550 ml bottles',
'UnitPrice' : 10.0000,
'UnitsInStock' : 13,
'UnitsOnOrder' : 70,
'ReorderLevel' : 25,
'Discontinued' : false,
'Category' : {
'CategoryID' : 2,
'CategoryName' : 'Condiments',
'Description' : 'Sweet and savory sauces, relishes, spreads, and seasonings'
}
}, {
'ProductID' : 4,
'ProductName' : 'Chef Anton\'s Cajun Seasoning',
'SupplierID' : 2,
'CategoryID' : 2,
'QuantityPerUnit' : '48 - 6 oz jars',
'UnitPrice' : 22.0000,
'UnitsInStock' : 53,
'UnitsOnOrder' : 0,
'ReorderLevel' : 0,
'Discontinued' : false,
'Category' : {
'CategoryID' : 2,
'CategoryName' : 'Condiments',
'Description' : 'Sweet and savory sauces, relishes, spreads, and seasonings'
}
}, {
'ProductID' : 5,
'ProductName' : 'Chef Anton\'s Gumbo Mix',
'SupplierID' : 2,
'CategoryID' : 2,
'QuantityPerUnit' : '36 boxes',
'UnitPrice' : 21.3500,
'UnitsInStock' : 0,
'UnitsOnOrder' : 0,
'ReorderLevel' : 0,
'Discontinued' : true,
'Category' : {
'CategoryID' : 2,
'CategoryName' : 'Condiments',
'Description' : 'Sweet and savory sauces, relishes, spreads, and seasonings'
}
}, {
'ProductID' : 6,
'ProductName' : 'Grandma\'s Boysenberry Spread',
'SupplierID' : 3,
'CategoryID' : 2,
'QuantityPerUnit' : '12 - 8 oz jars',
'UnitPrice' : 25.0000,
'UnitsInStock' : 120,
'UnitsOnOrder' : 0,
'ReorderLevel' : 25,
'Discontinued' : false,
'Category' : {
'CategoryID' : 2,
'CategoryName' : 'Condiments',
'Description' : 'Sweet and savory sauces, relishes, spreads, and seasonings'
}
}, {
'ProductID' : 7,
'ProductName' : 'Uncle Bob\'s Organic Dried Pears',
'SupplierID' : 3,
'CategoryID' : 7,
'QuantityPerUnit' : '12 - 1 lb pkgs.',
'UnitPrice' : 30.0000,
'UnitsInStock' : 15,
'UnitsOnOrder' : 0,
'ReorderLevel' : 10,
'Discontinued' : false,
'Category' : {
'CategoryID' : 7,
'CategoryName' : 'Produce',
'Description' : 'Dried fruit and bean curd'
}
}, {
'ProductID' : 8,
'ProductName' : 'Northwoods Cranberry Sauce',
'SupplierID' : 3,
'CategoryID' : 2,
'QuantityPerUnit' : '12 - 12 oz jars',
'UnitPrice' : 40.0000,
'UnitsInStock' : 6,
'UnitsOnOrder' : 0,
'ReorderLevel' : 0,
'Discontinued' : false,
'Category' : {
'CategoryID' : 2,
'CategoryName' : 'Condiments',
'Description' : 'Sweet and savory sauces, relishes, spreads, and seasonings'
}
}, {
'ProductID' : 9,
'ProductName' : 'Mishi Kobe Niku',
'SupplierID' : 4,
'CategoryID' : 6,
'QuantityPerUnit' : '18 - 500 g pkgs.',
'UnitPrice' : 97.0000,
'UnitsInStock' : 29,
'UnitsOnOrder' : 0,
'ReorderLevel' : 0,
'Discontinued' : true,
'Category' : {
'CategoryID' : 6,
'CategoryName' : 'Meat/Poultry',
'Description' : 'Prepared meats'
}
}, {
'ProductID' : 10,
'ProductName' : 'Ikura',
'SupplierID' : 4,
'CategoryID' : 8,
'QuantityPerUnit' : '12 - 200 ml jars',
'UnitPrice' : 31.0000,
'UnitsInStock' : 31,
'UnitsOnOrder' : 0,
'ReorderLevel' : 0,
'Discontinued' : false,
'Category' : {
'CategoryID' : 8,
'CategoryName' : 'Seafood',
'Description' : 'Seaweed and fish'
}
}];
const ProductType = new GraphQLObjectType({
name: 'Product',
fields: ( ) => ({
ProductID: { type: GraphQLID },
ProductName: { type: GraphQLString },
UnitPrice: { type: GraphQLFloat },
UnitsInStock: { type: GraphQLFloat }
})
});
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
products:{
type: new GraphQLList(ProductType),
resolve(parent, args){
return products
}
}
}
});
const Mutation = new GraphQLObjectType({
name:'Mutation',
fields:{
AddProduct:{
type: ProductType,
args:{
ProductName: { type: new GraphQLNonNull(GraphQLString) },
UnitPrice: { type: new GraphQLNonNull(GraphQLFloat) },
UnitsInStock: { type: new GraphQLNonNull(GraphQLFloat) },
},
resolve(parent, args) {
let newProduct = {
ProductID: uuidv1(),
ProductName: args.ProductName,
UnitsInStock: args.UnitsInStock,
UnitPrice: args.UnitPrice
}
products.unshift(newProduct)
return args
}
},
DeleteProduct:{
type: ProductType,
args:{
ProductID: { type: new GraphQLNonNull(GraphQLID) }
},
resolve(parent, args) {
let index = products.findIndex(product => product.ProductID == args.ProductID);
products.splice(index, 1)
return args.ProductID
}
},
UpdateProduct:{
type: ProductType,
args:{
ProductID: { type: new GraphQLNonNull(GraphQLID) },
ProductName: { type: new GraphQLNonNull(GraphQLString) },
UnitPrice: { type: new GraphQLNonNull(GraphQLFloat) },
UnitsInStock: { type: new GraphQLNonNull(GraphQLFloat) },
},
resolve(parent, args) {
let index = products.findIndex(product => product.ProductID == args.ProductID);
products[index].ProductName = args.ProductName
products[index].UnitsInStock = args.UnitsInStock
products[index].UnitPrice = args.UnitPrice
return args.ProductID
}
}
}
})
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutation
});