Last active
June 27, 2023 11:58
-
-
Save Aryan-Jagadale/9a5e47b976f630f0990df7f954a841ba to your computer and use it in GitHub Desktop.
Full Stack Open GraphQL solution
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const { ApolloServer } = require("@apollo/server"); | |
| const { startStandaloneServer } = require("@apollo/server/standalone"); | |
| const { GraphQLError } = require("graphql"); | |
| const { v1: uuid } = require("uuid"); | |
| const jwt = require("jsonwebtoken"); | |
| const mongoose = require("mongoose"); | |
| mongoose.set("strictQuery", false); | |
| const Author = require("./models/author"); | |
| const Book = require("./models/book"); | |
| const User = require("./models/user"); | |
| require("dotenv").config(); | |
| mongoose | |
| .connect(process.env.MONGODB_URI) | |
| .then(() => { | |
| console.log("connected to MongoDB"); | |
| }) | |
| .catch((error) => { | |
| console.log("error connection to MongoDB:", error.message); | |
| }); | |
| const typeDefs = ` | |
| type User { | |
| username: String! | |
| favoriteGenre: String! | |
| id: ID! | |
| } | |
| type Token { | |
| value: String! | |
| } | |
| type Book { | |
| title: String! | |
| published: String! | |
| author: Author! | |
| genres: [String!]! | |
| id: ID! | |
| } | |
| type Author { | |
| name: String! | |
| born: Int! | |
| id: ID! | |
| } | |
| input AuthorInput { | |
| name:String! | |
| born:String | |
| } | |
| type Query { | |
| bookCount:Int! | |
| authorCount:Int! | |
| allBooks(author: String,genre: String):[Book!]! | |
| allAuthors:[Author!]! | |
| me:User | |
| } | |
| type Mutation { | |
| addBook( | |
| title : String! | |
| published : String! | |
| author : AuthorInput! | |
| genres: [String!]! | |
| ) :Book | |
| editAuthor( | |
| name: String! | |
| setBornTo: Int! | |
| ):Author | |
| createUser( | |
| username: String! | |
| favoriteGenre: String! | |
| ): User | |
| login( | |
| username: String! | |
| password: String! | |
| ): Token | |
| } | |
| `; | |
| const resolvers = { | |
| Query: { | |
| authorCount: () => Author.collection.countDocuments(), | |
| bookCount: () => Book.collection.countDocuments(), | |
| allBooks: async (root, args) => { | |
| if (args.author) { | |
| const foundAuthor = await Author.findOne({ name: args.author }); | |
| if (foundAuthor) { | |
| if (args.genre) { | |
| return await Book.find({ | |
| author: foundAuthor._id, | |
| genres: { $in: [args.genre] }, | |
| }).populate("author"); | |
| } | |
| return await Book.find({ author: foundAuthor.id }).populate("author"); | |
| } | |
| return null; | |
| } | |
| if (args.genre) { | |
| return Book.find({ genres: { $in: [args.genre] } }).populate("author"); | |
| } | |
| return Book.find({}).populate("author"); | |
| }, | |
| allAuthors: async () => await Author.find({}), | |
| me: (root, args, context) => { | |
| return context.currentUser; | |
| }, | |
| }, | |
| Mutation: { | |
| addBook: async (root, args, context) => { | |
| const foundBook = await Book.findOne({ title: args.title }); | |
| const foundAuthor = await Author.findOne({ name: args.author.name }); | |
| const currentUser = context.currentUser; | |
| if (!currentUser) { | |
| throw new GraphQLError("You are not logged in"); | |
| } | |
| if (foundBook) { | |
| throw new GraphQLError("Book already exists", { | |
| invalidArgs: args.title, | |
| }); | |
| } | |
| if (!foundAuthor) { | |
| const author = await Author({ ...args.author }); | |
| try { | |
| await author.save(); | |
| } catch (error) { | |
| console.log("Error in found Author", error); | |
| throw new GraphQLError(error.message, { | |
| invalidArgs: args, | |
| }); | |
| } | |
| } | |
| const foundAuthor2 = await Author.findOne({ name: args.author.name }); | |
| const book = await Book({ ...args, author: foundAuthor2 }); | |
| try { | |
| await book.save(); | |
| } catch (error) { | |
| throw new GraphQLError(error.message, { | |
| invalidArgs: args, | |
| }); | |
| } | |
| return book; | |
| }, | |
| editAuthor: async (root, args,context) => { | |
| const author = await Author.findOne({ name: args.name }); | |
| const currentUser = context.currentUser | |
| if (!currentUser) { | |
| throw new AuthenticationError("not authenticated") | |
| } | |
| if (!author) { | |
| return null; | |
| } | |
| const setBornYear = await Author.findByIdAndUpdate( | |
| { _id: author._id }, | |
| { ...args, born: args.setBornTo } | |
| ); | |
| setBornYear.save(); | |
| return await Author.findOne({ name: args.name }); | |
| }, | |
| createUser: async (root, args) => { | |
| const user = await User({ | |
| username: args.username, | |
| favoriteGenre: args.favoriteGenre, | |
| }); | |
| try { | |
| await user.save(); | |
| } catch (error) { | |
| throw new GraphQLError("Creating the user failed", { | |
| extensions: { | |
| code: "BAD_USER_INPUT", | |
| invalidArgs: args.name, | |
| error, | |
| }, | |
| }); | |
| } | |
| }, | |
| login: async (root, args) => { | |
| const user = await User.findOne({ username: args.username }); | |
| if (!user || args.password !== "secret") { | |
| throw new GraphQLError("wrong credentials", { | |
| extensions: { | |
| code: "BAD_USER_INPUT", | |
| }, | |
| }); | |
| } | |
| const userForToken = { | |
| username: user.username, | |
| id: user._id, | |
| }; | |
| return { value: jwt.sign(userForToken, process.env.JWT_SECRET) }; | |
| }, | |
| }, | |
| }; | |
| const server = new ApolloServer({ | |
| typeDefs, | |
| resolvers, | |
| }); | |
| startStandaloneServer(server, { | |
| listen: { port: 4000 }, | |
| context: async ({ req, res }) => { | |
| const auth = req ? req.headers.authorization : null; | |
| if (auth && auth.startsWith("Bearer ")) { | |
| const decodedToken = jwt.verify( | |
| auth.substring(7), | |
| process.env.JWT_SECRET | |
| ); | |
| const currentUser = await User.findById(decodedToken.id) | |
| return { currentUser }; | |
| } | |
| }, | |
| }).then(({ url }) => { | |
| console.log(`Server ready at ${url}`); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const { ApolloServer } = require("@apollo/server"); | |
| const { startStandaloneServer } = require("@apollo/server/standalone"); | |
| const { GraphQLError } = require("graphql"); | |
| const { v1: uuid } = require("uuid"); | |
| const jwt = require("jsonwebtoken"); | |
| const mongoose = require("mongoose"); | |
| mongoose.set("strictQuery", false); | |
| const Author = require("./models/author"); | |
| const Book = require("./models/book"); | |
| const User = require("./models/user"); | |
| require("dotenv").config(); | |
| mongoose | |
| .connect(process.env.MONGODB_URI) | |
| .then(() => { | |
| console.log("connected to MongoDB"); | |
| }) | |
| .catch((error) => { | |
| console.log("error connection to MongoDB:", error.message); | |
| }); | |
| /*const typeDefs = ` | |
| type User { | |
| username: String! | |
| favoriteGenre: String! | |
| id: ID! | |
| } | |
| type Token { | |
| value: String! | |
| } | |
| type Book { | |
| title: String! | |
| published: String! | |
| author: Author! | |
| genres: [String!]! | |
| id: ID! | |
| } | |
| type Author { | |
| name: String! | |
| born: Int! | |
| id: ID! | |
| } | |
| input AuthorInput { | |
| name:String! | |
| born:String | |
| } | |
| type Query { | |
| bookCount:Int! | |
| authorCount:Int! | |
| allBooks(author: String,genre: String):[Book!]! | |
| allAuthors:[Author!]! | |
| me:User | |
| } | |
| type Mutation { | |
| addBook( | |
| title : String! | |
| published : String! | |
| author : AuthorInput! | |
| genres: [String!]! | |
| ) :Book | |
| editAuthor( | |
| name: String! | |
| setBornTo: Int! | |
| ):Author | |
| createUser( | |
| username: String! | |
| favoriteGenre: String! | |
| ): User | |
| login( | |
| username: String! | |
| password: String! | |
| ): Token | |
| } | |
| `;*/ | |
| const typeDefs =` | |
| type Author { | |
| name: String! | |
| born: Int | |
| bookCount: Int! | |
| id: ID! | |
| } | |
| input AuthorInput { | |
| name: String! | |
| born: Int | |
| } | |
| type Book { | |
| title: String! | |
| published: Int! | |
| author: Author! | |
| genres: [String!]! | |
| id: ID! | |
| } | |
| type User { | |
| username: String! | |
| favoriteGenre: String! | |
| id: ID! | |
| } | |
| type Token { | |
| value: String! | |
| } | |
| type Query { | |
| authorCount: Int! | |
| bookCount: Int! | |
| allBooks(author: String, genre: String): [Book!]! | |
| allAuthors: [Author!]! | |
| me: User | |
| } | |
| type Mutation { | |
| addBook( | |
| title: String! | |
| published: Int! | |
| author: AuthorInput! | |
| genres: [String!]! | |
| ): Book | |
| editAuthor(name: String!, setBornTo: Int!): Author | |
| createUser(username: String!, favoriteGenre: String!): User | |
| login(username: String!, password: String!): Token | |
| } | |
| `; | |
| const resolvers = { | |
| Query: { | |
| authorCount: () => Author.collection.countDocuments(), | |
| bookCount: () => Book.collection.countDocuments(), | |
| allBooks: async (root, args) => { | |
| if (args.author) { | |
| const foundAuthor = await Author.findOne({ name: args.author }); | |
| if (foundAuthor) { | |
| if (args.genre) { | |
| return await Book.find({ | |
| author: foundAuthor.id, | |
| genres: { $in: [args.genre] }, | |
| }).populate("author"); | |
| } | |
| return await Book.find({ author: foundAuthor.id }).populate("author"); | |
| } | |
| return null; | |
| } | |
| if (args.genre) { | |
| return Book.find({ genres: { $in: [args.genre] } }).populate("author"); | |
| } | |
| return Book.find({}).populate("author"); | |
| }, | |
| allAuthors: async () => await Author.find({}), | |
| me: (root, args, context) => { | |
| return context.currentUser; | |
| }, | |
| }, | |
| Author: { | |
| bookCount: async (root) => { | |
| const foundAuthor = await Author.findOne({ name: root.name }) | |
| const foundBooks = await Book.find({ author: foundAuthor.id }) | |
| return foundBooks.length | |
| } | |
| }, | |
| Mutation: { | |
| addBook: async (root, args, context) => { | |
| const foundBook = await Book.findOne({ title: args.title }); | |
| const foundAuthor = await Author.findOne({ name: args.author.name }); | |
| const currentUser = context.currentUser; | |
| if (!currentUser) { | |
| throw new GraphQLError("You are not logged in"); | |
| } | |
| if (foundBook) { | |
| throw new GraphQLError("Book already exists", { | |
| invalidArgs: args.title, | |
| }); | |
| } | |
| if (!foundAuthor) { | |
| const author = await Author({ ...args.author }); | |
| try { | |
| await author.save(); | |
| } catch (error) { | |
| console.log("Error in found Author", error); | |
| throw new GraphQLError(error.message, { | |
| invalidArgs: args, | |
| }); | |
| } | |
| } | |
| const foundAuthor2 = await Author.findOne({ name: args.author.name }); | |
| const book = await Book({ ...args, author: foundAuthor2 }); | |
| try { | |
| await book.save(); | |
| } catch (error) { | |
| throw new GraphQLError(error.message, { | |
| invalidArgs: args, | |
| }); | |
| } | |
| return book; | |
| }, | |
| editAuthor: async (root, args, context) => { | |
| const author = await Author.findOne({ name: args.name }); | |
| const currentUser = context.currentUser; | |
| if (!currentUser) { | |
| throw new AuthenticationError("not authenticated"); | |
| } | |
| if (!author) { | |
| return null; | |
| } | |
| const setBornYear = await Author.findByIdAndUpdate( | |
| { _id: author._id }, | |
| { ...args, born: args.setBornTo } | |
| ); | |
| setBornYear.save(); | |
| return await Author.findOne({ name: args.name }); | |
| }, | |
| createUser: async (root, args) => { | |
| const user = await User({ | |
| username: args.username, | |
| favoriteGenre: args.favoriteGenre, | |
| }); | |
| try { | |
| await user.save(); | |
| } catch (error) { | |
| throw new GraphQLError("Creating the user failed", { | |
| extensions: { | |
| code: "BAD_USER_INPUT", | |
| invalidArgs: args.name, | |
| error, | |
| }, | |
| }); | |
| } | |
| }, | |
| login: async (root, args) => { | |
| const user = await User.findOne({ username: args.username }); | |
| //mluukkai--secret | |
| if (!user || args.password !== "secret") { | |
| throw new GraphQLError("wrong credentials", { | |
| extensions: { | |
| code: "BAD_USER_INPUT", | |
| }, | |
| }); | |
| } | |
| const userForToken = { | |
| username: user.username, | |
| id: user._id, | |
| }; | |
| return { value: jwt.sign(userForToken, process.env.JWT_SECRET) }; | |
| }, | |
| }, | |
| }; | |
| const server = new ApolloServer({ | |
| typeDefs, | |
| resolvers, | |
| }); | |
| startStandaloneServer(server, { | |
| listen: { port: 4000 }, | |
| context: async ({ req, res }) => { | |
| const auth = req ? req.headers.authorization : null; | |
| if (auth && auth.startsWith("Bearer ")) { | |
| const decodedToken = jwt.verify( | |
| auth.substring(7), | |
| process.env.JWT_SECRET | |
| ); | |
| const currentUser = await User.findById(decodedToken.id); | |
| return { currentUser }; | |
| } | |
| }, | |
| }).then(({ url }) => { | |
| console.log(`Server ready at ${url}`); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //Frontend | |
| //Author.js | |
| import { useQuery } from "@apollo/client"; | |
| import { ALL_AUTHORS } from "../queries"; | |
| import YearForm from "./YearForm.jsx"; | |
| const Authors = (props) => { | |
| const result = useQuery(ALL_AUTHORS); | |
| if (!props.show) { | |
| return null; | |
| } | |
| if (result.loading) { | |
| return <div>loading....</div>; | |
| } | |
| const authors = result.data.allAuthors; | |
| return ( | |
| <div> | |
| <h2>authors</h2> | |
| <table> | |
| <tbody> | |
| <tr> | |
| <th></th> | |
| <th>born</th> | |
| <th>Book Count</th> | |
| </tr> | |
| {authors?.map((a) => ( | |
| <tr key={a.name}> | |
| <td>{a.name}</td> | |
| <td>{a.born}</td> | |
| <td>{a.bookCount}</td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| <br /> | |
| <YearForm allAuthors={authors} /> | |
| </div> | |
| ); | |
| }; | |
| export default Authors; | |
| //Books.js | |
| import { useLazyQuery, useQuery } from "@apollo/client"; | |
| import { ALL_BOOKS, ALL_BOOKS_WITH_GENRE } from "../queries"; | |
| import { useState } from "react"; | |
| import { useEffect } from "react"; | |
| const Books = (props) => { | |
| //const [str, setStr] = useState(""); | |
| const result = useQuery(ALL_BOOKS); | |
| const [books, setBooks] = useState([]); | |
| const [filteredBooks, setFilteredBooks] = useState([]); | |
| const [genres, setGenres] = useState([]); | |
| const [selectedGenre, setSelectedGenre] = useState(""); | |
| /*{ | |
| /*function genreFilter(e) { | |
| console.log("Clicked"); | |
| let string = e.target.textContent; | |
| string = string.toLowerCase(); | |
| setStr(string); | |
| }*/ | |
| useEffect(() => { | |
| if (result && result.data) { | |
| const allBooks = result.data.allBooks; | |
| setBooks(result.data.allBooks); | |
| //console.log(result); | |
| let genres = ["All genres"]; | |
| allBooks.forEach((element) => { | |
| element.genres.forEach((g) => { | |
| if (genres.indexOf(g) === -1) { | |
| //console.log(g); | |
| //console.log(genres.indexOf(g)); | |
| genres.push(g); | |
| } | |
| }); | |
| }); | |
| setGenres(genres); | |
| setSelectedGenre("All genres"); | |
| } | |
| }, [result.data]); | |
| useEffect(() => { | |
| if (selectedGenre === "All genres") { | |
| setFilteredBooks(books); | |
| } else { | |
| setFilteredBooks( | |
| books.filter((b) => b.genres.indexOf(selectedGenre) !== -1) | |
| ); | |
| } | |
| }, [books, selectedGenre]); | |
| // console.log(books); | |
| if (!props.show) { | |
| return null; | |
| } | |
| if (result.loading) { | |
| return <div>loading....</div>; | |
| } | |
| return ( | |
| <div> | |
| <h2>books</h2> | |
| {/*WIth react 8.19 */} | |
| {/*<button onClick={(e) => genreFilter(e)}>Comedy</button> | |
| <button onClick={(e) => genreFilter(e)}>MC</button> | |
| <button onClick={(e) => genreFilter(e)}>Agile</button> | |
| <button onClick={(e) => genreFilter(e)}>All</button>*/} | |
| {genres.length > 0 && | |
| genres.map((g) => ( | |
| <button onClick={() => setSelectedGenre(g)} key={g}> | |
| {g} | |
| </button> | |
| ))} | |
| <br /> | |
| <br /> | |
| <table> | |
| <tbody> | |
| <tr> | |
| <th></th> | |
| <th>author</th> | |
| <th>published</th> | |
| <th>genres</th> | |
| </tr> | |
| {/*{books | |
| ?.filter((item) => | |
| str !== "all" ? item.genres.includes(str) : item | |
| ) | |
| .map((item, index) => ( | |
| <tr key={index}> | |
| <th>{item.title}</th> | |
| <th>{item.author.name}</th> | |
| <th>{item.published}</th> | |
| <th>{item.genres}</th> | |
| </tr> | |
| ))}*/} | |
| {filteredBooks?.map((item, index) => ( | |
| <tr key={index}> | |
| <th>{item.title}</th> | |
| <th>{item.author.name}</th> | |
| <th>{item.published}</th> | |
| <th>{item.genres}</th> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| <br /> | |
| </div> | |
| ); | |
| }; | |
| export default Books; | |
| //Login Form.js | |
| import React, { useEffect, useState } from 'react' | |
| import { LOGIN } from '../queries' | |
| import { useMutation } from '@apollo/client' | |
| const LoginForm = ({setToken}) => { | |
| const [username, setUsername] = useState('mluukkai') | |
| const [password, setPassword] = useState('secret') | |
| const [login,result] = useMutation(LOGIN,{ | |
| onError:(error)=>{ | |
| console.log("Something went wrong in LoginForm"); | |
| } | |
| }) | |
| useEffect(() => { | |
| if (result.data) { | |
| const token = result.data.login.value | |
| localStorage.setItem("library-token",token) | |
| setToken(token) | |
| } | |
| }, [result.data]) | |
| const submit = async (event) => { | |
| event.preventDefault() | |
| if (!username|| !password) { | |
| return alert("Please fill all cred") | |
| } | |
| login({variables:{username,password}}) | |
| } | |
| return ( | |
| <div> | |
| <form onSubmit={submit}> | |
| <div> | |
| username <input | |
| value={username} | |
| onChange={({ target }) => setUsername(target.value)} | |
| /> | |
| </div> | |
| <div> | |
| password <input | |
| type='text' | |
| value={password} | |
| onChange={({ target }) => setPassword(target.value)} | |
| /> | |
| </div> | |
| <button type='submit'>login</button> | |
| </form> | |
| </div> | |
| ) | |
| } | |
| export default LoginForm | |
| //NewBook.js | |
| import React, { useState } from "react"; | |
| import { useMutation } from "@apollo/client"; | |
| import { ALL_AUTHORS, ALL_BOOKS, CREATE_BOOK } from "../queries"; | |
| const BookForm = ({show}) => { | |
| const [title, setTitle] = useState(""); | |
| const [author, setAuhtor] = useState(""); | |
| const [published, setPublished] = useState(undefined); | |
| const [genre, setGenre] = useState(""); | |
| const [genres, setGenres] = useState([]); | |
| const [createBook] = useMutation(CREATE_BOOK, { | |
| refetchQueries: [{ query: ALL_BOOKS }, { query: ALL_AUTHORS }], | |
| onError: (error) => { | |
| console.log("Wrong !", error); | |
| }, | |
| }); | |
| if (!show) { | |
| return null | |
| } | |
| const submit = async (event) => { | |
| event.preventDefault(); | |
| console.log("add book..."); | |
| createBook({ | |
| variables: { | |
| title, | |
| author: { name: author, born: 1200 }, | |
| published, | |
| genres, | |
| }, | |
| }); | |
| /*setTitle(""); | |
| setPublished(""); | |
| setAuhtor(""); | |
| setGenres([]); | |
| setGenre("");*/ | |
| }; | |
| const addGenre = () => { | |
| setGenres(genres.concat(genre)); | |
| setGenre(""); | |
| }; | |
| return ( | |
| <div> | |
| <form onSubmit={submit}> | |
| <div> | |
| title | |
| <input | |
| value={title} | |
| onChange={({ target }) => setTitle(target.value)} | |
| /> | |
| </div> | |
| <div> | |
| author | |
| <input | |
| value={author} | |
| onChange={({ target }) => setAuhtor(target.value)} | |
| /> | |
| </div> | |
| <div> | |
| published | |
| <input | |
| type="number" | |
| value={published} | |
| onChange={({ target }) => setPublished(parseInt(target.value))} | |
| /> | |
| </div> | |
| <div> | |
| <input | |
| value={genre} | |
| onChange={({ target }) => setGenre(target.value)} | |
| /> | |
| <button onClick={addGenre} type="button"> | |
| add genre | |
| </button> | |
| </div> | |
| <div>genres: {genres.join(" ")}</div> | |
| <button type="submit">create book</button> | |
| </form> | |
| </div> | |
| ); | |
| }; | |
| export default BookForm; | |
| //Recommend.js | |
| import React, { useState, useEffect } from 'react' | |
| import { useLazyQuery, useQuery } from '@apollo/client' | |
| import { ME, ALL_BOOKS_WITH_GENRE } from '../queries' | |
| const Recommended = ({ show }) => { | |
| const user = useQuery(ME) | |
| const [getFavoriteBooks, result] = useLazyQuery(ALL_BOOKS_WITH_GENRE) | |
| const [favoriteBooks, setFavoriteBooks] = useState([]) | |
| useEffect(() => { | |
| if (result.data) { | |
| setFavoriteBooks(result.data.allBooks) | |
| } | |
| }, [setFavoriteBooks, result]) | |
| useEffect(() => { | |
| if (user.data) { | |
| getFavoriteBooks({ variables: { genre: user.data.me.favoriteGenre } }) | |
| } | |
| }, [getFavoriteBooks, user]) | |
| if (!show) { | |
| return null | |
| } | |
| return ( | |
| <div> | |
| <p> | |
| books in your favorite genre <b>{user.data.me.favoriteGenre}</b> | |
| </p> | |
| <table> | |
| <tbody> | |
| <tr> | |
| <th></th> | |
| <th>author</th> | |
| <th>published</th> | |
| </tr> | |
| {favoriteBooks.map((a) => ( | |
| <tr key={a.title}> | |
| <td>{a.title}</td> | |
| <td>{a.author.name}</td> | |
| <td>{a.published}</td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| </div> | |
| ) | |
| } | |
| export default Recommended | |
| //YearForm.js | |
| import { useMutation } from "@apollo/client"; | |
| import React, { useState, useEffect } from "react"; | |
| import { ALL_AUTHORS, EDIT_BORN_YEAR } from "../queries"; | |
| import Select from "react-select"; | |
| const YearForm = ({ allAuthors }) => { | |
| const [nameOptions, setNameOptions] = useState(null); | |
| const [setBornTo, setBornYear] = useState(undefined); | |
| const [changeBornYear, result] = useMutation(EDIT_BORN_YEAR, { | |
| refetchQueries: [{ query: ALL_AUTHORS }], | |
| }); | |
| useEffect(() => { | |
| if (result.data && result.data.editAuthor === null) { | |
| alert("Author not found!,Yearform.js"); | |
| } | |
| }, [result.data]); | |
| const options = []; | |
| allAuthors.forEach((author) => | |
| options.push({ | |
| value: author.name, | |
| label: author.name, | |
| }) | |
| ); | |
| const submit = async (e) => { | |
| e.preventDefault(); | |
| const name = nameOptions.value; | |
| changeBornYear({ variables: { name, setBornTo } }); | |
| setNameOptions(""); | |
| setBornYear(""); | |
| console.log("Submitedd"); | |
| }; | |
| return ( | |
| <div> | |
| <h2>Set BirthYear</h2> | |
| <form onSubmit={submit}> | |
| <div> | |
| {/*8.12 Solution */} | |
| <Select | |
| value={nameOptions} | |
| onChange={setNameOptions} | |
| options={options} | |
| /> | |
| </div> | |
| <div> | |
| born{" "} | |
| <input | |
| value={setBornTo} | |
| onChange={({ target }) => setBornYear(parseInt(target.value))} | |
| /> | |
| </div> | |
| <button type="submit">Update Author</button> | |
| </form> | |
| </div> | |
| ); | |
| }; | |
| export default YearForm; | |
| //App.js | |
| import { useState } from "react"; | |
| import Authors from "./components/Authors"; | |
| import Books from "./components/Books"; | |
| import NewBook from "./components/NewBook"; | |
| import LoginForm from "./components/LoginForm"; | |
| import { useApolloClient } from "@apollo/client"; | |
| import Recommend from "./components/Recommend"; | |
| const App = () => { | |
| const client = useApolloClient(); | |
| const [token, setToken] = useState(null); | |
| const [page, setPage] = useState("authors"); | |
| if (!token) { | |
| return ( | |
| <> | |
| <LoginForm setToken={setToken} /> | |
| </> | |
| ); | |
| } | |
| const logout = () => { | |
| setToken(null); | |
| localStorage.clear(); | |
| client.resetStore(); | |
| }; | |
| return ( | |
| <div> | |
| <button onClick={logout}>Logout</button> | |
| <div> | |
| <button onClick={() => setPage("authors")}>authors</button> | |
| <button onClick={() => setPage("books")}>books</button> | |
| <button onClick={() => setPage("recommend")}>recommend</button> | |
| <button onClick={() => setPage("add")}>add book</button> | |
| </div> | |
| <br/> | |
| <NewBook show={page === "add"} /> | |
| <Authors show={page === "authors"} /> | |
| <Books show={page === "books"} /> | |
| <Recommend show={page === "recommend"} /> | |
| </div> | |
| ); | |
| }; | |
| export default App; | |
| //quiers.js | |
| import { gql } from "@apollo/client"; | |
| export const ALL_AUTHORS = gql` | |
| query { | |
| allAuthors { | |
| name | |
| born | |
| bookCount | |
| } | |
| } | |
| `; | |
| export const ALL_BOOKS = gql` | |
| query { | |
| allBooks { | |
| title | |
| published | |
| genres | |
| author { | |
| name | |
| } | |
| } | |
| } | |
| `; | |
| export const ALL_BOOKS_WITH_GENRE = gql` | |
| query getallBooks($genre: String!) { | |
| allBooks(genre: $genre) { | |
| title | |
| published | |
| genres | |
| author { | |
| name | |
| } | |
| } | |
| } | |
| `; | |
| export const CREATE_BOOK = gql` | |
| mutation createBook( | |
| $title: String! | |
| $author: String! | |
| $published: Int! | |
| $genres: [String!]! | |
| ) { | |
| addBook( | |
| title: $title | |
| author: $author | |
| published: $published | |
| genres: $genres | |
| ) { | |
| title | |
| author | |
| } | |
| } | |
| `; | |
| export const EDIT_BORN_YEAR = gql` | |
| mutation changeBornYear($name: String!, $setBornTo: Int!) { | |
| editAuthor(name: $name, setBornTo: $setBornTo) { | |
| name | |
| born | |
| } | |
| } | |
| `; | |
| export const LOGIN = gql` | |
| mutation loginUser($username: String!, $password: String!) { | |
| login(username: $username, password: $password) { | |
| value | |
| } | |
| } | |
| `; | |
| export const ME = gql` | |
| query { | |
| me { | |
| username | |
| favoriteGenre | |
| } | |
| } | |
| `; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const { ApolloServer } = require('@apollo/server') | |
| const { startStandaloneServer } = require('@apollo/server/standalone') | |
| let authors = [ | |
| { | |
| name: 'Robert Martin', | |
| id: "afa51ab0-344d-11e9-a414-719c6709cf3e", | |
| born: 1952, | |
| }, | |
| { | |
| name: 'Martin Fowler', | |
| id: "afa5b6f0-344d-11e9-a414-719c6709cf3e", | |
| born: 1963 | |
| }, | |
| { | |
| name: 'Fyodor Dostoevsky', | |
| id: "afa5b6f1-344d-11e9-a414-719c6709cf3e", | |
| born: 1821 | |
| }, | |
| { | |
| name: 'Joshua Kerievsky', // birthyear not known | |
| id: "afa5b6f2-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| { | |
| name: 'Sandi Metz', // birthyear not known | |
| id: "afa5b6f3-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| ] | |
| /* | |
| * Suomi: | |
| * Saattaisi olla järkevämpää assosioida kirja ja sen tekijä tallettamalla kirjan yhteyteen tekijän nimen sijaan tekijän id | |
| * Yksinkertaisuuden vuoksi tallennamme kuitenkin kirjan yhteyteen tekijän nimen | |
| * | |
| * English: | |
| * It might make more sense to associate a book with its author by storing the author's id in the context of the book instead of the author's name | |
| * However, for simplicity, we will store the author's name in connection with the book | |
| * | |
| * Spanish: | |
| * Podría tener más sentido asociar un libro con su autor almacenando la id del autor en el contexto del libro en lugar del nombre del autor | |
| * Sin embargo, por simplicidad, almacenaremos el nombre del autor en conección con el libro | |
| */ | |
| let books = [ | |
| { | |
| title: 'Clean Code', | |
| published: 2008, | |
| author: 'Robert Martin', | |
| id: "afa5b6f4-344d-11e9-a414-719c6709cf3e", | |
| genres: ['refactoring'] | |
| }, | |
| { | |
| title: 'Agile software development', | |
| published: 2002, | |
| author: 'Robert Martin', | |
| id: "afa5b6f5-344d-11e9-a414-719c6709cf3e", | |
| genres: ['agile', 'patterns', 'design'] | |
| }, | |
| { | |
| title: 'Refactoring, edition 2', | |
| published: 2018, | |
| author: 'Martin Fowler', | |
| id: "afa5de00-344d-11e9-a414-719c6709cf3e", | |
| genres: ['refactoring'] | |
| }, | |
| { | |
| title: 'Refactoring to patterns', | |
| published: 2008, | |
| author: 'Joshua Kerievsky', | |
| id: "afa5de01-344d-11e9-a414-719c6709cf3e", | |
| genres: ['refactoring', 'patterns'] | |
| }, | |
| { | |
| title: 'Practical Object-Oriented Design, An Agile Primer Using Ruby', | |
| published: 2012, | |
| author: 'Sandi Metz', | |
| id: "afa5de02-344d-11e9-a414-719c6709cf3e", | |
| genres: ['refactoring', 'design'] | |
| }, | |
| { | |
| title: 'Crime and punishment', | |
| published: 1866, | |
| author: 'Fyodor Dostoevsky', | |
| id: "afa5de03-344d-11e9-a414-719c6709cf3e", | |
| genres: ['classic', 'crime'] | |
| }, | |
| { | |
| title: 'The Demon ', | |
| published: 1872, | |
| author: 'Fyodor Dostoevsky', | |
| id: "afa5de04-344d-11e9-a414-719c6709cf3e", | |
| genres: ['classic', 'revolution'] | |
| }, | |
| ] | |
| /* | |
| you can remove the placeholder query once your first own has been implemented | |
| */ | |
| const typeDefs = ` | |
| type Query { | |
| bookCount:Int! | |
| authorCount:Int! | |
| } | |
| ` | |
| const resolvers = { | |
| Query: { | |
| bookCount: () => books.length, | |
| authorCount : () => authors.length | |
| } | |
| } | |
| const server = new ApolloServer({ | |
| typeDefs, | |
| resolvers, | |
| }) | |
| startStandaloneServer(server, { | |
| listen: { port: 4000 }, | |
| }).then(({ url }) => { | |
| console.log(`Server ready at ${url}`) | |
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //Copy all schema from questions | |
| const { ApolloServer } = require("@apollo/server"); | |
| const { startStandaloneServer } = require("@apollo/server/standalone"); | |
| const { GraphQLError } = require("graphql"); | |
| const { v1: uuid } = require("uuid"); | |
| const mongoose = require("mongoose"); | |
| mongoose.set("strictQuery", false); | |
| const Author = require("./models/author"); | |
| const Book = require("./models/book"); | |
| require("dotenv").config(); | |
| mongoose | |
| .connect(process.env.MONGODB_URI) | |
| .then(() => { | |
| console.log("connected to MongoDB"); | |
| }) | |
| .catch((error) => { | |
| console.log("error connection to MongoDB:", error.message); | |
| }); | |
| const typeDefs = ` | |
| type Book { | |
| title: String! | |
| published: String! | |
| author: Author! | |
| genres: [String!]! | |
| id: ID! | |
| } | |
| type Author { | |
| name: String! | |
| born: Int! | |
| id: ID! | |
| } | |
| input AuthorInput { | |
| name:String! | |
| born:String | |
| } | |
| type Query { | |
| bookCount:Int! | |
| authorCount:Int! | |
| allBooks(author: String,genre: String):[Book!]! | |
| allAuthors:[Author!]! | |
| } | |
| type Mutation { | |
| addBook( | |
| title : String! | |
| published : String! | |
| author : AuthorInput! | |
| genres: [String!]! | |
| ) :Book | |
| editAuthor( | |
| name: String! | |
| setBornTo: Int! | |
| ):Author | |
| } | |
| `; | |
| const resolvers = { | |
| Query: { | |
| authorCount: () => Author.collection.countDocuments(), | |
| bookCount: () => Book.collection.countDocuments(), | |
| allBooks: async (root, args) => { | |
| if (args.author) { | |
| const foundAuthor = await Author.findOne({ name: args.author }); | |
| if (foundAuthor) { | |
| if (args.genre) { | |
| return await Book.find({ | |
| author: foundAuthor._id, | |
| genres: { $in: [args.genre] }, | |
| }).populate("author"); | |
| } | |
| return await Book.find({ author: foundAuthor.id }).populate("author"); | |
| } | |
| return null; | |
| } | |
| if (args.genre) { | |
| return Book.find({ genres: { $in: [args.genre] } }).populate("author"); | |
| } | |
| return Book.find({}).populate("author"); | |
| }, | |
| allAuthors: async () => await Author.find({}), | |
| }, | |
| Mutation: { | |
| addBook: async (root, args) => { | |
| const foundBook = await Book.findOne({ title: args.title }); | |
| const foundAuthor = await Author.findOne({ name: args.author.name }); | |
| if (foundBook) { | |
| throw new GraphQLError("Book already exists", { | |
| invalidArgs: args.title, | |
| }); | |
| } | |
| if (!foundAuthor) { | |
| const author = await Author({ ...args.author }); | |
| try { | |
| await author.save(); | |
| } catch (error) { | |
| console.log("Error in found Author", error); | |
| throw new GraphQLError(error.message, { | |
| invalidArgs: args, | |
| }); | |
| } | |
| } | |
| const foundAuthor2 = await Author.findOne({ name: args.author.name }); | |
| const book = await Book({ ...args, author: foundAuthor2 }); | |
| try { | |
| await book.save(); | |
| } catch (error) { | |
| throw new GraphQLError(error.message, { | |
| invalidArgs: args, | |
| }); | |
| } | |
| return book; | |
| }, | |
| editAuthor: async (root, args) => { | |
| const author = await Author.findOne({ name: args.name }); | |
| if (!author) { | |
| return null; | |
| } | |
| const setBornYear = await Author.findByIdAndUpdate({_id:author._id},{...args,born:args.setBornTo}) | |
| setBornYear.save(); | |
| return await Author.findOne({ name: args.name }) | |
| }, | |
| }, | |
| }; | |
| const server = new ApolloServer({ | |
| typeDefs, | |
| resolvers, | |
| }); | |
| startStandaloneServer(server, { | |
| listen: { port: 4000 }, | |
| }).then(({ url }) => { | |
| console.log(`Server ready at ${url}`); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const { ApolloServer } = require("@apollo/server"); | |
| const { startStandaloneServer } = require("@apollo/server/standalone"); | |
| let authors = [ | |
| { | |
| name: "Robert Martin", | |
| id: "afa51ab0-344d-11e9-a414-719c6709cf3e", | |
| born: 1952, | |
| }, | |
| { | |
| name: "Martin Fowler", | |
| id: "afa5b6f0-344d-11e9-a414-719c6709cf3e", | |
| born: 1963, | |
| }, | |
| { | |
| name: "Fyodor Dostoevsky", | |
| id: "afa5b6f1-344d-11e9-a414-719c6709cf3e", | |
| born: 1821, | |
| }, | |
| { | |
| name: "Joshua Kerievsky", // birthyear not known | |
| id: "afa5b6f2-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| { | |
| name: "Sandi Metz", // birthyear not known | |
| id: "afa5b6f3-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| ]; | |
| let books = [ | |
| { | |
| title: "Clean Code", | |
| published: 2008, | |
| author: "Robert Martin", | |
| id: "afa5b6f4-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring"], | |
| }, | |
| { | |
| title: "Agile software development", | |
| published: 2002, | |
| author: "Robert Martin", | |
| id: "afa5b6f5-344d-11e9-a414-719c6709cf3e", | |
| genres: ["agile", "patterns", "design"], | |
| }, | |
| { | |
| title: "Refactoring, edition 2", | |
| published: 2018, | |
| author: "Martin Fowler", | |
| id: "afa5de00-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring"], | |
| }, | |
| { | |
| title: "Refactoring to patterns", | |
| published: 2008, | |
| author: "Joshua Kerievsky", | |
| id: "afa5de01-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring", "patterns"], | |
| }, | |
| { | |
| title: "Practical Object-Oriented Design, An Agile Primer Using Ruby", | |
| published: 2012, | |
| author: "Sandi Metz", | |
| id: "afa5de02-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring", "design"], | |
| }, | |
| { | |
| title: "Crime and punishment", | |
| published: 1866, | |
| author: "Fyodor Dostoevsky", | |
| id: "afa5de03-344d-11e9-a414-719c6709cf3e", | |
| genres: ["classic", "crime"], | |
| }, | |
| { | |
| title: "The Demon ", | |
| published: 1872, | |
| author: "Fyodor Dostoevsky", | |
| id: "afa5de04-344d-11e9-a414-719c6709cf3e", | |
| genres: ["classic", "revolution"], | |
| }, | |
| ]; | |
| const typeDefs = ` | |
| type Book { | |
| title : String! | |
| author : String! | |
| published : String! | |
| genres: [String!]! | |
| } | |
| type Author { | |
| name: String! | |
| born: Int | |
| bookCount: Int! | |
| id: ID! | |
| } | |
| type Query { | |
| bookCount:Int! | |
| authorCount:Int! | |
| allBooks(author: String!):[Book!]! | |
| allAuthors : [Author!]! | |
| } | |
| `; | |
| const resolvers = { | |
| Query: { | |
| bookCount: () => books.length, | |
| authorCount: () => authors.length, | |
| allBooks: (root,args) => books.filter((book) => book.author === args.author), | |
| allAuthors:() => authors, | |
| }, | |
| }; | |
| const server = new ApolloServer({ | |
| typeDefs, | |
| resolvers, | |
| }); | |
| startStandaloneServer(server, { | |
| listen: { port: 4000 }, | |
| }).then(({ url }) => { | |
| console.log(`Server ready at ${url}`); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const { ApolloServer } = require("@apollo/server"); | |
| const { startStandaloneServer } = require("@apollo/server/standalone"); | |
| let authors = [ | |
| { | |
| name: "Robert Martin", | |
| id: "afa51ab0-344d-11e9-a414-719c6709cf3e", | |
| born: 1952, | |
| }, | |
| { | |
| name: "Martin Fowler", | |
| id: "afa5b6f0-344d-11e9-a414-719c6709cf3e", | |
| born: 1963, | |
| }, | |
| { | |
| name: "Fyodor Dostoevsky", | |
| id: "afa5b6f1-344d-11e9-a414-719c6709cf3e", | |
| born: 1821, | |
| }, | |
| { | |
| name: "Joshua Kerievsky", // birthyear not known | |
| id: "afa5b6f2-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| { | |
| name: "Sandi Metz", // birthyear not known | |
| id: "afa5b6f3-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| ]; | |
| let books = [ | |
| { | |
| title: "Clean Code", | |
| published: 2008, | |
| author: "Robert Martin", | |
| id: "afa5b6f4-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring"], | |
| }, | |
| { | |
| title: "Agile software development", | |
| published: 2002, | |
| author: "Robert Martin", | |
| id: "afa5b6f5-344d-11e9-a414-719c6709cf3e", | |
| genres: ["agile", "patterns", "design"], | |
| }, | |
| { | |
| title: "Refactoring, edition 2", | |
| published: 2018, | |
| author: "Martin Fowler", | |
| id: "afa5de00-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring"], | |
| }, | |
| { | |
| title: "Refactoring to patterns", | |
| published: 2008, | |
| author: "Joshua Kerievsky", | |
| id: "afa5de01-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring", "patterns"], | |
| }, | |
| { | |
| title: "Practical Object-Oriented Design, An Agile Primer Using Ruby", | |
| published: 2012, | |
| author: "Sandi Metz", | |
| id: "afa5de02-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring", "design"], | |
| }, | |
| { | |
| title: "Crime and punishment", | |
| published: 1866, | |
| author: "Fyodor Dostoevsky", | |
| id: "afa5de03-344d-11e9-a414-719c6709cf3e", | |
| genres: ["classic", "crime"], | |
| }, | |
| { | |
| title: "The Demon ", | |
| published: 1872, | |
| author: "Fyodor Dostoevsky", | |
| id: "afa5de04-344d-11e9-a414-719c6709cf3e", | |
| genres: ["classic", "revolution"], | |
| }, | |
| ]; | |
| const typeDefs = ` | |
| type Book { | |
| title : String! | |
| author : String! | |
| published : String! | |
| genres: [String!]! | |
| } | |
| type Author { | |
| name: String! | |
| born: Int | |
| bookCount: Int! | |
| id: ID! | |
| } | |
| type Query { | |
| bookCount:Int! | |
| authorCount:Int! | |
| allBooks(author: String!,genre: String!):[Book!]! | |
| } | |
| `; | |
| const resolvers = { | |
| Query: { | |
| bookCount: () => books.length, | |
| authorCount: () => authors.length, | |
| allBooks: (root, args) => | |
| books.filter((book) => book.genres.includes(args.genre) && book.author === args.author), | |
| }, | |
| }; | |
| const server = new ApolloServer({ | |
| typeDefs, | |
| resolvers, | |
| }); | |
| startStandaloneServer(server, { | |
| listen: { port: 4000 }, | |
| }).then(({ url }) => { | |
| console.log(`Server ready at ${url}`); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const { ApolloServer } = require("@apollo/server"); | |
| const { startStandaloneServer } = require("@apollo/server/standalone"); | |
| const { GraphQLError } = require("graphql"); | |
| const { v1: uuid } = require("uuid"); | |
| let authors = [ | |
| { | |
| name: "Robert Martin", | |
| id: "afa51ab0-344d-11e9-a414-719c6709cf3e", | |
| born: 1952, | |
| }, | |
| { | |
| name: "Martin Fowler", | |
| id: "afa5b6f0-344d-11e9-a414-719c6709cf3e", | |
| born: 1963, | |
| }, | |
| { | |
| name: "Fyodor Dostoevsky", | |
| id: "afa5b6f1-344d-11e9-a414-719c6709cf3e", | |
| born: 1821, | |
| }, | |
| { | |
| name: "Joshua Kerievsky", // birthyear not known | |
| id: "afa5b6f2-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| { | |
| name: "Sandi Metz", // birthyear not known | |
| id: "afa5b6f3-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| ]; | |
| let books = [ | |
| { | |
| title: "Clean Code", | |
| published: 2008, | |
| author: "Robert Martin", | |
| id: "afa5b6f4-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring"], | |
| }, | |
| { | |
| title: "Agile software development", | |
| published: 2002, | |
| author: "Robert Martin", | |
| id: "afa5b6f5-344d-11e9-a414-719c6709cf3e", | |
| genres: ["agile", "patterns", "design"], | |
| }, | |
| { | |
| title: "Refactoring, edition 2", | |
| published: 2018, | |
| author: "Martin Fowler", | |
| id: "afa5de00-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring"], | |
| }, | |
| { | |
| title: "Refactoring to patterns", | |
| published: 2008, | |
| author: "Joshua Kerievsky", | |
| id: "afa5de01-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring", "patterns"], | |
| }, | |
| { | |
| title: "Practical Object-Oriented Design, An Agile Primer Using Ruby", | |
| published: 2012, | |
| author: "Sandi Metz", | |
| id: "afa5de02-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring", "design"], | |
| }, | |
| { | |
| title: "Crime and punishment", | |
| published: 1866, | |
| author: "Fyodor Dostoevsky", | |
| id: "afa5de03-344d-11e9-a414-719c6709cf3e", | |
| genres: ["classic", "crime"], | |
| }, | |
| { | |
| title: "The Demon ", | |
| published: 1872, | |
| author: "Fyodor Dostoevsky", | |
| id: "afa5de04-344d-11e9-a414-719c6709cf3e", | |
| genres: ["classic", "revolution"], | |
| }, | |
| ]; | |
| const typeDefs = ` | |
| type Book { | |
| title : String! | |
| author : String! | |
| published : Int! | |
| genres: [String!]! | |
| } | |
| type Author { | |
| name: String! | |
| born: Int | |
| bookCount: Int! | |
| id: ID! | |
| } | |
| type Query { | |
| bookCount:Int! | |
| authorCount:Int! | |
| allBooks(author: String!,genre: String!):[Book!]! | |
| } | |
| type Mutation { | |
| addBook( | |
| title : String! | |
| author : String! | |
| published : Int! | |
| genres: [String!]! | |
| ) :Book | |
| } | |
| `; | |
| const resolvers = { | |
| Query: { | |
| bookCount: () => books.length, | |
| authorCount: () => authors.length, | |
| allBooks: (root, args) => | |
| books.filter( | |
| (book) => | |
| book.genres.includes(args.genre) && book.author === args.author | |
| ) | |
| }, | |
| Mutation: { | |
| addBook: (root,args) => { | |
| if (books.find((b)=>b.title === args.title)) { | |
| throw new GraphQLError("Book Titlee must be unique", { | |
| extensions: { | |
| code: "BAD_USER_INPUT", | |
| invalidArgs: args.name, | |
| }, | |
| }); | |
| } | |
| const book = {...args,id:uuid()}; | |
| books = books.concat(book); | |
| return book | |
| } | |
| } | |
| }; | |
| const server = new ApolloServer({ | |
| typeDefs, | |
| resolvers, | |
| }); | |
| startStandaloneServer(server, { | |
| listen: { port: 4000 }, | |
| }).then(({ url }) => { | |
| console.log(`Server ready at ${url}`); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const { ApolloServer } = require("@apollo/server"); | |
| const { startStandaloneServer } = require("@apollo/server/standalone"); | |
| const { GraphQLError } = require("graphql"); | |
| const { v1: uuid } = require("uuid"); | |
| let authors = [ | |
| { | |
| name: "Robert Martin", | |
| id: "afa51ab0-344d-11e9-a414-719c6709cf3e", | |
| born: 1952, | |
| }, | |
| { | |
| name: "Martin Fowler", | |
| id: "afa5b6f0-344d-11e9-a414-719c6709cf3e", | |
| born: 1963, | |
| }, | |
| { | |
| name: "Fyodor Dostoevsky", | |
| id: "afa5b6f1-344d-11e9-a414-719c6709cf3e", | |
| born: 1821, | |
| }, | |
| { | |
| name: "Joshua Kerievsky", // birthyear not known | |
| id: "afa5b6f2-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| { | |
| name: "Sandi Metz", // birthyear not known | |
| id: "afa5b6f3-344d-11e9-a414-719c6709cf3e", | |
| }, | |
| ]; | |
| let books = [ | |
| { | |
| title: "Clean Code", | |
| published: 2008, | |
| author: "Robert Martin", | |
| id: "afa5b6f4-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring"], | |
| }, | |
| { | |
| title: "Agile software development", | |
| published: 2002, | |
| author: "Robert Martin", | |
| id: "afa5b6f5-344d-11e9-a414-719c6709cf3e", | |
| genres: ["agile", "patterns", "design"], | |
| }, | |
| { | |
| title: "Refactoring, edition 2", | |
| published: 2018, | |
| author: "Martin Fowler", | |
| id: "afa5de00-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring"], | |
| }, | |
| { | |
| title: "Refactoring to patterns", | |
| published: 2008, | |
| author: "Joshua Kerievsky", | |
| id: "afa5de01-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring", "patterns"], | |
| }, | |
| { | |
| title: "Practical Object-Oriented Design, An Agile Primer Using Ruby", | |
| published: 2012, | |
| author: "Sandi Metz", | |
| id: "afa5de02-344d-11e9-a414-719c6709cf3e", | |
| genres: ["refactoring", "design"], | |
| }, | |
| { | |
| title: "Crime and punishment", | |
| published: 1866, | |
| author: "Fyodor Dostoevsky", | |
| id: "afa5de03-344d-11e9-a414-719c6709cf3e", | |
| genres: ["classic", "crime"], | |
| }, | |
| { | |
| title: "The Demon ", | |
| published: 1872, | |
| author: "Fyodor Dostoevsky", | |
| id: "afa5de04-344d-11e9-a414-719c6709cf3e", | |
| genres: ["classic", "revolution"], | |
| }, | |
| ]; | |
| const typeDefs = ` | |
| type Book { | |
| title : String! | |
| author : String! | |
| published : Int! | |
| genres: [String!]! | |
| } | |
| type Author { | |
| name: String! | |
| born: Int | |
| bookCount: Int! | |
| id: ID! | |
| } | |
| type Query { | |
| bookCount:Int! | |
| authorCount:Int! | |
| allBooks(author: String!,genre: String!):[Book!]! | |
| } | |
| type Mutation { | |
| addBook( | |
| title : String! | |
| author : String! | |
| published : Int! | |
| genres: [String!]! | |
| ) :Book | |
| editAuthor( | |
| name: String! | |
| setBornTo: Int | |
| bookCount: Int | |
| id: ID | |
| ):Author | |
| } | |
| `; | |
| const resolvers = { | |
| Query: { | |
| bookCount: () => books.length, | |
| authorCount: () => authors.length, | |
| allBooks: (root, args) => | |
| books.filter( | |
| (book) => | |
| book.genres.includes(args.genre) && book.author === args.author | |
| ) | |
| }, | |
| Mutation: { | |
| addBook: (root,args) => { | |
| if (books.find((b)=>b.title === args.title)) { | |
| throw new GraphQLError("Book Titlee must be unique", { | |
| extensions: { | |
| code: "BAD_USER_INPUT", | |
| invalidArgs: args.name, | |
| }, | |
| }); | |
| } | |
| const book = {...args,id:uuid()}; | |
| books = books.concat(book); | |
| return book | |
| }, | |
| editAuthor: (root,args)=>{ | |
| const author = authors.find(author => author.name === args.name) | |
| if (!author) { | |
| return null; | |
| } | |
| const updatedAuthor = {...author,born:args.setBornTo} | |
| authors = authors.map((a)=>a.name === updatedAuthor.name ? updatedAuthor:a) | |
| return updatedAuthor | |
| } | |
| } | |
| }; | |
| const server = new ApolloServer({ | |
| typeDefs, | |
| resolvers, | |
| }); | |
| startStandaloneServer(server, { | |
| listen: { port: 4000 }, | |
| }).then(({ url }) => { | |
| console.log(`Server ready at ${url}`); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment