From 4e69b0dbccda89c98609e033b2e84849b4e16a4e Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Mon, 6 Jun 2022 05:35:16 +0300 Subject: [PATCH 01/13] Add endpoints for getting ,deleteing ,searching and posting questions --- backend/flaskr/__init__.py | 102 +++++++++++++++++++++++++++++++++++-- backend/models.py | 4 +- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 531034738..3b69c250d 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -1,3 +1,4 @@ +import json import os from flask import Flask, request, abort, jsonify from flask_sqlalchemy import SQLAlchemy @@ -12,7 +13,14 @@ def create_app(test_config=None): # create and configure the app app = Flask(__name__) setup_db(app) + CORS(app) + @app.after_request + def after_request(response): + response.headers.add("Access-Control-Allow-Headers","Content-Type,Authorization,true") + response.headers.add('Access-Control-Allow-Methods','GET,POST,OPTIONS,DELETE') + + return response """ @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs """ @@ -26,6 +34,18 @@ def create_app(test_config=None): Create an endpoint to handle GET requests for all available categories. """ + @app.route('/categories',methods=['GET']) + def get_all_questions(): + try: + categories=Category.query.order_by(Category.id).all() + formatted_categories=[category.format() for category in categories] + return jsonify({ + "success":True, + "categories":formatted_categories, + "total_categories":len(categories) + }) + except: + abort(404) """ @@ -40,7 +60,22 @@ def create_app(test_config=None): ten questions per page and pagination at the bottom of the screen for three pages. Clicking on the page numbers should update the questions. """ - + @app.route('/questions',methods=['GET']) + def get_paginated_questions(): + try: + page=request.args.get('page',1,type=int) + start=(page -1) * QUESTIONS_PER_PAGE + end=start + QUESTIONS_PER_PAGE + questions=Question.query.order_by(Question.id).all() + current_category=[question.format() for question in questions] + return jsonify({ + "success":True, + "books":current_category[start:end], + "total_questions":len(Question.query.all()) + }) + except: + abort(404) + """ @TODO: Create an endpoint to DELETE question using a question ID. @@ -48,18 +83,50 @@ def create_app(test_config=None): TEST: When you click the trash icon next to a question, the question will be removed. This removal will persist in the database and when you refresh the page. """ - + @app.route('/questions/',methods=['DELETE']) + def delete_question(question_id): + question=Question.query.filter(Question.id==question_id).one_or_none() + if question is None: + abort(422) + question.delete() + return jsonify({ + "success":True, + "delete_id":question.id, + "total_questions":len(Question.query.all()) + }) """ @TODO: Create an endpoint to POST a new question, which will require the question and answer text, category, and difficulty score. - + TEST: When you submit a question on the "Add" tab, the form will clear and the question will appear at the end of the last page of the questions list in the "List" tab. """ - + @app.route('/questions',methods=['POST']) + def create_question(): + body=request.get_json() + add_question=body.get('question',None) + new_answer=body.get('answer',None) + question_category=body.get('answer',None) + new_difficulty_score=body.get('difficulty',None) + try: + new_question=Question( + question=add_question, + answer=new_answer, + category=question_category, + difficulty=new_difficulty_score + ) + new_question.insert() + return jsonify({ + "success":True, + "created":new_question.id, + "total_questions":len(Question.query.all()) + + }) + except: + abort(405) """ @TODO: Create a POST endpoint to get questions based on a search term. @@ -70,7 +137,24 @@ def create_app(test_config=None): only question that include that string within their question. Try using the word "title" to start. """ - + @app.route('/questions/search',methods=['POST']) + def search_question(): + body=request.get_json() + search_term=body.get('searchTerm',None) + try: + if search_term: + page=request.args.get('page',1,type=int) + start=(page -1)* QUESTIONS_PER_PAGE + end=start +QUESTIONS_PER_PAGE + search_query=Question.query.filter_by(Question.question.ilike('%'+search_term+'%')).all() + formatted_search=[question.format() for question in search_query] + return jsonify({ + "success":True, + "questions":formatted_search[start:end], + "total_results":len(search_query) + }) + except: + abort(404) """ @TODO: Create a GET endpoint to get questions based on category. @@ -79,7 +163,15 @@ def create_app(test_config=None): categories in the left column will cause only questions of that category to be shown. """ + @app.route('/categories//questions',methods=['GET']) + def get_question_based_on_category(category_id): + questions=Question.query.filter(Question.category==category_id).all() + return jsonify({ + "success":True, + "questions":[question.format() for question in questions], + "total_questions":len(questions) + }) """ @TODO: Create a POST endpoint to get questions to play the quiz. diff --git a/backend/models.py b/backend/models.py index afc131824..9c21fc45b 100644 --- a/backend/models.py +++ b/backend/models.py @@ -4,7 +4,9 @@ import json database_name = 'trivia' -database_path = 'postgres://{}/{}'.format('localhost:5432', database_name) +password=5663 +database_path = 'postgres://{}:{}@{}/{}'.format('postgres',password,'localhost:5432', database_name) + db = SQLAlchemy() From b1dc63404365e434f0d3e8f126baf2f8e9b0daf4 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Tue, 7 Jun 2022 00:04:49 +0300 Subject: [PATCH 02/13] create endpoint to handle post requests --- backend/flaskr/__init__.py | 149 ++++-- backend/test_flaskr.py | 4 +- frontend/package-lock.json | 674 +++++++++++++++++++++++- frontend/src/components/QuestionView.js | 2 +- 4 files changed, 771 insertions(+), 58 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 3b69c250d..38adb6daf 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -1,5 +1,7 @@ import json import os +from tkinter import N +from unicodedata import category, name from flask import Flask, request, abort, jsonify from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS @@ -9,6 +11,7 @@ QUESTIONS_PER_PAGE = 10 + def create_app(test_config=None): # create and configure the app app = Flask(__name__) @@ -17,8 +20,10 @@ def create_app(test_config=None): @app.after_request def after_request(response): - response.headers.add("Access-Control-Allow-Headers","Content-Type,Authorization,true") - response.headers.add('Access-Control-Allow-Methods','GET,POST,OPTIONS,DELETE') + response.headers.add("Access-Control-Allow-Headers", + "Content-Type,Authorization,true") + response.headers.add('Access-Control-Allow-Methods', + 'GET,POST,OPTIONS,DELETE') return response """ @@ -34,20 +39,25 @@ def after_request(response): Create an endpoint to handle GET requests for all available categories. """ - @app.route('/categories',methods=['GET']) + @app.route('/categories', methods=['GET']) def get_all_questions(): try: - categories=Category.query.order_by(Category.id).all() - formatted_categories=[category.format() for category in categories] + categories = Category.query.order_by(Category.id).all() + + # for category in categories: + # formatted_Categories={ + # category.id:category.type + # } + formatted_Categories={category.id:category.type for category in categories} return jsonify({ - "success":True, - "categories":formatted_categories, - "total_categories":len(categories) + "success": True, + "categories": formatted_Categories, + "total_categories": len(categories), + }) except: abort(404) - """ @TODO: Create an endpoint to handle GET requests for questions, @@ -60,22 +70,25 @@ def get_all_questions(): ten questions per page and pagination at the bottom of the screen for three pages. Clicking on the page numbers should update the questions. """ - @app.route('/questions',methods=['GET']) + @app.route('/questions', methods=['GET']) def get_paginated_questions(): try: - page=request.args.get('page',1,type=int) - start=(page -1) * QUESTIONS_PER_PAGE - end=start + QUESTIONS_PER_PAGE - questions=Question.query.order_by(Question.id).all() - current_category=[question.format() for question in questions] + page = request.args.get('page', 1, type=int) + start = (page - 1) * QUESTIONS_PER_PAGE + end = start + QUESTIONS_PER_PAGE + questions = Question.query.order_by(Question.id).all() + formatted_questions = [question.format() for question in questions] + categories = Category.query.order_by(Category.id).all() + formatted_categories={category.id:category.type for category in categories} return jsonify({ - "success":True, - "books":current_category[start:end], - "total_questions":len(Question.query.all()) + "success": True, + "questions": formatted_questions[start:end], + "total_questions": len(Question.query.all()), + "categories":formatted_categories }) except: abort(404) - + """ @TODO: Create an endpoint to DELETE question using a question ID. @@ -83,16 +96,17 @@ def get_paginated_questions(): TEST: When you click the trash icon next to a question, the question will be removed. This removal will persist in the database and when you refresh the page. """ - @app.route('/questions/',methods=['DELETE']) + @app.route('/questions/', methods=['DELETE']) def delete_question(question_id): - question=Question.query.filter(Question.id==question_id).one_or_none() + question = Question.query.filter( + Question.id == question_id).one_or_none() if question is None: abort(422) question.delete() return jsonify({ - "success":True, - "delete_id":question.id, - "total_questions":len(Question.query.all()) + "success": True, + "delete_id": question.id, + "total_questions": len(Question.query.all()) }) """ @TODO: @@ -104,15 +118,17 @@ def delete_question(question_id): the form will clear and the question will appear at the end of the last page of the questions list in the "List" tab. """ - @app.route('/questions',methods=['POST']) + @app.route('/questions', methods=['POST']) def create_question(): - body=request.get_json() - add_question=body.get('question',None) - new_answer=body.get('answer',None) - question_category=body.get('answer',None) - new_difficulty_score=body.get('difficulty',None) + body = request.get_json() + add_question = body.get('question',None) + new_answer = body.get('answer',None) + question_category = body.get('category',None) + new_difficulty_score = body.get('difficulty',None) + try: - new_question=Question( + # if not(add_question in body) + new_question = Question( question=add_question, answer=new_answer, category=question_category, @@ -120,9 +136,9 @@ def create_question(): ) new_question.insert() return jsonify({ - "success":True, - "created":new_question.id, - "total_questions":len(Question.query.all()) + "success": True, + "created": new_question.id, + "total_questions": len(Question.query.all()) }) except: @@ -137,21 +153,23 @@ def create_question(): only question that include that string within their question. Try using the word "title" to start. """ - @app.route('/questions/search',methods=['POST']) + @app.route('/questions/search', methods=['POST']) def search_question(): - body=request.get_json() - search_term=body.get('searchTerm',None) + body = request.get_json() + search_term = body.get('searchTerm', None) try: if search_term: - page=request.args.get('page',1,type=int) - start=(page -1)* QUESTIONS_PER_PAGE - end=start +QUESTIONS_PER_PAGE - search_query=Question.query.filter_by(Question.question.ilike('%'+search_term+'%')).all() - formatted_search=[question.format() for question in search_query] + page = request.args.get('page', 1, type=int) + start = (page - 1) * QUESTIONS_PER_PAGE + end = start + QUESTIONS_PER_PAGE + search_query = Question.query.filter_by( + Question.question.ilike('%'+search_term+'%')).all() + formatted_search = [question.format() + for question in search_query] return jsonify({ - "success":True, - "questions":formatted_search[start:end], - "total_results":len(search_query) + "success": True, + "questions": formatted_search[start:end], + "total_results": len(search_query) }) except: abort(404) @@ -163,14 +181,15 @@ def search_question(): categories in the left column will cause only questions of that category to be shown. """ - @app.route('/categories//questions',methods=['GET']) + @app.route('/categories//questions', methods=['GET']) def get_question_based_on_category(category_id): - questions=Question.query.filter(Question.category==category_id).all() + questions = Question.query.filter( + Question.category == category_id).all() return jsonify({ - "success":True, - "questions":[question.format() for question in questions], - "total_questions":len(questions) + "success": True, + "questions": [question.format() for question in questions], + "total_questions": len(questions) }) """ @TODO: @@ -183,12 +202,38 @@ def get_question_based_on_category(category_id): one question at a time is displayed, the user is allowed to answer and shown whether they were correct or not. """ - + + @app.route('/questions',methods=['POST']) + def get_quiz_questions(): + pass """ @TODO: Create error handlers for all expected errors including 404 and 422. """ - return app + @app.errorhandler(404) + def not_found(error): + return jsonify({ + "success": False, + "error": 404, + "message": "Not Found" + }), 404 + + @app.errorhandler(422) + def unprocessable(error): + return jsonify({ + "success": False, + "error": 422, + "message": "Unprocessable" + }), 422 + + @app.errorhandler(405) + def method_not_allowed(error): + return jsonify({ + "success": False, + "error": 405, + "message": "Method Not Allowed" + }), 405 + return app diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index 16f9c5dd6..56ac6e2bc 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -15,7 +15,8 @@ def setUp(self): self.app = create_app() self.client = self.app.test_client self.database_name = "trivia_test" - self.database_path = "postgres://{}/{}".format('localhost:5432', self.database_name) + self.password=5663 + self.database_path = "postgres://{}:{}@{}/{}".format('postgres',self.password,'localhost:5432', self.database_name) setup_db(self.app, self.database_path) # binds the app to the current context @@ -33,6 +34,7 @@ def tearDown(self): TODO Write at least one test for each test for successful operation and for expected errors. """ + # Make the tests conveniently executable diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3b8729057..d6a0f3d65 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3384,6 +3384,7 @@ "anymatch": "^2.0.0", "async-each": "^1.0.1", "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", "inherits": "^2.0.3", "is-binary-path": "^1.0.0", @@ -3402,7 +3403,71 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "bundleDependencies": [ - "node-pre-gyp" + "node-pre-gyp", + "abbrev", + "ansi-regex", + "aproba", + "are-we-there-yet", + "balanced-match", + "brace-expansion", + "chownr", + "code-point-at", + "concat-map", + "console-control-strings", + "core-util-is", + "debug", + "deep-extend", + "delegates", + "detect-libc", + "fs-minipass", + "fs.realpath", + "gauge", + "glob", + "has-unicode", + "iconv-lite", + "ignore-walk", + "inflight", + "inherits", + "ini", + "is-fullwidth-code-point", + "isarray", + "minimatch", + "minimist", + "minipass", + "minizlib", + "mkdirp", + "ms", + "needle", + "nopt", + "npm-bundled", + "npm-packlist", + "npmlog", + "number-is-nan", + "object-assign", + "once", + "os-homedir", + "os-tmpdir", + "osenv", + "path-is-absolute", + "process-nextick-args", + "rc", + "readable-stream", + "rimraf", + "safe-buffer", + "safer-buffer", + "sax", + "semver", + "set-blocking", + "signal-exit", + "string_decoder", + "string-width", + "strip-ansi", + "strip-json-comments", + "tar", + "util-deprecate", + "wide-align", + "wrappy", + "yallist" ], "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", "hasInstallScript": true, @@ -3420,12 +3485,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/abbrev": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/ansi-regex": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "inBundle": true, "license": "MIT", "optional": true, @@ -3435,12 +3504,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/aproba": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/are-we-there-yet": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "inBundle": true, "license": "ISC", "optional": true, @@ -3451,12 +3524,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/balanced-match": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "inBundle": true, "license": "MIT", "optional": true, @@ -3467,12 +3544,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/chownr": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/code-point-at": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "inBundle": true, "license": "MIT", "optional": true, @@ -3482,24 +3563,32 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/core-util-is": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/debug": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "inBundle": true, "license": "MIT", "optional": true, @@ -3509,6 +3598,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/deep-extend": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "inBundle": true, "license": "MIT", "optional": true, @@ -3518,12 +3609,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/detect-libc": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "inBundle": true, "license": "Apache-2.0", "optional": true, @@ -3536,6 +3631,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/fs-minipass": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "inBundle": true, "license": "ISC", "optional": true, @@ -3545,12 +3642,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/gauge": { "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "inBundle": true, "license": "ISC", "optional": true, @@ -3567,6 +3668,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/glob": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "inBundle": true, "license": "ISC", "optional": true, @@ -3584,12 +3687,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/iconv-lite": { "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "inBundle": true, "license": "MIT", "optional": true, @@ -3602,6 +3709,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/ignore-walk": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "inBundle": true, "license": "ISC", "optional": true, @@ -3611,6 +3720,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "inBundle": true, "license": "ISC", "optional": true, @@ -3621,12 +3732,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/inherits": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/ini": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "inBundle": true, "license": "ISC", "optional": true, @@ -3636,6 +3751,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/is-fullwidth-code-point": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "inBundle": true, "license": "MIT", "optional": true, @@ -3648,12 +3765,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/isarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/minimatch": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "inBundle": true, "license": "ISC", "optional": true, @@ -3666,12 +3787,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/minimist": { "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/minipass": { "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "inBundle": true, "license": "ISC", "optional": true, @@ -3682,6 +3807,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/minizlib": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "inBundle": true, "license": "MIT", "optional": true, @@ -3691,6 +3818,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/mkdirp": { "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "inBundle": true, "license": "MIT", "optional": true, @@ -3703,12 +3832,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/ms": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/needle": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "inBundle": true, "license": "MIT", "optional": true, @@ -3726,6 +3859,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/node-pre-gyp": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "inBundle": true, "license": "BSD-3-Clause", "optional": true, @@ -3747,6 +3882,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/nopt": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "inBundle": true, "license": "ISC", "optional": true, @@ -3760,12 +3897,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/npm-bundled": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/npm-packlist": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "inBundle": true, "license": "ISC", "optional": true, @@ -3776,6 +3917,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/npmlog": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "inBundle": true, "license": "ISC", "optional": true, @@ -3788,6 +3931,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/number-is-nan": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "inBundle": true, "license": "MIT", "optional": true, @@ -3797,6 +3942,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "inBundle": true, "license": "MIT", "optional": true, @@ -3806,6 +3953,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "inBundle": true, "license": "ISC", "optional": true, @@ -3815,6 +3964,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/os-homedir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "inBundle": true, "license": "MIT", "optional": true, @@ -3824,6 +3975,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/os-tmpdir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "inBundle": true, "license": "MIT", "optional": true, @@ -3833,6 +3986,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/osenv": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "inBundle": true, "license": "ISC", "optional": true, @@ -3843,6 +3998,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "inBundle": true, "license": "MIT", "optional": true, @@ -3852,12 +4009,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/process-nextick-args": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/rc": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "inBundle": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "optional": true, @@ -3873,12 +4034,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/rc/node_modules/minimist": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/readable-stream": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "inBundle": true, "license": "MIT", "optional": true, @@ -3894,6 +4059,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/rimraf": { "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "inBundle": true, "license": "ISC", "optional": true, @@ -3906,24 +4073,32 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/sax": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/semver": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "inBundle": true, "license": "ISC", "optional": true, @@ -3933,18 +4108,24 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/signal-exit": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "inBundle": true, "license": "MIT", "optional": true, @@ -3954,6 +4135,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/string-width": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "inBundle": true, "license": "MIT", "optional": true, @@ -3968,6 +4151,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/strip-ansi": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "inBundle": true, "license": "MIT", "optional": true, @@ -3980,6 +4165,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "inBundle": true, "license": "MIT", "optional": true, @@ -3989,6 +4176,8 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/tar": { "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "inBundle": true, "license": "ISC", "optional": true, @@ -4007,12 +4196,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/wide-align": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "inBundle": true, "license": "ISC", "optional": true, @@ -4022,12 +4215,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/yallist": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "inBundle": true, "license": "ISC", "optional": true @@ -5556,7 +5753,8 @@ "esprima": "^3.1.3", "estraverse": "^4.2.0", "esutils": "^2.0.2", - "optionator": "^0.8.1" + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, "bin": { "escodegen": "bin/escodegen.js", @@ -8627,6 +8825,7 @@ "@jest/types": "^24.9.0", "anymatch": "^2.0.0", "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", "graceful-fs": "^4.1.15", "invariant": "^2.2.4", "jest-serializer": "^24.9.0", @@ -8648,7 +8847,71 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "bundleDependencies": [ - "node-pre-gyp" + "node-pre-gyp", + "abbrev", + "ansi-regex", + "aproba", + "are-we-there-yet", + "balanced-match", + "brace-expansion", + "chownr", + "code-point-at", + "concat-map", + "console-control-strings", + "core-util-is", + "debug", + "deep-extend", + "delegates", + "detect-libc", + "fs-minipass", + "fs.realpath", + "gauge", + "glob", + "has-unicode", + "iconv-lite", + "ignore-walk", + "inflight", + "inherits", + "ini", + "is-fullwidth-code-point", + "isarray", + "minimatch", + "minimist", + "minipass", + "minizlib", + "mkdirp", + "ms", + "needle", + "nopt", + "npm-bundled", + "npm-packlist", + "npmlog", + "number-is-nan", + "object-assign", + "once", + "os-homedir", + "os-tmpdir", + "osenv", + "path-is-absolute", + "process-nextick-args", + "rc", + "readable-stream", + "rimraf", + "safe-buffer", + "safer-buffer", + "sax", + "semver", + "set-blocking", + "signal-exit", + "string_decoder", + "string-width", + "strip-ansi", + "strip-json-comments", + "tar", + "util-deprecate", + "wide-align", + "wrappy", + "yallist" ], "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", "hasInstallScript": true, @@ -8666,12 +8929,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/abbrev": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/ansi-regex": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "inBundle": true, "license": "MIT", "optional": true, @@ -8681,12 +8948,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/aproba": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/are-we-there-yet": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "inBundle": true, "license": "ISC", "optional": true, @@ -8697,12 +8968,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/balanced-match": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "inBundle": true, "license": "MIT", "optional": true, @@ -8713,12 +8988,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/chownr": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/code-point-at": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "inBundle": true, "license": "MIT", "optional": true, @@ -8728,24 +9007,32 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/core-util-is": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/debug": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "inBundle": true, "license": "MIT", "optional": true, @@ -8755,6 +9042,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/deep-extend": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "inBundle": true, "license": "MIT", "optional": true, @@ -8764,12 +9053,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/detect-libc": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "inBundle": true, "license": "Apache-2.0", "optional": true, @@ -8782,6 +9075,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/fs-minipass": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "inBundle": true, "license": "ISC", "optional": true, @@ -8791,12 +9086,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/gauge": { "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "inBundle": true, "license": "ISC", "optional": true, @@ -8813,6 +9112,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/glob": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "inBundle": true, "license": "ISC", "optional": true, @@ -8830,12 +9131,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/iconv-lite": { "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "inBundle": true, "license": "MIT", "optional": true, @@ -8848,6 +9153,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/ignore-walk": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "inBundle": true, "license": "ISC", "optional": true, @@ -8857,6 +9164,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "inBundle": true, "license": "ISC", "optional": true, @@ -8867,12 +9176,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/inherits": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/ini": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "inBundle": true, "license": "ISC", "optional": true, @@ -8882,6 +9195,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/is-fullwidth-code-point": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "inBundle": true, "license": "MIT", "optional": true, @@ -8894,12 +9209,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/isarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/minimatch": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "inBundle": true, "license": "ISC", "optional": true, @@ -8912,12 +9231,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/minimist": { "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/minipass": { "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "inBundle": true, "license": "ISC", "optional": true, @@ -8928,6 +9251,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/minizlib": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "inBundle": true, "license": "MIT", "optional": true, @@ -8937,6 +9262,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/mkdirp": { "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "inBundle": true, "license": "MIT", "optional": true, @@ -8949,12 +9276,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/ms": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/needle": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "inBundle": true, "license": "MIT", "optional": true, @@ -8972,6 +9303,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/node-pre-gyp": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "inBundle": true, "license": "BSD-3-Clause", "optional": true, @@ -8993,6 +9326,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/nopt": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "inBundle": true, "license": "ISC", "optional": true, @@ -9006,12 +9341,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/npm-bundled": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/npm-packlist": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "inBundle": true, "license": "ISC", "optional": true, @@ -9022,6 +9361,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/npmlog": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "inBundle": true, "license": "ISC", "optional": true, @@ -9034,6 +9375,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/number-is-nan": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "inBundle": true, "license": "MIT", "optional": true, @@ -9043,6 +9386,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "inBundle": true, "license": "MIT", "optional": true, @@ -9052,6 +9397,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "inBundle": true, "license": "ISC", "optional": true, @@ -9061,6 +9408,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/os-homedir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "inBundle": true, "license": "MIT", "optional": true, @@ -9070,6 +9419,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/os-tmpdir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "inBundle": true, "license": "MIT", "optional": true, @@ -9079,6 +9430,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/osenv": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "inBundle": true, "license": "ISC", "optional": true, @@ -9089,6 +9442,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "inBundle": true, "license": "MIT", "optional": true, @@ -9098,12 +9453,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/process-nextick-args": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/rc": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "inBundle": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "optional": true, @@ -9119,12 +9478,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/rc/node_modules/minimist": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/readable-stream": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "inBundle": true, "license": "MIT", "optional": true, @@ -9140,6 +9503,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/rimraf": { "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "inBundle": true, "license": "ISC", "optional": true, @@ -9152,24 +9517,32 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/sax": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/semver": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "inBundle": true, "license": "ISC", "optional": true, @@ -9179,18 +9552,24 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/signal-exit": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "inBundle": true, "license": "MIT", "optional": true, @@ -9200,6 +9579,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/string-width": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "inBundle": true, "license": "MIT", "optional": true, @@ -9214,6 +9595,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/strip-ansi": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "inBundle": true, "license": "MIT", "optional": true, @@ -9226,6 +9609,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "inBundle": true, "license": "MIT", "optional": true, @@ -9235,6 +9620,8 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/tar": { "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "inBundle": true, "license": "ISC", "optional": true, @@ -9253,12 +9640,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "inBundle": true, "license": "MIT", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/wide-align": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "inBundle": true, "license": "ISC", "optional": true, @@ -9268,12 +9659,16 @@ }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "inBundle": true, "license": "ISC", "optional": true }, "node_modules/jest-haste-map/node_modules/fsevents/node_modules/yallist": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "inBundle": true, "license": "ISC", "optional": true @@ -9853,6 +10248,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dependencies": { + "graceful-fs": "^4.1.6" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -13164,6 +13562,7 @@ "eslint-plugin-react-hooks": "^1.5.0", "file-loader": "3.0.1", "fs-extra": "7.0.1", + "fsevents": "2.0.6", "html-webpack-plugin": "4.0.0-beta.5", "identity-obj-proxy": "3.0.0", "is-wsl": "^1.1.0", @@ -15746,8 +16145,10 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", "dependencies": { + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" }, "optionalDependencies": { "chokidar": "^3.4.1", @@ -15805,6 +16206,7 @@ "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", + "fsevents": "~2.3.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -19454,21 +19856,29 @@ "dependencies": { "abbrev": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "bundled": true, "optional": true }, "ansi-regex": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "bundled": true, "optional": true }, "aproba": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "bundled": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "bundled": true, "optional": true, "requires": { @@ -19478,11 +19888,15 @@ }, "balanced-match": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "bundled": true, "optional": true }, "brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "bundled": true, "optional": true, "requires": { @@ -19492,31 +19906,43 @@ }, "chownr": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "bundled": true, "optional": true }, "code-point-at": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "bundled": true, "optional": true }, "concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "bundled": true, "optional": true }, "console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "bundled": true, "optional": true }, "core-util-is": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "bundled": true, "optional": true }, "debug": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "bundled": true, "optional": true, "requires": { @@ -19525,21 +19951,29 @@ }, "deep-extend": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "bundled": true, "optional": true }, "delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "bundled": true, "optional": true }, "detect-libc": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "bundled": true, "optional": true }, "fs-minipass": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "bundled": true, "optional": true, "requires": { @@ -19548,11 +19982,15 @@ }, "fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "bundled": true, "optional": true }, "gauge": { "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "bundled": true, "optional": true, "requires": { @@ -19568,6 +20006,8 @@ }, "glob": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "bundled": true, "optional": true, "requires": { @@ -19581,11 +20021,15 @@ }, "has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "bundled": true, "optional": true }, "iconv-lite": { "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "bundled": true, "optional": true, "requires": { @@ -19594,6 +20038,8 @@ }, "ignore-walk": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "bundled": true, "optional": true, "requires": { @@ -19602,6 +20048,8 @@ }, "inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "bundled": true, "optional": true, "requires": { @@ -19611,16 +20059,22 @@ }, "inherits": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "bundled": true, "optional": true }, "ini": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "bundled": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "bundled": true, "optional": true, "requires": { @@ -19629,11 +20083,15 @@ }, "isarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "bundled": true, "optional": true }, "minimatch": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "bundled": true, "optional": true, "requires": { @@ -19642,11 +20100,15 @@ }, "minimist": { "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "bundled": true, "optional": true }, "minipass": { "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "bundled": true, "optional": true, "requires": { @@ -19656,6 +20118,8 @@ }, "minizlib": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "bundled": true, "optional": true, "requires": { @@ -19664,6 +20128,8 @@ }, "mkdirp": { "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "bundled": true, "optional": true, "requires": { @@ -19672,11 +20138,15 @@ }, "ms": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "bundled": true, "optional": true }, "needle": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "bundled": true, "optional": true, "requires": { @@ -19687,6 +20157,8 @@ }, "node-pre-gyp": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "bundled": true, "optional": true, "requires": { @@ -19704,6 +20176,8 @@ }, "nopt": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "bundled": true, "optional": true, "requires": { @@ -19713,11 +20187,15 @@ }, "npm-bundled": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "bundled": true, "optional": true }, "npm-packlist": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "bundled": true, "optional": true, "requires": { @@ -19727,6 +20205,8 @@ }, "npmlog": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "bundled": true, "optional": true, "requires": { @@ -19738,16 +20218,22 @@ }, "number-is-nan": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "bundled": true, "optional": true }, "object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "bundled": true, "optional": true }, "once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "bundled": true, "optional": true, "requires": { @@ -19756,16 +20242,22 @@ }, "os-homedir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "bundled": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "bundled": true, "optional": true }, "osenv": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "bundled": true, "optional": true, "requires": { @@ -19775,16 +20267,22 @@ }, "path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "bundled": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "bundled": true, "optional": true }, "rc": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "bundled": true, "optional": true, "requires": { @@ -19796,6 +20294,8 @@ "dependencies": { "minimist": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "bundled": true, "optional": true } @@ -19803,6 +20303,8 @@ }, "readable-stream": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "bundled": true, "optional": true, "requires": { @@ -19817,6 +20319,8 @@ }, "rimraf": { "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "bundled": true, "optional": true, "requires": { @@ -19825,36 +20329,50 @@ }, "safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "bundled": true, "optional": true }, "safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "bundled": true, "optional": true }, "sax": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "bundled": true, "optional": true }, "semver": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "bundled": true, "optional": true }, "set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "bundled": true, "optional": true }, "signal-exit": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "bundled": true, "optional": true }, "string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "bundled": true, "optional": true, "requires": { @@ -19863,6 +20381,8 @@ }, "string-width": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "bundled": true, "optional": true, "requires": { @@ -19873,6 +20393,8 @@ }, "strip-ansi": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "bundled": true, "optional": true, "requires": { @@ -19881,11 +20403,15 @@ }, "strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "bundled": true, "optional": true }, "tar": { "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "bundled": true, "optional": true, "requires": { @@ -19900,11 +20426,15 @@ }, "util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "bundled": true, "optional": true }, "wide-align": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "bundled": true, "optional": true, "requires": { @@ -19913,11 +20443,15 @@ }, "wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "bundled": true, "optional": true }, "yallist": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "bundled": true, "optional": true } @@ -23631,21 +24165,29 @@ "dependencies": { "abbrev": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "bundled": true, "optional": true }, "ansi-regex": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "bundled": true, "optional": true }, "aproba": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "bundled": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "bundled": true, "optional": true, "requires": { @@ -23655,11 +24197,15 @@ }, "balanced-match": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "bundled": true, "optional": true }, "brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "bundled": true, "optional": true, "requires": { @@ -23669,31 +24215,43 @@ }, "chownr": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "bundled": true, "optional": true }, "code-point-at": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "bundled": true, "optional": true }, "concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "bundled": true, "optional": true }, "console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "bundled": true, "optional": true }, "core-util-is": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "bundled": true, "optional": true }, "debug": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "bundled": true, "optional": true, "requires": { @@ -23702,21 +24260,29 @@ }, "deep-extend": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "bundled": true, "optional": true }, "delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "bundled": true, "optional": true }, "detect-libc": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "bundled": true, "optional": true }, "fs-minipass": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "bundled": true, "optional": true, "requires": { @@ -23725,11 +24291,15 @@ }, "fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "bundled": true, "optional": true }, "gauge": { "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "bundled": true, "optional": true, "requires": { @@ -23745,6 +24315,8 @@ }, "glob": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "bundled": true, "optional": true, "requires": { @@ -23758,11 +24330,15 @@ }, "has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "bundled": true, "optional": true }, "iconv-lite": { "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "bundled": true, "optional": true, "requires": { @@ -23771,6 +24347,8 @@ }, "ignore-walk": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "bundled": true, "optional": true, "requires": { @@ -23779,6 +24357,8 @@ }, "inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "bundled": true, "optional": true, "requires": { @@ -23788,16 +24368,22 @@ }, "inherits": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "bundled": true, "optional": true }, "ini": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "bundled": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "bundled": true, "optional": true, "requires": { @@ -23806,11 +24392,15 @@ }, "isarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "bundled": true, "optional": true }, "minimatch": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "bundled": true, "optional": true, "requires": { @@ -23819,11 +24409,15 @@ }, "minimist": { "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "bundled": true, "optional": true }, "minipass": { "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "bundled": true, "optional": true, "requires": { @@ -23833,6 +24427,8 @@ }, "minizlib": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "bundled": true, "optional": true, "requires": { @@ -23841,6 +24437,8 @@ }, "mkdirp": { "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "bundled": true, "optional": true, "requires": { @@ -23849,11 +24447,15 @@ }, "ms": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "bundled": true, "optional": true }, "needle": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "bundled": true, "optional": true, "requires": { @@ -23864,6 +24466,8 @@ }, "node-pre-gyp": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "bundled": true, "optional": true, "requires": { @@ -23881,6 +24485,8 @@ }, "nopt": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "bundled": true, "optional": true, "requires": { @@ -23890,11 +24496,15 @@ }, "npm-bundled": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "bundled": true, "optional": true }, "npm-packlist": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "bundled": true, "optional": true, "requires": { @@ -23904,6 +24514,8 @@ }, "npmlog": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "bundled": true, "optional": true, "requires": { @@ -23915,16 +24527,22 @@ }, "number-is-nan": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "bundled": true, "optional": true }, "object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "bundled": true, "optional": true }, "once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "bundled": true, "optional": true, "requires": { @@ -23933,16 +24551,22 @@ }, "os-homedir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "bundled": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "bundled": true, "optional": true }, "osenv": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "bundled": true, "optional": true, "requires": { @@ -23952,16 +24576,22 @@ }, "path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "bundled": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "bundled": true, "optional": true }, "rc": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "bundled": true, "optional": true, "requires": { @@ -23973,6 +24603,8 @@ "dependencies": { "minimist": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "bundled": true, "optional": true } @@ -23980,6 +24612,8 @@ }, "readable-stream": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "bundled": true, "optional": true, "requires": { @@ -23994,6 +24628,8 @@ }, "rimraf": { "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "bundled": true, "optional": true, "requires": { @@ -24002,36 +24638,50 @@ }, "safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "bundled": true, "optional": true }, "safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "bundled": true, "optional": true }, "sax": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "bundled": true, "optional": true }, "semver": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "bundled": true, "optional": true }, "set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "bundled": true, "optional": true }, "signal-exit": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "bundled": true, "optional": true }, "string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "bundled": true, "optional": true, "requires": { @@ -24040,6 +24690,8 @@ }, "string-width": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "bundled": true, "optional": true, "requires": { @@ -24050,6 +24702,8 @@ }, "strip-ansi": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "bundled": true, "optional": true, "requires": { @@ -24058,11 +24712,15 @@ }, "strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "bundled": true, "optional": true }, "tar": { "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "bundled": true, "optional": true, "requires": { @@ -24077,11 +24735,15 @@ }, "util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "bundled": true, "optional": true }, "wide-align": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "bundled": true, "optional": true, "requires": { @@ -24090,11 +24752,15 @@ }, "wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "bundled": true, "optional": true }, "yallist": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "bundled": true, "optional": true } diff --git a/frontend/src/components/QuestionView.js b/frontend/src/components/QuestionView.js index a86b1f8db..74901a329 100755 --- a/frontend/src/components/QuestionView.js +++ b/frontend/src/components/QuestionView.js @@ -84,7 +84,7 @@ class QuestionView extends Component { submitSearch = (searchTerm) => { $.ajax({ - url: `/questions`, //TODO: update request URL + url: `/questions/search`, //TODO: update request URL type: 'POST', dataType: 'json', contentType: 'application/json', From 05be41d81259a02d2c67bfb3eb0844e16b71a472 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Tue, 7 Jun 2022 05:26:18 +0300 Subject: [PATCH 03/13] implement search endpoint and tests --- backend/flaskr/__init__.py | 79 +++++++++++------------ backend/test_flaskr.py | 85 +++++++++++++++++++++++++ frontend/src/components/QuestionView.js | 2 +- 3 files changed, 124 insertions(+), 42 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 38adb6daf..7196094b9 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -43,17 +43,18 @@ def after_request(response): def get_all_questions(): try: categories = Category.query.order_by(Category.id).all() - + # for category in categories: # formatted_Categories={ # category.id:category.type # } - formatted_Categories={category.id:category.type for category in categories} + formatted_Categories = { + category.id: category.type for category in categories} return jsonify({ "success": True, "categories": formatted_Categories, "total_categories": len(categories), - + }) except: abort(404) @@ -79,12 +80,13 @@ def get_paginated_questions(): questions = Question.query.order_by(Question.id).all() formatted_questions = [question.format() for question in questions] categories = Category.query.order_by(Category.id).all() - formatted_categories={category.id:category.type for category in categories} + formatted_categories = { + category.id: category.type for category in categories} return jsonify({ "success": True, "questions": formatted_questions[start:end], "total_questions": len(Question.query.all()), - "categories":formatted_categories + "categories": formatted_categories }) except: abort(404) @@ -101,7 +103,7 @@ def delete_question(question_id): question = Question.query.filter( Question.id == question_id).one_or_none() if question is None: - abort(422) + abort(404) question.delete() return jsonify({ "success": True, @@ -121,19 +123,33 @@ def delete_question(question_id): @app.route('/questions', methods=['POST']) def create_question(): body = request.get_json() - add_question = body.get('question',None) - new_answer = body.get('answer',None) - question_category = body.get('category',None) - new_difficulty_score = body.get('difficulty',None) - + add_question = body.get('question', None) + new_answer = body.get('answer', None) + question_category = body.get('category', None) + new_difficulty_score = body.get('difficulty', None) + search_term = body.get('searchTerm', None) try: - # if not(add_question in body) - new_question = Question( - question=add_question, - answer=new_answer, - category=question_category, - difficulty=new_difficulty_score - ) + + if search_term: + page = request.args.get('page', 1, type=int) + start = (page - 1) * QUESTIONS_PER_PAGE + end = start + QUESTIONS_PER_PAGE + search_query = Question.query.filter( + Question.question.ilike(f'%{search_term}%')).all() + formatted_search = [question.format() + for question in search_query] + return jsonify({ + "success": True, + "questions": formatted_search[start:end], + "total_results": len(search_query) + }) + else: + new_question = Question( + question=add_question, + answer=new_answer, + category=question_category, + difficulty=new_difficulty_score + ) new_question.insert() return jsonify({ "success": True, @@ -142,7 +158,7 @@ def create_question(): }) except: - abort(405) + abort(422) """ @TODO: Create a POST endpoint to get questions based on a search term. @@ -153,26 +169,7 @@ def create_question(): only question that include that string within their question. Try using the word "title" to start. """ - @app.route('/questions/search', methods=['POST']) - def search_question(): - body = request.get_json() - search_term = body.get('searchTerm', None) - try: - if search_term: - page = request.args.get('page', 1, type=int) - start = (page - 1) * QUESTIONS_PER_PAGE - end = start + QUESTIONS_PER_PAGE - search_query = Question.query.filter_by( - Question.question.ilike('%'+search_term+'%')).all() - formatted_search = [question.format() - for question in search_query] - return jsonify({ - "success": True, - "questions": formatted_search[start:end], - "total_results": len(search_query) - }) - except: - abort(404) + """ @TODO: Create a GET endpoint to get questions based on category. @@ -202,10 +199,10 @@ def get_question_based_on_category(category_id): one question at a time is displayed, the user is allowed to answer and shown whether they were correct or not. """ - - @app.route('/questions',methods=['POST']) + @app.route('/quizzes', methods=['POST']) def get_quiz_questions(): pass + """ @TODO: Create error handlers for all expected errors diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index 56ac6e2bc..6389bf98b 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -18,6 +18,12 @@ def setUp(self): self.password=5663 self.database_path = "postgres://{}:{}@{}/{}".format('postgres',self.password,'localhost:5432', self.database_name) setup_db(self.app, self.database_path) + self.new_question={ + "question":"Are you happy", + "answer":"yes", + "category":2, + "difficulty":2 + } # binds the app to the current context with self.app.app_context(): @@ -34,8 +40,87 @@ def tearDown(self): TODO Write at least one test for each test for successful operation and for expected errors. """ + def test_get_paginated_questions(self): + res=self.client().get('/questions') + data=json.loads(res.data) + + self.assertEqual(res.status_code,200) + self.assertEqual(data['success'],True) + self.assertTrue(len(data['questions'])) + self.assertTrue(data['total_questions']) + self.assertTrue(data['categories']) + + # def test_404_sent_requesting_beyond_valid_page(self): + # res=self.client().get('/questions?page=1000') + # data=json.loads(res.data) + + # self.assertEqual(res.status_code,404) + # self.assertEqual(data['success'],False) + # self.assertEqual(data['message'],"resource not found") + + def test_categories(self): + res=self.client().get('/categories') + data=json.loads(res.data) + + self.assertEqual(res.status_code,200) + self.assertEqual(data['success'],True) + self.assertTrue(data['total_categories']) + self.assertTrue(len(data['categories'])) + + # def test_request_sent_beyond_valid_categories(self): + # res=self.client().get('/categories?page=500') + # data=json.loads(res.data) + + # self.assertEqual(res.status_code,404) + # self.assertEqual(data['success'],False) + # self.assertEqual(data['message'],'resource not found') + + def test_delete_question(self): + res=self.client().delete('/questions/15') + data=json.loads(res.data) + question=Question.query.filter(Question.id==15).one_or_none() + self.assertEqual(res.status_code,200) + self.assertEqual(data['success'],True) + self.assertEqual(data['delete_id'],15) + self.assertEqual(question,None) + # def test_failed_to_delete(self): + # res=self.client().get('/questions/4') + # data=json.loads(res.data) + + # self.assertEqual(res.status_code,405) + # self.assertEqual(data['success'],False) + # self.assertEqual(data['message'],"Method Not Allowed") + + def test_post_question(self): + res=self.client().post('/questions',json=self.new_question) + data=json.loads(res.data) + + self.assertEqual(res.status_code,200) + self.assertTrue(data['total_questions']) + + def test_search_term(self): + res=self.client().post('/questions',json={'searchTerm':'by'}) + data=json.loads(res.data) + self.assertEqual(res.status_code,200) + self.assertEqual(data['success'],True) + self.assertTrue(len(data['questions'])) + self.assertTrue(data['total_results']) + + # def test_422_search_Term_not_available(self): + # res=self.client().get('/questions',json={'searchTerm':''}) + # data=json.loads(res.data) + # self.assertEqual(res.status_code,422) + # self.assertEqual(data['questions',0]) + # self.assertEqual(data['total_results',0]) + def test_get_question_based_on_category(self): + res=self.client().get('/categories/5/questions') + data=json.loads(res.data) + self.assertEqual(res.status_code,200) + self.assertEqual(data['success'],True) + self.assertTrue(len(data['questions'])) + self.assertTrue(data['total_questions']) # Make the tests conveniently executable if __name__ == "__main__": diff --git a/frontend/src/components/QuestionView.js b/frontend/src/components/QuestionView.js index 74901a329..a86b1f8db 100755 --- a/frontend/src/components/QuestionView.js +++ b/frontend/src/components/QuestionView.js @@ -84,7 +84,7 @@ class QuestionView extends Component { submitSearch = (searchTerm) => { $.ajax({ - url: `/questions/search`, //TODO: update request URL + url: `/questions`, //TODO: update request URL type: 'POST', dataType: 'json', contentType: 'application/json', From f9521f05a973938b51b46e65442da5017be0bfd7 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Tue, 7 Jun 2022 08:43:07 +0300 Subject: [PATCH 04/13] add test fail for endpoints, /categories,/categories/id/questions... --- backend/flaskr/__init__.py | 55 ++++++----------- backend/test_flaskr.py | 123 ++++++++++++++++++++----------------- 2 files changed, 86 insertions(+), 92 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 7196094b9..5e4ac1be6 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -34,11 +34,7 @@ def after_request(response): @TODO: Use the after_request decorator to set Access-Control-Allow """ - """ - @TODO: - Create an endpoint to handle GET requests - for all available categories. - """ + @app.route('/categories', methods=['GET']) def get_all_questions(): try: @@ -91,17 +87,12 @@ def get_paginated_questions(): except: abort(404) - """ - @TODO: - Create an endpoint to DELETE question using a question ID. - TEST: When you click the trash icon next to a question, the question will be removed. - This removal will persist in the database and when you refresh the page. - """ @app.route('/questions/', methods=['DELETE']) def delete_question(question_id): + q_id=str(question_id) question = Question.query.filter( - Question.id == question_id).one_or_none() + Question.id == q_id).one_or_none() if question is None: abort(404) question.delete() @@ -170,24 +161,20 @@ def create_question(): Try using the word "title" to start. """ - """ - @TODO: - Create a GET endpoint to get questions based on category. - - TEST: In the "List" tab / main screen, clicking on one of the - categories in the left column will cause only questions of that - category to be shown. - """ @app.route('/categories//questions', methods=['GET']) def get_question_based_on_category(category_id): - questions = Question.query.filter( - Question.category == category_id).all() + try: + category=str(category_id) + questions = Question.query.filter( + Question.category == category).all() - return jsonify({ - "success": True, - "questions": [question.format() for question in questions], - "total_questions": len(questions) - }) + return jsonify({ + "success": True, + "questions": [question.format() for question in questions], + "total_questions": len(questions) + }) + except: + abort(404) """ @TODO: Create a POST endpoint to get questions to play the quiz. @@ -203,11 +190,7 @@ def get_question_based_on_category(category_id): def get_quiz_questions(): pass - """ - @TODO: - Create error handlers for all expected errors - including 404 and 422. - """ + @app.errorhandler(404) def not_found(error): @@ -225,12 +208,12 @@ def unprocessable(error): "message": "Unprocessable" }), 422 - @app.errorhandler(405) + @app.errorhandler(400) def method_not_allowed(error): return jsonify({ "success": False, - "error": 405, - "message": "Method Not Allowed" - }), 405 + "error": 400, + "message": "Bad Request" + }), 400 return app diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index 6389bf98b..de070e833 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -15,14 +15,15 @@ def setUp(self): self.app = create_app() self.client = self.app.test_client self.database_name = "trivia_test" - self.password=5663 - self.database_path = "postgres://{}:{}@{}/{}".format('postgres',self.password,'localhost:5432', self.database_name) + self.password = 5663 + self.database_path = "postgres://{}:{}@{}/{}".format( + 'postgres', self.password, 'localhost:5432', self.database_name) setup_db(self.app, self.database_path) - self.new_question={ - "question":"Are you happy", - "answer":"yes", - "category":2, - "difficulty":2 + self.new_question = { + "question": "Are you happy", + "answer": "yes", + "category": 2, + "difficulty": 2 } # binds the app to the current context @@ -31,7 +32,7 @@ def setUp(self): self.db.init_app(self.app) # create all tables self.db.create_all() - + def tearDown(self): """Executed after reach test""" pass @@ -40,12 +41,13 @@ def tearDown(self): TODO Write at least one test for each test for successful operation and for expected errors. """ + def test_get_paginated_questions(self): - res=self.client().get('/questions') - data=json.loads(res.data) + res = self.client().get('/questions') + data = json.loads(res.data) - self.assertEqual(res.status_code,200) - self.assertEqual(data['success'],True) + self.assertEqual(res.status_code, 200) + self.assertEqual(data['success'], True) self.assertTrue(len(data['questions'])) self.assertTrue(data['total_questions']) self.assertTrue(data['categories']) @@ -56,72 +58,81 @@ def test_get_paginated_questions(self): # self.assertEqual(res.status_code,404) # self.assertEqual(data['success'],False) - # self.assertEqual(data['message'],"resource not found") + # self.assertEqual(data['message'],"Not Found") def test_categories(self): - res=self.client().get('/categories') - data=json.loads(res.data) + res = self.client().get('/categories') + data = json.loads(res.data) - self.assertEqual(res.status_code,200) - self.assertEqual(data['success'],True) + self.assertEqual(res.status_code, 200) + self.assertEqual(data['success'], True) self.assertTrue(data['total_categories']) self.assertTrue(len(data['categories'])) - # def test_request_sent_beyond_valid_categories(self): - # res=self.client().get('/categories?page=500') - # data=json.loads(res.data) + def test_request_sent_beyond_valid_categories(self): + res = self.client().get('/categories/') + data = json.loads(res.data) - # self.assertEqual(res.status_code,404) - # self.assertEqual(data['success'],False) - # self.assertEqual(data['message'],'resource not found') + self.assertEqual(res.status_code, 404) + self.assertEqual(data['success'], False) + self.assertEqual(data['message'], 'Not Found') def test_delete_question(self): - res=self.client().delete('/questions/15') - data=json.loads(res.data) - question=Question.query.filter(Question.id==15).one_or_none() - self.assertEqual(res.status_code,200) - self.assertEqual(data['success'],True) - self.assertEqual(data['delete_id'],15) - self.assertEqual(question,None) - - # def test_failed_to_delete(self): - # res=self.client().get('/questions/4') - # data=json.loads(res.data) - - # self.assertEqual(res.status_code,405) - # self.assertEqual(data['success'],False) - # self.assertEqual(data['message'],"Method Not Allowed") + res = self.client().delete('/questions/24') + data = json.loads(res.data) + question = Question.query.filter(Question.id == 24).one_or_none() + self.assertEqual(res.status_code, 200) + self.assertEqual(data['success'], True) + self.assertEqual(data['delete_id'], 24) + self.assertEqual(question, None) + + def test_failed_to_delete(self): + res = self.client().delete('/questions/500') + data = json.loads(res.data) + + self.assertEqual(res.status_code, 404) + self.assertEqual(data['success'], False) + self.assertEqual(data['message'], "Not Found") def test_post_question(self): - res=self.client().post('/questions',json=self.new_question) - data=json.loads(res.data) + res = self.client().post('/questions', json=self.new_question) + data = json.loads(res.data) - self.assertEqual(res.status_code,200) + self.assertEqual(res.status_code, 200) self.assertTrue(data['total_questions']) def test_search_term(self): - res=self.client().post('/questions',json={'searchTerm':'by'}) - data=json.loads(res.data) - self.assertEqual(res.status_code,200) - self.assertEqual(data['success'],True) + res = self.client().post('/questions', json={'searchTerm': 'which'}) + data = json.loads(res.data) + self.assertEqual(res.status_code, 200) + self.assertEqual(data['success'], True) self.assertTrue(len(data['questions'])) self.assertTrue(data['total_results']) - + # def test_422_search_Term_not_available(self): - # res=self.client().get('/questions',json={'searchTerm':''}) - # data=json.loads(res.data) - # self.assertEqual(res.status_code,422) - # self.assertEqual(data['questions',0]) - # self.assertEqual(data['total_results',0]) + # res = self.client().post('/questions', json={'searchTerm': 'bodaboda'}) + # data = json.loads(res.data) + # self.assertEqual(res.status_code, 422) + # self.assertEqual(data['success'], False) + # self.assertEqual(data['questions', 0]) + # self.assertEqual(data['total_results', 0]) def test_get_question_based_on_category(self): - res=self.client().get('/categories/5/questions') - data=json.loads(res.data) - self.assertEqual(res.status_code,200) - self.assertEqual(data['success'],True) + res = self.client().get('/categories/2/questions') + data = json.loads(res.data) + self.assertEqual(res.status_code, 200) + self.assertEqual(data['success'], True) self.assertTrue(len(data['questions'])) self.assertTrue(data['total_questions']) + def test_get_404_failed(self): + res = self.client().get('/categories/id/questions') + data = json.loads(res.data) + self.assertEqual(res.status_code, 404) + self.assertEqual(data['success'], False) + self.assertEqual(data['message'], 'Not Found') + + # Make the tests conveniently executable if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From 428fa540bd751716dede17308452834d60fcd945 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Tue, 7 Jun 2022 23:07:52 +0300 Subject: [PATCH 05/13] add quiz end point --- backend/flaskr/__init__.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 5e4ac1be6..6065af9cc 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -30,20 +30,17 @@ def after_request(response): @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs """ + myname = "cleophas" + z = ("a"+myname)[1:] """ @TODO: Use the after_request decorator to set Access-Control-Allow """ - @app.route('/categories', methods=['GET']) def get_all_questions(): try: categories = Category.query.order_by(Category.id).all() - # for category in categories: - # formatted_Categories={ - # category.id:category.type - # } formatted_Categories = { category.id: category.type for category in categories} return jsonify({ @@ -87,10 +84,9 @@ def get_paginated_questions(): except: abort(404) - @app.route('/questions/', methods=['DELETE']) def delete_question(question_id): - q_id=str(question_id) + q_id = str(question_id) question = Question.query.filter( Question.id == q_id).one_or_none() if question is None: @@ -164,7 +160,7 @@ def create_question(): @app.route('/categories//questions', methods=['GET']) def get_question_based_on_category(category_id): try: - category=str(category_id) + category = str(category_id) questions = Question.query.filter( Question.category == category).all() @@ -188,9 +184,27 @@ def get_question_based_on_category(category_id): """ @app.route('/quizzes', methods=['POST']) def get_quiz_questions(): - pass - + body = request.get_json() + category = body['quiz_category'] + previous_question = body['previuous_question'] + category_id = category['id'] + try: + if category_id == 0: + questions = Question.query.filter( + Question.id.not_in(previous_question)).all() + else: + questions = Question.query.filter(Question.id.not_in( + previous_question), Question.category == category_id).all() + question = None + if questions: + question = random.choice(questions) + return jsonify({ + "success": True, + "question": question.format() + }) + except: + abort(404) @app.errorhandler(404) def not_found(error): From ff6f71058769e7b6c07fda0a0f7a57bf2a9662d9 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Wed, 8 Jun 2022 06:55:35 +0300 Subject: [PATCH 06/13] modify readme, implemented the questions to play' --- backend/README.md | 297 ++++++++++++++++++++++++++++++++++--- backend/flaskr/__init__.py | 57 +++---- backend/test_flaskr.py | 13 ++ 3 files changed, 323 insertions(+), 44 deletions(-) diff --git a/backend/README.md b/backend/README.md index edcc45ab0..ff5ce6f1b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -43,12 +43,16 @@ From within the `./src` directory first ensure you are working using your create To run the server, execute: ```bash -flask run --reload +export FLASK_APP=flaskr +export FLASK_ENV=development +python -m flask run ``` -The `--reload` flag will detect file changes and restart the server automatically. +setting the `FLASK_APP=flaskr` will allow the detection of flask app `__init__py` in the flaskr diretory -## To Do Tasks +setting `FLASK_ENV=development` enables auto detect on the changes made during development process + +## Tasks These are the files you'd want to edit in the backend: @@ -65,19 +69,37 @@ One note before you delve into your tasks: for each endpoint, you are expected t 6. Create a `POST` endpoint to get questions based on category. 7. Create a `POST` endpoint to get questions based on a search term. It should return any questions for whom the search term is a substring of the question. 8. Create a `POST` endpoint to get questions to play the quiz. This endpoint should take a category and previous question parameters and return a random questions within the given category, if provided, and that is not one of the previous questions. -9. Create error handlers for all expected errors including 400, 404, 422, and 500. +9. Create error handlers for all expected errors including 400, 404, and 422. + +## API Reference -## Documenting your Endpoints +###Getting started +* Backend base URL : `http://127.0.0.1:5000/` +* Front end base URL: `http://127.0.0.1:3000/` +* Authentication : Authentication and API keys have not been used in this application -You will need to provide detailed documentation of your API endpoints including the URL, request parameters, and the response body. Use the example below as a reference. -### Documentation Example +### Error Handling +Errors are returned as json object in the following format +```json +{ + "success": False, + "error": 400, + "message": "bad request" +} +``` -`GET '/api/v1.0/categories'` +The API returns three types of errors +* 404 :Not Found +* 400 :Bad Request +* 422 :Not processable -- Fetches a dictionary of categories in which the keys are the ids and the value is the corresponding string of the category -- Request Arguments: None -- Returns: An object with a single key, `categories`, that contains an object of `id: category_string` key: value pairs. + + +### End points +`GET '/categories'` +* Returns an object of categories ordered by id,that contains an object of `id:category_string` key:value pairs, success value and total number of categories +* Sample : `curl http://127.0.0.1:5000/categories` ```json { @@ -90,15 +112,252 @@ You will need to provide detailed documentation of your API endpoints including } ``` -## Testing +`GET '/questions'` +* Returns a paginated set of questions ,total number of questions +* Sample `curl http://127.0.0.1:5000/questions` +```json +{ + "categories": { + "1": "Science", + "2": "Art", + "3": "Geography", + "4": "History", + "5": "Entertainment", + "6": "Sports" + }, + "questions": [ + { + "answer": "Tom Cruise", + "category": 5, + "difficulty": 4, + "id": 4, + "question": "What actor did author Anne Rice first denounce, then praise in the role of her beloved Lestat?" + }, + { + "answer": "Muhammad Ali", + "category": 4, + "difficulty": 1, + "id": 9, + "question": "What boxer's original name is Cassius Clay?" + }, + { + "answer": "Brazil", + "category": 6, + "difficulty": 3, + "id": 10, + "question": "Which is the only team to play in every soccer World Cup tournament?" + }, + { + "answer": "Uruguay", + "category": 6, + "difficulty": 4, + "id": 11, + "question": "Which country won the first ever soccer World Cup in 1930?" + }, + { + "answer": "George Washington Carver", + "category": 4, + "difficulty": 2, + "id": 12, + "question": "Who invented Peanut Butter?" + }, + { + "answer": "Lake Victoria", + "category": 3, + "difficulty": 2, + "id": 13, + "question": "What is the largest lake in Africa?" + }, + { + "answer": "The Palace of Versailles", + "category": 3, + "difficulty": 3, + "id": 14, + "question": "In which royal palace would you find the Hall of Mirrors?" + }, + { + "answer": "Agra", + "category": 3, + "difficulty": 2, + "id": 15, + "question": "The Taj Mahal is located in which Indian city?" + }, + { + "answer": "Escher", + "category": 2, + "difficulty": 1, + "id": 16, + "question": "Which Dutch graphic artist\u2013initials M C was a creator of optical illusions?" + }, + { + "answer": "Mona Lisa", + "category": 2, + "difficulty": 3, + "id": 17, + "question": "La Giaconda is better known as what?" + } + ], + "success": true, + "total_questions": 27 +} +``` +`DELETE '/questions/{question_id}` +* Deletes the question of the given id if it exists +* Returns a success value, id of the deleted question and total number of questions +* sample `curl -X DELETE http://127.0.0.1:5000/14` -Write at least one test for the success and at least one error behavior of each endpoint using the unittest library. +```json +{ + "delete_id": 14, + "success": true, + "total_questions": 24 +} +``` -To deploy the tests, run +`POST '/questions'` +* Creates a new question using the submitted question,answer,category and difficulty +* Returns success value,id of created question and total number of questions +* sample `curl http://127.0.0.1:5000/questions -X POST -H "Content-Type: application/json" --data "{\"question\": \"Frankie Fredericks represented which African country in athletics?\", \"answer\": \"Cleo\", \"difficulty\": \"3\", \"category\": \"6\" }"` -```bash -dropdb trivia_test -createdb trivia_test -psql trivia_test < trivia.psql -python test_flaskr.py +```json +{ + "created": 36, + "success": true, + "total_questions": 25 +} ``` + +`POST '/questions'` +* Returns questions that match the searchTerm,success,total search query +* sample `curl http://127.0.0.1:5000/questions -X POST -H "Content-Type: application/json" -d "{\"searchTerm\": \"what\"}"` + +```json +{ + "questions": [ + { + "answer": "Muhammad Ali", + "category": 4, + "difficulty": 1, + "id": 9, + "question": "What boxer's original name is Cassius Clay?" + }, + { + "answer": "Lake Victoria", + "category": 3, + "difficulty": 2, + "id": 13, + "question": "What is the largest lake in Africa?" + }, + { + "answer": "Mona Lisa", + "category": 2, + "difficulty": 3, + "id": 17, + "question": "La Giaconda is better known as what?" + }, + { + "answer": "The Liver", + "category": 1, + "difficulty": 4, + "id": 20, + "question": "What is the heaviest organ in the human body?" + }, + { + "answer": "Blood", + "category": 1, + "difficulty": 4, + "id": 22, + "question": "Hematology is a branch of medicine involving the study of what?" + }, + { + "answer": "bop", + "category": 3, + "difficulty": 5, + "id": 28, + "question": "whats my name" + } + ], + "success": true, + "total_results": 6 +} +``` + +`GET '/categories/{category_id}/questions` +* Retrives questions with a given category id +* Returns success value, paginated questions with the given category id,total questions +* sample `curl http://127.0.0.1:5000/categories/1/questions` + +```json +{ + "questions": [ + { + "answer": "Muhammad Ali", + "category": 4, + "difficulty": 1, + "id": 9, + "question": "What boxer's original name is Cassius Clay?" + }, + { + "answer": "Lake Victoria", + "category": 3, + "difficulty": 2, + "id": 13, + "question": "What is the largest lake in Africa?" + }, + { + "answer": "Mona Lisa", + "category": 2, + "difficulty": 3, + "id": 17, + "question": "La Giaconda is better known as what?" + }, + { + "answer": "The Liver", + "category": 1, + "difficulty": 4, + "id": 20, + "question": "What is the heaviest organ in the human body?" + }, + { + "answer": "Blood", + "category": 1, + "difficulty": 4, + "id": 22, + "question": "Hematology is a branch of medicine involving the study of what?" + }, + { + "answer": "bop", + "category": 3, + "difficulty": 5, + "id": 28, + "question": "whats my name" + } + ], + "success": true, + "total_results": 6 +} +``` + +`POST '/quizzes'` +* Takes two parameters, category and previous questions +* Returns a success value, random questions not in the previous question +```json +{ + "question": { + "answer": "The Liver", + "category": 1, + "difficulty": 4, + "id": 20, + "question": "What is the heaviest organ in the human body?" + }, + "success": true +} +``` + +### Authors +* Cleophas Kadima +### Acknowledgements +* Udacity +## Testing + + diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 6065af9cc..1a09d4778 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -30,11 +30,6 @@ def after_request(response): @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs """ - myname = "cleophas" - z = ("a"+myname)[1:] - """ - @TODO: Use the after_request decorator to set Access-Control-Allow - """ @app.route('/categories', methods=['GET']) def get_all_questions(): @@ -53,11 +48,6 @@ def get_all_questions(): abort(404) """ - @TODO: - Create an endpoint to handle GET requests for questions, - including pagination (every 10 questions). - This endpoint should return a list of questions, - number of total questions, current category, categories. TEST: At this point, when you start the application you should see questions and categories generated, @@ -67,6 +57,7 @@ def get_all_questions(): @app.route('/questions', methods=['GET']) def get_paginated_questions(): try: + #get all questions but in a paginated format page = request.args.get('page', 1, type=int) start = (page - 1) * QUESTIONS_PER_PAGE end = start + QUESTIONS_PER_PAGE @@ -86,6 +77,7 @@ def get_paginated_questions(): @app.route('/questions/', methods=['DELETE']) def delete_question(question_id): + q_id = str(question_id) question = Question.query.filter( Question.id == q_id).one_or_none() @@ -116,7 +108,7 @@ def create_question(): new_difficulty_score = body.get('difficulty', None) search_term = body.get('searchTerm', None) try: - + #check if there's a search_term in body if search_term: page = request.args.get('page', 1, type=int) start = (page - 1) * QUESTIONS_PER_PAGE @@ -160,6 +152,7 @@ def create_question(): @app.route('/categories//questions', methods=['GET']) def get_question_based_on_category(category_id): try: + #convert category_id to a string to validate tests as its passed as a string category = str(category_id) questions = Question.query.filter( Question.category == category).all() @@ -185,26 +178,40 @@ def get_question_based_on_category(category_id): @app.route('/quizzes', methods=['POST']) def get_quiz_questions(): body = request.get_json() - category = body['quiz_category'] - previous_question = body['previuous_question'] + category = body.get('quiz_category',None) + previous_question = body.get('previous_questions') category_id = category['id'] try: + #if no category specified use the ALL category if category_id == 0: - questions = Question.query.filter( - Question.id.not_in(previous_question)).all() - + questions = Question.query.all() else: - questions = Question.query.filter(Question.id.not_in( - previous_question), Question.category == category_id).all() - question = None - if questions: - question = random.choice(questions) + + questions=Question.query.filter(Question.category==category_id).all() + + #return random questions from start=0, end len-1 of questions + def random_questions(): + return questions[random.randint(0,len(questions)-1)] + + #generate random questions for the next question + next_question=random_questions() + + not_previous=True + #get qustions not in previous category + while (not_previous): + if next_question.id in previous_question: + next_question=random_questions() + else: + not_previous=False return jsonify({ - "success": True, - "question": question.format() + "success":True, + "question":next_question.format() }) + + + except: - abort(404) + abort(400) @app.errorhandler(404) def not_found(error): @@ -219,7 +226,7 @@ def unprocessable(error): return jsonify({ "success": False, "error": 422, - "message": "Unprocessable" + "message": "Not processable" }), 422 @app.errorhandler(400) diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index de070e833..9a7f5c963 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -132,6 +132,19 @@ def test_get_404_failed(self): self.assertEqual(data['success'], False) self.assertEqual(data['message'], 'Not Found') + def test_quiz_questions(self): + res=self.client().post('/quizzes',json={ + 'previous_questions':[], + 'current_category':{'id':'5', + 'type':'Entertainment'} + }) + + data=json.loads(res.data) + self.assertEqual(res.status_code,200) + self.assertEqual(data['success'],True) + self.assertTrue(data['question']) + self.assertEqual(data['question']['category'],5) + # Make the tests conveniently executable if __name__ == "__main__": From d88f986c2a91d03a487e77d0ef5be5e7cb6242f0 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Wed, 8 Jun 2022 06:57:11 +0300 Subject: [PATCH 07/13] deleted testing in readme --- backend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/README.md b/backend/README.md index ff5ce6f1b..c584ed32d 100644 --- a/backend/README.md +++ b/backend/README.md @@ -358,6 +358,6 @@ The API returns three types of errors * Cleophas Kadima ### Acknowledgements * Udacity -## Testing + From f01c36401b1a3b0308bdc8ec0e05866eac4157a4 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Wed, 8 Jun 2022 06:58:11 +0300 Subject: [PATCH 08/13] modified headers --- backend/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/README.md b/backend/README.md index c584ed32d..b06fce9bc 100644 --- a/backend/README.md +++ b/backend/README.md @@ -354,9 +354,9 @@ The API returns three types of errors } ``` -### Authors +## Authors * Cleophas Kadima -### Acknowledgements +## Acknowledgements * Udacity From 4c473f301ef5b9045ebd258a48546f830e3cd0e5 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Wed, 8 Jun 2022 07:02:59 +0300 Subject: [PATCH 09/13] modified readme --- backend/README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/backend/README.md b/backend/README.md index b06fce9bc..841d8fe8e 100644 --- a/backend/README.md +++ b/backend/README.md @@ -73,11 +73,21 @@ One note before you delve into your tasks: for each endpoint, you are expected t ## API Reference -###Getting started +### Getting started * Backend base URL : `http://127.0.0.1:5000/` * Front end base URL: `http://127.0.0.1:3000/` * Authentication : Authentication and API keys have not been used in this application +### Testing + +To deploy the tests, run + +```bash +dropdb trivia_test +createdb trivia_test +psql trivia_test < trivia.psql +python test_flaskr.py +``` ### Error Handling Errors are returned as json object in the following format @@ -201,6 +211,7 @@ The API returns three types of errors "total_questions": 27 } ``` + `DELETE '/questions/{question_id}` * Deletes the question of the given id if it exists * Returns a success value, id of the deleted question and total number of questions @@ -359,5 +370,3 @@ The API returns three types of errors ## Acknowledgements * Udacity - - From 283e310f48bf8cded3896d9d4c6c9d66ad7ad7dd Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Wed, 8 Jun 2022 10:16:05 +0300 Subject: [PATCH 10/13] modify readme and add test for post request --- README.md | 398 ++++++++++++++++++++++++++++++++++--- backend/flaskr/__init__.py | 92 ++++----- backend/test_flaskr.py | 61 +++--- 3 files changed, 447 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 17fcdda27..9f771566b 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,401 @@ -# API Development and Documentation Final Project +# API Development and Documentation ## Trivia App -Udacity is invested in creating bonding experiences for its employees and students. A bunch of team members got the idea to hold trivia on a regular basis and created a webpage to manage the trivia app and play the game, but their API experience is limited and still needs to be built out. - -That's where you come in! Help them finish the trivia app so they can start holding trivia and seeing who's the most knowledgeable of the bunch. The application must: +This Trivia app allows users to play a trivia game where they test their knowledge by answering trivia questions +The web application offers the following functionalities 1. Display questions - both all questions and by category. Questions should show the question, category and difficulty rating by default and can show/hide the answer. -2. Delete questions. +2. Delete questions 3. Add questions and require that they include question and answer text. 4. Search for questions based on a text query string. 5. Play the quiz game, randomizing either all questions or within a specific category. -Completing this trivia app will give you the ability to structure plan, implement, and test an API - skills essential for enabling your future applications to communicate with others. +## About the Stack +The backend direcory contains files to setup the backend to enable communication with the front end and viewing of data from the database +> View the [Backend README](./backend/README.md) for more details. -## Starting and Submitting the Project +### Frontend -[Fork](https://help.github.com/en/articles/fork-a-repo) the project repository and [clone](https://help.github.com/en/articles/cloning-a-repository) your forked repository to your machine. Work on the project locally and make sure to push all your changes to the remote repository before submitting the link to your repository in the Classroom. +The [frontend](./frontend/README.md) directory contains a complete React frontend to consume the data from the Flask server. If you have prior experience building a frontend application, you should feel free to edit the endpoints as you see fit for the backend you design. If you do not have prior experience building a frontend application, you should read through the frontend code before starting and make notes regarding: -## About the Stack +1. What are the end points and HTTP methods the frontend is expecting to consume? +2. How are the requests from the frontend formatted? Are they expecting certain parameters or payloads? + +Pay special attention to what data the frontend is expecting from each API response to help guide how you format your API. + +> View the [Frontend README](./frontend/README.md) for more details. + +## Getting Started +Developers should have Python3,node, npm installed +### Install Dependencies + +1. **Python 3.7** - Follow instructions to install the latest version of python for your platform in the [python docs](https://docs.python.org/3/using/unix.html#getting-and-installing-the-latest-version-of-python) + +2. **Virtual Environment** - We recommend working within a virtual environment whenever using Python for projects. This keeps your dependencies for each project separate and organized. Instructions for setting up a virual environment for your platform can be found in the [python docs](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/) + +3. **PIP Dependencies** - Once your virtual environment is setup and running, install the required dependencies by navigating to the `/backend` directory and running: + +```bash +pip install -r requirements.txt +``` + +#### Key Pip Dependencies + +- [Flask](http://flask.pocoo.org/) is a lightweight backend microservices framework. Flask is required to handle requests and responses. + +- [SQLAlchemy](https://www.sqlalchemy.org/) is the Python SQL toolkit and ORM we'll use to handle the lightweight SQL database. You'll primarily work in `app.py`and can reference `models.py`. + +- [Flask-CORS](https://flask-cors.readthedocs.io/en/latest/#) is the extension we'll use to handle cross-origin requests from our frontend server. + +### Set up the Database + +With Postgres running, create a `trivia` database: + +```bash +createbd trivia +``` + +Populate the database using the `trivia.psql` file provided. From the `backend` folder in terminal run: + +```bash +psql trivia < trivia.psql +``` + +### Run the Server + +From within the `./src` directory first ensure you are working using your created virtual environment. + +To run the server, execute: -We started the full stack application for you. It is designed with some key functional areas: +```bash +export FLASK_APP=flaskr +export FLASK_ENV=development +python -m flask run +``` -### Backend +setting the `FLASK_APP=flaskr` will allow the detection of flask app `__init__py` in the flaskr diretory -The [backend](./backend/README.md) directory contains a partially completed Flask and SQLAlchemy server. You will work primarily in `__init__.py` to define your endpoints and can reference models.py for DB and SQLAlchemy setup. These are the files you'd want to edit in the backend: +setting `FLASK_ENV=development` enables auto detect on the changes made during development process + +## Tasks + +These are the files you'd want to edit in the backend: 1. `backend/flaskr/__init__.py` 2. `backend/test_flaskr.py` -> View the [Backend README](./backend/README.md) for more details. +One note before you delve into your tasks: for each endpoint, you are expected to define the endpoint and response data. The frontend will be a plentiful resource because it is set up to expect certain endpoints and response data formats already. You should feel free to specify endpoints in your own way; if you do so, make sure to update the frontend or you will get some unexpected behavior. -### Frontend +1. Use Flask-CORS to enable cross-domain requests and set response headers. +2. Create an endpoint to handle `GET` requests for questions, including pagination (every 10 questions). This endpoint should return a list of questions, number of total questions, current category, categories. +3. Create an endpoint to handle `GET` requests for all available categories. +4. Create an endpoint to `DELETE` a question using a question `ID`. +5. Create an endpoint to `POST` a new question, which will require the question and answer text, category, and difficulty score. +6. Create a `POST` endpoint to get questions based on category. +7. Create a `POST` endpoint to get questions based on a search term. It should return any questions for whom the search term is a substring of the question. +8. Create a `POST` endpoint to get questions to play the quiz. This endpoint should take a category and previous question parameters and return a random questions within the given category, if provided, and that is not one of the previous questions. +9. Create error handlers for all expected errors including 400, 404, and 422. -The [frontend](./frontend/README.md) directory contains a complete React frontend to consume the data from the Flask server. If you have prior experience building a frontend application, you should feel free to edit the endpoints as you see fit for the backend you design. If you do not have prior experience building a frontend application, you should read through the frontend code before starting and make notes regarding: +## API Reference -1. What are the end points and HTTP methods the frontend is expecting to consume? -2. How are the requests from the frontend formatted? Are they expecting certain parameters or payloads? +### Getting started +* Backend base URL : `http://127.0.0.1:5000/` +* Front end base URL: `http://127.0.0.1:3000/` +* Authentication : Authentication and API keys have not been used in this application + +### Testing + +To deploy the tests, run + +```bash +dropdb trivia_test +createdb trivia_test +psql trivia_test < trivia.psql +python test_flaskr.py +``` + +### Error Handling +Errors are returned as json object in the following format +```json +{ + "success": False, + "error": 400, + "message": "bad request" +} +``` + +The API returns three types of errors +* 404 :Not Found +* 400 :Bad Request +* 422 :Not processable + + + +### End points +`GET '/categories'` +* Returns an object of categories ordered by id,that contains an object of `id:category_string` key:value pairs, success value and total number of categories +* Sample : `curl http://127.0.0.1:5000/categories` + +```json +{ + "1": "Science", + "2": "Art", + "3": "Geography", + "4": "History", + "5": "Entertainment", + "6": "Sports" +} +``` + +`GET '/questions'` +* Returns a paginated set of questions ,total number of questions +* Sample `curl http://127.0.0.1:5000/questions` +```json +{ + "categories": { + "1": "Science", + "2": "Art", + "3": "Geography", + "4": "History", + "5": "Entertainment", + "6": "Sports" + }, + "questions": [ + { + "answer": "Tom Cruise", + "category": 5, + "difficulty": 4, + "id": 4, + "question": "What actor did author Anne Rice first denounce, then praise in the role of her beloved Lestat?" + }, + { + "answer": "Muhammad Ali", + "category": 4, + "difficulty": 1, + "id": 9, + "question": "What boxer's original name is Cassius Clay?" + }, + { + "answer": "Brazil", + "category": 6, + "difficulty": 3, + "id": 10, + "question": "Which is the only team to play in every soccer World Cup tournament?" + }, + { + "answer": "Uruguay", + "category": 6, + "difficulty": 4, + "id": 11, + "question": "Which country won the first ever soccer World Cup in 1930?" + }, + { + "answer": "George Washington Carver", + "category": 4, + "difficulty": 2, + "id": 12, + "question": "Who invented Peanut Butter?" + }, + { + "answer": "Lake Victoria", + "category": 3, + "difficulty": 2, + "id": 13, + "question": "What is the largest lake in Africa?" + }, + { + "answer": "The Palace of Versailles", + "category": 3, + "difficulty": 3, + "id": 14, + "question": "In which royal palace would you find the Hall of Mirrors?" + }, + { + "answer": "Agra", + "category": 3, + "difficulty": 2, + "id": 15, + "question": "The Taj Mahal is located in which Indian city?" + }, + { + "answer": "Escher", + "category": 2, + "difficulty": 1, + "id": 16, + "question": "Which Dutch graphic artist\u2013initials M C was a creator of optical illusions?" + }, + { + "answer": "Mona Lisa", + "category": 2, + "difficulty": 3, + "id": 17, + "question": "La Giaconda is better known as what?" + } + ], + "success": true, + "total_questions": 27 +} +``` + +`DELETE '/questions/{question_id}` +* Deletes the question of the given id if it exists +* Returns a success value, id of the deleted question and total number of questions +* sample `curl -X DELETE http://127.0.0.1:5000/14` + +```json +{ + "delete_id": 14, + "success": true, + "total_questions": 24 +} +``` + +`POST '/questions'` +* Creates a new question using the submitted question,answer,category and difficulty +* Returns success value,id of created question and total number of questions +* sample `curl http://127.0.0.1:5000/questions -X POST -H "Content-Type: application/json" --data "{\"question\": \"Frankie Fredericks represented which African country in athletics?\", \"answer\": \"Cleo\", \"difficulty\": \"3\", \"category\": \"6\" }"` + +```json +{ + "created": 36, + "success": true, + "total_questions": 25 +} +``` + +`POST '/questions'` +* Returns questions that match the searchTerm,success,total search query +* sample `curl http://127.0.0.1:5000/questions -X POST -H "Content-Type: application/json" -d "{\"searchTerm\": \"what\"}"` + +```json +{ + "questions": [ + { + "answer": "Muhammad Ali", + "category": 4, + "difficulty": 1, + "id": 9, + "question": "What boxer's original name is Cassius Clay?" + }, + { + "answer": "Lake Victoria", + "category": 3, + "difficulty": 2, + "id": 13, + "question": "What is the largest lake in Africa?" + }, + { + "answer": "Mona Lisa", + "category": 2, + "difficulty": 3, + "id": 17, + "question": "La Giaconda is better known as what?" + }, + { + "answer": "The Liver", + "category": 1, + "difficulty": 4, + "id": 20, + "question": "What is the heaviest organ in the human body?" + }, + { + "answer": "Blood", + "category": 1, + "difficulty": 4, + "id": 22, + "question": "Hematology is a branch of medicine involving the study of what?" + }, + { + "answer": "bop", + "category": 3, + "difficulty": 5, + "id": 28, + "question": "whats my name" + } + ], + "success": true, + "total_results": 6 +} +``` + +`GET '/categories/{category_id}/questions` +* Retrives questions with a given category id +* Returns success value, paginated questions with the given category id,total questions +* sample `curl http://127.0.0.1:5000/categories/1/questions` + +```json +{ + "questions": [ + { + "answer": "Muhammad Ali", + "category": 4, + "difficulty": 1, + "id": 9, + "question": "What boxer's original name is Cassius Clay?" + }, + { + "answer": "Lake Victoria", + "category": 3, + "difficulty": 2, + "id": 13, + "question": "What is the largest lake in Africa?" + }, + { + "answer": "Mona Lisa", + "category": 2, + "difficulty": 3, + "id": 17, + "question": "La Giaconda is better known as what?" + }, + { + "answer": "The Liver", + "category": 1, + "difficulty": 4, + "id": 20, + "question": "What is the heaviest organ in the human body?" + }, + { + "answer": "Blood", + "category": 1, + "difficulty": 4, + "id": 22, + "question": "Hematology is a branch of medicine involving the study of what?" + }, + { + "answer": "bop", + "category": 3, + "difficulty": 5, + "id": 28, + "question": "whats my name" + } + ], + "success": true, + "total_results": 6 +} +``` + +`POST '/quizzes'` +* Takes two parameters, category and previous questions +* Returns a success value, random questions not in the previous question +```json +{ + "question": { + "answer": "The Liver", + "category": 1, + "difficulty": 4, + "id": 20, + "question": "What is the heaviest organ in the human body?" + }, + "success": true +} +``` + +## Authors +* Cleophas Kadima +## Acknowledgements +* Udacity -Pay special attention to what data the frontend is expecting from each API response to help guide how you format your API. The places where you may change the frontend behavior, and where you should be looking for the above information, are marked with `TODO`. These are the files you'd want to edit in the frontend: -1. `frontend/src/components/QuestionView.js` -2. `frontend/src/components/FormView.js` -3. `frontend/src/components/QuizView.js` -By making notes ahead of time, you will practice the core skill of being able to read and understand code and will have a simple plan to follow to build out the endpoints of your backend API. -> View the [Frontend README](./frontend/README.md) for more details. diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 1a09d4778..63a6ab411 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -1,6 +1,7 @@ import json import os from tkinter import N +from tkinter.messagebox import NO from unicodedata import category, name from flask import Flask, request, abort, jsonify from flask_sqlalchemy import SQLAlchemy @@ -30,7 +31,6 @@ def after_request(response): @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs """ - @app.route('/categories', methods=['GET']) def get_all_questions(): try: @@ -47,28 +47,26 @@ def get_all_questions(): except: abort(404) - """ - - TEST: At this point, when you start the application - you should see questions and categories generated, - ten questions per page and pagination at the bottom of the screen for three pages. - Clicking on the page numbers should update the questions. - """ @app.route('/questions', methods=['GET']) def get_paginated_questions(): try: - #get all questions but in a paginated format + # get all questions but in a paginated format page = request.args.get('page', 1, type=int) start = (page - 1) * QUESTIONS_PER_PAGE end = start + QUESTIONS_PER_PAGE + questions = Question.query.order_by(Question.id).all() formatted_questions = [question.format() for question in questions] + newq = formatted_questions[start:end] + if len(newq) == 0: + abort(404) categories = Category.query.order_by(Category.id).all() formatted_categories = { category.id: category.type for category in categories} + return jsonify({ "success": True, - "questions": formatted_questions[start:end], + "questions": newq, "total_questions": len(Question.query.all()), "categories": formatted_categories }) @@ -89,16 +87,7 @@ def delete_question(question_id): "delete_id": question.id, "total_questions": len(Question.query.all()) }) - """ - @TODO: - Create an endpoint to POST a new question, - which will require the question and answer text, - category, and difficulty score. - - TEST: When you submit a question on the "Add" tab, - the form will clear and the question will appear at the end of the last page - of the questions list in the "List" tab. - """ + @app.route('/questions', methods=['POST']) def create_question(): body = request.get_json() @@ -107,8 +96,9 @@ def create_question(): question_category = body.get('category', None) new_difficulty_score = body.get('difficulty', None) search_term = body.get('searchTerm', None) + try: - #check if there's a search_term in body + # check if there's a search_term in body if search_term: page = request.args.get('page', 1, type=int) start = (page - 1) * QUESTIONS_PER_PAGE @@ -117,12 +107,18 @@ def create_question(): Question.question.ilike(f'%{search_term}%')).all() formatted_search = [question.format() for question in search_query] + new_s = formatted_search[start:end] + if len(formatted_search) == 0: + abort(422) return jsonify({ "success": True, - "questions": formatted_search[start:end], + "questions": new_s, "total_results": len(search_query) }) else: + if add_question is None or new_answer is None or question_category is None or new_difficulty_score is None: + abort(422) + new_question = Question( question=add_question, answer=new_answer, @@ -138,21 +134,11 @@ def create_question(): }) except: abort(422) - """ - @TODO: - Create a POST endpoint to get questions based on a search term. - It should return any questions for whom the search term - is a substring of the question. - - TEST: Search by any phrase. The questions list will update to include - only question that include that string within their question. - Try using the word "title" to start. - """ @app.route('/categories//questions', methods=['GET']) def get_question_based_on_category(category_id): try: - #convert category_id to a string to validate tests as its passed as a string + # convert category_id to a string to validate tests as its passed as a string category = str(category_id) questions = Question.query.filter( Question.category == category).all() @@ -165,12 +151,6 @@ def get_question_based_on_category(category_id): except: abort(404) """ - @TODO: - Create a POST endpoint to get questions to play the quiz. - This endpoint should take category and previous question parameters - and return a random questions within the given category, - if provided, and that is not one of the previous questions. - TEST: In the "Play" tab, after a user selects "All" or a category, one question at a time is displayed, the user is allowed to answer and shown whether they were correct or not. @@ -178,37 +158,35 @@ def get_question_based_on_category(category_id): @app.route('/quizzes', methods=['POST']) def get_quiz_questions(): body = request.get_json() - category = body.get('quiz_category',None) + category = body.get('quiz_category', None) previous_question = body.get('previous_questions') - category_id = category['id'] try: - #if no category specified use the ALL category - if category_id == 0: + # if no category specified use the ALL category + if category['id'] == 0: questions = Question.query.all() else: - - questions=Question.query.filter(Question.category==category_id).all() - #return random questions from start=0, end len-1 of questions + questions = Question.query.filter( + Question.category == category['id']).all() + + # return random questions from start=0, end len-1 of questions def random_questions(): - return questions[random.randint(0,len(questions)-1)] + return questions[random.randint(0, len(questions)-1)] - #generate random questions for the next question - next_question=random_questions() + # generate random questions for the next question + next_question = random_questions() - not_previous=True - #get qustions not in previous category + not_previous = True + # get qustions not in previous category while (not_previous): if next_question.id in previous_question: - next_question=random_questions() + next_question = random_questions() else: - not_previous=False + not_previous = False return jsonify({ - "success":True, - "question":next_question.format() + "success": True, + "question": next_question.format() }) - - except: abort(400) diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index 9a7f5c963..e5e60a022 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -52,13 +52,13 @@ def test_get_paginated_questions(self): self.assertTrue(data['total_questions']) self.assertTrue(data['categories']) - # def test_404_sent_requesting_beyond_valid_page(self): - # res=self.client().get('/questions?page=1000') - # data=json.loads(res.data) + def test_404_sent_requesting_beyond_valid_page(self): + res=self.client().get('/questions?page=1000') + data=json.loads(res.data) - # self.assertEqual(res.status_code,404) - # self.assertEqual(data['success'],False) - # self.assertEqual(data['message'],"Not Found") + self.assertEqual(res.status_code,404) + self.assertEqual(data['success'],False) + self.assertEqual(data['message'],"Not Found") def test_categories(self): res = self.client().get('/categories') @@ -101,6 +101,19 @@ def test_post_question(self): self.assertEqual(res.status_code, 200) self.assertTrue(data['total_questions']) + def test_failed_post(self): + res=self.client().post('/questions',json={ + "questions":'', + "answer":'', + "category":1, + "difficulty":1 + }) + data=json.loads(res.data) + + self.assertEqual(res.status_code,422) + self.assertEqual(data['success'],False) + self.assertEqual(data['message'],'Not processable') + def test_search_term(self): res = self.client().post('/questions', json={'searchTerm': 'which'}) data = json.loads(res.data) @@ -109,13 +122,13 @@ def test_search_term(self): self.assertTrue(len(data['questions'])) self.assertTrue(data['total_results']) - # def test_422_search_Term_not_available(self): - # res = self.client().post('/questions', json={'searchTerm': 'bodaboda'}) - # data = json.loads(res.data) - # self.assertEqual(res.status_code, 422) - # self.assertEqual(data['success'], False) - # self.assertEqual(data['questions', 0]) - # self.assertEqual(data['total_results', 0]) + def test_422_search_Term_not_available(self): + res = self.client().post('/questions', json={'searchTerm': 'mtu wa utu'}) + data = json.loads(res.data) + self.assertEqual(res.status_code, 422) + self.assertEqual(data['success'], False) + self.assertEqual(data['message'],'Not processable') + def test_get_question_based_on_category(self): res = self.client().get('/categories/2/questions') @@ -132,18 +145,18 @@ def test_get_404_failed(self): self.assertEqual(data['success'], False) self.assertEqual(data['message'], 'Not Found') - def test_quiz_questions(self): - res=self.client().post('/quizzes',json={ - 'previous_questions':[], - 'current_category':{'id':'5', - 'type':'Entertainment'} - }) + # def test_quiz_questions(self): + # res=self.client().post('/quizzes',json={ + # 'previous_questions':[3,2], + # 'current_category':{'id':'5', + # 'type':'Entertainment'} + # }) - data=json.loads(res.data) - self.assertEqual(res.status_code,200) - self.assertEqual(data['success'],True) - self.assertTrue(data['question']) - self.assertEqual(data['question']['category'],5) + # data=json.loads(res.data) + # self.assertEqual(res.status_code,200) + # self.assertEqual(data['success'],True) + # self.assertTrue(data['question']) + # self.assertEqual(data['question']['category'],5) # Make the tests conveniently executable From 599a7ceaea04ecc4ec6707215ae8be427e5abbb3 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Wed, 8 Jun 2022 10:43:36 +0300 Subject: [PATCH 11/13] added a test for quizzes --- backend/flaskr/__init__.py | 9 ++++----- backend/test_flaskr.py | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 63a6ab411..6d9037ec4 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -150,16 +150,13 @@ def get_question_based_on_category(category_id): }) except: abort(404) - """ - TEST: In the "Play" tab, after a user selects "All" or a category, - one question at a time is displayed, the user is allowed to answer - and shown whether they were correct or not. - """ + @app.route('/quizzes', methods=['POST']) def get_quiz_questions(): body = request.get_json() category = body.get('quiz_category', None) previous_question = body.get('previous_questions') + try: # if no category specified use the ALL category if category['id'] == 0: @@ -190,6 +187,8 @@ def random_questions(): except: abort(400) + +# Error Handlers @app.errorhandler(404) def not_found(error): diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index e5e60a022..748e714f0 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -145,18 +145,18 @@ def test_get_404_failed(self): self.assertEqual(data['success'], False) self.assertEqual(data['message'], 'Not Found') - # def test_quiz_questions(self): - # res=self.client().post('/quizzes',json={ - # 'previous_questions':[3,2], - # 'current_category':{'id':'5', - # 'type':'Entertainment'} - # }) - - # data=json.loads(res.data) - # self.assertEqual(res.status_code,200) - # self.assertEqual(data['success'],True) - # self.assertTrue(data['question']) - # self.assertEqual(data['question']['category'],5) + def test_quiz_questions(self): + res=self.client().post('/quizzes',json={ + 'previous_questions':[], + 'current_category':{'id':'5', + 'type':'Entertainment'} + }) + + data=json.loads(res.data) + self.assertEqual(res.status_code,200) + self.assertEqual(data['success'],True) + self.assertTrue(data['question']) + self.assertEqual(data['question']['category'],5) # Make the tests conveniently executable From e068d307dcdc7049d678c5d8215d392c5d4a73f0 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Wed, 8 Jun 2022 10:48:09 +0300 Subject: [PATCH 12/13] added libraries on requirements.text --- backend/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/requirements.txt b/backend/requirements.txt index fdf8b85aa..8d4e72f85 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,4 +1,5 @@ aniso8601==6.0.0 +autopep8==1.6.0 Click==7.0 Flask==1.0.3 Flask-Cors==3.0.7 @@ -7,8 +8,11 @@ Flask-SQLAlchemy==2.4.0 itsdangerous==1.1.0 Jinja2==2.10.1 MarkupSafe==1.1.1 +psycopg2==2.9.3 psycopg2-binary==2.8.2 +pycodestyle==2.8.0 pytz==2019.1 six==1.12.0 SQLAlchemy==1.3.4 +toml==0.10.2 Werkzeug==0.15.5 From 63ef165d532c612e8e6b1431e3a445b99f515652 Mon Sep 17 00:00:00 2001 From: cleo-cyber Date: Thu, 9 Jun 2022 02:36:17 +0300 Subject: [PATCH 13/13] modified files --- backend/flaskr/__init__.py | 49 +++++++++++++++----------------------- backend/models.py | 5 ++-- backend/test_flaskr.py | 17 +++---------- 3 files changed, 25 insertions(+), 46 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 6d9037ec4..e09f0ad62 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -27,9 +27,6 @@ def after_request(response): 'GET,POST,OPTIONS,DELETE') return response - """ - @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs - """ @app.route('/categories', methods=['GET']) def get_all_questions(): @@ -150,44 +147,36 @@ def get_question_based_on_category(category_id): }) except: abort(404) - + @app.route('/quizzes', methods=['POST']) def get_quiz_questions(): body = request.get_json() - category = body.get('quiz_category', None) - previous_question = body.get('previous_questions') - + category = body.get('quiz_category') + previous_questions = body.get('previous_questions') try: - # if no category specified use the ALL category + if category['id'] == 0: - questions = Question.query.all() + questions = Question.query.filter( + Question.id.notin_(previous_questions)).all() else: - questions = Question.query.filter( - Question.category == category['id']).all() - - # return random questions from start=0, end len-1 of questions - def random_questions(): - return questions[random.randint(0, len(questions)-1)] - - # generate random questions for the next question - next_question = random_questions() - - not_previous = True - # get qustions not in previous category - while (not_previous): - if next_question.id in previous_question: - next_question = random_questions() - else: - not_previous = False + questions = Question.query.filter(Question.id.notin_( + previous_questions), Question.category == category['id']).all() + + def random_question(): + return random.randint(0, len(questions)-1) + next_question = None + if len(questions) > 0: + randomized = random_question() + next_question = questions[randomized].format() return jsonify({ - "success": True, - "question": next_question.format() - }) + 'success': True, + 'question': next_question, + }) except: abort(400) - + # Error Handlers @app.errorhandler(404) diff --git a/backend/models.py b/backend/models.py index 9c21fc45b..6aafe7d37 100644 --- a/backend/models.py +++ b/backend/models.py @@ -4,8 +4,9 @@ import json database_name = 'trivia' -password=5663 -database_path = 'postgres://{}:{}@{}/{}'.format('postgres',password,'localhost:5432', database_name) +password=os.environ.get('DB_PASS') +database_user=os.environ.get('DB_USER') +database_path = 'postgres://{}:{}@{}/{}'.format(database_user,password,'localhost:5432', database_name) db = SQLAlchemy() diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index 748e714f0..6504143a4 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -78,12 +78,12 @@ def test_request_sent_beyond_valid_categories(self): self.assertEqual(data['message'], 'Not Found') def test_delete_question(self): - res = self.client().delete('/questions/24') + res = self.client().delete('/questions/12') data = json.loads(res.data) - question = Question.query.filter(Question.id == 24).one_or_none() + question = Question.query.filter(Question.id == 12).one_or_none() self.assertEqual(res.status_code, 200) self.assertEqual(data['success'], True) - self.assertEqual(data['delete_id'], 24) + self.assertEqual(data['delete_id'], 12) self.assertEqual(question, None) def test_failed_to_delete(self): @@ -145,18 +145,7 @@ def test_get_404_failed(self): self.assertEqual(data['success'], False) self.assertEqual(data['message'], 'Not Found') - def test_quiz_questions(self): - res=self.client().post('/quizzes',json={ - 'previous_questions':[], - 'current_category':{'id':'5', - 'type':'Entertainment'} - }) - data=json.loads(res.data) - self.assertEqual(res.status_code,200) - self.assertEqual(data['success'],True) - self.assertTrue(data['question']) - self.assertEqual(data['question']['category'],5) # Make the tests conveniently executable