From debca3352b148903b2f1d21029deba69c50aff2a Mon Sep 17 00:00:00 2001 From: Fre Timmerman Date: Mon, 1 Feb 2021 14:36:26 +0100 Subject: [PATCH 1/5] done 1 --- package.json | 14 +++++++++----- schema.gql | 16 ++++++++++++++++ src/data.js | 4 ++-- src/index.js | 27 ++++++++++++++------------- src/resolvers.js | 31 +++++++++++++++++++++++++++++++ src/resolvers.ts | 14 ++++++++++++++ src/typeDefs.js | 27 +++++++++++++++++++++++++++ src/typeDefs.ts | 15 +++++++++++++++ tsconfig.json | 13 +++++++++++++ 9 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 schema.gql create mode 100644 src/resolvers.js create mode 100644 src/resolvers.ts create mode 100644 src/typeDefs.js create mode 100644 src/typeDefs.ts create mode 100644 tsconfig.json diff --git a/package.json b/package.json index 1ebadab..e3fbd58 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,24 @@ { "name": "food-graphql-server", + "type": "module", "version": "1.0.0", "description": "The GraphQL server to reserve food.", "main": "index.js", "scripts": { - "start": "nodemon src/index.js" + "start": "tsc-watch --onsuccess \"node ./src/index.js\"" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { - "apollo-server": "^2.3.1", - "graphql": "^14.1.1", + "apollo-server": "^2.19.2", + "class-validator": "^0.13.1", + "graphql": "^15.5.0", + "reflect-metadata": "^0.1.13", + "type-graphql": "^1.1.1", "uuid": "^3.3.2" }, "devDependencies": { - "nodemon": "^1.18.9" + "tsc-watch": "^4.2.9" } -} +} \ No newline at end of file diff --git a/schema.gql b/schema.gql new file mode 100644 index 0000000..8dd1e45 --- /dev/null +++ b/schema.gql @@ -0,0 +1,16 @@ +# ----------------------------------------------- +# !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!! +# !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! +# ----------------------------------------------- + +type Query { + """Get all the stores """ + stores: [Store!]! +} + +type Store { + id: ID! + + """The name of the store""" + name: String! +} diff --git a/src/data.js b/src/data.js index a7eca1e..ca400a1 100644 --- a/src/data.js +++ b/src/data.js @@ -1,8 +1,8 @@ -const uuid = require('uuid'); +import * as uuid from 'uuid'; const stores = [ { - id: 'cc406ed9-fc02-4185-b073-8c12b61b5c79' + id: 'cc406ed9-fc02-4185-b073-8c12b61b5c79', name: 'Den Olijfboom', }, { diff --git a/src/index.js b/src/index.js index 6f5302b..ff05f4b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,10 @@ -const { ApolloServer, gql } = require('apollo-server'); + +import "reflect-metadata"; +import { buildSchema } from "type-graphql"; + +import * as path from "path"; +import { ApolloServer, gql } from "apollo-server"; +import { StoreResolver } from './resolvers.js'; // ⚽️ Goal // -------- @@ -19,23 +25,18 @@ const { ApolloServer, gql } = require('apollo-server'); // 4) Create a resolver function that returns the list of stores. // 5) Try out the GraphQL query in the GraphQL Playground (🚀 http://localhost:4000/) -// Type definitions define the "shape" of your data and specify -// which ways the data can be fetched from the GraphQL server. -const typeDefs = gql` - type Query { - test: String - } -`; -// Resolvers define the technique for fetching the types in the -// schema. -const resolvers = { -}; + +// create the schema using TypeGraphQL, pass the resolver +const schema = await buildSchema({ + resolvers: [StoreResolver], + emitSchemaFile: path.resolve(".", "schema.gql"), +}); // In the most basic sense, the ApolloServer can be started // by passing type definitions (typeDefs) and the resolvers // responsible for fetching the data for those types. -const server = new ApolloServer({ typeDefs, resolvers }); +const server = new ApolloServer({ schema }); // This `listen` method launches a web-server. Existing apps // can utilize middleware options. diff --git a/src/resolvers.js b/src/resolvers.js new file mode 100644 index 0000000..7753146 --- /dev/null +++ b/src/resolvers.js @@ -0,0 +1,31 @@ +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +import "reflect-metadata"; +import { Resolver, Query } from "type-graphql"; +import { Store } from "./typeDefs.js"; +import { getStores } from "./data.js"; +let StoreResolver = class StoreResolver { + constructor() { + this.storeCollection = getStores(); + } + async stores() { + return await this.storeCollection; + } +}; +__decorate([ + Query(returns => [Store], { description: "Get all the stores " }), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], StoreResolver.prototype, "stores", null); +StoreResolver = __decorate([ + Resolver() +], StoreResolver); +export { StoreResolver }; diff --git a/src/resolvers.ts b/src/resolvers.ts new file mode 100644 index 0000000..125227d --- /dev/null +++ b/src/resolvers.ts @@ -0,0 +1,14 @@ +import "reflect-metadata"; +import { Resolver, Query } from "type-graphql"; +import { Store } from "./typeDefs.js"; +import { getStores } from "./data.js"; + +@Resolver() +export class StoreResolver { + private storeCollection: Store[] = getStores(); + + @Query(returns => [Store], { description: "Get all the stores " }) + async stores(): Promise { + return await this.storeCollection; + } +} diff --git a/src/typeDefs.js b/src/typeDefs.js new file mode 100644 index 0000000..4619400 --- /dev/null +++ b/src/typeDefs.js @@ -0,0 +1,27 @@ +// Type definitions define the "shape" of your data and specify +// which ways the data can be fetched from the GraphQL server. +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +import "reflect-metadata"; +import { ObjectType, Field, ID } from "type-graphql"; +let Store = class Store { +}; +__decorate([ + Field(() => ID), + __metadata("design:type", String) +], Store.prototype, "id", void 0); +__decorate([ + Field(() => String, { description: "The name of the store" }), + __metadata("design:type", String) +], Store.prototype, "name", void 0); +Store = __decorate([ + ObjectType() +], Store); +export { Store }; diff --git a/src/typeDefs.ts b/src/typeDefs.ts new file mode 100644 index 0000000..2db0d04 --- /dev/null +++ b/src/typeDefs.ts @@ -0,0 +1,15 @@ +// Type definitions define the "shape" of your data and specify +// which ways the data can be fetched from the GraphQL server. + +import "reflect-metadata"; +import { ObjectType, Field, ID } from "type-graphql"; + + +@ObjectType() +export class Store { + @Field(() => ID) + id: string; + + @Field(() => String, { description: "The name of the store" }) + name: string; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7ede0a2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "esnext", + "lib": [ + "es2018", + "esnext.asynciterable" + ], + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} \ No newline at end of file From 40d325a318261bf3d1706188b1509fa9cc51090e Mon Sep 17 00:00:00 2001 From: Fre Timmerman Date: Tue, 2 Feb 2021 11:34:34 +0100 Subject: [PATCH 2/5] done 2 --- package.json | 5 +++-- schema.gql | 23 +++++++++++++++++++---- src/data.js | 23 +++++++++++++++++++---- src/resolvers.js | 41 +++++++++++++++++++++++++++++++++++++---- src/resolvers.ts | 37 +++++++++++++++++++++++++++++++++---- src/typeDefs.js | 48 +++++++++++++++++++++++++++++++++++++++++++++--- src/typeDefs.ts | 29 +++++++++++++++++++++++++---- tsconfig.json | 3 ++- 8 files changed, 183 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index e3fbd58..f273480 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,10 @@ "graphql": "^15.5.0", "reflect-metadata": "^0.1.13", "type-graphql": "^1.1.1", - "uuid": "^3.3.2" + "uuid": "^3.3.2", + "yup": "^0.32.8" }, "devDependencies": { "tsc-watch": "^4.2.9" } -} \ No newline at end of file +} diff --git a/schema.gql b/schema.gql index 8dd1e45..a36a933 100644 --- a/schema.gql +++ b/schema.gql @@ -3,14 +3,29 @@ # !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! # ----------------------------------------------- +type Mutation { + """Create a new store""" + createStore(input: StoreInput!): Store! +} + type Query { - """Get all the stores """ + """Get all the stores""" stores: [Store!]! } type Store { - id: ID! - - """The name of the store""" + city: String! + id: String! name: String! + number: Int! + postalCode: String! + street: String! +} + +input StoreInput { + city: String! + name: String! + number: Int! + postalCode: String! + street: String! } diff --git a/src/data.js b/src/data.js index ca400a1..214677b 100644 --- a/src/data.js +++ b/src/data.js @@ -1,18 +1,33 @@ -import * as uuid from 'uuid'; +import uuid from 'uuid'; const stores = [ { + city: 'Aalst', id: 'cc406ed9-fc02-4185-b073-8c12b61b5c79', name: 'Den Olijfboom', + number: 38, + postalCode: '9300', + street: 'Molenstraat', }, { + city: 'Aalst', id: '5f2919aa-333a-4745-8166-3002ab30de0e', - name: 'Pizza Talia' + name: 'Pizza Talia', + number: 147, + postalCode: '9300', + street: 'Sint Jobstraat', } ]; -export function createStore({ name }) { - const newStore = { id: uuid(), name }; +export function createStore({ city, name, number, postalCode, street }) { + const newStore = { + city, + id: uuid(), + name, + number, + postalCode, + street, + }; stores.push(newStore); return newStore; } diff --git a/src/resolvers.js b/src/resolvers.js index 7753146..12d032f 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -7,10 +7,24 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; import "reflect-metadata"; -import { Resolver, Query } from "type-graphql"; -import { Store } from "./typeDefs.js"; -import { getStores } from "./data.js"; +import { Resolver, Query, Mutation, Arg } from "type-graphql"; +import { Store, StoreInput } from "./typeDefs.js"; +import { createStore, getStores } from "./data.js"; +import * as yup from 'yup'; +import { UserInputError } from "apollo-server"; +// define input validations +const createStoreSchema = yup.object() + .shape({ + city: yup.string().max(255).required(), + name: yup.string().max(255).required(), + number: yup.number().required().positive().integer(), + postalCode: yup.string().required().max(10), + street: yup.string().required().max(255), +}); let StoreResolver = class StoreResolver { constructor() { this.storeCollection = getStores(); @@ -18,13 +32,32 @@ let StoreResolver = class StoreResolver { async stores() { return await this.storeCollection; } + async createStore(input) { + // check validity + return await createStoreSchema + .validate(input) + .then(validData => { + // put in db with data.js and return the value to grapqhl + return createStore(validData); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + } }; __decorate([ - Query(returns => [Store], { description: "Get all the stores " }), + Query(returns => [Store], { description: "Get all the stores" }), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], StoreResolver.prototype, "stores", null); +__decorate([ + Mutation(() => Store, { description: "Create a new store" }), + __param(0, Arg("input")), + __metadata("design:type", Function), + __metadata("design:paramtypes", [StoreInput]), + __metadata("design:returntype", Promise) +], StoreResolver.prototype, "createStore", null); StoreResolver = __decorate([ Resolver() ], StoreResolver); diff --git a/src/resolvers.ts b/src/resolvers.ts index 125227d..f37789c 100644 --- a/src/resolvers.ts +++ b/src/resolvers.ts @@ -1,14 +1,43 @@ import "reflect-metadata"; -import { Resolver, Query } from "type-graphql"; -import { Store } from "./typeDefs.js"; -import { getStores } from "./data.js"; +import { Resolver, Query, Mutation, Arg } from "type-graphql"; +import { Store, StoreInput } from "./typeDefs.js"; +import { createStore, getStores } from "./data.js"; +import * as yup from 'yup'; +import { UserInputError } from "apollo-server"; + +// define input validations +const createStoreSchema = yup.object() + .shape({ + city: yup.string().max(255).required(), + name: yup.string().max(255).required(), + number: yup.number().required().positive().integer(), + postalCode: yup.string().required().max(10), + street: yup.string().required().max(255), + }); + @Resolver() export class StoreResolver { private storeCollection: Store[] = getStores(); - @Query(returns => [Store], { description: "Get all the stores " }) + @Query(returns => [Store], { description: "Get all the stores" }) async stores(): Promise { return await this.storeCollection; } + + @Mutation(() => Store, { description: "Create a new store" }) + async createStore(@Arg("input") input: StoreInput) { + // check validity + return await createStoreSchema + .validate(input) + .then(validData => { + // put in db with data.js and return the value to grapqhl + return createStore(validData); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + + } + } diff --git a/src/typeDefs.js b/src/typeDefs.js index 4619400..11e624a 100644 --- a/src/typeDefs.js +++ b/src/typeDefs.js @@ -10,18 +10,60 @@ var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; import "reflect-metadata"; -import { ObjectType, Field, ID } from "type-graphql"; +import { ObjectType, Field, InputType, Int } from "type-graphql"; let Store = class Store { }; __decorate([ - Field(() => ID), + Field(), __metadata("design:type", String) ], Store.prototype, "id", void 0); __decorate([ - Field(() => String, { description: "The name of the store" }), + Field(), __metadata("design:type", String) ], Store.prototype, "name", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Store.prototype, "city", void 0); +__decorate([ + Field(type => Int), + __metadata("design:type", Number) +], Store.prototype, "number", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Store.prototype, "postalCode", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Store.prototype, "street", void 0); Store = __decorate([ ObjectType() ], Store); export { Store }; +let StoreInput = class StoreInput { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], StoreInput.prototype, "name", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], StoreInput.prototype, "city", void 0); +__decorate([ + Field(type => Int), + __metadata("design:type", Number) +], StoreInput.prototype, "number", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], StoreInput.prototype, "postalCode", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], StoreInput.prototype, "street", void 0); +StoreInput = __decorate([ + InputType() +], StoreInput); +export { StoreInput }; diff --git a/src/typeDefs.ts b/src/typeDefs.ts index 2db0d04..d3ef753 100644 --- a/src/typeDefs.ts +++ b/src/typeDefs.ts @@ -2,14 +2,35 @@ // which ways the data can be fetched from the GraphQL server. import "reflect-metadata"; -import { ObjectType, Field, ID } from "type-graphql"; +import { ObjectType, Field, ID, InputType, Int } from "type-graphql"; +import { number } from "yup/lib/locale"; @ObjectType() export class Store { - @Field(() => ID) + @Field() id: string; - - @Field(() => String, { description: "The name of the store" }) + @Field() name: string; + @Field() + city: string; + @Field(type => Int) + number: number; + @Field() + postalCode: string + @Field() + street: string +} +@InputType() +export class StoreInput { + @Field() + name: string; + @Field() + city: string; + @Field(type => Int) + number: number; + @Field() + postalCode: string + @Field() + street: string } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 7ede0a2..80c8404 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ ], "moduleResolution": "node", "experimentalDecorators": true, - "emitDecoratorMetadata": true + "emitDecoratorMetadata": true, + "allowSyntheticDefaultImports": true } } \ No newline at end of file From 54b2e99fd22c381c12ecf409d026c7a4dfe46f70 Mon Sep 17 00:00:00 2001 From: Fre Timmerman Date: Tue, 2 Feb 2021 15:08:42 +0100 Subject: [PATCH 3/5] done 3 --- schema.gql | 37 ++++++++++++++++++++++++------------- src/data.js | 44 +++++++++++++++++++++++++++++++++++++++++++- src/index.js | 1 + src/resolvers.js | 48 +++++++++++++++++++++++++++++++++++++++--------- src/resolvers.ts | 40 +++++++++++++++++++++++++++++++--------- src/typeDefs.js | 28 +++++++++++++++++++++++++++- src/typeDefs.ts | 17 +++++++++++++++-- 7 files changed, 180 insertions(+), 35 deletions(-) diff --git a/schema.gql b/schema.gql index a36a933..1d64a07 100644 --- a/schema.gql +++ b/schema.gql @@ -5,27 +5,38 @@ type Mutation { """Create a new store""" - createStore(input: StoreInput!): Store! + createStore(input: StoreInput): Store +} + +type Product { + description: String + id: String + name: String + price: Float } type Query { + """Get a specific store""" + store(id: String): Store + """Get all the stores""" - stores: [Store!]! + stores: [Store] } type Store { - city: String! - id: String! - name: String! - number: Int! - postalCode: String! - street: String! + city: String + id: String + name: String + number: Int + postalCode: String + products: [Product] + street: String } input StoreInput { - city: String! - name: String! - number: Int! - postalCode: String! - street: String! + city: String + name: String + number: Int + postalCode: String + street: String } diff --git a/src/data.js b/src/data.js index 214677b..18fcb66 100644 --- a/src/data.js +++ b/src/data.js @@ -19,6 +19,39 @@ const stores = [ } ]; +const products = [{ + description: 'Een broodje om u tegen te zeggen. Lekker, lekker, lekker!', + id: '9e3de707-8e96-45c8-8c1a-75d79fe74768', + name: 'Scampi Manis', + price: 5, + storeId: 'cc406ed9-fc02-4185-b073-8c12b61b5c79', +}, { + description: 'Eentje met kaas en fricandon, voor den groten honger.', + id: '1535f2a6-4341-4db0-a9c8-455c2347dcaf', + name: 'Gitaar', + price: 5, + storeId: 'cc406ed9-fc02-4185-b073-8c12b61b5c79', +}, { + description: 'Tomatensaus, kaas, ham, salami, champignons, paprika', + id: '5bb3fbcc-7ec2-44fe-a04b-a0251cecf1e6', + name: '4 Seizoenen Large', + price: 17, + storeId: '5f2919aa-333a-4745-8166-3002ab30de0e', +}, { + description: 'Bolognaisesaus, kaas, paprika, ham, salami, champignons', + id: '037e74f6-ae73-47a3-9acf-d1de0cdcd565', + name: 'Calzone Large', + price: 17, + storeId: '5f2919aa-333a-4745-8166-3002ab30de0e', +}, { + description: 'Tomatensaus, pepperoni, rode ui, paprika gehakt, jalapenos', + id: 'e8619184-2e86-4db5-b8ba-596720006a0f', + name: 'Hot \'n Spicy', + price: 20, + storeId: '5f2919aa-333a-4745-8166-3002ab30de0e', +}]; + + export function createStore({ city, name, number, postalCode, street }) { const newStore = { city, @@ -34,4 +67,13 @@ export function createStore({ city, name, number, postalCode, street }) { export function getStores() { return stores; -} \ No newline at end of file +} + +export function getStore(storeId) { + return stores.find(store => store.id === storeId); +} + +export function getStoreProducts(storeId) { + console.log(storeId); + return products.filter(prod => prod.storeId === storeId) +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index ff05f4b..49551f1 100644 --- a/src/index.js +++ b/src/index.js @@ -30,6 +30,7 @@ import { StoreResolver } from './resolvers.js'; // create the schema using TypeGraphQL, pass the resolver const schema = await buildSchema({ resolvers: [StoreResolver], + nullableByDefault: true, emitSchemaFile: path.resolve(".", "schema.gql"), }); diff --git a/src/resolvers.js b/src/resolvers.js index 12d032f..55b20b0 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -11,9 +11,9 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import "reflect-metadata"; -import { Resolver, Query, Mutation, Arg } from "type-graphql"; -import { Store, StoreInput } from "./typeDefs.js"; -import { createStore, getStores } from "./data.js"; +import { Resolver, Query, Mutation, Arg, FieldResolver, Root } from "type-graphql"; +import { Store, StoreInput, Product } from "./typeDefs.js"; +import { createStore, getStores, getStore, getStoreProducts } from "./data.js"; import * as yup from 'yup'; import { UserInputError } from "apollo-server"; // define input validations @@ -25,12 +25,28 @@ const createStoreSchema = yup.object() postalCode: yup.string().required().max(10), street: yup.string().required().max(255), }); +const getStoreSchema = yup.object() + .shape({ + id: yup.string().length(36).required() +}); let StoreResolver = class StoreResolver { - constructor() { - this.storeCollection = getStores(); - } async stores() { - return await this.storeCollection; + return await getStores(); + } + async store(id /* ,@Arg("withProducts", { nullable: true }) withProducts: boolean*/) { + // check validity + return await getStoreSchema + .validate({ id: id }) + .then(validData => { + return getStore(validData.id); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + } + //Extend the Store type with a list of its products. + async products(store) { + return await getStoreProducts(store.id); } async createStore(input) { // check validity @@ -46,11 +62,25 @@ let StoreResolver = class StoreResolver { } }; __decorate([ - Query(returns => [Store], { description: "Get all the stores" }), + Query(() => [Store], { description: "Get all the stores" }), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], StoreResolver.prototype, "stores", null); +__decorate([ + Query(() => Store, { description: "Get a specific store" }), + __param(0, Arg("id")), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String /* ,@Arg("withProducts", { nullable: true }) withProducts: boolean*/]), + __metadata("design:returntype", Promise) +], StoreResolver.prototype, "store", null); +__decorate([ + FieldResolver(() => [Product]), + __param(0, Root()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Store]), + __metadata("design:returntype", Promise) +], StoreResolver.prototype, "products", null); __decorate([ Mutation(() => Store, { description: "Create a new store" }), __param(0, Arg("input")), @@ -59,6 +89,6 @@ __decorate([ __metadata("design:returntype", Promise) ], StoreResolver.prototype, "createStore", null); StoreResolver = __decorate([ - Resolver() + Resolver(() => Store) ], StoreResolver); export { StoreResolver }; diff --git a/src/resolvers.ts b/src/resolvers.ts index f37789c..2a52c89 100644 --- a/src/resolvers.ts +++ b/src/resolvers.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; -import { Resolver, Query, Mutation, Arg } from "type-graphql"; -import { Store, StoreInput } from "./typeDefs.js"; -import { createStore, getStores } from "./data.js"; +import { Resolver, Query, Mutation, Arg, FieldResolver, Root } from "type-graphql"; +import { Store, StoreInput, Product } from "./typeDefs.js"; +import { createStore, getStores, getStore, getStoreProducts } from "./data.js"; import * as yup from 'yup'; import { UserInputError } from "apollo-server"; @@ -15,18 +15,41 @@ const createStoreSchema = yup.object() street: yup.string().required().max(255), }); +const getStoreSchema = yup.object() + .shape({ + id: yup.string().length(36).required() + }) -@Resolver() + +@Resolver(() => Store) export class StoreResolver { - private storeCollection: Store[] = getStores(); - @Query(returns => [Store], { description: "Get all the stores" }) + @Query(() => [Store], { description: "Get all the stores" }) async stores(): Promise { - return await this.storeCollection; + return await getStores(); + } + @Query(() => Store, { description: "Get a specific store" }) + async store(@Arg("id") id: String/* ,@Arg("withProducts", { nullable: true }) withProducts: boolean*/): Promise { + // check validity + return await getStoreSchema + .validate({ id: id }) + .then(validData => { + return getStore(validData.id); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); } + //Extend the Store type with a list of its products. + @FieldResolver(() => [Product]) + async products(@Root() store: Store): Promise<[Product]> { + return await getStoreProducts(store.id); + } + + @Mutation(() => Store, { description: "Create a new store" }) - async createStore(@Arg("input") input: StoreInput) { + async createStore(@Arg("input") input: StoreInput): Promise { // check validity return await createStoreSchema .validate(input) @@ -37,7 +60,6 @@ export class StoreResolver { .catch(err => { return new UserInputError('Invalid input', { validationErrors: err.errors }); }); - } } diff --git a/src/typeDefs.js b/src/typeDefs.js index 11e624a..d56a651 100644 --- a/src/typeDefs.js +++ b/src/typeDefs.js @@ -10,7 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; import "reflect-metadata"; -import { ObjectType, Field, InputType, Int } from "type-graphql"; +import { ObjectType, Field, InputType, Int, Float } from "type-graphql"; let Store = class Store { }; __decorate([ @@ -37,6 +37,10 @@ __decorate([ Field(), __metadata("design:type", String) ], Store.prototype, "street", void 0); +__decorate([ + Field(type => [Product]), + __metadata("design:type", Array) +], Store.prototype, "products", void 0); Store = __decorate([ ObjectType() ], Store); @@ -67,3 +71,25 @@ StoreInput = __decorate([ InputType() ], StoreInput); export { StoreInput }; +let Product = class Product { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], Product.prototype, "id", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Product.prototype, "name", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Product.prototype, "description", void 0); +__decorate([ + Field(type => Float), + __metadata("design:type", Number) +], Product.prototype, "price", void 0); +Product = __decorate([ + ObjectType() +], Product); +export { Product }; diff --git a/src/typeDefs.ts b/src/typeDefs.ts index d3ef753..25c32cb 100644 --- a/src/typeDefs.ts +++ b/src/typeDefs.ts @@ -2,8 +2,7 @@ // which ways the data can be fetched from the GraphQL server. import "reflect-metadata"; -import { ObjectType, Field, ID, InputType, Int } from "type-graphql"; -import { number } from "yup/lib/locale"; +import { ObjectType, Field, InputType, Int, Float } from "type-graphql"; @ObjectType() @@ -20,6 +19,8 @@ export class Store { postalCode: string @Field() street: string + @Field(type => [Product]) + products?: [Product] } @InputType() export class StoreInput { @@ -33,4 +34,16 @@ export class StoreInput { postalCode: string @Field() street: string +} + +@ObjectType() +export class Product { + @Field() + id: string; + @Field() + name: string; + @Field() + description: string; + @Field(type => Float) + price: number; } \ No newline at end of file From c7ee3ffb7c5fc5305f34928c013abe6aef06fc29 Mon Sep 17 00:00:00 2001 From: Fre Timmerman Date: Tue, 2 Feb 2021 16:52:57 +0100 Subject: [PATCH 4/5] done 4 --- schema.gql | 66 ++++++++++++++++++++++++++++++++++-------------- src/data.js | 35 +++++++++++++++++++++++-- src/index.js | 6 ++--- src/resolvers.js | 44 ++++++++++++++++++++++++++++++-- src/resolvers.ts | 38 +++++++++++++++++++++++++--- src/typeDefs.js | 56 ++++++++++++++++++++++++++++++++++++++++ src/typeDefs.ts | 33 ++++++++++++++++++++++++ 7 files changed, 248 insertions(+), 30 deletions(-) diff --git a/schema.gql b/schema.gql index 1d64a07..ddcdc74 100644 --- a/schema.gql +++ b/schema.gql @@ -3,40 +3,68 @@ # !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! # ----------------------------------------------- +""" +The javascript `Date` as string. Type represents date and time as the ISO Date string. +""" +scalar DateTime + type Mutation { + """Create a new reservation""" + createReservation(input: ReservationInput!): Reservation! + """Create a new store""" - createStore(input: StoreInput): Store + createStore(input: StoreInput!): Store! } type Product { - description: String - id: String - name: String - price: Float + description: String! + id: String! + name: String! + price: Float! } type Query { """Get a specific store""" - store(id: String): Store + store(id: String!): Store! """Get all the stores""" - stores: [Store] + stores: [Store!]! +} + +type Reservation { + date: DateTime! + id: String! + reservationProducts: [ReservationProduct!]! +} + +input ReservationInput { + reservationProducts: [ReservationProductInput!]! +} + +type ReservationProduct { + product: Product! + quantity: Int! +} + +input ReservationProductInput { + productId: String! + quantity: Int! } type Store { - city: String - id: String - name: String - number: Int - postalCode: String - products: [Product] - street: String + city: String! + id: String! + name: String! + number: Int! + postalCode: String! + products: [Product!]! + street: String! } input StoreInput { - city: String - name: String - number: Int - postalCode: String - street: String + city: String! + name: String! + number: Int! + postalCode: String! + street: String! } diff --git a/src/data.js b/src/data.js index 18fcb66..8c4d7a7 100644 --- a/src/data.js +++ b/src/data.js @@ -51,6 +51,10 @@ const products = [{ storeId: '5f2919aa-333a-4745-8166-3002ab30de0e', }]; +const reservations = []; + +let reservationProducts = []; + export function createStore({ city, name, number, postalCode, street }) { const newStore = { @@ -74,6 +78,33 @@ export function getStore(storeId) { } export function getStoreProducts(storeId) { - console.log(storeId); return products.filter(prod => prod.storeId === storeId) -} \ No newline at end of file +} + +export function getReservationProducts(reservationId) { + return reservationProducts + .filter(rp => rp.reservationId === reservationId) + .map(rp => ({ + product: products.find(p => p.id === rp.productId), + quantity: rp.quantity + }) + ); +} + +export function createReservation(reservation) { + //create new reservation and add it to db + const reservationId = uuid(); + const newReservation = { + id: reservationId, + date: new Date(), + }; + reservations.push(newReservation); + //add the reservationID to the reservationproducts, then add them to the db + reservationProducts = reservationProducts.concat( + reservation.reservationProducts.map(rp => ({ + ...rp, + reservationId + }) + )); + return newReservation; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 49551f1..618e524 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,7 @@ import { buildSchema } from "type-graphql"; import * as path from "path"; import { ApolloServer, gql } from "apollo-server"; -import { StoreResolver } from './resolvers.js'; +import { StoreResolver, ReservationResolver } from './resolvers.js'; // ⚽️ Goal // -------- @@ -29,8 +29,8 @@ import { StoreResolver } from './resolvers.js'; // create the schema using TypeGraphQL, pass the resolver const schema = await buildSchema({ - resolvers: [StoreResolver], - nullableByDefault: true, + resolvers: [StoreResolver, ReservationResolver], + //nullableByDefault: true, emitSchemaFile: path.resolve(".", "schema.gql"), }); diff --git a/src/resolvers.js b/src/resolvers.js index 55b20b0..c2b59fe 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -12,8 +12,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) { }; import "reflect-metadata"; import { Resolver, Query, Mutation, Arg, FieldResolver, Root } from "type-graphql"; -import { Store, StoreInput, Product } from "./typeDefs.js"; -import { createStore, getStores, getStore, getStoreProducts } from "./data.js"; +import { Store, StoreInput, Product, Reservation, ReservationProduct, ReservationInput } from "./typeDefs.js"; +import { createStore, getStores, getStore, getStoreProducts, createReservation, getReservationProducts } from "./data.js"; import * as yup from 'yup'; import { UserInputError } from "apollo-server"; // define input validations @@ -29,6 +29,13 @@ const getStoreSchema = yup.object() .shape({ id: yup.string().length(36).required() }); +const createReservationSchema = yup.object() + .shape({ + reservationProducts: yup.array(yup.object().shape({ + productId: yup.string().length(36), + quantity: yup.number().required().positive().integer(), + })), +}); let StoreResolver = class StoreResolver { async stores() { return await getStores(); @@ -92,3 +99,36 @@ StoreResolver = __decorate([ Resolver(() => Store) ], StoreResolver); export { StoreResolver }; +let ReservationResolver = class ReservationResolver { + async reservationProducts(reservation) { + return await getReservationProducts(reservation.id); + } + async createReservation(input) { + return await createReservationSchema + .validate(input) + .then(validData => { + return createReservation(validData); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + } +}; +__decorate([ + FieldResolver(() => [ReservationProduct]), + __param(0, Root()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Reservation]), + __metadata("design:returntype", Promise) +], ReservationResolver.prototype, "reservationProducts", null); +__decorate([ + Mutation(() => Reservation, { description: "Create a new reservation" }), + __param(0, Arg("input")), + __metadata("design:type", Function), + __metadata("design:paramtypes", [ReservationInput]), + __metadata("design:returntype", Promise) +], ReservationResolver.prototype, "createReservation", null); +ReservationResolver = __decorate([ + Resolver(() => Reservation) +], ReservationResolver); +export { ReservationResolver }; diff --git a/src/resolvers.ts b/src/resolvers.ts index 2a52c89..31084ef 100644 --- a/src/resolvers.ts +++ b/src/resolvers.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; import { Resolver, Query, Mutation, Arg, FieldResolver, Root } from "type-graphql"; -import { Store, StoreInput, Product } from "./typeDefs.js"; -import { createStore, getStores, getStore, getStoreProducts } from "./data.js"; +import { Store, StoreInput, Product, Reservation, ReservationProduct, ReservationInput } from "./typeDefs.js"; +import { createStore, getStores, getStore, getStoreProducts, createReservation, getReservationProducts } from "./data.js"; import * as yup from 'yup'; import { UserInputError } from "apollo-server"; @@ -18,8 +18,17 @@ const createStoreSchema = yup.object() const getStoreSchema = yup.object() .shape({ id: yup.string().length(36).required() - }) + }); +const createReservationSchema = yup.object() + .shape({ + reservationProducts: yup.array( + yup.object().shape({ + productId: yup.string().length(36), + quantity: yup.number().required().positive().integer(), + }) + ), + }); @Resolver(() => Store) export class StoreResolver { @@ -28,6 +37,7 @@ export class StoreResolver { async stores(): Promise { return await getStores(); } + @Query(() => Store, { description: "Get a specific store" }) async store(@Arg("id") id: String/* ,@Arg("withProducts", { nullable: true }) withProducts: boolean*/): Promise { // check validity @@ -48,6 +58,7 @@ export class StoreResolver { } + @Mutation(() => Store, { description: "Create a new store" }) async createStore(@Arg("input") input: StoreInput): Promise { // check validity @@ -61,5 +72,24 @@ export class StoreResolver { return new UserInputError('Invalid input', { validationErrors: err.errors }); }); } - +} +@Resolver(() => Reservation) +export class ReservationResolver { + + @FieldResolver(() => [ReservationProduct]) + async reservationProducts(@Root() reservation: Reservation) { + return await getReservationProducts(reservation.id); + } + + @Mutation(() => Reservation, { description: "Create a new reservation" }) + async createReservation(@Arg("input") input: ReservationInput): Promise { + return await createReservationSchema + .validate(input) + .then(validData => { + return createReservation(validData); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }) + } } diff --git a/src/typeDefs.js b/src/typeDefs.js index d56a651..36163dd 100644 --- a/src/typeDefs.js +++ b/src/typeDefs.js @@ -93,3 +93,59 @@ Product = __decorate([ ObjectType() ], Product); export { Product }; +let Reservation = class Reservation { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], Reservation.prototype, "id", void 0); +__decorate([ + Field(type => [ReservationProduct]), + __metadata("design:type", Array) +], Reservation.prototype, "reservationProducts", void 0); +__decorate([ + Field(type => Date), + __metadata("design:type", Date) +], Reservation.prototype, "date", void 0); +Reservation = __decorate([ + ObjectType() +], Reservation); +export { Reservation }; +let ReservationProduct = class ReservationProduct { +}; +__decorate([ + Field(type => Product), + __metadata("design:type", Product) +], ReservationProduct.prototype, "product", void 0); +__decorate([ + Field(type => Int), + __metadata("design:type", Number) +], ReservationProduct.prototype, "quantity", void 0); +ReservationProduct = __decorate([ + ObjectType() +], ReservationProduct); +export { ReservationProduct }; +let ReservationInput = class ReservationInput { +}; +__decorate([ + Field(type => [ReservationProductInput]), + __metadata("design:type", Array) +], ReservationInput.prototype, "reservationProducts", void 0); +ReservationInput = __decorate([ + InputType() +], ReservationInput); +export { ReservationInput }; +let ReservationProductInput = class ReservationProductInput { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], ReservationProductInput.prototype, "productId", void 0); +__decorate([ + Field(type => Int), + __metadata("design:type", Number) +], ReservationProductInput.prototype, "quantity", void 0); +ReservationProductInput = __decorate([ + InputType() +], ReservationProductInput); +export { ReservationProductInput }; diff --git a/src/typeDefs.ts b/src/typeDefs.ts index 25c32cb..cc05f41 100644 --- a/src/typeDefs.ts +++ b/src/typeDefs.ts @@ -46,4 +46,37 @@ export class Product { description: string; @Field(type => Float) price: number; +} + +@ObjectType() +export class Reservation { + @Field() + id: String; + @Field(type => [ReservationProduct]) + reservationProducts: [ReservationProduct]; + @Field(type => Date) + date: Date; +} + +@ObjectType() +export class ReservationProduct { + @Field(type => Product) + product: Product; + @Field(type => Int) + quantity: number; +} + + +@InputType() +export class ReservationInput { + @Field(type => [ReservationProductInput]) + reservationProducts: [ReservationProductInput]; +} + +@InputType() +export class ReservationProductInput { + @Field() + productId: string; + @Field(type => Int) + quantity: number; } \ No newline at end of file From 3f70e2a67ea719e14a2754a073d001ba2ecc3e33 Mon Sep 17 00:00:00 2001 From: Fre Timmerman Date: Fri, 5 Feb 2021 15:24:02 +0100 Subject: [PATCH 5/5] extra --- gql commands | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + schema.gql | 56 ++++++++++++++++++++++++------------------------ src/index.js | 2 +- 4 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 gql commands diff --git a/gql commands b/gql commands new file mode 100644 index 0000000..dbc6e56 --- /dev/null +++ b/gql commands @@ -0,0 +1,60 @@ + { + stores{ + id, + name, + city, + number, + postalCode, + street, + products{description,name,price} + } +} + +mutation{ + createStore( + input:{ + name: "asdf", + city: "asdf", + number:2, + postalCode: "hallohallo", + street: "asdf" + } + ) { + id, + name, + city, + number, + postalCode, + street + } +} + +query getStore($storeId: String!, $withProducts: Boolean!){ + store (id:$storeId){ + id, + name, + city, + number, + postalCode, + street, + products @include(if: $withProducts) {id,description,name,price} + } +} +var {"storeId": "5f2919aa-333a-4745-8166-3002ab30de0e","withProducts":true} + +mutation($productId:String!){ + createReservation( + input:{ + reservationProducts: + [ + { + productId:$productId, + quantity:2 + } + ] + } + ) { + id,date,reservationProducts{product{name,description,price},quantity} + } +} +var {"productId":"5bb3fbcc-7ec2-44fe-a04b-a0251cecf1e6"} \ No newline at end of file diff --git a/package.json b/package.json index f273480..988d746 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "graphql": "^15.5.0", "reflect-metadata": "^0.1.13", "type-graphql": "^1.1.1", + "typescript": "^4.1.3", "uuid": "^3.3.2", "yup": "^0.32.8" }, diff --git a/schema.gql b/schema.gql index ddcdc74..fcf2815 100644 --- a/schema.gql +++ b/schema.gql @@ -10,61 +10,61 @@ scalar DateTime type Mutation { """Create a new reservation""" - createReservation(input: ReservationInput!): Reservation! + createReservation(input: ReservationInput): Reservation """Create a new store""" - createStore(input: StoreInput!): Store! + createStore(input: StoreInput): Store } type Product { - description: String! - id: String! - name: String! - price: Float! + description: String + id: String + name: String + price: Float } type Query { """Get a specific store""" - store(id: String!): Store! + store(id: String): Store """Get all the stores""" - stores: [Store!]! + stores: [Store] } type Reservation { - date: DateTime! - id: String! - reservationProducts: [ReservationProduct!]! + date: DateTime + id: String + reservationProducts: [ReservationProduct] } input ReservationInput { - reservationProducts: [ReservationProductInput!]! + reservationProducts: [ReservationProductInput] } type ReservationProduct { - product: Product! - quantity: Int! + product: Product + quantity: Int } input ReservationProductInput { - productId: String! - quantity: Int! + productId: String + quantity: Int } type Store { - city: String! - id: String! - name: String! - number: Int! - postalCode: String! - products: [Product!]! - street: String! + city: String + id: String + name: String + number: Int + postalCode: String + products: [Product] + street: String } input StoreInput { - city: String! - name: String! - number: Int! - postalCode: String! - street: String! + city: String + name: String + number: Int + postalCode: String + street: String } diff --git a/src/index.js b/src/index.js index 618e524..aa20715 100644 --- a/src/index.js +++ b/src/index.js @@ -30,7 +30,7 @@ import { StoreResolver, ReservationResolver } from './resolvers.js'; // create the schema using TypeGraphQL, pass the resolver const schema = await buildSchema({ resolvers: [StoreResolver, ReservationResolver], - //nullableByDefault: true, + nullableByDefault: true, emitSchemaFile: path.resolve(".", "schema.gql"), });