├── src
├── resolvers
│ ├── ExoticPet.js
│ ├── FamilyPet.js
│ ├── Subscription.js
│ ├── Date.js
│ ├── Checkout.js
│ ├── Customer.js
│ ├── index.js
│ ├── Pet.js
│ ├── Query.js
│ └── Mutation.js
├── index.js
└── typeDefs.graphql
├── import-data
├── checkouts.json
├── customers.json
├── index.js
└── pets.json
├── .gitignore
├── package.json
└── README.md
/src/resolvers/ExoticPet.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | __resolveType: parent => (parent.fast ? "Stingray" : "Rabbit")
3 | };
4 |
--------------------------------------------------------------------------------
/src/resolvers/FamilyPet.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | __resolveType: parent => (parent.sleepAmount ? "Cat" : "Dog")
3 | };
4 |
--------------------------------------------------------------------------------
/src/resolvers/Subscription.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | petReturned: {
3 | subscribe: (parent, data, { pubsub }) =>
4 | pubsub.asyncIterator("pet-returned")
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/src/resolvers/Date.js:
--------------------------------------------------------------------------------
1 | const { GraphQLScalarType } = require("graphql");
2 |
3 | module.exports = new GraphQLScalarType({
4 | name: "Date",
5 | description: "A valid datetime value",
6 | serialize: value => new Date(value).toISOString(),
7 | parseValue: value => new Date(value),
8 | parseLiteral: ast => new Date(ast.value)
9 | });
10 |
--------------------------------------------------------------------------------
/src/resolvers/Checkout.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pet: ({ petId }, args, { pets }) => pets.findOne({ id: petId }),
3 | late: ({ checkOutDate, checkInDate }) => {
4 | let date = new Date(checkOutDate);
5 | let plusThree = date.getTime() + 3 * 60000;
6 | let dueString = new Date(plusThree).toISOString();
7 | return checkInDate > dueString ? true : false;
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/import-data/checkouts.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "username": "snelson",
4 | "petId": "C-1",
5 | "checkOutDate": "2019-03-26T19:45:24.668Z"
6 | },
7 | {
8 | "username": "snelson",
9 | "petId": "S-1",
10 | "checkOutDate": "2019-03-22T19:45:24.668Z"
11 | },
12 | {
13 | "username": "jbronson",
14 | "petId": "R-1",
15 | "checkOutDate": "2019-03-21T19:45:24.668Z"
16 | }
17 | ]
18 |
--------------------------------------------------------------------------------
/src/resolvers/Customer.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | async currentPets(parent, args, { pets, checkouts }) {
3 | let allPetsArray = await pets.find().toArray();
4 | let checkedOutPetsArray = await checkouts
5 | .find({ username: parent.username })
6 | .toArray();
7 | let checkoutIdsArray = checkedOutPetsArray.map(pet => pet.petId);
8 | if (checkedOutPetsArray !== []) {
9 | let petList = allPetsArray.filter(pet =>
10 | checkoutIdsArray.includes(pet.id)
11 | );
12 | return petList;
13 | }
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/resolvers/index.js:
--------------------------------------------------------------------------------
1 | const Query = require("./Query");
2 | const Mutation = require("./Mutation");
3 | const Subscription = require("./Subscription");
4 | const Customer = require("./Customer");
5 | const Date = require("./Date");
6 | const Pet = require("./Pet");
7 | const ExoticPet = require("./ExoticPet");
8 | const FamilyPet = require("./FamilyPet");
9 | const Checkout = require("./Checkout");
10 |
11 | module.exports = {
12 | Query,
13 | Mutation,
14 | Subscription,
15 | Date,
16 | Customer,
17 | Pet,
18 | ExoticPet,
19 | FamilyPet,
20 | Checkout
21 | };
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | .env
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 | .gatsby-context.js
30 | .sass-cache/
31 | .cache/
32 | .DS_Store
33 | public
34 | .tern-port
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pet-library",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "import": "node -r dotenv/config ./import-data",
8 | "postinstall": "node ./import-data",
9 | "start": "node ./src",
10 | "dev": "nodemon -r dotenv/config ./src -e graphql,js"
11 | },
12 | "engines": {
13 | "node": "13.1.0"
14 | },
15 | "keywords": [],
16 | "author": "",
17 | "license": "MIT",
18 | "dependencies": {
19 | "apollo-server": "^2.4.8",
20 | "bcrypt": "3.0.8",
21 | "dotenv": "^7.0.0",
22 | "graphql": "^14.1.1",
23 | "jsonwebtoken": "^8.5.0",
24 | "mongodb": "^4.1.0",
25 | "shortid": "^2.2.14"
26 | },
27 | "devDependencies": {
28 | "nodemon": "^1.18.10"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/resolvers/Pet.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | async inCareOf(parent, args, { customers, checkouts }) {
3 | let user = await checkouts.findOne({ petId: parent.id });
4 | if (user) {
5 | return customers.findOne({ username: user.username });
6 | } else {
7 | return null;
8 | }
9 | },
10 | async status(parent, args, { checkouts }) {
11 | let checkedOutPet = await checkouts.findOne({ petId: parent.id });
12 | return !checkedOutPet ? "AVAILABLE" : "CHECKEDOUT";
13 | },
14 | async dueDate(parent, args, { checkouts }) {
15 | let checkoutDate = await checkouts.findOne({ petId: parent.id });
16 | if (checkoutDate) {
17 | let date = new Date(checkoutDate.checkoutDate);
18 | let plusThree = date.getTime() + 3 * 60000;
19 | return new Date(plusThree).toISOString();
20 | } else {
21 | return null;
22 | }
23 | },
24 | __resolveType: parent => {
25 | if (parent.sleepAmount) {
26 | return "Cat";
27 | } else if (parent.favoriteFood) {
28 | return "Rabbit";
29 | } else if (parent.chill) {
30 | return "Stingray";
31 | } else {
32 | return "Dog";
33 | }
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/import-data/customers.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "John Bronson",
4 | "username": "jbronson",
5 | "password": "$2b$10$tYnVVnVCBDWC0DEtyXw.vew3ko3GTaX.5c3VoVfMPi9BzbwIWiYSO",
6 | "dateCreated": "2019-03-24T02:19:49.025Z",
7 | "checkoutHistory": [
8 | {
9 | "petId": "R-2",
10 | "username": "jbronson",
11 | "checkOutDate": "2019-03-25T20:20:13.615Z",
12 | "checkInDate": "2019-03-25T20:23:26.125Z"
13 | },
14 | {
15 | "petId": "C-2",
16 | "username": "jbronson",
17 | "checkOutDate": "2019-03-26T20:19:57.961Z",
18 | "checkInDate": "2019-03-26T20:19:59.297Z"
19 | }
20 | ]
21 | },
22 | {
23 | "name": "Paul Benson",
24 | "username": "pbenson",
25 | "password": "$2b$10$YpllnOeS33gtILG7xSnld.ZQS3UWMTo4D1f4wPR3LoZcgF10mzZkm",
26 | "dateCreated": "2019-03-24T02:19:20.848Z",
27 | "checkoutHistory": []
28 | },
29 | {
30 | "name": "Shana Nelson",
31 | "username": "snelson",
32 | "password": "$2b$10$RuYv5lx81pPxiowOlmUjhe0ts61Z3JGFkmcjGNBgKGM2H2ZejzZsS",
33 | "dateCreated": "2019-03-24T02:19:36.554Z",
34 | "checkoutHistory": []
35 | },
36 | {
37 | "name": "Gran Janson",
38 | "username": "gjanson",
39 | "password": "$2b$10$j3b1rIzwH85ceirKtDf2ZeM3RL8jxhFwnWwkPOrYQftR5hIGROxBO",
40 | "dateCreated": "2019-03-24T02:23:14.564Z",
41 | "checkoutHistory": []
42 | }
43 | ]
44 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const { ApolloServer, PubSub } = require("apollo-server");
2 | const { readFileSync } = require("fs");
3 | const { MongoClient } = require("mongodb");
4 | const jwt = require("jsonwebtoken");
5 | const resolvers = require("./resolvers");
6 | const path = require("path");
7 |
8 | const typeDefs = readFileSync(
9 | path.join(__dirname, "typeDefs.graphql"),
10 | "UTF-8"
11 | );
12 |
13 | const start = async () => {
14 | const uri =
15 | process.env.MONGODB_URI || "mongodb://localhost:27017/pet-library";
16 | console.log("connection to ", uri);
17 | const client = await MongoClient.connect(uri, {
18 | useNewUrlParser: true
19 | });
20 |
21 | const db = client.db();
22 |
23 | const pubsub = new PubSub();
24 |
25 | const context = async ({ req, connection }) => {
26 | const pets = db.collection("pets");
27 | const customers = db.collection("customers");
28 | const checkouts = db.collection("checkouts");
29 | let currentCustomer;
30 | let token;
31 |
32 | if (connection) {
33 | token = null;
34 | } else if (req.headers.authorization) {
35 | token = req.headers.authorization.replace("Bearer ", "");
36 | } else {
37 | token = null;
38 | }
39 |
40 | if (token) {
41 | try {
42 | const decoded = jwt.verify(token, process.env.SECRET);
43 | currentCustomer = await customers.findOne({
44 | username: decoded.username
45 | });
46 | } catch (e) {
47 | console.log("context token error: ", e.message);
48 | }
49 | }
50 |
51 | return { pets, customers, checkouts, currentCustomer, pubsub };
52 | };
53 |
54 | const PORT = process.env.PORT || 4000;
55 |
56 | const server = new ApolloServer({
57 | typeDefs,
58 | resolvers,
59 | context
60 | });
61 |
62 | server
63 | .listen({ port: PORT })
64 | .then(({ port }) => console.log(`Server running at ${port}`));
65 | };
66 |
67 | start();
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # Pet Library API
8 |
9 | ## Overview
10 |
11 | The Pet Library is a real pet library checkout system for a fake pet library. The purpose of the Pet Library is to teach developers how to work with the GraphQL Query Language and how to send queries, mutations, and subscriptions to this API.
12 |
13 | ## Installation
14 |
15 | ### 1. Clone or download this repository.
16 |
17 | ```
18 | git clone https://github.com/moonhighway/pet-library.git
19 | cd pet-library
20 | ```
21 |
22 | ### 2. Install the dependencies.
23 |
24 | ```
25 | npm install
26 | ```
27 |
28 | Or use yarn:
29 |
30 | ```
31 | yarn
32 | ```
33 |
34 | ### 3. Set up Mongo locally.
35 |
36 | This project will use Mongo as a database. If you aren't a user of Mongo already, you can install Mongo locally, or use [mLab](https://mlab.com), a cloud-based version of Mongo, for this app.
37 |
38 | For further installation instructions, check out these resources:
39 |
40 | - [Mongo Installation for Mac](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/)
41 | - [Mongo Installation for PC](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/)
42 | - [Local Instructions for Mongo & mLab](https://gist.github.com/eveporcello/e80a90f39de3b63a9c20136536f477df)
43 |
44 | ### 4. Add a `.env` file to the root of your project.
45 |
46 | You will need to add variables for `MONGODB_URI` and `SECRET`.
47 |
48 | - `MONGODB_URI` is the route where your installation of Mongo is running. This usually runs at `mongodb://localhost:27017/pet-library`.
49 | - `SECRET` is just a text string (can be anything) so that the user auth works as expected:
50 |
51 | ```
52 | MONGODB_URI=
53 | SECRET=
54 | ```
55 |
56 | ## Starting the Project - Dev Mode
57 |
58 | Run the following command: `npm run dev`.
59 |
60 | This project was created by [Alex Banks](http://twitter.com/moontahoe) and
61 | [Eve Porcello](http://twitter.com/eveporcello) from
62 | [Moon Highway](https://www.moonhighway.com).
63 |
--------------------------------------------------------------------------------
/import-data/index.js:
--------------------------------------------------------------------------------
1 | const { MongoClient } = require("mongodb");
2 | const pets = require("./pets.json");
3 | const customers = require("./customers.json");
4 | const checkouts = require("./checkouts.json");
5 |
6 | console.log(`
7 |
8 | IMPORTING MONGODB DATA
9 |
10 | `);
11 |
12 | const importCollection = async (db, collectionName, data) => {
13 | try {
14 | console.log(` creating ${collectionName} collection`);
15 | let collection = await db.createCollection(collectionName);
16 | console.log(` importing ${data.length} ${collectionName}`);
17 | let results = await collection.insertMany(data);
18 | if (results.result.ok) {
19 | console.log(` success ${results.result.n} ${collectionName} imported`);
20 | } else {
21 | console.log(` Error importing ${collectionName}`);
22 | process.exit(1);
23 | }
24 | } catch (e) {
25 | console.log(` error importing ${collectionName}`);
26 | console.log(" ERROR: ", e.message);
27 | process.exit(1);
28 | }
29 | };
30 |
31 | const start = async () => {
32 | let db;
33 |
34 | //
35 | // Connect to Mongo Database
36 | //
37 |
38 | try {
39 | let uri =
40 | process.env.MONGODB_URI || "mongodb://localhost:27017/pet-library";
41 | console.log("connecting to ", uri);
42 | const client = await MongoClient.connect(uri, {
43 | useNewUrlParser: true
44 | });
45 | db = client.db();
46 | } catch (e) {
47 | console.log(
48 | "error connection to mongodb at ",
49 | process.env.MONGODB_URI || "mongodb://localhost:27017/pet-library"
50 | );
51 | console.log("ERROR: ", e.message);
52 | process.exit(1);
53 | }
54 |
55 | //
56 | // Drop all connections
57 | //
58 |
59 | console.log("\n\ndropping database collections");
60 | try {
61 | await db.collection("pets").drop();
62 | } catch (e) {}
63 |
64 | try {
65 | await db.collection("customers").drop();
66 | } catch (e) {}
67 |
68 | try {
69 | await db.collection("checkouts").drop();
70 | } catch (e) {}
71 |
72 | //
73 | // Import all collections
74 | //
75 |
76 | console.log("\n\nimporting data\n\n");
77 | await Promise.all([
78 | importCollection(db, "pets", pets),
79 | importCollection(db, "customers", customers),
80 | importCollection(db, "checkouts", checkouts)
81 | ]);
82 |
83 | console.log(`
84 |
85 | DATA SUCCESSFULLY IMPORTED
86 |
87 | `);
88 |
89 | process.exit(0);
90 | };
91 |
92 | start();
93 |
--------------------------------------------------------------------------------
/src/resolvers/Query.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | async totalPets(parent, args, { pets }) {
3 | return pets.countDocuments();
4 | },
5 | async availablePets(parent, args, { pets, checkouts }) {
6 | let totalPets = await pets.countDocuments();
7 | let totalCheckouts = await checkouts.countDocuments();
8 | return totalPets - totalCheckouts;
9 | },
10 | async checkedOutPets(parent, args, { checkouts }) {
11 | return checkouts.countDocuments();
12 | },
13 | allPets: (parent, args, { pets }) => {
14 | return pets.find().toArray();
15 | },
16 | allAvailablePets: async (parent, args, { pets, checkouts }) => {
17 | let allPetsArray = await pets.find().toArray();
18 | let checkoutsArray = await checkouts.find().toArray();
19 | let checkoutIdsArray = checkoutsArray.map(pet => pet.petId);
20 | let availableAllPets = allPetsArray.filter(
21 | pet => !checkoutIdsArray.includes(pet.id)
22 | );
23 | return availableAllPets;
24 | },
25 | allCheckedOutPets: async (parent, args, { pets, checkouts }) => {
26 | let allPetsArray = await pets.find().toArray();
27 | let checkoutsArray = await checkouts.find().toArray();
28 | let checkoutIdsArray = checkoutsArray.map(pet => pet.petId);
29 | let checkedOutAllPets = allPetsArray.filter(pet =>
30 | checkoutIdsArray.includes(pet.id)
31 | );
32 | return checkedOutAllPets;
33 | },
34 | petById: (parent, { id }, { pets }) => pets.findOne({ id }),
35 | familyPets: async (parent, args, { pets }) => {
36 | let allPetsArray = await pets.find().toArray();
37 | return allPetsArray.filter(pet => pet.good || pet.sleepAmount);
38 | },
39 |
40 | exoticPets: async (parent, args, { pets }) => {
41 | let allPetsArray = await pets.find().toArray();
42 | return allPetsArray.filter(pet => pet.fast || pet.favoriteFood);
43 | },
44 |
45 | totalCustomers: (parent, args, { customers }) => customers.countDocuments(),
46 |
47 | allCustomers: (parent, args, { customers }) => customers.find().toArray(),
48 |
49 | customersWithPets: async (parent, args, { checkouts, customers }) => {
50 | let checkoutsArray = await checkouts.find().toArray();
51 | let customersArray = await customers.find().toArray();
52 | let usernamesWithPets = [
53 | ...new Set(checkoutsArray.map(checkout => checkout.username))
54 | ];
55 | return customersArray.filter(customer =>
56 | usernamesWithPets.includes(customer.username)
57 | );
58 | },
59 | me: (parent, args, { currentCustomer }) => currentCustomer
60 | };
61 |
--------------------------------------------------------------------------------
/src/resolvers/Mutation.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require("bcrypt");
2 | const { generate } = require("shortid");
3 | const jwt = require("jsonwebtoken");
4 |
5 | module.exports = {
6 | async createAccount(
7 | parent,
8 | {
9 | input: { name, username, password }
10 | },
11 | { customers }
12 | ) {
13 | let existingCustomer = await customers.findOne({ username });
14 | if (!existingCustomer) {
15 | let hash = bcrypt.hashSync(password, 10);
16 | let newCustomer = {
17 | id: generate(),
18 | name,
19 | username,
20 | currentPets: [],
21 | checkoutHistory: [],
22 | password: hash,
23 | dateCreated: new Date().toISOString()
24 | };
25 | await customers.insertOne(newCustomer);
26 | return newCustomer;
27 | } else {
28 | throw new Error("An account with this username already exists.");
29 | }
30 | },
31 | async logIn(parent, { username, password }, { customers, currentCustomer }) {
32 | let customer = await customers.findOne({ username });
33 | if (!customer) {
34 | throw new Error(`Account with that username: ${username} not found.`);
35 | }
36 | if (bcrypt.compareSync(password, customer.password)) {
37 | currentCustomer = customer;
38 | const token = jwt.sign(
39 | { username: currentCustomer.username },
40 | process.env.SECRET
41 | );
42 | currentCustomer.token = token;
43 | return {
44 | customer: currentCustomer,
45 | token
46 | };
47 | } else {
48 | throw new Error("Incorrect password.");
49 | }
50 | },
51 | async checkOut(
52 | parent,
53 | { id },
54 | { pets, customers, checkouts, currentCustomer }
55 | ) {
56 | if (!currentCustomer) {
57 | throw new Error("You have to be logged in to check out a pet.");
58 | }
59 | let pet = await checkouts.findOne({ petId: id });
60 | let petExists = await pets.findOne({ id });
61 | if (pet) {
62 | throw new Error("Sorry, this pet is already checked out.");
63 | } else if (petExists) {
64 | let checkout = {
65 | petId: id,
66 | username: currentCustomer.username,
67 | checkOutDate: new Date().toISOString()
68 | };
69 |
70 | await checkouts.replaceOne(checkout, checkout, {
71 | upsert: true
72 | });
73 | return {
74 | customer: await customers.findOne({
75 | username: currentCustomer.username
76 | }),
77 | pet: await pets.findOne({ id }),
78 | checkOutDate: checkout.checkOutDate
79 | };
80 | } else {
81 | throw new Error("This pet does not exist.");
82 | }
83 | },
84 | async checkIn(
85 | parent,
86 | { id },
87 | { pets, customers, checkouts, currentCustomer, pubsub }
88 | ) {
89 | if (!currentCustomer) {
90 | throw new Error("You have to be logged in to check in a pet.");
91 | }
92 | let pet = await checkouts.findOne({ petId: id });
93 | if (!pet) {
94 | throw new Error("This pet is not checked out.");
95 | } else {
96 | let checkout = await checkouts.findOne({ petId: id });
97 | delete checkout._id;
98 | checkout.checkInDate = new Date().toISOString();
99 |
100 | await checkouts.deleteOne({ petId: id });
101 |
102 | await customers.updateOne(
103 | { username: currentCustomer.username },
104 | {
105 | $set: {
106 | checkoutHistory: [checkout, ...currentCustomer.checkoutHistory]
107 | }
108 | }
109 | );
110 |
111 | pubsub.publish("pet-returned", { petReturned: checkout });
112 |
113 | return checkout;
114 | }
115 | }
116 | };
117 |
--------------------------------------------------------------------------------
/import-data/pets.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "C-1",
4 | "name": "Biscuit",
5 | "weight": 10.2,
6 | "photo": {
7 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/biscuit_tn_njuvwl.jpg",
8 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764166/biscuit_inhtt3.jpg"
9 | },
10 | "sleepAmount": 21,
11 | "curious": true
12 | },
13 | {
14 | "id": "C-2",
15 | "name": "Jungle",
16 | "weight": 9.7,
17 | "photo": {
18 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/jungle_tn_ayjti1.jpg",
19 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764112/jungle_evdm47.jpg"
20 | },
21 | "sleepAmount": 16,
22 | "curious": true
23 | },
24 | {
25 | "id": "C-3",
26 | "name": "Benji",
27 | "weight": 10.9,
28 | "photo": {
29 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/benji_tn_cjccyb.jpg",
30 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764036/benji_rkg884.jpg"
31 | },
32 | "sleepAmount": 20,
33 | "curious": true
34 | },
35 | {
36 | "id": "C-4",
37 | "name": "Beebee",
38 | "weight": 13.3,
39 | "photo": {
40 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/beebee_tn_ljyrpc.jpg",
41 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763988/beebee_oqtgld.jpg"
42 | },
43 | "sleepAmount": 3,
44 | "curious": true
45 | },
46 | {
47 | "id": "C-5",
48 | "name": "Pillow",
49 | "weight": 8.3,
50 | "photo": {
51 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/pillow_tn_owau6w.jpg",
52 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764042/pillow_j9yenx.jpg"
53 | },
54 | "sleepAmount": 13,
55 | "curious": false
56 | },
57 | {
58 | "id": "C-6",
59 | "name": "Buddy",
60 | "weight": 9.2,
61 | "photo": {
62 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/buddy_tn_syqsun.jpg",
63 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764293/buddy_oln5er.jpg"
64 | },
65 | "sleepAmount": 18,
66 | "curious": true
67 | },
68 | {
69 | "id": "C-7",
70 | "name": "Mini",
71 | "weight": 5.2,
72 | "photo": {
73 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/mini_tn_jpjxnq.jpg",
74 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764180/mini_stjtkm.jpg"
75 | },
76 | "sleepAmount": 1,
77 | "curious": true
78 | },
79 | {
80 | "id": "D-1",
81 | "name": "Danton",
82 | "weight": 50.4,
83 | "photo": {
84 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/danton_tn_dg5hwl.jpg",
85 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763990/danton_et1gql.jpg"
86 | },
87 | "good": true
88 | },
89 | {
90 | "id": "D-2",
91 | "name": "Archy",
92 | "weight": 19.9,
93 | "photo": {
94 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/archy_tn_qlgg24.jpg",
95 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763883/archy_yehmk9.jpg"
96 | },
97 | "good": true
98 | },
99 | {
100 | "id": "D-3",
101 | "name": "Otis",
102 | "weight": 50.4,
103 | "photo": {
104 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/otis_tn_lhim8b.jpg",
105 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764030/otis_s6rnrm.jpg"
106 | },
107 | "good": true
108 | },
109 | {
110 | "id": "D-4",
111 | "name": "Luna",
112 | "weight": 75.4,
113 | "photo": {
114 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/luna_tn_ghpxtv.jpg",
115 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763994/luna_zdqqlx.jpg"
116 | },
117 | "good": true
118 | },
119 | {
120 | "id": "D-5",
121 | "name": "Canela",
122 | "weight": 100.4,
123 | "photo": {
124 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/canela_tn_qxvlai.jpg",
125 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763877/canela_fwp5mm.jpg"
126 | },
127 | "good": true
128 | },
129 | {
130 | "id": "D-6",
131 | "name": "Mehla",
132 | "weight": 90.3,
133 | "photo": {
134 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/mehla_tn_cxccvz.jpg",
135 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764044/mehla_yvsqfb.jpg"
136 | },
137 | "good": true
138 | },
139 | {
140 | "id": "D-7",
141 | "name": "Rainier McCheddarton",
142 | "weight": 70.4,
143 | "photo": {
144 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/rainier_tn_naxsxt.jpg",
145 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763878/rainier_yeodlm.jpg"
146 | },
147 | "good": true
148 | },
149 | {
150 | "id": "D-8",
151 | "name": "Pax",
152 | "weight": 52.7,
153 | "photo": {
154 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/pax_tn_yhw1gz.jpg",
155 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764041/pax_aj6wqh.jpg"
156 | },
157 | "good": true
158 | },
159 | {
160 | "id": "D-9",
161 | "name": "Lucie",
162 | "weight": 44.7,
163 | "photo": {
164 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/lucie_tn_we1rji.jpg",
165 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763973/lucie_ro5isy.jpg"
166 | },
167 | "good": true
168 | },
169 | {
170 | "id": "S-1",
171 | "name": "Lazer",
172 | "weight": 15.7,
173 | "photo": {
174 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/lazer_tn_jgcolg.jpg",
175 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763985/lazer_yrwvzu.jpg"
176 | },
177 | "chill": 9,
178 | "fast": true
179 | },
180 | {
181 | "id": "S-2",
182 | "name": "Switchblade",
183 | "weight": 20.7,
184 | "photo": {
185 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764389/thumbs/switchblade_tn_qcmybh.jpg",
186 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764363/switchblade_emzkx3.jpg"
187 | },
188 | "chill": 10,
189 | "fast": true
190 | },
191 | {
192 | "id": "S-3",
193 | "name": "Steve",
194 | "weight": 20.4,
195 | "photo": {
196 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/steve_tn_klcjkw.jpg",
197 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582763991/steve_w7mqee.jpg"
198 | },
199 | "chill": 6,
200 | "fast": false
201 | },
202 | {
203 | "id": "S-4",
204 | "name": "Pluto",
205 | "weight": 66.1,
206 | "photo": {
207 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/pluto_tn_z3d6hs.jpg",
208 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764104/pluto_sbdj3l.jpg"
209 | },
210 | "chill": 6,
211 | "fast": false
212 | },
213 | {
214 | "id": "R-1",
215 | "name": "Pip",
216 | "weight": 3.7,
217 | "photo": {
218 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/pip_tn_xhrimm.jpg",
219 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764136/pip_o0snc2.jpg"
220 | },
221 | "favoriteFood": "carrots",
222 | "floppy": 8
223 | },
224 | {
225 | "id": "R-2",
226 | "name": "Sweetums",
227 | "weight": 4.6,
228 | "photo": {
229 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764389/thumbs/sweetums_tn_zz3r4f.jpg",
230 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764027/sweetums_prsppm.jpg"
231 | },
232 | "favoriteFood": "lettuce",
233 | "floppy": 9
234 | },
235 | {
236 | "id": "R-3",
237 | "name": "Jerry",
238 | "weight": 5.5,
239 | "photo": {
240 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/jerry_tn_zdcmvm.jpg",
241 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764083/jerry_drpooy.jpg"
242 | },
243 | "favoriteFood": "spaghetti",
244 | "floppy": 5
245 | },
246 | {
247 | "id": "R-4",
248 | "name": "Jason",
249 | "weight": 2.7,
250 | "photo": {
251 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764387/thumbs/jason_tn_fvjgiv.jpg",
252 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764178/jason_yzb3sg.jpg"
253 | },
254 | "favoriteFood": "carrots",
255 | "floppy": 3
256 | },
257 | {
258 | "id": "R-5",
259 | "name": "Peep",
260 | "weight": 6.5,
261 | "photo": {
262 | "thumb": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764388/thumbs/peep_tn_z4ckwy.jpg",
263 | "full": "https://res.cloudinary.com/hzrulbrds/image/upload/v1582764163/peep_g42p7k.jpg"
264 | },
265 | "favoriteFood": "enchiladas",
266 | "floppy": 10
267 | }
268 | ]
269 |
--------------------------------------------------------------------------------
/src/typeDefs.graphql:
--------------------------------------------------------------------------------
1 | """
2 | A custom type describing a date object serialized as an ISO String
3 | """
4 | scalar Date
5 |
6 | """
7 | The Pet Library would not be the Pet Library without a `Pet`. The `Pet` type describes an animal that is part of the pet database. Once a `Pet` has been added to the Pet Library, the pet can be checked in and checked out of the library.
8 | """
9 | interface Pet {
10 | "A unique identifier for each `Pet`. This value also serves as a lookup for the `petById` query."
11 | id: ID!
12 | "The pet's given name"
13 | name: String!
14 | "The weight (in pounds) of the pet in question."
15 | weight: Float
16 | "The current checkout status of the pet. Must one of the values of the `PetStatus` enumerator: AVAILABLE or CHECKEDOUT."
17 | status: PetStatus
18 | "A `Photo` type. The `Photo` type is an object containing `thumb` and `full` for thumbnail and fullsize image links."
19 | photo: Photo
20 | "If this pet is checked out (status: CHECKEDOUT), this field will return a date string describing when the pet is due back. Always return pets on time."
21 | dueDate: Date
22 | "If this pet is checked out (status: CHECKEDOUT), this field will return the `Customer` who checked this `Pet` out and is responsible for is security and overall happiness."
23 | inCareOf: Customer
24 | }
25 |
26 | """
27 | A `Cat` is a small, carnivorous mammal who is usually sleeping.
28 | """
29 | type Cat implements Pet {
30 | "A unique identifier for each `Cat`."
31 | id: ID!
32 | "The cat's given name"
33 | name: String!
34 | "The weight (in pounds) of the cat in question."
35 | weight: Float
36 | "The current checkout status of the cat. Must one of the values of the `PetStatus` enumerator: AVAILABLE or CHECKEDOUT."
37 | status: PetStatus
38 | "A `Photo` type. The `Photo` type is an object containing `thumb` and `full` for thumbnail and fullsize image links."
39 | photo: Photo
40 | "If this cat is checked out (status: CHECKEDOUT), this field will return a date string describing when the cat is due back. Always return cats on time."
41 | dueDate: Date
42 | "If this pet is checked out (status: CHECKEDOUT), this field will return the `Customer` who checked this `Cat` out and is responsible for is security and overall happiness."
43 | inCareOf: Customer
44 | "The number of hours a cat sleeps per day."
45 | sleepAmount: Int
46 | "A Boolean describing whether or not this `Cat` is curious. This is almost always `true`."
47 | curious: Boolean
48 | }
49 |
50 | """
51 | A `Dog` is a wolf descendant that you can dress in a little jacket.
52 | """
53 | type Dog implements Pet {
54 | "A unique identifier for each `Dog`."
55 | id: ID!
56 | "The dog's given name."
57 | name: String!
58 | "The weight (in pounds) of the dog in question."
59 | weight: Float
60 | "The current checkout status of the dog. Must one of the values of the `PetStatus` enumerator: AVAILABLE or CHECKEDOUT."
61 | status: PetStatus
62 | "A `Photo` type. The `Photo` type is an object containing `thumb` and `full` for thumbnail and fullsize image links."
63 | photo: Photo
64 | "If this dog is checked out (status: CHECKEDOUT), this field will return a date string describing when the dog is due back. Always return dogs on time."
65 | dueDate: Date
66 | "If this dog is checked out (status: CHECKEDOUT), this field will return the `Customer` who checked this `Dog` out and is responsible for is security and overall happiness."
67 | inCareOf: Customer
68 | "A Boolean describing whether or not this `Dog` is a good dog. This is almost always `true`."
69 | good: Boolean
70 | }
71 |
72 | """
73 | A `Rabbit` hops and flops around.
74 | """
75 | type Rabbit implements Pet {
76 | "A unique identifier for each `Rabbit`."
77 | id: ID!
78 | "The rabbit's given name."
79 | name: String!
80 | "The weight (in pounds) of the rabbit in question."
81 | weight: Float
82 | "The current checkout status of the rabbit. Must one of the values of the `PetStatus` enumerator: AVAILABLE or CHECKEDOUT."
83 | status: PetStatus
84 | "A `Photo` type. The `Photo` type is an object containing `thumb` and `full` for thumbnail and fullsize image links."
85 | photo: Photo
86 | "If this rabbit is checked out (status: CHECKEDOUT), this field will return a date string describing when the rabbit is due back. Always return rabbits on time."
87 | dueDate: Date
88 | "If this rabbit is checked out (status: CHECKEDOUT), this field will return the `Customer` who checked this `Rabbit` out and is responsible for is security and overall happiness."
89 | inCareOf: Customer
90 | "This field will describe the favorite food of this particular rabbit. Don't assume it's always carrots. Rabbits contain multitudes."
91 | favoriteFood: String
92 | "This field describes, on a scale of 1-10 how floppy this particular rabbit is."
93 | floppy: Int
94 | }
95 |
96 | """
97 | A `Stingray` is a cartilaginous fish related to sharks common to coastal tropical and subtropical waters that the Pet Library will let you take home for some reason.
98 | """
99 | type Stingray implements Pet {
100 | "A unique identifier for each `Stingray`."
101 | id: ID!
102 | "The stingray's given name."
103 | name: String!
104 | "The weight (in pounds) of the stingray in question."
105 | weight: Float
106 | "The current checkout status of the stingray. Must one of the values of the `PetStatus` enumerator: AVAILABLE or CHECKEDOUT."
107 | status: PetStatus
108 | "A `Photo` type. The `Photo` type is an object containing `thumb` and `full` for thumbnail and fullsize image links."
109 | photo: Photo
110 | "If this stingray is checked out (status: CHECKEDOUT), this field will return a date string describing when the stingray is due back. Always return stingrays on time."
111 | dueDate: Date
112 | "If this stingray is checked out (status: CHECKEDOUT), this field will return the `Customer` who checked this `Stingray` out and is responsible for is security and overall happiness"
113 | inCareOf: Customer
114 | "Is this stingray chill? This will tell you how chill on a scale of 1 (least chill) - 10 (most chill)."
115 | chill: Int
116 | "Is this stingray fast? If so, this field will return `true`."
117 | fast: Boolean
118 | }
119 |
120 | """
121 | Anyone who has created an account at the Pet Library is a `Customer`. Customers can login and checkout/checkin pets to the Pet Library.
122 | """
123 | type Customer {
124 | "A unique username for the `Customer`."
125 | username: ID!
126 | "The `Customer`'s name as a string."
127 | name: String!
128 | "The date that this `Customer` was created (whenever the `createAccount` mutation was sent)."
129 | dateCreated: Date
130 | "Returns a list of `Pet` objects that the `Customer` currently has checked out. If the `Customer` does not have any pets checked out, this value will be an empty array."
131 | currentPets: [Pet!]!
132 | "Returns a list of `Checkout`s, objects that describe a pet checkout transaction. If the `Customer` has never checked out a pet, this value will be an empty array."
133 | checkoutHistory: [Checkout!]!
134 | }
135 |
136 | """
137 | Each `Pet` has a `Photo`, and this object describes the structure of that `Photo`: a fullsize and thumbnail version of the same `Pet` photo.
138 | """
139 | type Photo {
140 | "The url for a fullsize photo of a `Pet`."
141 | full: String
142 | "The url for a thumbnail photo of a `Pet`."
143 | thumb: String
144 | }
145 |
146 | """
147 | This enumeration type tells us whether or not a pet is checked out. If `AVAILABLE`, a `Customer` can take this pet home. If `CHECKEDOUT`, a `Customer` has to wait for the pet to be returned.
148 | """
149 | enum PetStatus {
150 | "A `Pet` with this status can be checked out."
151 | AVAILABLE
152 | "A `Pet` with this status has already been checked out and is unavailable."
153 | CHECKEDOUT
154 | }
155 |
156 | """
157 | When a user creates an account, they must send `name`, `username`, and `password` to the `createAccount` mutation. This input type wraps those fields into one fancy object.
158 | """
159 | input CreateAccountInput {
160 | "A `Customer`'s full name."
161 | name: String!
162 | "A `Customer`'s unique user name."
163 | username: ID!
164 | "A `Customer`'s password."
165 | password: String!
166 | }
167 |
168 | """
169 | When a new customer creates an account, this object will be returned from the `createAccount` mutation.
170 | """
171 | type CreateAccountPayload {
172 | "The entire `Customer` object for the recently registered individual."
173 | customer: Customer
174 | "A welcome message to the new `Customer` encouraging them to log in."
175 | message: String
176 | }
177 |
178 | """
179 | When a `Customer` logs in to the Pet Library, they can get their token and customer information from the `LogInPayload` type. This is how user authentication is handled at the library.
180 | """
181 | type LogInPayload {
182 | "The entire `Customer` object for the recently logged in individual."
183 | customer: Customer
184 | "The authorization token that can be used to run queries and mutations that require login."
185 | token: String!
186 | }
187 |
188 | """
189 | Checking out a `Pet` is a dream come true for many people. Receiving `customer`, `pet`, and `checkoutDate` back as an object from the `checkOut` mutation is a dream come true for a pet librarian and a GraphQL enthusiast.
190 | """
191 | type CheckOutPayload {
192 | "The entire `Customer` object for the customer who just checked out a pet."
193 | customer: Customer
194 | "The entire `Pet` object for the pet who was just checked out."
195 | pet: Pet
196 | "The date that this `Pet` was checked out. This is used to calculate the due date."
197 | checkOutDate: Date
198 | }
199 |
200 | """
201 | Every time a `Pet` is checked out, a record of that `Checkout` is logged. This provides information about the pet, when the pet was checked out and in, and whether or not the pet was late. If you want to find out if a `Customer` is a delinquent, look for the `Checkout` type.
202 | """
203 | type Checkout {
204 | "The entire `Pet` object for the pet who was just checked out."
205 | pet: Pet
206 | "The date that this `Pet` was checked out. This is used to calculate the dueDate."
207 | checkOutDate: Date!
208 | "The date that this `Pet` was checked out. This is used to calculate the dueDate."
209 | checkInDate: Date!
210 | "A boolean flag to indicate whether the `Pet` was checked in late. If this value is true, this means that you blocked another person from checking out a `Pet`. You can do better than that."
211 | late: Boolean
212 | }
213 |
214 | """
215 | `FamilyPet` is a union type which means that either a `Cat` or `Dog` could be returned for each `FamilyPet`.
216 | """
217 | union FamilyPet = Cat | Dog
218 |
219 | """
220 | `ExoticPet` is a union type which means that either a `Rabbit` or `Stingray` could be returned for each `ExoticPet`.
221 | """
222 | union ExoticPet = Rabbit | Stingray
223 |
224 | """
225 | All of the Pet Library's root query types can be found in the `Query`. This list defines all of the data you can ask for with a query.
226 | """
227 | type Query {
228 | "This query returns the total number of `Pet`s that are part of the Pet Library."
229 | totalPets: Int!
230 | "This query returns the number of `Pet`s that are available for checkout."
231 | availablePets: Int!
232 | "This query returns the number of `Pet`s that have been checked out and are not available."
233 | checkedOutPets: Int!
234 | "This query returns all of the data about the `Pet`s. This is a list of `Pet` objects that are part of the Pet Library."
235 | allPets: [Pet!]!
236 | "This query returns data about the `Pet`s who are currently available and not checked out."
237 | allAvailablePets: [Pet!]!
238 | "This query returns data about the `Pet`s who are currently checked out and are not available."
239 | allCheckedOutPets: [Pet!]!
240 | "Use this query to return one `Pet` by ID. If you're not sure what the ID is, send an `allPets` query to find the pet you want to view."
241 | petById(
242 | "REQUIRED: Sending an `id` will return a pet that has that ID."
243 | id: ID!
244 | ): Pet!
245 | "This query returns a list of Family Pets, either a `Cat` or `Dog`."
246 | familyPets: [FamilyPet!]!
247 | "This query returns a list of Exotic Pets, either a `Rabbit` or `Stingray`."
248 | exoticPets: [ExoticPet!]!
249 | "This query returns the total number of customers that are Pet Library members."
250 | totalCustomers: Int!
251 | "This query returns all of the data about the customers. This is a list of `Customer` objects that are members of the Pet Library."
252 | allCustomers: [Customer!]!
253 | "This query will return a list of all of the customers who currently have pets checked out."
254 | customersWithPets: [Customer!]!
255 | "When a `Customer` is logged in and has a valid authorization token, they can send the `me` query to return data about themselves. `me` will return `null` if a `Customer` is not logged in."
256 | me: Customer
257 | }
258 |
259 | """
260 | All of the Pet Library's root mutation types can be found in the `Mutation`. Any types of data changes you want to make can be found here.
261 | """
262 | type Mutation {
263 | "When you need to create an account at the Pet Library, you'll need to send this mutation. Your name + username + password is all you need to be given dominion over cats, dogs, rabbits, and/or stingrays."
264 | createAccount(
265 | "REQUIRED: Send `username`, `name`, and `password` as arguments"
266 | input: CreateAccountInput!
267 | ): Customer
268 | "A mutation used to log in a registered user. If you haven't created an account yet, start with the `createAccount` mutation."
269 | logIn(
270 | "REQUIRED: Send your unique `username` as an argument to log in"
271 | username: ID!
272 | "REQUIRED: Send your `password` as an argument to log in"
273 | password: String!
274 | ): LogInPayload!
275 | "A mutation used to check out a `Pet`. Once you check out a `Pet`, that pet is in your care, even the stingrays."
276 | checkOut(
277 | "This id will be used to locate the `Pet` and check them out."
278 | id: ID!
279 | ): CheckOutPayload!
280 | "A mutation used to check a `Pet` back in to the Pet Library."
281 | checkIn(
282 | "This id will be used to locate the `Pet` and check them out."
283 | id: ID!
284 | ): Checkout!
285 | }
286 |
287 | type Subscription {
288 | petReturned: Checkout
289 | }
290 |
--------------------------------------------------------------------------------